@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.
Files changed (80) hide show
  1. package/package.json +15 -2
  2. package/src/framework/biz/OrgTree.tsx +10 -6
  3. package/src/framework/biz/PermActions.tsx +5 -3
  4. package/src/framework/biz/RoleTree.tsx +10 -6
  5. package/src/framework/biz/index.tsx +1 -0
  6. package/src/framework/components/ContextMenu/index.tsx +17 -1
  7. package/src/framework/components/DownloadModal/index.tsx +40 -33
  8. package/src/framework/components/FormModal/index.tsx +10 -8
  9. package/src/framework/components/NamedIcon/index.tsx +2 -2
  10. package/src/framework/components/OrgSwitcher/index.tsx +6 -7
  11. package/src/framework/components/PageLoading/index.tsx +3 -3
  12. package/src/framework/components/ProTable/index.tsx +28 -25
  13. package/src/framework/components/ValueType/index.tsx +1 -1
  14. package/src/framework/config/themeConfig.ts +5 -5
  15. package/src/framework/fields/BaseRemoteSelect/index.tsx +23 -21
  16. package/src/framework/fields/FieldBoolean/index.tsx +6 -6
  17. package/src/framework/fields/FieldDate/index.tsx +10 -10
  18. package/src/framework/fields/FieldDateRange/index.tsx +20 -20
  19. package/src/framework/fields/FieldDictSelect/index.tsx +2 -2
  20. package/src/framework/fields/FieldEditor/index.tsx +1 -1
  21. package/src/framework/fields/FieldNumberRange/index.tsx +16 -17
  22. package/src/framework/fields/FieldPercent/index.tsx +2 -2
  23. package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -4
  24. package/src/framework/fields/FieldRemoteTree/index.tsx +1 -1
  25. package/src/framework/fields/FieldRemoteTreeCascader/index.tsx +1 -1
  26. package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +1 -1
  27. package/src/framework/fields/FieldSysOrgTree/index.tsx +1 -1
  28. package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +1 -1
  29. package/src/framework/fields/FieldTable/index.tsx +15 -13
  30. package/src/framework/fields/FieldTableSelect/index.tsx +5 -5
  31. package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
  32. package/src/framework/fields/FieldUploadImage/index.tsx +31 -29
  33. package/src/framework/fields/types.ts +0 -2
  34. package/src/framework/router/PageFrame.tsx +1 -1
  35. package/src/framework/router/matcher.ts +2 -2
  36. package/src/framework/utils/ArrayUtils.ts +2 -2
  37. package/src/framework/utils/DateUtils.ts +1 -1
  38. package/src/framework/utils/{system/DictUtils.ts → DictUtils.ts} +1 -1
  39. package/src/framework/utils/EventBus.ts +7 -7
  40. package/src/framework/utils/HttpClient.ts +283 -0
  41. package/src/framework/utils/{system/PageUtils.ts → PageUtils.ts} +4 -4
  42. package/src/framework/utils/{system/PermUtils.ts → PermUtils.ts} +3 -3
  43. package/src/framework/utils/StringUtils.ts +10 -8
  44. package/src/framework/utils/TreeUtils.ts +3 -3
  45. package/src/framework/utils/UrlUtils.ts +2 -2
  46. package/src/framework/utils/index.ts +4 -1
  47. package/src/framework/views/ViewApproveStatus/index.tsx +1 -2
  48. package/src/framework/views/ViewBoolean/index.tsx +4 -1
  49. package/src/framework/views/ViewFile/index.tsx +4 -4
  50. package/src/framework/views/ViewImage/index.tsx +2 -2
  51. package/src/framework/views/ViewPassword/index.tsx +3 -2
  52. package/src/framework/views/ViewText/index.tsx +12 -3
  53. package/src/framework/views/types.ts +2 -4
  54. package/src/layouts/admin/AdminTabs.jsx +9 -0
  55. package/src/layouts/admin/AdminTabs.less +2 -2
  56. package/src/layouts/admin/HeaderRight.jsx +8 -7
  57. package/src/layouts/admin/TabLayout.jsx +2 -2
  58. package/src/layouts/admin/index.jsx +15 -11
  59. package/src/layouts/admin/index.less +16 -1
  60. package/src/layouts/index.tsx +24 -18
  61. package/src/pages/404.jsx +1 -1
  62. package/src/pages/article/$code.jsx +4 -4
  63. package/src/pages/index.jsx +1 -1
  64. package/src/pages/public/login.jsx +34 -27
  65. package/src/pages/public/test.jsx +1 -1
  66. package/src/pages/standalone/forceUpdatePwd.jsx +9 -7
  67. package/src/pages/system/article/index.jsx +9 -8
  68. package/src/pages/system/dict/index.jsx +19 -16
  69. package/src/pages/system/file/index.jsx +9 -9
  70. package/src/pages/system/job/index.jsx +17 -17
  71. package/src/pages/system/log/index.jsx +5 -7
  72. package/src/pages/system/org/index.jsx +31 -18
  73. package/src/pages/system/role/index.jsx +13 -12
  74. package/src/pages/system/role/rolePerm.jsx +25 -21
  75. package/src/pages/system/user/index.jsx +35 -33
  76. package/src/pages/system/user/userPerm.jsx +12 -12
  77. package/src/pages/userCenter/index.jsx +11 -9
  78. package/src/pages/userCenter/permView.jsx +7 -7
  79. package/src/framework/utils/system/HttpUtils.ts +0 -97
  80. 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": "^7.0.0",
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.15"
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 {HttpUtils} from "../utils";
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: any[];
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
- HttpUtils.get('admin/sysOrg/tree').then(tree => {
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={item => item.iconName ? <NamedIcon name={item.iconName}/> : null}
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?: 'small' | 'middle' | 'large';
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 {HttpUtils} from "../utils";
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: any[];
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
- HttpUtils.get('admin/sysRole/biz-tree').then(tree => {
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={item => <SolutionOutlined/>}
47
+ icon={renderRoleIcon}
48
48
  />
49
49
  }
50
50
  }
51
+
52
+ function renderRoleIcon() {
53
+ return <SolutionOutlined/>;
54
+ }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable react-refresh/only-export-components */
1
2
  import {FieldRemoteSelect, FieldRemoteTreeSelect} from "../fields";
2
3
  import React, {type ComponentProps} from "react";
3
4
 
@@ -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: item.danger ? '#ff4d4f' : item.disabled ? 'rgba(0,0,0,0.25)' : '#333',
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, any>;
21
- data?: Record<string, any>;
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: any) {
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 (e) {
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 && match[1] ? match[1].trim() : 'download.file';
154
+ let parsedName = match?.[1] ? match[1].trim() : 'download.file';
138
155
  try {
139
- parsedName = decodeURIComponent(parsedName.replace(/"/g, ''));
140
- } catch (e) {
141
- parsedName = parsedName.replace(/"/g, '');
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
- document.body.removeChild(link);
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: any = {
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: any) => {
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) : this.state.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: any) => {
245
+ axiosInstance(config).then((response) => {
229
246
  this.clearSpeedTimer();
230
247
  return this.saveBlob(response);
231
248
  }).then(() => {
232
- const total = this.state.total;
233
- this.setState({
249
+ this.setState((prev) => ({
234
250
  status: 'completed',
235
251
  progress: 100,
236
252
  speed: '',
237
- loaded: total || this.state.loaded,
238
- });
253
+ loaded: prev.total || prev.loaded,
254
+ }));
239
255
  this.props.onFinish?.();
240
- }).catch((error: any) => {
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, any>) => Promise<void> | void
7
- onValuesChange?: (changedValues: any, allValues: any) => void
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, any>
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, any> | null = null
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, any>) => {
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 && loaded.name === name) ? loaded.comp : iconCache.get(name);
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 {HttpUtils} from "../../utils";
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<{}, {tree: OrgNode[]; currentOrgId: string | null}> {
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
- HttpUtils.get("admin/myOrgs").then(data => {
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
- HttpUtils.post("admin/switchOrg", {orgId}).then(() => {
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 = any> {
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]: any;
22
+ [key: string]: unknown;
23
23
  };
24
24
  }
25
25
 
26
- export interface ProTableProps<T = any> {
27
- /** 数据请求,框架自动注入 page/size/sort 参数 */
28
- request: (params: Record<string, any>) => Promise<ProTableRequestResult<T>>;
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.MutableRefObject<ProTableActionRef | undefined>;
37
+ actionRef?: React.RefObject<ProTableActionRef | undefined>;
33
38
  /** 获取搜索表单实例 */
34
- formRef?: React.MutableRefObject<FormInstance | undefined>;
39
+ formRef?: React.RefObject<FormInstance | undefined>;
35
40
  /** 工具栏渲染,参数为当前搜索值与行选择状态 */
36
- toolBarRender?: (params: Record<string, any>, selection: {
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 = any> {
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, any>;
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]: any;
78
+ [key: string]: unknown;
74
79
  };
75
80
  scrollY: number | string | null;
76
81
  }
77
82
 
78
- export class ProTable<T = any> extends React.Component<ProTableProps<T>, ProTableState<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
- this.setState({loading: true})
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
- }).finally(() => {
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, extra) => {
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, any>) => {
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, any>) => {
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)
@@ -8,7 +8,7 @@ function getComponent(registry, type) {
8
8
  if (!type) {
9
9
  return
10
10
  }
11
- for (let key in registry) {
11
+ for (const key in registry) {
12
12
  if (key.toLowerCase() === type.toLowerCase()) {
13
13
  return registry[key]
14
14
  }