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.
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 -217
  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,24 +1,12 @@
1
1
  import {
2
- Bell,
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
- Rocket,
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
- MacPrimaryButton,
36
- MacSecondaryButton,
37
- MacStatePage,
38
- MacStatusPill,
39
- cn,
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 MenuItem = {
44
- name: string;
45
- path: string;
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
- path?: string | null;
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 || "OpenXiangda 应用");
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 groups = useMemo<MenuGroup[]>(() => {
123
- const platformItems = menus.data.length
124
- ? menus.data.flatMap(item => {
125
- const children = item.children?.length ? item.children : [item];
126
- return children
127
- .filter(child => !child.isHidden)
128
- .map(child => ({
129
- hint: child.routeCode || child.resourceCode || undefined,
130
- icon: resolveMenuIcon(String(child.name || child.path || "")),
131
- name: child.name,
132
- path: resolveRuntimeMenuPath(appType, child),
133
- }));
134
- })
135
- : fallbackNavigation.map(item => ({
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.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">
164
108
  <div className="flex h-20 shrink-0 items-center gap-3 border-b border-slate-200/70 px-5">
165
- <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`}>
166
- 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}
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
- OpenXiangda
117
+ {appName}
171
118
  </Link>
172
- <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>
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-slate-950 p-4 text-white shadow-lg shadow-slate-300/60">
237
- <div className="text-xs text-slate-300">Runtime Build</div>
238
- <div className="mt-1 truncate text-sm font-semibold">
239
- {String(bootstrap.data?.runtime?.activeBuildId || "local-dev")}
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 => <RuntimePermissionState appType={appType} state={state} />}
253
- loadingFallback={<RuntimeLoadingState />}
254
- menuCode="runtime_workspace"
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,#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">
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.78] backdrop-blur-xl">
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 flex min-w-0 items-center gap-2 text-xs text-slate-500">
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
- <MacStatusPill tone="emerald">wisejob</MacStatusPill>
312
- <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">
313
- <Bell size={18} />
314
- <span className="absolute right-2 top-2 h-2 w-2 rounded-full bg-rose-500 ring-2 ring-white" />
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
- <FolderOpen size={17} />
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-[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">
370
300
  <Outlet />
371
301
  </div>
372
302
  </main>
@@ -375,7 +305,7 @@ export function AdminShell() {
375
305
  );
376
306
  }
377
307
 
378
- function RuntimePermissionState({
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
- <MacStatePage
325
+ <StatePage
396
326
  actions={
397
- <MacPrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
327
+ <PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
398
328
  立即登录
399
- </MacPrimaryButton>
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
- <MacStatePage
341
+ <StatePage
412
342
  actions={
413
343
  <>
414
- <MacSecondaryButton onClick={() => window.history.back()}>返回上一页</MacSecondaryButton>
415
- <MacPrimaryButton onClick={() => window.location.assign(`/view/${appType}/portal`)}>
344
+ <SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
345
+ <PrimaryButton onClick={() => window.location.assign(`/view/${appType}/portal`)}>
416
346
  打开用户门户
417
- </MacPrimaryButton>
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 RuntimeLoadingState() {
359
+ function AdminLoadingState() {
430
360
  return (
431
- <MacStatePage
361
+ <StatePage
432
362
  description="正在读取应用、用户和页面权限信息。"
433
363
  fullScreen
434
- icon={<Rocket size={24} />}
364
+ icon={<Shield size={24} />}
435
365
  status="LOADING"
436
- title="正在进入工作区"
366
+ title="正在进入应用"
437
367
  />
438
368
  );
439
369
  }
440
370
 
441
- function resolveMenuPath(appType: string, rawPath: string) {
442
- const path = rawPath.replace(/:appType/g, appType).replace(/\/{2,}/g, "/");
443
- if (path.startsWith("/")) return path;
444
- return `/view/${appType}/${path}`.replace(/\/{2,}/g, "/");
445
- }
446
-
447
- function resolveRuntimeMenuPath(appType: string, menu: RuntimeMenuLike) {
448
- if (menu.path) return resolveMenuPath(appType, menu.path);
449
- const type = String(menu.type || "").toLowerCase();
450
- const formUuid = menu.formUuid ? encodeURIComponent(menu.formUuid) : "";
451
- if (formUuid && (type.includes("process") || type.includes("workflow"))) {
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: RuntimeMenuLike[],
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 children = (item as { children?: RuntimeMenuLike[] }).children || [];
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 { 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} />}