// 票 01:生成 pages-index.md(sitemap 树 + 每页文本量 + 重点标注) import fs from 'node:fs'; import path from 'node:path'; const ROOT = 'e:/code/crm-backend-matt'; const IN_JSON = `${ROOT}/.scratch/lanhu-latest/axure-v29.json`; const OUT_DIR = `${ROOT}/.scratch/opportunity-bugfix/lanhu-pages`; const OUT = `${ROOT}/.scratch/lanhu-latest/pages-index.md`; const data = JSON.parse(fs.readFileSync(IN_JSON, 'utf8')); // sitemap walk,保持树形结构输出 const lines = []; const stats = { total: 0, withFile: 0, noText: 0 }; const focus = { a1x: [], a3: [], a4: [], notes: [] }; function mdOf(pageName) { // 与 fetch-all-pages.mjs 相同的 safeName 规则 return pageName.replace(/[\\/:*?"<>|\s]+/g, '_') + '.md'; } const walk = (nodes, depth, folder) => { for (const n of nodes || []) { const hasKids = Array.isArray(n.children) && n.children.length > 0; // 带 url 的 Folder:自身也算页面,再递归 children(Axure sitemap 常见形态) if (n.url) { stats.total++; const file = path.join(OUT_DIR, mdOf(n.pageName)); let texts = -1, exists = fs.existsSync(file) && fs.statSync(file).size > 100; if (exists) { stats.withFile++; const head = fs.readFileSync(file, 'utf8').split('\n').slice(0, 6).join('\n'); const m = head.match(/有文本: (\d+)/); texts = m ? +m[1] : -1; if (texts === 0) stats.noText++; } const mark = !exists ? ' ⚠缺失' : texts === 0 ? ' (空页)' : ''; const isNote = exists && texts >= 0 && (() => { const c = fs.readFileSync(file, 'utf8'); return c.includes('开发说明') || c.includes('变更记录'); })(); lines.push(`${' '.repeat(depth)}- ${n.pageName}${mark}${isNote ? ' 〔含批注〕' : ''} ${texts >= 0 ? `(${texts})` : ''}`); // 重点标注 const url = n.url; if (/^a1x/i.test(n.pageName) || url.toLowerCase().startsWith('a1x')) focus.a1x.push(n.pageName); if (/^a3/i.test(n.pageName) || url.toLowerCase().startsWith('a3')) focus.a3.push(n.pageName); if (/^a4/i.test(n.pageName) || url.toLowerCase().startsWith('a4')) focus.a4.push(n.pageName); if (/暂缓|关闭/.test(n.pageName)) focus.notes.push(n.pageName); } if (hasKids) { if (!n.url) lines.push(`${' '.repeat(depth)}- **${n.pageName}/**`); walk(n.children, n.url ? depth + 1 : depth, [...folder, n.pageName]); } } }; walk(data.sitemap.rootNodes, 0, []); // sitemap 没覆盖的 pages key(兜底页) const inTree = new Set(); const collect = (nodes) => (nodes || []).forEach(n => { if (n.url) inTree.add(n.url); if (n.children) collect(n.children); }); collect(data.sitemap.rootNodes); const orphan = Object.keys(data.pages).filter(u => !inTree.has(u)); const out = [ '# v29 原型页面索引(182 页文本库)', '', `- 清单: .scratch/lanhu-latest/axure-v29.json(web 端 v29, 2026-08-28)`, `- 文本库: .scratch/opportunity-bugfix/lanhu-pages/*.md(id | 类型 | 名称 | 文本,含需求批注区)`, `- 括号数字 = 该页有文本控件数;〔含批注〕= 含开发说明/变更记录批注;⚠缺失 = 文本库无对应文件`, `- 拉取日志: .scratch/opportunity-bugfix/fetch-all.log`, '', `## 统计:sitemap 页 ${stats.total},已落盘 ${stats.withFile},空页 ${stats.noText},孤儿清单页 ${orphan.length}`, '', '## 重点页(票 02/07/08 输入)', '', `### A1X 工作计划(${focus.a1x.length} 页,票 07 可行性)`, ...focus.a1x.map(s => `- ${s}`), '', `### A4 客户管理(${focus.a4.length} 页,票 08 可行性)`, ...focus.a4.map(s => `- ${s}`), '', `### A3 商机全族(${focus.a3.length} 页,票 02 复测)`, ...focus.a3.map(s => `- ${s}`), '', `### 暂缓/关闭相关页(D-14 等裁决线索)`, ...focus.notes.map(s => `- ${s}`), '', '## Sitemap 树', '', ...lines, '', ]; if (orphan.length) out.push('## 清单里有但 sitemap 树没有的页', '', ...orphan.map(u => `- ${u} → ${mdOf(u.replace(/\.html$/, '').replace(/[\\/:*?"<>|\s]+/g, '_'))}`), ''); fs.writeFileSync(OUT, out.join('\n'), 'utf8'); console.log(`index written: ${OUT}`); console.log(JSON.stringify(stats)); console.log('focus counts:', Object.fromEntries(Object.entries(focus).map(([k, v]) => [k, v.length])));