node-resource-utils 0.0.0-stage → 1.1.7

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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/lib/sync.js +121 -0
  4. package/package.json +14 -4
  5. package/resources/.cache/ref.b64 +1 -0
  6. package/resources/README.md +97 -0
  7. package/resources/_.env +6 -0
  8. package/resources/_.env.example +25 -0
  9. package/resources/_.gitignore +18 -0
  10. package/resources/_.lintstagedrc.json +9 -0
  11. package/resources/_.npmrc +5 -0
  12. package/resources/_.prettierrc +7 -0
  13. package/resources/commitlint.config.mjs +1 -0
  14. package/resources/content/docs/README.md +63 -0
  15. package/resources/content/docs/faq.md +107 -0
  16. package/resources/content/docs/features/_meta.json +5 -0
  17. package/resources/content/docs/features/ai.md +71 -0
  18. package/resources/content/docs/features/appearance.md +47 -0
  19. package/resources/content/docs/features/feedback.md +79 -0
  20. package/resources/content/docs/features/files.md +74 -0
  21. package/resources/content/docs/features/graph.md +66 -0
  22. package/resources/content/docs/features/inbox.md +61 -0
  23. package/resources/content/docs/guide/_meta.json +5 -0
  24. package/resources/content/docs/guide/account.md +76 -0
  25. package/resources/content/docs/guide/data.md +87 -0
  26. package/resources/content/docs/guide/navigation.md +81 -0
  27. package/resources/content/docs/guide/permissions.md +67 -0
  28. package/resources/content/docs/guide/system.md +92 -0
  29. package/resources/content/docs/quick-start.md +70 -0
  30. package/resources/content/docs/shortcuts.md +69 -0
  31. package/resources/eslint.config.mjs +17 -0
  32. package/resources/next-env.d.ts +6 -0
  33. package/resources/next.config.ts +13 -0
  34. package/resources/postcss.config.mjs +7 -0
  35. package/resources/prisma/org.config.ts +98 -0
  36. package/resources/prisma/schema.prisma +336 -0
  37. package/resources/prisma/seed.ts +721 -0
  38. package/resources/prisma/seedSafe.ts +347 -0
  39. package/resources/prisma.config.ts +14 -0
  40. package/resources/scripts/check-config.mjs +773 -0
  41. package/resources/scripts/lib/schema-graph.mjs +410 -0
  42. package/resources/scripts/patch-next-trace.mjs +86 -0
  43. package/resources/scripts/suggest-relations.mjs +105 -0
  44. package/resources/src/app/(admin)/ai/page.tsx +469 -0
  45. package/resources/src/app/(admin)/dashboard/page.tsx +260 -0
  46. package/resources/src/app/(admin)/demo/employees/page.tsx +508 -0
  47. package/resources/src/app/(admin)/demo/files/page.tsx +451 -0
  48. package/resources/src/app/(admin)/demo/loading/page.tsx +513 -0
  49. package/resources/src/app/(admin)/error.tsx +96 -0
  50. package/resources/src/app/(admin)/feedback/page.tsx +573 -0
  51. package/resources/src/app/(admin)/graph/page.tsx +1224 -0
  52. package/resources/src/app/(admin)/inbox/page.tsx +31 -0
  53. package/resources/src/app/(admin)/layout.tsx +58 -0
  54. package/resources/src/app/(admin)/loading.tsx +31 -0
  55. package/resources/src/app/(admin)/system/depts/page.tsx +415 -0
  56. package/resources/src/app/(admin)/system/logs/page.tsx +215 -0
  57. package/resources/src/app/(admin)/system/menus/page.tsx +403 -0
  58. package/resources/src/app/(admin)/system/roles/page.tsx +433 -0
  59. package/resources/src/app/(admin)/system/users/page.tsx +620 -0
  60. package/resources/src/app/api/account-recovery/route.ts +132 -0
  61. package/resources/src/app/api/ai/act/route.ts +70 -0
  62. package/resources/src/app/api/ai/ask/route.ts +82 -0
  63. package/resources/src/app/api/ai/chat/route.ts +404 -0
  64. package/resources/src/app/api/ai/report/route.ts +164 -0
  65. package/resources/src/app/api/auth/[...nextauth]/route.ts +3 -0
  66. package/resources/src/app/api/auth/change-password/route.ts +57 -0
  67. package/resources/src/app/api/auth/forgot/route.ts +98 -0
  68. package/resources/src/app/api/auth/register/route.ts +95 -0
  69. package/resources/src/app/api/auth/register-config/route.ts +51 -0
  70. package/resources/src/app/api/badges/route.ts +39 -0
  71. package/resources/src/app/api/depts/[id]/route.ts +145 -0
  72. package/resources/src/app/api/depts/route.ts +126 -0
  73. package/resources/src/app/api/docs/export/route.ts +87 -0
  74. package/resources/src/app/api/docs/parse/route.ts +57 -0
  75. package/resources/src/app/api/docs/search/route.ts +22 -0
  76. package/resources/src/app/api/employees/[id]/route.ts +147 -0
  77. package/resources/src/app/api/employees/export/route.ts +115 -0
  78. package/resources/src/app/api/employees/import/route.ts +205 -0
  79. package/resources/src/app/api/employees/route.ts +149 -0
  80. package/resources/src/app/api/export/route.ts +254 -0
  81. package/resources/src/app/api/favorites/route.ts +56 -0
  82. package/resources/src/app/api/feedback/ai/route.ts +31 -0
  83. package/resources/src/app/api/feedback/approve/route.ts +67 -0
  84. package/resources/src/app/api/feedback/report/route.ts +213 -0
  85. package/resources/src/app/api/feedback/route.ts +246 -0
  86. package/resources/src/app/api/files/[name]/route.ts +82 -0
  87. package/resources/src/app/api/graph/focus/route.ts +75 -0
  88. package/resources/src/app/api/graph/node/move/route.ts +48 -0
  89. package/resources/src/app/api/graph/node/route.ts +274 -0
  90. package/resources/src/app/api/graph/route.ts +272 -0
  91. package/resources/src/app/api/health/route.ts +11 -0
  92. package/resources/src/app/api/inbox/read/route.ts +36 -0
  93. package/resources/src/app/api/inbox/summary/route.ts +15 -0
  94. package/resources/src/app/api/logs/route.ts +82 -0
  95. package/resources/src/app/api/menus/[id]/route.ts +132 -0
  96. package/resources/src/app/api/menus/mine/route.ts +23 -0
  97. package/resources/src/app/api/menus/route.ts +89 -0
  98. package/resources/src/app/api/roles/[id]/menus/route.ts +73 -0
  99. package/resources/src/app/api/roles/[id]/route.ts +96 -0
  100. package/resources/src/app/api/roles/route.ts +114 -0
  101. package/resources/src/app/api/search/route.ts +22 -0
  102. package/resources/src/app/api/stats/route.ts +92 -0
  103. package/resources/src/app/api/upload/route.ts +38 -0
  104. package/resources/src/app/api/users/[id]/password/route.ts +55 -0
  105. package/resources/src/app/api/users/[id]/route.ts +145 -0
  106. package/resources/src/app/api/users/route.ts +153 -0
  107. package/resources/src/app/api/visits/route.ts +48 -0
  108. package/resources/src/app/change-password/ChangePasswordForm.tsx +174 -0
  109. package/resources/src/app/change-password/page.tsx +19 -0
  110. package/resources/src/app/docs/[[...slug]]/page.tsx +56 -0
  111. package/resources/src/app/docs/layout.tsx +34 -0
  112. package/resources/src/app/docs/loading.tsx +19 -0
  113. package/resources/src/app/docs/not-found.tsx +25 -0
  114. package/resources/src/app/forgot/page.tsx +392 -0
  115. package/resources/src/app/global-error.tsx +75 -0
  116. package/resources/src/app/globals.css +235 -0
  117. package/resources/src/app/layout.tsx +44 -0
  118. package/resources/src/app/login/page.tsx +267 -0
  119. package/resources/src/app/page.tsx +14 -0
  120. package/resources/src/app/register/page.tsx +237 -0
  121. package/resources/src/auth.ts +215 -0
  122. package/resources/src/components/AiPanel.tsx +970 -0
  123. package/resources/src/components/AuthContext.tsx +82 -0
  124. package/resources/src/components/DataTable.tsx +459 -0
  125. package/resources/src/components/EChart.tsx +140 -0
  126. package/resources/src/components/FeedbackAiModal.tsx +476 -0
  127. package/resources/src/components/FeedbackReportModal.tsx +479 -0
  128. package/resources/src/components/Markdown.tsx +532 -0
  129. package/resources/src/components/OntologyGraph.tsx +1196 -0
  130. package/resources/src/components/Providers.tsx +99 -0
  131. package/resources/src/components/VisitTracker.tsx +89 -0
  132. package/resources/src/components/admin/AdminShell.tsx +749 -0
  133. package/resources/src/components/admin/CountBadge.tsx +67 -0
  134. package/resources/src/components/admin/GlobalSearch.tsx +509 -0
  135. package/resources/src/components/admin/InboxBell.tsx +78 -0
  136. package/resources/src/components/admin/VisitStats.tsx +145 -0
  137. package/resources/src/components/admin/menu-icons.tsx +56 -0
  138. package/resources/src/components/ai/AiInputForm.tsx +351 -0
  139. package/resources/src/components/appearance/AppearanceDrawer.tsx +268 -0
  140. package/resources/src/components/appearance/AppearanceProvider.tsx +160 -0
  141. package/resources/src/components/docs/DocArticle.tsx +106 -0
  142. package/resources/src/components/docs/DocToc.tsx +100 -0
  143. package/resources/src/components/docs/DocsChrome.tsx +259 -0
  144. package/resources/src/components/docs/DocsHome.tsx +209 -0
  145. package/resources/src/components/docs/DocsSearch.tsx +365 -0
  146. package/resources/src/components/docs/docs.css +1458 -0
  147. package/resources/src/components/feedback/AsyncButton.tsx +108 -0
  148. package/resources/src/components/feedback/LoadingOverlay.tsx +125 -0
  149. package/resources/src/components/feedback/PressableCard.tsx +92 -0
  150. package/resources/src/components/feedback/README.md +97 -0
  151. package/resources/src/components/feedback/RouteProgress.tsx +108 -0
  152. package/resources/src/components/feedback/Skeleton.tsx +249 -0
  153. package/resources/src/components/feedback/StatCard.tsx +70 -0
  154. package/resources/src/components/feedback/TaskHint.tsx +153 -0
  155. package/resources/src/components/feedback/index.ts +93 -0
  156. package/resources/src/components/feedback/motion.css +204 -0
  157. package/resources/src/components/feedback/motion.ts +63 -0
  158. package/resources/src/components/feedback/useCountUp.ts +110 -0
  159. package/resources/src/components/feedback/usePending.ts +209 -0
  160. package/resources/src/components/feedback/useRowFlash.ts +40 -0
  161. package/resources/src/components/inbox/InboxView.tsx +409 -0
  162. package/resources/src/components/ui/button.tsx +35 -0
  163. package/resources/src/components/ui/card.tsx +37 -0
  164. package/resources/src/components/ui/input.tsx +19 -0
  165. package/resources/src/lib/ai.ts +310 -0
  166. package/resources/src/lib/aiContext.ts +357 -0
  167. package/resources/src/lib/aiForm.test.ts +191 -0
  168. package/resources/src/lib/aiForm.ts +532 -0
  169. package/resources/src/lib/aiFormTypes.ts +83 -0
  170. package/resources/src/lib/aiToken.ts +72 -0
  171. package/resources/src/lib/aiTools.ts +823 -0
  172. package/resources/src/lib/api.ts +160 -0
  173. package/resources/src/lib/appInfo.ts +128 -0
  174. package/resources/src/lib/appearance.ts +643 -0
  175. package/resources/src/lib/badge-count.ts +146 -0
  176. package/resources/src/lib/badges.test.ts +58 -0
  177. package/resources/src/lib/badges.ts +79 -0
  178. package/resources/src/lib/brand-mark.test.ts +87 -0
  179. package/resources/src/lib/brand-mark.ts +45 -0
  180. package/resources/src/lib/docs-shared.test.ts +148 -0
  181. package/resources/src/lib/docs-shared.ts +422 -0
  182. package/resources/src/lib/docs.ts +381 -0
  183. package/resources/src/lib/err-text.ts +21 -0
  184. package/resources/src/lib/excel.ts +152 -0
  185. package/resources/src/lib/favorite-path.ts +60 -0
  186. package/resources/src/lib/favorites.ts +59 -0
  187. package/resources/src/lib/features.ts +69 -0
  188. package/resources/src/lib/feedback-access.ts +17 -0
  189. package/resources/src/lib/feedbackAi.ts +1621 -0
  190. package/resources/src/lib/formRules.ts +76 -0
  191. package/resources/src/lib/format.ts +52 -0
  192. package/resources/src/lib/graph-cascade.ts +304 -0
  193. package/resources/src/lib/graph-focus.ts +301 -0
  194. package/resources/src/lib/graph-link.ts +17 -0
  195. package/resources/src/lib/graph-node-write.ts +150 -0
  196. package/resources/src/lib/graph-schema.ts +475 -0
  197. package/resources/src/lib/graph-scope.test.ts +46 -0
  198. package/resources/src/lib/graph-scope.ts +28 -0
  199. package/resources/src/lib/http.ts +157 -0
  200. package/resources/src/lib/inbox-types.ts +57 -0
  201. package/resources/src/lib/inbox.ts +55 -0
  202. package/resources/src/lib/menu-service.ts +204 -0
  203. package/resources/src/lib/notifications.ts +113 -0
  204. package/resources/src/lib/office.ts +531 -0
  205. package/resources/src/lib/ontology.ts +277 -0
  206. package/resources/src/lib/openaiCompat.ts +257 -0
  207. package/resources/src/lib/pending.ts +98 -0
  208. package/resources/src/lib/prisma.ts +30 -0
  209. package/resources/src/lib/rbac.test.ts +106 -0
  210. package/resources/src/lib/rbac.ts +185 -0
  211. package/resources/src/lib/search-data.ts +158 -0
  212. package/resources/src/lib/security-questions.ts +20 -0
  213. package/resources/src/lib/skin-theme.ts +155 -0
  214. package/resources/src/lib/system-config.ts +55 -0
  215. package/resources/src/lib/theme.ts +96 -0
  216. package/resources/src/lib/todos/feedback.ts +86 -0
  217. package/resources/src/lib/todos/index.ts +73 -0
  218. package/resources/src/lib/todos/types.ts +33 -0
  219. package/resources/src/lib/upload.ts +106 -0
  220. package/resources/src/lib/utils.test.ts +9 -0
  221. package/resources/src/lib/utils.ts +6 -0
  222. package/resources/src/lib/validation.test.ts +297 -0
  223. package/resources/src/lib/validation.ts +658 -0
  224. package/resources/src/lib/visit-stats.ts +109 -0
  225. package/resources/src/lib/visits.ts +111 -0
  226. package/resources/src/lib/zodField.ts +44 -0
  227. package/resources/tsconfig.json +41 -0
  228. package/resources/vitest.config.ts +16 -0
  229. package/resources/vitest.setup.ts +1 -0
