@gadmin2n/schematics 0.0.96 → 0.0.98
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/dist/lib/application/files/gadmin2-game-angle-demo/DESIGN.md +348 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/PRODUCT.md +75 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +24 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +220 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +129 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.dto.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +46 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +43 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +109 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +205 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +55 -56
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +7 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +179 -160
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +34 -31
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +66 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.tsx +66 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +7 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +9 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +9 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.ts +27 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package-lock.json +0 -15579
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package-lock.json +0 -17555
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import React, {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useRef,
|
|
5
5
|
useCallback,
|
|
6
|
+
useState,
|
|
6
7
|
type CSSProperties,
|
|
7
8
|
} from 'react';
|
|
8
9
|
|
|
@@ -35,19 +36,25 @@ import {
|
|
|
35
36
|
} from 'antd';
|
|
36
37
|
import { Title } from './title';
|
|
37
38
|
import { agentAttrs } from 'components/agentPanel/agentAttributes';
|
|
39
|
+
import {
|
|
40
|
+
HEADER_HEIGHT,
|
|
41
|
+
SIDER_WIDTH,
|
|
42
|
+
SIDER_COLLAPSED_WIDTH,
|
|
43
|
+
Z_INDEX,
|
|
44
|
+
} from 'constants/layout';
|
|
45
|
+
|
|
46
|
+
const { SubMenu: _SubMenu } = Menu; // eslint silencer; preserved for future use
|
|
47
|
+
void _SubMenu;
|
|
48
|
+
const { useToken } = theme;
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
zIndex: 1001,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
let reRouteNum = 0;
|
|
50
|
+
/**
|
|
51
|
+
* Auto-redirect 安全阀: 一次 mount 周期内允许的最大重定向次数。
|
|
52
|
+
* 超过这个次数说明已经在 ping-pong, 应该停手避免死循环。
|
|
53
|
+
*/
|
|
54
|
+
const MAX_AUTO_REDIRECTS = 5;
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
const
|
|
56
|
+
/** Auto-redirect 触发延迟, 让 selectedKey / items 在同一帧内尘埃落定 */
|
|
57
|
+
const AUTO_REDIRECT_DEBOUNCE_MS = 300;
|
|
51
58
|
|
|
52
59
|
const getParentKeysFromMenuTree = (
|
|
53
60
|
items: ITreeMenu[],
|
|
@@ -85,16 +92,41 @@ const getMenuLabel = (
|
|
|
85
92
|
return meta?.label || name;
|
|
86
93
|
};
|
|
87
94
|
|
|
95
|
+
/** 共用的标题栏(移动端 Drawer 与桌面 Sider 头部都用) */
|
|
96
|
+
const SiderTitleBar: React.FC<{ collapsed: boolean }> = ({ collapsed }) => {
|
|
97
|
+
const { token } = useToken();
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
{...agentAttrs({ type: 'app-title' })}
|
|
101
|
+
style={{
|
|
102
|
+
width: collapsed ? SIDER_COLLAPSED_WIDTH : SIDER_WIDTH,
|
|
103
|
+
padding: collapsed ? 0 : '0 16px',
|
|
104
|
+
display: 'flex',
|
|
105
|
+
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
height: HEADER_HEIGHT,
|
|
108
|
+
backgroundColor: token.colorBgElevated,
|
|
109
|
+
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Title collapsed={collapsed} />
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
88
117
|
export const Sider: React.FC = () => {
|
|
89
118
|
const { token } = useToken();
|
|
90
119
|
const { menuItems, selectedKey, defaultOpenKeys } = useMenu();
|
|
91
120
|
const { pathname: currentPathname } = useLocation();
|
|
92
|
-
const {
|
|
121
|
+
const { i18n } = useTranslation();
|
|
93
122
|
const currentLocale = i18n.language;
|
|
94
123
|
|
|
95
|
-
const [openKeys, setOpenKeys] =
|
|
124
|
+
const [openKeys, setOpenKeys] = useState<string[]>(defaultOpenKeys);
|
|
96
125
|
|
|
97
126
|
const lastSelectedKeyRef = useRef<string | undefined>(undefined);
|
|
127
|
+
// 之前是 module-level 的 `let reRouteNum = 0`, 跨多个 mount 共享, HMR 后永远写死.
|
|
128
|
+
// 改成 useRef 让每次 mount 拿到独立计数器, 而仍然跨同一 mount 内的多次 effect 触发.
|
|
129
|
+
const redirectCountRef = useRef(0);
|
|
98
130
|
|
|
99
131
|
useEffect(() => {
|
|
100
132
|
if (
|
|
@@ -131,15 +163,20 @@ export const Sider: React.FC = () => {
|
|
|
131
163
|
const renderTreeView = useCallback(
|
|
132
164
|
(tree: ITreeMenu[], currentSelectedKey?: string): MenuProps['items'] => {
|
|
133
165
|
return tree
|
|
134
|
-
.map((item: ITreeMenu) => {
|
|
166
|
+
.map((item: ITreeMenu, index: number) => {
|
|
135
167
|
const { key, name, children, meta = {}, list } = item;
|
|
136
168
|
const { icon, parent: parentName } = meta;
|
|
137
169
|
|
|
138
170
|
const route = typeof list === 'string' ? list : listUrl(name);
|
|
139
171
|
|
|
172
|
+
// 优先用 item.key, 退而求其次用 name (永远存在), 最后才用稳定的 path-index.
|
|
173
|
+
// 之前用 Math.random() 作为 fallback —— 那是 React 反模式, 每次渲染都换 key
|
|
174
|
+
// 会让整棵子树 unmount/mount, 破坏滚动位置和动画状态.
|
|
175
|
+
const stableKey = key || name || `fallback-${index}`;
|
|
176
|
+
|
|
140
177
|
if (children.length > 0) {
|
|
141
178
|
return {
|
|
142
|
-
key:
|
|
179
|
+
key: stableKey,
|
|
143
180
|
label: (
|
|
144
181
|
<span style={{ fontWeight: 600 }}>
|
|
145
182
|
{getMenuLabel(name, meta, currentLocale)}
|
|
@@ -150,15 +187,14 @@ export const Sider: React.FC = () => {
|
|
|
150
187
|
};
|
|
151
188
|
}
|
|
152
189
|
|
|
153
|
-
const
|
|
154
|
-
const isSelected = menuItemKey === currentSelectedKey;
|
|
190
|
+
const isSelected = stableKey === currentSelectedKey;
|
|
155
191
|
const isRoute = !(
|
|
156
192
|
pickNotDeprecated(meta?.parent, meta?.parent, parentName) !==
|
|
157
193
|
undefined && children.length === 0
|
|
158
194
|
);
|
|
159
195
|
|
|
160
196
|
return {
|
|
161
|
-
key:
|
|
197
|
+
key: stableKey,
|
|
162
198
|
label: (
|
|
163
199
|
<>
|
|
164
200
|
<Link to={route ?? ''}>
|
|
@@ -181,9 +217,10 @@ export const Sider: React.FC = () => {
|
|
|
181
217
|
return renderTreeView(menuItems, selectedKey)?.filter(Boolean);
|
|
182
218
|
}, [menuItems, selectedKey, renderTreeView]);
|
|
183
219
|
|
|
184
|
-
// Auto-redirect to first available menu item if current selectedKey is not in the menu
|
|
220
|
+
// Auto-redirect to first available menu item if current selectedKey is not in the menu.
|
|
221
|
+
// 触发条件: items / selectedKey / pathname 变化.
|
|
185
222
|
useEffect(() => {
|
|
186
|
-
if (
|
|
223
|
+
if (redirectCountRef.current >= MAX_AUTO_REDIRECTS) return;
|
|
187
224
|
|
|
188
225
|
const isKeyInMenuItems = (
|
|
189
226
|
menuItems: MenuProps['items'],
|
|
@@ -201,12 +238,12 @@ export const Sider: React.FC = () => {
|
|
|
201
238
|
return false;
|
|
202
239
|
};
|
|
203
240
|
|
|
204
|
-
//
|
|
241
|
+
// selectedKey 已经在菜单里, 不需要重定向.
|
|
205
242
|
if (selectedKey && isKeyInMenuItems(items, selectedKey)) {
|
|
206
243
|
return;
|
|
207
244
|
}
|
|
208
245
|
|
|
209
|
-
// Skip auto-redirect for CRUD detail pages (edit/show/create)
|
|
246
|
+
// Skip auto-redirect for CRUD detail pages (edit/show/create).
|
|
210
247
|
// These pages are not in the menu but are valid navigation targets.
|
|
211
248
|
// Also skip auto-generated /admin/data-mngt/* CRUD list pages —
|
|
212
249
|
// some of them are intentionally hidden from the sidebar via
|
|
@@ -224,36 +261,41 @@ export const Sider: React.FC = () => {
|
|
|
224
261
|
}
|
|
225
262
|
|
|
226
263
|
const timer = setTimeout(() => {
|
|
227
|
-
|
|
264
|
+
redirectCountRef.current += 1;
|
|
228
265
|
|
|
229
266
|
const firstKey = items?.[0]?.key;
|
|
230
|
-
if (firstKey
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
if (!firstKey || firstKey === '/dashboard') return;
|
|
268
|
+
|
|
269
|
+
setOpenKeys([String(firstKey)]);
|
|
270
|
+
const firstItem = items?.find((item) => item?.key === firstKey) as any;
|
|
271
|
+
if (!firstItem?.children?.length) {
|
|
272
|
+
go({ to: String(firstKey), type: 'replace' });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const safeSelectedKey = selectedKey ?? '';
|
|
277
|
+
let childItem = firstItem.children.find((child: any) =>
|
|
278
|
+
safeSelectedKey.includes(String(child?.key ?? '')),
|
|
279
|
+
);
|
|
280
|
+
if (!childItem) {
|
|
281
|
+
childItem = firstItem.children[0];
|
|
282
|
+
}
|
|
283
|
+
const childKey = childItem?.key;
|
|
284
|
+
|
|
285
|
+
if (childItem?.children?.length) {
|
|
286
|
+
setOpenKeys([String(firstKey), String(childKey)]);
|
|
287
|
+
const grandsonItem = childItem.children.find((g: any) =>
|
|
288
|
+
safeSelectedKey.includes(String(g?.key ?? '')),
|
|
289
|
+
);
|
|
290
|
+
const grandsonKey =
|
|
291
|
+
grandsonItem?.key ?? firstItem.children[0]?.children?.[0]?.key;
|
|
292
|
+
if (grandsonKey) {
|
|
293
|
+
go({ to: String(grandsonKey), type: 'replace' });
|
|
254
294
|
}
|
|
295
|
+
} else if (childKey) {
|
|
296
|
+
go({ to: String(childKey), type: 'replace' });
|
|
255
297
|
}
|
|
256
|
-
},
|
|
298
|
+
}, AUTO_REDIRECT_DEBOUNCE_MS);
|
|
257
299
|
|
|
258
300
|
return () => clearTimeout(timer);
|
|
259
301
|
}, [items, selectedKey, currentPathname, go]);
|
|
@@ -284,35 +326,51 @@ export const Sider: React.FC = () => {
|
|
|
284
326
|
[menuItems, setMobileSiderOpen],
|
|
285
327
|
);
|
|
286
328
|
|
|
287
|
-
const renderMenu = useCallback(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
const renderMenu = useCallback(
|
|
330
|
+
(collapsed: boolean) => {
|
|
331
|
+
return (
|
|
332
|
+
<Menu
|
|
333
|
+
selectedKeys={selectedKey ? [selectedKey] : []}
|
|
334
|
+
defaultOpenKeys={defaultOpenKeys}
|
|
335
|
+
openKeys={openKeys}
|
|
336
|
+
onOpenChange={handleOpenChange}
|
|
337
|
+
mode="inline"
|
|
338
|
+
// Layout.Sider 的 collapsed 只压宽度, 不会自动让 Menu 切到 collapsed 模式.
|
|
339
|
+
// 必须显式 inlineCollapsed, 否则 SubMenu 的 label 仍渲染, 折叠时溢出 80px 容器
|
|
340
|
+
// (常见症状: 中文 label 被截成 "系统管", active 项还是蓝字)
|
|
341
|
+
inlineCollapsed={collapsed}
|
|
342
|
+
items={items}
|
|
343
|
+
style={{
|
|
344
|
+
paddingTop: 8,
|
|
345
|
+
border: 'none',
|
|
346
|
+
overflow: 'auto',
|
|
347
|
+
// 减去标题栏 (HEADER_HEIGHT) + 桌面 trigger 默认 ~48px 的占位.
|
|
348
|
+
height: `calc(100% - ${HEADER_HEIGHT + 8}px)`,
|
|
349
|
+
background: 'transparent',
|
|
350
|
+
}}
|
|
351
|
+
onClick={handleMenuClick}
|
|
352
|
+
/>
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
[
|
|
356
|
+
selectedKey,
|
|
357
|
+
defaultOpenKeys,
|
|
358
|
+
openKeys,
|
|
359
|
+
handleOpenChange,
|
|
360
|
+
items,
|
|
361
|
+
handleMenuClick,
|
|
362
|
+
],
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const drawerButtonStyles: CSSProperties = {
|
|
366
|
+
borderTopLeftRadius: 0,
|
|
367
|
+
borderBottomLeftRadius: 0,
|
|
368
|
+
position: 'fixed',
|
|
369
|
+
top: HEADER_HEIGHT,
|
|
370
|
+
zIndex: Z_INDEX.FAB,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
if (isMobile) {
|
|
316
374
|
return (
|
|
317
375
|
<>
|
|
318
376
|
<Drawer
|
|
@@ -320,39 +378,22 @@ export const Sider: React.FC = () => {
|
|
|
320
378
|
onClose={() => setMobileSiderOpen(false)}
|
|
321
379
|
placement="left"
|
|
322
380
|
closable={false}
|
|
323
|
-
width={
|
|
324
|
-
styles={{
|
|
325
|
-
|
|
326
|
-
padding: 0,
|
|
327
|
-
},
|
|
328
|
-
}}
|
|
329
|
-
maskClosable={true}
|
|
381
|
+
width={SIDER_WIDTH}
|
|
382
|
+
styles={{ body: { padding: 0 } }}
|
|
383
|
+
maskClosable
|
|
330
384
|
>
|
|
331
|
-
<Layout>
|
|
385
|
+
<Layout style={{ height: '100vh' }}>
|
|
332
386
|
<Layout.Sider
|
|
333
|
-
width={
|
|
387
|
+
width={SIDER_WIDTH}
|
|
334
388
|
style={{
|
|
335
389
|
height: '100vh',
|
|
336
390
|
backgroundColor: token.colorBgContainer,
|
|
337
391
|
borderRight: `1px solid ${token.colorBorderBg}`,
|
|
338
392
|
}}
|
|
339
393
|
>
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
width: '256px',
|
|
344
|
-
padding: '0 16px',
|
|
345
|
-
display: 'flex',
|
|
346
|
-
justifyContent: 'flex-start',
|
|
347
|
-
alignItems: 'center',
|
|
348
|
-
height: '64px',
|
|
349
|
-
backgroundColor: token.colorBgElevated,
|
|
350
|
-
borderBottom: 'none',
|
|
351
|
-
}}
|
|
352
|
-
>
|
|
353
|
-
<Title collapsed={false} />
|
|
354
|
-
</div>
|
|
355
|
-
{renderMenu()}
|
|
394
|
+
<SiderTitleBar collapsed={false} />
|
|
395
|
+
{/* 移动端 Drawer 打开时给的是全宽容器, Menu 永远展开. */}
|
|
396
|
+
{renderMenu(false)}
|
|
356
397
|
</Layout.Sider>
|
|
357
398
|
</Layout>
|
|
358
399
|
</Drawer>
|
|
@@ -361,13 +402,10 @@ export const Sider: React.FC = () => {
|
|
|
361
402
|
size="large"
|
|
362
403
|
onClick={() => setMobileSiderOpen(true)}
|
|
363
404
|
icon={<BarsOutlined />}
|
|
405
|
+
aria-label="Open navigation menu"
|
|
364
406
|
/>
|
|
365
407
|
</>
|
|
366
408
|
);
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
if (isMobile) {
|
|
370
|
-
return renderDrawerSider();
|
|
371
409
|
}
|
|
372
410
|
|
|
373
411
|
const siderStyles: React.CSSProperties = {
|
|
@@ -377,67 +415,48 @@ export const Sider: React.FC = () => {
|
|
|
377
415
|
top: 0,
|
|
378
416
|
left: 0,
|
|
379
417
|
height: '100vh',
|
|
380
|
-
zIndex:
|
|
418
|
+
zIndex: Z_INDEX.STICKY,
|
|
381
419
|
};
|
|
382
420
|
|
|
383
421
|
return (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
setSiderCollapsed(collapsed);
|
|
393
|
-
}
|
|
394
|
-
}}
|
|
395
|
-
collapsedWidth={80}
|
|
396
|
-
breakpoint="lg"
|
|
397
|
-
trigger={
|
|
398
|
-
<Button
|
|
399
|
-
type="text"
|
|
400
|
-
style={{
|
|
401
|
-
borderRadius: 0,
|
|
402
|
-
height: '100%',
|
|
403
|
-
width: '100%',
|
|
404
|
-
backgroundColor: token.colorBgElevated,
|
|
405
|
-
borderRight: `1px solid ${token.colorBorderBg}`,
|
|
406
|
-
}}
|
|
407
|
-
>
|
|
408
|
-
{siderCollapsed ? (
|
|
409
|
-
<RightOutlined
|
|
410
|
-
style={{
|
|
411
|
-
color: token.colorPrimary,
|
|
412
|
-
}}
|
|
413
|
-
/>
|
|
414
|
-
) : (
|
|
415
|
-
<LeftOutlined
|
|
416
|
-
style={{
|
|
417
|
-
color: token.colorPrimary,
|
|
418
|
-
}}
|
|
419
|
-
/>
|
|
420
|
-
)}
|
|
421
|
-
</Button>
|
|
422
|
+
<Layout.Sider
|
|
423
|
+
style={siderStyles}
|
|
424
|
+
width={SIDER_WIDTH}
|
|
425
|
+
collapsible
|
|
426
|
+
collapsed={siderCollapsed}
|
|
427
|
+
onCollapse={(collapsed, type) => {
|
|
428
|
+
if (type === 'clickTrigger') {
|
|
429
|
+
setSiderCollapsed(collapsed);
|
|
422
430
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
431
|
+
}}
|
|
432
|
+
collapsedWidth={SIDER_COLLAPSED_WIDTH}
|
|
433
|
+
breakpoint="lg"
|
|
434
|
+
trigger={
|
|
435
|
+
<Button
|
|
436
|
+
type="text"
|
|
437
|
+
aria-label={
|
|
438
|
+
siderCollapsed
|
|
439
|
+
? 'Expand navigation menu'
|
|
440
|
+
: 'Collapse navigation menu'
|
|
441
|
+
}
|
|
442
|
+
aria-expanded={!siderCollapsed}
|
|
426
443
|
style={{
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
justifyContent: siderCollapsed ? 'center' : 'flex-start',
|
|
431
|
-
alignItems: 'center',
|
|
432
|
-
height: '64px',
|
|
444
|
+
borderRadius: 0,
|
|
445
|
+
height: '100%',
|
|
446
|
+
width: '100%',
|
|
433
447
|
backgroundColor: token.colorBgElevated,
|
|
434
|
-
|
|
448
|
+
borderRight: `1px solid ${token.colorBorderBg}`,
|
|
449
|
+
// 之前用 colorPrimary 给 chrome 控件上色, 与 One Voice Rule 冲突.
|
|
450
|
+
// chrome 收纳器不需要抢眼, 用次级文本色即可.
|
|
451
|
+
color: token.colorTextSecondary,
|
|
435
452
|
}}
|
|
436
453
|
>
|
|
437
|
-
|
|
438
|
-
</
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
454
|
+
{siderCollapsed ? <RightOutlined /> : <LeftOutlined />}
|
|
455
|
+
</Button>
|
|
456
|
+
}
|
|
457
|
+
>
|
|
458
|
+
<SiderTitleBar collapsed={siderCollapsed} />
|
|
459
|
+
{renderMenu(siderCollapsed)}
|
|
460
|
+
</Layout.Sider>
|
|
442
461
|
);
|
|
443
462
|
};
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx
CHANGED
|
@@ -2,60 +2,63 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { RefineLayoutThemedTitleProps } from '@refinedev/antd';
|
|
4
4
|
import { useLink } from '@refinedev/core';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { theme } from 'antd';
|
|
7
8
|
|
|
8
9
|
import { Logo } from './logo';
|
|
9
10
|
|
|
10
11
|
const { useToken } = theme;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* App 品牌入口(左上角 Logo + 名称)。
|
|
15
|
+
*
|
|
16
|
+
* 设计意图:
|
|
17
|
+
* - 不再使用 Typography.Title —— 它会在每个页面渲染一个重复的 <h1>,
|
|
18
|
+
* 与页面自身的 <h1> 冲突,破坏 a11y 层级。这里只是一个导航入口,
|
|
19
|
+
* 用 <span> + 显式样式即可。
|
|
20
|
+
* - 名称走 i18n,方便后续 white-label 替换。
|
|
21
|
+
* - Logo 用 currentColor,颜色由这里的 wrapper 决定(主色蓝)。
|
|
22
|
+
*/
|
|
14
23
|
export const Title: React.FC<RefineLayoutThemedTitleProps> = ({
|
|
15
24
|
collapsed,
|
|
16
25
|
wrapperStyles,
|
|
17
26
|
}) => {
|
|
18
27
|
const { token } = useToken();
|
|
28
|
+
const { t } = useTranslation();
|
|
19
29
|
const Link = useLink();
|
|
20
30
|
|
|
31
|
+
const appName = t('app.name', 'AI+ Ops Admin');
|
|
32
|
+
|
|
21
33
|
return (
|
|
22
34
|
<Link
|
|
23
35
|
to="/"
|
|
24
36
|
style={{
|
|
25
|
-
display: 'inline-
|
|
37
|
+
display: 'inline-flex',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
gap: 8,
|
|
40
|
+
color: token.colorPrimary,
|
|
26
41
|
textDecoration: 'none',
|
|
42
|
+
...wrapperStyles,
|
|
27
43
|
}}
|
|
44
|
+
aria-label={appName}
|
|
28
45
|
>
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
fontSize: 'inherit',
|
|
34
|
-
...wrapperStyles,
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<div
|
|
46
|
+
<Logo width={24} height={24} />
|
|
47
|
+
|
|
48
|
+
{!collapsed && (
|
|
49
|
+
<span
|
|
38
50
|
style={{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
fontSize: 16,
|
|
52
|
+
fontWeight: 600,
|
|
53
|
+
lineHeight: 1.4,
|
|
54
|
+
color: token.colorTextHeading,
|
|
55
|
+
letterSpacing: 0,
|
|
56
|
+
whiteSpace: 'nowrap',
|
|
42
57
|
}}
|
|
43
58
|
>
|
|
44
|
-
|
|
45
|
-
</
|
|
46
|
-
|
|
47
|
-
{!collapsed && (
|
|
48
|
-
<Typography.Title
|
|
49
|
-
style={{
|
|
50
|
-
fontSize: 'inherit',
|
|
51
|
-
marginBottom: 0,
|
|
52
|
-
fontWeight: 700,
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
{name}
|
|
56
|
-
</Typography.Title>
|
|
57
|
-
)}
|
|
58
|
-
</Space>
|
|
59
|
+
{appName}
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
59
62
|
</Link>
|
|
60
63
|
);
|
|
61
64
|
};
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App chrome 共享尺寸。layout / sider / header / floating triggers
|
|
3
|
+
* 都从这里取,避免在多个文件里散落同一个魔法数字。
|
|
4
|
+
*/
|
|
5
|
+
export const HEADER_HEIGHT = 64;
|
|
6
|
+
export const SIDER_WIDTH = 256;
|
|
7
|
+
export const SIDER_COLLAPSED_WIDTH = 80;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 语义化的 z-index 层级表。
|
|
11
|
+
*
|
|
12
|
+
* Ant Design v5 的浮层 (Modal / Drawer / Dropdown / Select) 默认从 1000 起;
|
|
13
|
+
* 所以这里的 STICKY / FAB 都安排在 1000 以下,确保 chrome 在 popup 打开时
|
|
14
|
+
* 被正确遮挡。永远不要在业务代码里写裸 zIndex 数字 —— 加一个语义键回到这里。
|
|
15
|
+
*/
|
|
16
|
+
export const Z_INDEX = {
|
|
17
|
+
/** AppHeader、Sider 等 sticky chrome */
|
|
18
|
+
STICKY: 100,
|
|
19
|
+
/** 悬浮按钮:移动端的 Drawer 触发器 */
|
|
20
|
+
FAB: 110,
|
|
21
|
+
/** 自定义遮罩,低于 Ant popups */
|
|
22
|
+
OVERLAY: 800,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
1
25
|
/** List / Create 页面顶层组件的内容区样式 */
|
|
2
26
|
export const LIST_CONTENT_PROPS = {
|
|
3
27
|
style: { marginTop: '1rem' },
|