@jiangood/open-admin 2.5.9 → 3.0.1

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 (142) hide show
  1. package/package.json +20 -8
  2. package/src/framework/GlobalData.ts +85 -0
  3. package/src/framework/biz/OrgTree.tsx +2 -7
  4. package/src/framework/biz/Perm.tsx +11 -0
  5. package/src/framework/biz/PermActions.tsx +18 -15
  6. package/src/framework/biz/index.tsx +34 -19
  7. package/src/framework/components/ContextMenu/index.tsx +105 -78
  8. package/src/framework/components/DownloadModal/index.tsx +15 -17
  9. package/src/framework/components/ErrorBoundary/index.tsx +2 -2
  10. package/src/framework/components/FormModal/index.tsx +76 -0
  11. package/src/framework/components/Gap/index.tsx +1 -1
  12. package/src/framework/components/NamedIcon/index.tsx +55 -9
  13. package/src/framework/components/Page/index.less +0 -1
  14. package/src/framework/components/Page/index.tsx +34 -27
  15. package/src/framework/components/PageLoading/index.less +15 -0
  16. package/src/framework/components/PageLoading/index.tsx +32 -16
  17. package/src/framework/components/ProTable/index.tsx +86 -15
  18. package/src/framework/components/index.ts +1 -0
  19. package/src/framework/config/index.ts +1 -0
  20. package/src/framework/config/themeConfig.ts +39 -0
  21. package/src/framework/fields/FieldBoolean/index.tsx +11 -2
  22. package/src/framework/fields/FieldDate/index.tsx +22 -7
  23. package/src/framework/fields/FieldDateRange/index.tsx +24 -6
  24. package/src/framework/fields/FieldDictSelect/index.tsx +1 -0
  25. package/src/framework/fields/FieldEditor/index.tsx +7 -1
  26. package/src/framework/fields/FieldNumberRange/index.tsx +14 -5
  27. package/src/framework/fields/FieldPercent/index.tsx +16 -5
  28. package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -2
  29. package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +2 -1
  30. package/src/framework/fields/FieldSysOrgTree/index.tsx +6 -1
  31. package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +6 -1
  32. package/src/framework/fields/FieldTable/index.tsx +35 -6
  33. package/src/framework/fields/FieldTableSelect/index.tsx +18 -2
  34. package/src/framework/fields/FieldUploadFile/index.tsx +69 -28
  35. package/src/framework/fields/index.ts +0 -1
  36. package/src/framework/index.ts +2 -0
  37. package/src/framework/router/Link.tsx +26 -0
  38. package/src/framework/router/PageFrame.tsx +31 -0
  39. package/src/framework/router/history.ts +50 -0
  40. package/src/framework/router/index.ts +4 -0
  41. package/src/framework/router/matcher.ts +58 -0
  42. package/src/framework/utils/{ArrayUtils.js → ArrayUtils.ts} +5 -5
  43. package/src/framework/utils/{DateUtils.js → DateUtils.ts} +19 -19
  44. package/src/framework/utils/{DeviceUtils.js → DeviceUtils.ts} +8 -1
  45. package/src/framework/utils/{EventBus.js → EventBus.ts} +13 -6
  46. package/src/framework/utils/MessageApi.ts +5 -0
  47. package/src/framework/utils/ModalApi.ts +5 -0
  48. package/src/framework/utils/ObjectUtils.ts +15 -0
  49. package/src/framework/utils/{StorageUtils.js → StorageUtils.ts} +7 -7
  50. package/src/framework/utils/{StringUtils.js → StringUtils.ts} +15 -15
  51. package/src/framework/utils/{TreeUtils.js → TreeUtils.ts} +19 -12
  52. package/src/framework/utils/{UrlUtils.js → UrlUtils.ts} +10 -10
  53. package/src/framework/utils/index.ts +2 -1
  54. package/src/framework/utils/system/{DictUtils.js → DictUtils.ts} +16 -4
  55. package/src/framework/utils/system/{HttpUtils.js → HttpUtils.ts} +25 -21
  56. package/src/framework/utils/system/PageUtils.ts +49 -0
  57. package/src/framework/utils/system/{PermUtils.js → PermUtils.ts} +5 -6
  58. package/src/framework/utils/system/index.ts +0 -2
  59. package/src/framework/views/ViewBoolean/index.tsx +6 -3
  60. package/src/framework/views/ViewFileButton/index.tsx +13 -8
  61. package/src/framework/views/ViewImage/index.tsx +19 -14
  62. package/src/framework/views/ViewRange/index.tsx +10 -7
  63. package/src/framework/views/ViewText/index.tsx +9 -11
  64. package/src/layouts/admin/AdminTabs.jsx +79 -0
  65. package/src/layouts/admin/AdminTabs.less +63 -0
  66. package/src/layouts/admin/HeaderRight.jsx +27 -13
  67. package/src/layouts/admin/TabLayout.jsx +124 -0
  68. package/src/layouts/admin/index.jsx +5 -6
  69. package/src/layouts/admin/index.less +1 -82
  70. package/src/layouts/index.tsx +148 -0
  71. package/src/main.jsx +8 -0
  72. package/src/pages/account/changePassword.jsx +14 -10
  73. package/src/pages/article/$code.jsx +2 -2
  74. package/src/pages/index.jsx +6 -4
  75. package/src/pages/{login.jsx → public/login.jsx} +7 -10
  76. package/src/pages/public/test.jsx +30 -0
  77. package/src/pages/{forceUpdatePwd.jsx → standalone/forceUpdatePwd.jsx} +3 -24
  78. package/src/pages/system/article/index.jsx +51 -56
  79. package/src/pages/system/dict/index.jsx +60 -91
  80. package/src/pages/system/file/index.jsx +23 -17
  81. package/src/pages/system/job/index.jsx +63 -72
  82. package/src/pages/system/log/index.jsx +11 -8
  83. package/src/pages/system/org/index.jsx +60 -80
  84. package/src/pages/system/role/index.jsx +50 -86
  85. package/src/pages/system/role/rolePerm.jsx +2 -2
  86. package/src/pages/system/user/index.jsx +120 -73
  87. package/src/pages/system/user/userPerm.jsx +2 -2
  88. package/vite-plugin/index.js +107 -0
  89. package/config/common-plugin.js +0 -145
  90. package/config/config.js +0 -5
  91. package/config/utils.js +0 -73
  92. package/src/.umi-production/appData.json +0 -2257
  93. package/src/.umi-production/core/EmptyRoute.tsx +0 -9
  94. package/src/.umi-production/core/defineApp.ts +0 -16
  95. package/src/.umi-production/core/helmet.ts +0 -10
  96. package/src/.umi-production/core/helmetContext.ts +0 -4
  97. package/src/.umi-production/core/history.ts +0 -72
  98. package/src/.umi-production/core/historyIntelli.ts +0 -132
  99. package/src/.umi-production/core/plugin.ts +0 -40
  100. package/src/.umi-production/core/pluginConfig.ts +0 -330
  101. package/src/.umi-production/core/pluginConfigJoi.d.ts +0 -7
  102. package/src/.umi-production/core/polyfill.ts +0 -220
  103. package/src/.umi-production/core/route.tsx +0 -47
  104. package/src/.umi-production/core/routeProps.js +0 -5
  105. package/src/.umi-production/core/routeProps.ts +0 -6
  106. package/src/.umi-production/core/terminal.ts +0 -37
  107. package/src/.umi-production/exports.ts +0 -17
  108. package/src/.umi-production/testBrowser.tsx +0 -90
  109. package/src/.umi-production/tsconfig.json +0 -43
  110. package/src/.umi-production/typings.d.ts +0 -136
  111. package/src/.umi-production/umi.ts +0 -83
  112. package/src/framework/GlobalData.d.ts +0 -10
  113. package/src/framework/GlobalData.js +0 -42
  114. package/src/framework/biz/HasPerm.tsx +0 -14
  115. package/src/framework/fields/FieldRemoteSelectMultiple/index.tsx +0 -29
  116. package/src/framework/utils/ArrayUtils.d.ts +0 -6
  117. package/src/framework/utils/DateUtils.d.ts +0 -20
  118. package/src/framework/utils/DeviceUtils.d.ts +0 -3
  119. package/src/framework/utils/EventBus.d.ts +0 -6
  120. package/src/framework/utils/MessageUtils.d.ts +0 -10
  121. package/src/framework/utils/MessageUtils.js +0 -43
  122. package/src/framework/utils/ObjectUtils.d.ts +0 -14
  123. package/src/framework/utils/ObjectUtils.js +0 -12
  124. package/src/framework/utils/StorageUtils.d.ts +0 -4
  125. package/src/framework/utils/StringUtils.d.ts +0 -17
  126. package/src/framework/utils/TreeUtils.d.ts +0 -13
  127. package/src/framework/utils/UrlUtils.d.ts +0 -6
  128. package/src/framework/utils/system/DictUtils.d.ts +0 -20
  129. package/src/framework/utils/system/FormRegistry.d.ts +0 -8
  130. package/src/framework/utils/system/FormRegistry.js +0 -31
  131. package/src/framework/utils/system/HttpUtils.d.ts +0 -14
  132. package/src/framework/utils/system/PageUtils.d.ts +0 -14
  133. package/src/framework/utils/system/PageUtils.js +0 -59
  134. package/src/framework/utils/system/PermUtils.d.ts +0 -5
  135. package/src/framework/utils/system/ThemeUtils.d.ts +0 -6
  136. package/src/framework/utils/system/ThemeUtils.js +0 -52
  137. package/src/layouts/PageRender.tsx +0 -29
  138. package/src/layouts/admin/TabPageRender.jsx +0 -210
  139. package/src/layouts/index.jsx +0 -120
  140. package/src/loading.jsx +0 -19
  141. package/src/pages/about.jsx +0 -16
  142. /package/src/pages/{login.less → public/login.less} +0 -0
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "devDependencies": {
3
+ "@playwright/test": "^1.62.0",
3
4
  "@types/qs": "^6.15.1",
4
5
  "@types/react": "^19.2.2",
5
6
  "@types/react-dom": "^19.0.0",
7
+ "@vitejs/plugin-react": "^6.0.0",
6
8
  "babel-plugin-react-compiler": "^1.0.0",
7
- "typescript": "^6.0.3"
9
+ "less": "^4.2.0",
10
+ "typescript": "^7.0.0",
11
+ "vite": "^8.0.0"
8
12
  },
