@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
@@ -0,0 +1,49 @@
1
+ import {history} from '../../router';
2
+ import {StringUtils} from "../StringUtils";
3
+ import {UrlUtils} from "../UrlUtils";
4
+ import {EventBus} from "../EventBus";
5
+
6
+ export class PageUtils {
7
+ static redirectToLogin(): void {
8
+ if (PageUtils.currentPathname() === '/public/login') {
9
+ return;
10
+ }
11
+ const url = PageUtils.currentUrl();
12
+ const encodedUrl = encodeURIComponent(url);
13
+ history.push(`/public/login?redirect=${encodedUrl}`);
14
+ }
15
+
16
+ static currentParams(): Record<string, string | undefined> {
17
+ return UrlUtils.getParams();
18
+ }
19
+
20
+ static currentPathname(): string {
21
+ const path = window.location.hash.substring(1);
22
+ return StringUtils.subBefore(path, '?');
23
+ }
24
+
25
+ static currentUrl(): string {
26
+ return window.location.hash.substring(1);
27
+ }
28
+
29
+ static currentPathnameLastPart(): string | undefined {
30
+ const path = this.currentPathname();
31
+ return StringUtils.subAfterLast(path, '/') || undefined;
32
+ }
33
+
34
+ static open(path: string, label = '未命名'): void {
35
+ let targetPath = path;
36
+ if (label) {
37
+ targetPath = UrlUtils.setParam(targetPath, '_label', label);
38
+ }
39
+ history.push(targetPath);
40
+ }
41
+
42
+ static currentLabel(): string | undefined {
43
+ return this.currentParams()['_label'];
44
+ }
45
+
46
+ static closeCurrent(): void {
47
+ EventBus.emit('closePage', PageUtils.currentUrl());
48
+ }
49
+ }
@@ -2,14 +2,13 @@ import {GlobalData} from '../../GlobalData';
2
2
  import {ArrayUtils} from '../ArrayUtils';
3
3
 
