@jiangood/open-admin 2.0.1 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/common-plugin.js +51 -0
- package/config/config.js +1 -56
- package/package.json +5 -2
- package/src/.umi-production/appData.json +73 -100
- package/src/.umi-production/umi.ts +2 -2
- package/src/framework/components/DownloadFileButton/index.tsx +1 -1
- package/src/framework/components/OrgTree/index.tsx +1 -1
- package/src/framework/components/Page/index.less +1 -1
- package/src/framework/components/Page/index.tsx +19 -22
- package/src/framework/components/PageLoading/index.tsx +5 -3
- package/src/framework/components/ProModal/index.tsx +2 -2
- package/src/framework/components/ProTable/components/ToolBar/index.tsx +1 -1
- package/src/framework/fields/FieldEditor/index.tsx +3 -2
- package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +3 -3
- package/src/framework/fields/FieldRemoteTreeSelectMultiple/index.tsx +3 -3
- package/src/framework/fields/FieldUploadFile/index.tsx +8 -4
- package/src/framework/utils/MessageUtils.tsx +21 -99
- package/src/framework/utils/system/HttpUtils.ts +5 -1
- package/src/framework/utils/system/SysUtils.ts +8 -0
- package/src/framework/utils/system/ThemeUtils.ts +37 -12
- package/src/framework/views/ViewFile/index.tsx +1 -1
- package/src/framework/views/ViewProcessInstanceProgress/index.tsx +1 -0
- package/src/layouts/PageRender.tsx +29 -0
- package/src/layouts/admin/HeaderRight.jsx +1 -0
- package/src/layouts/admin/TabPageRender.jsx +138 -158
- package/src/layouts/admin/index.jsx +123 -19
- package/src/layouts/admin/index.less +124 -6
- package/src/layouts/index.jsx +94 -161
- package/src/pages/404.jsx +7 -5
- package/src/pages/about.jsx +3 -3
- package/src/pages/index.jsx +4 -5
- package/src/pages/login.jsx +18 -9
- package/src/pages/login.less +0 -1
- package/src/pages/system/dict/index.jsx +8 -11
- package/src/pages/system/file/index.jsx +6 -6
- package/src/pages/system/job/index.jsx +26 -11
- package/src/pages/system/log/index.jsx +4 -5
- package/src/pages/system/manual/index.jsx +8 -6
- package/src/pages/system/org/index.jsx +4 -2
- package/src/pages/system/role/index.jsx +16 -14
- package/src/pages/system/role/perm.jsx +3 -4
- package/src/pages/system/user/UserPerm.jsx +1 -1
- package/src/pages/system/user/index.jsx +9 -7
- package/src/pages/userCenter/index.jsx +2 -2
- package/src/pages/userCenter/message.jsx +4 -2
- package/src/layouts/PageRender.d.ts +0 -22
- package/src/layouts/PageRender.jsx +0 -90
|
@@ -1,173 +1,153 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
import {Tabs} from "antd";
|
|
1
|
+
import React, {useState, useEffect, useRef} from "react";
|
|
2
|
+
import {history} from "umi";
|
|
4
3
|
import {PageRender} from "../PageRender";
|
|
5
|
-
import {
|
|
4
|
+
import {UrlUtils} from "../../framework";
|
|
6
5
|
|
|
7
6
|
const MAX_TABS = 20;
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
accessOrder = [];
|
|
18
|
-
|
|
19
|
-
componentDidMount() {
|
|
20
|
-
const url = this.getUrl(this.props)
|
|
21
|
-
this.onUrlChange(url);
|
|
22
|
-
|
|
23
|
-
document.addEventListener('close-page-event', (e)=>{
|
|
24
|
-
const url = e.detail.url
|
|
25
|
-
this.onRemove(url)
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
componentDidUpdate(prevProps, prevState, snapshot) {
|
|
30
|
-
const url = this.getUrl(this.props)
|
|
31
|
-
const pUrl = this.getUrl(prevProps)
|
|
32
|
-
|
|
33
|
-
if (url !== pUrl) {
|
|
34
|
-
this.onUrlChange(url);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
recordAccess(url) {
|
|
39
|
-
this.accessOrder = this.accessOrder.filter(k => k !== url);
|
|
40
|
-
this.accessOrder.push(url);
|
|
41
|
-
}
|
|
8
|
+
function getLabel(pathname, search, pathMenuMap) {
|
|
9
|
+
if (pathname === '/') return '首页';
|
|
10
|
+
const params = UrlUtils.getParams(search);
|
|
11
|
+
if (params._label) return params._label;
|
|
12
|
+
const menu = pathMenuMap[pathname];
|
|
13
|
+
if (menu) return menu.name;
|
|
14
|
+
return '临时';
|
|
15
|
+
}
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
export function TabPageRender({pathMenuMap}) {
|
|
18
|
+
const [tabs, setTabs] = useState([]);
|
|
19
|
+
const [activeKey, setActiveKey] = useState(null);
|
|
20
|
+
const accessOrderRef = useRef([]);
|
|
21
|
+
const lastClickTimeRef = useRef({});
|
|
22
|
+
const activeKeyRef = useRef(activeKey);
|
|
23
|
+
activeKeyRef.current = activeKey;
|
|
24
|
+
const pathMenuMapRef = useRef(pathMenuMap);
|
|
25
|
+
pathMenuMapRef.current = pathMenuMap;
|
|
26
|
+
|
|
27
|
+
const recordAccess = (key) => {
|
|
28
|
+
accessOrderRef.current = accessOrderRef.current.filter(k => k !== key);
|
|
29
|
+
accessOrderRef.current.push(key);
|
|
30
|
+
};
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
// Use history.listen to monitor route changes
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const unlisten = history.listen(({location}) => {
|
|
35
|
+
const {pathname, search} = location;
|
|
36
|
+
const key = pathname + (search || '');
|
|
37
|
+
recordAccess(key);
|
|
38
|
+
|
|
39
|
+
setTabs(prev => {
|
|
40
|
+
if (prev.find(t => t.key === key)) return prev;
|
|
41
|
+
const content = <PageRender pathname={pathname}/>;
|
|
42
|
+
const label = getLabel(pathname, search, pathMenuMapRef.current);
|
|
43
|
+
const next = [...prev, {key, label, closable: true, content}];
|
|
44
|
+
if (next.length > MAX_TABS) {
|
|
45
|
+
const keep = new Set([key, ...accessOrderRef.current.slice(-3)]);
|
|
46
|
+
const removeIdx = next.findIndex(t => !keep.has(t.key));
|
|
47
|
+
if (removeIdx > -1) next.splice(removeIdx, 1);
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
56
50
|
});
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
setActiveKey(key);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Trigger for initial route
|
|
56
|
+
const {pathname, search} = history.location;
|
|
57
|
+
const key = pathname + (search || '');
|
|
58
|
+
recordAccess(key);
|
|
59
|
+
setTabs([{key, label: getLabel(pathname, search, pathMenuMap), closable: false, content: <PageRender pathname={pathname}/>}]);
|
|
60
|
+
setActiveKey(key);
|
|
61
|
+
|
|
62
|
+
return unlisten;
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
// Listen for close-page-event (stable listener, no re-registration)
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const handler = (e) => {
|
|
68
|
+
const url = e.detail.url;
|
|
69
|
+
setTabs(prev => {
|
|
70
|
+
const next = prev.filter(t => t.key !== url);
|
|
71
|
+
accessOrderRef.current = accessOrderRef.current.filter(k => k !== url);
|
|
72
|
+
if (url === activeKeyRef.current && next.length > 0) {
|
|
73
|
+
history.push(next[next.length - 1].key);
|
|
63
74
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const {tabs} = this.state
|
|
97
|
-
if (tabs.length === 0) {
|
|
98
|
-
return null
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return <>
|
|
102
|
-
<Tabs
|
|
103
|
-
items={tabs}
|
|
104
|
-
activeKey={this.state.active}
|
|
105
|
-
onChange={this.onChange}
|
|
106
|
-
onEdit={this.onRemove}
|
|
107
|
-
|
|
108
|
-
hideAdd
|
|
109
|
-
size='small'
|
|
110
|
-
type='editable-card'
|
|
111
|
-
style={{background: 'white'}}
|
|
112
|
-
rootClassName='tmgg-layout-tabs'
|
|
113
|
-
|
|
114
|
-
onTabClick={this.onTabClick}
|
|
115
|
-
|
|
116
|
-
>
|
|
117
|
-
</Tabs>
|
|
118
|
-
</>
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
lastTabClickTime = 0
|
|
122
|
-
onTabClick = (key, event) => {
|
|
123
|
-
const now = new Date().getTime();
|
|
124
|
-
const doubleClick = now - this.lastTabClickTime < 300;
|
|
125
|
-
if (doubleClick) {
|
|
126
|
-
this.refresh(key);
|
|
75
|
+
return next;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener('close-page-event', handler);
|
|
79
|
+
return () => document.removeEventListener('close-page-event', handler);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const handleTabClick = (key) => {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const last = lastClickTimeRef.current[key] || 0;
|
|
85
|
+
lastClickTimeRef.current[key] = now;
|
|
86
|
+
if (now - last < 300) {
|
|
87
|
+
// Double click → refresh
|
|
88
|
+
setTabs(prev => {
|
|
89
|
+
const idx = prev.findIndex(t => t.key === key);
|
|
90
|
+
if (idx === -1) return prev;
|
|
91
|
+
const originalContent = prev[idx].content;
|
|
92
|
+
const next = [...prev];
|
|
93
|
+
next[idx] = {...next[idx], content: '刷新中...'};
|
|
94
|
+
requestAnimationFrame(() => {
|
|
95
|
+
setTabs(prev2 => {
|
|
96
|
+
const idx2 = prev2.findIndex(t => t.key === key);
|
|
97
|
+
if (idx2 === -1) return prev2;
|
|
98
|
+
const next2 = [...prev2];
|
|
99
|
+
next2[idx2] = {...next2[idx2], content: originalContent};
|
|
100
|
+
return next2;
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
return next;
|
|
104
|
+
});
|
|
105
|
+
} else if (key !== activeKey) {
|
|
106
|
+
history.push(key);
|
|
127
107
|
}
|
|
128
|
-
|
|
129
|
-
this.lastTabClickTime = now
|
|
130
108
|
};
|
|
131
109
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if(
|
|
138
|
-
|
|
139
|
-
this.setState({tabs}, () => {
|
|
140
|
-
tab.children = old
|
|
141
|
-
this.setState({tabs})
|
|
142
|
-
})
|
|
110
|
+
const handleClose = (key, e) => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
setTabs(prev => {
|
|
113
|
+
const next = prev.filter(t => t.key !== key);
|
|
114
|
+
accessOrderRef.current = accessOrderRef.current.filter(k => k !== key);
|
|
115
|
+
if (key === activeKeyRef.current && next.length > 0) {
|
|
116
|
+
history.push(next[next.length - 1].key);
|
|
143
117
|
}
|
|
144
|
-
|
|
118
|
+
return next;
|
|
119
|
+
});
|
|
145
120
|
};
|
|
146
121
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
122
|
+
if (tabs.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="oa-tabs">
|
|
126
|
+
<div className="oa-tab-bar">
|
|
127
|
+
{tabs.map(tab => (
|
|
128
|
+
<div
|
|
129
|
+
key={tab.key}
|
|
130
|
+
className={'oa-tab' + (tab.key === activeKey ? ' active' : '')}
|
|
131
|
+
onClick={() => handleTabClick(tab.key)}
|
|
132
|
+
>
|
|
133
|
+
<span className="oa-tab-label">{tab.label}</span>
|
|
134
|
+
{tab.closable && tabs.length > 1 && (
|
|
135
|
+
<span className="oa-tab-close" onClick={(e) => handleClose(tab.key, e)}>×</span>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
<div className="oa-tabs-content">
|
|
141
|
+
{tabs.map(tab => (
|
|
142
|
+
<div
|
|
143
|
+
key={tab.key}
|
|
144
|
+
style={{display: tab.key === activeKey ? undefined : 'none'}}
|
|
145
|
+
className="oa-tab-pane"
|
|
146
|
+
>
|
|
147
|
+
{tab.content}
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -25,6 +25,9 @@ export default class extends React.Component {
|
|
|
25
25
|
|
|
26
26
|
currentMenuKey: null,
|
|
27
27
|
|
|
28
|
+
topMenus: [],
|
|
29
|
+
sideMenus: [],
|
|
30
|
+
activeTopMenuKey: null,
|
|
28
31
|
|
|
29
32
|
siteInfo: {},
|
|
30
33
|
|
|
@@ -55,24 +58,130 @@ export default class extends React.Component {
|
|
|
55
58
|
item.icon = <NamedIcon name={item.icon || 'AppstoreOutlined'} style={{fontSize: 12}}/>
|
|
56
59
|
})
|
|
57
60
|
|
|
61
|
+
const {topMenus} = this.classifyMenus(menuTree);
|
|
62
|
+
|
|
63
|
+
// 确定当前活跃的顶部菜单和侧边菜单
|
|
64
|
+
let activeTopMenuKey = null;
|
|
65
|
+
let sideMenus = [];
|
|
66
|
+
let currentMenuKey = null;
|
|
67
|
+
|
|
58
68
|
if (pathname !== "" && pathname !== "/") {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
69
|
+
const menuDef = pathMenuMap[pathname];
|
|
70
|
+
if (menuDef) {
|
|
71
|
+
currentMenuKey = menuDef.id;
|
|
72
|
+
const matched = this.findActiveTopMenu(topMenus, menuDef, menuMap);
|
|
73
|
+
if (matched) {
|
|
74
|
+
activeTopMenuKey = matched.key;
|
|
75
|
+
sideMenus = matched.children || [];
|
|
76
|
+
}
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
// 默认选中第一个顶部菜单
|
|
81
|
+
if (!activeTopMenuKey && topMenus.length > 0) {
|
|
82
|
+
activeTopMenuKey = topMenus[0].key;
|
|
83
|
+
sideMenus = topMenus[0].children || [];
|
|
84
|
+
if (!currentMenuKey && sideMenus.length > 0) {
|
|
85
|
+
currentMenuKey = sideMenus[0].key;
|
|
86
|
+
if (sideMenus[0].path) {
|
|
87
|
+
history.push(sideMenus[0].path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.setState({menuTree, pathMenuMap, topMenus, sideMenus, activeTopMenuKey, currentMenuKey})
|
|
66
93
|
|
|
67
94
|
this.loadBadge(menuMap)
|
|
95
|
+
}).catch(err => {
|
|
96
|
+
console.error('加载菜单失败:', err)
|
|
97
|
+
this.setState({menuTree: [], topMenus: [], sideMenus: []})
|
|
68
98
|
}).finally(()=>{
|
|
69
99
|
this.setState({menuLoading: false})
|
|
70
100
|
})
|
|
101
|
+
}
|
|
102
|
+
actionRef = React.createRef()
|
|
103
|
+
|
|
104
|
+
classifyMenus = (menuTree) => {
|
|
105
|
+
const topMenus = [];
|
|
106
|
+
const leafRootNodes = [];
|
|
71
107
|
|
|
108
|
+
(menuTree || []).forEach(node => {
|
|
109
|
+
const isDir = node.type === 'directory' || (node.children && node.children.length > 0);
|
|
110
|
+
if (isDir) {
|
|
111
|
+
topMenus.push(node);
|
|
112
|
+
} else {
|
|
113
|
+
leafRootNodes.push(node);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (leafRootNodes.length > 0) {
|
|
118
|
+
const defaultGroup = {
|
|
119
|
+
key: '_default_group',
|
|
120
|
+
label: '默认',
|
|
121
|
+
title: '默',
|
|
122
|
+
icon: <NamedIcon name="AppstoreOutlined" style={{fontSize: 12}}/>,
|
|
123
|
+
children: leafRootNodes,
|
|
124
|
+
type: 'directory',
|
|
125
|
+
};
|
|
126
|
+
topMenus.unshift(defaultGroup);
|
|
127
|
+
}
|
|
72
128
|
|
|
129
|
+
return {topMenus};
|
|
73
130
|
}
|
|
74
|
-
actionRef = React.createRef()
|
|
75
131
|
|
|
132
|
+
findActiveTopMenu = (topMenus, menuDef, menuMap) => {
|
|
133
|
+
if (!menuDef) return null;
|
|
134
|
+
|
|
135
|
+
// 从 menuDef 的 pid 链向上查找匹配的 topMenu
|
|
136
|
+
let pid = menuDef.pid || menuDef.parentKey;
|
|
137
|
+
while (pid) {
|
|
138
|
+
const match = topMenus.find(t => t.key === pid);
|
|
139
|
+
if (match) return match;
|
|
140
|
+
const parent = menuMap[pid];
|
|
141
|
+
pid = parent ? (parent.pid || parent.parentKey) : null;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onTopMenuClick = ({key}) => {
|
|
147
|
+
const topMenu = this.state.topMenus.find(t => t.key === key);
|
|
148
|
+
if (!topMenu) return;
|
|
149
|
+
|
|
150
|
+
const sideMenus = topMenu.children || [];
|
|
151
|
+
this.setState({activeTopMenuKey: key, sideMenus});
|
|
152
|
+
|
|
153
|
+
if (sideMenus.length > 0 && sideMenus[0].path) {
|
|
154
|
+
this.setState({currentMenuKey: sideMenus[0].key});
|
|
155
|
+
history.push(sideMenus[0].path);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
onLeftMenuClick = ({key}) => {
|
|
160
|
+
const menu = this.state.menuMap[key];
|
|
161
|
+
if (!menu) return;
|
|
162
|
+
const {path} = menu;
|
|
163
|
+
this.setState({currentMenuKey: key});
|
|
164
|
+
history.push(path);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
renderTopMenu = () => {
|
|
168
|
+
if (this.state.menuLoading) return null;
|
|
169
|
+
const {topMenus, activeTopMenuKey} = this.state;
|
|
170
|
+
if (!topMenus || topMenus.length === 0) return null;
|
|
171
|
+
return (
|
|
172
|
+
<nav className="top-nav">
|
|
173
|
+
{topMenus.map(item => (
|
|
174
|
+
<span
|
|
175
|
+
key={item.key}
|
|
176
|
+
className={'top-nav-item' + (item.key === activeTopMenuKey ? ' active' : '')}
|
|
177
|
+
onClick={() => this.onTopMenuClick({key: item.key})}
|
|
178
|
+
>
|
|
179
|
+
{item.label}
|
|
180
|
+
</span>
|
|
181
|
+
))}
|
|
182
|
+
</nav>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
76
185
|
|
|
77
186
|
loadBadge = menuMap => {
|
|
78
187
|
for (let id in menuMap) {
|
|
@@ -88,7 +197,8 @@ export default class extends React.Component {
|
|
|
88
197
|
menu.icon = <Badge dot count={rs} size={"small"}>{menu.icon}</Badge>
|
|
89
198
|
this.setState({menuTree: [...menuTree]})
|
|
90
199
|
}
|
|
91
|
-
|
|
200
|
+
}).catch(e => {
|
|
201
|
+
console.error('[Layout] 加载消息数失败:', e);
|
|
92
202
|
})
|
|
93
203
|
}
|
|
94
204
|
};
|
|
@@ -97,7 +207,7 @@ export default class extends React.Component {
|
|
|
97
207
|
render() {
|
|
98
208
|
const {siteInfo, loginInfo} = this.state
|
|
99
209
|
|
|
100
|
-
return <Layout className='main-layout'>
|
|
210
|
+
return <Layout className='main-layout' style={{'--primary-color': ThemeUtils.getColor("primary-color"), '--primary-color-click': ThemeUtils.getColor("primary-color-click")}}>
|
|
101
211
|
<Header className='header'>
|
|
102
212
|
<div className='header-left'>
|
|
103
213
|
{siteInfo.showLogo !== false && siteInfo.logoUrl &&
|
|
@@ -107,10 +217,11 @@ export default class extends React.Component {
|
|
|
107
217
|
</h3>
|
|
108
218
|
|
|
109
219
|
</div>
|
|
220
|
+
{this.renderTopMenu()}
|
|
110
221
|
<HeaderRight/>
|
|
111
222
|
</Header>
|
|
112
223
|
|
|
113
|
-
<Layout style={{
|
|
224
|
+
<Layout style={{flex: 1, overflow: 'hidden'}}>
|
|
114
225
|
<Sider id='left-sider'
|
|
115
226
|
collapsible
|
|
116
227
|
breakpoint={'md'}
|
|
@@ -137,17 +248,12 @@ export default class extends React.Component {
|
|
|
137
248
|
if(this.state.menuLoading){
|
|
138
249
|
return <div style={{padding: 16}}><Skeleton active title={false} paragraph={{rows: 8}} /></div>;
|
|
139
250
|
}
|
|
140
|
-
return <Menu items={this.state.
|
|
251
|
+
return <Menu items={this.state.sideMenus}
|
|
141
252
|
theme='dark'
|
|
142
253
|
mode="inline"
|
|
143
254
|
className='left-menu'
|
|
144
|
-
onClick={
|
|
145
|
-
|
|
146
|
-
const {path} = menu;
|
|
147
|
-
this.setState({currentMenuKey: key})
|
|
148
|
-
history.push(path)
|
|
149
|
-
}}
|
|
150
|
-
selectedKeys={[this.state.currentMenuKey]}
|
|
255
|
+
onClick={this.onLeftMenuClick}
|
|
256
|
+
selectedKeys={this.state.currentMenuKey ? [this.state.currentMenuKey] : []}
|
|
151
257
|
inlineIndent={16}
|
|
152
258
|
>
|
|
153
259
|
</Menu>;
|
|
@@ -155,9 +261,7 @@ export default class extends React.Component {
|
|
|
155
261
|
|
|
156
262
|
renderCenterContent = () => {
|
|
157
263
|
const {siteInfo, loginInfo} = this.state
|
|
158
|
-
|
|
159
|
-
return <Skeleton active paragraph={{rows: 10}} style={{padding: 24}} />
|
|
160
|
-
}
|
|
264
|
+
// 即使菜单为空也直接显示页面内容,不要一直显示 Skeleton
|
|
161
265
|
const tabPageRenderNode = <TabPageRender pathMenuMap={this.state.pathMenuMap}/>;
|
|
162
266
|
if (siteInfo.waterMark === true) {
|
|
163
267
|
return <Watermark content={[loginInfo.name, loginInfo.account]}>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
.main-layout {
|
|
2
|
-
|
|
2
|
+
height: 100vh;
|
|
3
|
+
overflow: hidden;
|
|
3
4
|
.header {
|
|
4
5
|
padding: 0 20px;
|
|
5
6
|
height: 48px;
|
|
6
7
|
display: flex;
|
|
7
|
-
justify-content: space-between;
|
|
8
8
|
align-items: center;
|
|
9
9
|
border-bottom: 1px solid rgb(233, 237, 240);
|
|
10
10
|
background-color: rgb(246, 248, 250);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
display: flex;
|
|
15
15
|
align-items: center;
|
|
16
16
|
gap: 8px;
|
|
17
|
+
flex-shrink: 0;
|
|
17
18
|
|
|
18
19
|
.logo-img {
|
|
19
20
|
height: 38px;
|
|
@@ -21,17 +22,48 @@
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
.top-nav {
|
|
26
|
+
flex: 1;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
margin: 0 16px;
|
|
30
|
+
height: 100%;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
|
|
33
|
+
.top-nav-item {
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 4px;
|
|
37
|
+
padding: 0 16px;
|
|
38
|
+
height: 100%;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
color: rgba(0, 0, 0, 0.65);
|
|
41
|
+
transition: color 0.2s;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
color: var(--primary-color, var(--primary-color, #1961AC));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.active {
|
|
49
|
+
color: var(--primary-color, var(--primary-color, #1961AC));
|
|
50
|
+
box-shadow: inset 0 -2px 0 var(--primary-color, var(--primary-color, #1961AC));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
.header-right {
|
|
25
56
|
line-height: 36px;
|
|
26
57
|
display: flex;
|
|
27
58
|
align-items: center;
|
|
59
|
+
flex-shrink: 0;
|
|
28
60
|
|
|
29
61
|
.item {
|
|
30
62
|
padding: 0 0.5rem;
|
|
31
63
|
cursor: pointer;
|
|
32
64
|
|
|
33
65
|
&:hover {
|
|
34
|
-
background: #
|
|
66
|
+
background: var(--primary-color-click, #124B93);
|
|
35
67
|
color: white;
|
|
36
68
|
}
|
|
37
69
|
}
|
|
@@ -59,10 +91,96 @@
|
|
|
59
91
|
}
|
|
60
92
|
|
|
61
93
|
#admin-layout-content {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
|
|
98
|
+
.oa-tabs {
|
|
99
|
+
flex: 1;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
|
|
104
|
+
.oa-tab-bar {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-wrap: nowrap;
|
|
107
|
+
overflow-x: auto;
|
|
108
|
+
overflow-y: hidden;
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
margin-bottom: 0;
|
|
111
|
+
border-bottom: 1px solid #e8e8e8;
|
|
112
|
+
background: #fff;
|
|
113
|
+
|
|
114
|
+
&::-webkit-scrollbar {
|
|
115
|
+
height: 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.oa-tab {
|
|
120
|
+
display: inline-flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 4px;
|
|
123
|
+
padding: 0 12px;
|
|
124
|
+
height: 32px;
|
|
125
|
+
line-height: 32px;
|
|
126
|
+
font-size: 13px;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
border-right: 1px solid #e8e8e8;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
color: rgba(0, 0, 0, 0.65);
|
|
131
|
+
background: #fafafa;
|
|
132
|
+
transition: color 0.2s, background 0.2s;
|
|
133
|
+
position: relative;
|
|
134
|
+
flex-shrink: 0;
|
|
135
|
+
|
|
136
|
+
&:hover {
|
|
137
|
+
color: var(--primary-color, #1961AC);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&.active {
|
|
141
|
+
color: var(--primary-color, #1961AC);
|
|
142
|
+
background: #fff;
|
|
143
|
+
font-weight: 500;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.oa-tab-label {
|
|
147
|
+
max-width: 120px;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
text-overflow: ellipsis;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.oa-tab-close {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: center;
|
|
156
|
+
width: 16px;
|
|
157
|
+
height: 16px;
|
|
158
|
+
font-size: 12px;
|
|
159
|
+
line-height: 1;
|
|
160
|
+
border-radius: 50%;
|
|
161
|
+
color: rgba(0, 0, 0, 0.45);
|
|
162
|
+
|
|
163
|
+
&:hover {
|
|
164
|
+
background: rgba(0, 0, 0, 0.08);
|
|
165
|
+
color: rgba(0, 0, 0, 0.88);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.oa-tabs-content {
|
|
171
|
+
flex: 1;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
|
|
174
|
+
.oa-tab-pane {
|
|
175
|
+
height: 100%;
|
|
176
|
+
}
|
|
65
177
|
}
|
|
66
178
|
}
|
|
67
179
|
}
|
|
68
180
|
}
|
|
181
|
+
|
|
182
|
+
@media (max-width: 768px) {
|
|
183
|
+
.top-nav {
|
|
184
|
+
display: none !important;
|
|
185
|
+
}
|
|
186
|
+
}
|