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:
+128
-2
@@ -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>
|
||||
Reference in New Issue
Block a user