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