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:
+119
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文档预览</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #1677FF;
|
||||
--primary-light: #E6F7FF;
|
||||
--text-primary: #1a1a2e;
|
||||
--text-secondary: #5a607f;
|
||||
--text-placeholder: #999;
|
||||
--border: #e8ecf1;
|
||||
--bg: #ffffff;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', 'Segoe UI', sans-serif; background: #f5f7fa; color: var(--text-primary); }
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.topbar { position: sticky; top: 0; z-index: 100; background: var(--bg); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; align-items: center; gap: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||
.topbar__back { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--primary); cursor: pointer; text-decoration: none; padding: 4px 10px; border-radius: 6px; transition: background .15s; border: none; background: none; }
|
||||
.topbar__back:hover { background: var(--primary-light); }
|
||||
.topbar__title { font-size: 15px; font-weight: 600; color: var(--text-primary); }
|
||||
.topbar__path { font-size: 12px; color: var(--text-placeholder); margin-left: auto; }
|
||||
|
||||
/* 内容区 */
|
||||
.content { max-width: 900px; margin: 0 auto; padding: 32px 40px 60px; background: var(--bg); min-height: calc(100vh - 50px); }
|
||||
.loading { display: flex; align-items: center; justify-content: center; height: 300px; color: var(--text-placeholder); font-size: 14px; }
|
||||
.error { color: #ff4d4f; }
|
||||
|
||||
/* Markdown 渲染样式 */
|
||||
.content h1 { font-size: 24px; font-weight: 800; margin: 0 0 16px; padding-bottom: 10px; border-bottom: 2px solid var(--border); }
|
||||
.content h2 { font-size: 20px; font-weight: 700; margin: 32px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
||||
.content h3 { font-size: 17px; font-weight: 600; margin: 24px 0 8px; }
|
||||
.content h4 { font-size: 15px; font-weight: 600; margin: 20px 0 6px; }
|
||||
.content p { font-size: 14px; line-height: 1.8; margin: 8px 0; }
|
||||
.content ul, .content ol { padding-left: 20px; margin: 8px 0; }
|
||||
.content table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
|
||||
.content th { background: #fafafa; font-weight: 600; text-align: left; padding: 8px 12px; border: 1px solid var(--border); }
|
||||
.content td { padding: 8px 12px; border: 1px solid var(--border); }
|
||||
.content code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; }
|
||||
.content pre { background: #f6f8fa; border-radius: 8px; padding: 16px; overflow-x: auto; margin: 12px 0; }
|
||||
.content pre code { background: none; padding: 0; font-size: 13px; line-height: 1.6; }
|
||||
.content blockquote { border-left: 4px solid var(--primary); padding: 8px 16px; margin: 12px 0; background: var(--primary-light); border-radius: 0 4px 4px 0; }
|
||||
.content blockquote p { color: var(--text-secondary); margin: 0; }
|
||||
.content strong { color: var(--text-primary); }
|
||||
.content hr { border: none; border-top: 1px solid var(--border); margin: 24px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<button class="topbar__back" onclick="window.close();history.back()">← 返回</button>
|
||||
<span class="topbar__title" id="doc-title">文档预览</span>
|
||||
<span class="topbar__path" id="doc-path"></span>
|
||||
</div>
|
||||
<div class="content" id="content">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const filePath = params.get('file');
|
||||
const title = params.get('title');
|
||||
|
||||
if (title) {
|
||||
document.getElementById('doc-title').textContent = title;
|
||||
document.title = title + ' - 文档预览';
|
||||
}
|
||||
if (filePath) {
|
||||
document.getElementById('doc-path').textContent = filePath;
|
||||
fetch(filePath)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('文件未找到 (' + res.status + ')');
|
||||
return res.text();
|
||||
})
|
||||
.then(md => {
|
||||
/* 检测是否为自带 HTML 结构的文件(如共性需求说明.md) */
|
||||
const isRawHtml = /^\s*<(?:style|div|!doctype|html)/i.test(md);
|
||||
if (isRawHtml) {
|
||||
/* 文件混合了 HTML 结构和 Markdown 内容,需先解析 Markdown 再渲染 */
|
||||
let processed = md;
|
||||
if (typeof marked !== 'undefined') {
|
||||
/* 保留 <style>/<div> 等 HTML 标签,只对标签外的 Markdown 文本做解析 */
|
||||
processed = md.replace(/(^[\s\S]*?<div id="main-content">)([\s\S]*?)(<\/div>\s*$)/i, function(_, before, mdBody, after) {
|
||||
return before + marked.parse(mdBody) + after;
|
||||
});
|
||||
/* 同时解析 toc-container 内的 Markdown 列表 */
|
||||
processed = processed.replace(/(<div id="toc-container">)([\s\S]*?)(<\/div>)/, function(_, before, tocBody, after) {
|
||||
return before + marked.parse(tocBody) + after;
|
||||
});
|
||||
}
|
||||
/* 用 iframe 隔离渲染,避免宿主页样式冲突 */
|
||||
document.querySelector('.topbar').style.display = 'none';
|
||||
const wrap = document.getElementById('content');
|
||||
wrap.style.cssText = 'max-width:none;margin:0;padding:0;min-height:100vh';
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.cssText = 'width:100%;height:100vh;border:none';
|
||||
iframe.srcdoc = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>' + processed + '</body></html>';
|
||||
wrap.innerHTML = '';
|
||||
wrap.appendChild(iframe);
|
||||
} else {
|
||||
const content = document.getElementById('content');
|
||||
if (typeof marked !== 'undefined') {
|
||||
content.innerHTML = marked.parse(md);
|
||||
} else {
|
||||
content.innerHTML = '<pre style="white-space:pre-wrap;font-size:14px;line-height:1.8">' + md.replace(/</g,'<').replace(/>/g,'>') + '</pre>';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById('content').innerHTML = '<div class="loading error">加载失败:' + err.message + '<br><small style="color:var(--text-placeholder);margin-top:8px;display:block">请确认文件路径:' + filePath + '</small></div>';
|
||||
});
|
||||
} else {
|
||||
document.getElementById('content').innerHTML = '<div class="loading">未指定文件路径</div>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user