# PRD:集合功能增强 ## 一、问题分析 ### 问题 1:集合详情页不显示已收录文章 **页面**:`https://blog.ephron.ren/collections/data-structure` **现象**:集合详情页显示「该集合暂无文章」,但实际上已有文章被收录到该集合。 **根因分析**: 路由代码 `blog/src/routes/pages.py` 第 488-519 行: ```python for item in collection.get("items", []): post = get_post_by_slug(item["post_slug"], include_drafts=False) if post: items.append({...}) ``` 可能原因: 1. `blog_collection_items` 表中没有对应数据 2. 文章 slug 与集合中记录的 `post_slug` 不匹配 3. 文章是草稿状态但使用了 `include_drafts=False` **需要验证**:查询数据库确认 `blog_collection_items` 表是否有数据。 --- ### 问题 2:全部文章列表不显示集合归属 **页面**:`https://blog.ephron.ren/posts` **现象**:文章列表只显示置顶、日期、标签、草稿标记,不显示文章所属的集合。 **用户期望**:已收录到集合的文章应在全部文章列表中显示集合标记(类似标签),方便识别哪些文章已被组织到集合中。 --- ### 问题 3:新建文章/提示词时无法选择集合 **页面**: - Blog:`https://blog.ephron.ren/admin/new` - Prompt:`https://prompt.ephron.ren/admin/new` **现象**:新建文章/提示词时,只能先创建内容,再手动编辑集合添加。 **用户期望**:创建时即可选择加入已有集合,提升内容组织效率。 --- ## 二、需求详情 ### 需求 1:修复集合详情页文章显示 **优先级**:P0(Bug 修复) **验收标准**: - [ ] 访问 `/collections/{key}` 能正确显示已收录的文章 - [ ] 文章按 `sort_order` 排序显示 - [ ] 显示文章标题、摘要、备注 - [ ] 空集合显示「该集合暂无文章」 **技术方案**: 1. 检查并修复 `blog_collection_items` 表数据 2. 确保 `get_post_by_slug` 在包含草稿时能正确返回 3. 添加日志排查 slug 匹配问题 --- ### 需求 2:全部文章列表显示集合标记 **优先级**:P1(功能增强) **验收标准**: - [ ] 文章列表中,已收录到集合的文章显示集合标记 - [ ] 标记可点击,跳转到集合详情页 - [ ] 一篇文章可属于多个集合,显示所有集合标记 - [ ] 未收录到任何集合的文章不显示标记 **UI 设计**: ``` 文章标题 📌 置顶 摘要内容... 2025-01-01 [标签1] [标签2] [集合A] [集合B] 草稿 ``` 集合标记样式: - 背景色:`var(--accent-glow)`(蓝色半透明) - 文字色:`var(--accent)` - 圆角:`4px` - 前缀图标:`📁` 或 `📚` **技术方案**: 1. 在 `pages.py` 的 `posts_list` 函数中,批量查询文章的集合归属 2. 使用 `get_all_collection_post_slugs()` 或更高效的批量查询 3. 将集合信息传递给模板 4. 模板中渲染集合标记 **API 变更**: ```python # 新增批量查询函数 def get_posts_collections(post_slugs: list[str]) -> dict[str, list[dict]]: """ 批量查询多篇文章的集合归属 返回: {post_slug: [{key, title}, ...]} """ ``` --- ### 需求 3:新建文章/提示词时选择集合 **优先级**:P1(功能增强) #### 3.1 Blog 新建文章 **页面**:`/admin/new` **验收标准**: - [ ] 新建文章表单增加「选择集合」下拉框 - [ ] 支持多选(可加入多个集合) - [ ] 显示集合名称和已有文章数量 - [ ] 提交时自动创建 `blog_collection_items` 记录 - [ ] 权限控制:需要 `blog.post.create_draft` 权限 **UI 设计**: ```html
按住 Ctrl/Cmd 可多选
``` **技术方案**: 1. `admin.py` 的 `new_collection_page` 函数传递集合列表 2. 表单增加 `collection_keys` 字段(数组) 3. `create_new_post` 函数处理集合关联 4. 调用 `add_item_to_collection` 创建关联记录 **API 变更**: ```python # POST /admin/new 新增字段 collection_keys: list[str] = Form(default=[]) # 集合 key 列表 ``` #### 3.2 Prompt 新建提示词 **页面**:`/admin/new` **验收标准**: - [ ] 新建提示词表单增加「选择集合」下拉框 - [ ] 支持多选 - [ ] 提交时自动创建 `collection_items` 记录 - [ ] 权限控制:需要 `prompt.create` 权限 **技术方案**:同 Blog,修改 Prompt 服务的 `admin.py` 和相关模板。 #### 3.3 API 创建文章/提示词 **Blog Service API**: ```python # POST /api/service/posts { "title": "文章标题", "content": "...", "tags": ["tag1"], "collection_keys": ["col1", "col2"] # 新增 } ``` **Prompt Service API**: ```python # POST /api/service/prompts { "key": "prompt-key", "title": "提示词标题", "content": "...", "collection_keys": ["col1", "col2"] # 新增 } ``` **验收标准**: - [ ] API 支持 `collection_keys` 参数 - [ ] 参数可选,默认为空数组 - [ ] 自动创建集合关联记录 - [ ] 集合不存在时忽略(不报错) - [ ] 权限检查:需要对应集合的编辑权限 --- ## 三、数据模型 ### Blog 集合表结构 ```sql -- 集合主表 CREATE TABLE blog_collections ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE NOT NULL, title TEXT NOT NULL, description TEXT DEFAULT '', cover_image TEXT DEFAULT '', created_by TEXT, is_active INTEGER DEFAULT 1, created_at DATETIME DEFAULT (datetime('now')), updated_at DATETIME DEFAULT (datetime('now')) ); -- 集合关联表 CREATE TABLE blog_collection_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_key TEXT NOT NULL, post_slug TEXT NOT NULL, sort_order INTEGER DEFAULT 0, note TEXT DEFAULT '', created_at DATETIME DEFAULT (datetime('now')), FOREIGN KEY (collection_key) REFERENCES blog_collections(key) ON DELETE CASCADE, UNIQUE(collection_key, post_slug) ); ``` ### Prompt 集合表结构 ```sql -- 集合主表 CREATE TABLE collections ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE NOT NULL, title TEXT NOT NULL, description TEXT DEFAULT '', created_by TEXT, is_active INTEGER DEFAULT 1, created_at DATETIME DEFAULT (datetime('now')), updated_at DATETIME DEFAULT (datetime('now')) ); -- 集合关联表 CREATE TABLE collection_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_key TEXT NOT NULL, prompt_key TEXT NOT NULL, sort_order INTEGER DEFAULT 0, note TEXT DEFAULT '', created_at DATETIME DEFAULT (datetime('now')), FOREIGN KEY (collection_key) REFERENCES collections(key) ON DELETE CASCADE, UNIQUE(collection_key, prompt_key) ); ``` --- ## 四、与 Prompt 集合逻辑的差异 | 特性 | Blog 集合 | Prompt 集合 | |------|----------|------------| | 内容类型 | 文章(Markdown) | 提示词(模板) | | 主键 | `post_slug`(文件名) | `prompt_key`(数据库 key) | | 创建方式 | 文件系统扫描 | 数据库插入 | | 集合显示 | 需求2:全部文章列表显示集合标记 | 已有:全部提示词列表显示集合标记 | | 创建时选择集合 | 需求3:新建时可选 | 需求3:新建时可选 | **关键差异**: - Blog 文章是文件系统驱动,集合关联是后加的 - Prompt 提示词是数据库驱动,集合关联是原生支持 --- ## 五、实施计划 ### Phase 1:Bug 修复(需求 1) **时间**:0.5 天 **任务**: 1. [ ] 检查 `blog_collection_items` 表数据 2. [ ] 修复数据不一致问题 3. [ ] 验证集合详情页正常显示 ### Phase 2:全部文章显示集合标记(需求 2) **时间**:1 天 **任务**: 1. [ ] 实现 `get_posts_collections()` 批量查询函数 2. [ ] 修改 `pages.py` 的 `posts_list` 函数 3. [ ] 修改 `index.html` 模板 4. [ ] 测试多集合、空集合场景 ### Phase 3:新建时选择集合(需求 3) **时间**:1.5 天 **任务**: 1. [ ] Blog:修改 `admin.py` 和 `new.html` 2. [ ] Prompt:修改 `admin.py` 和 `new.html` 3. [ ] Blog API:修改 Service API 支持 `collection_keys` 4. [ ] Prompt API:修改 Service API 支持 `collection_keys` 5. [ ] 测试权限控制和边界场景 --- ## 六、测试用例 ### 需求 1 测试 | 编号 | 测试内容 | 预期结果 | |------|---------|---------| | T-001 | 访收录有文章的集合详情页 | 显示文章列表 | | T-002 | 访问空集合详情页 | 显示「该集合暂无文章」 | | T-003 | 文章排序 | 按 sort_order 升序 | | T-004 | 草稿文章 | 不显示(公开页) | ### 需求 2 测试 | 编号 | 测试内容 | 预期结果 | |------|---------|---------| | T-010 | 已收录文章显示集合标记 | 显示蓝色标记 | | T-011 | 未收录文章 | 不显示集合标记 | | T-012 | 点击集合标记 | 跳转到集合详情页 | | T-013 | 文章属于多个集合 | 显示多个标记 | | T-014 | 搜索结果中显示 | 同样显示集合标记 | ### 需求 3 测试 | 编号 | 测试内容 | 预期结果 | |------|---------|---------| | T-020 | Blog 新建选择集合 | 自动关联到集合 | | T-021 | Blog 新建不选集合 | 正常创建,不关联 | | T-022 | Blog API 带 collection_keys | 自动关联 | | T-023 | Prompt 新建选择集合 | 自动关联到集合 | | T-024 | Prompt API 带 collection_keys | 自动关联 | | T-025 | 集合 key 不存在 | 忽略,不报错 | | T-026 | 权限不足 | 返回 403 | --- ## 七、风险与依赖 ### 风险 1. **数据一致性**:Blog 文章删除后,`blog_collection_items` 中的关联记录可能成为孤立数据 2. **性能影响**:全部文章列表需要额外查询集合信息,可能增加数据库压力 3. **权限复杂性**:创建文章时选择集合需要同时检查文章和集合的权限 ### 依赖 1. 需求 1 是需求 2 的前提(集合详情页必须能正常显示) 2. 需求 3 依赖现有集合 CRUD 功能 3. Blog 和 Prompt 服务的集合逻辑独立,可并行开发 --- ## 八、相关代码位置 ### Blog 服务 - 集合详情路由:`blog/src/routes/pages.py` 第 488-519 行 - 文章列表路由:`blog/src/routes/pages.py` 第 120-160 行 - Admin 新建路由:`blog/src/routes/admin.py` 第 431-497 行 - Service API 路由:`blog/src/routes/service_api.py` - 集合服务层:`blog/src/services/blog_collections.py` - 集合详情模板:`blog/templates/collection_detail.html` - 文章列表模板:`blog/templates/index.html` - Admin 新建模板:`blog/templates/admin/new.html` ### Prompt 服务 - Admin 新建路由:`prompt/src/routes/admin.py` - Service API 路由:`prompt/src/routes/service_api.py` - 集合服务层:`prompt/src/services/collections.py` - Admin 新建模板:`prompt/templates/admin/new.html` --- ## 九、验收标准汇总 - [ ] 集合详情页正确显示已收录文章 - [ ] 全部文章列表显示集合标记 - [ ] 新建文章时可选择加入集合 - [ ] 新建提示词时可选择加入集合 - [ ] API 创建支持 `collection_keys` 参数 - [ ] 权限控制正确 - [ ] 边界场景处理(空集合、不存在的 key、重复关联)