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

5.9 KiB
Raw Permalink Blame History

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
    <label class="filter-checkbox">
      <input type="checkbox" name="hide_ai_daily" value="1">
      隐藏AI日报
    </label>
    
  • 勾选状态变化时,自动重新请求列表(无需额外「确认」按钮)。
  • 当页面存在搜索词 q勾选框与搜索条件共存URL 同时携带 qmodehide_ai_daily

3.2 筛选规则

后端过滤条件(OR 逻辑

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 容器:

<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>

新增样式:

.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 自动提交表单:

document.querySelector('.filter-checkbox input').addEventListener('change', function() {
    document.querySelector('.search-box').submit();
});

4.2 后端修改(blog/src/routes/pages.py

posts_list() 函数中新增参数解析与过滤:

@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 列表后、渲染模板前,插入过滤逻辑:

    # ... 现有搜索/列表逻辑 ...

    # 过滤 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 接口,仅扩展现有路由参数。