9
13
  "peerDependencies": {
10
14
  "@ant-design/icons": "^6.0.0",
@@ -16,8 +20,7 @@
16
20
  "lodash": "^4.17.23",
17
21
  "qs": "^6.14.0",
18
22
  "react": "^19.0.0",
19
- "react-dom": "^19.0.0",
20
- "umi": "^4.0.0"
23
+ "react-dom": "^19.0.0"
21
24
  },
22
25
  "name": "@jiangood/open-admin",
23
26
  "repository": {
@@ -25,16 +28,25 @@
25
28
  "url": "https://github.com/jiangood/open-admin"
26
29
  },
27
30
  "description": "open-admin",
31
+ "type": "module",
28
32
  "files": [
29
33
  "src/*",
30
- "config/*",
31
- "!src/.umi",
34
+ "vite-plugin/*",
32
35
  "!src/pages/test/*"
33
36
  ],
34
37
  "main": "src/index.ts",
38
+ "exports": {
39
+ ".": "./src/index.ts",
40
+ "./vite-plugin": "./vite-plugin/index.js",
41
+ "./src/*": "./src/*",
42
+ "./package.json": "./package.json"
43
+ },
35
44
  "scripts": {
36
- "dev": "umi dev",
37
- "build": "umi build"
45
+ "dev": "vite",
46
+ "build": "vite build",
47
+ "preview": "vite preview",
48
+ "test:e2e": "playwright test",
49
+ "test:e2e:ui": "playwright test --ui"
38
50
  },
