diff --git a/backend/app/api/articles.py b/backend/app/api/articles.py index 5ee78f3..a6d0782 100644 --- a/backend/app/api/articles.py +++ b/backend/app/api/articles.py @@ -127,6 +127,10 @@ async def list_articles( published_at=art.published_at, fetched_at=art.fetched_at, image_url=art.image_url, + # 列表预览钩子:分类 + LLM 点评 + AI 插图 缩略图 + commentary=art.commentary, + commentary_status=art.commentary_status, + image_ai_url=art.image_ai_url, is_starred=art.id in starred_ids, ) items.append(item) diff --git a/backend/app/schemas/article.py b/backend/app/schemas/article.py index 66c048e..339d2b8 100644 --- a/backend/app/schemas/article.py +++ b/backend/app/schemas/article.py @@ -16,7 +16,7 @@ class SourceBrief(BaseModel): class ArticleListItem(BaseModel): - """列表项:精简字段。""" + """列表项:精简字段(首页只露钩子,详细阅读进详情页)。""" model_config = ConfigDict(from_attributes=True) @@ -31,6 +31,10 @@ class ArticleListItem(BaseModel): published_at: datetime | None = None fetched_at: datetime image_url: str | None = None + # === 列表预览钩子:点击进详情前的"诱导点" === + commentary: str | None = None # LLM 点评(列表里截断显示) + commentary_status: str | None = None # ok/failed/pending/n/a + image_ai_url: str | None = None # AI 插图(列表里缩略图) is_starred: bool = False diff --git a/frontend/src/api/articles.ts b/frontend/src/api/articles.ts index c5085aa..10bf8e9 100644 --- a/frontend/src/api/articles.ts +++ b/frontend/src/api/articles.ts @@ -31,6 +31,10 @@ export interface ArticleListItem { published_at?: string | null fetched_at: string image_url?: string | null + // 列表预览钩子(首页展示用,详情页看完整版) + commentary?: string | null + commentary_status?: string | null + image_ai_url?: string | null is_starred: boolean } diff --git a/frontend/src/views/Feed.vue b/frontend/src/views/Feed.vue index 55b322a..a67cec3 100644 --- a/frontend/src/views/Feed.vue +++ b/frontend/src/views/Feed.vue @@ -68,6 +68,26 @@ function fmtTime(s?: string | null) { return dayjs(s).fromNow() } +// category 是逗号分隔字符串(LLM 输出),拆成多个 tag +function splitCategory(c?: string | null): string[] { + if (!c) return [] + return c.split(',').map((s) => s.trim()).filter(Boolean) +} + +// 评论预览:长文截断,带状态点 +function previewCommentary(c?: string | null, max = 120): string { + if (!c) return '' + const trimmed = c.replace(/\s+/g, ' ').trim() + return trimmed.length > max ? trimmed.slice(0, max) + '…' : trimmed +} + +function commentaryStatusType(s?: string | null): 'success' | 'warning' | 'error' | 'default' { + if (s === 'ok') return 'success' + if (s === 'failed') return 'error' + if (s === 'pending') return 'warning' + return 'default' +} + onMounted(async () => { await loadSources() await load() @@ -112,6 +132,15 @@ onMounted(async () => { {{ a.translation_status }} + + + {{ c }} + {{ fmtTime(a.published_at || a.fetched_at) }}
{{ a.title }}
@@ -121,6 +150,28 @@ onMounted(async () => {
{{ a.summary_zh.slice(0, 200) }}{{ a.summary_zh.length > 200 ? '…' : '' }}
+ +
+ + 💬 评论 + + {{ a.commentary_status || 'n/a' }} + + + {{ previewCommentary(a.commentary, 140) }} +