feat(read): 已读功能 — 每账号标已读,列表默认隐藏

需求: 每个账号可标已读,已读过的文章刷新/重载后不在 24h feed 中显示。

设计:
- 新表 article_reads (user_id, article_id, read_at) 复合主键,on-delete CASCADE
- 迁移 0007_article_reads
- /me/reads/{id} POST/DELETE 标记 / 取消(幂等,PG upsert on_conflict_do_nothing)
- /me/reads GET 列出已读 IDs(默认 7 天,limit 500)
- articles.py 列表查询加 hide_read=true 参数(默认 true),用 NOT EXISTS 排除已读
- ArticleListItem / ArticleDetail schema 加 is_read 字段

前端:
- types 加 is_read + readsApi(mark/unmark/list)
- Feed 列表:
    顶部加 '隐藏已读' 开关,默认 ON
    每张卡片加 '标为已读 / 标为未读' 按钮(乐观更新,失败回滚)
    已读卡片 opacity 0.7 + 灰背景,标识弱化
- ArticleDetail 详情页操作栏加 '标为已读' 按钮(同样乐观)
This commit is contained in:
xiaji
2026-06-13 21:04:47 +08:00
parent 8b3c7caf87
commit 6c71ab2e79
9 changed files with 352 additions and 4 deletions

View File

@@ -42,6 +42,7 @@ class ArticleListItem(BaseModel):
commentary_engine: str | None = None # angel / meituan / "angel,meituan"
image_ai_url: str | None = None # AI 插图(列表里缩略图)
is_starred: bool = False
is_read: bool = False
class ArticleDetail(BaseModel):
@@ -84,6 +85,7 @@ class ArticleDetail(BaseModel):
published_at: datetime | None = None
fetched_at: datetime
is_starred: bool = False
is_read: bool = False
class ArticleListResponse(BaseModel):