openxiangda 1.0.68 → 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.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/templates/openxiangda-react-spa/AGENTS.md +26 -19
  3. package/templates/openxiangda-react-spa/src/app/navigation.ts +165 -0
  4. package/templates/openxiangda-react-spa/src/app/router.tsx +20 -62
  5. package/templates/openxiangda-react-spa/src/app/starter-content.ts +182 -0
  6. package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +94 -233
  7. package/templates/openxiangda-react-spa/src/layouts/PublicShell.tsx +6 -6
  8. package/templates/openxiangda-react-spa/src/layouts/UserShell.tsx +15 -15
  9. package/templates/openxiangda-react-spa/src/pages/admin/AdminDashboardPage.tsx +193 -0
  10. package/templates/openxiangda-react-spa/src/pages/admin/DataCenterPage.tsx +96 -0
  11. package/templates/openxiangda-react-spa/src/pages/admin/ServiceCenterPage.tsx +100 -0
  12. package/templates/openxiangda-react-spa/src/pages/admin/TaskCenterPage.tsx +135 -0
  13. package/templates/openxiangda-react-spa/src/pages/defaults/DataRoutePage.tsx +22 -25
  14. package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +41 -45
  15. package/templates/openxiangda-react-spa/src/pages/defaults/FormRoutePage.tsx +22 -30
  16. package/templates/openxiangda-react-spa/src/pages/portal/UserPortalPage.tsx +47 -42
  17. package/templates/openxiangda-react-spa/src/pages/public/PublicHomePage.tsx +30 -31
  18. package/templates/openxiangda-react-spa/src/pages/states/NotFoundPage.tsx +7 -7
  19. package/templates/openxiangda-react-spa/src/resources/menus/menus.json +32 -5
  20. package/templates/openxiangda-react-spa/src/resources/roles/roles.json +2 -2
  21. package/templates/openxiangda-react-spa/src/shared/mac-admin.tsx +2 -2
  22. package/templates/openxiangda-react-spa/src/shared/ui.tsx +13 -0
  23. package/templates/openxiangda-react-spa/src/pages/admin/RuntimeWorkspacePage.tsx +0 -219