4
4
  export class PermUtils {
5
- static getPermissions() {
6
- const info = GlobalData.getLoginInfo();
7
- const { permissions } = info;
5
+ static getPermissions(): string[] {
6
+ const { permissions } = GlobalData.getLoginInfo();
8
7
  if (permissions == null || permissions.length === 0) return [];
9
8
  return permissions;
10
9
  }
11
10
 
12
- static hasPermission(perm) {
11
+ static hasPermission(perm: string | null | undefined): boolean {
13
12
  if (perm === null || perm === undefined || perm === '') return false;
14
13
  const permissions = PermUtils.getPermissions();
15
14
  if (permissions.length === 0) return false;
@@ -17,11 +16,11 @@ export class PermUtils {
17
16
  return permissions.indexOf(perm) > -1;
18
17
  }
19
18
 
20
- static isPermitted(p) {
19
+ static isPermitted(p: string | null | undefined): boolean {
21
20
  return PermUtils.hasPermission(p);
22
21
  }
23
22
 
24
- static notPermitted(p) {
23
+ static notPermitted(p: string | null | undefined): boolean {
25
24
  return !PermUtils.isPermitted(p);
26
25
  }
27
26
  }
@@ -2,5 +2,3 @@ export * from './DictUtils'
2
2
  export * from './PermUtils'
3
3
  export * from './PageUtils'
4
4
  export * from './HttpUtils'
5
- export * from './FormRegistry'
6
- export * from './ThemeUtils'
@@ -1,6 +1,9 @@
1
+ import React from 'react';
1
2
  import type {ViewProps} from "../types";
2
3
 
3
- export function ViewBoolean(props: ViewProps<boolean>) {
4
- const {value} = props;
5
- return value == null ? null : (value ? '是' : '否');
4
+ export class ViewBoolean extends React.Component<ViewProps<boolean>> {
5
+ render() {
6
+ const {value} = this.props;
7
+ return value == null ? null : (value ? '是' : '否');
8
+ }
6
9
  }
@@ -1,22 +1,27 @@
1
1
  import React from "react";
2
- import {Button} from "antd";
3
- import { MessageUtils } from "../../utils";
2
+ import {Button, Modal} from "antd";
4
3
  import { ViewFile } from "../ViewFile";
5
4
 
6
5
  /**
7
6
  * 文件按钮查看组件
8
7
  */
9
8
  export class ViewFileButton extends React.Component {
9
+ state = { open: false }
10
+
10
11
  render() {
11
- const {value, height} = this.props;
12
+ const {value} = this.props;
12
13
  if (!value) {
13
14
  return null;
14
15
  }
15
16
 
16
- return <Button type="link" size="small" onClick={()=>{
17
- MessageUtils.alert(<ViewFile value={this.props.value} />)
18
- }}>
19
- 查看文件
20
- </Button>;
17
+ return <>
18
+ <Button type="link" size="small" onClick={() => this.setState({open: true})}>
19
+ 查看文件
20
+ </Button>
21
+ <Modal open={this.state.open} title="文件预览" footer={null} width="80vw"
22
+ onCancel={() => this.setState({open: false})}>
23
+ <ViewFile value={this.props.value} />
24
+ </Modal>
25
+ </>;
21
26
  }
22
27
  }
@@ -5,6 +5,11 @@ import {UrlUtils} from '../../utils';
5
5
 
6
6
  export class ViewImage extends React.Component {
7
7
 
8
+ state = {
9
+ modalOpen: false,
10
+ previewUrl: null,
11
+ };
12
+
8
13
  render() {
9
14
  let vs = this.props.value
10
15
 
@@ -34,29 +39,29 @@ export class ViewImage extends React.Component {
34
39
  urlList.push(UrlUtils.contextPath(v));
35
40
  }
36
41
 
42
+ const closeModal = () => this.setState({modalOpen: false});
43
+
37
44
  const imgs = urlList.map((url) => (
38
45
  <img
39
46
  style={{ display: 'inline-block' }}
40
47
  key={url}
41
48
  src={url}
42
- onClick={() => this.preview(url)}
49
+ onClick={() => this.setState({modalOpen: true, previewUrl: url})}
43
50
  width={60}
44
51
  height={60}
45
52
  />
46
53
  ));
47
54
 
48
- return imgs;
55
+ return (
56
+ <>
57
+ {imgs}
58
+ <Modal open={this.state.modalOpen} title="预览图片" width="70vw" footer={null}
59
+ onCancel={closeModal}>
60
+ <div style={{maxHeight:'70vh',overflow:'auto'}}>
61
+ <img src={this.state.previewUrl} style={{maxWidth: '100%'}}/>
62
+ </div>
63
+ </Modal>
64
+ </>
65
+ );
49
66
  }
50
-
51
- preview = (url) => {
52
- Modal.info({
53
- title: '预览图片',
54
- width: '70vw',
55
- content: <div style={{maxHeight:'70vh',overflow:'auto'}}>
56
- <img src={url} style={{maxWidth: '100%'}}/>
57
- </div>,
58
-
59
- });
60
- };
61
-
62
67
  }
@@ -5,11 +5,14 @@ interface ViewRangeProps {
5
5
  max?: string;
6
6
  }
7
7
 
8
- export const ViewRange: React.FC<ViewRangeProps> = ({ min, max }) => {
9
- if ((min == null || min === "") && (max == null || max === "")) {
10
- return null;
8
+ export class ViewRange extends React.Component<ViewRangeProps> {
9
+ render() {
10
+ const { min, max } = this.props;
11
+ if ((min == null || min === "") && (max == null || max === "")) {
12
+ return null;
13
+ }
14
+ const showMin = min ?? '未知';
15
+ const showMax = max ?? '未知';
16
+ return <>{showMin} - {showMax}</>;
11
17
  }
12
- const showMin = min ?? '未知';
13
- const showMax = max ?? '未知';
14
- return <>{showMin} - {showMax}</>;
15
- };
18
+ }
@@ -4,6 +4,10 @@ import {StringUtils} from "../../utils";
4
4
 
5
5
  export class ViewText extends React.Component {
6
6
 
7
+ state = {
8
+ modalOpen: false,
9
+ };
10
+
7
11
  static defaultProps = {
8
12
  ellipsis: false,
9
13
  maxLength: 15
@@ -23,20 +27,14 @@ export class ViewText extends React.Component {
23
27
  const short = StringUtils.ellipsis(value, maxLength);
24
28
  return (
25
29
  <Tooltip placement="topLeft" title={value}>
26
- <span style={{cursor: 'pointer', borderBottom: '1px dashed #d9d9d9'}} onClick={this.showModal}>
30
+ <span style={{cursor: 'pointer', borderBottom: '1px dashed #d9d9d9'}} onClick={() => this.setState({modalOpen: true})}>
27
31
  {short}
28
32
  </span>
33
+ <Modal open={this.state.modalOpen} title="长文本内容" width={700} footer={null}
34
+ onCancel={() => this.setState({modalOpen: false})}>
35
+ <div style={{height: 500, overflowY: 'auto'}}>{value}</div>
36
+ </Modal>
29
37
  </Tooltip>
30
38
  );
31
39
  }
32
-
33
- showModal = () => {
34
- const {value} = this.props;
35
- Modal.info({
36
- icon: null,
37
- title: '长文本内容',
38
- content: <div style={{height: 500, overflowY: 'auto'}}>{value}</div>,
39
- width: 700
40
- });
41
- };
42
40
  }
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import {CloseOutlined} from '@ant-design/icons';
3
+ import {ContextMenu} from '../../framework';
4
+ import './AdminTabs.less';
5
+
6
+ export class AdminTabs extends React.Component {
7
+ state = {
8
+ contextMenu: null,
9
+ contextMenuTabKey: null,
10
+ };
11
+
12
+ handleContextMenu = (e, key) => {
13
+ e.preventDefault();
14
+ this.setState({
15
+ contextMenu: {x: e.clientX, y: e.clientY},
16
+ contextMenuTabKey: key,
17
+ });
18
+ };
19
+
20
+ handleContextMenuClick = ({key: actionKey}) => {
21
+ const tabKey = this.state.contextMenuTabKey;
22
+ this.setState({contextMenu: null});
23
+ const {onRefresh, onClose, onCloseOthers, onCloseAll} = this.props;
24
+ if (actionKey === 'refresh') {
25
+ onRefresh(tabKey);
26
+ } else if (actionKey === 'close') {
27
+ onClose(tabKey);
28
+ } else if (actionKey === 'closeOthers') {
29
+ onCloseOthers(tabKey);
30
+ } else if (actionKey === 'closeAll') {
31
+ onCloseAll();
32
+ }
33
+ };
34
+
35
+ render() {
36
+ const {tabs, activeKey, onChange, onClose} = this.props;
37
+ const {contextMenu} = this.state;
38
+
39
+ return (
40
+ <>
41
+ <div className="admin-tabs-bar">
42
+ {tabs.map(tab => (
43
+ <div
44
+ key={tab.key}
45
+ className={`admin-tab${tab.key === activeKey ? ' active' : ''}`}
46
+ onClick={() => onChange(tab.key)}
47
+ onContextMenu={e => this.handleContextMenu(e, tab.key)}
48
+ >
49
+ <span className="admin-tab-label">{tab.label}</span>
50
+ {tabs.length > 1 && (
51
+ <CloseOutlined
52
+ className="admin-tab-close"
53
+ onClick={e => {
54
+ e.stopPropagation();
55
+ onClose(tab.key);
56
+ }}
57
+ />
58
+ )}
59
+ </div>
60
+ ))}
61
+ </div>
62
+ {contextMenu && (
63
+ <ContextMenu
64
+ x={contextMenu.x}
65
+ y={contextMenu.y}
66
+ items={[
67
+ {key: 'refresh', label: '刷新'},
68
+ {key: 'close', label: '关闭', disabled: tabs.length <= 1},
69
+ {key: 'closeOthers', label: '关闭其他', disabled: tabs.length <= 1},
70
+ {key: 'closeAll', label: '关闭全部'},
71
+ ]}
72
+ onClick={this.handleContextMenuClick}
73
+ onClose={() => this.setState({contextMenu: null})}
74
+ />
75
+ )}
76
+ </>
77
+ );
78
+ }
79
+ }
@@ -0,0 +1,63 @@
1
+ .admin-tabs-bar {
2
+ display: flex;
3
+ flex-wrap: nowrap;
4
+ overflow-x: auto;
5
+ overflow-y: hidden;
6
+ flex-shrink: 0;
7
+ border-bottom: 1px solid #e8e8e8;
8
+ background: #fff;
9
+
10
+ &::-webkit-scrollbar {
11
+ height: 0;
12
+ }
13
+
14
+ .admin-tab {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ gap: 4px;
18
+ padding: 0 12px;
19
+ height: 32px;
20
+ line-height: 32px;
21
+ font-size: 13px;
22
+ cursor: pointer;
23
+ border-right: 1px solid #e8e8e8;
24
+ white-space: nowrap;
25
+ color: rgba(0, 0, 0, 0.65);
26
+ background: #fafafa;
27
+ transition: color 0.2s, background 0.2s;
28
+ position: relative;
29
+ flex-shrink: 0;
30
+
31
+ &:hover {
32
+ color: var(--primary-color);
33
+ }
34
+
35
+ &.active {
36
+ color: var(--primary-color);
37
+ background: #fff;
38
+ font-weight: 500;
39
+ }
40
+
41
+ .admin-tab-label {
42
+ max-width: 120px;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ }
46
+
47
+ .admin-tab-close {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ width: 16px;
52
+ height: 16px;
53
+ font-size: 12px;
54
+ border-radius: 50%;
55
+ color: rgba(0, 0, 0, 0.45);
56
+
57
+ &:hover {
58
+ background: rgba(0, 0, 0, 0.08);
59
+ color: rgba(0, 0, 0, 0.88);
60
+ }
61
+ }
62
+ }
63
+ }
@@ -1,7 +1,6 @@
1
- import {Avatar, Dropdown} from "antd";
1
+ import {Avatar, Dropdown, Modal} from "antd";
2
2
  import React from "react";
3
- import {history} from "umi";
4
- import {DeviceUtils, HttpUtils, MessageUtils, PageUtils, GlobalData, ThemeUtils} from "../../framework";
3
+ import {DeviceUtils, HttpUtils, PageUtils, GlobalData, getToken, history, ARTICLE_HEADER_AVATAR_DROPDOWN, ARTICLE_HEADER_RIGHT} from "../../framework";
5
4
 
6
5
  export class HeaderRight extends React.Component {
7
6
 
@@ -9,6 +8,8 @@ export class HeaderRight extends React.Component {
9
8
  isMobileDevice: false,
10
9
  dropdownArticles: [],
11
10
  headerArticles: [],
11
+ alertVisible: false,
12
+ confirmVisible: false,
12
13
  };
13
14
 
14
15
  componentDidMount() {
@@ -21,23 +22,18 @@ export class HeaderRight extends React.Component {
21
22
  loadArticles = () => {
22
23
  const siteArticles = GlobalData.getSiteArticles();
23
24
  this.setState({
24
- dropdownArticles: siteArticles['HEADER_AVATAR_DROPDOWN'] || [],
25
- headerArticles: siteArticles['HEADER_RIGHT'] || [],
25
+ dropdownArticles: siteArticles[ARTICLE_HEADER_AVATAR_DROPDOWN] || [],
26
+ headerArticles: siteArticles[ARTICLE_HEADER_RIGHT] || [],
26
27
  })
27
28
  }
28
29
 
29
30
  logout = () => {
30
31
  HttpUtils.post('admin/auth/logout').then(async () => {
31
32
  localStorage.clear()
32
- await MessageUtils.alert('退出登录成功');
33
- history.replace('/login')
33
+ this.setState({alertVisible: true})
34
34
  }).catch(async e => {
35
35
  console.error('[HeaderRight] 退出登录失败:', e);
36
- const confirm = await MessageUtils.confirm('退出登录失败,是否清空缓存');
37
- if (confirm) {
38
- localStorage.clear();
39
- history.replace('/login')
40
- }
36
+ this.setState({confirmVisible: true})
41
37
  })
42
38
  }
43
39
 
@@ -93,12 +89,30 @@ export class HeaderRight extends React.Component {
93
89
  items: menuItems,
94
90
  }}>
95
91
  <div className='item' style={{cursor: 'pointer'}}>
96
- <Avatar size="default" style={{backgroundColor: ThemeUtils.getColor('primary-color')}}>
92
+ <Avatar size="default" style={{backgroundColor: getToken().colorPrimary}}>
97
93
  {info.name?.charAt(0)}
98
94
  </Avatar>
99
95
  <span style={{marginLeft: 8}}>{info.name}</span>
100
96
  </div>
101
97
  </Dropdown>
98
+
99
+ <Modal open={this.state.alertVisible} title="提示" okText="确定"
100
+ onCancel={() => this.setState({alertVisible: false})}
101
+ onOk={() => {
102
+ this.setState({alertVisible: false});
103
+ history.replace('/public/login')
104
+ }}>
105
+ 退出登录成功
106
+ </Modal>
107
+ <Modal open={this.state.confirmVisible} title="确认操作" okText="确定" cancelText="取消"
108
+ onCancel={() => this.setState({confirmVisible: false})}
109
+ onOk={() => {
110
+ this.setState({confirmVisible: false});
111
+ localStorage.clear();
112
+ history.replace('/public/login')
113
+ }}>
114
+ 退出登录失败,是否清空缓存
115
+ </Modal>
102
116
  </div>
103
117
  }
104
118
  }
@@ -0,0 +1,124 @@
1
+ import React from "react";
2
+ import {history, PageFrame, UrlUtils, EventBus} from "../../framework";
3
+ import {AdminTabs} from "./AdminTabs";
4
+
5
+ function getLabel(pathname, search, pathMenuMap) {
6
+ if (pathname === '/') return '首页';
7
+ const params = UrlUtils.getParams(search);
8
+ if (params._label) return params._label;
9
+ const menu = pathMenuMap[pathname];
10
+ if (menu) return menu.name;
11
+ return '未命名';
12
+ }
13
+
14
+ export class TabLayout extends React.Component {
15
+ constructor(props) {
16
+ super(props);
17
+ const initUrl = history.location.pathname + history.location.search;
18
+ const qIndex = initUrl.indexOf('?');
19
+ const initPathname = qIndex === -1 ? initUrl : initUrl.substring(0, qIndex);
20
+ const initSearch = qIndex === -1 ? '' : initUrl.substring(qIndex);
21
+ const initLabel = getLabel(initPathname, initSearch, props.pathMenuMap);
22
+ this.state = {
23
+ tabs: [{key: initUrl, label: initLabel, refreshKey: 0}],
24
+ activeKey: initUrl,
25
+ };
26
+ this.activeKey = initUrl;
27
+ }
28
+
29
+ unlisten = null;
30
+ unsubscribeClosePage = null;
31
+
32
+ openTab = (url) => {
33
+ const qIndex = url.indexOf('?');
34
+ const pathname = qIndex === -1 ? url : url.substring(0, qIndex);
35
+ const search = qIndex === -1 ? '' : url.substring(qIndex);
36
+ this.setState(prev => ({
37
+ tabs: prev.tabs.find(t => t.key === url) ? prev.tabs
38
+ : [...prev.tabs, {key: url, label: getLabel(pathname, search, this.props.pathMenuMap), refreshKey: 0}],
39
+ activeKey: url,
40
+ }));
41
+ };
42
+
43
+ closeTab = (key) => {
44
+ this.setState(prev => {
45
+ if (!prev.tabs.find(t => t.key === key)) return null;
46
+ const next = prev.tabs.filter(t => t.key !== key);
47
+ if (next.length === 0) {
48
+ setTimeout(() => history.push('/'), 0);
49
+ } else if (key === this.activeKey) {
50
+ const fallback = next[next.length - 1].key;
51
+ setTimeout(() => history.push(fallback), 0);
52
+ }
53
+ return {tabs: next};
54
+ });
55
+ };
56
+
57
+ refreshTab = (key) => {
58
+ this.setState(prev => ({
59
+ tabs: prev.tabs.map(t => t.key === key ? {...t, refreshKey: t.refreshKey + 1} : t),
60
+ }));
61
+ };
62
+
63
+ handleClosePageEvent = (url) => {
64
+ this.closeTab(url);
65
+ };
66
+
67
+ handleCloseOthers = (key) => {
68
+ this.setState(prev => ({tabs: prev.tabs.filter(t => t.key === key)}));
69
+ if (key !== this.activeKey) {
70
+ history.push(key);
71
+ }
72
+ };
73
+
74
+ handleCloseAll = () => {
75
+ this.setState({tabs: []});
76
+ setTimeout(() => history.push('/'), 0);
77
+ };
78
+
79
+ componentDidMount() {
80
+ this.unlisten = history.listen(({location}) => {
81
+ this.openTab(location.pathname + location.search);
82
+ });
83
+ this.unsubscribeClosePage = EventBus.on('closePage', this.handleClosePageEvent);
84
+ }
85
+
86
+ componentDidUpdate() {
87
+ this.activeKey = this.state.activeKey;
88
+ }
89
+
90
+ componentWillUnmount() {
91
+ if (this.unlisten) {
92
+ this.unlisten();
93
+ }
94
+ if (this.unsubscribeClosePage) {
95
+ this.unsubscribeClosePage();
96
+ }
97
+ }
98
+
99
+ render() {
100
+ const {tabs, activeKey} = this.state;
101
+
102
+ return (
103
+ <div style={{display: 'flex', flexDirection: 'column', height: '100%'}}>
104
+ <AdminTabs
105
+ tabs={tabs.map(t => ({key: t.key, label: t.label}))}
106
+ activeKey={activeKey}
107
+ onChange={key => history.push(key)}
108
+ onClose={this.closeTab}
109
+ onRefresh={this.refreshTab}
110
+ onCloseOthers={this.handleCloseOthers}
111
+ onCloseAll={this.handleCloseAll}
112
+ />
113
+ <div style={{flex: 1, overflow: 'hidden'}}>
114
+ {tabs.map(t => (
115
+ <div key={t.key} style={{display: t.key === activeKey ? 'block' : 'none', height: '100%'}}>
116
+ <PageFrame key={t.refreshKey} url={t.key}/>
117
+ </div>
118
+ ))}
119
+ </div>
120
+ </div>
121
+ );
122
+ }
123
+ }
124
+
@@ -1,12 +1,11 @@
1
1
  import React from 'react';
2
2
  import {Layout, Menu, Skeleton, Watermark} from 'antd';
3
3
 
4
- import {history, Link} from 'umi';
5
4
  import "./index.less"
6
- import {HttpUtils, NamedIcon, PageUtils, GlobalData, ThemeUtils, TreeUtils} from "../../framework";
5
+ import {HttpUtils, NamedIcon, PageUtils, GlobalData, TreeUtils, history, Link, ARTICLE_HEADER_LEFT} from "../../framework";
7
6
 
8
7
  import { HeaderRight } from "./HeaderRight";
9
- import { TabPageRender } from "./TabPageRender";
8
+ import { TabLayout } from "./TabLayout";
10
9
  const {Header, Sider, Content} = Layout;
11
10
  /**
12
11
  * 带菜单的布局,主要处理布局宇框架结构
@@ -45,7 +44,7 @@ export default class extends React.Component {
45
44
  this.initMenu()
46
45
 
47
46
  const siteArticles = GlobalData.getSiteArticles();
48
- this.setState({headerLeftArticles: siteArticles['HEADER_LEFT'] || []})
47
+ this.setState({headerLeftArticles: siteArticles[ARTICLE_HEADER_LEFT] || []})
49
48
  }
50
49
 
51
50
 
@@ -176,7 +175,7 @@ export default class extends React.Component {
176
175
  render() {
177
176
  const {siteInfo, loginInfo} = this.state
178
177
 
179
- return <Layout className='main-layout' style={{'--primary-color': ThemeUtils.getColor("primary-color"), '--primary-color-click': ThemeUtils.getColor("primary-color-click"), '--primary-color-hover': ThemeUtils.getColor("primary-color-hover")}}>
178
+ return <Layout className='main-layout'>
180
179
  <Sider id='left-sider'
181
180
  width={180}
182
181
  collapsible
@@ -229,7 +228,7 @@ export default class extends React.Component {
229
228
  renderCenterContent = () => {
230
229
  const {siteInfo, loginInfo} = this.state
231
230
  // 即使菜单为空也直接显示页面内容,不要一直显示 Skeleton
232
- const tabPageRenderNode = <TabPageRender pathMenuMap={this.state.pathMenuMap}/>;
231
+ const tabPageRenderNode = <TabLayout pathMenuMap={this.state.pathMenuMap}/>;
233
232
  if (siteInfo.waterMark === true) {
234
233
  return <Watermark content={[loginInfo.name, loginInfo.account]}>
235
234
  {tabPageRenderNode}