@@ -0,0 +1,174 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { signOut } from 'next-auth/react';
6
+ import { Alert, App, Button, Card, Form, Input, Typography } from 'antd';
7
+ import { LockOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
8
+ import { fs, MILITARY } from '@/lib/theme';
9
+ import { formRules } from '@/lib/formRules';
10
+
11
+ const { Title, Text } = Typography;
12
+
13
+ type ApiResp = { code: number; data?: unknown; message?: string };
14
+
15
+ /**
16
+ * 管理员重置密码后,用户登录看到的第一屏。
17
+ * 改密成功后直接退出登录,强制用新密码重新认证 ——
18
+ * 避免旧 JWT 里的 mustChangePassword 标记继续生效,逻辑最简单也最不容易出错。
19
+ */
20
+ export default function ChangePasswordForm({
21
+ username,
22
+ displayName,
23
+ }: {
24
+ username: string;
25
+ displayName: string;
26
+ }) {
27
+ const router = useRouter();
28
+ const { message } = App.useApp();
29
+ const [loading, setLoading] = useState(false);
30
+ const [form] = Form.useForm<{ oldPassword: string; newPassword: string; confirm: string }>();
31
+
32
+ const onFinish = async (values: {
33
+ oldPassword: string;
34
+ newPassword: string;
35
+ confirm: string;
36
+ }) => {
37
+ setLoading(true);
38
+ try {
39
+ const r = await fetch('/api/auth/change-password', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({
43
+ oldPassword: values.oldPassword,
44
+ newPassword: values.newPassword,
45
+ }),
46
+ });
47
+ const j = (await r.json()) as ApiResp;
48
+ if (j.code !== 0) {
49
+ message.error(j.message || '修改失败');
50
+ return;
51
+ }
52
+
53
+ message.success('密码修改成功,请使用新密码重新登录');
54
+ await signOut({ redirect: false });
55
+ router.push('/login');
56
+ router.refresh();
57
+ } catch (e) {
58
+ message.error(`修改失败:${String(e)}`);
59
+ } finally {
60
+ setLoading(false);
61
+ }
62
+ };
63
+
64
+ return (
65
+ <div
66
+ style={{
67
+ minHeight: '100vh',
68
+ display: 'flex',
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ padding: 24,
72
+ backgroundImage:
73
+ 'var(--wb-brand-bg)',
74
+ }}
75
+ >
76
+ <Card
77
+ style={{
78
+ width: '100%',
79
+ maxWidth: 440,
80
+ borderRadius: 14,
81
+ boxShadow: 'var(--wb-brand-shadow)',
82
+ }}
83
+ >
84
+ <div style={{ textAlign: 'center', marginBottom: 20 }}>
85
+ <div
86
+ style={{
87
+ width: 44,
88
+ height: 44,
89
+ borderRadius: 12,
90
+ margin: '0 auto 12px',
91
+ display: 'grid',
92
+ placeItems: 'center',
93
+ background: MILITARY.soft,
94
+ border: `1px solid ${MILITARY.softStrong}`,
95
+ color: MILITARY.primary,
96
+ }}
97
+ >
98
+ <SafetyCertificateOutlined style={{ fontSize: fs(22) }} />
99
+ </div>
100
+ <Title level={3} style={{ marginBottom: 4 }}>
101
+ 请先修改密码
102
+ </Title>
103
+ <Text type="secondary">
104
+ {displayName}({username})
105
+ </Text>
106
+ </div>
107
+
108
+ <Alert
109
+ type="warning"
110
+ showIcon
111
+ style={{ marginBottom: 18 }}
112
+ title="管理员已重置你的密码"
113
+ description="为保障账号安全,首次登录必须设置新密码,完成后请用新密码重新登录。"
114
+ />
115
+
116
+ <Form form={form} layout="vertical" onFinish={onFinish} requiredMark={false}>
117
+ <Form.Item
118
+ name="oldPassword"
119
+ label="当前密码"
120
+ rules={[{ required: true, message: '请输入当前密码' }]}
121
+ >
122
+ <Input.Password
123
+ prefix={<LockOutlined />}
124
+ placeholder="管理员重置后的密码"
125
+ autoComplete="current-password"
126
+ />
127
+ </Form.Item>
128
+ <Form.Item
129
+ name="newPassword"
130
+ label="新密码"
131
+ rules={formRules('password', { label: '新密码', required: true })}
132
+ >
133
+ <Input.Password
134
+ prefix={<LockOutlined />}
135
+ maxLength={64}
136
+ placeholder="6-64 位,不能含空格,不能与原密码相同"
137
+ autoComplete="new-password"
138
+ />
139
+ </Form.Item>
140
+ <Form.Item
141
+ name="confirm"
142
+ label="确认新密码"
143
+ dependencies={['newPassword']}
144
+ rules={[
145
+ { required: true, message: '请再次输入新密码' },
146
+ ({ getFieldValue }) => ({
147
+ validator: (_r, value) =>
148
+ !value || getFieldValue('newPassword') === value
149
+ ? Promise.resolve()
150
+ : Promise.reject(new Error('两次输入的密码不一致')),
151
+ }),
152
+ ]}
153
+ >
154
+ <Input.Password
155
+ prefix={<LockOutlined />}
156
+ maxLength={64}
157
+ placeholder="再次输入"
158
+ autoComplete="new-password"
159
+ />
160
+ </Form.Item>
161
+ <Button type="primary" htmlType="submit" block size="large" loading={loading}>
162
+ 修改密码
163
+ </Button>
164
+ </Form>
165
+
166
+ <div style={{ textAlign: 'center', marginTop: 14 }}>
167
+ <Button type="link" size="small" onClick={() => void signOut({ callbackUrl: '/login' })}>
168
+ 退出登录
169
+ </Button>
170
+ </div>
171
+ </Card>
172
+ </div>
173
+ );
174
+ }
@@ -0,0 +1,19 @@
1
+ import { redirect } from 'next/navigation';
2
+ import { currentUser } from '@/auth';
3
+ import ChangePasswordForm from './ChangePasswordForm';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ /**
8
+ * 首次登录强制改密页。
9
+ *
10
+ * 放在 (admin) 布局之外:那个布局会把 mustChangePassword 的用户重定向到本页,
11
+ * 如果本页也在布局里,就会自己跳自己,形成死循环。
12
+ */
13
+ export default async function ChangePasswordPage() {
14
+ const user = await currentUser();
15
+ if (!user) redirect('/login');
16
+ if (!user.mustChangePassword) redirect('/dashboard');
17
+
18
+ return <ChangePasswordForm username={user.username} displayName={user.name} />;
19
+ }
@@ -0,0 +1,56 @@
1
+ import type { Metadata } from 'next';
2
+ import { notFound } from 'next/navigation';
3
+ import { currentUser } from '@/auth';
4
+ import { getDoc, getDocNeighbors, loadDocs } from '@/lib/docs';
5
+ import { buildSearchEntries, getUserMenuTree } from '@/lib/menu-service';
6
+ import { APP_NAME } from '@/lib/appInfo';
7
+ import DocsHome from '@/components/docs/DocsHome';
8
+ import DocArticle from '@/components/docs/DocArticle';
9
+
10
+ export const dynamic = 'force-dynamic';
11
+
12
+ type PageProps = { params: Promise<{ slug?: string[] }> };
13
+
14
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
15
+ const { slug = [] } = await params;
16
+ if (!slug.length) return { title: `帮助中心 · ${APP_NAME}` };
17
+
18
+ const doc = await getDoc(slug);
19
+ if (!doc) return { title: `文档未找到 · ${APP_NAME}` };
20
+
21
+ return {
22
+ title: `${doc.title} · 帮助中心`,
23
+ description: doc.description || undefined,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * 文档站路由。
29
+ *
30
+ * `/docs` → 文档首页(搜索、分类、功能速查)
31
+ * `/docs/<slug...>` → 单篇文档(正文、右侧目录、上下篇)
32
+ *
33
+ * slug 直接对应 `content/docs` 下的 Markdown 路径。内容不存在时走文档站
34
+ * 自己的 404,而不是把用户甩回后台错误页。
35
+ */
36
+ export default async function DocsPage({ params }: PageProps) {
37
+ const { slug = [] } = await params;
38
+
39
+ if (!slug.length) {
40
+ const user = await currentUser();
41
+ const { nav, stats } = await loadDocs();
42
+ // 功能速查与系统菜单共用同一棵树:新模块登记菜单后自动出现在文档首页,
43
+ // 不需要再维护第二份“功能清单”。
44
+ const menus = user ? await getUserMenuTree(user) : [];
45
+ const features = buildSearchEntries(menus);
46
+
47
+ return <DocsHome appName={APP_NAME} nav={nav} stats={stats} features={features} />;
48
+ }
49
+
50
+ const doc = await getDoc(slug);
51
+ if (!doc) notFound();
52
+
53
+ const { prev, next } = await getDocNeighbors(doc.slug);
54
+
55
+ return <DocArticle doc={doc} prev={prev} next={next} />;
56
+ }
@@ -0,0 +1,34 @@
1
+ import type { Metadata } from 'next';
2
+ import { redirect } from 'next/navigation';
3
+ import { currentUser } from '@/auth';
4
+ import { loadDocs } from '@/lib/docs';
5
+ import { APP_NAME } from '@/lib/appInfo';
6
+ import DocsChrome from '@/components/docs/DocsChrome';
7
+ import '@/components/docs/docs.css';
8
+
9
+ export const dynamic = 'force-dynamic';
10
+
11
+ export const metadata: Metadata = {
12
+ title: `帮助中心 · ${APP_NAME}`,
13
+ description: `${APP_NAME} 的系统使用手册:功能说明、操作步骤与常见问题。`,
14
+ };
15
+
16
+ /**
17
+ * 文档站布局(独立于后台管理外壳)。
18
+ *
19
+ * 仍然要求登录:帮助文档会暴露系统模块与权限设计,放在登录后更符合业务系统习惯;
20
+ * 从后台侧边栏或顶栏进入时天然带着会话,不会出现二次登录。
21
+ */
22
+ export default async function DocsLayout({ children }: { children: React.ReactNode }) {
23
+ const user = await currentUser();
24
+ if (!user) redirect('/login');
25
+ if (user.mustChangePassword) redirect('/change-password');
26
+
27
+ const { nav, stats } = await loadDocs();
28
+
29
+ return (
30
+ <DocsChrome appName={APP_NAME} nav={nav} stats={stats} userName={user.name}>
31
+ {children}
32
+ </DocsChrome>
33
+ );
34
+ }
@@ -0,0 +1,19 @@
1
+ /** 文档页加载骨架:布局已经直出,只有正文区域等待数据 */
2
+ export default function DocsLoading() {
3
+ return (
4
+ <div className="wb-docs-article-layout">
5
+ <div className="wb-docs-article" aria-label="文档加载中">
6
+ <div className="wb-skeleton" style={{ width: '36%', height: 13, marginBottom: 20 }} />
7
+ <div className="wb-skeleton" style={{ width: '62%', height: 32, marginBottom: 14 }} />
8
+ <div className="wb-skeleton" style={{ width: '78%', height: 14, marginBottom: 30 }} />
9
+ {[92, 100, 84, 96, 70].map((width, index) => (
10
+ <div
11
+ key={index}
12
+ className="wb-skeleton"
13
+ style={{ width: `${width}%`, height: 13, marginBottom: index === 4 ? 0 : 11 }}
14
+ />
15
+ ))}
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { FileSearchOutlined } from '@ant-design/icons';
5
+ import { fs } from '@/lib/theme';
6
+
7
+ /** 文档站内的 404:保持阅读场景,不把用户甩回后台错误页 */
8
+ export default function DocsNotFound() {
9
+ return (
10
+ <div className="wb-docs-article-layout">
11
+ <article className="wb-docs-article">
12
+ <div className="wb-docs-empty">
13
+ <FileSearchOutlined style={{ fontSize: fs(30), color: '#8b9181', marginBottom: 10 }} />
14
+ <h2>没有找到这篇文档</h2>
15
+ <p>它可能已被移动或删除。可以返回文档首页,或使用顶栏搜索重新查找。</p>
16
+ <p style={{ marginTop: 16 }}>
17
+ <Link href="/docs" className="wb-docs-group-enter">
18
+ 返回帮助中心
19
+ </Link>
20
+ </p>
21
+ </div>
22
+ </article>
23
+ </div>
24
+ );
25
+ }