@jiangood/open-admin 3.0.15 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -2
- package/src/framework/biz/OrgTree.tsx +10 -6
- package/src/framework/biz/PermActions.tsx +5 -3
- package/src/framework/biz/RoleTree.tsx +10 -6
- package/src/framework/biz/index.tsx +1 -0
- package/src/framework/components/ContextMenu/index.tsx +17 -1
- package/src/framework/components/DownloadModal/index.tsx +40 -33
- package/src/framework/components/FormModal/index.tsx +10 -8
- package/src/framework/components/NamedIcon/index.tsx +2 -2
- package/src/framework/components/OrgSwitcher/index.tsx +6 -7
- package/src/framework/components/PageLoading/index.tsx +3 -3
- package/src/framework/components/ProTable/index.tsx +28 -25
- package/src/framework/components/ValueType/index.tsx +1 -1
- package/src/framework/config/themeConfig.ts +5 -5
- package/src/framework/fields/BaseRemoteSelect/index.tsx +23 -21
- package/src/framework/fields/FieldBoolean/index.tsx +6 -6
- package/src/framework/fields/FieldDate/index.tsx +10 -10
- package/src/framework/fields/FieldDateRange/index.tsx +20 -20
- package/src/framework/fields/FieldDictSelect/index.tsx +2 -2
- package/src/framework/fields/FieldEditor/index.tsx +1 -1
- package/src/framework/fields/FieldNumberRange/index.tsx +16 -17
- package/src/framework/fields/FieldPercent/index.tsx +2 -2
- package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -4
- package/src/framework/fields/FieldRemoteTree/index.tsx +1 -1
- package/src/framework/fields/FieldRemoteTreeCascader/index.tsx +1 -1
- package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +1 -1
- package/src/framework/fields/FieldSysOrgTree/index.tsx +1 -1
- package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +1 -1
- package/src/framework/fields/FieldTable/index.tsx +15 -13
- package/src/framework/fields/FieldTableSelect/index.tsx +5 -5
- package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
- package/src/framework/fields/FieldUploadImage/index.tsx +31 -29
- package/src/framework/fields/types.ts +0 -2
- package/src/framework/router/PageFrame.tsx +1 -1
- package/src/framework/router/matcher.ts +2 -2
- package/src/framework/utils/ArrayUtils.ts +2 -2
- package/src/framework/utils/DateUtils.ts +1 -1
- package/src/framework/utils/{system/DictUtils.ts → DictUtils.ts} +1 -1
- package/src/framework/utils/EventBus.ts +7 -7
- package/src/framework/utils/HttpClient.ts +283 -0
- package/src/framework/utils/{system/PageUtils.ts → PageUtils.ts} +4 -4
- package/src/framework/utils/{system/PermUtils.ts → PermUtils.ts} +3 -3
- package/src/framework/utils/StringUtils.ts +10 -8
- package/src/framework/utils/TreeUtils.ts +3 -3
- package/src/framework/utils/UrlUtils.ts +2 -2
- package/src/framework/utils/index.ts +4 -1
- package/src/framework/views/ViewApproveStatus/index.tsx +1 -2
- package/src/framework/views/ViewBoolean/index.tsx +4 -1
- package/src/framework/views/ViewFile/index.tsx +4 -4
- package/src/framework/views/ViewImage/index.tsx +2 -2
- package/src/framework/views/ViewPassword/index.tsx +3 -2
- package/src/framework/views/ViewText/index.tsx +12 -3
- package/src/framework/views/types.ts +2 -4
- package/src/layouts/admin/AdminTabs.jsx +9 -0
- package/src/layouts/admin/AdminTabs.less +2 -2
- package/src/layouts/admin/HeaderRight.jsx +8 -7
- package/src/layouts/admin/TabLayout.jsx +2 -2
- package/src/layouts/admin/index.jsx +15 -11
- package/src/layouts/admin/index.less +16 -1
- package/src/layouts/index.tsx +24 -18
- package/src/pages/404.jsx +1 -1
- package/src/pages/article/$code.jsx +4 -4
- package/src/pages/index.jsx +1 -1
- package/src/pages/public/login.jsx +34 -27
- package/src/pages/public/test.jsx +1 -1
- package/src/pages/standalone/forceUpdatePwd.jsx +9 -7
- package/src/pages/system/article/index.jsx +9 -8
- package/src/pages/system/dict/index.jsx +19 -16
- package/src/pages/system/file/index.jsx +9 -9
- package/src/pages/system/job/index.jsx +17 -17
- package/src/pages/system/log/index.jsx +5 -7
- package/src/pages/system/org/index.jsx +31 -18
- package/src/pages/system/role/index.jsx +13 -12
- package/src/pages/system/role/rolePerm.jsx +25 -21
- package/src/pages/system/user/index.jsx +35 -33
- package/src/pages/system/user/userPerm.jsx +12 -12
- package/src/pages/userCenter/index.jsx +11 -9
- package/src/pages/userCenter/permView.jsx +7 -7
- package/src/framework/utils/system/HttpUtils.ts +0 -97
- package/src/framework/utils/system/index.ts +0 -4
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"devDependencies": {
|
|
3
|
+
"@babel/core": "^7.29.7",
|
|
4
|
+
"@eslint/js": "^10.0.1",
|
|
3
5
|
"@playwright/test": "^1.62.0",
|
|
4
6
|
"@types/qs": "^6.15.1",
|
|
5
7
|
"@types/react": "^19.2.2",
|
|
6
8
|
"@types/react-dom": "^19.0.0",
|
|
7
9
|
"@vitejs/plugin-react": "^6.0.0",
|
|
8
10
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
11
|
+
"eslint": "^10.8.1",
|
|
12
|
+
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
13
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
14
|
+
"eslint-plugin-react-refresh": "^0.5.4",
|
|
15
|
+
"globals": "^17.11.0",
|
|
9
16
|
"less": "^4.2.0",
|
|
10
|
-
"typescript": "^
|
|
17
|
+
"typescript": "^5.9.3",
|
|
18
|
+
"typescript-eslint": "^8.67.0",
|
|
11
19
|
"vite": "^8.0.0"
|
|
12
20
|
},
|
|
13
21
|
"peerDependencies": {
|
|
@@ -46,8 +54,13 @@
|
|
|
46
54
|
"dev": "vite",
|
|
47
55
|
"build": "vite build",
|
|
48
56
|
"preview": "vite preview",
|
|
57
|
+
"lint": "eslint src --max-warnings=0",
|
|
58
|
+
"lint:fix": "eslint src --fix",
|
|
49
59
|
"test:e2e": "playwright test",
|
|
50
60
|
"test:e2e:ui": "playwright test --ui"
|
|
51
61
|
},
|
|
52
|
-
"version": "3.0
|
|
62
|
+
"version": "3.1.0",
|
|
63
|
+
"overrides": {
|
|
64
|
+
"zod-validation-error": "^4.0.2"
|
|
65
|
+
}
|
|
53
66
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {Alert, Skeleton, Tree} from 'antd';
|
|
1
|
+
import {Alert, Skeleton, Tree, type TreeDataNode} from 'antd';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import {HttpClient} from "../utils";
|
|
4
4
|
import {NamedIcon} from "../components/NamedIcon";
|
|
5
5
|
|
|
6
6
|
interface OrgTreeProps {
|
|
@@ -9,7 +9,7 @@ interface OrgTreeProps {
|
|
|
9
9
|
|
|
10
10
|
interface OrgTreeState {
|
|
11
11
|
treeDataLoading: boolean;
|
|
12
|
-
treeData:
|
|
12
|
+
treeData: TreeDataNode[];
|
|
13
13
|
currentOrgId: string | null;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -22,8 +22,8 @@ export class OrgTree extends React.Component<OrgTreeProps, OrgTreeState> {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
componentDidMount() {
|
|
25
|
-
|
|
26
|
-
this.setState({treeData: tree, treeDataLoading: false})
|
|
25
|
+
HttpClient.get('admin/sysOrg/tree', null).then(tree => {
|
|
26
|
+
this.setState({treeData: tree.data, treeDataLoading: false})
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -48,7 +48,11 @@ export class OrgTree extends React.Component<OrgTreeProps, OrgTreeState> {
|
|
|
48
48
|
onSelect={this.onSelectOrg}
|
|
49
49
|
showIcon
|
|
50
50
|
blockNode
|
|
51
|
-
icon={
|
|
51
|
+
icon={renderOrgIcon}
|
|
52
52
|
/>
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
function renderOrgIcon(item) {
|
|
57
|
+
return item.iconName ? <NamedIcon name={item.iconName}/> : null;
|
|
58
|
+
}
|
|
@@ -22,11 +22,13 @@ export interface PermAction {
|
|
|
22
22
|
type?: 'primary' | 'dashed' | 'link' | 'text' | 'default';
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export type ButtonSize = 'small' | 'middle' | 'large';
|
|
26
|
+
|
|
25
27
|
export interface PermActionsProps {
|
|
26
28
|
/** 是否折叠为「第一个操作 + 更多下拉」,多余操作进入 `...` 菜单 */
|
|
27
29
|
more?: boolean;
|
|
28
30
|
/** 按钮尺寸 */
|
|
29
|
-
size?:
|
|
31
|
+
size?: ButtonSize;
|
|
30
32
|
/** 传统写法:包裹带 `perm` 属性的子元素,按 `perm` 过滤。不推荐,建议改用 `actions` 数据驱动 */
|
|
31
33
|
children?: React.ReactNode;
|
|
32
34
|
/** 数据驱动模式:操作数组,按每个 action 的 perm 过滤 */
|
|
@@ -34,7 +36,7 @@ export interface PermActionsProps {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export class PermActions extends React.Component<PermActionsProps> {
|
|
37
|
-
static defaultProps: Partial<PermActionsProps> = {
|
|
39
|
+
static readonly defaultProps: Partial<PermActionsProps> = {
|
|
38
40
|
more: false,
|
|
39
41
|
size: 'middle',
|
|
40
42
|
};
|
|
@@ -99,7 +101,7 @@ export class PermActions extends React.Component<PermActionsProps> {
|
|
|
99
101
|
|
|
100
102
|
if (!more || visible.length <= 1) {
|
|
101
103
|
const buttons = visible.map((action, index) => (
|
|
102
|
-
<React.Fragment key={index}>{this.renderButton(action, size)}</React.Fragment>
|
|
104
|
+
<React.Fragment key={index}>{this.renderButton(action, size)}</React.Fragment> // NOSONAR: 按钮无稳定 id,静态过滤后顺序不变
|
|
103
105
|
));
|
|
104
106
|
return <Space>{buttons}</Space>;
|
|
105
107
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {Skeleton, Tree} from 'antd';
|
|
1
|
+
import {Skeleton, Tree, type TreeDataNode} from 'antd';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import {SolutionOutlined} from '@ant-design/icons';
|
|
4
|
-
import {
|
|
4
|
+
import {HttpClient} from "../utils";
|
|
5
5
|
|
|
6
6
|
interface RoleTreeProps {
|
|
7
7
|
onSelect?: (orgId: string | null) => void;
|
|
@@ -9,7 +9,7 @@ interface RoleTreeProps {
|
|
|
9
9
|
|
|
10
10
|
interface RoleTreeState {
|
|
11
11
|
treeDataLoading: boolean;
|
|
12
|
-
treeData:
|
|
12
|
+
treeData: TreeDataNode[];
|
|
13
13
|
currentOrgId: string | null;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -22,8 +22,8 @@ export class RoleTree extends React.Component<RoleTreeProps, RoleTreeState> {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
componentDidMount() {
|
|
25
|
-
|
|
26
|
-
this.setState({treeData: tree, treeDataLoading: false})
|
|
25
|
+
HttpClient.get('admin/sysRole/biz-tree', null).then(tree => {
|
|
26
|
+
this.setState({treeData: tree.data, treeDataLoading: false})
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -44,7 +44,11 @@ export class RoleTree extends React.Component<RoleTreeProps, RoleTreeState> {
|
|
|
44
44
|
onSelect={this.onSelect}
|
|
45
45
|
showIcon
|
|
46
46
|
blockNode
|
|
47
|
-
icon={
|
|
47
|
+
icon={renderRoleIcon}
|
|
48
48
|
/>
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
function renderRoleIcon() {
|
|
53
|
+
return <SolutionOutlined/>;
|
|
54
|
+
}
|
|
@@ -18,6 +18,11 @@ interface ContextMenuProps {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export class ContextMenu extends React.Component<ContextMenuProps> {
|
|
21
|
+
menuItemColor = (item): string => {
|
|
22
|
+
if (item.danger) return '#ff4d4f';
|
|
23
|
+
if (item.disabled) return 'rgba(0,0,0,0.25)';
|
|
24
|
+
return '#333';
|
|
25
|
+
};
|
|
21
26
|
menuRef = React.createRef<HTMLDivElement>();
|
|
22
27
|
timer: ReturnType<typeof setTimeout> | null = null;
|
|
23
28
|
|
|
@@ -90,17 +95,28 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|
|
90
95
|
) : (
|
|
91
96
|
<div
|
|
92
97
|
key={item.key}
|
|
98
|
+
role="menuitem"
|
|
99
|
+
tabIndex={item.disabled ? -1 : 0}
|
|
93
100
|
onClick={() => {
|
|
94
101
|
if (!item.disabled) {
|
|
95
102
|
onClick({ key: item.key });
|
|
96
103
|
onClose();
|
|
97
104
|
}
|
|
98
105
|
}}
|
|
106
|
+
onKeyDown={(e) => {
|
|
107
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
if (!item.disabled) {
|
|
110
|
+
onClick({ key: item.key });
|
|
111
|
+
onClose();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
99
115
|
style={{
|
|
100
116
|
padding: '5px 12px',
|
|
101
117
|
fontSize: 13,
|
|
102
118
|
cursor: item.disabled ? 'not-allowed' : 'pointer',
|
|
103
|
-
color:
|
|
119
|
+
color: this.menuItemColor(item),
|
|
104
120
|
display: 'flex',
|
|
105
121
|
alignItems: 'center',
|
|
106
122
|
gap: 8,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@ant-design/icons";
|
|
8
8
|
import axios from "axios";
|
|
9
9
|
import qs from 'qs';
|
|
10
|
+
import type {AxiosProgressEvent, AxiosRequestConfig, AxiosResponse} from "axios";
|
|
10
11
|
|
|
11
12
|
const axiosInstance = axios.create({
|
|
12
13
|
baseURL: import.meta.env.VITE_SERVER_SERVLET_CONTEXT_PATH,
|
|
@@ -17,8 +18,8 @@ const axiosInstance = axios.create({
|
|
|
17
18
|
|
|
18
19
|
interface DownloadOptions {
|
|
19
20
|
url: string;
|
|
20
|
-
params?: Record<string,
|
|
21
|
-
data?: Record<string,
|
|
21
|
+
params?: Record<string, unknown>;
|
|
22
|
+
data?: Record<string, unknown>;
|
|
22
23
|
method?: 'GET' | 'POST';
|
|
23
24
|
fileName?: string;
|
|
24
25
|
}
|
|
@@ -39,8 +40,8 @@ export interface DownloadModalProps {
|
|
|
39
40
|
onFinish?: () => void;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export class DownloadModal extends React.Component<DownloadModalProps, ModalState> {
|
|
43
|
-
private abortController: AbortController | null = null;
|
|
43
|
+
export class DownloadModal extends React.Component<DownloadModalProps, ModalState> { // NOSONAR: state/定时器字段构造器赋值,无法全部 readonly
|
|
44
|
+
private abortController: AbortController | null = null; // NOSONAR: 构造器中赋值
|
|
44
45
|
private lastOptions: DownloadOptions | null = null;
|
|
45
46
|
private lastTime: number = 0;
|
|
46
47
|
private lastLoaded: number = 0;
|
|
@@ -60,7 +61,7 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
download = (options: DownloadOptions) => {
|
|
64
|
+
download = (options: DownloadOptions) => { // NOSONAR: ref 暴露给父组件/业务项目调用的公共 API
|
|
64
65
|
this.startDownload(options);
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -101,13 +102,29 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
101
102
|
this.setState({open: false, status: ''});
|
|
102
103
|
};
|
|
103
104
|
|
|
105
|
+
renderFooter = () => {
|
|
106
|
+
const {status} = this.state;
|
|
107
|
+
if (status === 'downloading') {
|
|
108
|
+
return <Button danger onClick={this.handleCancel}>取消下载</Button>;
|
|
109
|
+
}
|
|
110
|
+
if (status === 'failed') {
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
113
|
+
<Button onClick={this.handleClose}>关闭</Button>
|
|
114
|
+
<Button type="primary" onClick={this.handleRetry}>重试</Button>
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
|
|
104
121
|
private handleRetry = () => {
|
|
105
122
|
if (this.lastOptions) {
|
|
106
123
|
this.startDownload(this.lastOptions);
|
|
107
124
|
}
|
|
108
125
|
};
|
|
109
126
|
|
|
110
|
-
private saveBlob(response:
|
|
127
|
+
private saveBlob(response: AxiosResponse) {
|
|
111
128
|
return new Promise<void>((resolve, reject) => {
|
|
112
129
|
const {data: blob, headers} = response;
|
|
113
130
|
|
|
@@ -122,7 +139,7 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
122
139
|
try {
|
|
123
140
|
const rs = JSON.parse(reader.result as string);
|
|
124
141
|
reject(new Error(rs.message || '下载失败'));
|
|
125
|
-
} catch
|
|
142
|
+
} catch {
|
|
126
143
|
reject(new Error('解析错误响应失败'));
|
|
127
144
|
}
|
|
128
145
|
};
|
|
@@ -134,11 +151,11 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
134
151
|
let filename = this.state.fileName;
|
|
135
152
|
if (!filename && contentDisposition) {
|
|
136
153
|
const match = /filename\*?=(?:['"]?)(?:UTF-8''|)(.+?)(?:['"]?$|;)/i.exec(contentDisposition);
|
|
137
|
-
let parsedName = match
|
|
154
|
+
let parsedName = match?.[1] ? match[1].trim() : 'download.file';
|
|
138
155
|
try {
|
|
139
|
-
parsedName = decodeURIComponent(parsedName.
|
|
140
|
-
} catch
|
|
141
|
-
parsedName = parsedName.
|
|
156
|
+
parsedName = decodeURIComponent(parsedName.replaceAll('"', ''));
|
|
157
|
+
} catch {
|
|
158
|
+
parsedName = parsedName.replaceAll('"', '');
|
|
142
159
|
}
|
|
143
160
|
filename = parsedName;
|
|
144
161
|
}
|
|
@@ -153,7 +170,7 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
153
170
|
link.download = filename || 'download.file';
|
|
154
171
|
document.body.appendChild(link);
|
|
155
172
|
link.click();
|
|
156
|
-
|
|
173
|
+
link.remove();
|
|
157
174
|
window.URL.revokeObjectURL(url);
|
|
158
175
|
resolve();
|
|
159
176
|
});
|
|
@@ -193,12 +210,12 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
193
210
|
}
|
|
194
211
|
}, 2000);
|
|
195
212
|
|
|
196
|
-
const config:
|
|
213
|
+
const config: AxiosRequestConfig = {
|
|
197
214
|
url: options.url,
|
|
198
215
|
method: options.method || 'GET',
|
|
199
216
|
responseType: 'blob',
|
|
200
217
|
signal: this.abortController.signal,
|
|
201
|
-
onDownloadProgress: (progressEvent:
|
|
218
|
+
onDownloadProgress: (progressEvent: AxiosProgressEvent) => {
|
|
202
219
|
const {loaded, total} = progressEvent;
|
|
203
220
|
const now = Date.now();
|
|
204
221
|
// 计算速度
|
|
@@ -209,12 +226,12 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
209
226
|
this.lastTime = now;
|
|
210
227
|
this.lastLoaded = loaded;
|
|
211
228
|
}
|
|
212
|
-
this.setState({
|
|
229
|
+
this.setState((prev) => ({
|
|
213
230
|
progress: total ? Math.round((loaded / total) * 100) : 0,
|
|
214
231
|
loaded,
|
|
215
232
|
total,
|
|
216
|
-
speed: speed > 0 ? this.formatSpeed(speed) :
|
|
217
|
-
});
|
|
233
|
+
speed: speed > 0 ? this.formatSpeed(speed) : prev.speed,
|
|
234
|
+
}));
|
|
218
235
|
},
|
|
219
236
|
};
|
|
220
237
|
|
|
@@ -225,19 +242,18 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
225
242
|
config.data = options.data;
|
|
226
243
|
}
|
|
227
244
|
|
|
228
|
-
axiosInstance(config).then((response
|
|
245
|
+
axiosInstance(config).then((response) => {
|
|
229
246
|
this.clearSpeedTimer();
|
|
230
247
|
return this.saveBlob(response);
|
|
231
248
|
}).then(() => {
|
|
232
|
-
|
|
233
|
-
this.setState({
|
|
249
|
+
this.setState((prev) => ({
|
|
234
250
|
status: 'completed',
|
|
235
251
|
progress: 100,
|
|
236
252
|
speed: '',
|
|
237
|
-
loaded: total ||
|
|
238
|
-
});
|
|
253
|
+
loaded: prev.total || prev.loaded,
|
|
254
|
+
}));
|
|
239
255
|
this.props.onFinish?.();
|
|
240
|
-
}).catch((error
|
|
256
|
+
}).catch((error) => {
|
|
241
257
|
this.clearSpeedTimer();
|
|
242
258
|
if (axios.isCancel(error)) {
|
|
243
259
|
// 用户取消,不显示错误
|
|
@@ -266,16 +282,7 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
266
282
|
mask={{closable: false}}
|
|
267
283
|
closable={status !== 'downloading'}
|
|
268
284
|
onCancel={this.handleClose}
|
|
269
|
-
footer={
|
|
270
|
-
status === 'downloading' ? (
|
|
271
|
-
<Button danger onClick={this.handleCancel}>取消下载</Button>
|
|
272
|
-
) : status === 'failed' ? (
|
|
273
|
-
<>
|
|
274
|
-
<Button onClick={this.handleClose}>关闭</Button>
|
|
275
|
-
<Button type="primary" onClick={this.handleRetry}>重试</Button>
|
|
276
|
-
</>
|
|
277
|
-
) : null
|
|
278
|
-
}
|
|
285
|
+
footer={this.renderFooter()}
|
|
279
286
|
destroyOnHidden
|
|
280
287
|
>
|
|
281
288
|
<div style={{padding: '20px 0'}}>
|
|
@@ -3,28 +3,28 @@ import React from 'react'
|
|
|
3
3
|
|
|
4
4
|
export interface FormModalProps {
|
|
5
5
|
title: string
|
|
6
|
-
onFinish?: (values: Record<string,
|
|
7
|
-
onValuesChange?: (changedValues:
|
|
6
|
+
onFinish?: (values: Record<string, unknown>) => Promise<void> | void
|
|
7
|
+
onValuesChange?: (changedValues: Record<string, unknown>, allValues: Record<string, unknown>) => void
|
|
8
8
|
width?: number
|
|
9
|
-
labelCol?: Record<string,
|
|
9
|
+
labelCol?: Record<string, unknown>
|
|
10
10
|
children?: React.ReactNode
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export default class FormModal extends React.Component<FormModalProps> {
|
|
13
|
+
export default class FormModal extends React.Component<FormModalProps> { // NOSONAR: state 构造器中赋值
|
|
14
14
|
|
|
15
|
-
state = {
|
|
15
|
+
readonly state = { // NOSONAR: React state 惯例
|
|
16
16
|
visible: false,
|
|
17
17
|
confirmLoading: false,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
private pendingValues: Record<string,
|
|
20
|
+
private pendingValues: Record<string, unknown> | null = null
|
|
21
21
|
private formRef = React.createRef<FormInstance>()
|
|
22
22
|
|
|
23
|
-
get formInstance() {
|
|
23
|
+
get formInstance() { // NOSONAR: ref 暴露给父组件/业务项目调用的公共 API
|
|
24
24
|
return this.formRef.current
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
open = (values?: Record<string,
|
|
27
|
+
open = (values?: Record<string, unknown>) => { // NOSONAR: ref 暴露给父组件/业务项目调用的公共 API
|
|
28
28
|
this.pendingValues = values || {}
|
|
29
29
|
this.setState({visible: true})
|
|
30
30
|
}
|
|
@@ -49,6 +49,8 @@ export default class FormModal extends React.Component<FormModalProps> {
|
|
|
49
49
|
if (values == null) return
|
|
50
50
|
await this.props.onFinish?.(values)
|
|
51
51
|
this.setState({visible: false})
|
|
52
|
+
} catch {
|
|
53
|
+
// 提交失败(错误提示已由 HttpClient 弹出),保持弹窗打开以便用户修改后重试
|
|
52
54
|
} finally {
|
|
53
55
|
this.setState({confirmLoading: false})
|
|
54
56
|
}
|
|
@@ -12,7 +12,7 @@ const iconLoaders: Record<string, () => Promise<{ default: IconComponent }>> = {
|
|
|
12
12
|
for (const [path, loader] of Object.entries(
|
|
13
13
|
import.meta.glob('/node_modules/@ant-design/icons/es/icons/*.js')
|
|
14
14
|
)) {
|
|
15
|
-
const name = /([^/]+)\.js$/.exec(path)?.[1];
|
|
15
|
+
const name = /([^/]+)\.js$/.exec(path)?.[1]; // NOSONAR: 提取文件名,已最简
|
|
16
16
|
if (name) {
|
|
17
17
|
iconLoaders[name] = loader as () => Promise<{ default: IconComponent }>;
|
|
18
18
|
}
|
|
@@ -52,7 +52,7 @@ export function NamedIcon(props: NamedIconProps): React.ReactElement {
|
|
|
52
52
|
};
|
|
53
53
|
}, [name]);
|
|
54
54
|
|
|
55
|
-
const Icon = (loaded
|
|
55
|
+
const Icon = (loaded?.name === name) ? loaded.comp : iconCache.get(name);
|
|
56
56
|
|
|
57
57
|
if (Icon) {
|
|
58
58
|
return <Icon {...rest} />;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {TreeSelect} from "antd";
|
|
3
|
-
import {
|
|
3
|
+
import {HttpClient} from "../../utils";
|
|
4
4
|
|
|
5
5
|
interface OrgNode {
|
|
6
6
|
title: string;
|
|
@@ -14,21 +14,20 @@ const countUnits = (nodes: OrgNode[] = []): number =>
|
|
|
14
14
|
/**
|
|
15
15
|
* 组织机构切换器:切换当前组织机构(单位),默认隐藏,由 Layouts 的 orgSwitcher 插槽按需渲染。
|
|
16
16
|
*/
|
|
17
|
-
export class OrgSwitcher extends React.Component<
|
|
17
|
+
export class OrgSwitcher extends React.Component<Record<string, never>, {tree: OrgNode[]; currentOrgId: string | null}> {
|
|
18
18
|
state = {tree: [], currentOrgId: null};
|
|
19
19
|
|
|
20
20
|
componentDidMount() {
|
|
21
|
-
|
|
22
|
-
if (data) {
|
|
23
|
-
this.setState({tree: data.tree || [], currentOrgId: data.currentOrgId || null});
|
|
21
|
+
HttpClient.get("admin/myOrgs", null).then(data => {
|
|
22
|
+
if (data.data) {
|
|
23
|
+
this.setState({tree: data.data.tree || [], currentOrgId: data.data.currentOrgId || null});
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
handleChange = (orgId: string) => {
|
|
29
|
-
|
|
29
|
+
HttpClient.post("admin/switchOrg", {orgId}, null).then(() => {
|
|
30
30
|
setTimeout(() => location.reload(), 1000);
|
|
31
|
-
}).catch(() => {
|
|
32
31
|
});
|
|
33
32
|
};
|
|
34
33
|
|
|
@@ -9,9 +9,9 @@ export interface PageLoadingProps {
|
|
|
9
9
|
messages?: string[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function PageLoading(props: PageLoadingProps) {
|
|
12
|
+
export function PageLoading(props: PageLoadingProps) { // NOSONAR: 仅读取 props,函数组件无需变更
|
|
13
13
|
const titles = props.messages || (props.message ? [props.message] : ['页面加载中...']);
|
|
14
|
-
const primaryColor = getToken().colorPrimary;
|
|
14
|
+
const primaryColor = getToken().colorPrimary ?? "";
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<div className="oa-page-loading">
|
|
@@ -22,7 +22,7 @@ export function PageLoading(props: PageLoadingProps) {
|
|
|
22
22
|
<div className="oa-page-loading-messages">
|
|
23
23
|
{titles.map((msg, i) => (
|
|
24
24
|
<Alert
|
|
25
|
-
key={i}
|
|
25
|
+
key={i} // NOSONAR: 提示文案列表静态无稳定 key
|
|
26
26
|
title={<span style={{color: primaryColor}}>{msg}</span>}
|
|
27
27
|
type="info"
|
|
28
28
|
showIcon
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import {Button, Form, Table} from 'antd';
|
|
3
3
|
import type {FormInstance, TableProps} from 'antd';
|
|
4
4
|
|
|
5
|
-
import {StringUtils} from "../../utils";
|
|
5
|
+
import {StringUtils, type AjaxBody} from "../../utils";
|
|
6
6
|
|
|
7
7
|
import './index.less'
|
|
8
8
|
|
|
@@ -13,27 +13,32 @@ export interface ProTableActionRef {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** request 返回结构(Spring Data Page 序列化 + 扩展数据) */
|
|
16
|
-
export interface ProTableRequestResult<T =
|
|
16
|
+
export interface ProTableRequestResult<T = unknown> {
|
|
17
17
|
content: T[];
|
|
18
18
|
totalElements: number | string;
|
|
19
19
|
size: number;
|
|
20
20
|
extData?: {
|
|
21
21
|
summary?: React.ReactNode;
|
|
22
|
-
[key: string]:
|
|
22
|
+
[key: string]: unknown;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export interface ProTableProps<T =
|
|
27
|
-
/**
|
|
28
|
-
|
|
26
|
+
export interface ProTableProps<T = unknown> {
|
|
27
|
+
/**
|
|
28
|
+
* 数据请求(Promise 式),框架自动注入 page/size/sort 参数。
|
|
29
|
+
* 返回 HttpClient 的 Promise:request = (params) => HttpClient.get(url, params)
|
|
30
|
+
* ProTable 自动取 AjaxResult 的 data(分页结构 {content, totalElements, extData})。
|
|
31
|
+
* 失败时 HttpClient 默认自动弹错,ProTable 内部静默复位 loading。
|
|
32
|
+
*/
|
|
33
|
+
request: (params: Record<string, unknown>) => Promise<AjaxBody<ProTableRequestResult<T>>>;
|
|
29
34
|
/** antd Table 列定义 */
|
|
30
35
|
columns: TableProps<T>['columns'];
|
|
31
36
|
/** 获取表格操作句柄(reload) */
|
|
32
|
-
actionRef?: React.
|
|
37
|
+
actionRef?: React.RefObject<ProTableActionRef | undefined>;
|
|
33
38
|
/** 获取搜索表单实例 */
|
|
34
|
-
formRef?: React.
|
|
39
|
+
formRef?: React.RefObject<FormInstance | undefined>;
|
|
35
40
|
/** 工具栏渲染,参数为当前搜索值与行选择状态 */
|
|
36
|
-
toolBarRender?: (params: Record<string,
|
|
41
|
+
toolBarRender?: (params: Record<string, unknown>, selection: {
|
|
37
42
|
selectedRows: T[];
|
|
38
43
|
selectedRowKeys: React.Key[];
|
|
39
44
|
}) => React.ReactNode;
|
|
@@ -54,12 +59,12 @@ export interface ProTableProps<T = any> {
|
|
|
54
59
|
treeMode?: boolean;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
interface ProTableState<T =
|
|
62
|
+
interface ProTableState<T = unknown> {
|
|
58
63
|
selectedRowKeys: React.Key[];
|
|
59
64
|
selectedRows: T[];
|
|
60
65
|
tableSize: 'small' | 'middle' | 'large';
|
|
61
66
|
loading: boolean;
|
|
62
|
-
params: Record<string,
|
|
67
|
+
params: Record<string, unknown>;
|
|
63
68
|
dataSource: T[];
|
|
64
69
|
total: number;
|
|
65
70
|
current: number;
|
|
@@ -70,12 +75,12 @@ interface ProTableState<T = any> {
|
|
|
70
75
|
};
|
|
71
76
|
extData: {
|
|
72
77
|
summary?: React.ReactNode;
|
|
73
|
-
[key: string]:
|
|
78
|
+
[key: string]: unknown;
|
|
74
79
|
};
|
|
75
80
|
scrollY: number | string | null;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
export class ProTable<T =
|
|
83
|
+
export class ProTable<T = unknown> extends React.Component<ProTableProps<T>, ProTableState<T>> {
|
|
79
84
|
|
|
80
85
|
|
|
81
86
|
state: ProTableState<T> = {
|
|
@@ -150,23 +155,22 @@ export class ProTable<T = any> extends React.Component<ProTableProps<T>, ProTabl
|
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
request(params).then(rs => {
|
|
155
|
-
const {content, totalElements, size,extData} = rs;
|
|
158
|
+
this.setState({loading: true})
|
|
159
|
+
request(params).then((rs: AjaxBody<ProTableRequestResult<T>>) => {
|
|
160
|
+
const {content, totalElements, size, extData} = rs.data;
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
this.setState({dataSource: content, total: Number(totalElements),pageSize:size})
|
|
162
|
+
this.setState({dataSource: content, total: Number(totalElements), pageSize: size})
|
|
159
163
|
if (extData) {
|
|
160
164
|
this.setState({extData})
|
|
161
165
|
}
|
|
162
166
|
this.updateSelectedRows(content)
|
|
163
|
-
|
|
164
|
-
}).
|
|
167
|
+
this.setState({loading: false})
|
|
168
|
+
}).catch(() => {
|
|
169
|
+
// 错误提示已由 HttpClient 默认弹出,此处仅复位 loading
|
|
165
170
|
this.setState({loading: false})
|
|
166
171
|
})
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
|
|
170
174
|
// 数据重新加载后,更新toolbar需要的已选择数据行
|
|
171
175
|
updateSelectedRows = list => {
|
|
172
176
|
const {rowKey = "id"} = this.props
|
|
@@ -180,7 +184,6 @@ export class ProTable<T = any> extends React.Component<ProTableProps<T>, ProTabl
|
|
|
180
184
|
|
|
181
185
|
render() {
|
|
182
186
|
const {
|
|
183
|
-
actionRef,
|
|
184
187
|
toolBarRender,
|
|
185
188
|
columns,
|
|
186
189
|
rowSelection,
|
|
@@ -215,7 +218,7 @@ export class ProTable<T = any> extends React.Component<ProTableProps<T>, ProTabl
|
|
|
215
218
|
showTotal: (total) => `共 ${total} 条`
|
|
216
219
|
}}
|
|
217
220
|
|
|
218
|
-
onChange={(pagination, filters, sorter
|
|
221
|
+
onChange={(pagination, filters, sorter) => {
|
|
219
222
|
if (this.props.treeMode) {
|
|
220
223
|
this.setState({sorter}, this.loadData)
|
|
221
224
|
} else {
|
|
@@ -298,11 +301,11 @@ export class ProTable<T = any> extends React.Component<ProTableProps<T>, ProTabl
|
|
|
298
301
|
};
|
|
299
302
|
};
|
|
300
303
|
|
|
301
|
-
onSearch = (values: Record<string,
|
|
304
|
+
onSearch = (values: Record<string, unknown>) => {
|
|
302
305
|
this.setState({params: values, current: 1, sorter: {}}, this.loadData)
|
|
303
306
|
}
|
|
304
307
|
|
|
305
|
-
changeFormValues = (values: Record<string,
|
|
308
|
+
changeFormValues = (values: Record<string, unknown>) => { // NOSONAR: ref 暴露给父组件/业务项目调用的公共 API
|
|
306
309
|
if (this.formRef.current) {
|
|
307
310
|
this.formRef.current.resetFields()
|
|
308
311
|
this.formRef.current.setFieldsValue(values)
|