diff --git a/.claude/skills/generate-prototype-grid/SKILL.md b/.claude/skills/generate-prototype-grid/SKILL.md
index a7c4813..74f1c21 100644
--- a/.claude/skills/generate-prototype-grid/SKILL.md
+++ b/.claude/skills/generate-prototype-grid/SKILL.md
@@ -319,96 +319,250 @@ prototype/
- 快速链接导航
- 项目概览信息
-## Step 5: 生成文档系统(前端渲染方案)
+## Step 5: 生成文档系统(独立页面方案)⭐
-生成 `docs-index.html` 文档首页,使用前端 JavaScript 渲染 Markdown 文件。
+生成 `view-doc.html` 独立文档查看页面,通过 URL 参数传递文档路径。
-### 功能特性
-- 自动扫描 `docs/` 或 `planning-artifacts/` 目录
-- **前端 Markdown 渲染**:使用 markdown-it 库在浏览器中渲染
-- 文档卡片展示,点击后在页面中展开显示内容
-- 响应式设计,支持桌面端和移动端
+### ⭐ 推荐方案:独立页面架构
+
+使用独立页面(`view-doc.html?doc=xxx.md`)展示文档,而非在当前页面内展开。
+
+### 方案对比
+
+| 对比项 | 独立页面方案 ⭐ | 页面内展开方案 |
+|--------|----------------|----------------|
+| **安全性** | ✅ URL 参数验证,路径遍历防护 | ❌ innerHTML 直接注入,XSS 风险 |
+| **性能** | ✅ 页面级缓存,独立加载 | ❌ DOM 操作,主线程阻塞 |
+| **可维护性** | ✅ 单一职责,代码简洁 | ❌ 逻辑耦合,状态管理复杂 |
+| **用户体验** | ✅ 可分享链接,书签友好 | ❌ 刷新丢失状态 |
+| **可扩展性** | ✅ 易于添加新功能 | ❌ 受限于宿主页面 |
### 技术实现
-**1. 引入 markdown-it 库**
+**1. view-doc.html 页面结构**
```html
-
+
+
+
+
+
+ 文档查看器
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-**2. 文档卡片结构**
+**2. 安全性增强:路径验证**
+```javascript
+// 允许的文档目录白名单
+const ALLOWED_PATHS = [
+ '/docs/',
+ '/planning-artifacts/',
+ '/'
+];
+
+// 验证文档路径
+function validateDocPath(docPath) {
+ // 移除 URL 编码
+ const decoded = decodeURIComponent(docPath);
+
+ // 检查路径遍历攻击
+ if (decoded.includes('..') || decoded.includes('\\')) {
+ throw new Error('非法的文档路径');
+ }
+
+ // 检查文件扩展名
+ if (!decoded.endsWith('.md')) {
+ throw new Error('仅支持 Markdown 文件');
+ }
+
+ // 确保路径在白名单内
+ const isValid = ALLOWED_PATHS.some(prefix => decoded.startsWith(prefix));
+ if (!isValid) {
+ throw new Error('文档路径不在允许范围内');
+ }
+
+ return decoded;
+}
+```
+
+**3. 性能优化:缓存策略**
+```javascript
+// 文档缓存(内存级)
+const docCache = new Map();
+const CACHE_MAX_SIZE = 20;
+const CACHE_TTL = 5 * 60 * 1000; // 5 分钟
+
+function getFromCache(key) {
+ const cached = docCache.get(key);
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
+ return cached.content;
+ }
+ return null;
+}
+
+function setCache(key, content) {
+ // LRU 淘汰策略
+ if (docCache.size >= CACHE_MAX_SIZE) {
+ const firstKey = docCache.keys().next().value;
+ docCache.delete(firstKey);
+ }
+ docCache.set(key, {
+ content,
+ timestamp: Date.now()
+ });
+}
+```
+
+**4. 完整加载逻辑**
+```javascript
+// 从 URL 获取文档路径
+const urlParams = new URLSearchParams(window.location.search);
+const docPath = urlParams.get('doc');
+
+if (!docPath) {
+ showError('缺少文档参数');
+} else {
+ loadDocument(docPath);
+}
+
+async function loadDocument(docPath) {
+ try {
+ // 验证路径
+ const validatedPath = validateDocPath(docPath);
+
+ // 检查缓存
+ const cached = getFromCache(validatedPath);
+ if (cached) {
+ renderMarkdown(cached);
+ return;
+ }
+
+ // 加载文档
+ const response = await fetch(validatedPath);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ const markdown = await response.text();
+
+ // 存入缓存
+ setCache(validatedPath, markdown);
+
+ // 渲染
+ renderMarkdown(markdown);
+
+ } catch (error) {
+ showError(error.message);
+ }
+}
+
+function renderMarkdown(markdown) {
+ // 使用 marked 解析
+ const html = marked.parse(markdown);
+
+ // 安全过滤(DOMPurify 可选,marked 已有基础防护)
+ const safeHtml = sanitizeHtml(html);
+
+ // 渲染到页面
+ document.getElementById('doc-content').innerHTML = safeHtml;
+
+ // 代码高亮
+ document.querySelectorAll('pre code').forEach(block => {
+ hljs.highlightElement(block);
+ });
+
+ // 更新标题
+ const titleMatch = markdown.match(/^#\s+(.+)$/m);
+ if (titleMatch) {
+ document.getElementById('doc-title').textContent = titleMatch[1];
+ document.title = titleMatch[1] + ' - 文档';
+ }
+}
+```
+
+### 文档链接格式
+
+在 `index.html`、`docs-index.html` 和其他页面中,文档链接使用独立页面:
+
```html
+
+
+ PRD 文档
+
+
+
+
+ 架构设计
+
+
-
-
- 1,082 行
- 40 KB
-
-
-
-
-
-
+
产品需求文档 (PRD)
+
+ 查看文档
+
```
-**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');
- }
-}
+| 措施 | 实现方式 | 防护目标 |
+|------|---------|---------|
+| **路径白名单** | `ALLOWED_PATHS` 数组验证 | 防止路径遍历攻击 |
+| **扩展名限制** | 仅允许 `.md` 文件 | 防止执行非法文件 |
+| **URL 解码检查** | 检测 `..` 和 `\` | 防止编码绕过 |
+| **XSS 防护** | marked 配置 + 可选 DOMPurify | 防止脚本注入 |
+| **CSP 头部** | 服务器设置 Content-Security-Policy | 限制资源加载 |
-// 加载文档
-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;
- }
- }
+| 优化 | 实现方式 | 效果 |
+|------|---------|------|
+| **内存缓存** | LRU 缓存,5 分钟 TTL | 减少重复请求 |
+| **懒加载** | 仅在打开文档时加载 | 减少初始负载 |
+| **代码高亮** | 按需 highlight.js | 提升渲染速度 |
+| **CDN 资源** | 使用稳定 CDN | 全球加速访问 |
- // 所有路径都失败
- container.innerHTML = '无法加载文档: ' + docFile + '
';
- container.classList.remove('hidden');
-}
-```
+### ⭐ 最佳实践建议
+
+1. **优先使用独立页面**:所有文档查看都应通过 `view-doc.html` 访问
+2. **新标签页打开**:使用 `target="_blank"` 保持上下文
+3. **添加 noopener**:使用 `rel="noopener"` 防止 window.opener 漏洞
+4. **错误处理友好**:显示清晰的错误消息和建议操作
+5. **支持书签分享**:URL 参数状态可直接分享
+
+### Markdown 样式(保持不变)
-**4. Markdown 内容样式**
```css
.markdown-body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
@@ -470,30 +624,6 @@ async function loadDoc(docFile, container) {
max-width: 100%;
}
```
-
-### 文档链接格式
-
-在 `index.html` 和其他页面中,文档链接使用以下格式:
-
-```html
-
-
-
-
-
- PRD 文档
-
-```
-
-### ⚠️ 关键设计原则
-
-1. **前端渲染优先**:使用 markdown-it 在浏览器中渲染,不依赖后端 API
-2. **直接展示**:点击文档链接后,直接在页面中展开显示内容,不使用弹窗/模态框
-3. **渐进加载**:文档内容在需要时才加载,提升页面加载速度
-4. **多路径兼容**:尝试多个可能的文档路径(planning-artifacts、docs、根目录)
-
## Step 6: 生成本地预览服务器
生成 `server.js` 文件,提供静态文件服务。