Add: 博客 /posts 隐藏AI日报筛选功能 PRD
This commit is contained in:
186
prd-blog-hide-ai-daily-filter.md
Normal file
186
prd-blog-hide-ai-daily-filter.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# PRD:博客 /posts 页面添加「隐藏AI日报」筛选功能
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
在 `https://blog.ephron.ren/posts` 页面的搜索区域左侧,新增一个勾选框 **「隐藏AI日报」**。
|
||||
勾选后,文章列表(含搜索结果)中不再显示标题以 `AI日报` 开头、或 slug 以 `ai-` 开头的文章。
|
||||
|
||||
## 2. 背景与现状
|
||||
|
||||
### 2.1 页面结构
|
||||
- `/posts` 路由由 `blog/src/routes/pages.py` 中的 `posts_list()` 处理。
|
||||
- 同一页面承担「全部文章列表」和「搜索结果展示」两种状态。
|
||||
- 前端模板:`blog/templates/index.html`
|
||||
|
||||
### 2.2 当前搜索参数
|
||||
| 参数 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `q` | 搜索关键词 | `RAG` |
|
||||
| `mode` | 搜索模式:`simple` / `fulltext` | `simple` |
|
||||
|
||||
### 2.3 AI日报特征
|
||||
- **标题格式**:`AI日报 · YYYY-MM-DD`,前两个字固定为 `AI日报`
|
||||
- **Slug 格式**:`ai-YYYY-MM-DD`,以 `ai-` 开头
|
||||
- **数据规模**:目前共 70 篇文章,其中 AI日报约占 40+ 篇,占比超过一半
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 交互
|
||||
- 在搜索框左侧(`page-header` 区域)增加一个 checkbox:
|
||||
```html
|
||||
<label class="filter-checkbox">
|
||||
<input type="checkbox" name="hide_ai_daily" value="1">
|
||||
隐藏AI日报
|
||||
</label>
|
||||
```
|
||||
- 勾选状态变化时,自动重新请求列表(无需额外「确认」按钮)。
|
||||
- 当页面存在搜索词 `q` 时,勾选框与搜索条件共存;URL 同时携带 `q`、`mode`、`hide_ai_daily`。
|
||||
|
||||
### 3.2 筛选规则
|
||||
后端过滤条件(**OR 逻辑**):
|
||||
```python
|
||||
if hide_ai_daily:
|
||||
posts = [
|
||||
p for p in posts
|
||||
if not (p.title.startswith("AI日报") or p.slug.startswith("ai-"))
|
||||
]
|
||||
```
|
||||
|
||||
### 3.3 持久化
|
||||
- **不**使用 Cookie / 本地存储记住勾选状态。
|
||||
- 仅通过 URL 参数传递,用户刷新或分享链接时状态自然保持或丢失。
|
||||
|
||||
## 4. 技术方案
|
||||
|
||||
### 4.1 前端修改(`blog/templates/index.html`)
|
||||
|
||||
在 `.search-box` 左侧插入 checkbox 容器:
|
||||
|
||||
```html
|
||||
<div class="page-header">
|
||||
<div class="page-header-left">
|
||||
<h1>所有文章</h1>
|
||||
<p>共 {{ posts | length }} 篇文章</p>
|
||||
</div>
|
||||
<div class="search-area">
|
||||
<label class="filter-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="hide_ai_daily"
|
||||
value="1"
|
||||
{% if hide_ai_daily %}checked{% endif %}
|
||||
>
|
||||
隐藏AI日报
|
||||
</label>
|
||||
<form class="search-box" method="GET" action="/posts">
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
class="input"
|
||||
placeholder="搜索文章..."
|
||||
value="{{ search_query or '' }}"
|
||||
autocomplete="off"
|
||||
>
|
||||
<input type="hidden" name="mode" value="{{ search_mode or 'simple' }}">
|
||||
{% if hide_ai_daily %}
|
||||
<input type="hidden" name="hide_ai_daily" value="1">
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
新增样式:
|
||||
```css
|
||||
.search-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.filter-checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-checkbox input[type="checkbox"] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
```
|
||||
|
||||
**交互逻辑**:勾选/取消勾选后,通过 JavaScript 自动提交表单:
|
||||
```javascript
|
||||
document.querySelector('.filter-checkbox input').addEventListener('change', function() {
|
||||
document.querySelector('.search-box').submit();
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 后端修改(`blog/src/routes/pages.py`)
|
||||
|
||||
在 `posts_list()` 函数中新增参数解析与过滤:
|
||||
|
||||
```python
|
||||
@router.get("/posts", response_class=HTMLResponse)
|
||||
async def posts_list(
|
||||
request: Request,
|
||||
q: str | None = Query(default=None, description="搜索关键词"),
|
||||
mode: str | None = Query(default="simple", description="搜索模式: simple 或 fulltext"),
|
||||
hide_ai_daily: bool | None = Query(default=False, description="隐藏AI日报"),
|
||||
):
|
||||
```
|
||||
|
||||
在获取到 `posts` 列表后、渲染模板前,插入过滤逻辑:
|
||||
|
||||
```python
|
||||
# ... 现有搜索/列表逻辑 ...
|
||||
|
||||
# 过滤 AI日报
|
||||
if hide_ai_daily:
|
||||
posts = [
|
||||
p for p in posts
|
||||
if not (p.title.startswith("AI日报") or p.slug.startswith("ai-"))
|
||||
]
|
||||
|
||||
# 获取文章摘要(后续不变)
|
||||
```
|
||||
|
||||
### 4.3 搜索服务适配
|
||||
|
||||
- `simple` 模式:过滤在 `pages.py` 层完成,`search_posts()` 无需改动。
|
||||
- `fulltext` 模式:过滤同样在 `pages.py` 层完成,`search_posts_fulltext()` 无需改动。
|
||||
|
||||
**理由**:两种搜索模式返回的都是 `PostMeta` 列表,在路由层统一过滤最简洁,避免改动 Whoosh 索引逻辑。
|
||||
|
||||
## 5. 边界情况
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 未勾选 + 无搜索词 | 显示全部文章(当前行为) |
|
||||
| 未勾选 + 有搜索词 | 搜索结果正常展示 |
|
||||
| 勾选 + 无搜索词 | 全局列表隐藏 AI日报 |
|
||||
| 勾选 + 有搜索词 | 搜索结果中隐藏 AI日报 |
|
||||
| 过滤后结果为空 | 显示「没有找到匹配的文章」空状态 |
|
||||
| AI日报标题前缀变更 | 需同步修改过滤条件(低概率) |
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
1. 访问 `/posts`,页面右上角搜索框左侧出现「隐藏AI日报」勾选框。
|
||||
2. 勾选后页面刷新,AI日报文章从列表中消失,URL 携带 `hide_ai_daily=1`。
|
||||
3. 取消勾选,AI日报恢复显示,URL 参数消失。
|
||||
4. 在有搜索词的情况下勾选,搜索结果中 AI日报被过滤。
|
||||
5. 过滤后结果为空时,正确显示空状态提示。
|
||||
6. 样式与现有设计系统一致(使用 `var(--text-muted)` / `var(--accent)` 变量)。
|
||||
|
||||
## 7. 不在此次范围
|
||||
|
||||
- 不持久化用户偏好(Cookie / 本地存储)。
|
||||
- 不修改搜索索引(Whoosh)。
|
||||
- 不影响草稿(`draft`)过滤逻辑。
|
||||
- 不新增后端 API 接口,仅扩展现有路由参数。
|
||||
Reference in New Issue
Block a user