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
|
@@ -1,193 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrowRight,
|
|
3
|
-
BarChart3,
|
|
4
|
-
ClipboardList,
|
|
5
|
-
Database,
|
|
6
|
-
FileText,
|
|
7
|
-
Inbox,
|
|
8
|
-
Sparkles,
|
|
9
|
-
Workflow,
|
|
10
|
-
} from "lucide-react";
|
|
11
|
-
import { useMemo } from "react";
|
|
12
|
-
import { useParams } from "react-router-dom";
|
|
1
|
+
import { Home } from "lucide-react";
|
|
13
2
|
|
|
14
|
-
import {
|
|
15
|
-
dataPath,
|
|
16
|
-
formPath,
|
|
17
|
-
processPath,
|
|
18
|
-
viewPath,
|
|
19
|
-
} from "@/app/navigation";
|
|
20
|
-
import {
|
|
21
|
-
dashboardHero,
|
|
22
|
-
dashboardMetrics,
|
|
23
|
-
quickActions,
|
|
24
|
-
recentActivities,
|
|
25
|
-
taskItems,
|
|
26
|
-
trendOverview,
|
|
27
|
-
} from "@/app/starter-content";
|
|
28
|
-
import { runtimeDefaultRoutes } from "@/runtime/default-routes";
|
|
29
|
-
import {
|
|
30
|
-
ListItem,
|
|
31
|
-
MetricCard,
|
|
32
|
-
PageHeader,
|
|
33
|
-
Panel,
|
|
34
|
-
PrimaryButton,
|
|
35
|
-
SecondaryButton,
|
|
36
|
-
StatusPill,
|
|
37
|
-
TrendBars,
|
|
38
|
-
} from "@/shared/ui";
|
|
39
|
-
|
|
40
|
-
const metricIcons = [Inbox, FileText, Database, BarChart3];
|
|
3
|
+
import { PageHeader, Panel, StatusPill } from "@/shared/ui";
|
|
41
4
|
|
|
42
5
|
export function AdminDashboardPage() {
|
|
43
|
-
const { appType = "" } = useParams();
|
|
44
|
-
const actionPaths = useActionPaths(appType);
|
|
45
|
-
|
|
46
6
|
return (
|
|
47
7
|
<div className="min-w-0 space-y-5">
|
|
48
8
|
<PageHeader
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<Database size={17} />
|
|
53
|
-
查看数据
|
|
54
|
-
</SecondaryButton>
|
|
55
|
-
<PrimaryButton onClick={() => window.location.assign(actionPaths.form)}>
|
|
56
|
-
<FileText size={17} />
|
|
57
|
-
发起申请
|
|
58
|
-
</PrimaryButton>
|
|
59
|
-
</>
|
|
60
|
-
}
|
|
61
|
-
description={dashboardHero.description}
|
|
62
|
-
meta={
|
|
63
|
-
<>
|
|
64
|
-
<StatusPill tone="blue">业务办理</StatusPill>
|
|
65
|
-
<StatusPill tone="emerald">数据可查</StatusPill>
|
|
66
|
-
<StatusPill tone="amber">待办提醒</StatusPill>
|
|
67
|
-
</>
|
|
68
|
-
}
|
|
69
|
-
title={dashboardHero.title}
|
|
9
|
+
description="这是应用默认首页。你可以在这里接入真实业务模块、数据看板或常用操作。"
|
|
10
|
+
meta={<StatusPill tone="blue">默认首页</StatusPill>}
|
|
11
|
+
title="首页"
|
|
70
12
|
/>
|
|
71
13
|
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
icon={metricIcons[index]}
|
|
77
|
-
key={metric.label}
|
|
78
|
-
label={metric.label}
|
|
79
|
-
tone={metric.tone}
|
|
80
|
-
value={metric.value}
|
|
81
|
-
/>
|
|
82
|
-
))}
|
|
83
|
-
</section>
|
|
84
|
-
|
|
85
|
-
<section className="grid gap-5 xl:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.65fr)]">
|
|
86
|
-
<Panel
|
|
87
|
-
action={<StatusPill tone="emerald">持续更新</StatusPill>}
|
|
88
|
-
description={trendOverview.description}
|
|
89
|
-
title={trendOverview.title}
|
|
90
|
-
>
|
|
91
|
-
<div className="grid gap-5 lg:grid-cols-[minmax(0,1fr)_240px]">
|
|
92
|
-
<TrendBars values={trendOverview.values} tone="blue" />
|
|
93
|
-
<div className="grid gap-3">
|
|
94
|
-
{trendOverview.summary.map(item => (
|
|
95
|
-
<div
|
|
96
|
-
className="rounded-2xl bg-slate-50 px-4 py-3 ring-1 ring-slate-200/70"
|
|
97
|
-
key={item.label}
|
|
98
|
-
>
|
|
99
|
-
<div className="text-xs text-slate-500">{item.label}</div>
|
|
100
|
-
<div className="mt-1 text-lg font-semibold text-slate-950">{item.value}</div>
|
|
101
|
-
</div>
|
|
102
|
-
))}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
</Panel>
|
|
106
|
-
|
|
107
|
-
<Panel title="快捷入口" description="把高频业务放在这里,减少用户查找成本。">
|
|
108
|
-
<div className="space-y-3">
|
|
109
|
-
{quickActions.map(action => (
|
|
110
|
-
<button
|
|
111
|
-
className="block w-full text-left"
|
|
112
|
-
key={action.label}
|
|
113
|
-
onClick={() => window.location.assign(actionPaths[action.type])}
|
|
114
|
-
type="button"
|
|
115
|
-
>
|
|
116
|
-
<ListItem
|
|
117
|
-
icon={resolveActionIcon(action.type)}
|
|
118
|
-
tone={action.tone}
|
|
119
|
-
>
|
|
120
|
-
<div className="flex items-center justify-between gap-3">
|
|
121
|
-
<div className="min-w-0">
|
|
122
|
-
<div className="truncate text-sm font-semibold text-slate-950">{action.label}</div>
|
|
123
|
-
<div className="mt-1 truncate text-xs text-slate-500">{action.desc}</div>
|
|
124
|
-
</div>
|
|
125
|
-
<ArrowRight className="shrink-0 text-slate-400" size={17} />
|
|
126
|
-
</div>
|
|
127
|
-
</ListItem>
|
|
128
|
-
</button>
|
|
129
|
-
))}
|
|
130
|
-
</div>
|
|
131
|
-
</Panel>
|
|
132
|
-
</section>
|
|
133
|
-
|
|
134
|
-
<section className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_360px]">
|
|
135
|
-
<Panel title="待办事项" description="优先处理即将到期和影响业务推进的事项。">
|
|
136
|
-
<div className="space-y-3">
|
|
137
|
-
{taskItems.map(item => (
|
|
138
|
-
<ListItem
|
|
139
|
-
icon={<ClipboardList size={17} />}
|
|
140
|
-
key={item.title}
|
|
141
|
-
tone={item.priority === "high" ? "rose" : item.priority === "medium" ? "amber" : "blue"}
|
|
142
|
-
>
|
|
143
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
144
|
-
<div className="min-w-0">
|
|
145
|
-
<div className="truncate text-sm font-semibold text-slate-950">{item.title}</div>
|
|
146
|
-
<div className="mt-1 text-xs text-slate-500">
|
|
147
|
-
{item.assignee} · {item.due}
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
<StatusPill tone={item.priority === "high" ? "rose" : "amber"}>{item.status}</StatusPill>
|
|
151
|
-
</div>
|
|
152
|
-
</ListItem>
|
|
153
|
-
))}
|
|
14
|
+
<Panel className="min-h-[420px]">
|
|
15
|
+
<div className="flex min-h-[360px] flex-col items-center justify-center rounded-2xl border border-dashed border-slate-200 bg-white/60 px-6 text-center">
|
|
16
|
+
<div className="grid h-14 w-14 place-items-center rounded-2xl bg-blue-50 text-blue-700 ring-1 ring-blue-100">
|
|
17
|
+
<Home size={24} />
|
|
154
18
|
</div>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
icon={<Sparkles size={17} />}
|
|
162
|
-
key={activity}
|
|
163
|
-
tone={index % 2 === 0 ? "emerald" : "violet"}
|
|
164
|
-
>
|
|
165
|
-
<div className="text-sm leading-6 text-slate-600">{activity}</div>
|
|
166
|
-
</ListItem>
|
|
167
|
-
))}
|
|
168
|
-
</div>
|
|
169
|
-
</Panel>
|
|
170
|
-
</section>
|
|
19
|
+
<h2 className="mt-5 text-lg font-semibold text-slate-950">默认首页</h2>
|
|
20
|
+
<p className="mt-2 max-w-md text-sm leading-6 text-slate-500">
|
|
21
|
+
保留应用框架和登录账号信息,页面内容由后续业务开发自行接入。
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
</Panel>
|
|
171
25
|
</div>
|
|
172
26
|
);
|
|
173
27
|
}
|
|
174
|
-
|
|
175
|
-
function useActionPaths(appType: string) {
|
|
176
|
-
return useMemo(
|
|
177
|
-
() => ({
|
|
178
|
-
data: dataPath(appType, runtimeDefaultRoutes.dataManageList?.formUuid || runtimeDefaultRoutes.formSubmit?.formUuid),
|
|
179
|
-
form: formPath(appType, runtimeDefaultRoutes.formSubmit?.formUuid),
|
|
180
|
-
portal: viewPath(appType, "portal"),
|
|
181
|
-
process: processPath(appType, runtimeDefaultRoutes.processSubmit?.formUuid),
|
|
182
|
-
tasks: viewPath(appType, "admin/tasks"),
|
|
183
|
-
}),
|
|
184
|
-
[appType],
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function resolveActionIcon(type: (typeof quickActions)[number]["type"]) {
|
|
189
|
-
if (type === "form") return <FileText size={17} />;
|
|
190
|
-
if (type === "tasks") return <Inbox size={17} />;
|
|
191
|
-
if (type === "data") return <Database size={17} />;
|
|
192
|
-
return <Workflow size={17} />;
|
|
193
|
-
}
|
|
@@ -2,57 +2,12 @@
|
|
|
2
2
|
"menus": [
|
|
3
3
|
{
|
|
4
4
|
"code": "admin_dashboard",
|
|
5
|
-
"name": "
|
|
5
|
+
"name": "首页",
|
|
6
6
|
"type": "nav",
|
|
7
7
|
"routeCode": "admin.dashboard",
|
|
8
8
|
"path": "/view/:appType/admin",
|
|
9
|
-
"icon": "LayoutDashboard",
|
|
10
|
-
"sortOrder": 10
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"code": "task_center",
|
|
14
|
-
"name": "待办中心",
|
|
15
|
-
"type": "nav",
|
|
16
|
-
"routeCode": "admin.tasks",
|
|
17
|
-
"path": "/view/:appType/admin/tasks",
|
|
18
|
-
"icon": "Inbox",
|
|
19
|
-
"sortOrder": 20
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"code": "service_center",
|
|
23
|
-
"name": "服务入口",
|
|
24
|
-
"type": "nav",
|
|
25
|
-
"routeCode": "admin.services",
|
|
26
|
-
"path": "/view/:appType/admin/services",
|
|
27
|
-
"icon": "ClipboardList",
|
|
28
|
-
"sortOrder": 30
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"code": "data_center",
|
|
32
|
-
"name": "数据中心",
|
|
33
|
-
"type": "nav",
|
|
34
|
-
"routeCode": "admin.data",
|
|
35
|
-
"path": "/view/:appType/admin/data",
|
|
36
|
-
"icon": "Database",
|
|
37
|
-
"sortOrder": 40
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"code": "user_portal",
|
|
41
|
-
"name": "用户门户",
|
|
42
|
-
"type": "nav",
|
|
43
|
-
"routeCode": "portal.home",
|
|
44
|
-
"path": "/view/:appType/portal",
|
|
45
9
|
"icon": "Home",
|
|
46
|
-
"sortOrder":
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"code": "public_home",
|
|
50
|
-
"name": "公开服务",
|
|
51
|
-
"type": "nav",
|
|
52
|
-
"routeCode": "public.home",
|
|
53
|
-
"path": "/view/:appType/public",
|
|
54
|
-
"icon": "Globe",
|
|
55
|
-
"sortOrder": 60
|
|
10
|
+
"sortOrder": 10
|
|
56
11
|
}
|
|
57
12
|
]
|
|
58
13
|
}
|
package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-admin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"code": "app_admin_spa_pages",
|
|
3
|
-
"name": "
|
|
3
|
+
"name": "应用管理员页面权限",
|
|
4
4
|
"roles": ["app_admin"],
|
|
5
|
-
"menuCodes": ["
|
|
6
|
-
"routeCodes": ["admin.dashboard"
|
|
7
|
-
"pathPatterns": ["/view/:appType/admin
|
|
5
|
+
"menuCodes": ["admin_dashboard"],
|
|
6
|
+
"routeCodes": ["admin.dashboard"],
|
|
7
|
+
"pathPatterns": ["/view/:appType/admin", "/view/:appType/admin/*"]
|
|
8
8
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Globe2, ShieldCheck } from "lucide-react";
|
|
2
|
-
import { Link, Outlet, useParams } from "react-router-dom";
|
|
3
|
-
|
|
4
|
-
import { StatusPill } from "@/shared/ui";
|
|
5
|
-
|
|
6
|
-
export function PublicShell() {
|
|
7
|
-
const { appType = "" } = useParams();
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#f8fafc_0%,#eef2f7_100%)]">
|
|
11
|
-
<header className="border-b border-white/70 bg-white/[0.78] backdrop-blur-xl">
|
|
12
|
-
<div className="mx-auto flex max-w-6xl items-center justify-between gap-3 px-5 py-4">
|
|
13
|
-
<Link className="flex min-w-0 items-center gap-3" to={`/view/${appType}/public`}>
|
|
14
|
-
<span className="grid h-11 w-11 shrink-0 place-items-center rounded-2xl bg-slate-950 text-white">
|
|
15
|
-
<Globe2 size={22} />
|
|
16
|
-
</span>
|
|
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">无需登录的服务入口</span>
|
|
20
|
-
</span>
|
|
21
|
-
</Link>
|
|
22
|
-
<StatusPill tone="blue">
|
|
23
|
-
<ShieldCheck size={13} />
|
|
24
|
-
安全访问
|
|
25
|
-
</StatusPill>
|
|
26
|
-
</div>
|
|
27
|
-
</header>
|
|
28
|
-
<main className="mx-auto min-w-0 max-w-6xl px-5 py-8">
|
|
29
|
-
<Outlet />
|
|
30
|
-
</main>
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Home, LogIn, Shield, Sparkles } from "lucide-react";
|
|
2
|
-
import { useEffect } from "react";
|
|
3
|
-
import { Link, Outlet, useParams } from "react-router-dom";
|
|
4
|
-
import {
|
|
5
|
-
PermissionBoundary,
|
|
6
|
-
useCanAccessRoute,
|
|
7
|
-
useRuntimeAuth,
|
|
8
|
-
useRuntimeBootstrap,
|
|
9
|
-
type PermissionBoundaryFallbackState,
|
|
10
|
-
} from "openxiangda/runtime/react";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
PrimaryButton,
|
|
14
|
-
SecondaryButton,
|
|
15
|
-
StatePage,
|
|
16
|
-
StatusPill,
|
|
17
|
-
} from "@/shared/ui";
|
|
18
|
-
|
|
19
|
-
export function UserShell() {
|
|
20
|
-
const { appType = "" } = useParams();
|
|
21
|
-
const bootstrap = useRuntimeBootstrap();
|
|
22
|
-
const adminAccess = useCanAccessRoute({
|
|
23
|
-
routeCode: "admin.dashboard",
|
|
24
|
-
menuCode: "admin_dashboard",
|
|
25
|
-
path: `/view/${appType}/admin`,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<PermissionBoundary
|
|
30
|
-
fallback={state => <UserAccessState appType={appType} state={state} />}
|
|
31
|
-
loadingFallback={<UserLoadingState />}
|
|
32
|
-
menuCode="user_portal"
|
|
33
|
-
path={`/view/${appType}/portal`}
|
|
34
|
-
routeCode="portal.home"
|
|
35
|
-
>
|
|
36
|
-
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#eef2f7_0%,#f8fafc_58%,#eef2f7_100%)]">
|
|
37
|
-
<header className="border-b border-white/70 bg-white/[0.78] backdrop-blur-xl">
|
|
38
|
-
<div className="mx-auto flex h-20 max-w-6xl items-center justify-between gap-3 px-5 py-4">
|
|
39
|
-
<Link className="flex min-w-0 items-center gap-3" to={`/view/${appType}/portal`}>
|
|
40
|
-
<span className="grid h-11 w-11 shrink-0 place-items-center rounded-2xl bg-slate-950 text-sm font-semibold text-white shadow-lg shadow-slate-300/70">
|
|
41
|
-
OX
|
|
42
|
-
</span>
|
|
43
|
-
<span className="min-w-0">
|
|
44
|
-
<span className="block truncate text-base font-semibold text-slate-950">
|
|
45
|
-
{String(bootstrap.data?.app?.name || "用户门户")}
|
|
46
|
-
</span>
|
|
47
|
-
<span className="block truncate text-xs text-slate-500">服务门户</span>
|
|
48
|
-
</span>
|
|
49
|
-
</Link>
|
|
50
|
-
<div className="flex shrink-0 items-center gap-2">
|
|
51
|
-
<StatusPill tone="emerald">已登录</StatusPill>
|
|
52
|
-
{adminAccess.canAccess ? (
|
|
53
|
-
<Link
|
|
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"
|
|
55
|
-
to={`/view/${appType}/admin`}
|
|
56
|
-
>
|
|
57
|
-
<Home size={17} />
|
|
58
|
-
管理后台
|
|
59
|
-
</Link>
|
|
60
|
-
) : null}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</header>
|
|
64
|
-
<main className="mx-auto min-w-0 max-w-6xl px-5 py-6">
|
|
65
|
-
<Outlet />
|
|
66
|
-
</main>
|
|
67
|
-
</div>
|
|
68
|
-
</PermissionBoundary>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function UserAccessState({
|
|
73
|
-
appType,
|
|
74
|
-
state,
|
|
75
|
-
}: {
|
|
76
|
-
appType: string;
|
|
77
|
-
state: PermissionBoundaryFallbackState;
|
|
78
|
-
}) {
|
|
79
|
-
const auth = useRuntimeAuth();
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (state.errorType === "unauthenticated") {
|
|
83
|
-
void auth.redirectToLogin({ replace: true });
|
|
84
|
-
}
|
|
85
|
-
}, [auth, state.errorType]);
|
|
86
|
-
|
|
87
|
-
if (state.errorType === "unauthenticated") {
|
|
88
|
-
return (
|
|
89
|
-
<StatePage
|
|
90
|
-
actions={<PrimaryButton onClick={() => void auth.redirectToLogin({ replace: true })}>去登录</PrimaryButton>}
|
|
91
|
-
description="当前没有有效登录态,正在跳转到登录页。"
|
|
92
|
-
fullScreen
|
|
93
|
-
icon={<LogIn size={24} />}
|
|
94
|
-
status="401"
|
|
95
|
-
title="需要登录"
|
|
96
|
-
/>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<StatePage
|
|
102
|
-
actions={
|
|
103
|
-
<>
|
|
104
|
-
<SecondaryButton onClick={() => window.history.back()}>返回上一页</SecondaryButton>
|
|
105
|
-
<PrimaryButton onClick={() => window.location.assign(`/view/${appType}/public`)}>
|
|
106
|
-
打开公开页
|
|
107
|
-
</PrimaryButton>
|
|
108
|
-
</>
|
|
109
|
-
}
|
|
110
|
-
description={state.message || "请联系管理员开通用户门户页面权限。"}
|
|
111
|
-
fullScreen
|
|
112
|
-
icon={<Shield size={24} />}
|
|
113
|
-
status="403"
|
|
114
|
-
title="无权访问用户门户"
|
|
115
|
-
/>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function UserLoadingState() {
|
|
120
|
-
return (
|
|
121
|
-
<StatePage
|
|
122
|
-
description="正在确认当前用户、菜单和页面权限。"
|
|
123
|
-
fullScreen
|
|
124
|
-
icon={<Sparkles size={24} />}
|
|
125
|
-
status="LOADING"
|
|
126
|
-
title="正在进入用户门户"
|
|
127
|
-
/>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { ArrowRight, BarChart3, Database, Filter, RefreshCw, Search } from "lucide-react";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { useParams } from "react-router-dom";
|
|
4
|
-
|
|
5
|
-
import { dataPath } from "@/app/navigation";
|
|
6
|
-
import { dataRows } from "@/app/starter-content";
|
|
7
|
-
import { runtimeDefaultRoutes } from "@/runtime/default-routes";
|
|
8
|
-
import {
|
|
9
|
-
ListItem,
|
|
10
|
-
MetricCard,
|
|
11
|
-
PageHeader,
|
|
12
|
-
Panel,
|
|
13
|
-
PrimaryButton,
|
|
14
|
-
SecondaryButton,
|
|
15
|
-
StatusPill,
|
|
16
|
-
TrendBars,
|
|
17
|
-
} from "@/shared/ui";
|
|
18
|
-
|
|
19
|
-
export function DataCenterPage() {
|
|
20
|
-
const { appType = "" } = useParams();
|
|
21
|
-
const dataFormUuid = runtimeDefaultRoutes.dataManageList?.formUuid || runtimeDefaultRoutes.formSubmit?.formUuid;
|
|
22
|
-
const listPath = useMemo(
|
|
23
|
-
() => dataPath(appType, dataFormUuid),
|
|
24
|
-
[appType, dataFormUuid],
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="min-w-0 space-y-5">
|
|
29
|
-
<PageHeader
|
|
30
|
-
actions={
|
|
31
|
-
<>
|
|
32
|
-
<SecondaryButton>
|
|
33
|
-
<Filter size={17} />
|
|
34
|
-
筛选
|
|
35
|
-
</SecondaryButton>
|
|
36
|
-
{dataFormUuid ? (
|
|
37
|
-
<PrimaryButton onClick={() => window.location.assign(listPath)}>
|
|
38
|
-
<Database size={17} />
|
|
39
|
-
打开数据列表
|
|
40
|
-
</PrimaryButton>
|
|
41
|
-
) : (
|
|
42
|
-
<StatusPill tone="amber">配置后可用</StatusPill>
|
|
43
|
-
)}
|
|
44
|
-
</>
|
|
45
|
-
}
|
|
46
|
-
description="汇总关键数据、业务趋势和常用查询入口,帮助团队快速掌握当前进展。"
|
|
47
|
-
meta={
|
|
48
|
-
<>
|
|
49
|
-
<StatusPill tone="emerald">权限数据</StatusPill>
|
|
50
|
-
<StatusPill tone="blue">业务台账</StatusPill>
|
|
51
|
-
<StatusPill tone="violet">趋势分析</StatusPill>
|
|
52
|
-
</>
|
|
53
|
-
}
|
|
54
|
-
title="数据中心"
|
|
55
|
-
/>
|
|
56
|
-
|
|
57
|
-
<section className="grid gap-4 md:grid-cols-3">
|
|
58
|
-
{dataRows.map((row, index) => (
|
|
59
|
-
<MetricCard
|
|
60
|
-
caption={row.trend}
|
|
61
|
-
icon={index === 0 ? BarChart3 : index === 1 ? RefreshCw : Database}
|
|
62
|
-
key={row.label}
|
|
63
|
-
label={row.label}
|
|
64
|
-
tone={index === 0 ? "blue" : index === 1 ? "amber" : "emerald"}
|
|
65
|
-
value={row.count}
|
|
66
|
-
/>
|
|
67
|
-
))}
|
|
68
|
-
</section>
|
|
69
|
-
|
|
70
|
-
<section className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_360px]">
|
|
71
|
-
<Panel title="业务趋势" description="用于呈现关键业务数据的变化节奏。">
|
|
72
|
-
<TrendBars values={[24, 36, 42, 39, 58, 64, 72, 81]} tone="emerald" />
|
|
73
|
-
</Panel>
|
|
74
|
-
|
|
75
|
-
<Panel title="常用查询">
|
|
76
|
-
<button
|
|
77
|
-
className="block w-full text-left disabled:cursor-not-allowed disabled:opacity-70"
|
|
78
|
-
disabled={!dataFormUuid}
|
|
79
|
-
onClick={() => dataFormUuid && window.location.assign(listPath)}
|
|
80
|
-
type="button"
|
|
81
|
-
>
|
|
82
|
-
<ListItem icon={<Search size={17} />} tone="blue">
|
|
83
|
-
<div className="flex items-center justify-between gap-3">
|
|
84
|
-
<div className="min-w-0">
|
|
85
|
-
<div className="truncate text-sm font-semibold text-slate-950">业务数据列表</div>
|
|
86
|
-
<div className="mt-1 truncate text-xs text-slate-500">查看当前账号有权限访问的数据。</div>
|
|
87
|
-
</div>
|
|
88
|
-
<ArrowRight className="shrink-0 text-slate-400" size={17} />
|
|
89
|
-
</div>
|
|
90
|
-
</ListItem>
|
|
91
|
-
</button>
|
|
92
|
-
</Panel>
|
|
93
|
-
</section>
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { ArrowRight, Database, FileText, Home, Workflow } from "lucide-react";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { useParams } from "react-router-dom";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
dataPath,
|
|
7
|
-
formPath,
|
|
8
|
-
processPath,
|
|
9
|
-
viewPath,
|
|
10
|
-
} from "@/app/navigation";
|
|
11
|
-
import { serviceEntries } from "@/app/starter-content";
|
|
12
|
-
import { runtimeDefaultRoutes } from "@/runtime/default-routes";
|
|
13
|
-
import {
|
|
14
|
-
ListItem,
|
|
15
|
-
PageHeader,
|
|
16
|
-
Panel,
|
|
17
|
-
PrimaryButton,
|
|
18
|
-
StatusPill,
|
|
19
|
-
} from "@/shared/ui";
|
|
20
|
-
|
|
21
|
-
export function ServiceCenterPage() {
|
|
22
|
-
const { appType = "" } = useParams();
|
|
23
|
-
const paths = useMemo(
|
|
24
|
-
() => ({
|
|
25
|
-
data: dataPath(appType, runtimeDefaultRoutes.dataManageList?.formUuid || runtimeDefaultRoutes.formSubmit?.formUuid),
|
|
26
|
-
form: formPath(appType, runtimeDefaultRoutes.formSubmit?.formUuid),
|
|
27
|
-
portal: viewPath(appType, "portal"),
|
|
28
|
-
process: processPath(appType, runtimeDefaultRoutes.processSubmit?.formUuid),
|
|
29
|
-
}),
|
|
30
|
-
[appType],
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div className="min-w-0 space-y-5">
|
|
35
|
-
<PageHeader
|
|
36
|
-
actions={
|
|
37
|
-
<PrimaryButton onClick={() => window.location.assign(paths.form)}>
|
|
38
|
-
<FileText size={17} />
|
|
39
|
-
发起申请
|
|
40
|
-
</PrimaryButton>
|
|
41
|
-
}
|
|
42
|
-
description="把常用表单、流程、数据和门户入口集中起来,形成清晰的业务办理路径。"
|
|
43
|
-
meta={
|
|
44
|
-
<>
|
|
45
|
-
<StatusPill tone="blue">申请登记</StatusPill>
|
|
46
|
-
<StatusPill tone="amber">流程办理</StatusPill>
|
|
47
|
-
<StatusPill tone="emerald">数据查询</StatusPill>
|
|
48
|
-
</>
|
|
49
|
-
}
|
|
50
|
-
title="服务入口"
|
|
51
|
-
/>
|
|
52
|
-
|
|
53
|
-
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
54
|
-
{serviceEntries.map(entry => (
|
|
55
|
-
<button
|
|
56
|
-
className="block text-left"
|
|
57
|
-
key={entry.label}
|
|
58
|
-
onClick={() => window.location.assign(paths[entry.type])}
|
|
59
|
-
type="button"
|
|
60
|
-
>
|
|
61
|
-
<div className="h-full rounded-2xl border border-white/70 bg-white/[0.86] p-5 shadow-[0_18px_55px_rgba(15,23,42,0.06)] ring-1 ring-slate-900/5 transition hover:-translate-y-0.5 hover:shadow-[0_24px_70px_rgba(15,23,42,0.10)]">
|
|
62
|
-
<div className="flex items-start justify-between gap-3">
|
|
63
|
-
<span className="grid h-11 w-11 place-items-center rounded-2xl bg-blue-50 text-blue-700">
|
|
64
|
-
{resolveServiceIcon(entry.type)}
|
|
65
|
-
</span>
|
|
66
|
-
<ArrowRight className="text-slate-300" size={18} />
|
|
67
|
-
</div>
|
|
68
|
-
<div className="mt-5 text-base font-semibold text-slate-950">{entry.label}</div>
|
|
69
|
-
<div className="mt-2 text-sm leading-6 text-slate-500">{entry.desc}</div>
|
|
70
|
-
</div>
|
|
71
|
-
</button>
|
|
72
|
-
))}
|
|
73
|
-
</section>
|
|
74
|
-
|
|
75
|
-
<Panel title="服务组织方式" description="不同服务按办理方式归类,用户可以更快找到需要发起或查询的业务。">
|
|
76
|
-
<div className="grid gap-3 lg:grid-cols-3">
|
|
77
|
-
<ListItem icon={<FileText size={17} />} tone="blue">
|
|
78
|
-
<div className="text-sm font-semibold text-slate-950">表单入口</div>
|
|
79
|
-
<div className="mt-1 text-xs leading-5 text-slate-500">适合标准登记、收集和提交场景。</div>
|
|
80
|
-
</ListItem>
|
|
81
|
-
<ListItem icon={<Workflow size={17} />} tone="amber">
|
|
82
|
-
<div className="text-sm font-semibold text-slate-950">流程入口</div>
|
|
83
|
-
<div className="mt-1 text-xs leading-5 text-slate-500">适合审批、办理、抄送和多角色协同。</div>
|
|
84
|
-
</ListItem>
|
|
85
|
-
<ListItem icon={<Database size={17} />} tone="emerald">
|
|
86
|
-
<div className="text-sm font-semibold text-slate-950">数据入口</div>
|
|
87
|
-
<div className="mt-1 text-xs leading-5 text-slate-500">适合查询台账、跟进状态和查看历史记录。</div>
|
|
88
|
-
</ListItem>
|
|
89
|
-
</div>
|
|
90
|
-
</Panel>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function resolveServiceIcon(type: (typeof serviceEntries)[number]["type"]) {
|
|
96
|
-
if (type === "form") return <FileText size={21} />;
|
|
97
|
-
if (type === "process") return <Workflow size={21} />;
|
|
98
|
-
if (type === "data") return <Database size={21} />;
|
|
99
|
-
return <Home size={21} />;
|
|
100
|
-
}
|