@jiangood/open-admin 2.5.8 → 3.0.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 (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 +66 -33
  67. package/src/layouts/admin/TabLayout.jsx +124 -0
  68. package/src/layouts/admin/index.jsx +12 -5
  69. package/src/layouts/admin/index.less +5 -83
  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 +43 -0
  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 -32
  78. package/src/pages/system/article/index.jsx +147 -0
  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 -2237
  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 -45
  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 -8
  113. package/src/framework/GlobalData.js +0 -33
  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 -119
  140. package/src/loading.jsx +0 -19
  141. package/src/pages/about.jsx +0 -11
  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,34 +1,39 @@
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} 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
 
8
7
  state = {
9
- isMobileDevice: false
8
+ isMobileDevice: false,
9
+ dropdownArticles: [],
10
+ headerArticles: [],
11
+ alertVisible: false,
12
+ confirmVisible: false,
10
13
  };
11
14
 
12
15
  componentDidMount() {
13
16
  if (DeviceUtils.isMobileDevice()) {
14
17
  this.setState({isMobileDevice: true})
15
18
  }
19
+ this.loadArticles()
16
20
  }
17
21
 
18
-
22
+ loadArticles = () => {
23
+ const siteArticles = GlobalData.getSiteArticles();
24
+ this.setState({
25
+ dropdownArticles: siteArticles[ARTICLE_HEADER_AVATAR_DROPDOWN] || [],
26
+ headerArticles: siteArticles[ARTICLE_HEADER_RIGHT] || [],
27
+ })
28
+ }
19
29
 
20
30
  logout = () => {
21
31
  HttpUtils.post('admin/auth/logout').then(async () => {
22
32
  localStorage.clear()
23
- await MessageUtils.alert('退出登录成功');
24
- history.replace('/login')
33
+ this.setState({alertVisible: true})
25
34
  }).catch(async e => {
26
35
  console.error('[HeaderRight] 退出登录失败:', e);
27
- const confirm = await MessageUtils.confirm('退出登录失败,是否清空缓存');
28
- if (confirm) {
29
- localStorage.clear();
30
- history.replace('/login')
31
- }
36
+ this.setState({confirmVisible: true})
32
37
  })
33
38
  }
34
39
 
@@ -36,8 +41,13 @@ export class HeaderRight extends React.Component {
36
41
  PageUtils.open('/account', '个人中心')
37
42
  }
38
43
 
44
+ openArticle = (code, title) => {
45
+ PageUtils.open('/article/' + code, title)
46
+ }
47
+
39
48
  render() {
40
49
  const info = GlobalData.getLoginInfo()
50
+ const {dropdownArticles, headerArticles} = this.state
41
51
 
42
52
  if (this.state.isMobileDevice) {
43
53
  return <div className='header-right'>
@@ -45,41 +55,64 @@ export class HeaderRight extends React.Component {
45
55
  </div>
46
56
  }
47
57
 
58
+ const articleItems = dropdownArticles.map(a => ({
59
+ key: 'article:' + a.code,
60
+ label: a.title,
61
+ }))
62
+
63
+ const menuItems = [
64
+ {key: 'account', label: '个人中心'},
65
+ ...articleItems,
66
+ {key: 'logout', label: '退出登录'},
67
+ ]
68
+
48
69
  return <div className='header-right'>
70
+ {headerArticles.map(a => (
71
+ <div key={a.code} className='item' style={{cursor: 'pointer'}}
72
+ onClick={() => this.openArticle(a.code, a.title)}>
73
+ {a.title}
74
+ </div>
75
+ ))}
49
76
 
50
77
  <Dropdown menu={{
51
78
  onClick: ({key}) => {
52
- switch (key) {
53
- case 'account':
54
- this.account()
55
- break;
56
- case 'logout':
57
- this.logout();
58
- break;
59
- case 'about':
60
- this.about()
61
- break
79
+ if (key === 'account') {
80
+ this.account()
81
+ } else if (key === 'logout') {
82
+ this.logout();
83
+ } else if (key.startsWith('article:')) {
84
+ const code = key.substring(8)
85
+ const article = dropdownArticles.find(a => a.code === code)
86
+ this.openArticle(code, article ? article.title : code)
62
87
  }
63
88
  },
64
- items: [
65
- {key: 'account', label: '个人中心'},
66
- {key: 'about', label: '关于系统'},
67
- {key: 'logout', label: '退出登录'},
68
- ]
89
+ items: menuItems,
69
90
  }}>
70
91
  <div className='item' style={{cursor: 'pointer'}}>
71
- <Avatar size="small" style={{backgroundColor: '#1677ff', verticalAlign: 'middle'}}>
92
+ <Avatar size="default" style={{backgroundColor: getToken().colorPrimary}}>
72
93
  {info.name?.charAt(0)}
73
94
  </Avatar>
74
95
  <span style={{marginLeft: 8}}>{info.name}</span>
75
96
  </div>
76
97
  </Dropdown>
77
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>
78
116
  </div>
79
117
  }
80
-
81
-
82
- about = () => {
83
- history.push("/about")
84
- }
85
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
+