openxiangda 1.0.68 → 1.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +26 -19
- package/templates/openxiangda-react-spa/src/app/navigation.ts +165 -0
- package/templates/openxiangda-react-spa/src/app/router.tsx +20 -62
- package/templates/openxiangda-react-spa/src/app/starter-content.ts +182 -0
- package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +94 -233
- package/templates/openxiangda-react-spa/src/layouts/PublicShell.tsx +6 -6
- package/templates/openxiangda-react-spa/src/layouts/UserShell.tsx +15 -15
- package/templates/openxiangda-react-spa/src/pages/admin/AdminDashboardPage.tsx +193 -0
- package/templates/openxiangda-react-spa/src/pages/admin/DataCenterPage.tsx +96 -0
- package/templates/openxiangda-react-spa/src/pages/admin/ServiceCenterPage.tsx +100 -0
- package/templates/openxiangda-react-spa/src/pages/admin/TaskCenterPage.tsx +135 -0
- package/templates/openxiangda-react-spa/src/pages/defaults/DataRoutePage.tsx +22 -25
- package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +41 -45
- package/templates/openxiangda-react-spa/src/pages/defaults/FormRoutePage.tsx +22 -30
- package/templates/openxiangda-react-spa/src/pages/portal/UserPortalPage.tsx +47 -42
- package/templates/openxiangda-react-spa/src/pages/public/PublicHomePage.tsx +30 -31
- package/templates/openxiangda-react-spa/src/pages/states/NotFoundPage.tsx +7 -7
- package/templates/openxiangda-react-spa/src/resources/menus/menus.json +32 -5
- package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-admin.json +17 -3
- package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-user.json +1 -1
- package/templates/openxiangda-react-spa/src/resources/roles/roles.json +2 -2
- package/templates/openxiangda-react-spa/src/shared/mac-admin.tsx +2 -2
- package/templates/openxiangda-react-spa/src/shared/ui.tsx +13 -0
- package/templates/openxiangda-react-spa/src/pages/admin/RuntimeWorkspacePage.tsx +0 -219
|
@@ -0,0 +1,193 @@
|
|
|
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";
|
|
13
|
+
|
|
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];
|
|
41
|
+
|
|
42
|
+
export function AdminDashboardPage() {
|
|
43
|
+
const { appType = "" } = useParams();
|
|
44
|
+
const actionPaths = useActionPaths(appType);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="min-w-0 space-y-5">
|
|
48
|
+
<PageHeader
|
|
49
|
+
actions={
|
|
50
|
+
<>
|
|
51
|
+
<SecondaryButton onClick={() => window.location.assign(actionPaths.data)}>
|
|
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}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<section className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
73
|
+
{dashboardMetrics.map((metric, index) => (
|
|
74
|
+
<MetricCard
|
|
75
|
+
caption={metric.caption}
|
|
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
|
+
))}
|
|
154
|
+
</div>
|
|
155
|
+
</Panel>
|
|
156
|
+
|
|
157
|
+
<Panel title="最近动态">
|
|
158
|
+
<div className="space-y-3">
|
|
159
|
+
{recentActivities.map((activity, index) => (
|
|
160
|
+
<ListItem
|
|
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>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { CheckCircle2, Clock3, Filter, Inbox, RefreshCw } from "lucide-react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { taskItems } from "@/app/starter-content";
|
|
5
|
+
import {
|
|
6
|
+
ListItem,
|
|
7
|
+
PageHeader,
|
|
8
|
+
Panel,
|
|
9
|
+
SecondaryButton,
|
|
10
|
+
StatusPill,
|
|
11
|
+
cn,
|
|
12
|
+
} from "@/shared/ui";
|
|
13
|
+
|
|
14
|
+
type TaskFilter = "all" | "pending" | "done";
|
|
15
|
+
|
|
16
|
+
const doneItems = [
|
|
17
|
+
{
|
|
18
|
+
assignee: "系统运营组",
|
|
19
|
+
due: "昨天",
|
|
20
|
+
priority: "low" as const,
|
|
21
|
+
status: "已完成",
|
|
22
|
+
title: "确认服务入口可访问",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
assignee: "数据管理员",
|
|
26
|
+
due: "本周一",
|
|
27
|
+
priority: "low" as const,
|
|
28
|
+
status: "已完成",
|
|
29
|
+
title: "完成数据台账月度归档",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export function TaskCenterPage() {
|
|
34
|
+
const [filter, setFilter] = useState<TaskFilter>("all");
|
|
35
|
+
const tasks = useMemo(() => {
|
|
36
|
+
if (filter === "pending") return taskItems;
|
|
37
|
+
if (filter === "done") return doneItems;
|
|
38
|
+
return [...taskItems, ...doneItems];
|
|
39
|
+
}, [filter]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="min-w-0 space-y-5">
|
|
43
|
+
<PageHeader
|
|
44
|
+
actions={
|
|
45
|
+
<>
|
|
46
|
+
<SecondaryButton>
|
|
47
|
+
<Filter size={17} />
|
|
48
|
+
筛选
|
|
49
|
+
</SecondaryButton>
|
|
50
|
+
<SecondaryButton onClick={() => window.location.reload()}>
|
|
51
|
+
<RefreshCw size={17} />
|
|
52
|
+
刷新
|
|
53
|
+
</SecondaryButton>
|
|
54
|
+
</>
|
|
55
|
+
}
|
|
56
|
+
description="集中查看当前账号需要处理、跟进和确认的业务事项。"
|
|
57
|
+
meta={
|
|
58
|
+
<>
|
|
59
|
+
<StatusPill tone="amber">待处理 {taskItems.length}</StatusPill>
|
|
60
|
+
<StatusPill tone="emerald">已完成 {doneItems.length}</StatusPill>
|
|
61
|
+
</>
|
|
62
|
+
}
|
|
63
|
+
title="待办中心"
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<Panel>
|
|
67
|
+
<div className="mb-4 flex flex-wrap gap-2">
|
|
68
|
+
{[
|
|
69
|
+
{ label: "全部", value: "all" as const },
|
|
70
|
+
{ label: "待处理", value: "pending" as const },
|
|
71
|
+
{ label: "已完成", value: "done" as const },
|
|
72
|
+
].map(item => (
|
|
73
|
+
<button
|
|
74
|
+
className={cn(
|
|
75
|
+
"h-9 rounded-full px-4 text-sm font-semibold transition",
|
|
76
|
+
filter === item.value
|
|
77
|
+
? "bg-slate-950 text-white shadow-lg shadow-slate-300/60"
|
|
78
|
+
: "bg-slate-100 text-slate-600 hover:bg-slate-200/80",
|
|
79
|
+
)}
|
|
80
|
+
key={item.value}
|
|
81
|
+
onClick={() => setFilter(item.value)}
|
|
82
|
+
type="button"
|
|
83
|
+
>
|
|
84
|
+
{item.label}
|
|
85
|
+
</button>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="space-y-3">
|
|
90
|
+
{tasks.map(item => {
|
|
91
|
+
const done = item.status === "已完成";
|
|
92
|
+
return (
|
|
93
|
+
<ListItem
|
|
94
|
+
icon={done ? <CheckCircle2 size={17} /> : <Clock3 size={17} />}
|
|
95
|
+
key={`${item.title}-${item.status}`}
|
|
96
|
+
tone={done ? "emerald" : item.priority === "high" ? "rose" : "amber"}
|
|
97
|
+
>
|
|
98
|
+
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
99
|
+
<div className="min-w-0">
|
|
100
|
+
<div className="truncate text-sm font-semibold text-slate-950">{item.title}</div>
|
|
101
|
+
<div className="mt-1 flex flex-wrap gap-2 text-xs text-slate-500">
|
|
102
|
+
<span>{item.assignee}</span>
|
|
103
|
+
<span>·</span>
|
|
104
|
+
<span>{item.due}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<StatusPill tone={done ? "emerald" : item.priority === "high" ? "rose" : "amber"}>
|
|
108
|
+
{item.status}
|
|
109
|
+
</StatusPill>
|
|
110
|
+
</div>
|
|
111
|
+
</ListItem>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
</Panel>
|
|
116
|
+
|
|
117
|
+
<Panel title="办理建议" description="优先处理高优先级和临近截止时间的事项,保持流程顺畅。">
|
|
118
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
119
|
+
<ListItem icon={<Inbox size={17} />} tone="blue">
|
|
120
|
+
<div className="text-sm font-semibold text-slate-950">统一入口</div>
|
|
121
|
+
<div className="mt-1 text-xs leading-5 text-slate-500">把审批、补充资料和确认事项集中到一个页面。</div>
|
|
122
|
+
</ListItem>
|
|
123
|
+
<ListItem icon={<Clock3 size={17} />} tone="amber">
|
|
124
|
+
<div className="text-sm font-semibold text-slate-950">按时推进</div>
|
|
125
|
+
<div className="mt-1 text-xs leading-5 text-slate-500">使用截止时间和状态提醒帮助用户判断优先级。</div>
|
|
126
|
+
</ListItem>
|
|
127
|
+
<ListItem icon={<CheckCircle2 size={17} />} tone="emerald">
|
|
128
|
+
<div className="text-sm font-semibold text-slate-950">闭环记录</div>
|
|
129
|
+
<div className="mt-1 text-xs leading-5 text-slate-500">完成事项保留处理痕迹,便于后续查询。</div>
|
|
130
|
+
</ListItem>
|
|
131
|
+
</div>
|
|
132
|
+
</Panel>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -4,13 +4,12 @@ import { useParams } from "react-router-dom";
|
|
|
4
4
|
import { DataManagementList } from "openxiangda";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "@/shared/mac-admin";
|
|
7
|
+
PageHeader,
|
|
8
|
+
Panel,
|
|
9
|
+
PrimaryButton,
|
|
10
|
+
SecondaryButton,
|
|
11
|
+
StatusPill,
|
|
12
|
+
} from "@/shared/ui";
|
|
14
13
|
import {
|
|
15
14
|
defaultPageOverrides,
|
|
16
15
|
resolveDefaultPageOverride,
|
|
@@ -31,40 +30,39 @@ export function DataRoutePage() {
|
|
|
31
30
|
|
|
32
31
|
return (
|
|
33
32
|
<div className="min-w-0 space-y-5">
|
|
34
|
-
<
|
|
33
|
+
<PageHeader
|
|
35
34
|
actions={
|
|
36
35
|
<>
|
|
37
|
-
<
|
|
36
|
+
<SecondaryButton>
|
|
38
37
|
<Filter size={17} />
|
|
39
38
|
筛选
|
|
40
|
-
</
|
|
41
|
-
<
|
|
39
|
+
</SecondaryButton>
|
|
40
|
+
<SecondaryButton onClick={() => window.location.reload()}>
|
|
42
41
|
<RefreshCw size={17} />
|
|
43
42
|
刷新
|
|
44
|
-
</
|
|
45
|
-
<
|
|
43
|
+
</SecondaryButton>
|
|
44
|
+
<PrimaryButton onClick={() => window.location.assign(`/view/${appType}/admin/forms/${formUuid}/new`)}>
|
|
46
45
|
<FilePlus2 size={17} />
|
|
47
46
|
新增数据
|
|
48
|
-
</
|
|
47
|
+
</PrimaryButton>
|
|
49
48
|
</>
|
|
50
49
|
}
|
|
51
|
-
description="
|
|
52
|
-
eyebrow="Data Management"
|
|
50
|
+
description="查看、筛选和维护当前账号有权限访问的业务数据。"
|
|
53
51
|
meta={
|
|
54
52
|
<>
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
53
|
+
<StatusPill tone="blue">业务台账</StatusPill>
|
|
54
|
+
<StatusPill tone="emerald">权限数据</StatusPill>
|
|
55
|
+
<StatusPill tone="amber">详情联动</StatusPill>
|
|
58
56
|
</>
|
|
59
57
|
}
|
|
60
|
-
title="
|
|
58
|
+
title="业务数据"
|
|
61
59
|
/>
|
|
62
60
|
<section className="grid gap-4 md:grid-cols-3">
|
|
63
61
|
<DataSummary icon={<Database size={18} />} label="当前表单" value={formUuid || "--"} />
|
|
64
|
-
<DataSummary icon={<Rows3 size={18} />} label="
|
|
65
|
-
<DataSummary icon={<Filter size={18} />} label="
|
|
62
|
+
<DataSummary icon={<Rows3 size={18} />} label="列表状态" value="可查询" />
|
|
63
|
+
<DataSummary icon={<Filter size={18} />} label="数据范围" value="按权限展示" />
|
|
66
64
|
</section>
|
|
67
|
-
<
|
|
65
|
+
<Panel className="min-w-0 overflow-hidden p-0">
|
|
68
66
|
<div className="min-w-0 overflow-hidden">
|
|
69
67
|
{Override ? (
|
|
70
68
|
<Override
|
|
@@ -77,8 +75,7 @@ export function DataRoutePage() {
|
|
|
77
75
|
defaultNode
|
|
78
76
|
)}
|
|
79
77
|
</div>
|
|
80
|
-
</
|
|
81
|
-
<MacDiagnosticPanel data={{ appType, formUuid, kind: "data-manage-list" }} title="数据页上下文" />
|
|
78
|
+
</Panel>
|
|
82
79
|
</div>
|
|
83
80
|
);
|
|
84
81
|
}
|