39
- "version": "2.5.9"
51
+ "version": "3.0.1"
40
52
  }
@@ -0,0 +1,85 @@
1
+ import {StorageUtils} from './utils/StorageUtils';
2
+
3
+ const SITE_INFO_KEY = "siteInfo";
4
+ const LOGIN_INFO_KEY = "loginInfo";
5
+ const DICT_INFO_KEY = "dictInfo";
6
+ const SITE_ARTICLES_KEY = "siteArticles";
7
+
8
+ export interface SiteInfo {
9
+ title?: string;
10
+ waterMark?: boolean;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ export interface LoginInfo {
15
+ name?: string;
16
+ account?: string;
17
+ phone?: string;
18
+ email?: string;
19
+ unit?: string;
20
+ dept?: string;
21
+ roles?: string;
22
+ createTime?: string;
23
+ permissions?: string[];
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ export interface DictItem {
28
+ code: string | number;
29
+ label: string;
30
+ color?: string;
31
+ typeCode?: string;
32
+ }
33
+
34
+ export interface ArticleItem {
35
+ code: string;
36
+ title: string;
37
+ [key: string]: unknown;
38
+ }
39
+
40
+ export interface SiteArticles {
41
+ HEADER_AVATAR_DROPDOWN?: ArticleItem[];
42
+ HEADER_RIGHT?: ArticleItem[];
43
+ HEADER_LEFT?: ArticleItem[];
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ export class GlobalData {
48
+ static setSiteInfo(data: SiteInfo): void {
49
+ StorageUtils.set(SITE_INFO_KEY, data);
50
+ }
51
+
52
+ static getSiteInfo(): SiteInfo {
53
+ return StorageUtils.get<SiteInfo>(SITE_INFO_KEY) || {};
54
+ }
55
+
56
+ static setLoginInfo(data: LoginInfo): void {
57
+ StorageUtils.set(LOGIN_INFO_KEY, data);
58
+ }
59
+
60
+ static getLoginInfo(): LoginInfo {
61
+ return StorageUtils.get<LoginInfo>(LOGIN_INFO_KEY) || {};
62
+ }
63
+
64
+ static setDictInfo(data: DictItem[] | null): void {
65
+ StorageUtils.set(DICT_INFO_KEY, data);
66
+ }
67
+
68
+ static getDictInfo(): DictItem[] | null {
69
+ return StorageUtils.get<DictItem[]>(DICT_INFO_KEY);
70
+ }
71
+
72
+ static setSiteArticles(data: SiteArticles): void {
73
+ StorageUtils.set(SITE_ARTICLES_KEY, data);
74
+ }
75
+
76
+ static getSiteArticles(): SiteArticles {
77
+ return StorageUtils.get<SiteArticles>(SITE_ARTICLES_KEY) || {};
78
+ }
79
+ }
80
+
81
+ export const ARTICLE_HEADER_LEFT = 'HEADER_LEFT';
82
+ export const ARTICLE_HEADER_RIGHT = 'HEADER_RIGHT';
83
+ export const ARTICLE_HEADER_AVATAR_DROPDOWN = 'HEADER_AVATAR_DROPDOWN';
84
+
85
+ export { SITE_INFO_KEY, LOGIN_INFO_KEY, DICT_INFO_KEY, SITE_ARTICLES_KEY };
@@ -1,7 +1,7 @@
1
1
  import {Alert, Skeleton, Tree} from 'antd';
2
2
  import React from 'react';
3
- import * as Icons from '@ant-design/icons';
4
3
  import {HttpUtils} from "../utils";
4
+ import {NamedIcon} from "../components/NamedIcon";
5
5
 
6
6
  interface OrgTreeProps {
7
7
  onChange?: (orgId: string | null) => void;
@@ -48,12 +48,7 @@ export class OrgTree extends React.Component<OrgTreeProps, OrgTreeState> {
48
48
  onSelect={this.onSelectOrg}
49
49
  showIcon
50
50
  blockNode
51
- icon={item => {
52
- const icon = Icons[item.iconName]
53
- if (icon) {
54
- return React.createElement(icon)
55
- }
56
- }}
51
+ icon={item => item.iconName ? <NamedIcon name={item.iconName}/> : null}
57
52
  />
58
53
  }
59
54
  }
@@ -0,0 +1,11 @@
1
+ import React, {ReactNode} from "react";
2
+ import {PermUtils} from "../utils";
3
+
4
+ export class Perm extends React.Component<{ code: string, children: ReactNode }> {
5
+ render() {
6
+ if (PermUtils.hasPermission(this.props.code)) {
7
+ return this.props.children;
8
+ }
9
+ return null;
10
+ }
11
+ }
@@ -2,22 +2,25 @@ import React from 'react';
2
2
  import { Space } from 'antd';
3
3
  import { PermUtils } from '../utils';
4
4
 
5
- /**
6
- * 带权限的操作区。根据子元素的 `perm` 属性控制显隐,默认用 Space 包裹。
7
- * @param gap - 是否在操作项之间增加间距,默认 true
8
- */
9
- export function PermActions(props) {
10
- const { children, gap = true } = props;
5
+ interface PermActionsProps {
6
+ gap?: boolean;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ export class PermActions extends React.Component<PermActionsProps> {
11
+ render() {
12
+ const { children, gap = true } = this.props;
11
13
 
12
- const checkPerm = (element) => {
13
- const _props = element?.props;
14
- return !_props?.perm || PermUtils.hasPermission(_props.perm);
15
- };
14
+ const checkPerm = (element) => {
15
+ const _props = element?.props;
16
+ return !_props?.perm || PermUtils.hasPermission(_props.perm);
17
+ };
16
18
 
17
- const nodes = React.Children.toArray(children).filter(
18
- (child) => child != null && checkPerm(child)
19
- );
19
+ const nodes = React.Children.toArray(children).filter(
20
+ (child) => child != null && checkPerm(child)
21
+ );
20
22
 
21
- if (!gap) return <>{nodes}</>;
22
- return <Space>{nodes}</Space>;
23
+ if (!gap) return <>{nodes}</>;
24
+ return <Space>{nodes}</Space>;
25
+ }
23
26
  }
@@ -1,31 +1,46 @@
1
- import {FieldRemoteSelect, FieldRemoteSelectMultiple, FieldRemoteTreeSelect} from "../fields";
2
- import React from "react";
1
+ import {FieldRemoteSelect, FieldRemoteTreeSelect} from "../fields";
2
+ import React, {type ComponentProps} from "react";
3
3
 
4
4
  export * from './PermActions'
5
- export * from './HasPerm'
5
+ export * from './Perm'
6
6
  export * from './OrgTree'
7
7
  export * from './RoleTree'
8
8
 
9
- export function FieldUserSelect(props) {
10
- return <FieldRemoteSelect url="admin/sysUser/options" {...props} />;
9
+ class InnerFieldUserSelect extends React.Component<ComponentProps<typeof FieldRemoteSelect>> {
10
+ render() {
11
+ return <FieldRemoteSelect url="admin/sysUser/options" {...this.props} />;
12
+ }
11
13
  }
12
- export function FieldUserSelectMultiple(props) {
13
- return <FieldRemoteSelectMultiple url="admin/sysUser/options" {...props} />;
14
+ class InnerFieldUserSelectMultiple extends React.Component<ComponentProps<typeof FieldRemoteSelect>> {
15
+ render() {
16
+ return <FieldRemoteSelect url="admin/sysUser/options" multiple {...this.props} />;
17
+ }
14
18
  }
15
-
16
- export function FieldUnitTreeSelect(props) {
17
- return <FieldRemoteTreeSelect url="admin/sysOrg/unit-tree" {...props} />;
19
+ class InnerFieldUnitTreeSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
20
+ render() {
21
+ return <FieldRemoteTreeSelect url="admin/sysOrg/unit-tree" {...this.props} />;
22
+ }
18
23
  }
19
-
20
- export function FieldDeptTreeSelect(props) {
21
- return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...props} />;
24
+ class InnerFieldDeptTreeSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
25
+ render() {
26
+ return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...this.props} />;
27
+ }
22
28
  }
23
-
24
- export function FieldOrgTreeSelect(props) {
25
- return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...props} />;
29
+ class InnerFieldOrgTreeSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
30
+ render() {
31
+ return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...this.props} />;
32
+ }
26
33
  }
27
-
28
- export function FieldOrgTreeMultipleSelect(props) {
29
- return <FieldRemoteSelectMultiple url="/sysOrg/dept-tree" {...props} />;
34
+ class InnerFieldOrgTreeMultipleSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
35
+ render() {
36
+ return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" multiple {...this.props} />;
37
+ }
30
38
  }
31
39
 
40
+ export const FieldUserSelect = InnerFieldUserSelect;
41
+ export const FieldUserSelectMultiple = InnerFieldUserSelectMultiple;
42
+ export const FieldUnitTreeSelect = InnerFieldUnitTreeSelect;
43
+ export const FieldDeptTreeSelect = InnerFieldDeptTreeSelect;
44
+ export const FieldOrgTreeSelect = InnerFieldOrgTreeSelect;
45
+ export const FieldOrgTreeMultipleSelect = InnerFieldOrgTreeMultipleSelect;
46
+
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef } from 'react';
1
+ import React from 'react';
2
2
 
3
3
  interface ContextMenuItem {
4
4
  key: string;
@@ -17,83 +17,110 @@ interface ContextMenuProps {
17
17
  onClose: () => void;
18
18
  }
19
19
 
20
- export const ContextMenu: React.FC<ContextMenuProps> = ({ x, y, items, onClick, onClose }) => {
21
- const ref = useRef<HTMLDivElement>(null);
20
+ export class ContextMenu extends React.Component<ContextMenuProps> {
21
+ menuRef = React.createRef<HTMLDivElement>();
22
+ timer: ReturnType<typeof setTimeout> | null = null;
22
23
 
23
- useEffect(() => {
24
- const handleMouseDown = (e: MouseEvent) => {
25
- if (ref.current && !ref.current.contains(e.target as Node)) {
26
- onClose();
27
- }
28
- };
29
- const handleScroll = () => onClose();
30
- const handleKeyDown = (e: KeyboardEvent) => {
31
- if (e.key === 'Escape') onClose();
32
- };
33
- const timer = setTimeout(() => {
34
- document.addEventListener('mousedown', handleMouseDown);
35
- document.addEventListener('scroll', handleScroll, true);
36
- document.addEventListener('keydown', handleKeyDown);
24
+ handleMouseDown = (e: MouseEvent) => {
25
+ if (this.menuRef.current && !this.menuRef.current.contains(e.target as Node)) {
26
+ this.props.onClose();
27
+ }
28
+ };
29
+
30
+ handleScroll = () => {
31
+ this.props.onClose();
32
+ };
33
+
34
+ handleKeyDown = (e: KeyboardEvent) => {
35
+ if (e.key === 'Escape') this.props.onClose();
36
+ };
37
+
38
+ componentDidMount() {
39
+ this.timer = setTimeout(() => {
40
+ document.addEventListener('mousedown', this.handleMouseDown);
41
+ document.addEventListener('scroll', this.handleScroll, true);
42
+ document.addEventListener('keydown', this.handleKeyDown);
37
43
  }, 0);
38
- return () => {
39
- clearTimeout(timer);
40
- document.removeEventListener('mousedown', handleMouseDown);
41
- document.removeEventListener('scroll', handleScroll, true);
42
- document.removeEventListener('keydown', handleKeyDown);
43
- };
44
- }, [onClose]);
44
+ }
45
+
46
+ componentDidUpdate(prevProps: ContextMenuProps) {
47
+ if (prevProps.onClose !== this.props.onClose) {
48
+ if (this.timer !== null) clearTimeout(this.timer);
49
+ document.removeEventListener('mousedown', this.handleMouseDown);
50
+ document.removeEventListener('scroll', this.handleScroll, true);
51
+ document.removeEventListener('keydown', this.handleKeyDown);
52
+ this.timer = setTimeout(() => {
53
+ document.addEventListener('mousedown', this.handleMouseDown);
54
+ document.addEventListener('scroll', this.handleScroll, true);
55
+ document.addEventListener('keydown', this.handleKeyDown);
56
+ }, 0);
57
+ }
58
+ }
45
59
 
46
- return (
47
- <div
48
- ref={ref}
49
- style={{
50
- position: 'fixed',
51
- left: x,
52
- top: y,
53
- zIndex: 1050,
54
- background: '#fff',
55
- borderRadius: 4,
56
- padding: '4px 0',
57
- minWidth: 120,
58
- boxShadow: '0 6px 16px 0 rgba(0,0,0,0.08), 0 3px 6px -4px rgba(0,0,0,0.12), 0 9px 28px 8px rgba(0,0,0,0.05)',
59
- }}
60
- >
61
- {items.map(item => (
62
- item.divider ? (
63
- <div key={item.key} style={{ height: 1, background: '#f0f0f0', margin: '4px 0' }} />
64
- ) : (
65
- <div
66
- key={item.key}
67
- onClick={() => {
68
- if (!item.disabled) {
69
- onClick({ key: item.key });
70
- onClose();
71
- }
72
- }}
73
- style={{
74
- padding: '5px 12px',
75
- fontSize: 13,
76
- cursor: item.disabled ? 'not-allowed' : 'pointer',
77
- color: item.danger ? '#ff4d4f' : item.disabled ? 'rgba(0,0,0,0.25)' : '#333',
78
- display: 'flex',
79
- alignItems: 'center',
80
- gap: 8,
81
- background: 'transparent',
82
- transition: 'background 0.15s',
83
- userSelect: 'none',
84
- }}
85
- onMouseEnter={(e) => {
86
- if (!item.disabled) e.currentTarget.style.background = '#f5f5f5';
87
- }}
88
- onMouseLeave={(e) => {
89
- e.currentTarget.style.background = 'transparent';
90
- }}
91
- >
92
- {item.icon && <span style={{ fontSize: 14, lineHeight: 1 }}>{item.icon}</span>}
93
- <span>{item.label}</span>
94
- </div>
95
- )
96
- ))}
97
- </div>
98
- );
99
- };
60
+ componentWillUnmount() {
61
+ if (this.timer !== null) {
62
+ clearTimeout(this.timer);
63
+ }
64
+ document.removeEventListener('mousedown', this.handleMouseDown);
65
+ document.removeEventListener('scroll', this.handleScroll, true);
66
+ document.removeEventListener('keydown', this.handleKeyDown);
67
+ }
68
+
69
+ render() {
70
+ const { x, y, items, onClick, onClose } = this.props;
71
+
72
+ return (
73
+ <div
74
+ ref={this.menuRef}
75
+ style={{
76
+ position: 'fixed',
77
+ left: x,
78
+ top: y,
79
+ zIndex: 1050,
80
+ background: '#fff',
81
+ borderRadius: 4,
82
+ padding: '4px 0',
83
+ minWidth: 120,
84
+ boxShadow: '0 6px 16px 0 rgba(0,0,0,0.08), 0 3px 6px -4px rgba(0,0,0,0.12), 0 9px 28px 8px rgba(0,0,0,0.05)',
85
+ }}
86
+ >
87
+ {items.map(item => (
88
+ item.divider ? (
89
+ <div key={item.key} style={{ height: 1, background: '#f0f0f0', margin: '4px 0' }} />
90
+ ) : (
91
+ <div
92
+ key={item.key}
93
+ onClick={() => {
94
+ if (!item.disabled) {
95
+ onClick({ key: item.key });
96
+ onClose();
97
+ }
98
+ }}
99
+ style={{
100
+ padding: '5px 12px',
101
+ fontSize: 13,
102
+ cursor: item.disabled ? 'not-allowed' : 'pointer',
103
+ color: item.danger ? '#ff4d4f' : item.disabled ? 'rgba(0,0,0,0.25)' : '#333',
104
+ display: 'flex',
105
+ alignItems: 'center',
106
+ gap: 8,
107
+ background: 'transparent',
108
+ transition: 'background 0.15s',
109
+ userSelect: 'none',
110
+ }}
111
+ onMouseEnter={(e) => {
112
+ if (!item.disabled) e.currentTarget.style.background = '#f5f5f5';
113
+ }}
114
+ onMouseLeave={(e) => {
115
+ e.currentTarget.style.background = 'transparent';
116
+ }}
117
+ >
118
+ {item.icon && <span style={{ fontSize: 14, lineHeight: 1 }}>{item.icon}</span>}
119
+ <span>{item.label}</span>
120
+ </div>
121
+ )
122
+ ))}
123
+ </div>
124
+ );
125
+ }
126
+ }
@@ -9,7 +9,7 @@ import axios from "axios";
9
9
  import qs from 'qs';
10
10
 
11
11
  const axiosInstance = axios.create({
12
- baseURL: (typeof SERVLET_CONTEXT !== 'undefined' && SERVLET_CONTEXT) || '',
12
+ baseURL: import.meta.env.VITE_SERVER_SERVLET_CONTEXT_PATH,
13
13
  withCredentials: true,
14
14
  headers: {'Content-Type': 'application/json'},
15
15
  paramsSerializer: (params) => qs.stringify(params, {indices: false})
@@ -34,15 +34,19 @@ interface ModalState {
34
34
  errorMessage: string;
35
35
  }
36
36
 
37
- export class DownloadModal extends React.Component<{}, ModalState> {
38
- static instance: DownloadModal | null = null;
37
+ export interface DownloadModalProps {
38
+ title?: string;
39
+ onFinish?: () => void;
40
+ }
41
+
42
+ export class DownloadModal extends React.Component<DownloadModalProps, ModalState> {
39
43
  private abortController: AbortController | null = null;
40
44
  private lastOptions: DownloadOptions | null = null;
41
45
  private lastTime: number = 0;
42
46
  private lastLoaded: number = 0;
43
47
  private speedTimer: ReturnType<typeof setInterval> | null = null;
44
48
 
45
- constructor(props: {}) {
49
+ constructor(props: DownloadModalProps) {
46
50
  super(props);
47
51
  this.state = {
48
52
  open: false,
@@ -54,21 +58,14 @@ export class DownloadModal extends React.Component<{}, ModalState> {
54
58
  speed: '',
55
59
  errorMessage: '',
56
60
  };
57
- DownloadModal.instance = this;
58
61
  }
59
62
 
60
- static download(options: DownloadOptions) {
61
- const instance = DownloadModal.instance;
62
- if (instance) {
63
- instance.startDownload(options);
64
- } else {
65
- console.warn('[DownloadModal] 组件未挂载,download() 调用被忽略');
66
- }
67
- }
63
+ download = (options: DownloadOptions) => {
64
+ this.startDownload(options);
65
+ };
68
66
 
69
67
  componentWillUnmount() {
70
68
  this.clearSpeedTimer();
71
- DownloadModal.instance = null;
72
69
  }
73
70
 
74
71
  private clearSpeedTimer() {
@@ -236,6 +233,7 @@ export class DownloadModal extends React.Component<{}, ModalState> {
236
233
  speed: '',
237
234
  loaded: total || this.state.loaded,
238
235
  });
236
+ this.props.onFinish?.();
239
237
  }).catch((error: any) => {
240
238
  this.clearSpeedTimer();
241
239
  if (axios.isCancel(error)) {
@@ -260,9 +258,9 @@ export class DownloadModal extends React.Component<{}, ModalState> {
260
258
 
261
259
  return (
262
260
  <Modal
263
- title={<span><DownloadOutlined style={{marginRight: 8}}/>文件下载</span>}
261
+ title={<span><DownloadOutlined style={{marginRight: 8}}/>{this.props.title || '文件下载'}</span>}
264
262
  open={open}
265
- maskClosable={false}
263
+ mask={{closable: false}}
266
264
  closable={status !== 'downloading'}
267
265
  onCancel={this.handleClose}
268
266
  footer={
@@ -275,7 +273,7 @@ export class DownloadModal extends React.Component<{}, ModalState> {
275
273
  </>
276
274
  ) : null
277
275
  }
278
- destroyOnClose
276
+ destroyOnHidden
279
277
  >
280
278
  <div style={{padding: '20px 0'}}>
281
279
  {/* 文件名 */}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import {Button, Collapse, Result} from 'antd';
3
- import {ThemeUtils} from '../../utils';
3
+ import {getToken} from '../../config';
4
4
 
5
5
  interface Props {
6
6
  children: React.ReactNode;
@@ -69,7 +69,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
69
69
  fontSize: 12, fontFamily: 'monospace',
70
70
  whiteSpace: 'pre-wrap', wordBreak: 'break-all',
71
71
  background: '#f5f5f5', padding: 12, borderRadius: 4,
72
- color: ThemeUtils.getColor('error-color') || '#ff4d4f',
72
+ color: getToken().colorError,
73
73
  };
74
74
 
75
75
  return (
@@ -0,0 +1,76 @@
1
+ import {Form, FormInstance, Modal} from 'antd'
2
+ import React from 'react'
3
+
4
+ export interface FormModalProps {
5
+ title: string
6
+ onFinish?: (values: Record<string, any>) => Promise<void> | void
7
+ onValuesChange?: (changedValues: any, allValues: any) => void
8
+ width?: number
9
+ labelCol?: Record<string, any>
10
+ children?: React.ReactNode
11
+ }
12
+
13
+ export default class FormModal extends React.Component<FormModalProps> {
14
+
15
+ state = {
16
+ visible: false,
17
+ confirmLoading: false,
18
+ }
19
+
20
+ private pendingValues: Record<string, any> | null = null
21
+ private formRef = React.createRef<FormInstance>()
22
+
23
+ get formInstance() {
24
+ return this.formRef.current
25
+ }
26
+
27
+ open = (values?: Record<string, any>) => {
28
+ this.pendingValues = values || {}
29
+ this.setState({visible: true})
30
+ }
31
+
32
+ componentDidUpdate(_: Readonly<FormModalProps>, prevState: Readonly<{ visible: boolean; confirmLoading: boolean }>) {
33
+ if (this.state.visible && !prevState.visible) {
34
+ setTimeout(() => {
35
+ if (this.pendingValues && Object.keys(this.pendingValues).length > 0) {
36
+ this.formRef.current?.setFieldsValue(this.pendingValues)
37
+ } else {
38
+ this.formRef.current?.resetFields()
39
+ }
40
+ this.pendingValues = null
41
+ })
42
+ }
43
+ }
44
+
45
+ private handleOk = async () => {
46
+ this.setState({confirmLoading: true})
47
+ try {
48
+ const values = await this.formRef.current?.validateFields()
49
+ if (values == null) return
50
+ await this.props.onFinish?.(values)
51
+ this.setState({visible: false})
52
+ } finally {
53
+ this.setState({confirmLoading: false})
54
+ }
55
+ }
56
+
57
+ render() {
58
+ return (
59
+ <Modal
60
+ title={this.props.title}
61
+ open={this.state.visible}
62
+ confirmLoading={this.state.confirmLoading}
63
+ onOk={this.handleOk}
64
+ onCancel={() => this.setState({visible: false})}
65
+ destroyOnHidden
66
+ width={this.props.width ?? 600}
67
+ >
68
+ <Form ref={this.formRef} labelCol={this.props.labelCol ?? {flex: '100px'}}
69
+ onValuesChange={this.props.onValuesChange}>
70
+ <Form.Item name='id' noStyle/>
71
+ {this.props.children}
72
+ </Form>
73
+ </Modal>
74
+ )
75
+ }
76
+ }