@@ -1,25 +1,12 @@
1
1
  import {
2
- Bell,
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
- Rocket,
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
- MacPrimaryButton,
37
- MacSecondaryButton,
38
- MacStatePage,
39
- MacStatusPill,
40
- cn,
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 MenuItem = {
45
- name: string;
46
- path: string;
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
- path?: string | null;
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 || "OpenXiangda 应用");
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 groups = useMemo<MenuGroup[]>(() => {
124
- const platformItems = menus.data.length
125
- ? menus.data.flatMap(item => {
126
- const children = item.children?.length ? item.children : [item];
127
- return children
128
- .filter(child => !child.isHidden)
129
- .map(child => ({
130
- hint: child.routeCode || child.resourceCode || undefined,
131
- icon: resolveMenuIcon(String(child.name || child.path || "")),
132
- name: child.name,
133
- path: resolveRuntimeMenuPath(appType, child),
134
- }));
135
- })
136
- : fallbackNavigation.map(item => ({
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.84] backdrop-blur-xl">
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 className="grid h-12 w-12 place-items-center rounded-2xl bg-slate-950 text-sm font-semibold text-white shadow-lg shadow-slate-300/70" to={`/view/${appType}/admin`}>
167
- OX
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
- OpenXiangda
117
+ {appName}
172
118
  </Link>
173
- <div className="mt-0.5 truncate text-xs text-slate-500">React SPA Runtime</div>
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-slate-950 p-4 text-white shadow-lg shadow-slate-300/60">
238
- <div className="text-xs text-slate-300">Runtime Build</div>
239
- <div className="mt-1 truncate text-sm font-semibold">
240
- {String(bootstrap.data?.runtime?.activeBuildId || "local-dev")}
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 => <RuntimePermissionState appType={appType} state={state} />}
254
- loadingFallback={<RuntimeLoadingState />}
255
- menuCode="runtime_workspace"
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,#eef2f7_0%,#f8fafc_48%,#edf2f7_100%)] text-slate-950">
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.78] backdrop-blur-xl">
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 flex min-w-0 items-center gap-2 text-xs text-slate-500">
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
- <MacStatusPill tone="emerald">wisejob</MacStatusPill>
313
- <button className="relative grid 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" type="button">
314
- <Bell size={18} />
315
- <span className="absolute right-2 top-2 h-2 w-2 rounded-full bg-rose-500 ring-2 ring-white" />
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
- <FolderOpen size={17} />
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-[1600px] px-4 py-5 sm:px-6">
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 RuntimePermissionState({
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
- <MacStatePage
325
+ <StatePage
412
326
  actions={
413
- <MacPrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
327
+ <PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
414
328
  立即登录
415
- </MacPrimaryButton>
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
- <MacStatePage
341
+ <StatePage
428
342
  actions={
429
343
  <>
430
- <MacSecondaryButton onClick={() => window.history.back()}>返回上一页</MacSecondaryButton>
431
- <MacPrimaryButton onClick={() => window.location.assign(`/view/${appType}/portal`)}>
344
+ <SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
345
+ <PrimaryButton onClick={() => window.location.assign(`/view/${appType}/portal`)}>
432
346
  打开用户门户
433
- </MacPrimaryButton>
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 RuntimeLoadingState() {
359
+ function AdminLoadingState() {
446
360
  return (
447
- <MacStatePage
361
+ <StatePage
448
362
  description="正在读取应用、用户和页面权限信息。"
449
363
  fullScreen
450
- icon={<Rocket size={24} />}
364
+ icon={<Shield size={24} />}
451
365
  status="LOADING"
452
- title="正在进入工作区"
366
+ title="正在进入应用"
453
367
  />
454
368
  );
455
369
  }
456
370
 
457
- function resolveMenuPath(appType: string, rawPath: string) {
458
- const path = rawPath.replace(/:appType/g, appType).replace(/\/{2,}/g, "/");
459
- if (path.startsWith("/")) return path;
460
- return `/view/${appType}/${path}`.replace(/\/{2,}/g, "/");
461
- }
462
-
463
- function resolveRuntimeMenuPath(appType: string, menu: RuntimeMenuLike) {
464
- if (menu.path) return resolveMenuPath(appType, menu.path);
465
- const type = String(menu.type || "").toLowerCase();
466
- const formUuid = menu.formUuid ? encodeURIComponent(menu.formUuid) : "";
467
- if (formUuid && (type.includes("process") || type.includes("workflow"))) {
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: RuntimeMenuLike[],
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 children = (item as { children?: RuntimeMenuLike[] }).children || [];
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 { MacStatusPill } from "@/shared/mac-admin";
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">公开访问</span>
19
- <span className="block truncate text-xs text-slate-500">Public Runtime</span>
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
- <MacStatusPill tone="blue">
22
+ <StatusPill tone="blue">
23
23
  <ShieldCheck size={13} />
24
- ticket / guest
25
- </MacStatusPill>
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
- MacPrimaryButton,
14
- MacSecondaryButton,
15
- MacStatePage,
16
- MacStatusPill,
17
- } from "@/shared/mac-admin";
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: "runtime_workspace",
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">OpenXiangda Portal</span>
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
- <MacStatusPill tone="emerald">已登录</MacStatusPill>
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
- <MacStatePage
90
- actions={<MacPrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>去登录</MacPrimaryButton>}
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
- <MacStatePage
101
+ <StatePage
102
102
  actions={
103
103
  <>
104
- <MacSecondaryButton onClick={() => window.history.back()}>返回上一页</MacSecondaryButton>
105
- <MacPrimaryButton onClick={() => window.location.assign(`/view/${appType}/public`)}>
104
+ <SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
105
+ <PrimaryButton onClick={() => window.location.assign(`/view/${appType}/public`)}>
106
106
  打开公开页
107
- </MacPrimaryButton>
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
- <MacStatePage
121
+ <StatePage
122
122
  description="正在确认当前用户、菜单和页面权限。"
123
123
  fullScreen
124
124
  icon={<Sparkles size={24} />}