feat: 新增模块文档规范体系及导航页PRD文档入口

- 新增 doc/CHANGELOG.md 和 doc/PRD.md 模块文档规范,更新项目背景和CLAUDE.md约束
- 导航页新增"产品需求文档"Tab,展示各模块PRD编写状态
- 模块卡片左下角新增PRD文档快捷链接
- 新增 md-viewer.html 用于Markdown文件预览,支持HTML混合内容iframe隔离渲染
- 健康咨询模块完成首份PRD文档和变更记录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
冯普
2026-04-09 17:41:45 +08:00
co-authored by Claude Opus 4.6
parent fd39307aaf
commit 82f811c1fc
7 changed files with 947 additions and 2 deletions
+128 -2
View File
@@ -105,6 +105,37 @@ body { background: var(--background); font-family: -apple-system, BlinkMacSystem
.page-popover__item svg { flex-shrink: 0; }
.page-popover__item--dialog { color: #ad4e00; }
.page-popover__item--dialog:hover { background: #fff7e6; color: #ad4e00; }
/* ===== 卡片底部 PRD 链接 ===== */
.module-card__footer { display: flex; justify-content: flex-start; margin-top: 10px; }
.module-card__prd { font-size: 12px; color: var(--primary); cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: var(--radius-sm); transition: all .15s; }
.module-card__prd:hover { background: var(--primary-light); color: var(--primary-dark); }
.module-card__prd svg { flex-shrink: 0; }
/* ===== PRD Tab 页 ===== */
.prd-page__global { margin-bottom: 28px; }
.prd-global-card { background: linear-gradient(135deg, #f6ffed 0%, #e6f7ff 100%); border: 1.5px solid var(--green-border); border-radius: var(--radius-lg); padding: 20px 24px; cursor: pointer; transition: all .25s; display: flex; align-items: center; gap: 16px; }
.prd-global-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.prd-global-card__icon { width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, var(--primary), #4facfe); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.prd-global-card__info { flex: 1; }
.prd-global-card__title { font-size: 16px; font-weight: 700; color: var(--text-primary); }
.prd-global-card__desc { font-size: 13px; color: var(--text-secondary); margin-top: 4px; }
.prd-section { margin-bottom: 28px; }
.prd-section__title { font-size: 15px; font-weight: 600; color: var(--text-primary); margin-bottom: 14px; padding: 6px 16px; background: var(--card-bg); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; gap: 8px; }
.prd-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.prd-item { background: var(--card-bg); border: 1.5px solid var(--border); border-radius: var(--radius-md); padding: 16px 18px; cursor: pointer; transition: all .2s; display: flex; align-items: center; gap: 12px; }
.prd-item:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); border-color: var(--primary); }
.prd-item--disabled { opacity: 0.45; cursor: default; border-style: dashed; }
.prd-item--disabled:hover { box-shadow: none; transform: none; border-color: var(--border); }
.prd-item__icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 18px; }
.prd-item__icon--ready { background: var(--green-light); color: #389e0d; }
.prd-item__icon--empty { background: #f5f5f5; color: #bbb; }
.prd-item__info { flex: 1; min-width: 0; }
.prd-item__name { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.prd-item__path { font-size: 11px; color: var(--text-placeholder); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prd-item__status { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 600; flex-shrink: 0; }
.prd-item__status--ready { background: var(--green-light); color: #389e0d; border: 1px solid var(--green-border); }
.prd-item__status--empty { background: #fffbe6; color: #d48806; border: 1px solid #ffe58f; }
</style>
</head>
<body>
@@ -127,6 +158,8 @@ const ICONS = {
page: '<svg width="14" height="14" viewBox="0 0 12 12" fill="none"><path d="M3 1h4l3 3v7H3V1z" stroke="currentColor" stroke-width="1.1"/><path d="M7 1v3h3" stroke="currentColor" stroke-width="1.1"/></svg>',
dialog: '<svg width="14" height="14" viewBox="0 0 12 12" fill="none"><rect x="1.5" y="2" width="9" height="8" rx="1.5" stroke="currentColor" stroke-width="1.1"/><line x1="4" y1="5" x2="8" y2="5" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><line x1="4" y1="7.5" x2="6.5" y2="7.5" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/></svg>',
empty: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.2" stroke-dasharray="3 2"/><path d="M8 5.5v3M8 10v.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>',
prd: '<svg class="tab-nav__icon" viewBox="0 0 18 18" fill="none"><path d="M4 1h7l4 4v12H4V1z" stroke="currentColor" stroke-width="1.5"/><path d="M11 1v4h4" stroke="currentColor" stroke-width="1.5"/><line x1="7" y1="9" x2="13" y2="9" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><line x1="7" y1="12" x2="11" y2="12" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>',
prdSmall: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 1h5l2.5 2.5v7.5h-7.5V1z" stroke="currentColor" stroke-width="1"/><path d="M7.5 1v2.5h2.5" stroke="currentColor" stroke-width="1"/></svg>',
role: { expert:'👨‍⚕', assistant:'🤖', operator:'📋', worker:'👷', onsite:'🏥', admin:'⚙', shared:'🔗' }
};
@@ -161,7 +194,7 @@ const SITE_MAP = [
{ name:'心脑血管病预防', dir:'employee-app/cardiovascular', pages:[] },
{ name:'糖尿病预防', dir:'employee-app/diabetes', pages:[] },
{ name:'癌症预防', dir:'employee-app/cancer', pages:[] },
{ name:'健康咨询', dir:'employee-app/health-consult', pages:[
{ name:'健康咨询', dir:'employee-app/health-consult', prd:'employee-app/health-consult/doc/PRD.md', pages:[
{ file:'index.html', label:'咨询首页' }, { file:'expert-list.html', label:'专家列表' },
{ file:'expert-profile.html', label:'专家主页' }, { file:'find-expert.html', label:'找专家' },
{ file:'text-chat.html', label:'图文咨询' }, { file:'video-booking.html', label:'视频预约' },
@@ -251,9 +284,13 @@ function countPages(t) {
/* Tab 导航 */
const tabNav = document.getElementById('tab-nav');
const tabContents = document.getElementById('tab-contents');
const totalTabs = SITE_MAP.length + 1; /* +1 for PRD tab */
SITE_MAP.forEach((t, i) => {
tabNav.innerHTML += `<div class="tab-nav__item${i===0?' tab-nav__item--active':''}" data-idx="${i}">${ICONS[t.icon]}${t.label}<span class="tab-nav__badge">${countPages(t)}</span></div>`;
});
/* PRD Tab */
const prdTabIdx = SITE_MAP.length;
tabNav.innerHTML += `<div class="tab-nav__item" data-idx="${prdTabIdx}">${ICONS.prd}产品需求文档</div>`;
/* 生成卡片 — 点击卡片跳转主页,点击徽章弹出页面列表 */
let openPopover = null;
@@ -277,7 +314,12 @@ function cardHTML(mod, uid) {
}
const body = has ? '' : `<div class="module-card__empty-hint">${ICONS.empty}暂无原型页面</div>`;
return `<div class="${cls}" ${onclick}><div class="module-card__header"><span class="module-card__name">${mod.name}</span>${tagHtml}</div>${body}</div>`;
/* PRD 文档链接(右下角) */
let prdHtml = '';
if (mod.prd) {
prdHtml = `<div class="module-card__footer"><a class="module-card__prd" onclick="event.stopPropagation();openMdPreview('${mod.prd}','${mod.name} PRD')">${ICONS.prdSmall}PRD文档</a></div>`;
}
return `<div class="${cls}" ${onclick}><div class="module-card__header"><span class="module-card__name">${mod.name}</span>${tagHtml}</div>${body}${prdHtml}</div>`;
}
function togglePop(uid) {
@@ -338,6 +380,90 @@ tabNav.addEventListener('click', e => {
item.classList.add('tab-nav__item--active');
tabContents.querySelector(`[data-idx="${idx}"]`).classList.add('tab-content--active');
});
/* ===== PRD Tab 内容生成 ===== */
(function buildPrdTab() {
const div = document.createElement('div');
div.className = 'tab-content';
div.dataset.idx = prdTabIdx;
let html = '';
/* 顶部:共性需求说明 */
html += `<div class="prd-page__global">
<div class="prd-global-card" onclick="openMdPreview('共性需求说明.md','共性需求说明')">
<div class="prd-global-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M6 2h9l5 5v15H6V2z" stroke="#fff" stroke-width="1.8"/><path d="M15 2v5h5" stroke="#fff" stroke-width="1.8"/><line x1="9" y1="11" x2="17" y2="11" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/><line x1="9" y1="14.5" x2="15" y2="14.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/><line x1="9" y1="18" x2="13" y2="18" stroke="#fff" stroke-width="1.4" stroke-linecap="round"/></svg>
</div>
<div class="prd-global-card__info">
<div class="prd-global-card__title">共性需求说明</div>
<div class="prd-global-card__desc">全平台通用的业务规则、交互规范和共性功能说明</div>
</div>
</div>
</div>`;
/* 收集所有模块的 PRD 信息 */
function collectModules(siteEntry) {
const result = [];
if (siteEntry.modules) {
siteEntry.modules.forEach(m => {
result.push({ name: m.name, dir: m.dir, prd: m.prd || null });
});
}
if (siteEntry.roles) {
siteEntry.roles.forEach(r => {
r.modules.forEach(m => {
result.push({ name: r.role + ' - ' + m.name, dir: m.dir, prd: m.prd || null });
});
});
}
if (siteEntry.shared) {
siteEntry.shared.forEach(m => {
result.push({ name: '共享 - ' + m.name, dir: m.dir, prd: m.prd || null });
});
}
return result;
}
SITE_MAP.forEach(t => {
const mods = collectModules(t);
const readyCount = mods.filter(m => m.prd).length;
html += `<div class="prd-section">
<div class="prd-section__title">${ICONS[t.icon] || ''}${t.label}<span class="tab-nav__badge" style="margin-left:6px">${readyCount}/${mods.length}</span></div>
<div class="prd-grid">`;
mods.forEach(m => {
if (m.prd) {
html += `<div class="prd-item" onclick="openMdPreview('${m.prd}','${m.name} PRD')">
<div class="prd-item__icon prd-item__icon--ready">📄</div>
<div class="prd-item__info">
<div class="prd-item__name">${m.name} PRD</div>
<div class="prd-item__path">${m.prd}</div>
</div>
<span class="prd-item__status prd-item__status--ready">已就绪</span>
</div>`;
} else {
html += `<div class="prd-item prd-item--disabled">
<div class="prd-item__icon prd-item__icon--empty">📄</div>
<div class="prd-item__info">
<div class="prd-item__name">${m.name} PRD</div>
<div class="prd-item__path">${m.dir}/doc/PRD.md</div>
</div>
<span class="prd-item__status prd-item__status--empty">待编写</span>
</div>`;
}
});
html += '</div></div>';
});
div.innerHTML = html;
tabContents.appendChild(div);
})();
/* ===== Markdown 预览(新标签页打开) ===== */
function openMdPreview(filePath, title) {
window.open('md-viewer.html?file=' + encodeURIComponent(filePath) + '&title=' + encodeURIComponent(title || ''), '_blank');
}
</script>
</body>
</html>