docs: 更新生成原型技能,明确前端渲染方案
- 将 Step 5 和 Step 6 更新为前端渲染方案 - 使用 markdown-it 在浏览器中渲染 Markdown - 文档点击后直接在页面中展开,不使用模态框 - 明确多路径兼容和渐进加载策略 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -319,105 +319,219 @@ prototype/
|
||||
- 快速链接导航
|
||||
- 项目概览信息
|
||||
|
||||
## Step 5: 生成文档系统
|
||||
## Step 5: 生成文档系统(前端渲染方案)
|
||||
|
||||
生成 `docs-index.html` 文档首页,自动读取和展示 `docs/` 目录下的 Markdown 文件。
|
||||
生成 `docs-index.html` 文档首页,使用前端 JavaScript 渲染 Markdown 文件。
|
||||
|
||||
### 功能特性
|
||||
- 自动扫描 `docs/` 目录
|
||||
- 实时 Markdown 转 HTML
|
||||
- 文档搜索功能
|
||||
- 卡片/内容双视图切换
|
||||
- 自动扫描 `docs/` 或 `planning-artifacts/` 目录
|
||||
- **前端 Markdown 渲染**:使用 markdown-it 库在浏览器中渲染
|
||||
- 文档卡片展示,点击后在页面中展开显示内容
|
||||
- 响应式设计,支持桌面端和移动端
|
||||
|
||||
### 技术实现
|
||||
|
||||
**1. 引入 markdown-it 库**
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.0.0/dist/markdown-it.min.js"></script>
|
||||
```
|
||||
|
||||
**2. 文档卡片结构**
|
||||
```html
|
||||
<!-- 文档卡片 -->
|
||||
<div class="doc-card">
|
||||
<div class="doc-header">
|
||||
<h3>产品需求文档 (PRD)</h3>
|
||||
<span class="doc-badge">核心文档</span>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
<span>1,082 行</span>
|
||||
<span>40 KB</span>
|
||||
</div>
|
||||
<div class="doc-actions">
|
||||
<button onclick="toggleDoc('prd.md')" class="btn-view">
|
||||
<i class="fas fa-eye"></i> 查看文档
|
||||
</button>
|
||||
</div>
|
||||
<!-- 文档内容容器(默认隐藏) -->
|
||||
<div id="doc-prd.md" class="doc-content hidden"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**3. JavaScript 渲染逻辑**
|
||||
```javascript
|
||||
// 初始化 markdown-it
|
||||
const md = window.markdownit({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true
|
||||
});
|
||||
|
||||
// 切换文档显示
|
||||
function toggleDoc(docFile) {
|
||||
const container = document.getElementById('doc-' + docFile);
|
||||
const isHidden = container.classList.contains('hidden');
|
||||
|
||||
if (isHidden) {
|
||||
// 加载并渲染文档
|
||||
loadDoc(docFile, container);
|
||||
} else {
|
||||
// 隐藏文档
|
||||
container.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载文档
|
||||
async function loadDoc(docFile, container) {
|
||||
// 尝试多个路径
|
||||
const paths = ['/planning-artifacts/' + docFile, '/docs/' + docFile, '/' + docFile];
|
||||
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
if (response.ok) {
|
||||
const markdown = await response.text();
|
||||
const html = md.render(markdown);
|
||||
container.innerHTML = html;
|
||||
container.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有路径都失败
|
||||
container.innerHTML = '<p class="error">无法加载文档: ' + docFile + '</p>';
|
||||
container.classList.remove('hidden');
|
||||
}
|
||||
```
|
||||
|
||||
**4. Markdown 内容样式**
|
||||
```css
|
||||
.markdown-body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.markdown-body h1, .markdown-body h2 {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h1 { font-size: 2em; }
|
||||
.markdown-body h2 { font-size: 1.5em; }
|
||||
.markdown-body h3 { font-size: 1.25em; }
|
||||
|
||||
.markdown-body pre {
|
||||
background: #1f2937;
|
||||
color: #e5e7eb;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
background: #f3f4f6;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markdown-body table th, .markdown-body table td {
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown-body table tr:nth-child(even) {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding-left: 1rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
### 文档链接格式
|
||||
|
||||
在 `index.html` 和其他页面中,文档链接使用以下格式:
|
||||
|
||||
```html
|
||||
<!-- 点击展开文档 -->
|
||||
<button onclick="toggleDoc('prd.md')">
|
||||
<i class="fas fa-eye"></i> 查看 PRD
|
||||
</button>
|
||||
|
||||
<!-- 或者使用链接 -->
|
||||
<a href="#" onclick="toggleDoc('prd.md'); return false;">
|
||||
<i class="fas fa-file-alt"></i> PRD 文档
|
||||
</a>
|
||||
```
|
||||
|
||||
### ⚠️ 关键设计原则
|
||||
|
||||
1. **前端渲染优先**:使用 markdown-it 在浏览器中渲染,不依赖后端 API
|
||||
2. **直接展示**:点击文档链接后,直接在页面中展开显示内容,不使用弹窗/模态框
|
||||
3. **渐进加载**:文档内容在需要时才加载,提升页面加载速度
|
||||
4. **多路径兼容**:尝试多个可能的文档路径(planning-artifacts、docs、根目录)
|
||||
|
||||
## Step 6: 生成本地预览服务器
|
||||
|
||||
生成 `server.js` 文件,提供静态文件服务和文档渲染 API。
|
||||
生成 `server.js` 文件,提供静态文件服务。
|
||||
|
||||
### 服务器功能
|
||||
- 静态文件服务
|
||||
- Markdown 转 HTML
|
||||
- 文档列表 API: `GET /api/docs`
|
||||
- 文档渲染 API: `GET /api/doc/:name`
|
||||
- **中文文件名支持**: URL 解码处理 `decodeURIComponent()`
|
||||
- 静态文件服务(HTML、CSS、JS)
|
||||
- 支持 `.md` 文件的直接访问(返回原始 Markdown)
|
||||
- 支持父目录文件访问(用于 planning-artifacts 目录)
|
||||
- CORS 支持(如果需要)
|
||||
|
||||
### ⚠️ 中文文件名处理
|
||||
|
||||
**关键修复**: 文档渲染 API 必须处理 URL 编码的中文文件名
|
||||
### 服务器配置
|
||||
|
||||
```javascript
|
||||
// server.js 中文档渲染端点
|
||||
app.get('/api/doc/:name', (req, res) => {
|
||||
const docName = decodeURIComponent(req.params.name); // 必须解码!
|
||||
// 如果文件名已包含 .md 后缀,不要再加
|
||||
const docPath = docName.endsWith('.md') ? docName : docName + '.md';
|
||||
const filePath = path.join(DOCS_DIR, docPath);
|
||||
// ...
|
||||
});
|
||||
const PORT = 8080;
|
||||
const PROTOTYPE_DIR = __dirname;
|
||||
const PARENT_DIR = path.dirname(__dirname); // 支持父目录访问
|
||||
|
||||
// MIME 类型
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.md': 'text/markdown; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.svg': 'image/svg+xml',
|
||||
};
|
||||
|
||||
// 构建文件路径(支持父目录)
|
||||
const filePath = urlPath.startsWith("/planning-artifacts/")
|
||||
? path.join(PARENT_DIR, urlPath)
|
||||
: path.join(PROTOTYPE_DIR, urlPath);
|
||||
```
|
||||
|
||||
**问题原因**: 前端请求中文文件名时会自动 URL 编码(如 `%E5%8E%9F%E5%A7%8B...`),后端必须解码才能找到文件。
|
||||
|
||||
### ⚠️ 代码块样式处理
|
||||
|
||||
**关键修复**: Markdown 转 HTML 时必须保护代码块内容,避免换行被错误替换
|
||||
|
||||
```javascript
|
||||
// 使用占位符保护代码块
|
||||
const codeBlocks = [];
|
||||
let html = markdown
|
||||
// 先提取代码块,用占位符替换
|
||||
.replace(/```(\w*)\n?([\s\S]*?)```/g, (match, lang, code) => {
|
||||
const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`;
|
||||
const isAsciiArt = /┌|│|└|├|─|┐|┘|┤/.test(code);
|
||||
const bgClass = isAsciiArt ? 'bg-gray-800 text-gray-100' : 'bg-gray-100 text-gray-900';
|
||||
codeBlocks.push(`<pre class="${bgClass} p-4 rounded-lg overflow-x-auto my-4 font-mono text-sm whitespace-pre"><code>${code}</code></pre>`);
|
||||
return placeholder;
|
||||
})
|
||||
// ... 其他处理 ...
|
||||
// 最后恢复代码块
|
||||
.replace(/__CODE_BLOCK_(\d+)__/g, (match, index) => codeBlocks[index]);
|
||||
```
|
||||
|
||||
**问题原因**: 直接用 `.replace(/\n/g, '<br>')` 会把代码块中的换行也替换成 `<br>`,导致每行都有背景。
|
||||
|
||||
**样式规范**:
|
||||
- ASCII图表代码块:`bg-gray-800 text-gray-100`(深色背景+浅色文字)
|
||||
- 普通代码块:`bg-gray-100 text-gray-900`(浅色背景+深色文字)
|
||||
- 内联代码:`bg-gray-200 text-gray-800`(浅灰背景+深色文字)
|
||||
|
||||
### ⚠️ Markdown 代码块样式
|
||||
|
||||
**关键修复**: 代码块必须正确处理,避免白字白底无法阅读
|
||||
|
||||
```javascript
|
||||
// 代码块正则(支持带或不带换行符的格式)
|
||||
.replace(/```(\w*)\n?([\s\S]*?)```/g, (match, lang, code) => {
|
||||
// 判断是否为ASCII图表(包含框线字符)
|
||||
const isAsciiArt = /┌|│|└|├|─|┐|┘|┤/.test(code);
|
||||
// 如果是ASCII图表,使用深色背景+浅色文字;否则使用灰色背景
|
||||
const bgClass = isAsciiArt ? 'bg-gray-800 text-gray-100' : 'bg-gray-100 text-gray-800';
|
||||
return `<pre class="${bgClass} p-4 rounded-lg overflow-x-auto my-4 font-mono text-sm whitespace-pre"><code>${code}</code></pre>`;
|
||||
})
|
||||
```
|
||||
|
||||
**问题原因**: Markdown 代码块可能使用 ``` 后面直接跟内容(无换行符),正则必须兼容这种格式。同时需要为 ASCII 图表使用深色背景以提升可读性。
|
||||
|
||||
### 使用方法
|
||||
|
||||
```bash
|
||||
cd prototype/
|
||||
node server.js
|
||||
# 访问: http://localhost:7357
|
||||
```
|
||||
|
||||
### 端口说明
|
||||
- **默认端口**: 7357 (偏门端口)
|
||||
- **统一入口**: http://localhost:7357/index.html
|
||||
- **原型系统**: http://localhost:7357/admin-index.html
|
||||
- **文档系统**: http://localhost:7357/docs-index.html
|
||||
|
||||
---
|
||||
### ⚠️ 中文文件名支持
|
||||
|
||||
前端使用 fetch 请求时,浏览器会自动处理 URL 编码,无需特殊处理。
|
||||
## Step 7: 自动验证与测试
|
||||
|
||||
使用 **Playwright MCP** 和 **图片 MCP** 进行自动化验证。
|
||||
|
||||
Reference in New Issue
Block a user