openxiangda 1.0.69 → 1.0.71
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 +6 -8
- package/templates/openxiangda-react-spa/src/app/navigation.ts +5 -109
- package/templates/openxiangda-react-spa/src/app/router.tsx +2 -54
- package/templates/openxiangda-react-spa/src/app/starter-content.ts +2 -179
- package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +19 -75
- package/templates/openxiangda-react-spa/src/pages/admin/AdminDashboardPage.tsx +15 -181
- package/templates/openxiangda-react-spa/src/resources/menus/menus.json +2 -47
- package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-admin.json +4 -4
- package/templates/openxiangda-react-spa/src/resources/roles/roles.json +1 -6
- package/templates/openxiangda-react-spa/src/layouts/PublicShell.tsx +0 -33
- package/templates/openxiangda-react-spa/src/layouts/UserShell.tsx +0 -129
- package/templates/openxiangda-react-spa/src/pages/admin/DataCenterPage.tsx +0 -96
- package/templates/openxiangda-react-spa/src/pages/admin/ServiceCenterPage.tsx +0 -100
- package/templates/openxiangda-react-spa/src/pages/admin/TaskCenterPage.tsx +0 -135
- package/templates/openxiangda-react-spa/src/pages/portal/UserPortalPage.tsx +0 -71
- package/templates/openxiangda-react-spa/src/pages/public/PublicHomePage.tsx +0 -49
- package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-user.json +0 -8
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# AGENTS.md — OpenXiangda React SPA 应用工作区
|
|
2
2
|
|
|
3
|
-
本工作区是标准 React 18 + Vite + React Router
|
|
3
|
+
本工作区是标准 React 18 + Vite + React Router 应用。默认模板只提供应用壳、账号菜单和一个首页,不是开发验证控制台。
|
|
4
4
|
|
|
5
5
|
## 开发原则
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 默认用户界面保持克制:左侧应用导航、顶部账号信息、首页内容区域。
|
|
8
8
|
- 不在默认可见页面展示 SDK、Runtime、Cookie、Proxy、Playwright、AI 验证、调试上下文、构建号等开发语言。
|
|
9
9
|
- 使用 React Router 管理路由,路由定义在 `src/app/router.tsx`。
|
|
10
10
|
- 使用 Tailwind CSS 表达样式,不依赖平台 theme tokens。
|
|
11
|
-
-
|
|
11
|
+
- 菜单和首页文案优先改 `src/app/navigation.ts`、`src/app/starter-content.ts` 与 `src/pages/admin/AdminDashboardPage.tsx`。
|
|
12
12
|
- 页面、表单、字段、数据范围、流程动作、文件、连接器权限以后端接口为准;前端只做展示保护和清晰状态页。
|
|
13
13
|
- 本地开发通过 Vite `/service` 代理远端平台,保持 HttpOnly Cookie 同域访问。
|
|
14
14
|
|
|
@@ -38,9 +38,7 @@ openxiangda runtime deploy --profile <name>
|
|
|
38
38
|
## 应用结构
|
|
39
39
|
|
|
40
40
|
- `src/layouts/AdminShell.tsx`:管理后台应用壳、侧边栏、顶部栏、用户菜单。
|
|
41
|
-
- `src/pages/admin
|
|
42
|
-
- `src/pages/portal/UserPortalPage.tsx`:普通用户门户。
|
|
43
|
-
- `src/pages/public/PublicHomePage.tsx`:公开服务页。
|
|
41
|
+
- `src/pages/admin/AdminDashboardPage.tsx`:默认首页。
|
|
44
42
|
- `src/pages/defaults/*`:表单、流程、数据列表、文件预览等默认页。
|
|
45
43
|
- `src/runtime/default-page-overrides.tsx`:整页覆盖默认页的入口。
|
|
46
44
|
|
|
@@ -51,7 +49,7 @@ React SPA 页面需要声明菜单 code、route code 和 path pattern。默认
|
|
|
51
49
|
```json
|
|
52
50
|
{
|
|
53
51
|
"code": "admin_dashboard",
|
|
54
|
-
"name": "
|
|
52
|
+
"name": "首页",
|
|
55
53
|
"routeCode": "admin.dashboard",
|
|
56
54
|
"path": "/view/:appType/admin"
|
|
57
55
|
}
|
|
@@ -71,6 +69,6 @@ import { PermissionBoundary, useAppMenus, useRuntimeBootstrap } from "openxiangd
|
|
|
71
69
|
- 表单详情:`/view/:appType/admin/forms/:formUuid/:formInstId`
|
|
72
70
|
- 流程详情:`/view/:appType/admin/process/:formUuid/:formInstId`
|
|
73
71
|
- 数据列表:`/view/:appType/admin/data/:formUuid`
|
|
74
|
-
- 文件预览:`/view/file-preview?ticket=...`
|
|
72
|
+
- 文件预览:`/view/:appType/file-preview?ticket=...`
|
|
75
73
|
|
|
76
74
|
需要自定义默认页时,在 `src/runtime/default-page-overrides.tsx` 中按页面类型和 `formUuid` 注册覆盖组件。不要回到旧平台 `isRenderNav` 或 workbench 参数模型。
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import type { ComponentType } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ClipboardList,
|
|
4
|
-
Database,
|
|
5
|
-
FileText,
|
|
6
|
-
Globe2,
|
|
7
|
-
Home,
|
|
8
|
-
Inbox,
|
|
9
|
-
LayoutDashboard,
|
|
10
|
-
Search,
|
|
11
|
-
Workflow,
|
|
12
|
-
} from "lucide-react";
|
|
2
|
+
import { Home } from "lucide-react";
|
|
13
3
|
|
|
14
4
|
export type StarterNavigationItem = {
|
|
15
5
|
code?: string;
|
|
@@ -27,120 +17,26 @@ export type StarterNavigationGroup = {
|
|
|
27
17
|
|
|
28
18
|
export type BuildStarterNavigationOptions = {
|
|
29
19
|
appType: string;
|
|
30
|
-
dataFormUuid?: string;
|
|
31
|
-
processFormUuid?: string;
|
|
32
|
-
receiptFormUuid?: string;
|
|
33
20
|
};
|
|
34
21
|
|
|
35
22
|
export const viewPath = (appType: string, path: string) =>
|
|
36
23
|
`/view/${appType}/${path}`.replace(/\/{2,}/g, "/");
|
|
37
24
|
|
|
38
|
-
export const formPath = (appType: string, formUuid?: string) =>
|
|
39
|
-
formUuid ? viewPath(appType, `admin/forms/${encodeURIComponent(formUuid)}/new`) : viewPath(appType, "admin/forms");
|
|
40
|
-
|
|
41
|
-
export const processPath = (appType: string, formUuid?: string) =>
|
|
42
|
-
formUuid ? viewPath(appType, `admin/forms/${encodeURIComponent(formUuid)}/new`) : viewPath(appType, "admin/process");
|
|
43
|
-
|
|
44
|
-
export const dataPath = (appType: string, formUuid?: string) =>
|
|
45
|
-
formUuid ? viewPath(appType, `admin/data/${encodeURIComponent(formUuid)}`) : viewPath(appType, "admin/data");
|
|
46
|
-
|
|
47
25
|
export function buildStarterAdminNavigation({
|
|
48
26
|
appType,
|
|
49
|
-
dataFormUuid,
|
|
50
|
-
processFormUuid,
|
|
51
|
-
receiptFormUuid,
|
|
52
27
|
}: BuildStarterNavigationOptions): StarterNavigationGroup[] {
|
|
53
28
|
return [
|
|
54
29
|
{
|
|
55
|
-
title: "
|
|
30
|
+
title: "应用",
|
|
56
31
|
items: [
|
|
57
32
|
{
|
|
58
33
|
code: "admin_dashboard",
|
|
59
|
-
hint: "
|
|
60
|
-
icon:
|
|
61
|
-
name: "
|
|
34
|
+
hint: "默认首页",
|
|
35
|
+
icon: Home,
|
|
36
|
+
name: "首页",
|
|
62
37
|
path: viewPath(appType, "admin"),
|
|
63
38
|
routeCode: "admin.dashboard",
|
|
64
39
|
},
|
|
65
|
-
{
|
|
66
|
-
code: "task_center",
|
|
67
|
-
hint: "待办与跟进",
|
|
68
|
-
icon: Inbox,
|
|
69
|
-
name: "待办中心",
|
|
70
|
-
path: viewPath(appType, "admin/tasks"),
|
|
71
|
-
routeCode: "admin.tasks",
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
title: "业务办理",
|
|
77
|
-
items: [
|
|
78
|
-
{
|
|
79
|
-
code: "service_center",
|
|
80
|
-
hint: "常用服务",
|
|
81
|
-
icon: ClipboardList,
|
|
82
|
-
name: "服务入口",
|
|
83
|
-
path: viewPath(appType, "admin/services"),
|
|
84
|
-
routeCode: "admin.services",
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
code: "service_center",
|
|
88
|
-
hint: receiptFormUuid ? "提交业务申请" : "配置后可用",
|
|
89
|
-
icon: FileText,
|
|
90
|
-
name: "发起申请",
|
|
91
|
-
path: formPath(appType, receiptFormUuid),
|
|
92
|
-
routeCode: "admin.services",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
code: "service_center",
|
|
96
|
-
hint: processFormUuid ? "发起流程办理" : "配置后可用",
|
|
97
|
-
icon: Workflow,
|
|
98
|
-
name: "流程办理",
|
|
99
|
-
path: processPath(appType, processFormUuid),
|
|
100
|
-
routeCode: "admin.services",
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
title: "数据管理",
|
|
106
|
-
items: [
|
|
107
|
-
{
|
|
108
|
-
code: "data_center",
|
|
109
|
-
hint: "经营数据",
|
|
110
|
-
icon: Database,
|
|
111
|
-
name: "数据中心",
|
|
112
|
-
path: viewPath(appType, "admin/data"),
|
|
113
|
-
routeCode: "admin.data",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
code: "data_center",
|
|
117
|
-
hint: dataFormUuid ? "业务台账" : "配置后可用",
|
|
118
|
-
icon: Search,
|
|
119
|
-
name: "数据查询",
|
|
120
|
-
path: dataPath(appType, dataFormUuid),
|
|
121
|
-
routeCode: "admin.data",
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
title: "服务门户",
|
|
127
|
-
items: [
|
|
128
|
-
{
|
|
129
|
-
code: "user_portal",
|
|
130
|
-
hint: "普通用户入口",
|
|
131
|
-
icon: Home,
|
|
132
|
-
name: "用户门户",
|
|
133
|
-
path: viewPath(appType, "portal"),
|
|
134
|
-
routeCode: "portal.home",
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
code: "public_home",
|
|
138
|
-
hint: "公开服务",
|
|
139
|
-
icon: Globe2,
|
|
140
|
-
name: "公开访问",
|
|
141
|
-
path: viewPath(appType, "public"),
|
|
142
|
-
routeCode: "public.home",
|
|
143
|
-
},
|
|
144
40
|
],
|
|
145
41
|
},
|
|
146
42
|
];
|
|
@@ -5,18 +5,11 @@ import {
|
|
|
5
5
|
useParams,
|
|
6
6
|
} from "react-router-dom";
|
|
7
7
|
import { lazy, Suspense, type ReactNode } from "react";
|
|
8
|
-
import {
|
|
8
|
+
import { Sparkles } from "lucide-react";
|
|
9
9
|
import { OpenXiangdaProvider } from "openxiangda/runtime/react";
|
|
10
10
|
|
|
11
11
|
import { AdminShell } from "@/layouts/AdminShell";
|
|
12
|
-
import { PublicShell } from "@/layouts/PublicShell";
|
|
13
|
-
import { UserShell } from "@/layouts/UserShell";
|
|
14
12
|
import { AdminDashboardPage } from "@/pages/admin/AdminDashboardPage";
|
|
15
|
-
import { DataCenterPage } from "@/pages/admin/DataCenterPage";
|
|
16
|
-
import { ServiceCenterPage } from "@/pages/admin/ServiceCenterPage";
|
|
17
|
-
import { TaskCenterPage } from "@/pages/admin/TaskCenterPage";
|
|
18
|
-
import { UserPortalPage } from "@/pages/portal/UserPortalPage";
|
|
19
|
-
import { PublicHomePage } from "@/pages/public/PublicHomePage";
|
|
20
13
|
import { NotFoundPage } from "@/pages/states/NotFoundPage";
|
|
21
14
|
import { StatePage } from "@/shared/ui";
|
|
22
15
|
|
|
@@ -81,32 +74,11 @@ export const router = createBrowserRouter([
|
|
|
81
74
|
element: <AdminShell />,
|
|
82
75
|
children: [
|
|
83
76
|
{ index: true, element: <AdminDashboardPage /> },
|
|
84
|
-
{ path: "tasks", element: <TaskCenterPage /> },
|
|
85
|
-
{ path: "services", element: <ServiceCenterPage /> },
|
|
86
|
-
{ path: "forms", element: <ResourceEmptyPage type="form" /> },
|
|
87
|
-
{ path: "process", element: <ResourceEmptyPage type="process" /> },
|
|
88
|
-
{ path: "data", element: <DataCenterPage /> },
|
|
89
77
|
{ path: "data/:formUuid", element: routeElement(<DataRoutePage />) },
|
|
90
78
|
{ path: "forms/:formUuid/new", element: routeElement(<FormRoutePage mode="submit" />) },
|
|
91
79
|
{ path: "forms/:formUuid/:formInstId", element: routeElement(<FormRoutePage mode="detail" />) },
|
|
92
80
|
{ path: "process/:formUuid/:formInstId", element: routeElement(<FormRoutePage mode="process" />) },
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
path: "portal",
|
|
97
|
-
element: <UserShell />,
|
|
98
|
-
children: [
|
|
99
|
-
{ index: true, element: <UserPortalPage /> },
|
|
100
|
-
{ path: "forms/:formUuid/new", element: routeElement(<FormRoutePage mode="submit" />) },
|
|
101
|
-
{ path: "data/:formUuid", element: routeElement(<DataRoutePage />) },
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
path: "public",
|
|
106
|
-
element: <PublicShell />,
|
|
107
|
-
children: [
|
|
108
|
-
{ index: true, element: <PublicHomePage /> },
|
|
109
|
-
{ path: "forms/:formUuid/new", element: routeElement(<FormRoutePage mode="submit" />) },
|
|
81
|
+
{ path: "*", element: <NotFoundPage /> },
|
|
110
82
|
],
|
|
111
83
|
},
|
|
112
84
|
{ path: "file-preview", element: routeElement(<FilePreviewRoutePage />) },
|
|
@@ -115,27 +87,3 @@ export const router = createBrowserRouter([
|
|
|
115
87
|
},
|
|
116
88
|
{ path: "*", element: <NotFoundPage /> },
|
|
117
89
|
]);
|
|
118
|
-
|
|
119
|
-
function ResourceEmptyPage({ type }: { type: "form" | "process" }) {
|
|
120
|
-
const config = {
|
|
121
|
-
form: {
|
|
122
|
-
description: "当前应用还没有配置可发起的普通表单。配置完成后,这里会进入对应的申请页面。",
|
|
123
|
-
icon: <FileText size={24} />,
|
|
124
|
-
title: "暂无可发起申请",
|
|
125
|
-
},
|
|
126
|
-
process: {
|
|
127
|
-
description: "当前应用还没有配置流程办理入口。配置完成后,这里会进入对应的流程页面。",
|
|
128
|
-
icon: <Workflow size={24} />,
|
|
129
|
-
title: "暂无流程入口",
|
|
130
|
-
},
|
|
131
|
-
}[type];
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<StatePage
|
|
135
|
-
description={config.description}
|
|
136
|
-
icon={config.icon}
|
|
137
|
-
status="EMPTY"
|
|
138
|
-
title={config.title}
|
|
139
|
-
/>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,182 +1,5 @@
|
|
|
1
|
-
import type { AppTone } from "@/shared/ui";
|
|
2
|
-
|
|
3
1
|
export const starterBrand = {
|
|
4
2
|
fallbackName: "业务应用",
|
|
5
|
-
logoText: "
|
|
6
|
-
subtitle: "
|
|
3
|
+
logoText: "A",
|
|
4
|
+
subtitle: "React SPA Starter",
|
|
7
5
|
};
|
|
8
|
-
|
|
9
|
-
export const dashboardHero = {
|
|
10
|
-
title: "业务工作台",
|
|
11
|
-
description: "集中处理待办事项、跟进关键业务进展,并快速进入常用服务。",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const dashboardMetrics: Array<{
|
|
15
|
-
label: string;
|
|
16
|
-
value: string;
|
|
17
|
-
caption: string;
|
|
18
|
-
tone: AppTone;
|
|
19
|
-
}> = [
|
|
20
|
-
{
|
|
21
|
-
caption: "待审批、待办理和需要补充资料的事项。",
|
|
22
|
-
label: "今日待办",
|
|
23
|
-
tone: "amber",
|
|
24
|
-
value: "18",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
caption: "本周新增的申请、登记和服务请求。",
|
|
28
|
-
label: "新增业务",
|
|
29
|
-
tone: "blue",
|
|
30
|
-
value: "126",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
caption: "按当前权限可查看的数据记录。",
|
|
34
|
-
label: "可查数据",
|
|
35
|
-
tone: "emerald",
|
|
36
|
-
value: "3,428",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
caption: "近七日平均办理完成率。",
|
|
40
|
-
label: "办理效率",
|
|
41
|
-
tone: "violet",
|
|
42
|
-
value: "92%",
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
export const trendOverview = {
|
|
47
|
-
title: "近七日业务趋势",
|
|
48
|
-
description: "用于呈现申请、审批和数据更新节奏,帮助团队判断当前业务负载。",
|
|
49
|
-
values: [38, 52, 46, 68, 74, 63, 91],
|
|
50
|
-
summary: [
|
|
51
|
-
{ label: "平均响应", value: "2.6 小时" },
|
|
52
|
-
{ label: "按时完成", value: "94%" },
|
|
53
|
-
{ label: "本周高峰", value: "周五" },
|
|
54
|
-
],
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const quickActions = [
|
|
58
|
-
{
|
|
59
|
-
desc: "提交业务申请、登记信息或创建流程。",
|
|
60
|
-
label: "发起申请",
|
|
61
|
-
tone: "blue" as AppTone,
|
|
62
|
-
type: "form" as const,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
desc: "查看需要当前用户处理的审批与任务。",
|
|
66
|
-
label: "处理待办",
|
|
67
|
-
tone: "amber" as AppTone,
|
|
68
|
-
type: "tasks" as const,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
desc: "进入有权限的数据列表和业务台账。",
|
|
72
|
-
label: "数据查询",
|
|
73
|
-
tone: "emerald" as AppTone,
|
|
74
|
-
type: "data" as const,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
desc: "打开面向普通用户的服务门户。",
|
|
78
|
-
label: "用户门户",
|
|
79
|
-
tone: "violet" as AppTone,
|
|
80
|
-
type: "portal" as const,
|
|
81
|
-
},
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
export const taskItems = [
|
|
85
|
-
{
|
|
86
|
-
assignee: "当前用户",
|
|
87
|
-
due: "今天 18:00",
|
|
88
|
-
priority: "high" as const,
|
|
89
|
-
status: "待处理",
|
|
90
|
-
title: "审批新的业务申请",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
assignee: "业务运营组",
|
|
94
|
-
due: "明天 10:00",
|
|
95
|
-
priority: "medium" as const,
|
|
96
|
-
status: "跟进中",
|
|
97
|
-
title: "补充客户资料并更新台账",
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
assignee: "数据管理员",
|
|
101
|
-
due: "本周五",
|
|
102
|
-
priority: "low" as const,
|
|
103
|
-
status: "待确认",
|
|
104
|
-
title: "核对本周数据汇总结果",
|
|
105
|
-
},
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
export const recentActivities = [
|
|
109
|
-
"业务申请已提交并进入审批流程。",
|
|
110
|
-
"数据列表完成更新,新增记录已可查询。",
|
|
111
|
-
"用户门户服务入口已同步最新配置。",
|
|
112
|
-
"公开服务页收到新的访问请求。",
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
export const serviceEntries = [
|
|
116
|
-
{
|
|
117
|
-
desc: "适合信息登记、业务申请和资料收集。",
|
|
118
|
-
label: "普通表单",
|
|
119
|
-
tone: "blue" as AppTone,
|
|
120
|
-
type: "form" as const,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
desc: "适合需要审批、办理或抄送的业务流程。",
|
|
124
|
-
label: "流程办理",
|
|
125
|
-
tone: "amber" as AppTone,
|
|
126
|
-
type: "process" as const,
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
desc: "按权限查看、筛选和维护业务数据。",
|
|
130
|
-
label: "数据管理",
|
|
131
|
-
tone: "emerald" as AppTone,
|
|
132
|
-
type: "data" as const,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
desc: "面向内部普通用户的统一服务入口。",
|
|
136
|
-
label: "用户门户",
|
|
137
|
-
tone: "violet" as AppTone,
|
|
138
|
-
type: "portal" as const,
|
|
139
|
-
},
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
export const portalEntries = [
|
|
143
|
-
{
|
|
144
|
-
desc: "快速提交常用业务申请。",
|
|
145
|
-
label: "我要申请",
|
|
146
|
-
tone: "blue" as AppTone,
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
desc: "查看待处理、已提交和已完成事项。",
|
|
150
|
-
label: "我的事项",
|
|
151
|
-
tone: "amber" as AppTone,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
desc: "查询当前账号有权限访问的数据。",
|
|
155
|
-
label: "数据查询",
|
|
156
|
-
tone: "emerald" as AppTone,
|
|
157
|
-
},
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
export const publicServices = [
|
|
161
|
-
{
|
|
162
|
-
desc: "发布无需登录即可查看的说明、指南或入口。",
|
|
163
|
-
label: "公开说明",
|
|
164
|
-
tone: "blue" as AppTone,
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
desc: "开放访客可提交的登记或反馈表单。",
|
|
168
|
-
label: "公开登记",
|
|
169
|
-
tone: "emerald" as AppTone,
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
desc: "使用短期凭证打开文件、邀请或查询页面。",
|
|
173
|
-
label: "安全链接",
|
|
174
|
-
tone: "amber" as AppTone,
|
|
175
|
-
},
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
export const dataRows = [
|
|
179
|
-
{ count: "428", label: "本月新增", trend: "+12%" },
|
|
180
|
-
{ count: "96", label: "处理中", trend: "-8%" },
|
|
181
|
-
{ count: "2,904", label: "累计完成", trend: "+18%" },
|
|
182
|
-
];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CalendarDays,
|
|
3
2
|
ChevronDown,
|
|
4
3
|
ChevronRight,
|
|
5
4
|
LogOut,
|
|
@@ -25,12 +24,10 @@ import {
|
|
|
25
24
|
type StarterNavigationGroup,
|
|
26
25
|
} from "@/app/navigation";
|
|
27
26
|
import { starterBrand } from "@/app/starter-content";
|
|
28
|
-
import { runtimeDefaultRoutes } from "@/runtime/default-routes";
|
|
29
27
|
import {
|
|
30
28
|
PrimaryButton,
|
|
31
29
|
SecondaryButton,
|
|
32
30
|
StatePage,
|
|
33
|
-
StatusPill,
|
|
34
31
|
cn,
|
|
35
32
|
} from "@/shared/ui";
|
|
36
33
|
|
|
@@ -62,26 +59,6 @@ export function AdminShell() {
|
|
|
62
59
|
bootstrap.data?.user?.id ||
|
|
63
60
|
"当前用户",
|
|
64
61
|
);
|
|
65
|
-
const todayText = new Intl.DateTimeFormat("zh-CN", {
|
|
66
|
-
day: "numeric",
|
|
67
|
-
month: "long",
|
|
68
|
-
weekday: "long",
|
|
69
|
-
}).format(new Date());
|
|
70
|
-
|
|
71
|
-
const primaryReceiptFormUuid = useMemo(
|
|
72
|
-
() =>
|
|
73
|
-
runtimeDefaultRoutes.formSubmit?.formUuid ||
|
|
74
|
-
findMenuFormUuid(menus.data, ["receipt", "form"]),
|
|
75
|
-
[menus.data],
|
|
76
|
-
);
|
|
77
|
-
const primaryProcessFormUuid = useMemo(
|
|
78
|
-
() =>
|
|
79
|
-
runtimeDefaultRoutes.processSubmit?.formUuid ||
|
|
80
|
-
findMenuFormUuid(menus.data, ["process", "workflow"]),
|
|
81
|
-
[menus.data],
|
|
82
|
-
);
|
|
83
|
-
const primaryDataFormUuid =
|
|
84
|
-
runtimeDefaultRoutes.dataManageList?.formUuid || primaryReceiptFormUuid;
|
|
85
62
|
|
|
86
63
|
const menuCodes = useMemo(() => collectMenuCodes(menus.data), [menus.data]);
|
|
87
64
|
const groups = useMemo<StarterNavigationGroup[]>(
|
|
@@ -89,13 +66,10 @@ export function AdminShell() {
|
|
|
89
66
|
filterNavigationByMenuCodes(
|
|
90
67
|
buildStarterAdminNavigation({
|
|
91
68
|
appType,
|
|
92
|
-
dataFormUuid: primaryDataFormUuid,
|
|
93
|
-
processFormUuid: primaryProcessFormUuid,
|
|
94
|
-
receiptFormUuid: primaryReceiptFormUuid,
|
|
95
69
|
}),
|
|
96
70
|
menuCodes,
|
|
97
71
|
),
|
|
98
|
-
[appType, menuCodes
|
|
72
|
+
[appType, menuCodes],
|
|
99
73
|
);
|
|
100
74
|
|
|
101
75
|
const handleLogout = async () => {
|
|
@@ -107,7 +81,7 @@ export function AdminShell() {
|
|
|
107
81
|
<aside className="flex h-full min-h-0 flex-col border-r border-white/70 bg-white/[0.86] backdrop-blur-xl">
|
|
108
82
|
<div className="flex h-20 shrink-0 items-center gap-3 border-b border-slate-200/70 px-5">
|
|
109
83
|
<Link
|
|
110
|
-
className="grid h-
|
|
84
|
+
className="grid h-11 w-11 place-items-center rounded-2xl bg-[linear-gradient(135deg,#2563eb,#172554)] text-base font-semibold text-white shadow-lg shadow-blue-200/70"
|
|
111
85
|
to={`/view/${appType}/admin`}
|
|
112
86
|
>
|
|
113
87
|
{appName.slice(0, 1) || starterBrand.logoText}
|
|
@@ -178,28 +152,19 @@ export function AdminShell() {
|
|
|
178
152
|
);
|
|
179
153
|
})}
|
|
180
154
|
</nav>
|
|
181
|
-
|
|
182
|
-
<div className="shrink-0 border-t border-slate-200/70 p-4">
|
|
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
|
-
关注待办事项和业务数据变化,保持关键流程及时推进。
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
155
|
</aside>
|
|
191
156
|
);
|
|
192
157
|
|
|
193
158
|
return (
|
|
194
159
|
<PermissionBoundary
|
|
195
|
-
fallback={state => <AdminPermissionState
|
|
160
|
+
fallback={state => <AdminPermissionState state={state} />}
|
|
196
161
|
loadingFallback={<AdminLoadingState />}
|
|
197
162
|
menuCode="admin_dashboard"
|
|
198
163
|
path={`/view/${appType}/admin`}
|
|
199
164
|
routeCode="admin.dashboard"
|
|
200
165
|
>
|
|
201
166
|
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#edf4ff_0%,#f8fafc_42%,#f1f5f9_100%)] text-slate-950">
|
|
202
|
-
<div className="fixed inset-y-0 left-0 z-30 hidden w-
|
|
167
|
+
<div className="fixed inset-y-0 left-0 z-30 hidden w-64 lg:block">{sidebar}</div>
|
|
203
168
|
|
|
204
169
|
{mobileOpen ? (
|
|
205
170
|
<div className="fixed inset-0 z-40 lg:hidden">
|
|
@@ -223,7 +188,7 @@ export function AdminShell() {
|
|
|
223
188
|
</div>
|
|
224
189
|
) : null}
|
|
225
190
|
|
|
226
|
-
<main className="min-w-0 lg:pl-
|
|
191
|
+
<main className="min-w-0 lg:pl-64">
|
|
227
192
|
<header className="sticky top-0 z-20 border-b border-white/70 bg-white/[0.8] backdrop-blur-xl">
|
|
228
193
|
<div className="flex h-20 min-w-0 items-center justify-between gap-3 px-4 sm:px-6">
|
|
229
194
|
<div className="flex min-w-0 items-center gap-3">
|
|
@@ -236,16 +201,12 @@ export function AdminShell() {
|
|
|
236
201
|
<Menu size={20} />
|
|
237
202
|
</button>
|
|
238
203
|
<div className="min-w-0">
|
|
239
|
-
<div className="truncate text-
|
|
240
|
-
<div className="mt-1 truncate text-
|
|
204
|
+
<div className="truncate text-xs font-medium text-slate-500">首页 / 应用</div>
|
|
205
|
+
<div className="mt-1 truncate text-lg font-semibold text-slate-950">{appName}</div>
|
|
241
206
|
</div>
|
|
242
207
|
</div>
|
|
243
208
|
|
|
244
|
-
<div className="flex shrink-0 items-center
|
|
245
|
-
<StatusPill tone="blue">
|
|
246
|
-
<CalendarDays size={13} />
|
|
247
|
-
{todayText}
|
|
248
|
-
</StatusPill>
|
|
209
|
+
<div className="flex shrink-0 items-center">
|
|
249
210
|
<div className="relative">
|
|
250
211
|
<button
|
|
251
212
|
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"
|
|
@@ -296,7 +257,7 @@ export function AdminShell() {
|
|
|
296
257
|
</div>
|
|
297
258
|
</header>
|
|
298
259
|
|
|
299
|
-
<div className="mx-auto min-w-0 max-w-[
|
|
260
|
+
<div className="mx-auto min-w-0 max-w-[1440px] px-4 py-5 sm:px-6">
|
|
300
261
|
<Outlet />
|
|
301
262
|
</div>
|
|
302
263
|
</main>
|
|
@@ -305,13 +266,7 @@ export function AdminShell() {
|
|
|
305
266
|
);
|
|
306
267
|
}
|
|
307
268
|
|
|
308
|
-
function AdminPermissionState({
|
|
309
|
-
appType,
|
|
310
|
-
state,
|
|
311
|
-
}: {
|
|
312
|
-
appType: string;
|
|
313
|
-
state: PermissionBoundaryFallbackState;
|
|
314
|
-
}) {
|
|
269
|
+
function AdminPermissionState({ state }: { state: PermissionBoundaryFallbackState }) {
|
|
315
270
|
const auth = useRuntimeAuth();
|
|
316
271
|
|
|
317
272
|
useEffect(() => {
|
|
@@ -342,8 +297,8 @@ function AdminPermissionState({
|
|
|
342
297
|
actions={
|
|
343
298
|
<>
|
|
344
299
|
<SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
|
|
345
|
-
<PrimaryButton onClick={() =>
|
|
346
|
-
|
|
300
|
+
<PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>
|
|
301
|
+
切换账号
|
|
347
302
|
</PrimaryButton>
|
|
348
303
|
</>
|
|
349
304
|
}
|
|
@@ -381,23 +336,12 @@ function collectMenuCodes(items: PlatformMenuLike[]): Set<string> {
|
|
|
381
336
|
return codes;
|
|
382
337
|
}
|
|
383
338
|
|
|
384
|
-
function findMenuFormUuid(
|
|
385
|
-
items: PlatformMenuLike[],
|
|
386
|
-
types: string[],
|
|
387
|
-
): string | undefined {
|
|
388
|
-
for (const item of items) {
|
|
389
|
-
const type = String(item.type || "").toLowerCase();
|
|
390
|
-
if (item.formUuid && types.some(candidate => type.includes(candidate))) {
|
|
391
|
-
return item.formUuid;
|
|
392
|
-
}
|
|
393
|
-
const nested = findMenuFormUuid(item.children || [], types);
|
|
394
|
-
if (nested) return nested;
|
|
395
|
-
}
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
339
|
function isMenuActive(pathname: string, search: string, target: string) {
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
340
|
+
const normalize = (value: string) =>
|
|
341
|
+
value
|
|
342
|
+
.replace(/[?#].*$/, "")
|
|
343
|
+
.replace(/\/+$/, "")
|
|
344
|
+
.replace(/\/{2,}/g, "/");
|
|
345
|
+
if (target.includes("?")) return `${pathname}${search}` === target;
|
|
346
|
+
return normalize(pathname) === normalize(target);
|
|
403
347
|
}
|