@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
@@ -1,8 +1,9 @@
1
1
  <!--
2
- 用户管理高保真可交互原型
2
+ 用户管理高保真可交互原型(技能内置参考示例:examples/list-crud/)
3
3
  对应文档:同目录 prd.md(§3 列表、§4 详情、§5 表单、§6 删除、§9 对照表)
4
- 技术:Tailwind Play CDN + 原生 JS,数据为 mock,无后端。
5
- 需求标注:各区块「PRD §」按钮打开琥珀色标题的标注弹框(deppon-prd-generator 技能约定),z-index 高于业务弹层。
4
+ 技术:Tailwind Play CDN + marked(Markdown)+ 原生 JS;业务数据为 mock
5
+ 需求标注:运行时 fetch 同目录 prd.md,按章节切片渲染进标注弹框(与 PRD 联动);请用本地 HTTP 打开本目录,勿依赖 file://。
6
+ 弹层内长表格/长文:纵向 + 横向可滚动(见样式 #annoDlgBody .prd-md)。
6
7
  -->
7
8
  <!DOCTYPE html>
8
9
  <html lang="zh-CN">
@@ -44,9 +45,60 @@
44
45
  outline: 2px solid rgb(217 119 6);
45
46
  outline-offset: 2px;
46
47
  }
48
+ /* 标注弹框内:Markdown 渲染区,表格过宽时可横向滚动 */
49
+ #annoDlgBody .prd-md {
50
+ max-width: none;
51
+ }
52
+ #annoDlgBody .prd-md table {
53
+ width: 100%;
54
+ border-collapse: collapse;
55
+ font-size: 0.75rem;
56
+ line-height: 1.35;
57
+ }
58
+ #annoDlgBody .prd-md th,
59
+ #annoDlgBody .prd-md td {
60
+ border: 1px solid rgb(226 232 240);
61
+ padding: 0.35rem 0.5rem;
62
+ vertical-align: top;
63
+ }
64
+ #annoDlgBody .prd-md th {
65
+ background: rgb(248 250 252);
66
+ font-weight: 600;
67
+ text-align: left;
68
+ }
69
+ #annoDlgBody .prd-md h2 {
70
+ font-size: 1rem;
71
+ font-weight: 700;
72
+ margin: 0.75rem 0 0.5rem;
73
+ color: rgb(15 23 42);
74
+ }
75
+ #annoDlgBody .prd-md h3 {
76
+ font-size: 0.875rem;
77
+ font-weight: 600;
78
+ margin: 0.65rem 0 0.35rem;
79
+ color: rgb(30 41 59);
80
+ }
81
+ #annoDlgBody .prd-md ul {
82
+ margin: 0.35rem 0 0.35rem 1.1rem;
83
+ list-style: disc;
84
+ }
85
+ #annoDlgBody .prd-md p {
86
+ margin: 0.35rem 0;
87
+ }
88
+ #annoDlgBody .prd-md code {
89
+ font-size: 0.7rem;
90
+ border-radius: 0.25rem;
91
+ background: rgb(241 245 249);
92
+ padding: 0.1rem 0.3rem;
93
+ }
47
94
  </style>
48
95
  </head>
49
96
  <body class="min-h-screen bg-slate-100 text-slate-800 antialiased">
97
+ <div
98
+ id="prdLoadBanner"
99
+ class="hidden border-b border-amber-300 bg-amber-100 px-4 py-2 text-center text-xs text-amber-950"
100
+ role="status"
101
+ ></div>
50
102
  <div
51
103
  id="toast"
52
104
  class="fixed top-4 right-4 z-[60] hidden max-w-sm rounded-lg bg-slate-900 px-4 py-3 text-sm text-white shadow-lg"
@@ -109,7 +161,7 @@
109
161
  <input
110
162
  type="text"
111
163
  id="fUsername"
112
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
164
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
113
165
  placeholder="模糊匹配"
114
166
  autocomplete="off"
115
167
  />
@@ -119,7 +171,7 @@
119
171
  <input
