node-resource-utils 0.0.0-stage → 1.1.8

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,259 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { usePathname } from 'next/navigation';
5
+ import { useEffect, useState, type ReactNode } from 'react';
6
+ import { Drawer } from 'antd';
7
+ import {
8
+ AppstoreOutlined,
9
+ MenuOutlined,
10
+ ReadOutlined,
11
+ } from '@ant-design/icons';
12
+ import {
13
+ docHref,
14
+ type DocNav,
15
+ type DocNavGroup,
16
+ type DocNavPage,
17
+ type DocsStats,
18
+ } from '@/lib/docs-shared';
19
+ import DocsSearchProvider, { DocsSearchLauncher } from './DocsSearch';
20
+
21
+ /**
22
+ * 文档站外壳:顶栏 + 左侧目录 + 内容区。
23
+ *
24
+ * 和后台管理外壳刻意分开:
25
+ * - 文档是「阅读场景」,不需要业务菜单、收藏、徽标
26
+ * - 独立的全宽版式能把正文与右侧目录都放在舒适的位置
27
+ * - 从后台进入文档站后,顶栏保留「返回系统」,形成清晰的来去路径
28
+ *
29
+ * 移动端把左侧目录收进 Drawer;桌面端保持常驻并 sticky,
30
+ * 长文档向下滚动时目录始终可见。
31
+ */
32
+
33
+ function NavPage({
34
+ page,
35
+ active,
36
+ onNavigate,
37
+ }: {
38
+ page: DocNavPage;
39
+ active: boolean;
40
+ onNavigate?: () => void;
41
+ }) {
42
+ return (
43
+ <Link
44
+ href={docHref(page.slug)}
45
+ onClick={onNavigate}
46
+ className={`wb-docs-nav-page ${active ? 'is-active' : ''}`}
47
+ >
48
+ <span className="wb-docs-nav-page-text">{page.title}</span>
49
+ </Link>
50
+ );
51
+ }
52
+
53
+ function groupHasActive(group: DocNavGroup, activeSlug: string): boolean {
54
+ if (group.pages.some((page) => page.slug === activeSlug)) return true;
55
+ return group.groups.some((child) => groupHasActive(child, activeSlug));
56
+ }
57
+
58
+ function NavGroup({
59
+ group,
60
+ activeSlug,
61
+ depth = 0,
62
+ onNavigate,
63
+ }: {
64
+ group: DocNavGroup;
65
+ activeSlug: string;
66
+ depth?: number;
67
+ onNavigate?: () => void;
68
+ }) {
69
+ const hasActive = groupHasActive(group, activeSlug);
70
+ const first = group.pages[0];
71
+
72
+ return (
73
+ <div className={`wb-docs-nav-group ${depth > 0 ? 'is-nested' : ''}`}>
74
+ {first ? (
75
+ <Link
76
+ href={docHref(first.slug)}
77
+ onClick={onNavigate}
78
+ className={`wb-docs-nav-group-title ${hasActive ? 'has-active' : ''}`}
79
+ >
80
+ {group.title}
81
+ </Link>
82
+ ) : (
83
+ <div className={`wb-docs-nav-group-title ${hasActive ? 'has-active' : ''}`}>
84
+ {group.title}
85
+ </div>
86
+ )}
87
+ {group.pages.map((page) => (
88
+ <NavPage
89
+ key={page.slug}
90
+ page={page}
91
+ active={page.slug === activeSlug}
92
+ onNavigate={onNavigate}
93
+ />
94
+ ))}
95
+ {group.groups.map((child) => (
96
+ <NavGroup
97
+ key={child.key}
98
+ group={child}
99
+ activeSlug={activeSlug}
100
+ depth={depth + 1}
101
+ onNavigate={onNavigate}
102
+ />
103
+ ))}
104
+ </div>
105
+ );
106
+ }
107
+
108
+ function DocsNav({
109
+ nav,
110
+ activeSlug,
111
+ stats,
112
+ onNavigate,
113
+ }: {
114
+ nav: DocNav;
115
+ activeSlug: string;
116
+ stats: DocsStats;
117
+ onNavigate?: () => void;
118
+ }) {
119
+ return (
120
+ <nav className="wb-docs-nav" aria-label="文档目录">
121
+ <Link
122
+ href="/docs"
123
+ onClick={onNavigate}
124
+ className={`wb-docs-nav-home ${activeSlug === '' ? 'is-active' : ''}`}
125
+ >
126
+ <ReadOutlined />
127
+ <span>文档首页</span>
128
+ </Link>
129
+
130
+ <div className="wb-docs-nav-scroll">
131
+ {nav.sections.map((section) =>
132
+ section.type === 'page' ? (
133
+ <NavPage
134
+ key={section.slug}
135
+ page={section}
136
+ active={section.slug === activeSlug}
137
+ onNavigate={onNavigate}
138
+ />
139
+ ) : (
140
+ <NavGroup
141
+ key={section.key}
142
+ group={section}
143
+ activeSlug={activeSlug}
144
+ onNavigate={onNavigate}
145
+ />
146
+ )
147
+ )}
148
+ </div>
149
+
150
+ <div className="wb-docs-nav-foot">
151
+ {stats.pageCount} 篇文档
152
+ {stats.updatedAt ? ` · 更新于 ${stats.updatedAt}` : ''}
153
+ </div>
154
+ </nav>
155
+ );
156
+ }
157
+
158
+ export default function DocsChrome({
159
+ appName,
160
+ nav,
161
+ stats,
162
+ userName,
163
+ children,
164
+ }: {
165
+ appName: string;
166
+ nav: DocNav;
167
+ stats: DocsStats;
168
+ userName: string;
169
+ children: ReactNode;
170
+ }) {
171
+ const pathname = usePathname();
172
+ const [drawerOpen, setDrawerOpen] = useState(false);
173
+
174
+ // URL 里的中文/空格会被编码,而导航数据里是原始 slug;两边先统一解码再比较,
175
+ // 否则中文文件名(通用底座允许)会导致当前项不高亮。
176
+ const activeSlug =
177
+ pathname === '/docs'
178
+ ? ''
179
+ : pathname
180
+ .replace(/^\/docs\/?/, '')
181
+ .replace(/\/+$/, '')
182
+ .split('/')
183
+ .map((part) => {
184
+ try {
185
+ return decodeURIComponent(part);
186
+ } catch {
187
+ return part;
188
+ }
189
+ })
190
+ .join('/');
191
+
192
+ // 文档站正文走 window 滚动;切换页面后回到顶部,移动端顺便收起目录
193
+ useEffect(() => {
194
+ setDrawerOpen(false);
195
+ window.scrollTo({ top: 0, behavior: 'auto' });
196
+ }, [pathname]);
197
+
198
+ return (
199
+ <DocsSearchProvider nav={nav}>
200
+ <div className="wb-docs">
201
+ <header className="wb-docs-header">
202
+ <div className="wb-docs-header-inner">
203
+ <button
204
+ type="button"
205
+ className="wb-docs-menu-btn"
206
+ aria-label="打开文档目录"
207
+ onClick={() => setDrawerOpen(true)}
208
+ >
209
+ <MenuOutlined />
210
+ </button>
211
+
212
+ <Link href="/docs" className="wb-docs-brand">
213
+ <span className="wb-docs-brand-logo">{appName.slice(0, 1)}</span>
214
+ <span className="wb-docs-brand-name">{appName}</span>
215
+ <span className="wb-docs-brand-divider" />
216
+ <span className="wb-docs-brand-badge">帮助中心</span>
217
+ </Link>
218
+
219
+ <div className="wb-docs-header-search">
220
+ <DocsSearchLauncher />
221
+ </div>
222
+
223
+ <div className="wb-docs-header-actions">
224
+ <span className="wb-docs-user">{userName}</span>
225
+ <Link href="/dashboard" className="wb-docs-return">
226
+ <AppstoreOutlined />
227
+ <span>返回系统</span>
228
+ </Link>
229
+ </div>
230
+ </div>
231
+ </header>
232
+
233
+ <div className="wb-docs-body">
234
+ <aside className="wb-docs-sidebar">
235
+ <DocsNav nav={nav} activeSlug={activeSlug} stats={stats} />
236
+ </aside>
237
+ <main className="wb-docs-main">{children}</main>
238
+ </div>
239
+
240
+ <Drawer
241
+ open={drawerOpen}
242
+ onClose={() => setDrawerOpen(false)}
243
+ placement="left"
244
+ size={300}
245
+ title="帮助中心"
246
+ className="wb-docs-drawer"
247
+ styles={{ body: { padding: '8px' } }}
248
+ >
249
+ <DocsNav
250
+ nav={nav}
251
+ activeSlug={activeSlug}
252
+ stats={stats}
253
+ onNavigate={() => setDrawerOpen(false)}
254
+ />
255
+ </Drawer>
256
+ </div>
257
+ </DocsSearchProvider>
258
+ );
259
+ }
@@ -0,0 +1,209 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import {
5
+ ArrowRightOutlined,
6
+ BookOutlined,
7
+ CompassOutlined,
8
+ MessageOutlined,
9
+ } from '@ant-design/icons';
10
+ import { iconOf } from '@/components/admin/menu-icons';
11
+ import type { SearchEntry } from '@/lib/menu-service';
12
+ import {
13
+ docHref,
14
+ type DocNav,
15
+ type DocNavGroup,
16
+ type DocNavPage,
17
+ type DocsStats,
18
+ } from '@/lib/docs-shared';
19
+ import { DocsSearchLauncher } from './DocsSearch';
20
+
21
+ const TONES = 5;
22
+
23
+ /** 文档首页:搜索入口 + 快速开始 + 分类卡片 + 系统功能速查 */
24
+ export default function DocsHome({
25
+ appName,
26
+ nav,
27
+ stats,
28
+ features,
29
+ }: {
30
+ appName: string;
31
+ nav: DocNav;
32
+ stats: DocsStats;
33
+ features: SearchEntry[];
34
+ }) {
35
+ const rootPages = nav.sections.filter(
36
+ (section): section is ({ type: 'page' } & DocNavPage) => section.type === 'page'
37
+ );
38
+ const groups = nav.sections.filter(
39
+ (section): section is ({ type: 'group' } & DocNavGroup) => section.type === 'group'
40
+ );
41
+
42
+ // 功能速查由菜单树自动生成:新增业务模块只要登记了菜单,
43
+ // 这里就会自动出现,不需要再改文档站代码。
44
+ const featureGroups = new Map<string, SearchEntry[]>();
45
+ for (const entry of features) {
46
+ // 目录节点会指向第一个子页面,和它自己的子项重复;功能速查只列具体页面
47
+ if (entry.path === '/docs' || entry.type === 'dir') continue;
48
+ const key = entry.trail.split(' / ')[0] || '常用入口';
49
+ const list = featureGroups.get(key) ?? [];
50
+ if (list.length < 6) list.push(entry);
51
+ featureGroups.set(key, list);
52
+ }
53
+
54
+ const empty = stats.pageCount === 0 && groups.length === 0;
55
+
56
+ return (
57
+ <div className="wb-docs-home">
58
+ <section className="wb-docs-hero">
59
+ <div className="wb-docs-hero-kicker">
60
+ <BookOutlined />
61
+ {appName} · 帮助中心
62
+ </div>
63
+ <h1 className="wb-docs-hero-title">系统使用手册</h1>
64
+ <p className="wb-docs-hero-sub">
65
+ 从账号登录到数据导出,把每一项功能、每一个常见问题按主题整理清楚。
66
+ 找不到答案时,可以直接从顶栏搜索,或前往问题反馈。
67
+ </p>
68
+
69
+ <DocsSearchLauncher variant="hero" />
70
+
71
+ <div className="wb-docs-hero-stats">
72
+ <span>
73
+ <strong>{stats.pageCount}</strong> 篇文档
74
+ </span>
75
+ <span className="wb-docs-hero-dot" />
76
+ <span>
77
+ <strong>{stats.groupCount}</strong> 个分类
78
+ </span>
79
+ {stats.updatedAt && (
80
+ <>
81
+ <span className="wb-docs-hero-dot" />
82
+ <span>最近更新 {stats.updatedAt}</span>
83
+ </>
84
+ )}
85
+ </div>
86
+ </section>
87
+
88
+ {empty && (
89
+ <section className="wb-docs-empty">
90
+ <h2>文档正在建设中</h2>
91
+ <p>
92
+ 在项目的 <code>content/docs</code> 目录新增 Markdown 文件,刷新后会自动生成文档页与侧边目录。
93
+ </p>
94
+ </section>
95
+ )}
96
+
97
+ {rootPages.length > 0 && (
98
+ <section className="wb-docs-section">
99
+ <div className="wb-docs-section-head">
100
+ <h2>快速入口</h2>
101
+ <span>第一次使用,从这里开始</span>
102
+ </div>
103
+ <div className="wb-docs-quick-grid">
104
+ {rootPages.map((page) => (
105
+ <Link key={page.slug} href={docHref(page.slug)} className="wb-docs-quick-card">
106
+ <span className="wb-docs-quick-icon">
107
+ <CompassOutlined />
108
+ </span>
109
+ <span className="wb-docs-quick-main">
110
+ <span className="wb-docs-quick-title">{page.title}</span>
111
+ <span className="wb-docs-quick-desc">{page.description || '查看文档'}</span>
112
+ </span>
113
+ <ArrowRightOutlined className="wb-docs-quick-arrow" />
114
+ </Link>
115
+ ))}
116
+ </div>
117
+ </section>
118
+ )}
119
+
120
+ {groups.length > 0 && (
121
+ <section className="wb-docs-section">
122
+ <div className="wb-docs-section-head">
123
+ <h2>按主题浏览</h2>
124
+ <span>{stats.groupCount} 个分类</span>
125
+ </div>
126
+ <div className="wb-docs-group-grid">
127
+ {groups.map((group, index) => (
128
+ <article key={group.key} className={`wb-docs-group-card tone-${index % TONES}`}>
129
+ <div className="wb-docs-group-card-head">
130
+ <span className="wb-docs-group-badge">{group.title.slice(0, 1)}</span>
131
+ <div>
132
+ <h3 className="wb-docs-group-title">{group.title}</h3>
133
+ <span className="wb-docs-group-count">{group.count} 篇文档</span>
134
+ </div>
135
+ </div>
136
+
137
+ {group.description && (
138
+ <p className="wb-docs-group-desc">{group.description}</p>
139
+ )}
140
+
141
+ <div className="wb-docs-group-links">
142
+ {group.pages.slice(0, 4).map((page) => (
143
+ <Link key={page.slug} href={docHref(page.slug)} className="wb-docs-group-link">
144
+ <span>{page.title}</span>
145
+ <ArrowRightOutlined />
146
+ </Link>
147
+ ))}
148
+ {group.groups.map((child) => (
149
+ <Link
150
+ key={child.key}
151
+ href={child.pages[0] ? docHref(child.pages[0].slug) : '/docs'}
152
+ className="wb-docs-group-link"
153
+ >
154
+ <span>{child.title}</span>
155
+ <ArrowRightOutlined />
156
+ </Link>
157
+ ))}
158
+ </div>
159
+
160
+ {group.pages[0] && (
161
+ <Link href={docHref(group.pages[0].slug)} className="wb-docs-group-enter">
162
+ 进入分类 <ArrowRightOutlined />
163
+ </Link>
164
+ )}
165
+ </article>
166
+ ))}
167
+ </div>
168
+ </section>
169
+ )}
170
+
171
+ {featureGroups.size > 0 && (
172
+ <section className="wb-docs-section wb-docs-feature-section">
173
+ <div className="wb-docs-section-head">
174
+ <h2>功能速查</h2>
175
+ <span>与系统菜单同步,新模块自动出现</span>
176
+ </div>
177
+ <div className="wb-docs-feature-grid">
178
+ {[...featureGroups.entries()].map(([title, entries]) => (
179
+ <div key={title} className="wb-docs-feature-card">
180
+ <div className="wb-docs-feature-title">{title}</div>
181
+ <div className="wb-docs-feature-links">
182
+ {entries.map((entry) => (
183
+ <Link key={entry.id} href={entry.path} className="wb-docs-feature-link">
184
+ <span className="wb-docs-feature-icon">{iconOf(entry.icon)}</span>
185
+ <span className="wb-docs-feature-name">{entry.name}</span>
186
+ </Link>
187
+ ))}
188
+ </div>
189
+ </div>
190
+ ))}
191
+ </div>
192
+ </section>
193
+ )}
194
+
195
+ <section className="wb-docs-cta">
196
+ <span className="wb-docs-cta-icon">
197
+ <MessageOutlined />
198
+ </span>
199
+ <div className="wb-docs-cta-main">
200
+ <h2>文档没有覆盖到你的问题?</h2>
201
+ <p>把使用中遇到的异常、建议或数据问题提交给管理员,处理进度可以在反馈页跟踪。</p>
202
+ </div>
203
+ <Link href="/feedback" className="wb-docs-cta-btn">
204
+ 前往问题反馈 <ArrowRightOutlined />
205
+ </Link>
206
+ </section>
207
+ </div>
208
+ );
209
+ }