mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-15 08:25:10 +08:00
update 升级全部依赖至最新版本并适配
- react/react-dom 升级 19.3.0 应用了 useEffectEvent 新特性(TypeScript 保持 6.x) - 使用 @noble/ciphers 替换已废弃的 crypto-js 保持与后端 AES 加解密兼容 - @wangeditor-next 5->6 升级(editor/editor-for-react 6.4.1) - antd/@ant-design/icons/@ant-design/x/ahooks/zustand/axios 等升级至最新 - 适配 oxlint 1.82 新规则(refs/set-state-in-effect/immutability/exhaustive-effect-dependencies) - 适配 axios 1.20 类型变化 使用 useEffectEvent 重构弹窗初始化逻辑
This commit is contained in:
parent
29fc02f0a6
commit
b56ebf5ae6
39
package.json
39
package.json
@ -19,46 +19,45 @@
|
|||||||
"start": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev"
|
"start": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.3.2",
|
"@ant-design/icons": "^6.3.4",
|
||||||
"@ant-design/pro-components": "3.1.12-0",
|
"@ant-design/pro-components": "3.1.12-0",
|
||||||
"@ant-design/x": "^2.8.0",
|
"@ant-design/x": "^2.9.0",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@noble/ciphers": "^2.4.0",
|
||||||
"@umijs/max": "^4.6.82",
|
"@tanstack/react-query": "^5.102.8",
|
||||||
"@wangeditor-next/editor": "^5.7.16",
|
"@umijs/max": "^4.7.16",
|
||||||
"@wangeditor-next/editor-for-react": "^2.0.3",
|
"@wangeditor-next/editor": "^6.4.1",
|
||||||
"ahooks": "^3.9.7",
|
"@wangeditor-next/editor-for-react": "^6.4.1",
|
||||||
"antd": "^6.5.2",
|
"ahooks": "^3.10.0",
|
||||||
|
"antd": "^6.6.3",
|
||||||
"antd-style": "^4.1.0",
|
"antd-style": "^4.1.0",
|
||||||
"axios": "1.18.1",
|
"axios": "1.20.0",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "4.2.0",
|
"dayjs": "^1.11.23",
|
||||||
"dayjs": "^1.11.21",
|
|
||||||
"echarts": "^6.1.0",
|
"echarts": "^6.1.0",
|
||||||
"echarts-for-react": "^3.0.6",
|
"echarts-for-react": "^3.0.6",
|
||||||
"jsencrypt": "3.5.4",
|
"jsencrypt": "3.5.4",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"react": "^19.2.8",
|
"react": "^19.3.0",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.3.0",
|
||||||
"react-easy-crop": "^6.2.3",
|
"react-easy-crop": "^6.2.3",
|
||||||
"react-json-view-lite": "^2.5.0",
|
"react-json-view-lite": "^2.5.0",
|
||||||
"react-syntax-highlighter": "^16.1.1",
|
"react-syntax-highlighter": "^16.1.1",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"zustand": "^5.0.14"
|
"zustand": "^5.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/crypto-js": "4.2.2",
|
|
||||||
"@types/node": "^26.1.1",
|
"@types/node": "^26.1.1",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.3.0",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.3.0",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@umijs/lint": "^4.6.82",
|
"@umijs/lint": "^4.7.16",
|
||||||
"@umijs/max-plugin-openapi": "^2.0.3",
|
"@umijs/max-plugin-openapi": "^2.0.3",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"oxfmt": "^0.60.0",
|
"oxfmt": "^0.67.0",
|
||||||
"oxlint": "^1.75.0",
|
"oxlint": "^1.82.0",
|
||||||
"typescript": "^6.0.3"
|
"typescript": "^6.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
3426
pnpm-lock.yaml
generated
3426
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -240,5 +240,6 @@ service.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function request<T = unknown, D = unknown>(config: RequestConfig<D>): Promise<T> {
|
export default function request<T = unknown, D = unknown>(config: RequestConfig<D>): Promise<T> {
|
||||||
return service.request<T, T, D>(config);
|
// axios 1.20 泛型返回为条件类型 AxiosResponseResult 需要断言 响应拦截器已返回 response.data
|
||||||
|
return service.request<T, T, D>(config) as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { DataNode, EventDataNode } from 'antd/es/tree';
|
import type { DataNode, EventDataNode } from 'antd/es/tree';
|
||||||
import { LeftOutlined, SearchOutlined } from '@ant-design/icons';
|
import { LeftOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { Button, Card, Input, Tree } from 'antd';
|
import { Button, Card, Input, Tree } from 'antd';
|
||||||
import { useEffect, useMemo, useState, type Key } from 'react';
|
import { useMemo, useState, type Key } from 'react';
|
||||||
import { collectTreeKeys } from '@/utils/ruoyi';
|
import { collectTreeKeys } from '@/utils/ruoyi';
|
||||||
|
|
||||||
export interface TreePanelProps<T extends object = Record<string, unknown>> {
|
export interface TreePanelProps<T extends object = Record<string, unknown>> {
|
||||||
@ -94,16 +94,12 @@ export default function TreePanel<T extends object = Record<string, unknown>>({
|
|||||||
);
|
);
|
||||||
const allTreeKeys = useMemo(() => collectTreeKeys(treeData, node => node.key), [treeData]);
|
const allTreeKeys = useMemo(() => collectTreeKeys(treeData, node => node.key), [treeData]);
|
||||||
const mergedCollapsed = collapsed ?? innerCollapsed;
|
const mergedCollapsed = collapsed ?? innerCollapsed;
|
||||||
|
// 数据变化时默认展开全部 通过渲染期对比 prev 派生 而非 effect 中 setState
|
||||||
useEffect(() => {
|
const [prevAllTreeKeys, setPrevAllTreeKeys] = useState(allTreeKeys);
|
||||||
if (collapsed !== undefined) {
|
if (allTreeKeys !== prevAllTreeKeys) {
|
||||||
setInnerCollapsed(collapsed);
|
setPrevAllTreeKeys(allTreeKeys);
|
||||||
}
|
|
||||||
}, [collapsed]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setExpandedKeys(allTreeKeys);
|
setExpandedKeys(allTreeKeys);
|
||||||
}, [allTreeKeys]);
|
}
|
||||||
|
|
||||||
const toggleCollapsed = () => {
|
const toggleCollapsed = () => {
|
||||||
const nextCollapsed = !mergedCollapsed;
|
const nextCollapsed = !mergedCollapsed;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { DataNode } from 'antd/es/tree';
|
|||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { useBoolean, useRequest } from 'ahooks';
|
import { useBoolean, useRequest } from 'ahooks';
|
||||||
import { Button, Form, Input, Modal, Space, Table, Tag, Tree } from 'antd';
|
import { Button, Form, Input, Modal, Space, Table, Tag, Tree } from 'antd';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useEffectEvent, useMemo, useState } from 'react';
|
||||||
import type { DeptTreeVO } from '@/api/system/dept/types';
|
import type { DeptTreeVO } from '@/api/system/dept/types';
|
||||||
import type { UserQuery, UserVO } from '@/api/system/user/types';
|
import type { UserQuery, UserVO } from '@/api/system/user/types';
|
||||||
import { deptTreeSelect, listUser, optionSelect } from '@/api/system/user';
|
import { deptTreeSelect, listUser, optionSelect } from '@/api/system/user';
|
||||||
@ -98,23 +98,38 @@ export default function UserSelect({
|
|||||||
setSelectedUsers(res.data || []);
|
setSelectedUsers(res.data || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: modal initialization is intentionally tied to open state.
|
// useEffectEvent 读取最新的 value/data/query 等非响应式逻辑 只在 open 变化时执行
|
||||||
useEffect(() => {
|
const initModal = useEffectEvent(() => {
|
||||||
if (!open) {
|
|
||||||
form.resetFields();
|
|
||||||
setQuery({ pageNum: 1, pageSize: 10 });
|
|
||||||
setUsers([]);
|
|
||||||
setTotal(0);
|
|
||||||
setSelectedUsers([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
deptTreeSelect().then(res => setDeptOptions(res.data || [])),
|
deptTreeSelect().then(res => setDeptOptions(res.data || [])),
|
||||||
loadUsers({ pageNum: 1, pageSize: 10 }),
|
loadUsers({ pageNum: 1, pageSize: 10 }),
|
||||||
initSelectedUsers()
|
initSelectedUsers()
|
||||||
]);
|
]);
|
||||||
}, [open]);
|
});
|
||||||
|
|
||||||
|
// 关闭时重置弹窗状态
|
||||||
|
const resetModal = useEffectEvent(() => {
|
||||||
|
form.resetFields();
|
||||||
|
setQuery({ pageNum: 1, pageSize: 10 });
|
||||||
|
setUsers([]);
|
||||||
|
setTotal(0);
|
||||||
|
setSelectedUsers([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
// useEffectEvent 在 effect 中调用是 React 官方推荐用法
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
|
resetModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// useEffectEvent 在 effect 中调用是 React 官方推荐用法
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
|
initModal();
|
||||||
|
// initModal/resetModal 为 useEffectEvent 引用稳定 无需作为依赖
|
||||||
|
// oxlint-disable-next-line react/exhaustive-effect-dependencies
|
||||||
|
}, [initModal, open, resetModal]);
|
||||||
|
|
||||||
const handleQuery = async () => {
|
const handleQuery = async () => {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { CheckOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons';
|
import { CheckOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
import { Button, Divider, Drawer, Input, message, Segmented, Slider, Space, Switch } from 'antd';
|
import { Button, Divider, Drawer, Input, message, Segmented, Slider, Space, Switch } from 'antd';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useState } from 'react';
|
||||||
import { defaultLayoutSettings, useAppStore, type LayoutSettingsValue, type NavLayout } from '@/stores/appStore';
|
import { defaultLayoutSettings, useAppStore, type LayoutSettingsValue, type NavLayout } from '@/stores/appStore';
|
||||||
|
|
||||||
interface LayoutSettingsProps {
|
interface LayoutSettingsProps {
|
||||||
@ -21,22 +21,17 @@ function SettingRow({ label, children }: { label: string; children: React.ReactN
|
|||||||
|
|
||||||
export default function LayoutSettings({ open, value, onChange, onOpenChange }: LayoutSettingsProps) {
|
export default function LayoutSettings({ open, value, onChange, onOpenChange }: LayoutSettingsProps) {
|
||||||
const resetLayoutSettings = useAppStore(state => state.resetLayoutSettings);
|
const resetLayoutSettings = useAppStore(state => state.resetLayoutSettings);
|
||||||
const preferredSideThemeRef = useRef(value.sideTheme);
|
// 记住非暗色模式下选中的主题 仅在事件回调中更新 避免渲染期读取 ref
|
||||||
|
const [preferredSideTheme, setPreferredSideTheme] = useState(value.sideTheme);
|
||||||
useEffect(() => {
|
|
||||||
if (!value.darkMode) {
|
|
||||||
preferredSideThemeRef.current = value.sideTheme;
|
|
||||||
}
|
|
||||||
}, [value.darkMode, value.sideTheme]);
|
|
||||||
|
|
||||||
const patchValue = (patch: Partial<LayoutSettingsValue>) => {
|
const patchValue = (patch: Partial<LayoutSettingsValue>) => {
|
||||||
if (patch.sideTheme) {
|
if (patch.sideTheme) {
|
||||||
preferredSideThemeRef.current = patch.sideTheme;
|
setPreferredSideTheme(patch.sideTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = { ...value, ...patch };
|
const next = { ...value, ...patch };
|
||||||
if (patch.darkMode === false) {
|
if (patch.darkMode === false) {
|
||||||
next.sideTheme = preferredSideThemeRef.current;
|
next.sideTheme = preferredSideTheme;
|
||||||
}
|
}
|
||||||
if (patch.tagsView === false) {
|
if (patch.tagsView === false) {
|
||||||
next.tagsViewPersist = false;
|
next.tagsViewPersist = false;
|
||||||
@ -46,10 +41,11 @@ export default function LayoutSettings({ open, value, onChange, onOpenChange }:
|
|||||||
|
|
||||||
const resetSettings = () => {
|
const resetSettings = () => {
|
||||||
resetLayoutSettings();
|
resetLayoutSettings();
|
||||||
|
setPreferredSideTheme(defaultLayoutSettings.sideTheme);
|
||||||
onChange(defaultLayoutSettings);
|
onChange(defaultLayoutSettings);
|
||||||
message.success('配置已重置');
|
message.success('配置已重置');
|
||||||
};
|
};
|
||||||
const selectedSideTheme = value.darkMode ? preferredSideThemeRef.current : value.sideTheme;
|
const selectedSideTheme = value.darkMode ? preferredSideTheme : value.sideTheme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { DownloadOutlined } from '@ant-design/icons';
|
|||||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { Button, Modal, Popover, Spin, Table, Tabs } from 'antd';
|
import { Button, Modal, Popover, Spin, Table, Tabs } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { OssVO } from '@/api/system/oss/types';
|
import type { OssVO } from '@/api/system/oss/types';
|
||||||
import type { FlowHistoryVO } from '@/api/workflow/task/types';
|
import type { FlowHistoryVO } from '@/api/workflow/task/types';
|
||||||
import { downloadOss, listByIds } from '@/api/system/oss';
|
import { downloadOss, listByIds } from '@/api/system/oss';
|
||||||
@ -52,19 +52,21 @@ export default function ApprovalRecord({ businessId, open, onOpenChange, width =
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
// 打开时清空旧数据并加载 打开弹窗同步外部状态属于合理场景
|
||||||
if (!open || !businessId) {
|
const resetAndLoad = useCallback(async (nextBusinessId?: string | number) => {
|
||||||
if (!open) {
|
|
||||||
setRecordList([]);
|
|
||||||
setInstanceId(undefined);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRecordList([]);
|
setRecordList([]);
|
||||||
setInstanceId(undefined);
|
setInstanceId(undefined);
|
||||||
loadRecords(businessId);
|
if (nextBusinessId) {
|
||||||
}, [businessId, loadRecords, open]);
|
await loadRecords(nextBusinessId);
|
||||||
|
}
|
||||||
|
}, [loadRecords]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
// 打开弹窗时同步清空旧数据属于与外部状态对齐的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
|
void resetAndLoad(businessId);
|
||||||
|
}, [businessId, open, resetAndLoad]);
|
||||||
|
|
||||||
const downloadAttachment = async (row: OssVO) => {
|
const downloadAttachment = async (row: OssVO) => {
|
||||||
const blob = await downloadOss(row.ossId);
|
const blob = await downloadOss(row.ossId);
|
||||||
|
|||||||
@ -10,19 +10,21 @@ const maxScale = 3;
|
|||||||
|
|
||||||
export default function FlowChartImage({ imgUrl }: FlowChartImageProps) {
|
export default function FlowChartImage({ imgUrl }: FlowChartImageProps) {
|
||||||
const wrapperRef = useRef<HTMLButtonElement>(null);
|
const wrapperRef = useRef<HTMLButtonElement>(null);
|
||||||
const dragRef = useRef({ dragging: false, x: 0, y: 0 });
|
const dragRef = useRef({ x: 0, y: 0 });
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
const [translate, setTranslate] = useState({ x: 0, y: 0 });
|
const [translate, setTranslate] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
const transformStyle = useMemo(
|
const transformStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
transform: `translate(${translate.x}px, ${translate.y}px) scale(${scale})`,
|
transform: `translate(${translate.x}px, ${translate.y}px) scale(${scale})`,
|
||||||
transition: dragRef.current.dragging ? 'none' : 'transform 0.2s ease'
|
transition: dragging ? 'none' : 'transform 0.2s ease'
|
||||||
}),
|
}),
|
||||||
[scale, translate]
|
[dragging, scale, translate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetTransform = () => {
|
const resetTransform = () => {
|
||||||
|
setDragging(false);
|
||||||
setScale(1);
|
setScale(1);
|
||||||
setTranslate({ x: 0, y: 0 });
|
setTranslate({ x: 0, y: 0 });
|
||||||
};
|
};
|
||||||
@ -37,19 +39,20 @@ export default function FlowChartImage({ imgUrl }: FlowChartImageProps) {
|
|||||||
const handleMouseDown = (event: MouseEvent<HTMLButtonElement>) => {
|
const handleMouseDown = (event: MouseEvent<HTMLButtonElement>) => {
|
||||||
if (scale <= 1) return;
|
if (scale <= 1) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dragRef.current = { dragging: true, x: event.clientX, y: event.clientY };
|
dragRef.current = { x: event.clientX, y: event.clientY };
|
||||||
|
setDragging(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (event: MouseEvent<HTMLButtonElement>) => {
|
const handleMouseMove = (event: MouseEvent<HTMLButtonElement>) => {
|
||||||
if (!dragRef.current.dragging) return;
|
if (!dragging) return;
|
||||||
const deltaX = event.clientX - dragRef.current.x;
|
const deltaX = event.clientX - dragRef.current.x;
|
||||||
const deltaY = event.clientY - dragRef.current.y;
|
const deltaY = event.clientY - dragRef.current.y;
|
||||||
dragRef.current = { dragging: true, x: event.clientX, y: event.clientY };
|
dragRef.current = { x: event.clientX, y: event.clientY };
|
||||||
setTranslate(prev => ({ x: prev.x + deltaX, y: prev.y + deltaY }));
|
setTranslate(prev => ({ x: prev.x + deltaX, y: prev.y + deltaY }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopDrag = () => {
|
const stopDrag = () => {
|
||||||
dragRef.current.dragging = false;
|
setDragging(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!imgUrl) {
|
if (!imgUrl) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useBoolean } from 'ahooks';
|
import { useBoolean } from 'ahooks';
|
||||||
import { Button, Form, Input, message, Modal, Select, Space, Table, Tag } from 'antd';
|
import { Button, Form, Input, message, Modal, Select, Space, Table, Tag } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useEffectEvent, useState } from 'react';
|
||||||
import type { UserVO } from '@/api/system/user/types';
|
import type { UserVO } from '@/api/system/user/types';
|
||||||
import type { FlowNextNodeVO, FlowTaskVO } from '@/api/workflow/task/types';
|
import type { FlowNextNodeVO, FlowTaskVO } from '@/api/workflow/task/types';
|
||||||
import {
|
import {
|
||||||
@ -72,29 +72,31 @@ export default function SubmitVerify({ open, taskId, variables = emptyVariables,
|
|||||||
const [signatureOpen, { setTrue: openSignatureModal, setFalse: closeSignatureModal }] = useBoolean(false);
|
const [signatureOpen, { setTrue: openSignatureModal, setFalse: closeSignatureModal }] = useBoolean(false);
|
||||||
const [signatureUsers, setSignatureUsers] = useState<SignatureUser[]>([]);
|
const [signatureUsers, setSignatureUsers] = useState<SignatureUser[]>([]);
|
||||||
|
|
||||||
|
// useEffectEvent 让 variables 始终读到最新值 避免其引用变化触发重复加载
|
||||||
|
const loadTask = useEffectEvent(async (taskId: string | number) => {
|
||||||
|
setApprovalLoading(true);
|
||||||
|
approveForm.setFieldsValue({ message: '', messageType: ['1'], assigneeMap: {}, fileId: undefined });
|
||||||
|
setCopyUsers([]);
|
||||||
|
setAssigneeNames({});
|
||||||
|
setCurrentTask(undefined);
|
||||||
|
setNextNodes([]);
|
||||||
|
try {
|
||||||
|
const taskRes = await getTask(taskId);
|
||||||
|
setCurrentTask(taskRes.data);
|
||||||
|
setCopyUsers((taskRes.data.copyList || []).map(user => ({ userId: user.userId, nickName: user.nickName })));
|
||||||
|
const nextRes = await getNextNodeList({ taskId, variables });
|
||||||
|
setNextNodes(nextRes.data || []);
|
||||||
|
} finally {
|
||||||
|
setApprovalLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !taskId) return;
|
if (!open || !taskId) return;
|
||||||
|
// useEffectEvent 在 effect 中调用是 React 官方推荐用法
|
||||||
const loadTask = async () => {
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
setApprovalLoading(true);
|
loadTask(taskId);
|
||||||
approveForm.setFieldsValue({ message: '', messageType: ['1'], assigneeMap: {}, fileId: undefined });
|
}, [open, taskId]);
|
||||||
setCopyUsers([]);
|
|
||||||
setAssigneeNames({});
|
|
||||||
setCurrentTask(undefined);
|
|
||||||
setNextNodes([]);
|
|
||||||
try {
|
|
||||||
const taskRes = await getTask(taskId);
|
|
||||||
setCurrentTask(taskRes.data);
|
|
||||||
setCopyUsers((taskRes.data.copyList || []).map(user => ({ userId: user.userId, nickName: user.nickName })));
|
|
||||||
const nextRes = await getNextNodeList({ taskId, variables });
|
|
||||||
setNextNodes(nextRes.data || []);
|
|
||||||
} finally {
|
|
||||||
setApprovalLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadTask();
|
|
||||||
}, [approveForm, open, taskId, variables]);
|
|
||||||
|
|
||||||
const closeApprovalModal = () => {
|
const closeApprovalModal = () => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
|
|||||||
@ -34,8 +34,8 @@ export function useTableScroll(options: TableScrollX | TableScrollOptions) {
|
|||||||
|
|
||||||
const refreshTableScroll = useCallback(() => {
|
const refreshTableScroll = useCallback(() => {
|
||||||
if (!fullHeightTable || disabled || typeof window === 'undefined') {
|
if (!fullHeightTable || disabled || typeof window === 'undefined') {
|
||||||
|
// tableY 仅在启用时参与 memo 计算 这里无需同步重置
|
||||||
clearTableBodyHeight();
|
clearTableBodyHeight();
|
||||||
setTableY(undefined);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,11 +14,10 @@ function buildChatUrl(openId: string, trustedCredential: string) {
|
|||||||
export default function AiChatPage() {
|
export default function AiChatPage() {
|
||||||
const [chatUrl, setChatUrl] = useState('');
|
const [chatUrl, setChatUrl] = useState('');
|
||||||
const [loadError, setLoadError] = useState('');
|
const [loadError, setLoadError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
// 初始即为加载中 effect 首次触发时无需同步 setState
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const loadChat = useCallback(async () => {
|
const loadChat = useCallback(async () => {
|
||||||
setLoading(true);
|
|
||||||
setLoadError('');
|
|
||||||
try {
|
try {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@ -44,6 +43,8 @@ export default function AiChatPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 挂载时加载 AI 聊天地址属于与外部系统同步的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
loadChat();
|
loadChat();
|
||||||
}, [loadChat]);
|
}, [loadChat]);
|
||||||
|
|
||||||
@ -60,7 +61,15 @@ export default function AiChatPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Empty className="ai-chat-empty" description={loadError || '正在加载 Snail AI'}>
|
<Empty className="ai-chat-empty" description={loadError || '正在加载 Snail AI'}>
|
||||||
{loadError ? (
|
{loadError ? (
|
||||||
<Button type="primary" icon={<ReloadOutlined />} onClick={loadChat}>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(true);
|
||||||
|
setLoadError('');
|
||||||
|
loadChat();
|
||||||
|
}}
|
||||||
|
>
|
||||||
重新加载
|
重新加载
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -124,6 +124,8 @@ export default function Login() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getToken()) return;
|
if (getToken()) return;
|
||||||
|
// 挂载时加载验证码属于与外部系统同步的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
loadCaptcha().catch(() => {
|
loadCaptcha().catch(() => {
|
||||||
setCaptcha({ captchaEnabled: false });
|
setCaptcha({ captchaEnabled: false });
|
||||||
});
|
});
|
||||||
@ -135,7 +137,7 @@ export default function Login() {
|
|||||||
|
|
||||||
const doSocialLogin = async (source: string) => {
|
const doSocialLogin = async (source: string) => {
|
||||||
const res = await authRouterUrl(source);
|
const res = await authRouterUrl(source);
|
||||||
window.location.href = res.data;
|
window.location.assign(res.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -83,6 +83,8 @@ export default function Register() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 挂载时加载验证码属于与外部系统同步的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
loadCaptcha().catch(() => setCaptcha({ captchaEnabled: false }));
|
loadCaptcha().catch(() => setCaptcha({ captchaEnabled: false }));
|
||||||
}, [loadCaptcha]);
|
}, [loadCaptcha]);
|
||||||
|
|
||||||
|
|||||||
@ -123,6 +123,8 @@ export default function SystemNoticePage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const noticeId = new URLSearchParams(location.search).get('noticeId');
|
const noticeId = new URLSearchParams(location.search).get('noticeId');
|
||||||
if (noticeId) {
|
if (noticeId) {
|
||||||
|
// URL 深链同步打开公告详情属于与外部状态对齐的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
openDetail(noticeId);
|
openDetail(noticeId);
|
||||||
}
|
}
|
||||||
}, [location.search, openDetail]);
|
}, [location.search, openDetail]);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { DataNode } from 'antd/es/tree';
|
import type { DataNode } from 'antd/es/tree';
|
||||||
import { useBoolean } from 'ahooks';
|
import { useBoolean } from 'ahooks';
|
||||||
import { Checkbox, message, type FormInstance } from 'antd';
|
import { Checkbox, message, type FormInstance } from 'antd';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useEffectEvent, useMemo, useState } from 'react';
|
||||||
import type { DeptTreeVO } from '@/api/system/dept/types';
|
import type { DeptTreeVO } from '@/api/system/dept/types';
|
||||||
import type { MenuTreeOption } from '@/api/system/menu/types';
|
import type { MenuTreeOption } from '@/api/system/menu/types';
|
||||||
import type { RoleForm } from '@/api/system/role/types';
|
import type { RoleForm } from '@/api/system/role/types';
|
||||||
@ -104,39 +104,37 @@ export function useRolePermission({ open, roleId, form, onSuccess }: UseRolePerm
|
|||||||
buttonAllKeys.every(key => hasId(buttonCheckedKeys, key));
|
buttonAllKeys.every(key => hasId(buttonCheckedKeys, key));
|
||||||
const deptAllChecked = deptAllKeys.length > 0 && deptAllKeys.every(key => deptCheckedKeys.includes(key));
|
const deptAllChecked = deptAllKeys.length > 0 && deptAllKeys.every(key => deptCheckedKeys.includes(key));
|
||||||
|
|
||||||
|
// useEffectEvent 隔离 form 实例等非响应式逻辑 依赖项只保留 open/roleId
|
||||||
|
const loadPermission = useEffectEvent(async (roleId: string | number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setActiveTab('menu');
|
||||||
|
try {
|
||||||
|
const [roleRes, menuRes, deptRes] = await Promise.all([getRole(roleId), roleMenuTreeselect(roleId), deptTreeSelect(roleId)]);
|
||||||
|
form.setFieldsValue({
|
||||||
|
...defaultRoleForm,
|
||||||
|
...roleRes.data
|
||||||
|
});
|
||||||
|
setMenuConnect(roleRes.data.menuCheckStrictly ?? true);
|
||||||
|
setDeptConnect(roleRes.data.deptCheckStrictly ?? true);
|
||||||
|
const nextMenuTree = menuRes.data.menus || [];
|
||||||
|
const nextMenuMeta = buildMenuPermissionMeta(nextMenuTree);
|
||||||
|
const checkedKeys = menuRes.data.checkedKeys || [];
|
||||||
|
setMenuTree(nextMenuTree);
|
||||||
|
setDeptTree(deptRes.data.depts || []);
|
||||||
|
setMenuCheckedKeys(checkedKeys.filter(key => !hasId(nextMenuMeta.buttonIds, key)));
|
||||||
|
setButtonCheckedKeys(checkedKeys.filter(key => hasId(nextMenuMeta.buttonIds, key)));
|
||||||
|
setDeptCheckedKeys(deptRes.data.checkedKeys || []);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !roleId) return;
|
if (!open || !roleId) return;
|
||||||
|
// useEffectEvent 在 effect 中调用是 React 官方推荐用法
|
||||||
const loadPermission = async () => {
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
setLoading(true);
|
loadPermission(roleId);
|
||||||
setActiveTab('menu');
|
}, [open, roleId]);
|
||||||
try {
|
|
||||||
const [roleRes, menuRes, deptRes] = await Promise.all([
|
|
||||||
getRole(roleId),
|
|
||||||
roleMenuTreeselect(roleId),
|
|
||||||
deptTreeSelect(roleId)
|
|
||||||
]);
|
|
||||||
form.setFieldsValue({
|
|
||||||
...defaultRoleForm,
|
|
||||||
...roleRes.data
|
|
||||||
});
|
|
||||||
setMenuConnect(roleRes.data.menuCheckStrictly ?? true);
|
|
||||||
setDeptConnect(roleRes.data.deptCheckStrictly ?? true);
|
|
||||||
const nextMenuTree = menuRes.data.menus || [];
|
|
||||||
const nextMenuMeta = buildMenuPermissionMeta(nextMenuTree);
|
|
||||||
const checkedKeys = menuRes.data.checkedKeys || [];
|
|
||||||
setMenuTree(nextMenuTree);
|
|
||||||
setDeptTree(deptRes.data.depts || []);
|
|
||||||
setMenuCheckedKeys(checkedKeys.filter(key => !hasId(nextMenuMeta.buttonIds, key)));
|
|
||||||
setButtonCheckedKeys(checkedKeys.filter(key => hasId(nextMenuMeta.buttonIds, key)));
|
|
||||||
setDeptCheckedKeys(deptRes.data.checkedKeys || []);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadPermission();
|
|
||||||
}, [form, open, roleId]);
|
|
||||||
|
|
||||||
const resetPermission = () => {
|
const resetPermission = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
|||||||
@ -27,14 +27,20 @@ export default function UserAuthRolePage() {
|
|||||||
const [roleIds, setRoleIds] = useState<Array<string | number>>([]);
|
const [roleIds, setRoleIds] = useState<Array<string | number>>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
const loadAuthRole = async () => {
|
||||||
getAuthRole(userId)
|
setLoading(true);
|
||||||
.then(res => {
|
try {
|
||||||
|
const res = await getAuthRole(userId);
|
||||||
setUser(res.data.user);
|
setUser(res.data.user);
|
||||||
setRoles(res.data.roles || []);
|
setRoles(res.data.roles || []);
|
||||||
setRoleIds((res.data.roles || []).filter(role => role.flag).map(role => role.roleId));
|
setRoleIds((res.data.roles || []).filter(role => role.flag).map(role => role.roleId));
|
||||||
})
|
} finally {
|
||||||
.finally(() => setLoading(false));
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadAuthRole();
|
||||||
|
// userId 来自路由参数 变化时需重新加载
|
||||||
|
// oxlint-disable-next-line react/exhaustive-effect-dependencies
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
const columns: ProColumns<RoleVO>[] = useMemo(
|
const columns: ProColumns<RoleVO>[] = useMemo(
|
||||||
|
|||||||
@ -275,7 +275,7 @@ export default function Profile() {
|
|||||||
|
|
||||||
const bindAuth = async (source: string) => {
|
const bindAuth = async (source: string) => {
|
||||||
const res = await authRouterUrl(source);
|
const res = await authRouterUrl(source);
|
||||||
window.location.href = res.data;
|
window.location.assign(res.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deviceColumns: ProColumns<OnlineVO>[] = [
|
const deviceColumns: ProColumns<OnlineVO>[] = [
|
||||||
|
|||||||
@ -90,6 +90,8 @@ export default function ToolGenPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 挂载时加载数据表名属于与外部系统同步的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
loadDataNames();
|
loadDataNames();
|
||||||
}, [loadDataNames]);
|
}, [loadDataNames]);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import { message, Modal, Space, TreeSelect, Upload, type UploadProps } from 'antd';
|
import { message, Modal, Space, TreeSelect, Upload, type UploadProps } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { CategoryTreeVO } from '@/api/workflow/category/types';
|
import type { CategoryTreeVO } from '@/api/workflow/category/types';
|
||||||
import { importDefinition } from '@/api/workflow/definition';
|
import { importDefinition } from '@/api/workflow/definition';
|
||||||
import { buildAccept, getUploadErrorMessage, validateUploadFile } from '@/utils/upload';
|
import { buildAccept, getUploadErrorMessage, validateUploadFile } from '@/utils/upload';
|
||||||
@ -22,13 +22,8 @@ export default function DefinitionImportModal({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: DefinitionImportModalProps) {
|
}: DefinitionImportModalProps) {
|
||||||
const [uploadCategory, setUploadCategory] = useState<string | number>();
|
// 父组件通过 key 重挂载打开弹窗 这里的初值即为每次打开时的 initialCategory 无需 effect 同步
|
||||||
|
const [uploadCategory, setUploadCategory] = useState<string | number | undefined>(initialCategory);
|
||||||
useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
setUploadCategory(initialCategory);
|
|
||||||
}
|
|
||||||
}, [initialCategory, open]);
|
|
||||||
|
|
||||||
const handleImportDefinition: UploadProps['customRequest'] = async options => {
|
const handleImportDefinition: UploadProps['customRequest'] = async options => {
|
||||||
const file = options.file as File;
|
const file = options.file as File;
|
||||||
|
|||||||
@ -120,6 +120,8 @@ export default function WorkflowProcessDefinitionPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextTab = getExplicitActiveTabFromSearch(location.search);
|
const nextTab = getExplicitActiveTabFromSearch(location.search);
|
||||||
if (!nextTab) return;
|
if (!nextTab) return;
|
||||||
|
// URL 查询参数同步页签属于与外部状态对齐的合理场景
|
||||||
|
// oxlint-disable-next-line react/set-state-in-effect
|
||||||
switchTab(nextTab);
|
switchTab(nextTab);
|
||||||
}, [location.search, switchTab]);
|
}, [location.search, switchTab]);
|
||||||
|
|
||||||
@ -414,6 +416,7 @@ export default function WorkflowProcessDefinitionPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<DefinitionImportModal
|
<DefinitionImportModal
|
||||||
|
key={uploadOpen ? 'open' : 'closed'}
|
||||||
open={uploadOpen}
|
open={uploadOpen}
|
||||||
categoryOptions={categoryOptions}
|
categoryOptions={categoryOptions}
|
||||||
initialCategory={category}
|
initialCategory={category}
|
||||||
|
|||||||
@ -61,7 +61,6 @@ export default function WorkflowSpelPage() {
|
|||||||
const { ids, selectedOne, handleSelectionChange, clearSelection } = useTableSelection<SpelVO>(row => row.id);
|
const { ids, selectedOne, handleSelectionChange, clearSelection } = useTableSelection<SpelVO>(row => row.id);
|
||||||
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
|
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
|
||||||
const [modalTitle, setModalTitle] = useState('');
|
const [modalTitle, setModalTitle] = useState('');
|
||||||
const [preview, setPreview] = useState('');
|
|
||||||
|
|
||||||
const canAdd = hasPermi(userInfo, ['workflow:spel:add']);
|
const canAdd = hasPermi(userInfo, ['workflow:spel:add']);
|
||||||
const canEdit = hasPermi(userInfo, ['workflow:spel:edit']);
|
const canEdit = hasPermi(userInfo, ['workflow:spel:edit']);
|
||||||
@ -69,17 +68,17 @@ export default function WorkflowSpelPage() {
|
|||||||
const statusOptions = useMemo(() => dictOptions(dicts.sys_normal_disable), [dicts.sys_normal_disable]);
|
const statusOptions = useMemo(() => dictOptions(dicts.sys_normal_disable), [dicts.sys_normal_disable]);
|
||||||
const watchedValues = Form.useWatch([], form);
|
const watchedValues = Form.useWatch([], form);
|
||||||
|
|
||||||
|
// preview 由表单值派生 无需独立 state
|
||||||
|
const preview = useMemo(() => buildViewSpel(watchedValues || {}), [watchedValues]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextPreview = buildViewSpel(watchedValues || {});
|
form.setFieldValue('viewSpel', preview);
|
||||||
setPreview(nextPreview);
|
}, [form, preview]);
|
||||||
form.setFieldValue('viewSpel', nextPreview);
|
|
||||||
}, [form, watchedValues]);
|
|
||||||
|
|
||||||
const openAdd = () => {
|
const openAdd = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldsValue(defaultSpelForm);
|
form.setFieldsValue(defaultSpelForm);
|
||||||
setModalTitle('添加流程spel表达式定义');
|
setModalTitle('添加流程spel表达式定义');
|
||||||
setPreview('');
|
|
||||||
openModal();
|
openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,7 +89,6 @@ export default function WorkflowSpelPage() {
|
|||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldsValue(res.data);
|
form.setFieldsValue(res.data);
|
||||||
setModalTitle('修改流程spel表达式定义');
|
setModalTitle('修改流程spel表达式定义');
|
||||||
setPreview(res.data.viewSpel || '');
|
|
||||||
openModal();
|
openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import * as CryptoJSModule from 'crypto-js';
|
import { utf8ToBytes } from '@noble/ciphers/utils.js';
|
||||||
|
import { ecb } from '@noble/ciphers/aes.js';
|
||||||
const CryptoJS = ('default' in CryptoJSModule ? CryptoJSModule.default : CryptoJSModule) as typeof CryptoJSModule;
|
|
||||||
|
|
||||||
function generateRandomString() {
|
function generateRandomString() {
|
||||||
const array = new Uint8Array(32);
|
const array = new Uint8Array(32);
|
||||||
@ -10,29 +9,36 @@ function generateRandomString() {
|
|||||||
.slice(0, 32);
|
.slice(0, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateAesKey(): CryptoJSModule.lib.WordArray {
|
function bytesToBase64(bytes: Uint8Array) {
|
||||||
return CryptoJS.enc.Utf8.parse(generateRandomString());
|
return btoa(String.fromCharCode(...bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encryptBase64(str: CryptoJSModule.lib.WordArray) {
|
function base64ToBytes(base64: string) {
|
||||||
return CryptoJS.enc.Base64.stringify(str);
|
return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 AES-256 密钥 (32 字节随机字符串编码) 兼容后端 SecureUtil.aes(key)
|
||||||
|
*/
|
||||||
|
export function generateAesKey(): Uint8Array {
|
||||||
|
return utf8ToBytes(generateRandomString());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encryptBase64(bytes: Uint8Array) {
|
||||||
|
return bytesToBase64(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decryptBase64(str: string) {
|
export function decryptBase64(str: string) {
|
||||||
return CryptoJS.enc.Base64.parse(str);
|
return base64ToBytes(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encryptWithAes(message: string, aesKey: CryptoJSModule.lib.WordArray) {
|
/**
|
||||||
return CryptoJS.AES.encrypt(message, aesKey, {
|
* AES-256-ECB + PKCS7 填充 输出 Base64 密文 与后端 AES/ECB/PKCS5Padding 格式兼容
|
||||||
mode: CryptoJS.mode.ECB,
|
*/
|
||||||
padding: CryptoJS.pad.Pkcs7
|
export function encryptWithAes(message: string, aesKey: Uint8Array) {
|
||||||
}).toString();
|
return bytesToBase64(ecb(aesKey).encrypt(utf8ToBytes(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decryptWithAes(message: string, aesKey: CryptoJSModule.lib.WordArray) {
|
export function decryptWithAes(message: string, aesKey: Uint8Array) {
|
||||||
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
|
return new TextDecoder().decode(ecb(aesKey).decrypt(base64ToBytes(message)));
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7
|
|
||||||
});
|
|
||||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user