120
172
  type="text"
121
173
  id="fName"
122
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
174
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
123
175
  placeholder="模糊匹配"
124
176
  autocomplete="off"
125
177
  />
@@ -129,7 +181,7 @@
129
181
  <input
130
182
  type="text"
131
183
  id="fPhone"
132
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
184
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
133
185
  placeholder="精准"
134
186
  inputmode="numeric"
135
187
  autocomplete="off"
@@ -139,7 +191,7 @@
139
191
  >用户状态
140
192
  <select
141
193
  id="fStatus"
142
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
194
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
143
195
  >
144
196
  <option value="">全部</option>
145
197
  <option value="启用">启用</option>
@@ -151,7 +203,7 @@
151
203
  <button
152
204
  type="button"
153
205
  id="btnQuery"
154
- class="rounded-lg bg-slate-800 px-4 py-2 text-sm font-medium text-white hover:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
206
+ class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
155
207
  >
156
208
  查询
157
209
  </button>
@@ -194,7 +246,7 @@
194
246
  <button
195
247
  type="button"
196
248
  id="btnAdd"
197
- class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
249
+ class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
198
250
  >
199
251
  新增用户
200
252
  </button>
@@ -230,7 +282,7 @@
230
282
  <button
231
283
  type="button"
232
284
  id="btnPrev"
233
- class="rounded border border-slate-300 px-3 py-1.5 text-xs font-medium hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-indigo-500"
285
+ class="rounded border border-slate-300 px-3 py-1.5 text-xs font-medium hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
234
286
  >
235
287
  上一页
236
288
  </button>
@@ -238,7 +290,7 @@
238
290
  <button
239
291
  type="button"
240
292
  id="btnNext"
241
- class="rounded border border-slate-300 px-3 py-1.5 text-xs font-medium hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-indigo-500"
293
+ class="rounded border border-slate-300 px-3 py-1.5 text-xs font-medium hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
242
294
  >
243
295
  下一页
244
296
  </button>
@@ -314,7 +366,7 @@
314
366
  <input
315
367
  name="username"
316
368
  id="formUsername"
317
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
369
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
318
370
  autocomplete="off"
319
371
  />
320
372
  <p id="errUsername" class="mt-1 hidden text-xs text-red-600"></p>
@@ -326,7 +378,7 @@
326
378
  <input
327
379
  name="name"
328
380
  id="formName"
329
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
381
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
330
382
  autocomplete="off"
331
383
  />
332
384
  <p id="errName" class="mt-1 hidden text-xs text-red-600"></p>
@@ -338,7 +390,7 @@
338
390
  <input
339
391
  name="phone"
340
392
  id="formPhone"
341
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
393
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
342
394
  inputmode="numeric"
343
395
  autocomplete="off"
344
396
  />
@@ -350,7 +402,7 @@
350
402
  name="email"
351
403
  id="formEmail"
352
404
  type="email"
353
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
405
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
354
406
  autocomplete="off"
355
407
  />
356
408
  <p id="errEmail" class="mt-1 hidden text-xs text-red-600"></p>
@@ -361,7 +413,7 @@
361
413
  >
362
414
  <select
363
415
  id="formDept"
364
- class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
416
+ class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
365
417
  >
366
418
  <option value="">请选择</option>
367
419
  <option value="研发中心">研发中心</option>
@@ -380,7 +432,7 @@
380
432
  type="checkbox"
381
433
  name="role"
382
434
  value="管理员"
383
- class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
435
+ class="rounded border-slate-300 text-blue-600 focus:ring-blue-500"
384
436
  />
385
437
  管理员</label
386
438
  >
@@ -389,7 +441,7 @@
389
441
  type="checkbox"
390
442
  name="role"
391
443
  value="开发"
392
- class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
444
+ class="rounded border-slate-300 text-blue-600 focus:ring-blue-500"
393
445
  />
394
446
  开发</label
395
447
  >
@@ -398,7 +450,7 @@
398
450
  type="checkbox"
