@deppon/deppon-skills 2.4.18 → 2.4.21

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 (30) hide show
  1. package/dist/deppon-npm-skills/SKILL.md +3 -3
  2. package/dist/deppon-prd-generator/SKILL.md +81 -18
  3. package/dist/deppon-prd-generator/examples/README.md +17 -0
  4. package/dist/deppon-prd-generator/examples/app-shell-navigation/layout-spec.md +54 -0
  5. package/dist/deppon-prd-generator/examples/app-shell-navigation/pages/demo-form.html +57 -0
  6. package/dist/deppon-prd-generator/examples/app-shell-navigation/pages/demo-home.html +92 -0
  7. package/dist/deppon-prd-generator/examples/app-shell-navigation/pages/demo-list.html +47 -0
  8. package/dist/deppon-prd-generator/examples/app-shell-navigation/prd.md +164 -0
  9. package/dist/deppon-prd-generator/examples/app-shell-navigation/prototype.html +794 -0
  10. package/dist/deppon-prd-generator/examples/backend-list/prd.md +91 -0
  11. package/dist/deppon-prd-generator/examples/backend-list/prototype.html +369 -0
  12. package/dist/deppon-prd-generator/examples/data-dashboard/prd.md +127 -0
  13. package/dist/deppon-prd-generator/examples/data-dashboard/prototype.html +281 -0
  14. package/dist/deppon-prd-generator/examples/form-edit/prd.md +108 -0
  15. package/dist/deppon-prd-generator/examples/form-edit/prototype.html +280 -0
  16. package/dist/deppon-prd-generator/examples/form-preview/prd.md +106 -0
  17. package/dist/deppon-prd-generator/examples/form-preview/prototype.html +240 -0
  18. package/dist/deppon-prd-generator/{user-management → examples/list-crud}/prd.md +9 -4
  19. package/dist/deppon-prd-generator/{user-management → examples/list-crud}/prototype.html +246 -94
  20. package/dist/deppon-prd-generator/examples/user-frontend/prd.md +86 -0
  21. package/dist/deppon-prd-generator/examples/user-frontend/prototype.html +223 -0
  22. package/dist/deppon-prd-generator/quick-reference.md +23 -6
  23. package/dist/deppon-prd-generator/template/app-shell-navigation-prd-template.md +180 -0
  24. package/dist/deppon-prd-generator/template/backend-form-edit-prd-template.md +4 -0
  25. package/dist/deppon-prd-generator/template/backend-form-preview-prd-template.md +4 -0
  26. package/dist/deppon-prd-generator/template/backend-list-prd-template.md +4 -0
  27. package/dist/deppon-prd-generator/template/data-prd-template.md +4 -0
  28. package/dist/deppon-prd-generator/template/user-frontend-prd-template.md +4 -0
  29. package/dist/deppon-prd-generator/workflow.md +34 -16
  30. package/package.json +1 -1
