openxiangda 1.0.67 → 1.0.69
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/package.json +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +26 -19
- package/templates/openxiangda-react-spa/src/app/navigation.ts +165 -0
- package/templates/openxiangda-react-spa/src/app/router.tsx +20 -62
- package/templates/openxiangda-react-spa/src/app/starter-content.ts +182 -0
- package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +94 -217
- package/templates/openxiangda-react-spa/src/layouts/PublicShell.tsx +6 -6
- package/templates/openxiangda-react-spa/src/layouts/UserShell.tsx +15 -15
- package/templates/openxiangda-react-spa/src/pages/admin/AdminDashboardPage.tsx +193 -0
- package/templates/openxiangda-react-spa/src/pages/admin/DataCenterPage.tsx +96 -0
- package/templates/openxiangda-react-spa/src/pages/admin/ServiceCenterPage.tsx +100 -0
- package/templates/openxiangda-react-spa/src/pages/admin/TaskCenterPage.tsx +135 -0
- package/templates/openxiangda-react-spa/src/pages/defaults/DataRoutePage.tsx +22 -25
- package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +41 -45
- package/templates/openxiangda-react-spa/src/pages/defaults/FormRoutePage.tsx +22 -30
- package/templates/openxiangda-react-spa/src/pages/portal/UserPortalPage.tsx +47 -42
- package/templates/openxiangda-react-spa/src/pages/public/PublicHomePage.tsx +30 -31
- package/templates/openxiangda-react-spa/src/pages/states/NotFoundPage.tsx +7 -7
- package/templates/openxiangda-react-spa/src/resources/menus/menus.json +32 -5
- package/templates/openxiangda-react-spa/src/resources/roles/roles.json +2 -2
- package/templates/openxiangda-react-spa/src/shared/mac-admin.tsx +2 -2
- package/templates/openxiangda-react-spa/src/shared/ui.tsx +13 -0
- package/templates/openxiangda-react-spa/src/pages/admin/RuntimeWorkspacePage.tsx +0 -219
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
CalendarDays,
|
|
3
3
|
ChevronDown,
|
|
4
4
|
ChevronRight,
|
|
5
|
-
CircleHelp,
|
|
6
|
-
Database,
|
|
7
|
-
FileText,
|
|
8
|
-
FolderOpen,
|
|
9
|
-
Globe2,
|
|
10
|
-
Home,
|
|
11
|
-
LayoutDashboard,
|
|
12
5
|
LogOut,
|
|
13
6
|
Menu,
|
|
14
|
-
|
|
15
|
-
Search,
|
|
16
|
-
Settings,
|
|
7
|
+
RefreshCw,
|
|
17
8
|
Shield,
|
|
18
|
-
Sparkles,
|
|
19
|
-
Upload,
|
|
20
9
|
UserCircle,
|
|
21
|
-
Workflow,
|
|
22
10
|
X,
|
|
23
11
|
} from "lucide-react";
|
|
24
12
|
import { useEffect, useMemo, useState } from "react";
|
|
@@ -32,56 +20,27 @@ import {
|
|
|
32
20
|
} from "openxiangda/runtime/react";
|
|
33
21
|
|
|
34
22
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "@/shared/mac-admin";
|
|
23
|
+
buildStarterAdminNavigation,
|
|
24
|
+
filterNavigationByMenuCodes,
|
|
25
|
+
type StarterNavigationGroup,
|
|
26
|
+
} from "@/app/navigation";
|
|
27
|
+
import { starterBrand } from "@/app/starter-content";
|
|
41
28
|
import { runtimeDefaultRoutes } from "@/runtime/default-routes";
|
|
29
|
+
import {
|
|
30
|
+
PrimaryButton,
|
|
31
|
+
SecondaryButton,
|
|
32
|
+
StatePage,
|
|
33
|
+
StatusPill,
|
|
34
|
+
cn,
|
|
35
|
+
} from "@/shared/ui";
|
|
42
36
|
|
|
43
|
-
type
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
icon: typeof LayoutDashboard;
|
|
47
|
-
hint?: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type MenuGroup = {
|
|
51
|
-
title: string;
|
|
52
|
-
items: MenuItem[];
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const fallbackNavigation: MenuItem[] = [
|
|
56
|
-
{ hint: "应用总览", icon: LayoutDashboard, name: "运行时工作台", path: "admin" },
|
|
57
|
-
{ hint: "普通用户入口", icon: Home, name: "用户门户", path: "portal" },
|
|
58
|
-
{ hint: "匿名访问链路", icon: Globe2, name: "公开访问", path: "public" },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
const systemNavigation: MenuGroup[] = [
|
|
62
|
-
{
|
|
63
|
-
title: "表单与流程",
|
|
64
|
-
items: [
|
|
65
|
-
{ hint: "默认提交页", icon: FileText, name: "表单模板", path: "admin/forms" },
|
|
66
|
-
{ hint: "流程发起和详情", icon: Workflow, name: "流程模板", path: "admin/process" },
|
|
67
|
-
{ hint: "数据管理列表", icon: Database, name: "数据列表", path: "admin/data" },
|
|
68
|
-
{ hint: "平台文件 ticket", icon: Upload, name: "文件预览", path: "file-preview" },
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
title: "权限与发布",
|
|
73
|
-
items: [
|
|
74
|
-
{ hint: "真实用户验证", icon: Sparkles, name: "AI 验证", path: "admin/verification" },
|
|
75
|
-
{ hint: "页面 code 与角色", icon: Shield, name: "角色权限", path: "admin/permissions" },
|
|
76
|
-
{ hint: "版本与部署", icon: Rocket, name: "发布版本", path: "admin/releases" },
|
|
77
|
-
{ hint: "运行参数", icon: Settings, name: "系统设置", path: "admin/settings" },
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
type RuntimeMenuLike = {
|
|
37
|
+
type PlatformMenuLike = {
|
|
38
|
+
children?: PlatformMenuLike[];
|
|
39
|
+
code?: string | null;
|
|
83
40
|
formUuid?: string | null;
|
|
84
|
-
|
|
41
|
+
isHidden?: boolean | null;
|
|
42
|
+
resourceCode?: string | null;
|
|
43
|
+
routeCode?: string | null;
|
|
85
44
|
type?: string | null;
|
|
86
45
|
};
|
|
87
46
|
|
|
@@ -96,14 +55,19 @@ export function AdminShell() {
|
|
|
96
55
|
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
|
97
56
|
const [loggingOut, setLoggingOut] = useState(false);
|
|
98
57
|
|
|
99
|
-
const appName = String(bootstrap.data?.app?.name ||
|
|
100
|
-
const runtimeMode = String(bootstrap.data?.runtime?.mode || "react-spa");
|
|
58
|
+
const appName = String(bootstrap.data?.app?.name || starterBrand.fallbackName);
|
|
101
59
|
const userName = String(
|
|
102
60
|
bootstrap.data?.user?.name ||
|
|
103
61
|
bootstrap.data?.user?.nickName ||
|
|
104
62
|
bootstrap.data?.user?.id ||
|
|
105
|
-
"
|
|
63
|
+
"当前用户",
|
|
106
64
|
);
|
|
65
|
+
const todayText = new Intl.DateTimeFormat("zh-CN", {
|
|
66
|
+
day: "numeric",
|
|
67
|
+
month: "long",
|
|
68
|
+
weekday: "long",
|
|
69
|
+
}).format(new Date());
|
|
70
|
+
|
|
107
71
|
const primaryReceiptFormUuid = useMemo(
|
|
108
72
|
() =>
|
|
109
73
|
runtimeDefaultRoutes.formSubmit?.formUuid ||
|
|
@@ -119,40 +83,20 @@ export function AdminShell() {
|
|
|
119
83
|
const primaryDataFormUuid =
|
|
120
84
|
runtimeDefaultRoutes.dataManageList?.formUuid || primaryReceiptFormUuid;
|
|
121
85
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
...item,
|
|
137
|
-
path: resolveMenuPath(appType, item.path),
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
return [
|
|
141
|
-
{ title: "应用导航", items: platformItems },
|
|
142
|
-
...buildSystemNavigation({
|
|
143
|
-
filePreviewTicket: runtimeDefaultRoutes.filePreview?.ticket,
|
|
144
|
-
processFormUuid: primaryProcessFormUuid,
|
|
145
|
-
receiptFormUuid: primaryReceiptFormUuid,
|
|
146
|
-
dataFormUuid: primaryDataFormUuid,
|
|
147
|
-
}).map(group => ({
|
|
148
|
-
...group,
|
|
149
|
-
items: group.items.map(item => ({
|
|
150
|
-
...item,
|
|
151
|
-
path: resolveMenuPath(appType, item.path),
|
|
152
|
-
})),
|
|
153
|
-
})),
|
|
154
|
-
];
|
|
155
|
-
}, [appType, menus.data, primaryDataFormUuid, primaryProcessFormUuid, primaryReceiptFormUuid]);
|
|
86
|
+
const menuCodes = useMemo(() => collectMenuCodes(menus.data), [menus.data]);
|
|
87
|
+
const groups = useMemo<StarterNavigationGroup[]>(
|
|
88
|
+
() =>
|
|
89
|
+
filterNavigationByMenuCodes(
|
|
90
|
+
buildStarterAdminNavigation({
|
|
91
|
+
appType,
|
|
92
|
+
dataFormUuid: primaryDataFormUuid,
|
|
93
|
+
processFormUuid: primaryProcessFormUuid,
|
|
94
|
+
receiptFormUuid: primaryReceiptFormUuid,
|
|
95
|
+
}),
|
|
96
|
+
menuCodes,
|
|
97
|
+
),
|
|
98
|
+
[appType, menuCodes, primaryDataFormUuid, primaryProcessFormUuid, primaryReceiptFormUuid],
|
|
99
|
+
);
|
|
156
100
|
|
|
157
101
|
const handleLogout = async () => {
|
|
158
102
|
setLoggingOut(true);
|
|
@@ -160,16 +104,19 @@ export function AdminShell() {
|
|
|
160
104
|
};
|
|
161
105
|
|
|
162
106
|
const sidebar = (
|
|
163
|
-
<aside className="flex h-full min-h-0 flex-col border-r border-white/70 bg-white/[0.
|
|
107
|
+
<aside className="flex h-full min-h-0 flex-col border-r border-white/70 bg-white/[0.86] backdrop-blur-xl">
|
|
164
108
|
<div className="flex h-20 shrink-0 items-center gap-3 border-b border-slate-200/70 px-5">
|
|
165
|
-
<Link
|
|
166
|
-
|
|
109
|
+
<Link
|
|
110
|
+
className="grid h-12 w-12 place-items-center rounded-2xl bg-[linear-gradient(135deg,#2563eb,#0f172a)] text-base font-semibold text-white shadow-lg shadow-blue-200/70"
|
|
111
|
+
to={`/view/${appType}/admin`}
|
|
112
|
+
>
|
|
113
|
+
{appName.slice(0, 1) || starterBrand.logoText}
|
|
167
114
|
</Link>
|
|
168
115
|
<div className="min-w-0">
|
|
169
116
|
<Link className="block truncate text-base font-semibold text-slate-950" to={`/view/${appType}/admin`}>
|
|
170
|
-
|
|
117
|
+
{appName}
|
|
171
118
|
</Link>
|
|
172
|
-
<div className="mt-0.5 truncate text-xs text-slate-500">
|
|
119
|
+
<div className="mt-0.5 truncate text-xs text-slate-500">{starterBrand.subtitle}</div>
|
|
173
120
|
</div>
|
|
174
121
|
</div>
|
|
175
122
|
|
|
@@ -233,14 +180,10 @@ export function AdminShell() {
|
|
|
233
180
|
</nav>
|
|
234
181
|
|
|
235
182
|
<div className="shrink-0 border-t border-slate-200/70 p-4">
|
|
236
|
-
<div className="rounded-2xl bg-
|
|
237
|
-
<div className="text-xs text-
|
|
238
|
-
<div className="mt-1
|
|
239
|
-
|
|
240
|
-
</div>
|
|
241
|
-
<div className="mt-3 flex flex-wrap gap-2">
|
|
242
|
-
<span className="rounded-full bg-white/[0.12] px-2.5 py-1 text-xs text-slate-100">{runtimeMode}</span>
|
|
243
|
-
<span className="rounded-full bg-emerald-400/18 px-2.5 py-1 text-xs text-emerald-100">online</span>
|
|
183
|
+
<div className="rounded-2xl bg-[linear-gradient(135deg,#eff6ff,#ecfeff)] p-4 ring-1 ring-blue-100">
|
|
184
|
+
<div className="text-xs font-semibold text-blue-700">今日提醒</div>
|
|
185
|
+
<div className="mt-1 text-sm leading-5 text-slate-600">
|
|
186
|
+
关注待办事项和业务数据变化,保持关键流程及时推进。
|
|
244
187
|
</div>
|
|
245
188
|
</div>
|
|
246
189
|
</div>
|
|
@@ -249,13 +192,13 @@ export function AdminShell() {
|
|
|
249
192
|
|
|
250
193
|
return (
|
|
251
194
|
<PermissionBoundary
|
|
252
|
-
fallback={state => <
|
|
253
|
-
loadingFallback={<
|
|
254
|
-
menuCode="
|
|
195
|
+
fallback={state => <AdminPermissionState appType={appType} state={state} />}
|
|
196
|
+
loadingFallback={<AdminLoadingState />}
|
|
197
|
+
menuCode="admin_dashboard"
|
|
255
198
|
path={`/view/${appType}/admin`}
|
|
256
199
|
routeCode="admin.dashboard"
|
|
257
200
|
>
|
|
258
|
-
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#
|
|
201
|
+
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#edf4ff_0%,#f8fafc_42%,#f1f5f9_100%)] text-slate-950">
|
|
259
202
|
<div className="fixed inset-y-0 left-0 z-30 hidden w-80 lg:block">{sidebar}</div>
|
|
260
203
|
|
|
261
204
|
{mobileOpen ? (
|
|
@@ -281,7 +224,7 @@ export function AdminShell() {
|
|
|
281
224
|
) : null}
|
|
282
225
|
|
|
283
226
|
<main className="min-w-0 lg:pl-80">
|
|
284
|
-
<header className="sticky top-0 z-20 border-b border-white/70 bg-white/[0.
|
|
227
|
+
<header className="sticky top-0 z-20 border-b border-white/70 bg-white/[0.8] backdrop-blur-xl">
|
|
285
228
|
<div className="flex h-20 min-w-0 items-center justify-between gap-3 px-4 sm:px-6">
|
|
286
229
|
<div className="flex min-w-0 items-center gap-3">
|
|
287
230
|
<button
|
|
@@ -294,28 +237,15 @@ export function AdminShell() {
|
|
|
294
237
|
</button>
|
|
295
238
|
<div className="min-w-0">
|
|
296
239
|
<div className="truncate text-lg font-semibold text-slate-950">{appName}</div>
|
|
297
|
-
<div className="mt-1
|
|
298
|
-
<span className="truncate">/view/{appType || "APP"}/admin</span>
|
|
299
|
-
<span className="hidden sm:inline">·</span>
|
|
300
|
-
<span className="hidden sm:inline">{runtimeMode}</span>
|
|
301
|
-
</div>
|
|
240
|
+
<div className="mt-1 truncate text-xs text-slate-500">欢迎回来,{userName}</div>
|
|
302
241
|
</div>
|
|
303
242
|
</div>
|
|
304
243
|
|
|
305
|
-
<div className="hidden min-w-0 max-w-xl flex-1 items-center rounded-2xl border border-slate-200 bg-slate-50/90 px-3 py-2 text-sm text-slate-400 xl:flex">
|
|
306
|
-
<Search className="mr-2 shrink-0" size={17} />
|
|
307
|
-
<span className="truncate">搜索菜单、表单、页面 code</span>
|
|
308
|
-
</div>
|
|
309
|
-
|
|
310
244
|
<div className="flex shrink-0 items-center gap-2">
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
</button>
|
|
316
|
-
<button className="hidden h-10 w-10 place-items-center rounded-xl bg-white text-slate-600 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 sm:grid" type="button">
|
|
317
|
-
<CircleHelp size={18} />
|
|
318
|
-
</button>
|
|
245
|
+
<StatusPill tone="blue">
|
|
246
|
+
<CalendarDays size={13} />
|
|
247
|
+
{todayText}
|
|
248
|
+
</StatusPill>
|
|
319
249
|
<div className="relative">
|
|
320
250
|
<button
|
|
321
251
|
className="flex h-10 items-center gap-2 rounded-full bg-white py-1 pl-1 pr-3 text-sm font-medium text-slate-700 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50"
|
|
@@ -347,8 +277,8 @@ export function AdminShell() {
|
|
|
347
277
|
}}
|
|
348
278
|
type="button"
|
|
349
279
|
>
|
|
350
|
-
<
|
|
351
|
-
|
|
280
|
+
<RefreshCw size={17} />
|
|
281
|
+
刷新数据
|
|
352
282
|
</button>
|
|
353
283
|
<button
|
|
354
284
|
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-rose-600 transition hover:bg-rose-50"
|
|
@@ -366,7 +296,7 @@ export function AdminShell() {
|
|
|
366
296
|
</div>
|
|
367
297
|
</header>
|
|
368
298
|
|
|
369
|
-
<div className="mx-auto min-w-0 max-w-[
|
|
299
|
+
<div className="mx-auto min-w-0 max-w-[1500px] px-4 py-5 sm:px-6">
|
|
370
300
|
<Outlet />
|
|
371
301
|
</div>
|
|
372
302
|
</main>
|
|
@@ -375,7 +305,7 @@ export function AdminShell() {
|
|
|
375
305
|
);
|
|
376
306
|
}
|
|
377
307
|
|
|
378
|
-
function
|
|
308
|
+
function AdminPermissionState({
|
|
379
309
|
appType,
|
|
380
310
|
state,
|
|
381
311
|
}: {
|
|
@@ -392,13 +322,13 @@ function RuntimePermissionState({
|
|
|
392
322
|
|
|
393
323
|
if (state.errorType === "unauthenticated") {
|
|
394
324
|
return (
|
|
395
|
-
<
|
|
325
|
+
<StatePage
|
|
396
326
|
actions={
|
|
397
|
-
<
|
|
327
|
+
<PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
|
|
398
328
|
立即登录
|
|
399
|
-
</
|
|
329
|
+
</PrimaryButton>
|
|
400
330
|
}
|
|
401
|
-
description="
|
|
331
|
+
description="当前浏览器没有有效登录态,正在为你跳转到登录页。"
|
|
402
332
|
fullScreen
|
|
403
333
|
icon={<LogOut size={24} />}
|
|
404
334
|
status="401"
|
|
@@ -408,16 +338,16 @@ function RuntimePermissionState({
|
|
|
408
338
|
}
|
|
409
339
|
|
|
410
340
|
return (
|
|
411
|
-
<
|
|
341
|
+
<StatePage
|
|
412
342
|
actions={
|
|
413
343
|
<>
|
|
414
|
-
<
|
|
415
|
-
<
|
|
344
|
+
<SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
|
|
345
|
+
<PrimaryButton onClick={() => window.location.assign(`/view/${appType}/portal`)}>
|
|
416
346
|
打开用户门户
|
|
417
|
-
</
|
|
347
|
+
</PrimaryButton>
|
|
418
348
|
</>
|
|
419
349
|
}
|
|
420
|
-
description={state.message || "
|
|
350
|
+
description={state.message || "请切换到有权限的账号,或联系管理员开通后台访问权限。"}
|
|
421
351
|
fullScreen
|
|
422
352
|
icon={<Shield size={24} />}
|
|
423
353
|
status="403"
|
|
@@ -426,75 +356,33 @@ function RuntimePermissionState({
|
|
|
426
356
|
);
|
|
427
357
|
}
|
|
428
358
|
|
|
429
|
-
function
|
|
359
|
+
function AdminLoadingState() {
|
|
430
360
|
return (
|
|
431
|
-
<
|
|
361
|
+
<StatePage
|
|
432
362
|
description="正在读取应用、用户和页面权限信息。"
|
|
433
363
|
fullScreen
|
|
434
|
-
icon={<
|
|
364
|
+
icon={<Shield size={24} />}
|
|
435
365
|
status="LOADING"
|
|
436
|
-
title="
|
|
366
|
+
title="正在进入应用"
|
|
437
367
|
/>
|
|
438
368
|
);
|
|
439
369
|
}
|
|
440
370
|
|
|
441
|
-
function
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
return resolveMenuPath(appType, `admin/forms/${formUuid}/new`);
|
|
453
|
-
}
|
|
454
|
-
if (formUuid && (type.includes("receipt") || type.includes("form"))) {
|
|
455
|
-
return resolveMenuPath(appType, `admin/forms/${formUuid}/new`);
|
|
456
|
-
}
|
|
457
|
-
return resolveMenuPath(appType, "admin");
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function buildSystemNavigation({
|
|
461
|
-
dataFormUuid,
|
|
462
|
-
filePreviewTicket,
|
|
463
|
-
processFormUuid,
|
|
464
|
-
receiptFormUuid,
|
|
465
|
-
}: {
|
|
466
|
-
dataFormUuid?: string;
|
|
467
|
-
filePreviewTicket?: string;
|
|
468
|
-
processFormUuid?: string;
|
|
469
|
-
receiptFormUuid?: string;
|
|
470
|
-
}): MenuGroup[] {
|
|
471
|
-
const receiptPath = receiptFormUuid
|
|
472
|
-
? `admin/forms/${encodeURIComponent(receiptFormUuid)}/new`
|
|
473
|
-
: "admin/forms";
|
|
474
|
-
const processPath = processFormUuid
|
|
475
|
-
? `admin/forms/${encodeURIComponent(processFormUuid)}/new`
|
|
476
|
-
: "admin/process";
|
|
477
|
-
const dataPath = dataFormUuid
|
|
478
|
-
? `admin/data/${encodeURIComponent(dataFormUuid)}`
|
|
479
|
-
: "admin/data";
|
|
480
|
-
const filePreviewPath = filePreviewTicket
|
|
481
|
-
? `file-preview?ticket=${encodeURIComponent(filePreviewTicket)}`
|
|
482
|
-
: "file-preview";
|
|
483
|
-
|
|
484
|
-
return systemNavigation.map(group => ({
|
|
485
|
-
...group,
|
|
486
|
-
items: group.items.map(item => {
|
|
487
|
-
if (item.name === "表单模板") return { ...item, path: receiptPath };
|
|
488
|
-
if (item.name === "流程模板") return { ...item, path: processPath };
|
|
489
|
-
if (item.name === "数据列表") return { ...item, path: dataPath };
|
|
490
|
-
if (item.name === "文件预览") return { ...item, path: filePreviewPath };
|
|
491
|
-
return item;
|
|
492
|
-
}),
|
|
493
|
-
}));
|
|
371
|
+
function collectMenuCodes(items: PlatformMenuLike[]): Set<string> {
|
|
372
|
+
const codes = new Set<string>();
|
|
373
|
+
const visit = (item: PlatformMenuLike) => {
|
|
374
|
+
if (item.isHidden) return;
|
|
375
|
+
for (const value of [item.code, item.resourceCode, item.routeCode]) {
|
|
376
|
+
if (value) codes.add(String(value));
|
|
377
|
+
}
|
|
378
|
+
for (const child of item.children || []) visit(child);
|
|
379
|
+
};
|
|
380
|
+
for (const item of items) visit(item);
|
|
381
|
+
return codes;
|
|
494
382
|
}
|
|
495
383
|
|
|
496
384
|
function findMenuFormUuid(
|
|
497
|
-
items:
|
|
385
|
+
items: PlatformMenuLike[],
|
|
498
386
|
types: string[],
|
|
499
387
|
): string | undefined {
|
|
500
388
|
for (const item of items) {
|
|
@@ -502,23 +390,12 @@ function findMenuFormUuid(
|
|
|
502
390
|
if (item.formUuid && types.some(candidate => type.includes(candidate))) {
|
|
503
391
|
return item.formUuid;
|
|
504
392
|
}
|
|
505
|
-
const
|
|
506
|
-
const nested: string | undefined = findMenuFormUuid(children, types);
|
|
393
|
+
const nested = findMenuFormUuid(item.children || [], types);
|
|
507
394
|
if (nested) return nested;
|
|
508
395
|
}
|
|
509
396
|
return undefined;
|
|
510
397
|
}
|
|
511
398
|
|
|
512
|
-
function resolveMenuIcon(value: string): typeof LayoutDashboard {
|
|
513
|
-
const normalized = value.toLowerCase();
|
|
514
|
-
if (normalized.includes("用户") || normalized.includes("portal")) return Home;
|
|
515
|
-
if (normalized.includes("公开") || normalized.includes("public")) return Globe2;
|
|
516
|
-
if (normalized.includes("数据") || normalized.includes("data")) return Database;
|
|
517
|
-
if (normalized.includes("流程") || normalized.includes("process")) return Workflow;
|
|
518
|
-
if (normalized.includes("表单") || normalized.includes("form")) return FileText;
|
|
519
|
-
return LayoutDashboard;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
399
|
function isMenuActive(pathname: string, search: string, target: string) {
|
|
523
400
|
const current = `${pathname}${search}`;
|
|
524
401
|
if (target.includes("?")) return current === target;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Globe2, ShieldCheck } from "lucide-react";
|
|
2
2
|
import { Link, Outlet, useParams } from "react-router-dom";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { StatusPill } from "@/shared/ui";
|
|
5
5
|
|
|
6
6
|
export function PublicShell() {
|
|
7
7
|
const { appType = "" } = useParams();
|
|
@@ -15,14 +15,14 @@ export function PublicShell() {
|
|
|
15
15
|
<Globe2 size={22} />
|
|
16
16
|
</span>
|
|
17
17
|
<span className="min-w-0">
|
|
18
|
-
<span className="block truncate text-base font-semibold text-slate-950"
|
|
19
|
-
<span className="block truncate text-xs text-slate-500"
|
|
18
|
+
<span className="block truncate text-base font-semibold text-slate-950">公开服务</span>
|
|
19
|
+
<span className="block truncate text-xs text-slate-500">无需登录的服务入口</span>
|
|
20
20
|
</span>
|
|
21
21
|
</Link>
|
|
22
|
-
<
|
|
22
|
+
<StatusPill tone="blue">
|
|
23
23
|
<ShieldCheck size={13} />
|
|
24
|
-
|
|
25
|
-
</
|
|
24
|
+
安全访问
|
|
25
|
+
</StatusPill>
|
|
26
26
|
</div>
|
|
27
27
|
</header>
|
|
28
28
|
<main className="mx-auto min-w-0 max-w-6xl px-5 py-8">
|
|
@@ -10,18 +10,18 @@ import {
|
|
|
10
10
|
} from "openxiangda/runtime/react";
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "@/shared/
|
|
13
|
+
PrimaryButton,
|
|
14
|
+
SecondaryButton,
|
|
15
|
+
StatePage,
|
|
16
|
+
StatusPill,
|
|
17
|
+
} from "@/shared/ui";
|
|
18
18
|
|
|
19
19
|
export function UserShell() {
|
|
20
20
|
const { appType = "" } = useParams();
|
|
21
21
|
const bootstrap = useRuntimeBootstrap();
|
|
22
22
|
const adminAccess = useCanAccessRoute({
|
|
23
23
|
routeCode: "admin.dashboard",
|
|
24
|
-
menuCode: "
|
|
24
|
+
menuCode: "admin_dashboard",
|
|
25
25
|
path: `/view/${appType}/admin`,
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -44,11 +44,11 @@ export function UserShell() {
|
|
|
44
44
|
<span className="block truncate text-base font-semibold text-slate-950">
|
|
45
45
|
{String(bootstrap.data?.app?.name || "用户门户")}
|
|
46
46
|
</span>
|
|
47
|
-
<span className="block truncate text-xs text-slate-500"
|
|
47
|
+
<span className="block truncate text-xs text-slate-500">服务门户</span>
|
|
48
48
|
</span>
|
|
49
49
|
</Link>
|
|
50
50
|
<div className="flex shrink-0 items-center gap-2">
|
|
51
|
-
<
|
|
51
|
+
<StatusPill tone="emerald">已登录</StatusPill>
|
|
52
52
|
{adminAccess.canAccess ? (
|
|
53
53
|
<Link
|
|
54
54
|
className="inline-flex h-10 items-center gap-2 rounded-xl bg-white px-3 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50"
|
|
@@ -86,8 +86,8 @@ function UserAccessState({
|
|
|
86
86
|
|
|
87
87
|
if (state.errorType === "unauthenticated") {
|
|
88
88
|
return (
|
|
89
|
-
<
|
|
90
|
-
actions={<
|
|
89
|
+
<StatePage
|
|
90
|
+
actions={<PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>去登录</PrimaryButton>}
|
|
91
91
|
description="当前没有有效登录态,正在跳转到登录页。"
|
|
92
92
|
fullScreen
|
|
93
93
|
icon={<LogIn size={24} />}
|
|
@@ -98,13 +98,13 @@ function UserAccessState({
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
|
-
<
|
|
101
|
+
<StatePage
|
|
102
102
|
actions={
|
|
103
103
|
<>
|
|
104
|
-
<
|
|
105
|
-
<
|
|
104
|
+
<SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
|
|
105
|
+
<PrimaryButton onClick={() => window.location.assign(`/view/${appType}/public`)}>
|
|
106
106
|
打开公开页
|
|
107
|
-
</
|
|
107
|
+
</PrimaryButton>
|
|
108
108
|
</>
|
|
109
109
|
}
|
|
110
110
|
description={state.message || "请联系管理员开通用户门户页面权限。"}
|
|
@@ -118,7 +118,7 @@ function UserAccessState({
|
|
|
118
118
|
|
|
119
119
|
function UserLoadingState() {
|
|
120
120
|
return (
|
|
121
|
-
<
|
|
121
|
+
<StatePage
|
|
122
122
|
description="正在确认当前用户、菜单和页面权限。"
|
|
123
123
|
fullScreen
|
|
124
124
|
icon={<Sparkles size={24} />}
|