399
451
  name="role"
400
452
  value="运营"
401
- class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
453
+ class="rounded border-slate-300 text-blue-600 focus:ring-blue-500"
402
454
  />
403
455
  运营</label
404
456
  >
@@ -414,7 +466,7 @@
414
466
  name="status"
415
467
  value="启用"
416
468
  checked
417
- class="text-indigo-600 focus:ring-indigo-500"
469
+ class="text-blue-600 focus:ring-blue-500"
418
470
  />
419
471
  启用</label
420
472
  >
@@ -423,7 +475,7 @@
423
475
  type="radio"
424
476
  name="status"
425
477
  value="禁用"
426
- class="text-indigo-600 focus:ring-indigo-500"
478
+ class="text-blue-600 focus:ring-blue-500"
427
479
  />
428
480
  禁用</label
429
481
  >
@@ -439,7 +491,7 @@
439
491
  </button>
440
492
  <button
441
493
  type="submit"
442
- class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
494
+ class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
443
495
  >
444
496
  保存
445
497
  </button>
@@ -527,20 +579,22 @@
527
579
  aria-labelledby="annoDlgTitle"
528
580
  >
529
581
  <div
530
- class="max-h-[88vh] w-full max-w-lg overflow-hidden rounded-xl border-2 border-amber-400 bg-white shadow-2xl ring-4 ring-amber-300/30"
582
+ class="flex max-h-[88vh] w-full max-w-2xl flex-col overflow-hidden rounded-xl border-2 border-amber-400 bg-white shadow-2xl ring-4 ring-amber-300/30"
531
583
  role="document"
532
584
  >
533
- <div class="flex items-center justify-between border-b border-amber-200 bg-amber-50 px-4 py-2">
534
- <div>
585
+ <div
586
+ class="flex shrink-0 items-center justify-between border-b border-amber-200 bg-amber-50 px-4 py-2"
587
+ >
588
+ <div class="min-w-0 pr-2">
535
589
  <p class="text-[10px] font-bold uppercase tracking-wider text-amber-900">
536
590
  需求标注 · 非业务弹窗
537
591
  </p>
538
- <h3 id="annoDlgTitle" class="mt-0.5 text-sm font-semibold text-slate-900"></h3>
592
+ <h3 id="annoDlgTitle" class="mt-0.5 truncate text-sm font-semibold text-slate-900"></h3>
539
593
  </div>
540
594
  <button
541
595
  type="button"
542
596
  id="btnAnnoClose"
543
- class="rounded-md p-1.5 text-slate-600 hover:bg-amber-200/60 hover:text-slate-900 focus:outline-none focus:ring-2 focus:ring-amber-600"
597
+ class="shrink-0 rounded-md p-1.5 text-slate-600 hover:bg-amber-200/60 hover:text-slate-900 focus:outline-none focus:ring-2 focus:ring-amber-600"
544
598
  aria-label="关闭标注"
545
599
  >
546
600
 
@@ -548,13 +602,15 @@
548
602
  </div>
549
603
  <div
550
604
  id="annoDlgBody"
551
- class="max-h-[calc(88vh-5rem)] overflow-y-auto p-4 text-sm leading-relaxed text-slate-700"
605
+ class="min-h-0 flex-1 overflow-y-auto overflow-x-auto p-4 text-sm leading-relaxed text-slate-700"
606
+ tabindex="-1"
552
607
  ></div>
553
608
  </div>
554
609
  </div>
555
610
 
611
+ <script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
556
612
  <script>
