@jiangood/open-admin 2.0.1 → 2.2.4
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/config.js +9 -14
- package/package.json +5 -1
- package/src/.umi-production/appData.json +75 -90
- package/src/.umi-production/umi.ts +2 -2
- package/src/framework/components/Page/index.less +1 -1
- package/src/framework/components/Page/index.tsx +19 -22
- package/src/framework/fields/FieldEditor/index.tsx +3 -2
- package/src/framework/fields/FieldUploadFile/index.tsx +8 -4
- package/src/framework/utils/MessageUtils.tsx +21 -99
- package/src/framework/utils/system/HttpUtils.ts +2 -1
- package/src/framework/utils/system/SysUtils.ts +8 -0
- package/src/layouts/PageRender.d.ts +2 -17
- package/src/layouts/PageRender.jsx +14 -76
- package/src/layouts/admin/TabPageRender.jsx +138 -158
- package/src/layouts/admin/index.jsx +120 -17
- package/src/layouts/admin/index.less +123 -5
- package/src/layouts/index.jsx +78 -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 +16 -9
- package/src/pages/login.less +0 -1
- package/src/pages/system/dict/index.jsx +7 -10
- package/src/pages/system/file/index.jsx +5 -5
- package/src/pages/system/job/index.jsx +9 -7
- package/src/pages/system/log/index.jsx +4 -5
- package/src/pages/system/manual/index.jsx +6 -4
- package/src/pages/system/org/index.jsx +4 -2
- package/src/pages/system/role/index.jsx +9 -7
- package/src/pages/system/role/perm.jsx +3 -4
- package/src/pages/system/user/index.jsx +8 -6
- package/src/pages/userCenter/index.jsx +1 -1
- package/src/pages/userCenter/message.jsx +4 -2
|
@@ -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) {
|
|
@@ -107,10 +216,11 @@ export default class extends React.Component {
|
|
|
107
216
|
</h3>
|
|
108
217
|
|
|
109
218
|
</div>
|
|
219
|
+
{this.renderTopMenu()}
|
|
110
220
|
<HeaderRight/>
|
|
111
221
|
</Header>
|
|
112
222
|
|
|
113
|
-
<Layout style={{
|
|
223
|
+
<Layout style={{flex: 1, overflow: 'hidden'}}>
|
|
114
224
|
<Sider id='left-sider'
|
|
115
225
|
collapsible
|
|
116
226
|
breakpoint={'md'}
|
|
@@ -137,17 +247,12 @@ export default class extends React.Component {
|
|
|
137
247
|
if(this.state.menuLoading){
|
|
138
248
|
return <div style={{padding: 16}}><Skeleton active title={false} paragraph={{rows: 8}} /></div>;
|
|
139
249
|
}
|
|
140
|
-
return <Menu items={this.state.
|
|
250
|
+
return <Menu items={this.state.sideMenus}
|
|
141
251
|
theme='dark'
|
|
142
252
|
mode="inline"
|
|
143
253
|
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]}
|
|
254
|
+
onClick={this.onLeftMenuClick}
|
|
255
|
+
selectedKeys={this.state.currentMenuKey ? [this.state.currentMenuKey] : []}
|
|
151
256
|
inlineIndent={16}
|
|
152
257
|
>
|
|
153
258
|
</Menu>;
|
|
@@ -155,9 +260,7 @@ export default class extends React.Component {
|
|
|
155
260
|
|
|
156
261
|
renderCenterContent = () => {
|
|
157
262
|
const {siteInfo, loginInfo} = this.state
|
|
158
|
-
|
|
159
|
-
return <Skeleton active paragraph={{rows: 10}} style={{padding: 24}} />
|
|
160
|
-
}
|
|
263
|
+
// 即使菜单为空也直接显示页面内容,不要一直显示 Skeleton
|
|
161
264
|
const tabPageRenderNode = <TabPageRender pathMenuMap={this.state.pathMenuMap}/>;
|
|
162
265
|
if (siteInfo.waterMark === true) {
|
|
163
266
|
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,10 +22,41 @@
|
|
|
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: #1961AC;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.active {
|
|
49
|
+
color: #1961AC;
|
|
50
|
+
box-shadow: inset 0 -2px 0 #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;
|
|
@@ -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: #1961AC;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&.active {
|
|
141
|
+
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
|
+
}
|