@@ -0,0 +1,794 @@
1
+ <!--
2
+ 管理台壳层 + 多页 iframe 导航(技能示例 examples/app-shell-navigation/)
3
+ 对应:同目录 prd.md。子页:pages/demo-*.html
4
+ 打开:本地 HTTP 访问本目录;fetch prd.md 用于标注弹框。
5
+ -->
6
+ <!DOCTYPE html>
7
+ <html lang="zh-CN">
8
+ <head>
9
+ <meta charset="UTF-8" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
+ <title>PRD 文档中心 · 壳层导航演示</title>
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+ <script>
14
+ tailwind.config = {
15
+ theme: {
16
+ extend: {
17
+ fontFamily: {
18
+ sans: ['"DM Sans"', 'system-ui', 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'sans-serif'],
19
+ },
20
+ boxShadow: {
21
+ shell: '0 1px 3px rgb(15 23 42 / 0.06), 0 12px 40px -8px rgb(15 23 42 / 0.12)',
22
+ sidebar: '4px 0 24px -4px rgb(15 23 42 / 0.08)',
23
+ },
24
+ },
25
+ },
26
+ };
27
+ </script>
28
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
29
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
30
+ <link
31
+ href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&display=swap"
32
+ rel="stylesheet"
33
+ />
34
+ <style>
35
+ .anno-trigger {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ border-radius: 0.375rem;
40
+ border: 1px solid rgba(217, 119, 6, 0.85);
41
+ background: rgba(254, 243, 199, 0.95);
42
+ padding: 0.1rem 0.45rem;
43
+ font-size: 0.625rem;
44
+ font-weight: 700;
45
+ color: rgb(69 26 3);
46
+ cursor: pointer;
47
+ }
48
+ .anno-trigger:hover {
49
+ background: rgb(253 230 138);
50
+ }
51
+ .anno-trigger:focus-visible {
52
+ outline: 2px solid rgb(217 119 6);
53
+ outline-offset: 2px;
54
+ }
55
+ #annoDlgBody .prd-md table {
56
+ width: 100%;
57
+ border-collapse: collapse;
58
+ font-size: 0.75rem;
59
+ }
60
+ #annoDlgBody .prd-md th,
61
+ #annoDlgBody .prd-md td {
62
+ border: 1px solid rgb(226 232 240);
63
+ padding: 0.35rem 0.5rem;
64
+ }
65
+ #annoDlgBody .prd-md h2 {
66
+ font-size: 1rem;
67
+ font-weight: 700;
68
+ margin: 0.5rem 0;
69
+ }
70
+ #annoDlgBody .prd-md h3 {
71
+ font-size: 0.875rem;
72
+ font-weight: 600;
73
+ margin: 0.4rem 0;
74
+ }
75
+ body.sidebar-collapsed #sidebar {
76
+ width: 56px;
77
+ }
78
+ body.sidebar-collapsed .nav-label,
79
+ body.sidebar-collapsed .chevron {
80
+ display: none !important;
81
+ }
82
+ /* 收起时只藏二级展开区,保留「产品管理」图标行;子页用 .nav-shortcuts-collapsed 图标入口 */
83
+ body.sidebar-collapsed #subProd {
84
+ display: none !important;
85
+ }
86
+ .nav-shortcuts-collapsed {
87
+ display: none;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ gap: 0.25rem;
91
+ padding-top: 0.25rem;
92
+ }
93
+ body.sidebar-collapsed .nav-shortcuts-collapsed {
94
+ display: flex;
95
+ }
96
+ body.sidebar-collapsed .nav-l1,
97
+ body.sidebar-collapsed #btnToggleProd {
98
+ justify-content: center;
99
+ padding-left: 0;
100
+ padding-right: 0;
101
+ }
102
+ body.sidebar-collapsed .brand-text {
103
+ display: none !important;
104
+ }
105
+ body.sidebar-collapsed .sidebar-brand-head {
106
+ justify-content: center;
107
+ }
108
+ body.sidebar-collapsed .brand-mark {
109
+ margin: 0 auto;
110
+ }
111
+ .nav-item {
112
+ transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
113
+ }
114
+ .nav-item[aria-current='page'] {
115
+ background: linear-gradient(135deg, rgb(238 242 255) 0%, rgb(224 231 255) 100%);
116
+ color: rgb(67 56 202);
117
+ box-shadow: inset 3px 0 0 0 rgb(99 102 241);
118
+ }
119
+ .nav-item:not([aria-current='page']):hover {
120
+ background-color: rgb(248 250 252);
121
+ color: rgb(15 23 42);
122
+ }
123
+ #btnToggleProd[aria-expanded='true'] {
124
+ background: rgb(248 250 252);
125
+ }
126
+ </style>
127
+ </head>
128
+ <body
129
+ class="h-screen overflow-hidden bg-gradient-to-br from-slate-100 via-slate-50 to-indigo-50/50 text-slate-800 antialiased selection:bg-indigo-100 selection:text-indigo-900"
130
+ >
131
+ <div
132
+ id="prdLoadBanner"
133
+ class="hidden border-b border-amber-200/90 bg-gradient-to-r from-amber-50 to-amber-100/80 px-4 py-2.5 text-center text-xs font-medium text-amber-950"
134
+ role="status"
135
+ ></div>
136
+
137
+ <div
138
+ id="prdAnnoDock"
139
+ class="fixed bottom-6 left-[200px] z-[65] flex items-center gap-1.5 sidebar-collapsed:left-[72px]"
140
+ role="toolbar"
141
+ aria-label="PRD 标注说明入口"
142
+ >
143
+ <button
144
+ type="button"
145
+ id="btnAnnoLegend"
146
+ class="flex items-center gap-2 rounded-full border border-amber-300/90 bg-white/95 px-3.5 py-2 text-xs font-semibold text-amber-950 shadow-shell backdrop-blur-sm transition-all hover:border-amber-400 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-amber-400 focus:ring-offset-2"
147
+ title="需求标注说明"
148
+ >
149
+ <span
150
+ class="flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-br from-amber-400 to-amber-600 text-xs font-bold text-white shadow-sm"
151
+ >?</span
152
+ >
153
+ 标注说明
154
+ </button>
155
+ </div>
156
+
157
+ <div class="flex h-[calc(100vh-0px)] min-h-0 gap-4 p-4 pb-6">
158
+ <aside
159
+ id="sidebar"
160
+ class="relative flex w-[180px] shrink-0 flex-col overflow-hidden rounded-2xl border border-white/60 bg-white/90 shadow-shell backdrop-blur-md transition-[width] duration-300 ease-out"
161
+ aria-label="主导航"
162
+ >
163
+ <div
164
+ class="sidebar-brand-head flex h-[52px] shrink-0 items-center gap-2 border-b border-slate-100/90 bg-gradient-to-r from-white to-slate-50/80 px-3"
165
+ >
166
+ <div
167
+ class="brand-mark flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-xs font-bold tracking-tight text-white shadow-md shadow-indigo-500/25"
168
+ aria-hidden="true"
169
+ >
170
+ PR
171
+ </div>
172
+ <div class="brand-text min-w-0 flex-1">
173
+ <div class="text-sm font-bold leading-tight tracking-tight text-slate-900">PRD 文档中心</div>
174
+ <div class="text-[10px] font-medium leading-tight tracking-wider text-slate-400">交互原型</div>
175
+ </div>
176
+ </div>
177
+
178
+ <nav class="min-h-0 flex-1 space-y-0.5 overflow-y-auto px-2 py-3 text-[13px] font-medium">
179
+ <a
180
+ href="pages/demo-home.html"
181
+ target="prdMainFrame"
182
+ data-tab="home"
183
+ class="nav-item nav-l1 flex h-[52px] items-center gap-2.5 rounded-xl px-3 text-slate-600"
184
+ aria-current="page"
185
+ >
186
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-500" aria-hidden="true">
187
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
188
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" stroke-linecap="round" stroke-linejoin="round" />
189
+ <path d="M9 22V12h6v10" stroke-linecap="round" stroke-linejoin="round" />
190
+ </svg>
191
+ </span>
192
+ <span class="nav-label">工作台</span>
193
+ </a>
194
+
195
+ <div class="submenu-panel pt-1">
196
+ <button
197
+ type="button"
198
+ class="nav-l1 flex h-[52px] w-full items-center gap-2.5 rounded-xl px-3 text-left text-slate-600 transition-colors hover:bg-slate-50/90"
199
+ id="btnToggleProd"
200
+ aria-expanded="true"
201
+ >
202
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-500" aria-hidden="true">
203
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
204
+ <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" stroke-linecap="round" stroke-linejoin="round" />
205
+ <path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12" stroke-linecap="round" stroke-linejoin="round" />
206
+ </svg>
207
+ </span>
208
+ <span class="nav-label flex-1 truncate">产品管理</span>
209
+ <span class="chevron text-slate-400 transition-transform duration-200" aria-hidden="true">
210
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
211
+ <path d="M6 9l6 6 6-6" stroke-linecap="round" stroke-linejoin="round" />
212
+ </svg>
213
+ </span>
214
+ </button>
215
+ <div
216
+ id="subProd"
217
+ class="mt-1 space-y-0.5 overflow-hidden rounded-xl border border-indigo-100/80 bg-gradient-to-b from-indigo-50/40 to-slate-50/30 py-1"
218
+ >
219
+ <button
220
+ type="button"
221
+ id="btnDiscountPrice"
222
+ class="nav-label flex h-10 w-full items-center justify-between rounded-lg px-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-600 transition-colors hover:bg-white/70 hover:text-slate-900"
223
+ aria-expanded="true"
224
+ aria-controls="discountSubmenu"
225
+ >
226
+ <span>折扣与价格</span>
227
+ <span
228
+ class="text-slate-400 transition-transform duration-200"
229
+ id="chevronDiscount"
230
+ style="transform: rotate(180deg)"
231
+ aria-hidden="true"
232
+ >
233
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
234
+ <path d="M6 9l6 6 6-6" stroke-linecap="round" stroke-linejoin="round" />
235
+ </svg>
236
+ </span>
237
+ </button>
238
+ <div id="discountSubmenu" class="border-t border-indigo-100/80 px-2 pb-2 pt-1" role="region" aria-label="折扣与价格子菜单">
239
+ <a
240
+ href="pages/demo-list.html"
241
+ target="prdMainFrame"
242
+ data-tab="list"
243
+ class="nav-item flex items-center gap-2 rounded-lg px-3 py-2.5 text-sm text-slate-700 transition-colors hover:bg-white/90 hover:text-indigo-800"
244
+ >
245
+ <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-400"></span>
246
+ 产品折扣列表
247
+ </a>
248
+ <a
249
+ href="pages/demo-form.html"
250
+ target="prdMainFrame"
251
+ data-tab="form"
252
+ class="nav-item flex items-center gap-2 rounded-lg px-3 py-2.5 text-sm text-slate-700 transition-colors hover:bg-white/90 hover:text-indigo-800"
253
+ >
254
+ <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-slate-300"></span>
255
+ 新建折扣(演示)
256
+ </a>
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <div class="nav-shortcuts-collapsed" aria-label="侧栏收起时的子页入口">
262
+ <a
263
+ href="pages/demo-list.html"
264
+ target="prdMainFrame"
265
+ data-tab="list"
266
+ title="产品折扣列表"
267
+ class="nav-item flex h-11 w-11 items-center justify-center rounded-xl text-slate-600 transition-colors hover:bg-slate-50/90"
268
+ >
269
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-500" aria-hidden="true">
270
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
271
+ <path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" stroke-linecap="round" stroke-linejoin="round" />
272
+ </svg>
273
+ </span>
274
+ </a>
275
+ <a
276
+ href="pages/demo-form.html"
277
+ target="prdMainFrame"
278
+ data-tab="form"
279
+ title="新建折扣(演示)"
280
+ class="nav-item flex h-11 w-11 items-center justify-center rounded-xl text-slate-600 transition-colors hover:bg-slate-50/90"
281
+ >
282
+ <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-500" aria-hidden="true">
283
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
284
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke-linecap="round" stroke-linejoin="round" />
285
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke-linecap="round" stroke-linejoin="round" />
286
+ </svg>
287
+ </span>
288
+ </a>
289
+ </div>
290
+
291
+ <div class="nav-label mt-3 rounded-lg border border-dashed border-slate-200/90 bg-slate-50/50 px-3 py-2 text-center text-[11px] font-medium leading-snug text-slate-400">
292
+ 更多一级菜单…
293
+ </div>
294
+ </nav>
295
+
296
+ <button
297
+ type="button"
298
+ id="btnSidebarCollapse"
299
+ class="absolute -right-3 top-1/2 z-10 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full border border-slate-200/90 bg-white text-slate-500 shadow-lg shadow-slate-900/8 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2"
300
+ aria-expanded="true"
301
+ aria-label="收起或展开侧栏"
302
+ title="收起 / 展开"
303
+ >
304
+ <svg id="icoCollapse" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
305
+ <path d="M15 18l-6-6 6-6" stroke-linecap="round" stroke-linejoin="round" />
306
+ </svg>
307
+ </button>
308
+ </aside>
309
+
310
+ <div class="flex min-h-0 min-w-0 flex-1 flex-col gap-4">
311
+ <header
312
+ class="flex h-14 shrink-0 items-center justify-between rounded-2xl border border-white/70 bg-white/85 px-5 shadow-shell backdrop-blur-md"
313
+ >
314
+ <div class="flex min-w-0 items-center gap-4">
315
+ <div class="min-w-0">
316
+ <div class="flex items-center gap-2">
317
+ <h1 class="truncate text-sm font-semibold text-slate-900">演示项目</h1>
318
+ <span
319
+ class="hidden shrink-0 rounded-md bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 sm:inline-block"
320
+ >Live</span
321
+ >
322
+ </div>
323
+ <p class="truncate text-xs text-slate-500">壳层 + 多页 iframe · 走查用</p>
324
+ </div>
325
+ <div class="flex shrink-0 items-center gap-1" role="toolbar" aria-label="PRD 顶栏标注">
326
+ <button type="button" class="anno-trigger shrink-0" data-anno-key="s4" aria-label="PRD 侧栏 §4">§4</button>
327
+ <button type="button" class="anno-trigger shrink-0" data-anno-key="s44" aria-label="PRD 收起导航 §4.4">§4.4</button>
328
+ <button type="button" class="anno-trigger shrink-0" data-anno-key="s5" aria-label="PRD 顶栏">§5</button>
329
+ </div>
330
+ </div>
331
+ <div class="flex items-center gap-2 sm:gap-3">
332
+ <div class="hidden items-center rounded-xl border border-slate-200/80 bg-slate-50/80 px-3 py-1.5 sm:flex">
333
+ <svg class="mr-2 h-4 w-4 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
334
+ <circle cx="11" cy="11" r="8" />
335
+ <path d="M21 21l-4.35-4.35" stroke-linecap="round" />
336
+ </svg>
337
+ <input
338
+ type="search"
339
+ placeholder="搜索菜单或页面…"
340
+ class="w-40 border-0 bg-transparent text-xs text-slate-700 placeholder:text-slate-400 focus:outline-none focus:ring-0 lg:w-52"
341
+ readonly
342
+ />
343
+ </div>
344
+ <button
345
+ type="button"
346
+ class="relative flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200/80 bg-white text-slate-500 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-600"
347
+ title="通知"
348
+ >
349
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
350
+ <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" stroke-linecap="round" stroke-linejoin="round" />
351
+ <path d="M13.73 21a2 2 0 0 1-3.46 0" stroke-linecap="round" stroke-linejoin="round" />
352
+ </svg>
353
+ <span class="absolute right-2 top-2 h-2 w-2 rounded-full bg-rose-500 ring-2 ring-white"></span>
354
+ </button>
355
+ <div
356
+ class="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-xs font-bold text-white shadow-md shadow-indigo-500/25"
357
+ title="用户"
358
+ >
359
+ U
360
+ </div>
361
+ </div>
362
+ </header>
363
+
364
+ <div
365
+ class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl border border-white/70 bg-white/90 shadow-shell backdrop-blur-md"
366
+ >
367
+ <div
368
+ class="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-slate-100/90 bg-gradient-to-r from-slate-50/80 to-white px-4 py-3"
369
+ >
370
+ <div id="tabList" class="flex flex-wrap items-center gap-1.5" role="tablist" aria-label="页签">
371
+ <div
372
+ data-tab-wrap="home"
373
+ class="tab-wrap inline-flex items-stretch overflow-hidden rounded-full border border-transparent transition-all duration-200 hover:border-slate-200/70 hover:bg-slate-100/60"
374
+ >
375
+ <button
376
+ type="button"
377
+ class="tab-btn group shrink-0 rounded-full border-0 bg-transparent px-3 py-2 text-xs font-semibold text-slate-600 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-400"
378
+ data-src="pages/demo-home.html"
379
+ data-tab="home"
380
+ role="tab"
381
+ >
382
+ <span class="flex items-center gap-1.5">
383
+ <svg class="h-3.5 w-3.5 opacity-70" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
384
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" stroke-linecap="round" stroke-linejoin="round" />
385
+ </svg>
386
+ 工作台
387
+ </span>
388
+ </button>
389
+ </div>
390
+ <div
391
+ data-tab-wrap="list"
392
+ class="tab-wrap inline-flex items-stretch overflow-hidden rounded-full border border-transparent transition-all duration-200 hover:border-slate-200/70 hover:bg-slate-100/60"
393
+ >
394
+ <button
395
+ type="button"
396
+ class="tab-btn group shrink-0 rounded-l-full border-0 bg-transparent py-2 pl-3 pr-1 text-xs font-semibold text-slate-600 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-400"
397
+ data-src="pages/demo-list.html"
398
+ data-tab="list"
399
+ role="tab"
400
+ >
401
+ <span class="flex items-center gap-1.5">
402
+ <svg class="h-3.5 w-3.5 opacity-60" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
403
+ <path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" stroke-linecap="round" stroke-linejoin="round" />
404
+ </svg>
405
+ 列表
406
+ </span>
407
+ </button>
408
+ <button
409
+ type="button"
410
+ class="tab-close flex w-7 shrink-0 items-center justify-center rounded-r-full text-slate-400 transition-colors hover:bg-slate-200/70 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-400"
411
+ data-close-tab="list"
412
+ aria-label="关闭列表页签"
413
+ >
414
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
415
+ <path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round" />
416
+ </svg>
417
+ </button>
418
+ </div>
419
+ <div
420
+ data-tab-wrap="form"
421
+ class="tab-wrap inline-flex items-stretch overflow-hidden rounded-full border border-transparent transition-all duration-200 hover:border-slate-200/70 hover:bg-slate-100/60"
422
+ >
423
+ <button
424
+ type="button"
425
+ class="tab-btn group shrink-0 rounded-l-full border-0 bg-transparent py-2 pl-3 pr-1 text-xs font-semibold text-slate-600 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-400"
426
+ data-src="pages/demo-form.html"
427
+ data-tab="form"
428
+ role="tab"
429
+ >
430
+ <span class="flex items-center gap-1.5">
431
+ <svg class="h-3.5 w-3.5 opacity-60" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
432
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke-linecap="round" stroke-linejoin="round" />
433
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke-linecap="round" stroke-linejoin="round" />
434
+ </svg>
435
+ 表单
436
+ </span>
437
+ </button>
438
+ <button
439
+ type="button"
440
+ class="tab-close flex w-7 shrink-0 items-center justify-center rounded-r-full text-slate-400 transition-colors hover:bg-slate-200/70 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-400"
441
+ data-close-tab="form"
442
+ aria-label="关闭表单页签"
443
+ >
444
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
445
+ <path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round" />
446
+ </svg>
447
+ </button>
448
+ </div>
449
+ <button type="button" class="anno-trigger ml-1" data-anno-key="s6" aria-label="PRD 多页">§6</button>
450
+ </div>
451
+ <button type="button" class="anno-trigger" data-anno-key="s31" aria-label="PRD 栅格">§3.1</button>
452
+ </div>
453
+ <div class="relative min-h-0 flex-1 bg-gradient-to-b from-slate-50/50 to-slate-100/30 p-2 sm:p-3">
454
+ <div
455
+ class="relative h-full min-h-[min(420px,50vh)] overflow-hidden rounded-xl border border-slate-200/60 bg-white shadow-inner shadow-slate-900/5"
456
+ >
457
+ <iframe
458
+ name="prdMainFrame"
459
+ id="prdMainFrame"
460
+ title="子页内容"
461
+ src="pages/demo-home.html"
462
+ class="h-full w-full min-h-[380px] border-0"
463
+ ></iframe>
464
+ </div>
465
+ </div>
466
+ </div>
467
+ </div>
468
+ </div>
469
+
470
+ <div
471
+ id="modalAnno"
472
+ class="fixed inset-0 z-[70] hidden items-center justify-center bg-slate-900/50 p-4 backdrop-blur-sm"
473
+ role="dialog"
474
+ aria-modal="true"
475
+ aria-labelledby="annoDlgTitle"
476
+ >
477
+ <div
478
+ class="flex max-h-[88vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl border-2 border-amber-400/90 bg-white shadow-2xl ring-4 ring-amber-200/40"
479
+ >
480
+ <div
481
+ class="flex shrink-0 items-center justify-between border-b border-amber-100 bg-gradient-to-r from-amber-50 to-orange-50/50 px-4 py-3"
482
+ >
483
+ <div class="min-w-0 pr-2">
484
+ <p class="text-[10px] font-bold uppercase tracking-wider text-amber-900/90">需求标注 · 非业务</p>
485
+ <h3 id="annoDlgTitle" class="truncate text-sm font-semibold text-slate-900"></h3>
486
+ </div>
487
+ <button
488
+ type="button"
489
+ id="btnAnnoClose"
490
+ class="rounded-lg p-2 text-slate-500 transition hover:bg-white/80 hover:text-slate-900"
491
+ aria-label="关闭"
492
+ >
493
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
494
+ <path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round" />
495
+ </svg>
496
+ </button>
497
+ </div>
498
+ <div
499
+ id="annoDlgBody"
500
+ class="min-h-0 flex-1 overflow-y-auto overflow-x-auto p-4 text-sm text-slate-700"
501
+ tabindex="-1"
502
+ ></div>
503
+ </div>
504
+ </div>
505
+
506
+ <script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
507
+ <script>
508
+ (function () {
509
+ function $(id) {
510
+ return document.getElementById(id);
511
+ }
512
+
513
+ function sliceBetween(md, startRe, endRe) {
514
+ if (!md) return '';
515
+ var m = md.match(startRe);
516
+ if (!m || m.index === undefined) return '';
517
+ var i0 = m.index;
518
+ if (!endRe) return md.slice(i0).trim();
519
+ var tail = md.slice(i0 + m[0].length);
520
+ var j = tail.search(endRe);
521
+ if (j === -1) return md.slice(i0).trim();
522
+ return md.slice(i0, i0 + m[0].length + j).trim();
523
+ }
524
+
525
+ function escapeHtml(s) {
526
+ return String(s)
527
+ .replace(/&/g, '&amp;')
528
+ .replace(/</g, '&lt;')
529
+ .replace(/>/g, '&gt;')
530
+ .replace(/"/g, '&quot;');
531
+ }
532
+
533
+ function mdToHtml(src) {
534
+ if (!src) return '<p class="text-xs text-slate-500">(未匹配到章节)</p>';
535
+ if (typeof marked !== 'undefined' && marked.parse) {
536
+ if (marked.use) marked.use({ gfm: true, breaks: true });
537
+ return '<div class="prd-md">' + marked.parse(src) + '</div>';
538
+ }
539
+ return '<pre class="text-xs">' + escapeHtml(src) + '</pre>';
540
+ }
541
+
542
+ var prdMd = '';
543
+ var prdLoadError = null;
544
+
545
+ var ANNO_EXTRACT = {
546
+ s31: { start: /^### 3\.1/m, end: /^### 3\.2/m, title: 'prd.md §3.1 尺寸与间距(原文)' },
547
+ s4: { start: /^## 4\./m, end: /^## 5\./m, title: 'prd.md §4 侧栏导航(原文)' },
548
+ s44: { start: /^### 4\.4/m, end: /^## 5\./m, title: 'prd.md §4.4 收起导航(原文)' },
549
+ s5: { start: /^## 5\./m, end: /^## 6\./m, title: 'prd.md §5 顶栏与页签(原文)' },
550
+ s6: { start: /^## 6\./m, end: /^## 7\./m, title: 'prd.md §6 多页跳转与目录(原文)' },
551
+ };
552
+
553
+ var ANNO_FALLBACK = {
554
+ s31: {
555
+ title: '§3.1 兜底',
556
+ html: '<ul class="list-disc pl-4 text-sm"><li>侧栏 180px、顶栏 56px、间距 16px、底 24px。</li></ul>',
557
+ },
558
+ s4: { title: '§4 兜底', html: '<ul class="list-disc pl-4 text-sm"><li>一级 / 二级 / 三级飞层与收起态。</li></ul>' },
559
+ s44: { title: '§4.4 兜底', html: '<ul class="list-disc pl-4 text-sm"><li>侧栏折叠按钮。</li></ul>' },
560
+ s5: { title: '§5 兜底', html: '<ul class="list-disc pl-4 text-sm"><li>顶栏占位 + 页签切换 iframe。</li></ul>' },
561
+ s6: { title: '§6 兜底', html: '<ul class="list-disc pl-4 text-sm"><li>pages/*.html 与 target=prdMainFrame。</li></ul>' },
562
+ };
563
+
564
+ var LEGEND_INTRO =
565
+ '<ul class="list-disc space-y-2 pl-4 text-sm"><li>标注正文来自同目录 <strong>prd.md</strong> 切片。</li><li>请用 HTTP 打开目录;勿 file://。</li></ul>';
566
+
567
+ function openAnno(key) {
568
+ var warn =
569
+ !prdMd || prdLoadError
570
+ ? '<div class="mb-3 rounded-lg border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900">prd.md 未加载,以下为兜底。</div>'
571
+ : '';
572
+ if (key === 'legend') {
573
+ $('annoDlgTitle').textContent = '需求标注 · prd.md §9';
574
+ var s9 = prdMd ? sliceBetween(prdMd, /^## 9\./m, /^## 10\./m) : '';
575
+ $('annoDlgBody').innerHTML =
576
+ warn + LEGEND_INTRO + (s9 ? '<div class="mt-3 border-t border-slate-100 pt-3">' + mdToHtml(s9) + '</div>' : '');
577
+ $('modalAnno').classList.remove('hidden');
578
+ $('modalAnno').classList.add('flex');
579
+ return;
580
+ }
581
+ var spec = ANNO_EXTRACT[key];
582
+ if (!spec) return;
583
+ var slice = prdMd ? sliceBetween(prdMd, spec.start, spec.end) : '';
584
+ $('annoDlgTitle').textContent = spec.title;
585
+ $('annoDlgBody').innerHTML =
586
+ warn + (slice ? mdToHtml(slice) : ANNO_FALLBACK[key] ? ANNO_FALLBACK[key].html : '');
587
+ $('modalAnno').classList.remove('hidden');
588
+ $('modalAnno').classList.add('flex');
589
+ }
590
+
591
+ function closeAnno() {
592
+ $('modalAnno').classList.add('hidden');
593
+ $('modalAnno').classList.remove('flex');
594
+ }
595
+
596
+ document.addEventListener(
597
+ 'click',
598
+ function (e) {
599
+ var t = e.target.closest('[data-anno-key]');
600
+ if (!t) return;
601
+ var k = t.getAttribute('data-anno-key');
602
+ if (!k) return;
603
+ e.preventDefault();
604
+ e.stopPropagation();
605
+ openAnno(k);
606
+ },
607
+ true,
608
+ );
609
+ $('btnAnnoClose').addEventListener('click', closeAnno);
610
+ $('modalAnno').addEventListener('click', function (e) {
611
+ if (e.target === $('modalAnno')) closeAnno();
612
+ });
613
+ $('btnAnnoLegend').addEventListener('click', function () {
614
+ openAnno('legend');
615
+ });
616
+ document.addEventListener('keydown', function (e) {
617
+ if (e.key === 'Escape' && !$('modalAnno').classList.contains('hidden')) closeAnno();
618
+ });
619
+
620
+ var collapsed = false;
621
+ var icoCollapse = $('icoCollapse');
622
+ $('btnSidebarCollapse').addEventListener('click', function () {
623
+ collapsed = !collapsed;
624
+ document.body.classList.toggle('sidebar-collapsed', collapsed);
625
+ $('btnSidebarCollapse').setAttribute('aria-expanded', collapsed ? 'false' : 'true');
626
+ if (icoCollapse) {
627
+ icoCollapse.innerHTML = collapsed
628
+ ? '<path d="M9 18l6-6-6-6" stroke-linecap="round" stroke-linejoin="round" />'
629
+ : '<path d="M15 18l-6-6 6-6" stroke-linecap="round" stroke-linejoin="round" />';
630
+ }
631
+ });
632
+
633
+ $('btnToggleProd').addEventListener('click', function () {
634
+ if (document.body.classList.contains('sidebar-collapsed')) return;
635
+ var el = $('subProd');
636
+ el.classList.toggle('hidden');
637
+ var visible = !el.classList.contains('hidden');
638
+ $('btnToggleProd').setAttribute('aria-expanded', visible ? 'true' : 'false');
639
+ var ch = $('btnToggleProd').querySelector('.chevron');
640
+ if (ch)
641
+ ch.style.transform = visible ? 'rotate(0deg)' : 'rotate(-90deg)';
642
+ });
643
+
644
+ var btnDiscount = $('btnDiscountPrice');
645
+ var discountSub = $('discountSubmenu');
646
+ var chevDiscount = $('chevronDiscount');
647
+ if (btnDiscount && discountSub) {
648
+ btnDiscount.addEventListener('click', function () {
649
+ discountSub.classList.toggle('hidden');
650
+ var open = !discountSub.classList.contains('hidden');
651
+ btnDiscount.setAttribute('aria-expanded', open ? 'true' : 'false');
652
+ if (chevDiscount) chevDiscount.style.transform = open ? 'rotate(180deg)' : 'rotate(0deg)';
653
+ });
654
+ }
655
+
656
+ var frame = $('prdMainFrame');
657
+ var activeTabId = 'home';
658
+
659
+ var WRAP_ON =
660
+ 'tab-wrap inline-flex items-stretch overflow-hidden rounded-full border border-indigo-200/60 bg-white shadow-md shadow-indigo-900/8 ring-1 ring-slate-200/80';
661
+ var WRAP_OFF =
662
+ 'tab-wrap inline-flex items-stretch overflow-hidden rounded-full border border-transparent transition-all duration-200 hover:border-slate-200/70 hover:bg-slate-100/60';
663
+
664
+ var TAB_BTN_ON =
665
+ 'tab-btn group shrink-0 rounded-l-full border-0 bg-transparent py-2 pl-3 pr-1 text-xs font-semibold text-indigo-700 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-400';
666
+ var TAB_BTN_OFF =
667
+ 'tab-btn group shrink-0 rounded-l-full border-0 bg-transparent py-2 pl-3 pr-1 text-xs font-semibold text-slate-600 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-400';
668
+ var TAB_BTN_PIN_ON =
669
+ 'tab-btn group shrink-0 rounded-full border-0 bg-transparent px-3 py-2 text-xs font-semibold text-indigo-700 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-400';
670
+ var TAB_BTN_PIN_OFF =
671
+ 'tab-btn group shrink-0 rounded-full border-0 bg-transparent px-3 py-2 text-xs font-semibold text-slate-600 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-400';
672
+
673
+ function ensureTabVisible(tab) {
674
+ var wrap = document.querySelector('[data-tab-wrap="' + tab + '"]');
675
+ if (wrap) wrap.classList.remove('hidden');
676
+ }
677
+
678
+ function getVisibleTabWraps() {
679
+ return [].slice.call(document.querySelectorAll('#tabList [data-tab-wrap]')).filter(function (w) {
680
+ return !w.classList.contains('hidden');
681
+ });
682
+ }
683
+
684
+ function updateNavActive(tab) {
685
+ document.querySelectorAll('nav a.nav-item[data-tab]').forEach(function (a) {
686
+ if (a.getAttribute('data-tab') === tab) {
687
+ a.setAttribute('aria-current', 'page');
688
+ } else {
689
+ a.removeAttribute('aria-current');
690
+ }
691
+ });
692
+ }
693
+
694
+ function setTabActive(tab) {
695
+ ensureTabVisible(tab);
696
+ activeTabId = tab;
697
+ document.querySelectorAll('#tabList [data-tab-wrap]').forEach(function (wrap) {
698
+ var t = wrap.getAttribute('data-tab-wrap');
699
+ var on = t === tab;
700
+ // 保留已关闭页签的 hidden;整串 className 赋值会抹掉 hidden,导致「页签关不掉」
701
+ var keepHidden = wrap.classList.contains('hidden');
702
+ wrap.className = (on ? WRAP_ON : WRAP_OFF) + (keepHidden ? ' hidden' : '');
703
+ var btn = wrap.querySelector('.tab-btn[data-tab]');
704
+ if (btn) {
705
+ var pinned = !wrap.querySelector('.tab-close');
706
+ btn.className = pinned
707
+ ? on
708
+ ? TAB_BTN_PIN_ON
709
+ : TAB_BTN_PIN_OFF
710
+ : on
711
+ ? TAB_BTN_ON
712
+ : TAB_BTN_OFF;
713
+ btn.setAttribute('aria-selected', on ? 'true' : 'false');
714
+ }
715
+ });
716
+ updateNavActive(tab);
717
+ }
718
+
719
+ function loadFrame(src, tab) {
720
+ frame.src = src;
721
+ if (tab) setTabActive(tab);
722
+ }
723
+
724
+ function closeTab(tab) {
725
+ if (tab === 'home') return;
726
+ var wrap = document.querySelector('[data-tab-wrap="' + tab + '"]');
727
+ if (!wrap || wrap.classList.contains('hidden')) return;
728
+ var visible = getVisibleTabWraps();
729
+ if (visible.length <= 1) return;
730
+ var closingActive = activeTabId === tab;
731
+ var idx = visible.indexOf(wrap);
732
+ if (idx < 0) idx = 0;
733
+ wrap.classList.add('hidden');
734
+ if (closingActive) {
735
+ var after = getVisibleTabWraps();
736
+ if (after.length) {
737
+ // 不要固定选 DOM 第一个(常是工作台);优先留在「同序号」上的页签(多为右侧内收后的邻居)
738
+ var newIdx = Math.min(idx, after.length - 1);
739
+ var next = after[newIdx];
740
+ var nb = next && next.querySelector('.tab-btn[data-tab]');
741
+ if (nb) loadFrame(nb.getAttribute('data-src'), nb.getAttribute('data-tab'));
742
+ }
743
+ }
744
+ }
745
+
746
+ var tabList = $('tabList');
747
+ if (tabList) {
748
+ tabList.addEventListener('click', function (e) {
749
+ var closeBtn = e.target.closest('[data-close-tab]');
750
+ if (closeBtn) {
751
+ e.preventDefault();
752
+ e.stopPropagation();
753
+ e.stopImmediatePropagation();
754
+ closeTab(closeBtn.getAttribute('data-close-tab'));
755
+ return;
756
+ }
757
+ var tabBtn = e.target.closest('.tab-btn[data-tab]');
758
+ if (tabBtn) {
759
+ loadFrame(tabBtn.getAttribute('data-src'), tabBtn.getAttribute('data-tab'));
760
+ }
761
+ });
762
+ }
763
+
764
+ document.querySelectorAll('a[target="prdMainFrame"]').forEach(function (a) {
765
+ a.addEventListener('click', function () {
766
+ var tab = a.getAttribute('data-tab');
767
+ if (tab) {
768
+ ensureTabVisible(tab);
769
+ setTabActive(tab);
770
+ }
771
+ });
772
+ });
773
+
774
+ fetch('prd.md', { cache: 'no-store' })
775
+ .then(function (r) {
776
+ if (!r.ok) throw new Error(String(r.status));
777
+ return r.text();
778
+ })
779
+ .then(function (t) {
780
+ prdMd = t;
781
+ $('prdLoadBanner').classList.add('hidden');
782
+ })
783
+ .catch(function () {
784
+ prdLoadError = true;
785
+ $('prdLoadBanner').textContent =
786
+ '未加载 prd.md:标注使用兜底。请用本地 HTTP 打开本目录。';
787
+ $('prdLoadBanner').classList.remove('hidden');
788
+ });
789
+
790
+ setTabActive('home');
791
+ })();
792
+ </script>
793
+ </body>
794
+ </html>