557
- (function () {
613
+ function runPrototype(prdMd, prdLoadError) {
558
614
  const MOCK = [
559
615
  {
560
616
  id: '10001',
@@ -628,6 +684,152 @@
628
684
  return document.getElementById(id);
629
685
  };
630
686
 
687
+ function escapeHtml(s) {
688
+ return String(s)
689
+ .replace(/&/g, '&amp;')
690
+ .replace(/</g, '&lt;')
691
+ .replace(/>/g, '&gt;')
692
+ .replace(/"/g, '&quot;');
693
+ }
694
+
695
+ if (typeof marked !== 'undefined' && marked.use) {
696
+ marked.use({ gfm: true, breaks: true });
697
+ }
698
+
699
+ (function showPrdBanner() {
700
+ var el = $('prdLoadBanner');
701
+ if (!el) return;
702
+ if (prdLoadError || !prdMd) {
703
+ el.textContent =
704
+ '未加载到同目录 prd.md:需求标注仅显示兜底摘要。请在本文件所在目录启动本地静态服务后刷新页面(勿直接 file:// 双击打开)。';
705
+ el.classList.remove('hidden');
706
+ }
707
+ })();
708
+
709
+ function sliceBetween(md, startRe, endRe) {
710
+ if (!md) return '';
711
+ var m = md.match(startRe);
712
+ if (!m || m.index === undefined) return '';
713
+ var i0 = m.index;
714
+ if (!endRe) return md.slice(i0).trim();
715
+ var tail = md.slice(i0 + m[0].length);
716
+ var j = tail.search(endRe);
717
+ if (j === -1) return md.slice(i0).trim();
718
+ return md.slice(i0, i0 + m[0].length + j).trim();
719
+ }
720
+
721
+ function mdToHtml(src) {
722
+ if (!src)
723
+ return '<p class="text-slate-500 text-xs">(在 prd.md 中未匹配到对应章节,请检查标题层级是否与 ANNO_EXTRACT 正则一致。)</p>';
724
+ if (typeof marked !== 'undefined' && typeof marked.parse === 'function') {
725
+ return '<div class="prd-md">' + marked.parse(src) + '</div>';
726
+ }
727
+ return (
728
+ '<pre class="max-h-[60vh] overflow-auto whitespace-pre-wrap rounded border border-slate-200 bg-slate-50 p-3 font-mono text-[11px] leading-snug">' +
729
+ escapeHtml(src) +
730
+ '</pre>'
731
+ );
732
+ }
733
+
734
+ var LEGEND_INTRO =
735
+ '<ul class="list-disc space-y-2 pl-4 text-slate-700"><li>琥珀色顶栏弹框为 <strong>PRD 走查标注</strong>,正文由<strong>同目录 prd.md</strong>按章节切片后经 Markdown 渲染得到;修改 prd.md 后<strong>刷新页面</strong>即可同步。</li><li>表格或章节较长时,可在本弹框内<strong>上下左右滚动</strong>查看。</li><li>按 <kbd class="rounded border border-slate-300 bg-slate-50 px-1 text-xs">Esc</kbd>、点遮罩或 ✕ 关闭;本层 z-index 高于业务弹窗。</li></ul>';
736
+
737
+ var ANNO_EXTRACT = {
738
+ s1: { start: /^## 1\./m, end: /^## 2\./m, title: 'prd.md §1 引言(原文)' },
739
+ s2_toolbar: {
740
+ start: /^## 2\./m,
741
+ end: /^### 3\.2/m,
742
+ title: 'prd.md §2·§3.1 原文(菜单权限至 §3.2 之前)',
743
+ },
744
+ s31: { start: /^### 3\.1/m, end: /^### 3\.2/m, title: 'prd.md §3.1 筛选查询区(原文)' },
745
+ s32: { start: /^### 3\.2/m, end: /^## 4\./m, title: 'prd.md §3.2 数据列表(原文)' },
746
+ s4: { start: /^## 4\./m, end: /^## 5\./m, title: 'prd.md §4 用户详情(原文)' },
747
+ s5: { start: /^## 5\./m, end: /^## 6\./m, title: 'prd.md §5 新增/编辑表单(原文)' },
748
+ s6: { start: /^## 6\./m, end: /^## 7\./m, title: 'prd.md §6 删除与审计(原文)' },
749
+ };
750
+
751
+ var ANNO_FALLBACK = {
752
+ s1: {
753
+ title: 'prd.md §1 引言(兜底)',
754
+ html: '<ul class="list-disc space-y-2 pl-4"><li>定位:后台用户账号统一管理(查、增、改、删)。</li><li>功能清单见 PRD §1.2;本原型用 mock 数据演示主路径。</li><li>请用 HTTP 打开同目录以加载 prd.md 全文联动。</li></ul>',
755
+ },
756
+ s2_toolbar: {
757
+ title: 'prd.md §2·§3.1(兜底)',
758
+ html: '<ul class="list-disc space-y-2 pl-4"><li><strong>新增用户</strong>需「编辑」类权限(PRD §2.2)。</li><li>「新增用户」位于列表标题行右侧。</li></ul>',
759
+ },
760
+ s31: {
761
+ title: 'prd.md §3.1(兜底)',
762
+ html: '<ul class="list-disc space-y-2 pl-4"><li>变更条件后须点击<strong>查询</strong>才刷新列表。</li><li><strong>重置</strong>恢复默认。</li></ul>',
763
+ },
764
+ s32: {
765
+ title: 'prd.md §3.2(兜底)',
766
+ html: '<ul class="list-disc space-y-2 pl-4"><li>列字段、分页以 PRD 表格为准。</li><li>行操作:查看 / 编辑 / 删除。</li></ul>',
767
+ },
768
+ s4: {
769
+ title: 'prd.md §4(兜底)',
770
+ html: '<ul class="list-disc space-y-2 pl-4"><li>只读详情弹层;字段见 PRD §4。</li></ul>',
771
+ },
772
+ s5: {
773
+ title: 'prd.md §5(兜底)',
774
+ html: '<ul class="list-disc space-y-2 pl-4"><li>新增与编辑校验见 PRD §5 表格。</li></ul>',
775
+ },
776
+ s6: {
777
+ title: 'prd.md §6(兜底)',
778
+ html: '<ul class="list-disc space-y-2 pl-4"><li>删除须二次确认;建议审计日志。</li></ul>',
779
+ },
780
+ };
781
+
782
+ function openAnno(key) {
783
+ var warn =
784
+ !prdMd || prdLoadError
785
+ ? '<div class="mb-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900"><strong>prd.md 未联动:</strong>请用本地静态服务打开与本文件同级目录(勿 file://),以下为兜底摘要。</div>'
786
+ : '';
787
+
788
+ if (key === 'legend') {
789
+ $('annoDlgTitle').textContent = '需求标注说明(附录 · prd.md §9)';
790
+ var s9 = prdMd ? sliceBetween(prdMd, /^## 9\./m, /^## 10\./m) : '';
791
+ $('annoDlgBody').innerHTML =
792
+ warn +
793
+ LEGEND_INTRO +
794
+ (s9
795
+ ? '<div class="mt-4 border-t border-amber-100 pt-3"><p class="mb-2 text-xs font-semibold text-slate-700">prd.md · §9 原型与 PRD 对应关系(原文)</p>' +
796
+ mdToHtml(s9) +
797
+ '</div>'
798
+ : '');
799
+ $('modalAnno').classList.remove('hidden');
800
+ $('modalAnno').classList.add('flex');
801
+ $('annoDlgBody').scrollTop = 0;
802
+ $('annoDlgBody').focus({ preventScroll: true });
803
+ return;
804
+ }
805
+
806
+ var spec = ANNO_EXTRACT[key];
807
+ if (!spec) return;
808
+ var slice = prdMd ? sliceBetween(prdMd, spec.start, spec.end) : '';
809
+ if (slice) {
810
+ $('annoDlgTitle').textContent = spec.title;
811
+ $('annoDlgBody').innerHTML = warn + mdToHtml(slice);
812
+ } else if (ANNO_FALLBACK[key]) {
813
+ $('annoDlgTitle').textContent = ANNO_FALLBACK[key].title;
814
+ $('annoDlgBody').innerHTML = warn + ANNO_FALLBACK[key].html;
815
+ } else {
816
+ return;
817
+ }
818
+ $('modalAnno').classList.remove('hidden');
819
+ $('modalAnno').classList.add('flex');
820
+ $('annoDlgBody').scrollTop = 0;
821
+ $('annoDlgBody').focus({ preventScroll: true });
822
+ }
823
+
824
+ function closeAnno() {
825
+ $('modalAnno').classList.add('hidden');
826
+ $('modalAnno').classList.remove('flex');
827
+ }
828
+
829
+ function isAnnoOpen() {
830
+ return $('modalAnno') && !$('modalAnno').classList.contains('hidden');
831
+ }
832
+
631
833
  function getFormStateJson() {
632
834
  var roles = [];
633
835
  document.querySelectorAll('input[name="role"]:checked').forEach(function (c) {
@@ -672,60 +874,6 @@
672
874
  $('modalFormLeave').classList.remove('flex');
673
875
  }
674
876
 
675
- var ANNO = {
676
- legend: {
677
- title: '如何使用本页「需求标注」',
678
- html: '<ul class="list-disc space-y-2 pl-4"><li>带<strong class="text-amber-800"> 琥珀色顶栏 </strong>的弹框为 <strong>PRD 走查标注</strong>,不是生产系统功能。</li><li>各区块旁的 <code class="rounded bg-slate-100 px-1 text-xs">PRD §…</code> 与 <code class="rounded bg-slate-100 px-1 text-xs">prd.md</code> 章节对应,便于评审对照。</li><li>按 <kbd class="rounded border border-slate-300 bg-slate-50 px-1 text-xs">Esc</kbd> 或点遮罩、点 ✕ 可关闭标注弹框。</li><li>本层 <code class="text-xs">z-index: 70</code>,高于业务弹窗,避免被盖住。</li></ul>',
679
- },
680
- s1: {
681
- title: 'prd.md §1 引言',
682
- html: '<ul class="list-disc space-y-2 pl-4"><li>定位:后台用户账号统一管理(查、增、改、删)。</li><li>功能清单见 PRD §1.2;本原型用 mock 数据演示主路径。</li><li>落地时以接口字段、UUMS 权限码为准修订 PRD。</li></ul>',
683
- },
684
- s2_toolbar: {
685
- title: 'prd.md §2 权限 · §3.1 工具区',
686
- html: '<ul class="list-disc space-y-2 pl-4"><li><strong>新增用户</strong>需「编辑」类权限(PRD §2.2)。</li><li>「新增用户」位于<strong>用户列表</strong>标题行右侧,与列表同区。</li><li>真实环境可能还有导出等按钮,见 PRD 可选导出条款。</li></ul>',
687
- },
688
- s31: {
689
- title: 'prd.md §3.1 筛选查询区',
690
- html: '<ul class="list-disc space-y-2 pl-4"><li>变更条件后须点击<strong>查询</strong>才刷新列表(页码回到第 1 页)。</li><li><strong>重置</strong>清空条件并恢复默认列表。</li><li>用户名/姓名为模糊;手机号为精准;状态为枚举精准(见 PRD 字段表)。</li></ul>',
691
- },
692
- s32: {
693
- title: 'prd.md §3.2 数据列表',
694
- html: '<ul class="list-disc space-y-2 pl-4"><li>列字段、排序能力、分页规则以 PRD 表格为准;原型中手机号为脱敏展示。</li><li>行操作:<strong>查看 / 编辑 / 删除</strong>;删除需二次确认(§6)。</li><li>批量删除为可选能力,本原型未演示多选。</li></ul>',
695
- },
696
- s4: {
697
- title: 'prd.md §4 用户详情(只读)',
698
- html: '<ul class="list-disc space-y-2 pl-4"><li>由列表「查看」打开;只读,无保存。</li><li>字段集合与 PRD §4 一致;真实项目可增减审计字段。</li><li>本弹层为<strong>业务弹窗</strong>(灰遮罩),与琥珀色「需求标注」弹框区分。</li></ul>',
699
- },
700
- s5: {
701
- title: 'prd.md §5 新增 / 编辑表单',
702
- html: '<ul class="list-disc space-y-2 pl-4"><li>新增:用户名可编;编辑:用户名<strong>只读</strong>(常见安全策略)。</li><li>校验:必填、手机号正则、角色至少一项等见 PRD §5.1。</li><li>保存成功应关闭弹层并刷新列表;原型用 Toast 模拟。</li></ul>',
703
- },
704
- s6: {
705
- title: 'prd.md §6 删除与审计',
706
- html: '<ul class="list-disc space-y-2 pl-4"><li>文案须含用户姓名,避免误删(PRD 建议话术)。</li><li>确认后调删除接口;原型仅从本地 mock 数组移除。</li><li>生产环境建议后端记操作审计日志。</li></ul>',
707
- },
708
- };
709
-
710
- function openAnno(key) {
711
- var item = ANNO[key];
712
- if (!item) return;
713
- $('annoDlgTitle').textContent = item.title;
714
- $('annoDlgBody').innerHTML = item.html;
715
- $('modalAnno').classList.remove('hidden');
716
- $('modalAnno').classList.add('flex');
717
- }
718
-
719
- function closeAnno() {
720
- $('modalAnno').classList.add('hidden');
721
- $('modalAnno').classList.remove('flex');
722
- }
723
-
724
- function isAnnoOpen() {
725
- return $('modalAnno') && !$('modalAnno').classList.contains('hidden');
726
- }
727
-
728
- // 捕获阶段:业务弹层内白底容器使用了 stopPropagation,冒泡到不了 body,故必须在捕获阶段处理
729
877
  document.addEventListener(
730
878
  'click',
731
879
  function (e) {
@@ -823,10 +971,10 @@
823
971
  '<td class="whitespace-nowrap px-4 py-3 text-right text-xs">' +
824
972
  '<button type="button" data-a="view" data-id="' +
825
973
  u.id +
826
- '" class="mr-2 text-indigo-600 hover:underline focus:outline-none focus:ring-2 focus:ring-indigo-500 rounded px-1">查看</button>' +
974
+ '" class="mr-2 text-blue-600 hover:underline focus:outline-none focus:ring-2 focus:ring-blue-500 rounded px-1">查看</button>' +
827
975
  '<button type="button" data-a="edit" data-id="' +
828
976
  u.id +
829
- '" class="mr-2 text-indigo-600 hover:underline focus:outline-none focus:ring-2 focus:ring-indigo-500 rounded px-1">编辑</button>' +
977
+ '" class="mr-2 text-blue-600 hover:underline focus:outline-none focus:ring-2 focus:ring-blue-500 rounded px-1">编辑</button>' +
830
978
  '<button type="button" data-a="del" data-id="' +
831
979
  u.id +
832
980
  '" class="text-red-600 hover:underline focus:outline-none focus:ring-2 focus:ring-red-500 rounded px-1">删除</button>' +
@@ -835,14 +983,6 @@
835
983
  });
836
984
  }
837
985
 
838
- function escapeHtml(s) {
839
- return String(s)
840
- .replace(/&/g, '&amp;')
841
- .replace(/</g, '&lt;')
842
- .replace(/>/g, '&gt;')
843
- .replace(/"/g, '&quot;');
844
- }
845
-
846
986
  function openDetail(u) {
847
987
  var body = $('detailBody');
848
988
  body.innerHTML =
@@ -1181,7 +1321,19 @@
1181
1321
  });
1182
1322
 
1183
1323
  renderTable();
1184
- })();
1324
+ }
1325
+
1326
+ fetch('prd.md', { cache: 'no-store' })
1327
+ .then(function (r) {
1328
+ if (!r.ok) throw new Error('HTTP ' + r.status);
1329
+ return r.text();
1330
+ })
1331
+ .then(function (text) {
1332
+ runPrototype(text, null);
1333
+ })
1334
+ .catch(function () {
1335
+ runPrototype('', new Error('fetch'));
1336
+ });
1185
1337
  </script>
1186
1338
  </body>
1187
1339
  </html>
@@ -0,0 +1,86 @@
1
+ # 春季拉新活动页 PRD(产品需求文档)
2
+
3
+ > **文档说明**:本文件位于技能目录 `examples/user-frontend/`,对应 **`user-frontend-prd-template.md`**(用户角色 + 功能模块 + 交互流程)。实际产出写入 `src/prototypes/<page-name>/prd.md`。
4
+
5
+ > **交互原型**:[打开 prototype.html](./prototype.html);标注 **`fetch` 同目录 `prd.md`**;须 **HTTP** 打开。
6
+
7
+ ---
8
+
9
+ ## 1. 引言
10
+
11
+ ### 1.1 背景
12
+
13
+ C 端活动页:向新用户展示权益与参与规则,引导完成注册/领券;需兼容移动端首屏与分享落地。
14
+
15
+ ### 1.2 用户角色
16
+
17
+ | 用户角色 | 描述 | 使用场景 |
18
+ | -------- | ---------- | ---------- |
19
+ | 游客 | 未登录访客 | 浏览活动规则 |
20
+ | 新用户 | 首次注册 | 领券、参与活动 |
21
+
22
+ ### 1.3 功能清单
23
+
24
+ | 模块 | 功能点 | 功能描述 |
25
+ | ------ | -------- | ------------------ |
26
+ | 活动页 | 首屏展示 | 主标题、权益、主 CTA |
27
+ | 活动页 | 规则说明 | 折叠/锚点查看细则 |
28
+ | 活动页 | 立即参与 | 跳转注册或唤起登录 |
29
+
30
+ ---
31
+
32
+ ## 2. 春季活动落地页需求说明
33
+
34
+ ### 2.1 首屏与权益说明
35
+
36
+ #### 2.1.1 功能描述
37
+
38
+ 首屏包含主视觉、活动标题、核心权益三点、主按钮「立即参与」;向下滚动可见规则摘要。
39
+
40
+ #### 2.1.2 交互流程
41
+
42
+ 1. **触发条件**:用户进入 H5/PC 落地页。
43
+ 2. **操作步骤**:
44
+ - 步骤1:浏览首屏权益。
45
+ - 步骤2:点击「立即参与」。
46
+ 3. **操作反馈**:
47
+ - 成功:已登录跳转任务页;未登录跳转注册(原型 Toast 模拟)。
48
+ - 失败:网络错误时轻提示。
49
+
50
+ ### 2.2 规则与 FAQ
51
+
52
+ #### 2.2.1 功能描述
53
+
54
+ 折叠面板展示活动时间、参与资格、奖品发放说明。
55
+
56
+ #### 2.2.2 交互流程
57
+
58
+ 1. **触发条件**:点击「活动规则」标题。
59
+ 2. **操作步骤**:展开/收起面板。
60
+ 3. **操作反馈**:`aria-expanded` 同步。
61
+
62
+ ---
63
+
64
+ ## 3. 交互与反馈
65
+
66
+ 主 CTA 使用品牌主色;次要链接下划线;Toast 与 Loading 遵循设计规范;分享缩略图与标题由运营配置(非本页范围)。
67
+
68
+ ---
69
+
70
+ ## 9. 原型与 PRD 对应关系
71
+
72
+ | PRD 章节 | prototype.html 演示点 |
73
+ | -------- | ---------------------- |
74
+ | §1 | 页内「PRD §1」 |
75
+ | §2.1 | 首屏区「PRD §2.1」 |
76
+ | §2.2 | 规则折叠「PRD §2.2」 |
77
+ | §3 | 全局反馈说明入口 |
78
+ | 走查说明 | 左下角「标注说明」 |
79
+
80
+ ---
81
+
82
+ ## 10. 修订记录
83
+
84
+ | 版本 | 日期 | 说明 |
85
+ | ---- | ---------- | ---- |
86
+ | v0.1 | 2026-05-14 | 初版:对齐 user-frontend 模板示例 |