yingzios-platform-frontend-devtools 1.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.
- package/README.md +77 -0
- package/dist/base-path.d.ts +2 -0
- package/dist/base-path.d.ts.map +1 -0
- package/dist/base-path.js +2 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.d.ts +13 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.d.ts.map +1 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.js +137 -0
- package/dist/components/DevUserMenu/index.d.ts +31 -0
- package/dist/components/DevUserMenu/index.d.ts.map +1 -0
- package/dist/components/DevUserMenu/index.js +219 -0
- package/dist/components/DevUserMenu/jwt-utils.d.ts +34 -0
- package/dist/components/DevUserMenu/jwt-utils.d.ts.map +1 -0
- package/dist/components/DevUserMenu/jwt-utils.js +85 -0
- package/dist/components/DevUserMenu/styles.d.ts +5 -0
- package/dist/components/DevUserMenu/styles.d.ts.map +1 -0
- package/dist/components/DevUserMenu/styles.js +225 -0
- package/dist/components/StandaloneAppLayout/index.d.ts +63 -0
- package/dist/components/StandaloneAppLayout/index.d.ts.map +1 -0
- package/dist/components/StandaloneAppLayout/index.js +161 -0
- package/dist/components/StandaloneAppLayout/styles.d.ts +5 -0
- package/dist/components/StandaloneAppLayout/styles.d.ts.map +1 -0
- package/dist/components/StandaloneAppLayout/styles.js +230 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/manifest-loader.d.ts +50 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +90 -0
- package/package.json +50 -0
- package/src/base-path.ts +2 -0
- package/src/components/DevUserMenu/JwtGeneratorModal.tsx +219 -0
- package/src/components/DevUserMenu/index.tsx +372 -0
- package/src/components/DevUserMenu/jwt-utils.ts +121 -0
- package/src/components/DevUserMenu/styles.ts +229 -0
- package/src/components/StandaloneAppLayout/index.tsx +332 -0
- package/src/components/StandaloneAppLayout/styles.ts +234 -0
- package/src/index.ts +4 -0
- package/src/manifest-loader.ts +146 -0
- package/src/types/scss.d.ts +19 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
export const devUserMenuStyles = {
|
|
2
|
+
container: {
|
|
3
|
+
position: 'relative',
|
|
4
|
+
display: 'flex',
|
|
5
|
+
alignItems: 'center',
|
|
6
|
+
gap: 10,
|
|
7
|
+
},
|
|
8
|
+
switchOrgButton: {
|
|
9
|
+
height: 32,
|
|
10
|
+
padding: '0 14px',
|
|
11
|
+
border: '1px solid #d8e0eb',
|
|
12
|
+
borderRadius: 16,
|
|
13
|
+
background: '#ffffff',
|
|
14
|
+
color: '#10233d',
|
|
15
|
+
fontSize: 13,
|
|
16
|
+
fontWeight: 500,
|
|
17
|
+
cursor: 'pointer',
|
|
18
|
+
transition: 'border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease',
|
|
19
|
+
},
|
|
20
|
+
avatar: {
|
|
21
|
+
width: 32,
|
|
22
|
+
height: 32,
|
|
23
|
+
borderRadius: '50%',
|
|
24
|
+
background: '#1890ff',
|
|
25
|
+
color: '#fff',
|
|
26
|
+
border: 'none',
|
|
27
|
+
cursor: 'pointer',
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
justifyContent: 'center',
|
|
31
|
+
fontSize: 14,
|
|
32
|
+
fontWeight: 500,
|
|
33
|
+
transition: 'opacity 0.2s',
|
|
34
|
+
},
|
|
35
|
+
avatarImage: {
|
|
36
|
+
width: '100%',
|
|
37
|
+
height: '100%',
|
|
38
|
+
borderRadius: '50%',
|
|
39
|
+
objectFit: 'cover',
|
|
40
|
+
},
|
|
41
|
+
loginButton: {
|
|
42
|
+
padding: '6px 16px',
|
|
43
|
+
background: '#1890ff',
|
|
44
|
+
color: '#fff',
|
|
45
|
+
border: 'none',
|
|
46
|
+
borderRadius: 4,
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
fontSize: 14,
|
|
49
|
+
transition: 'background 0.2s',
|
|
50
|
+
},
|
|
51
|
+
dropdown: {
|
|
52
|
+
position: 'absolute',
|
|
53
|
+
top: '100%',
|
|
54
|
+
right: 0,
|
|
55
|
+
marginTop: 8,
|
|
56
|
+
minWidth: 180,
|
|
57
|
+
background: '#fff',
|
|
58
|
+
borderRadius: 6,
|
|
59
|
+
boxShadow: '0 3px 12px rgba(0, 0, 0, 0.15)',
|
|
60
|
+
zIndex: 1000,
|
|
61
|
+
overflow: 'hidden',
|
|
62
|
+
},
|
|
63
|
+
dropdownHeader: {
|
|
64
|
+
padding: '12px 16px',
|
|
65
|
+
display: 'flex',
|
|
66
|
+
flexDirection: 'column',
|
|
67
|
+
gap: 4,
|
|
68
|
+
},
|
|
69
|
+
dropdownNickname: {
|
|
70
|
+
fontSize: 14,
|
|
71
|
+
fontWeight: 500,
|
|
72
|
+
color: '#333',
|
|
73
|
+
},
|
|
74
|
+
dropdownPhone: {
|
|
75
|
+
fontSize: 12,
|
|
76
|
+
color: '#999',
|
|
77
|
+
},
|
|
78
|
+
dropdownDivider: {
|
|
79
|
+
height: 1,
|
|
80
|
+
background: '#f0f0f0',
|
|
81
|
+
margin: '0 8px',
|
|
82
|
+
},
|
|
83
|
+
dropdownItem: {
|
|
84
|
+
display: 'block',
|
|
85
|
+
width: '100%',
|
|
86
|
+
padding: '10px 16px',
|
|
87
|
+
textAlign: 'left',
|
|
88
|
+
background: 'none',
|
|
89
|
+
border: 'none',
|
|
90
|
+
fontSize: 14,
|
|
91
|
+
color: '#333',
|
|
92
|
+
cursor: 'pointer',
|
|
93
|
+
transition: 'background 0.2s',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export const modalStyles = {
|
|
97
|
+
overlay: {
|
|
98
|
+
position: 'fixed',
|
|
99
|
+
top: 0,
|
|
100
|
+
left: 0,
|
|
101
|
+
right: 0,
|
|
102
|
+
bottom: 0,
|
|
103
|
+
background: 'rgba(0, 0, 0, 0.45)',
|
|
104
|
+
display: 'flex',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
zIndex: 10000,
|
|
108
|
+
},
|
|
109
|
+
modal: {
|
|
110
|
+
width: 360,
|
|
111
|
+
background: '#fff',
|
|
112
|
+
borderRadius: 8,
|
|
113
|
+
boxShadow: '0 6px 24px rgba(0, 0, 0, 0.2)',
|
|
114
|
+
},
|
|
115
|
+
header: {
|
|
116
|
+
display: 'flex',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
justifyContent: 'space-between',
|
|
119
|
+
padding: '16px 20px',
|
|
120
|
+
borderBottom: '1px solid #f0f0f0',
|
|
121
|
+
},
|
|
122
|
+
title: {
|
|
123
|
+
margin: 0,
|
|
124
|
+
fontSize: 16,
|
|
125
|
+
fontWeight: 500,
|
|
126
|
+
color: '#333',
|
|
127
|
+
},
|
|
128
|
+
closeButton: {
|
|
129
|
+
width: 24,
|
|
130
|
+
height: 24,
|
|
131
|
+
background: 'none',
|
|
132
|
+
border: 'none',
|
|
133
|
+
fontSize: 20,
|
|
134
|
+
color: '#999',
|
|
135
|
+
cursor: 'pointer',
|
|
136
|
+
display: 'flex',
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
justifyContent: 'center',
|
|
139
|
+
borderRadius: 4,
|
|
140
|
+
transition: 'background 0.2s',
|
|
141
|
+
},
|
|
142
|
+
form: {
|
|
143
|
+
padding: 20,
|
|
144
|
+
},
|
|
145
|
+
tip: {
|
|
146
|
+
margin: '12px 0',
|
|
147
|
+
padding: '8px 12px',
|
|
148
|
+
background: '#fffbe6',
|
|
149
|
+
border: '1px solid #ffe58f',
|
|
150
|
+
borderRadius: 4,
|
|
151
|
+
fontSize: 12,
|
|
152
|
+
color: '#8c6d1f',
|
|
153
|
+
},
|
|
154
|
+
tipCode: {
|
|
155
|
+
padding: '2px 6px',
|
|
156
|
+
background: 'rgba(0, 0, 0, 0.06)',
|
|
157
|
+
borderRadius: 3,
|
|
158
|
+
fontFamily: 'monospace',
|
|
159
|
+
},
|
|
160
|
+
submitButton: {
|
|
161
|
+
width: '100%',
|
|
162
|
+
padding: 10,
|
|
163
|
+
background: '#1890ff',
|
|
164
|
+
color: '#fff',
|
|
165
|
+
border: 'none',
|
|
166
|
+
borderRadius: 4,
|
|
167
|
+
fontSize: 14,
|
|
168
|
+
cursor: 'pointer',
|
|
169
|
+
transition: 'background 0.2s',
|
|
170
|
+
},
|
|
171
|
+
submitButtonDisabled: {
|
|
172
|
+
opacity: 0.65,
|
|
173
|
+
cursor: 'not-allowed',
|
|
174
|
+
},
|
|
175
|
+
error: {
|
|
176
|
+
marginBottom: 12,
|
|
177
|
+
padding: '8px 12px',
|
|
178
|
+
background: '#fff2f0',
|
|
179
|
+
border: '1px solid #ffccc7',
|
|
180
|
+
borderRadius: 4,
|
|
181
|
+
fontSize: 13,
|
|
182
|
+
color: '#cf1322',
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
export const formGroupStyles = {
|
|
186
|
+
group: {
|
|
187
|
+
marginBottom: 16,
|
|
188
|
+
},
|
|
189
|
+
label: {
|
|
190
|
+
display: 'block',
|
|
191
|
+
marginBottom: 6,
|
|
192
|
+
fontSize: 14,
|
|
193
|
+
color: '#333',
|
|
194
|
+
},
|
|
195
|
+
input: {
|
|
196
|
+
width: '100%',
|
|
197
|
+
padding: '8px 12px',
|
|
198
|
+
border: '1px solid #d9d9d9',
|
|
199
|
+
borderRadius: 4,
|
|
200
|
+
fontSize: 14,
|
|
201
|
+
outline: 'none',
|
|
202
|
+
transition: 'border-color 0.2s',
|
|
203
|
+
boxSizing: 'border-box',
|
|
204
|
+
},
|
|
205
|
+
inputRow: {
|
|
206
|
+
display: 'flex',
|
|
207
|
+
gap: 8,
|
|
208
|
+
},
|
|
209
|
+
sendButton: {
|
|
210
|
+
flexShrink: 0,
|
|
211
|
+
padding: '8px 12px',
|
|
212
|
+
background: '#fff',
|
|
213
|
+
border: '1px solid #d9d9d9',
|
|
214
|
+
borderRadius: 4,
|
|
215
|
+
fontSize: 14,
|
|
216
|
+
color: '#333',
|
|
217
|
+
cursor: 'pointer',
|
|
218
|
+
whiteSpace: 'nowrap',
|
|
219
|
+
transition: 'border-color 0.2s, color 0.2s',
|
|
220
|
+
},
|
|
221
|
+
sendButtonDisabled: {
|
|
222
|
+
color: '#bfbfbf',
|
|
223
|
+
cursor: 'not-allowed',
|
|
224
|
+
},
|
|
225
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FrontendNavIconKey } from 'yz-frontend-core';
|
|
3
|
+
export type IconRenderer = (iconKey: FrontendNavIconKey) => React.ReactNode;
|
|
4
|
+
export interface StandaloneAppLayoutProps {
|
|
5
|
+
/**
|
|
6
|
+
* manifest 文件路径,默认 'app-manifest.json'(相对路径,受 <base> 标签影响)
|
|
7
|
+
*/
|
|
8
|
+
manifestUrl?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 子内容
|
|
11
|
+
*/
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* 自定义 Logo 组件
|
|
15
|
+
*/
|
|
16
|
+
logo?: React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* 自定义顶部右侧内容
|
|
19
|
+
* 如果为 false,则不显示默认的登录组件
|
|
20
|
+
*/
|
|
21
|
+
headerRight?: React.ReactNode | false;
|
|
22
|
+
/**
|
|
23
|
+
* 自定义图标渲染函数
|
|
24
|
+
* 传入 iconKey,返回 React 节点
|
|
25
|
+
*/
|
|
26
|
+
renderIcon?: IconRenderer;
|
|
27
|
+
/**
|
|
28
|
+
* API 基础路径,用于登录接口
|
|
29
|
+
* 默认 '/portal/api'
|
|
30
|
+
*/
|
|
31
|
+
apiBasePath?: string;
|
|
32
|
+
/**
|
|
33
|
+
* 组织切换页面路径
|
|
34
|
+
* 默认 '/org-select'
|
|
35
|
+
*/
|
|
36
|
+
orgSwitchPath?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 独立运行模式下的应用布局
|
|
40
|
+
*
|
|
41
|
+
* 自动从 app-manifest.json 加载配置并生成导航菜单。
|
|
42
|
+
* 用于微应用本地独立开发时提供统一的导航体验。
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* import { StandaloneAppLayout } from 'yz-frontend-devtools';
|
|
47
|
+
*
|
|
48
|
+
* function StandaloneApp() {
|
|
49
|
+
* return (
|
|
50
|
+
* <StandaloneAppLayout>
|
|
51
|
+
* <AppRoutes />
|
|
52
|
+
* </StandaloneAppLayout>
|
|
53
|
+
* );
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function StandaloneAppLayout({ manifestUrl, children, logo, headerRight, renderIcon, apiBasePath, orgSwitchPath, }: StandaloneAppLayoutProps): React.JSX.Element;
|
|
58
|
+
export declare namespace StandaloneAppLayout {
|
|
59
|
+
var displayName: string;
|
|
60
|
+
}
|
|
61
|
+
declare const _default: React.MemoExoticComponent<typeof StandaloneAppLayout>;
|
|
62
|
+
export default _default;
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/StandaloneAppLayout/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAGlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAW3D,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;AA+B5E,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,WAAiC,EACjC,QAAQ,EACR,IAAI,EACJ,WAAW,EACX,UAAU,EACV,WAA2B,EAC3B,aAA6B,GAC9B,EAAE,wBAAwB,qBA4G1B;yBApHe,mBAAmB;;;;AAwOnC,wBAAyC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, { memo, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
3
|
+
import { loadAppManifest, buildMenuGroupsFromManifest, } from '../../manifest-loader';
|
|
4
|
+
import { DevUserMenu } from '../DevUserMenu';
|
|
5
|
+
import { layoutStyles, leftNavStyles, groupNavStyles } from './styles';
|
|
6
|
+
function isPathActive(currentPath, candidatePath, end) {
|
|
7
|
+
if (candidatePath === currentPath) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return end === false && currentPath.startsWith(candidatePath + '/');
|
|
11
|
+
}
|
|
12
|
+
function isStandaloneMenuItemActive(currentPath, item) {
|
|
13
|
+
if (isPathActive(currentPath, item.path, item.end)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return item.matchPaths.some((matchPath) => isPathActive(currentPath, matchPath, item.end));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 独立运行模式下的应用布局
|
|
20
|
+
*
|
|
21
|
+
* 自动从 app-manifest.json 加载配置并生成导航菜单。
|
|
22
|
+
* 用于微应用本地独立开发时提供统一的导航体验。
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import { StandaloneAppLayout } from 'yz-frontend-devtools';
|
|
27
|
+
*
|
|
28
|
+
* function StandaloneApp() {
|
|
29
|
+
* return (
|
|
30
|
+
* <StandaloneAppLayout>
|
|
31
|
+
* <AppRoutes />
|
|
32
|
+
* </StandaloneAppLayout>
|
|
33
|
+
* );
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function StandaloneAppLayout({ manifestUrl = 'app-manifest.json', children, logo, headerRight, renderIcon, apiBasePath = '/portal/api', orgSwitchPath = '/org-select', }) {
|
|
38
|
+
const [menuGroups, setMenuGroups] = useState([]);
|
|
39
|
+
const [appName, setAppName] = useState('');
|
|
40
|
+
const [loading, setLoading] = useState(true);
|
|
41
|
+
const [error, setError] = useState(null);
|
|
42
|
+
const location = useLocation();
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
loadAppManifest(manifestUrl)
|
|
45
|
+
.then((manifest) => {
|
|
46
|
+
const groups = buildMenuGroupsFromManifest(manifest);
|
|
47
|
+
setMenuGroups(groups);
|
|
48
|
+
setAppName(manifest.displayName);
|
|
49
|
+
setLoading(false);
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
console.error('[StandaloneAppLayout] Failed to load manifest:', err);
|
|
53
|
+
setError(err.message);
|
|
54
|
+
setLoading(false);
|
|
55
|
+
});
|
|
56
|
+
}, [manifestUrl]);
|
|
57
|
+
const isMenuItemActive = (item) => isStandaloneMenuItemActive(location.pathname, item);
|
|
58
|
+
const primaryNavItems = useMemo(() => menuGroups.flatMap((group) => {
|
|
59
|
+
const firstItem = group.items[0];
|
|
60
|
+
if (!firstItem) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return [{
|
|
64
|
+
key: `group-${group.groupLabel}`,
|
|
65
|
+
label: group.groupLabel,
|
|
66
|
+
path: firstItem.path,
|
|
67
|
+
iconKey: firstItem.iconKey,
|
|
68
|
+
items: group.items,
|
|
69
|
+
}];
|
|
70
|
+
}), [menuGroups]);
|
|
71
|
+
const activePrimaryNav = useMemo(() => primaryNavItems.find((item) => item.items.some((child) => isMenuItemActive(child))) || primaryNavItems[0], [location.pathname, primaryNavItems]);
|
|
72
|
+
const activeSidebarItems = activePrimaryNav?.items ?? [];
|
|
73
|
+
// 渲染右上角内容
|
|
74
|
+
const renderHeaderRight = () => {
|
|
75
|
+
// 如果显式传入 false,则不渲染
|
|
76
|
+
if (headerRight === false) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
// 如果传入了自定义内容,则渲染自定义内容
|
|
80
|
+
if (headerRight) {
|
|
81
|
+
return headerRight;
|
|
82
|
+
}
|
|
83
|
+
// 默认渲染登录组件
|
|
84
|
+
return React.createElement(DevUserMenu, { apiBasePath: apiBasePath, orgSwitchPath: orgSwitchPath });
|
|
85
|
+
};
|
|
86
|
+
if (loading) {
|
|
87
|
+
return (React.createElement("div", { style: layoutStyles.layout },
|
|
88
|
+
React.createElement("div", { style: layoutStyles.loading }, "\u52A0\u8F7D\u4E2D...")));
|
|
89
|
+
}
|
|
90
|
+
if (error) {
|
|
91
|
+
return (React.createElement("div", { style: layoutStyles.layout },
|
|
92
|
+
React.createElement("div", { style: layoutStyles.error },
|
|
93
|
+
React.createElement("span", null,
|
|
94
|
+
"\u52A0\u8F7D\u5931\u8D25: ",
|
|
95
|
+
error))));
|
|
96
|
+
}
|
|
97
|
+
return (React.createElement("div", { style: layoutStyles.layout },
|
|
98
|
+
React.createElement("header", { style: layoutStyles.header },
|
|
99
|
+
React.createElement("div", { style: layoutStyles.headerLeft },
|
|
100
|
+
logo ?? (React.createElement("span", { style: layoutStyles.logo }, appName || 'App')),
|
|
101
|
+
React.createElement("span", { style: layoutStyles.devBadge }, "DEV")),
|
|
102
|
+
React.createElement("div", { style: layoutStyles.headerRight }, renderHeaderRight())),
|
|
103
|
+
React.createElement("div", { style: layoutStyles.shell },
|
|
104
|
+
React.createElement("aside", { style: layoutStyles.sidebar },
|
|
105
|
+
React.createElement(LeftNav, { items: activeSidebarItems, currentPath: location.pathname, renderIcon: renderIcon })),
|
|
106
|
+
React.createElement("main", { style: layoutStyles.main },
|
|
107
|
+
React.createElement(GroupNav, { items: primaryNavItems, currentPath: location.pathname }),
|
|
108
|
+
React.createElement("div", { style: layoutStyles.content }, children)))));
|
|
109
|
+
}
|
|
110
|
+
function LeftNav({ items, currentPath, renderIcon }) {
|
|
111
|
+
const [hoveredKey, setHoveredKey] = useState(null);
|
|
112
|
+
const isActive = (item) => isStandaloneMenuItemActive(currentPath, item);
|
|
113
|
+
const getIcon = (iconKey, active) => {
|
|
114
|
+
if (!iconKey) {
|
|
115
|
+
return React.createElement("span", { style: leftNavStyles.itemIconPlaceholder });
|
|
116
|
+
}
|
|
117
|
+
if (renderIcon) {
|
|
118
|
+
return renderIcon(iconKey);
|
|
119
|
+
}
|
|
120
|
+
// 默认:显示一个简单的圆点
|
|
121
|
+
return React.createElement("span", { style: leftNavStyles.itemIconDot });
|
|
122
|
+
};
|
|
123
|
+
const getItemStyle = (item) => {
|
|
124
|
+
const active = isActive(item);
|
|
125
|
+
const hovered = hoveredKey === item.key;
|
|
126
|
+
return {
|
|
127
|
+
...leftNavStyles.item,
|
|
128
|
+
...(hovered && !active ? leftNavStyles.itemHover : {}),
|
|
129
|
+
...(active ? leftNavStyles.itemActive : {}),
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
const getIconStyle = (item) => {
|
|
133
|
+
const active = isActive(item);
|
|
134
|
+
return {
|
|
135
|
+
...leftNavStyles.itemIcon,
|
|
136
|
+
...(active ? leftNavStyles.itemIconActive : {}),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
return (React.createElement("nav", { "aria-label": "\u5F00\u53D1\u6A21\u5F0F\u4E3B\u5BFC\u822A", style: leftNavStyles.nav },
|
|
140
|
+
React.createElement("div", { style: leftNavStyles.scrollArea },
|
|
141
|
+
React.createElement("section", { style: leftNavStyles.group }, items.map((item) => (React.createElement(Link, { "aria-current": isActive(item) ? 'page' : undefined, key: item.key, to: item.path, title: item.label, style: getItemStyle(item), onMouseEnter: () => setHoveredKey(item.key), onMouseLeave: () => setHoveredKey(null) },
|
|
142
|
+
React.createElement("span", { style: getIconStyle(item) }, getIcon(item.iconKey, isActive(item))),
|
|
143
|
+
React.createElement("span", { style: leftNavStyles.itemLabel }, item.label))))))));
|
|
144
|
+
}
|
|
145
|
+
function GroupNav({ items, currentPath }) {
|
|
146
|
+
const [hoveredKey, setHoveredKey] = useState(null);
|
|
147
|
+
const isActive = (item) => item.items.some((child) => isStandaloneMenuItemActive(currentPath, child));
|
|
148
|
+
const getItemStyle = (item) => {
|
|
149
|
+
const active = isActive(item);
|
|
150
|
+
const hovered = hoveredKey === item.key;
|
|
151
|
+
return {
|
|
152
|
+
...groupNavStyles.item,
|
|
153
|
+
...(hovered && !active ? groupNavStyles.itemHover : {}),
|
|
154
|
+
...(active ? groupNavStyles.itemActive : {}),
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
return (React.createElement("div", { style: groupNavStyles.nav },
|
|
158
|
+
React.createElement("div", { style: groupNavStyles.scroll }, items.map((item) => (React.createElement(Link, { "aria-current": isActive(item) ? 'page' : undefined, key: item.key, to: item.path, style: getItemStyle(item), onMouseEnter: () => setHoveredKey(item.key), onMouseLeave: () => setHoveredKey(null) }, item.label))))));
|
|
159
|
+
}
|
|
160
|
+
StandaloneAppLayout.displayName = 'StandaloneAppLayout';
|
|
161
|
+
export default memo(StandaloneAppLayout);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export declare const layoutStyles: Record<string, CSSProperties>;
|
|
3
|
+
export declare const leftNavStyles: Record<string, CSSProperties>;
|
|
4
|
+
export declare const groupNavStyles: Record<string, CSSProperties>;
|
|
5
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/components/StandaloneAppLayout/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAgGtD,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAsFvD,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CA4CxD,CAAC"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// CSS-in-JS 样式定义,避免 scss 构建问题
|
|
2
|
+
export const layoutStyles = {
|
|
3
|
+
layout: {
|
|
4
|
+
display: 'flex',
|
|
5
|
+
flexDirection: 'column',
|
|
6
|
+
height: '100vh',
|
|
7
|
+
minHeight: 0,
|
|
8
|
+
width: '100%',
|
|
9
|
+
overflow: 'hidden',
|
|
10
|
+
background: '#ffffff',
|
|
11
|
+
},
|
|
12
|
+
loading: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
justifyContent: 'center',
|
|
16
|
+
minHeight: '100vh',
|
|
17
|
+
color: '#666',
|
|
18
|
+
fontSize: 14,
|
|
19
|
+
},
|
|
20
|
+
error: {
|
|
21
|
+
display: 'flex',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
minHeight: '100vh',
|
|
25
|
+
color: '#ff4d4f',
|
|
26
|
+
fontSize: 14,
|
|
27
|
+
},
|
|
28
|
+
header: {
|
|
29
|
+
position: 'sticky',
|
|
30
|
+
top: 0,
|
|
31
|
+
zIndex: 20,
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
justifyContent: 'space-between',
|
|
35
|
+
height: 56,
|
|
36
|
+
padding: '0 16px',
|
|
37
|
+
background: '#ffffff',
|
|
38
|
+
borderBottom: '1px solid rgba(226, 232, 240, 0.9)',
|
|
39
|
+
flexShrink: 0,
|
|
40
|
+
},
|
|
41
|
+
headerLeft: {
|
|
42
|
+
display: 'flex',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
gap: 12,
|
|
45
|
+
},
|
|
46
|
+
headerRight: {
|
|
47
|
+
display: 'flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
gap: 8,
|
|
50
|
+
},
|
|
51
|
+
logo: {
|
|
52
|
+
fontSize: 18,
|
|
53
|
+
fontWeight: 600,
|
|
54
|
+
color: '#0f172a',
|
|
55
|
+
},
|
|
56
|
+
devBadge: {
|
|
57
|
+
display: 'inline-flex',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
padding: '2px 8px',
|
|
60
|
+
fontSize: 11,
|
|
61
|
+
fontWeight: 600,
|
|
62
|
+
color: '#0f172a',
|
|
63
|
+
background: '#f1f5f9',
|
|
64
|
+
border: '1px solid #e2e8f0',
|
|
65
|
+
borderRadius: 999,
|
|
66
|
+
textTransform: 'uppercase',
|
|
67
|
+
letterSpacing: 0.5,
|
|
68
|
+
},
|
|
69
|
+
shell: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flex: 1,
|
|
72
|
+
overflow: 'hidden',
|
|
73
|
+
minHeight: 0,
|
|
74
|
+
background: 'linear-gradient(180deg, #f8fafc 0%, #f3f6fb 100%)',
|
|
75
|
+
},
|
|
76
|
+
sidebar: {
|
|
77
|
+
width: 256,
|
|
78
|
+
display: 'flex',
|
|
79
|
+
flexShrink: 0,
|
|
80
|
+
minHeight: 0,
|
|
81
|
+
overflow: 'hidden',
|
|
82
|
+
},
|
|
83
|
+
main: {
|
|
84
|
+
flex: 1,
|
|
85
|
+
display: 'flex',
|
|
86
|
+
flexDirection: 'column',
|
|
87
|
+
minWidth: 0,
|
|
88
|
+
minHeight: 0,
|
|
89
|
+
},
|
|
90
|
+
content: {
|
|
91
|
+
flex: 1,
|
|
92
|
+
overflowY: 'auto',
|
|
93
|
+
minHeight: 0,
|
|
94
|
+
position: 'relative',
|
|
95
|
+
padding: '24px 28px 28px',
|
|
96
|
+
background: 'transparent',
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
export const leftNavStyles = {
|
|
100
|
+
nav: {
|
|
101
|
+
width: 256,
|
|
102
|
+
flex: 1,
|
|
103
|
+
flexShrink: 0,
|
|
104
|
+
display: 'flex',
|
|
105
|
+
flexDirection: 'column',
|
|
106
|
+
minHeight: 0,
|
|
107
|
+
height: '100%',
|
|
108
|
+
borderRight: '1px solid rgba(226, 232, 240, 0.9)',
|
|
109
|
+
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 250, 252, 0.98) 100%)',
|
|
110
|
+
padding: 0,
|
|
111
|
+
overflow: 'hidden',
|
|
112
|
+
},
|
|
113
|
+
scrollArea: {
|
|
114
|
+
flex: 1,
|
|
115
|
+
overflowY: 'auto',
|
|
116
|
+
padding: '18px 16px 12px',
|
|
117
|
+
},
|
|
118
|
+
group: {
|
|
119
|
+
display: 'flex',
|
|
120
|
+
flexDirection: 'column',
|
|
121
|
+
gap: 6,
|
|
122
|
+
},
|
|
123
|
+
item: {
|
|
124
|
+
display: 'flex',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
justifyContent: 'flex-start',
|
|
127
|
+
gap: 12,
|
|
128
|
+
minHeight: 44,
|
|
129
|
+
padding: '10px 12px',
|
|
130
|
+
color: '#475569',
|
|
131
|
+
textDecoration: 'none',
|
|
132
|
+
border: '1px solid transparent',
|
|
133
|
+
borderRadius: 8,
|
|
134
|
+
background: 'transparent',
|
|
135
|
+
transition: 'background 0.15s, color 0.15s',
|
|
136
|
+
cursor: 'pointer',
|
|
137
|
+
},
|
|
138
|
+
itemHover: {
|
|
139
|
+
background: '#f8fafc',
|
|
140
|
+
color: '#0f172a',
|
|
141
|
+
},
|
|
142
|
+
itemActive: {
|
|
143
|
+
background: '#030213',
|
|
144
|
+
color: '#ffffff',
|
|
145
|
+
fontWeight: 500,
|
|
146
|
+
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.1)',
|
|
147
|
+
},
|
|
148
|
+
itemIcon: {
|
|
149
|
+
display: 'flex',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
width: 32,
|
|
153
|
+
minWidth: 32,
|
|
154
|
+
height: 32,
|
|
155
|
+
borderRadius: 8,
|
|
156
|
+
background: 'linear-gradient(180deg, #f8fafc 0%, #edf2f7 100%)',
|
|
157
|
+
color: '#64748b',
|
|
158
|
+
boxShadow: 'inset 0 0 0 1px rgba(226, 232, 240, 0.85)',
|
|
159
|
+
},
|
|
160
|
+
itemIconActive: {
|
|
161
|
+
background: 'rgba(255, 255, 255, 0.15)',
|
|
162
|
+
color: '#ffffff',
|
|
163
|
+
boxShadow: 'none',
|
|
164
|
+
},
|
|
165
|
+
itemIconPlaceholder: {
|
|
166
|
+
width: 6,
|
|
167
|
+
height: 6,
|
|
168
|
+
borderRadius: '50%',
|
|
169
|
+
background: '#94a3b8',
|
|
170
|
+
},
|
|
171
|
+
itemIconDot: {
|
|
172
|
+
width: 6,
|
|
173
|
+
height: 6,
|
|
174
|
+
borderRadius: '50%',
|
|
175
|
+
background: 'currentColor',
|
|
176
|
+
opacity: 0.5,
|
|
177
|
+
},
|
|
178
|
+
itemLabel: {
|
|
179
|
+
fontSize: 14,
|
|
180
|
+
fontWeight: 500,
|
|
181
|
+
whiteSpace: 'nowrap',
|
|
182
|
+
overflow: 'hidden',
|
|
183
|
+
textOverflow: 'ellipsis',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
export const groupNavStyles = {
|
|
187
|
+
nav: {
|
|
188
|
+
display: 'flex',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
height: 56,
|
|
191
|
+
padding: '0 28px',
|
|
192
|
+
borderBottom: '1px solid rgba(226, 232, 240, 0.9)',
|
|
193
|
+
background: 'rgba(255, 255, 255, 0.7)',
|
|
194
|
+
backdropFilter: 'blur(8px)',
|
|
195
|
+
flexShrink: 0,
|
|
196
|
+
},
|
|
197
|
+
scroll: {
|
|
198
|
+
display: 'flex',
|
|
199
|
+
alignItems: 'center',
|
|
200
|
+
gap: 8,
|
|
201
|
+
overflowX: 'auto',
|
|
202
|
+
},
|
|
203
|
+
item: {
|
|
204
|
+
display: 'inline-flex',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
justifyContent: 'center',
|
|
207
|
+
height: 32,
|
|
208
|
+
padding: '0 14px',
|
|
209
|
+
fontSize: 13,
|
|
210
|
+
fontWeight: 500,
|
|
211
|
+
color: '#64748b',
|
|
212
|
+
textDecoration: 'none',
|
|
213
|
+
border: '1px solid transparent',
|
|
214
|
+
borderRadius: 8,
|
|
215
|
+
background: 'transparent',
|
|
216
|
+
whiteSpace: 'nowrap',
|
|
217
|
+
cursor: 'pointer',
|
|
218
|
+
transition: 'all 0.15s',
|
|
219
|
+
},
|
|
220
|
+
itemHover: {
|
|
221
|
+
color: '#0f172a',
|
|
222
|
+
background: '#f1f5f9',
|
|
223
|
+
},
|
|
224
|
+
itemActive: {
|
|
225
|
+
color: '#0f172a',
|
|
226
|
+
background: '#ffffff',
|
|
227
|
+
borderColor: '#e2e8f0',
|
|
228
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
|
|
229
|
+
},
|
|
230
|
+
};
|