Files
ephron-ren-prd/prd-blog-hide-ai-daily-filter.md

187 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 接口,仅扩展现有路由参数。