390 lines
11 KiB
Markdown
390 lines
11 KiB
Markdown
# 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
|
||
<div class="form-group">
|
||
<label>加入集合(可选)</label>
|
||
<select name="collection_keys" multiple class="input">
|
||
<option value="">-- 不加入集合 --</option>
|
||
<option value="data-structure">数据结构 (5篇)</option>
|
||
<option value="algorithm">算法 (3篇)</option>
|
||
</select>
|
||
<span class="hint">按住 Ctrl/Cmd 可多选</span>
|
||
</div>
|
||
```
|
||
|
||
**技术方案**:
|
||
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、重复关联)
|