feat(ui): 评论三态显式 — 有内容 / 等待中(灰斜体) / 失败(红)
- 之前: 只有 commentary 内容才显示 v-if 块,否则空白
- 现在: 评论卡永远显示,三态语义明确:
ok + 有内容 → 评论文本(正常)
pending / n/a / null → 等待中(灰斜体 + 提示 enrichment_loop 会跑)
failed → 评论生成失败(红斜体 + 失败原因)
- 列表 Feed + 详情 ArticleDetail 同步改造
- 加 commentaryState() 辅助函数:status+content → 'ok'|'waiting'|'failed'
This commit is contained in:
@@ -64,6 +64,24 @@ const publishedAt = computed(() => article.value?.published_at || article.value?
|
||||
const isOwner = computed(() => auth.isOwner)
|
||||
const categories = computed(() => (article.value?.category || '').split(',').filter(Boolean))
|
||||
|
||||
// === 评论三态 ===
|
||||
// 'ok' + 有内容 → 显示真实评论
|
||||
// 'ok' 但无内容 → 等待(防御性)
|
||||
// 'pending' / 'n/a' / null → 等待
|
||||
// 'failed' → 失败
|
||||
type CommentaryState = 'ok' | 'waiting' | 'failed'
|
||||
function commentaryState(status?: string | null, content?: string | null): CommentaryState {
|
||||
if (status === 'failed') return 'failed'
|
||||
if (status === 'ok' && content) return 'ok'
|
||||
return 'waiting'
|
||||
}
|
||||
const angelState = computed<CommentaryState>(() =>
|
||||
commentaryState(article.value?.commentary_status, article.value?.commentary)
|
||||
)
|
||||
const meituanState = computed<CommentaryState>(() =>
|
||||
commentaryState(article.value?.commentary_meituan_status, article.value?.commentary_meituan)
|
||||
)
|
||||
|
||||
/** 把"一坨"译文/原文按"中文句号"切成 <p> 段,改善"挤在一起"的观感。
|
||||
* 优先按 \n 切(LLM 排版过的),没有换行再按句号/问号/感叹号切。
|
||||
* 句中常见的"Mr./U.S."等缩写不会出现在中文译文里,按 6+ 字符才切,避免半句话被切。
|
||||
@@ -258,44 +276,81 @@ onMounted(load)
|
||||
本条翻译失败,可点 "重译" 重试,或查看后端日志。
|
||||
</NAlert>
|
||||
|
||||
<!-- 1) 评论(双 provider:Angel + 美团,各自一张卡) -->
|
||||
<NCard v-if="article.commentary" class="detail-card" style="margin-top: 16px">
|
||||
<!-- 1) Angel 评论(永远显示,三态:有内容 / 等待中 / 失败) -->
|
||||
<NCard class="detail-card" style="margin-top: 16px">
|
||||
<template #header>
|
||||
<span class="card-header-title">💬 Angel 评论</span>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_status)" :bordered="false" round>
|
||||
{{ article.commentary_status || 'n/a' }}
|
||||
{{
|
||||
angelState === 'ok' ? 'ok'
|
||||
: angelState === 'failed' ? 'failed'
|
||||
: '等待中'
|
||||
}}
|
||||
</NTag>
|
||||
</template>
|
||||
<p class="commentary-text-detail">{{ article.commentary }}</p>
|
||||
<p
|
||||
v-if="angelState === 'ok'"
|
||||
class="commentary-text-detail"
|
||||
>{{ article.commentary }}</p>
|
||||
<p v-else-if="angelState === 'failed'" class="commentary-text-detail commentary-text-failed-detail">
|
||||
⚠️ 评论生成失败
|
||||
<NText v-if="article.commentary_status === 'failed'" :depth="3" style="display:block; font-size:12px; margin-top:6px">
|
||||
enrichment_loop 会自动重试;如持续失败,检查后端 worker 日志
|
||||
</NText>
|
||||
</p>
|
||||
<p v-else class="commentary-text-detail commentary-text-waiting-detail">
|
||||
🕒 等待评论中
|
||||
<NText :depth="3" style="display:block; font-size:12px; margin-top:6px">
|
||||
enrichment_loop 会在翻译完成后跑(每篇约 15-20 秒)
|
||||
</NText>
|
||||
</p>
|
||||
</NCard>
|
||||
|
||||
<NCard v-if="article.commentary_meituan" class="detail-card" style="margin-top: 16px">
|
||||
<!-- 2) 美团评论(永远显示,三态) -->
|
||||
<NCard class="detail-card" style="margin-top: 16px">
|
||||
<template #header>
|
||||
<span class="card-header-title commentary-header-meituan">🐱 美团评论</span>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_meituan_status)" :bordered="false" round>
|
||||
{{ article.commentary_meituan_status || 'n/a' }}
|
||||
{{
|
||||
meituanState === 'ok' ? 'ok'
|
||||
: meituanState === 'failed' ? 'failed'
|
||||
: '等待中'
|
||||
}}
|
||||
</NTag>
|
||||
</template>
|
||||
<p class="commentary-text-detail">{{ article.commentary_meituan }}</p>
|
||||
<NText v-if="article.commentary_meituan_model" :depth="3" style="font-size: 11px; display:block; margin-top:8px">
|
||||
<p
|
||||
v-if="meituanState === 'ok'"
|
||||
class="commentary-text-detail"
|
||||
>{{ article.commentary_meituan }}</p>
|
||||
<p v-else-if="meituanState === 'failed'" class="commentary-text-detail commentary-text-failed-detail">
|
||||
⚠️ 评论生成失败
|
||||
<NText
|
||||
v-if="article.commentary_meituan_error"
|
||||
:depth="3"
|
||||
style="display:block; font-size:12px; margin-top:6px"
|
||||
>
|
||||
{{ article.commentary_meituan_error }}
|
||||
</NText>
|
||||
</p>
|
||||
<p v-else class="commentary-text-detail commentary-text-waiting-detail">
|
||||
🕒 等待评论中
|
||||
<NText :depth="3" style="display:block; font-size:12px; margin-top:6px">
|
||||
enrichment_loop 会在翻译完成后跑(每篇约 15-20 秒)
|
||||
</NText>
|
||||
</p>
|
||||
<NText
|
||||
v-if="meituanState === 'ok' && article.commentary_meituan_model"
|
||||
:depth="3"
|
||||
style="font-size: 11px; display:block; margin-top:8px"
|
||||
>
|
||||
模型: {{ article.commentary_meituan_model }}
|
||||
</NText>
|
||||
</NCard>
|
||||
|
||||
<NAlert
|
||||
v-else-if="article.commentary_meituan_status === 'failed' && article.commentary_meituan_error"
|
||||
type="warning"
|
||||
style="margin-top: 16px"
|
||||
:show-icon="false"
|
||||
>
|
||||
<div><strong>美团评论生成失败</strong></div>
|
||||
<div style="font-size: 12px; margin-top: 4px">{{ article.commentary_meituan_error }}</div>
|
||||
</NAlert>
|
||||
|
||||
<!-- 2) 译文(优先 LLM 排版版) -->
|
||||
<div v-if="showTranslation" style="margin-top: 16px">
|
||||
<NCard v-if="article.body_zh_formatted" class="detail-card">
|
||||
@@ -379,6 +434,16 @@ onMounted(load)
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.commentary-text-waiting-detail {
|
||||
color: var(--color-text-faint);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.commentary-text-failed-detail {
|
||||
color: #d03050;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.article-body-fallback {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.95;
|
||||
|
||||
@@ -99,6 +99,18 @@ function commentaryStatusType(s?: string | null): 'success' | 'warning' | 'error
|
||||
return 'default'
|
||||
}
|
||||
|
||||
// === 评论三态语义 ===
|
||||
// status 'ok' + 有内容 → 显示评论
|
||||
// status 'ok' + 无内容 → 视为等待(防御性,正常不会触发)
|
||||
// status 'pending' / 'n/a' / null → 等待中
|
||||
// status 'failed' → 显示失败提示
|
||||
type CommentaryState = 'ok' | 'waiting' | 'failed'
|
||||
function commentaryState(status?: string | null, content?: string | null): CommentaryState {
|
||||
if (status === 'failed') return 'failed'
|
||||
if (status === 'ok' && content) return 'ok'
|
||||
return 'waiting'
|
||||
}
|
||||
|
||||
// 正文摘要(取 body_zh_text 前 N 字;没有就 fallback 到 summary_zh)
|
||||
function bodyExcerpt(text?: string | null, max = 200): string {
|
||||
if (!text) return ''
|
||||
@@ -224,35 +236,71 @@ onMounted(async () => {
|
||||
{{ bodyExcerpt(a.body_zh_text || a.summary_zh, 220) }}
|
||||
</div>
|
||||
|
||||
<!-- 评论钩子(双 provider:Angel + 美团,淡木色背景 + 木色左边框,与 Android 对齐) -->
|
||||
<!-- 评论钩子(双 provider:Angel + 美团,三态显式显示:有内容 / 等待中 / 失败) -->
|
||||
<div
|
||||
v-if="a.commentary || a.commentary_meituan"
|
||||
v-if="true"
|
||||
class="commentary-box"
|
||||
>
|
||||
<!-- Angel 评论 -->
|
||||
<template v-if="a.commentary">
|
||||
<template>
|
||||
<NSpace align="center" :size="6" style="margin-bottom: 6px">
|
||||
<span class="commentary-label">💬 Angel 评论</span>
|
||||
<NTag size="tiny" :type="commentaryStatusType(a.commentary_status)" round :bordered="false">
|
||||
{{ a.commentary_status || 'n/a' }}
|
||||
<NTag
|
||||
size="tiny"
|
||||
:type="commentaryStatusType(a.commentary_status)"
|
||||
round
|
||||
:bordered="false"
|
||||
>
|
||||
{{
|
||||
commentaryState(a.commentary_status, a.commentary) === 'ok' ? 'ok'
|
||||
: commentaryState(a.commentary_status, a.commentary) === 'failed' ? 'failed'
|
||||
: '等待中'
|
||||
}}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<div class="commentary-text">
|
||||
<div
|
||||
v-if="commentaryState(a.commentary_status, a.commentary) === 'ok'"
|
||||
class="commentary-text"
|
||||
>
|
||||
{{ previewCommentary(a.commentary, 140) }}
|
||||
</div>
|
||||
<div v-else-if="commentaryState(a.commentary_status, a.commentary) === 'failed'" class="commentary-text commentary-text-failed">
|
||||
⚠️ 评论生成失败,后台重试中
|
||||
</div>
|
||||
<div v-else class="commentary-text commentary-text-waiting">
|
||||
🕒 等待评论中
|
||||
</div>
|
||||
</template>
|
||||
<!-- 美团评论 -->
|
||||
<template v-if="a.commentary_meituan">
|
||||
<div v-if="a.commentary" class="commentary-divider" />
|
||||
<template>
|
||||
<div class="commentary-divider" />
|
||||
<NSpace align="center" :size="6" style="margin-bottom: 6px">
|
||||
<span class="commentary-label commentary-label-meituan">🐱 美团评论</span>
|
||||
<NTag size="tiny" :type="commentaryStatusType(a.commentary_meituan_status)" round :bordered="false">
|
||||
{{ a.commentary_meituan_status || 'n/a' }}
|
||||
<NTag
|
||||
size="tiny"
|
||||
:type="commentaryStatusType(a.commentary_meituan_status)"
|
||||
round
|
||||
:bordered="false"
|
||||
>
|
||||
{{
|
||||
commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok' ? 'ok'
|
||||
: commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'failed' ? 'failed'
|
||||
: '等待中'
|
||||
}}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<div class="commentary-text">
|
||||
<div
|
||||
v-if="commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok'"
|
||||
class="commentary-text"
|
||||
>
|
||||
{{ previewCommentary(a.commentary_meituan, 140) }}
|
||||
</div>
|
||||
<div v-else-if="commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'failed'" class="commentary-text commentary-text-failed">
|
||||
⚠️ 评论生成失败,后台重试中
|
||||
</div>
|
||||
<div v-else class="commentary-text commentary-text-waiting">
|
||||
🕒 等待评论中
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</NSpace>
|
||||
@@ -303,6 +351,18 @@ onMounted(async () => {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.commentary-text-waiting {
|
||||
color: var(--color-text-faint);
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.commentary-text-failed {
|
||||
color: #d03050;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.commentary-divider {
|
||||
height: 1px;
|
||||
background: var(--color-primary-soft);
|
||||
|
||||
Reference in New Issue
Block a user