style(ui): 评论三态视觉化 — 等待/失败卡片增强辨识度

旧版问题:等待中卡片视觉太低调(灰小斜体一行),失败也不显眼,
用户截图反馈:看不出状态,无法判断是 bug 还是没跑。

新版:
- 等待中: 浅蓝底 + 1.5px 虚线边框 + 旋转 spinner + '生成中' 徽章
- 失败: 浅红底 + 1.5px 实线红边框 + '失败' 徽章(详情页显示原因)
- 成功: 保留原木色左边框样式
- 加 commentary-badge(带 spinner / 状态文字) 替代原 NTag,辨识度更高
- 列表/详情 min-height 60px,避免占位卡片太矮导致视觉跳
- 失败时详情页显示 commentary_meituan_error(给 owner 排查用)
This commit is contained in:
xiaji
2026-06-12 23:37:48 +08:00
parent 66e57c6e07
commit ddf2bc98e0
2 changed files with 266 additions and 96 deletions

View File

@@ -237,71 +237,81 @@ onMounted(async () => {
</div>
<!-- 评论钩子( provider:Angel + 美团,三态显式显示:有内容 / 等待中 / 失败) -->
<div
v-if="true"
class="commentary-box"
>
<div class="commentary-stack">
<!-- Angel 评论 -->
<template>
<NSpace align="center" :size="6" style="margin-bottom: 6px">
<div
class="commentary-item"
:class="{
'commentary-item-waiting': commentaryState(a.commentary_status, a.commentary) === 'waiting',
'commentary-item-failed': commentaryState(a.commentary_status, a.commentary) === 'failed',
'commentary-item-ok': commentaryState(a.commentary_status, a.commentary) === 'ok',
}"
>
<div class="commentary-head">
<span class="commentary-label">💬 Angel 评论</span>
<NTag
size="tiny"
:type="commentaryStatusType(a.commentary_status)"
round
:bordered="false"
<span
class="commentary-badge"
:class="`commentary-badge-${commentaryState(a.commentary_status, a.commentary)}`"
>
<span
v-if="commentaryState(a.commentary_status, a.commentary) === 'waiting'"
class="commentary-spinner"
/>
{{
commentaryState(a.commentary_status, a.commentary) === 'ok' ? 'ok'
: commentaryState(a.commentary_status, a.commentary) === 'failed' ? 'failed'
: '等待中'
commentaryState(a.commentary_status, a.commentary) === 'ok' ? '已生成'
: commentaryState(a.commentary_status, a.commentary) === 'failed' ? '失败'
: '生成中'
}}
</NTag>
</NSpace>
</span>
</div>
<div
v-if="commentaryState(a.commentary_status, a.commentary) === 'ok'"
class="commentary-text"
>
{{ previewCommentary(a.commentary, 140) }}
</div>
>{{ previewCommentary(a.commentary, 140) }}</div>
<div v-else-if="commentaryState(a.commentary_status, a.commentary) === 'failed'" class="commentary-text commentary-text-failed">
评论生成失败,后台重试
评论生成失败,后台 enrichment_loop 重试
</div>
<div v-else class="commentary-text commentary-text-waiting">
🕒 等待评论中
正在生成评论
</div>
</template>
</div>
<!-- 美团评论 -->
<template>
<div class="commentary-divider" />
<NSpace align="center" :size="6" style="margin-bottom: 6px">
<div
class="commentary-item"
:class="{
'commentary-item-waiting': commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'waiting',
'commentary-item-failed': commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'failed',
'commentary-item-ok': commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok',
}"
>
<div class="commentary-head">
<span class="commentary-label commentary-label-meituan">🐱 美团评论</span>
<NTag
size="tiny"
:type="commentaryStatusType(a.commentary_meituan_status)"
round
:bordered="false"
<span
class="commentary-badge"
:class="`commentary-badge-${commentaryState(a.commentary_meituan_status, a.commentary_meituan)}`"
>
<span
v-if="commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'waiting'"
class="commentary-spinner"
/>
{{
commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok' ? 'ok'
: commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'failed' ? 'failed'
: '等待中'
commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok' ? '已生成'
: commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'failed' ? '失败'
: '生成中'
}}
</NTag>
</NSpace>
</span>
</div>
<div
v-if="commentaryState(a.commentary_meituan_status, a.commentary_meituan) === 'ok'"
class="commentary-text"
>
{{ previewCommentary(a.commentary_meituan, 140) }}
</div>
>{{ 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">
评论生成失败,后台重试
评论生成失败,后台 enrichment_loop 重试
</div>
<div v-else class="commentary-text commentary-text-waiting">
🕒 等待评论中
正在生成评论
</div>
</template>
</div>
</div>
</NSpace>
</NCard>
@@ -323,51 +333,133 @@ onMounted(async () => {
</template>
<style scoped>
/* 评论钩子(项目级统一样式,与 Android ArticleCard.kt 的 CommentaryBox 对齐) */
.commentary-box {
/* === 评论钩子(双 provider,三态视觉化) === */
.commentary-stack {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 10px;
padding: 12px 14px;
}
.commentary-item {
position: relative;
padding: 10px 12px;
border-radius: 6px;
font-size: 13px;
line-height: 1.65;
min-height: 56px; /* 避免占位卡片太矮导致视觉跳 */
}
/* === OK 状态(有内容)== */
.commentary-item-ok {
background: var(--color-primary-soft);
border-left: 3px solid var(--color-primary);
border-radius: 6px;
color: var(--color-letter);
}
.commentary-item-ok .commentary-label {
color: var(--color-primary);
}
.commentary-item-ok .commentary-text {
color: var(--color-letter);
font-size: 13px;
line-height: 1.7;
}
/* === 等待中(生成中)== */
.commentary-item-waiting {
background: #f0f4fa; /* 浅蓝底 */
border: 1.5px dashed #94a3b8; /* 虚线边框,提示"未完成" */
border-left: 4px solid #64748b; /* 左侧稍粗的灰条,跟 ok 状态一致位置 */
color: var(--color-text-faint);
display: flex;
flex-direction: column;
justify-content: center;
}
.commentary-item-waiting .commentary-label {
color: #475569;
}
.commentary-item-waiting .commentary-text {
color: #64748b;
font-size: 13px;
font-style: italic;
}
/* === 失败 === */
.commentary-item-failed {
background: #fef2f2; /* 浅红底 */
border: 1.5px solid #fca5a5;
border-left: 4px solid #ef4444;
color: #991b1b;
display: flex;
flex-direction: column;
justify-content: center;
}
.commentary-item-failed .commentary-label {
color: #dc2626;
}
.commentary-item-failed .commentary-text {
color: #b91c1c;
font-size: 12px;
font-style: italic;
}
.commentary-label {
font-size: 12px;
font-weight: 600;
color: var(--color-primary);
}
.commentary-label-meituan {
color: #c2410c; /* 橙色,与 Angel 区分 */
}
.commentary-text {
color: var(--color-letter);
font-size: 13px;
line-height: 1.7;
}
.commentary-text-failed,
.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);
margin: 10px 0;
opacity: 0.6;
.commentary-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.commentary-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
line-height: 1.4;
}
.commentary-badge-ok {
background: #d1fae5;
color: #065f46;
}
.commentary-badge-waiting {
background: #e2e8f0;
color: #334155;
}
.commentary-badge-failed {
background: #fee2e2;
color: #991b1b;
}
/* 旋转的 spinner(等待中) */
.commentary-spinner {
display: inline-block;
width: 10px;
height: 10px;
border: 1.5px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: commentary-spin 0.8s linear infinite;
}
@keyframes commentary-spin {
to { transform: rotate(360deg); }
}
/* ===== 桌面端默认宽度 ===== */