style(web): 改暖色木色调,跟 Android 端对齐
Web 端原本是冷色蓝调(#2080f0 + #fafbfc), Android 端已经是暖色木色(#8B6B45 + #F5E9D0),两端不一致。 统一到暖色木色调(跟 logo "D" 木质方块的视觉延伸): style.css 重写: - CSS 变量: --color-primary #8B6B45, --color-bg #F5E9D0 等 - 字体栈: serif 用于标题(Georgia / Songti), sans-serif 用于正文 - 卡片: 圆角 12px, 边框 + 浅阴影, hover 浮起 1px - 滚动条: 木色风格 - 按钮 / 分页 / TopBar 主题色统一 Feed.vue: - 中文标题字体 18px -> 20px, 字重 600 -> 700 - 原标题字号 13, 颜色淡木色 - 插图圆角 4px -> 8px, 高度自适应 max 280px - 评论钩子: 淡木色背景 + 木色左边框 3px(与 Android 一致) - 标签全部用 round ArticleDetail.vue: - 中文主标题大字号, 衬线粗体 - 原标题灰色辅助 - 操作按钮全部 round - 卡片标题统一用 serif Login.vue: - 登录卡 圆角 16px, 木色渐变背景 - 标题用 serif, 按钮 round 未提交 Android 端改动 — 在 D:/selftools/diary-news-android/ 独立目录, 会重新 build APK 后单独交付。
This commit is contained in:
@@ -1,10 +1,46 @@
|
||||
/* ============================================================
|
||||
* Diary News · Web 全局样式
|
||||
*
|
||||
* 主题色(与 Android 端对齐):暖色木色调
|
||||
* --color-primary: #8B6B45 木色 primary
|
||||
* --color-bg: #F5E9D0 米色背景
|
||||
* --color-surface: #FBEFD7 卡片表面(略浅米色)
|
||||
* --color-letter: #3E2A1E 深棕文字
|
||||
* --color-text-muted: #8B6B45 暗木色(副标题/时间)
|
||||
* --color-divider: #E8D4A8 中木色分隔
|
||||
*
|
||||
* 设计原则:
|
||||
* - 标题用 serif(衬线感,与木质积木字"D"呼应)
|
||||
* - 正文用 sans-serif,行高 1.7 阅读舒适
|
||||
* - 评论钩子统一为淡木色背景 + 木色左边框
|
||||
* ============================================================ */
|
||||
|
||||
:root {
|
||||
--max-width: 1200px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
|
||||
/* 主题色板 */
|
||||
--color-primary: #8B6B45;
|
||||
--color-primary-hover: #6B4F30;
|
||||
--color-primary-soft: rgba(139, 107, 69, 0.12);
|
||||
--color-bg: #F5E9D0;
|
||||
--color-bg-soft: #FBEFD7;
|
||||
--color-surface: #FBEFD7;
|
||||
--color-surface-variant: #E8D4A8;
|
||||
--color-letter: #3E2A1E;
|
||||
--color-text-muted: #8B6B45;
|
||||
--color-text-faint: #A8825A;
|
||||
--color-divider: #E8D4A8;
|
||||
--color-link: #8B6B45;
|
||||
|
||||
/* 字体栈 */
|
||||
--font-serif: Georgia, 'Times New Roman', 'Songti SC', 'Source Han Serif SC', serif;
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
|
||||
'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Roboto, sans-serif;
|
||||
|
||||
font-family: var(--font-sans);
|
||||
line-height: 1.6;
|
||||
color: #1f2328;
|
||||
background: #fafbfc;
|
||||
color: var(--color-letter);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
@@ -13,31 +49,51 @@ html, body, #app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: #2080f0; }
|
||||
a { color: var(--color-link); text-decoration: none; transition: color 0.15s; }
|
||||
a:hover { color: var(--color-primary-hover); }
|
||||
|
||||
img { max-width: 100%; }
|
||||
|
||||
.n-card.article-card {
|
||||
margin-bottom: 16px;
|
||||
transition: box-shadow 0.15s;
|
||||
}
|
||||
.n-card.article-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
/* ===== 排版基础 ===== */
|
||||
h1, h2, h3, h4 {
|
||||
font-family: var(--font-serif);
|
||||
color: var(--color-letter);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* === 文章正文容器(项目级固定 CSS,排版版译文用)===
|
||||
h1 { font-size: 28px; font-weight: 700; line-height: 1.3; }
|
||||
h2 { font-size: 22px; font-weight: 700; line-height: 1.35; }
|
||||
h3 { font-size: 18px; font-weight: 700; line-height: 1.4; }
|
||||
|
||||
/* ===== 文章卡片 ===== */
|
||||
.n-card.article-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 12px !important;
|
||||
border: 1px solid var(--color-divider) !important;
|
||||
background: var(--color-surface) !important;
|
||||
transition: box-shadow 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
.n-card.article-card:hover {
|
||||
box-shadow: 0 6px 16px rgba(107, 79, 48, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.n-card.article-card .n-card__content {
|
||||
padding: 16px 18px !important;
|
||||
}
|
||||
|
||||
/* ===== 文章正文容器(项目级固定 CSS,排版版译文用)===
|
||||
* 后端 enrichment 也会在容器 div 上内联同样的属性;这里做兜底,
|
||||
* 万一 inline style 被 sanitizer 剥掉,样式仍生效。
|
||||
* 与 backend/app/services/llm/enrichment.py 里的常量保持一致。
|
||||
*/
|
||||
.article-body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
color: #3e3e3e;
|
||||
color: var(--color-letter);
|
||||
}
|
||||
.article-body p {
|
||||
margin: 0 0 1.5em 0;
|
||||
@@ -46,15 +102,60 @@ img { max-width: 100%; }
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* === 排版段落(.diary-para)兜底规则 ===
|
||||
* 后端 enrichment._wrap_article_body 会内联 style 到 <p class="diary-para">;
|
||||
* 这里做兜底,保证 v-html 渲染时一定有合理的行距和段距。
|
||||
*/
|
||||
/* ===== 排版段落(.diary-para)兜底规则 ===== */
|
||||
.diary-para {
|
||||
margin: 0 0 1.5em 0;
|
||||
line-height: 1.7;
|
||||
color: #3e3e3e;
|
||||
color: var(--color-letter);
|
||||
font-size: 17px;
|
||||
}
|
||||
.diary-para:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ===== TopBar / NavBar 主题色 ===== */
|
||||
.n-layout-header,
|
||||
.n-layout-header.n-layout-header--absolute-positioned {
|
||||
background: var(--color-primary) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ===== Button 主按钮(主题色)===== */
|
||||
.n-button.n-button--primary-type:not(.n-button--disabled) {
|
||||
background: var(--color-primary) !important;
|
||||
color: #fff !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
}
|
||||
.n-button.n-button--primary-type:not(.n-button--disabled):hover {
|
||||
background: var(--color-primary-hover) !important;
|
||||
border-color: var(--color-primary-hover) !important;
|
||||
}
|
||||
|
||||
/* ===== Pagination 当前页 ===== */
|
||||
.n-pagination .n-pagination-item--active {
|
||||
background: var(--color-primary) !important;
|
||||
color: #fff !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
/* ===== 空状态 / Loading 文字颜色 ===== */
|
||||
.n-empty .n-empty__description,
|
||||
.n-skeleton {
|
||||
color: var(--color-text-muted) !important;
|
||||
}
|
||||
|
||||
/* ===== 全局滚动条(木色风格)===== */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-surface-variant);
|
||||
border-radius: 5px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-soft);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
NCard, NSpace, NTag, NText, NButton, NSpin, NEmpty, NDivider, NAlert, NSkeleton, NImage, useMessage,
|
||||
NCard, NSpace, NTag, NText, NButton, NSpin, NEmpty, NAlert, NSkeleton, NImage, useMessage,
|
||||
} from 'naive-ui'
|
||||
import { articlesApi, adminApi, type ArticleDetail } from '@/api/articles'
|
||||
import { http } from '@/api/client'
|
||||
@@ -103,51 +103,92 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical>
|
||||
<NButton text @click="router.back()">← 返回</NButton>
|
||||
<NSpace vertical :size="16">
|
||||
<NButton text @click="router.back()" class="back-button">← 返回</NButton>
|
||||
|
||||
<NSpin :show="loading">
|
||||
<NSkeleton v-if="loading" :repeat="6" />
|
||||
<NEmpty v-else-if="!article" description="文章不存在" />
|
||||
<div v-else>
|
||||
<NCard>
|
||||
<NSpace vertical :size="8">
|
||||
<NSpace align="center">
|
||||
<NTag type="info">{{ article.source.name }}</NTag>
|
||||
<NText depth="3" style="font-size: 12px">{{ fmtTime(publishedAt) }}</NText>
|
||||
<NTag v-if="article.translation_status !== 'ok'" size="small" type="warning">
|
||||
<!-- 顶部信息卡 -->
|
||||
<NCard class="article-detail-card">
|
||||
<NSpace vertical :size="14">
|
||||
<!-- tag 行 -->
|
||||
<NSpace align="center" :size="6" :wrap="false" style="overflow: hidden">
|
||||
<NTag type="primary" :bordered="false" round>{{ article.source.name }}</NTag>
|
||||
<NTag v-if="article.lang_src" :bordered="false" round>{{ article.lang_src.toUpperCase() }}</NTag>
|
||||
<NTag v-if="article.translation_status !== 'ok'" size="small" type="warning" :bordered="false" round>
|
||||
翻译:{{ article.translation_status }}
|
||||
</NTag>
|
||||
<NTag v-if="article.translation_engine" size="small">
|
||||
<NTag v-if="article.translation_engine" size="small" :bordered="false" round>
|
||||
{{ article.translation_engine }}
|
||||
</NTag>
|
||||
<NTag v-for="c in categories" :key="c" type="success" size="small">{{ c }}</NTag>
|
||||
<NTag v-for="c in categories" :key="c" type="success" size="small" :bordered="false" round>
|
||||
{{ c }}
|
||||
</NTag>
|
||||
<NText :depth="3" style="font-size: 12px; margin-left: auto">
|
||||
{{ fmtTime(publishedAt) }}
|
||||
</NText>
|
||||
</NSpace>
|
||||
<h1 style="margin: 0">{{ article.title }}</h1>
|
||||
<h2 v-if="article.title_zh" style="margin: 0; color: #2080f0">{{ article.title_zh }}</h2>
|
||||
<NSpace>
|
||||
<NButton :type="starred ? 'warning' : 'default'" @click="toggleStar">
|
||||
|
||||
<!-- 主标题(中文) -->
|
||||
<h1 v-if="article.title_zh" style="margin: 0; color: var(--color-letter)">
|
||||
{{ article.title_zh }}
|
||||
</h1>
|
||||
<h2 v-else style="margin: 0; color: var(--color-letter)">
|
||||
{{ article.title }}
|
||||
</h2>
|
||||
|
||||
<!-- 原标题(英文/外文) -->
|
||||
<div
|
||||
v-if="article.title_zh && article.title"
|
||||
style="font-size: 14px; color: var(--color-text-faint); line-height: 1.5;"
|
||||
>
|
||||
{{ article.title }}
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮行 -->
|
||||
<NSpace :size="8" :wrap="false">
|
||||
<NButton
|
||||
:type="starred ? 'warning' : 'primary'"
|
||||
:ghost="!starred"
|
||||
@click="toggleStar"
|
||||
round
|
||||
>
|
||||
{{ starred ? '★ 已收藏' : '☆ 收藏' }}
|
||||
</NButton>
|
||||
<NButton text @click="showOriginal = !showOriginal">
|
||||
<NButton text @click="showOriginal = !showOriginal" round>
|
||||
{{ showOriginal ? '隐藏原文' : '显示原文' }}
|
||||
</NButton>
|
||||
<NButton text @click="showTranslation = !showTranslation">
|
||||
<NButton text @click="showTranslation = !showTranslation" round>
|
||||
{{ showTranslation ? '隐藏译文' : '显示译文' }}
|
||||
</NButton>
|
||||
<NButton v-if="isOwner" type="error" ghost @click="rerunTranslation">
|
||||
<NButton v-if="isOwner" type="error" ghost @click="rerunTranslation" round>
|
||||
重译
|
||||
</NButton>
|
||||
<NButton v-if="isOwner" type="info" ghost :loading="enriching" @click="triggerEnrich">
|
||||
<NButton v-if="isOwner" type="info" ghost :loading="enriching" @click="triggerEnrich" round>
|
||||
跑 LLM 增强
|
||||
</NButton>
|
||||
<NButton tag="a" :href="article.url" target="_blank" rel="noopener">原文链接 ↗</NButton>
|
||||
<NButton tag="a" :href="article.url" target="_blank" rel="noopener" ghost round>
|
||||
原文链接 ↗
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<!-- owner 专属:LLM 状态 -->
|
||||
<NSpace v-if="isOwner" size="small" align="center">
|
||||
<NText depth="3" style="font-size: 12px">LLM 状态:</NText>
|
||||
<NTag size="tiny" :type="statusTagType(article.format_status)">排版:{{ article.format_status || 'n/a' }}</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.classify_status)">分类:{{ article.classify_status || 'n/a' }}</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.image_ai_status)">插图:{{ article.image_ai_status || 'n/a' }}</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_status)">点评:{{ article.commentary_status || 'n/a' }}</NTag>
|
||||
<NText :depth="3" style="font-size: 12px">LLM 状态:</NText>
|
||||
<NTag size="tiny" :type="statusTagType(article.format_status)" :bordered="false" round>
|
||||
排版:{{ article.format_status || 'n/a' }}
|
||||
</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.classify_status)" :bordered="false" round>
|
||||
分类:{{ article.classify_status || 'n/a' }}
|
||||
</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.image_ai_status)" :bordered="false" round>
|
||||
插图:{{ article.image_ai_status || 'n/a' }}
|
||||
</NTag>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_status)" :bordered="false" round>
|
||||
点评:{{ article.commentary_status || 'n/a' }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
@@ -156,58 +197,111 @@ onMounted(load)
|
||||
本条翻译失败,可点 "重译" 重试,或查看后端日志。
|
||||
</NAlert>
|
||||
|
||||
<!-- 三段式:评论(顶部) / 译文(中) / 原文(底) -->
|
||||
|
||||
<!-- 1) 评论(LLM 点评) -->
|
||||
<NCard v-if="article.commentary" style="margin-top: 16px" title="💬 评论">
|
||||
<template #header-extra>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_status)">{{ article.commentary_status || 'n/a' }}</NTag>
|
||||
<NCard v-if="article.commentary" class="detail-card" style="margin-top: 16px">
|
||||
<template #header>
|
||||
<span class="card-header-title">💬 评论</span>
|
||||
</template>
|
||||
<p style="white-space: pre-wrap; line-height: 1.8; margin: 0">{{ article.commentary }}</p>
|
||||
<template #header-extra>
|
||||
<NTag size="tiny" :type="statusTagType(article.commentary_status)" :bordered="false" round>
|
||||
{{ article.commentary_status || 'n/a' }}
|
||||
</NTag>
|
||||
</template>
|
||||
<p class="commentary-text-detail">{{ article.commentary }}</p>
|
||||
</NCard>
|
||||
|
||||
<!-- 2) 译文(优先 LLM 排版版,fallback 原始译文) -->
|
||||
<!-- 2) 译文(优先 LLM 排版版) -->
|
||||
<div v-if="showTranslation" style="margin-top: 16px">
|
||||
<NCard v-if="article.body_zh_formatted" title="📖 文章译文">
|
||||
<NCard v-if="article.body_zh_formatted" class="detail-card">
|
||||
<template #header>
|
||||
<span class="card-header-title">📖 文章译文</span>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NSpace align="center">
|
||||
<NTag size="tiny" :type="statusTagType(article.format_status)">排版:{{ article.format_status || 'n/a' }}</NTag>
|
||||
<NButton text size="tiny" @click="showFormatted = !showFormatted">
|
||||
<NSpace align="center" :size="4">
|
||||
<NTag size="tiny" :type="statusTagType(article.format_status)" :bordered="false" round>
|
||||
排版:{{ article.format_status || 'n/a' }}
|
||||
</NTag>
|
||||
<NButton text size="tiny" @click="showFormatted = !showFormatted" round>
|
||||
{{ showFormatted ? '隐藏排版' : '显示排版' }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
<div v-if="showFormatted" v-html="article.body_zh_formatted" />
|
||||
<NText v-else depth="3" style="font-size: 12px">已隐藏排版版(点击右上角显示)</NText>
|
||||
<NText v-else :depth="3" style="font-size: 12px">已隐藏排版版(点击右上角显示)</NText>
|
||||
</NCard>
|
||||
<NCard v-else title="📖 文章译文(原始)" style="margin-top: 16px">
|
||||
<div v-if="article.body_zh_html" v-html="article.body_zh_html" style="line-height: 1.8" />
|
||||
<div v-else-if="article.body_zh_text" style="white-space: pre-wrap; line-height: 1.8">
|
||||
{{ article.body_zh_text }}
|
||||
</div>
|
||||
<NText v-else depth="3">暂无译文</NText>
|
||||
|
||||
<NCard v-else class="detail-card">
|
||||
<template #header>
|
||||
<span class="card-header-title">📖 文章译文(原始)</span>
|
||||
</template>
|
||||
<div v-if="article.body_zh_html" v-html="article.body_zh_html" />
|
||||
<div v-else-if="article.body_zh_text" class="article-body-fallback">{{ article.body_zh_text }}</div>
|
||||
<NText v-else :depth="3">暂无译文</NText>
|
||||
</NCard>
|
||||
</div>
|
||||
|
||||
<!-- AI 插图(挂在译文卡片下,作附属) -->
|
||||
<div v-if="article.image_ai_url" style="margin-top: 16px">
|
||||
<NCard title="🎨 AI 插图">
|
||||
<NImage :src="article.image_ai_url" object-fit="cover" style="max-width: 100%; border-radius: 6px" />
|
||||
<!-- AI 插图 -->
|
||||
<NCard v-if="article.image_ai_url" class="detail-card" style="margin-top: 16px">
|
||||
<template #header>
|
||||
<span class="card-header-title">🎨 AI 插图</span>
|
||||
</template>
|
||||
<NImage :src="article.image_ai_url" object-fit="cover" class="article-image" />
|
||||
</NCard>
|
||||
</div>
|
||||
|
||||
<!-- 3) 原文 -->
|
||||
<div v-if="showOriginal" style="margin-top: 16px">
|
||||
<NCard title="📄 文章原文">
|
||||
<div v-if="article.body_html" v-html="article.body_html" style="line-height: 1.8" />
|
||||
<div v-else style="white-space: pre-wrap; line-height: 1.8">{{ article.body_text }}</div>
|
||||
<NCard class="detail-card">
|
||||
<template #header>
|
||||
<span class="card-header-title">📄 文章原文</span>
|
||||
</template>
|
||||
<div v-if="article.body_html" v-html="article.body_html" class="article-body-fallback" />
|
||||
<div v-else class="article-body-fallback">{{ article.body_text }}</div>
|
||||
</NCard>
|
||||
</div>
|
||||
|
||||
<NCard v-if="article.entities" style="margin-top: 16px" title="🔍 实体(预留)">
|
||||
<NCard v-if="article.entities" class="detail-card" style="margin-top: 16px">
|
||||
<template #header>
|
||||
<span class="card-header-title">🔍 实体(预留)</span>
|
||||
</template>
|
||||
<code style="font-size: 12px">{{ JSON.stringify(article.entities) }}</code>
|
||||
</NCard>
|
||||
</div>
|
||||
</NSpin>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.back-button {
|
||||
font-size: 14px;
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.card-header-title {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: var(--color-letter);
|
||||
}
|
||||
|
||||
.commentary-text-detail {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.85;
|
||||
margin: 0;
|
||||
color: var(--color-letter);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.article-body-fallback {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.85;
|
||||
color: var(--color-letter);
|
||||
font-size: 16px;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.article-image {
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
background: var(--color-surface-variant);
|
||||
}
|
||||
</style>
|
||||
@@ -115,9 +115,9 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical>
|
||||
<NSpace vertical :size="16">
|
||||
<NSpace align="center" justify="space-between">
|
||||
<NSpace>
|
||||
<NSpace :size="10">
|
||||
<NSelect
|
||||
v-model:value="sourceFilter"
|
||||
multiple
|
||||
@@ -127,11 +127,11 @@ onMounted(async () => {
|
||||
style="min-width: 240px"
|
||||
@update:value="resetToFirstPage"
|
||||
/>
|
||||
<NInput v-model:value="q" placeholder="关键词搜索" clearable style="width: 200px"
|
||||
<NInput v-model:value="q" placeholder="关键词搜索" clearable style="width: 220px"
|
||||
@keyup.enter="resetToFirstPage" @clear="resetToFirstPage" />
|
||||
<NButton @click="resetToFirstPage">刷新</NButton>
|
||||
<NButton type="primary" @click="resetToFirstPage">刷新</NButton>
|
||||
</NSpace>
|
||||
<NText depth="3">{{ itemsLabel }}</NText>
|
||||
<NText :depth="3" style="font-size: 13px">{{ itemsLabel }}</NText>
|
||||
</NSpace>
|
||||
|
||||
<NSpin :show="loading && items.length === 0">
|
||||
@@ -145,11 +145,16 @@ onMounted(async () => {
|
||||
hoverable
|
||||
@click="open(a)"
|
||||
>
|
||||
<NSpace vertical :size="6">
|
||||
<NSpace align="center" :size="8">
|
||||
<NTag size="small" type="info">{{ a.source.name }}</NTag>
|
||||
<NTag v-if="a.lang_src" size="small">{{ a.lang_src }}</NTag>
|
||||
<NTag v-if="a.translation_status !== 'ok'" size="small" type="warning">
|
||||
<NSpace vertical :size="10">
|
||||
<!-- 顶行:源 / 语言 / 分类 tag / 时间 -->
|
||||
<NSpace align="center" :size="6" :wrap="false" style="overflow: hidden">
|
||||
<NTag size="small" type="primary" :bordered="false" round>
|
||||
{{ a.source.name }}
|
||||
</NTag>
|
||||
<NTag v-if="a.lang_src" size="small" round :bordered="false">
|
||||
{{ a.lang_src.toUpperCase() }}
|
||||
</NTag>
|
||||
<NTag v-if="a.translation_status !== 'ok'" size="small" type="warning" :bordered="false" round>
|
||||
{{ a.translation_status }}
|
||||
</NTag>
|
||||
<NTag
|
||||
@@ -157,77 +162,88 @@ onMounted(async () => {
|
||||
:key="c"
|
||||
size="small"
|
||||
type="success"
|
||||
:bordered="false"
|
||||
round
|
||||
>
|
||||
{{ c }}
|
||||
</NTag>
|
||||
<NText depth="3" style="font-size: 12px">{{ fmtTime(a.published_at || a.fetched_at) }}</NText>
|
||||
<NText :depth="3" style="font-size: 12px; margin-left: auto">
|
||||
{{ fmtTime(a.published_at || a.fetched_at) }}
|
||||
</NText>
|
||||
</NSpace>
|
||||
|
||||
<!-- 原标题(灰色,辅助) -->
|
||||
<div style="font-size: 13px; color: #999; line-height: 1.4;">
|
||||
{{ a.title }}
|
||||
</div>
|
||||
<!-- 中文标题(主标题) -->
|
||||
<div v-if="a.title_zh" style="font-size: 18px; font-weight: 600; color: #333; line-height: 1.4;">
|
||||
<!-- 中文标题(主) -->
|
||||
<div
|
||||
v-if="a.title_zh"
|
||||
style="
|
||||
font-family: var(--font-serif);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--color-letter);
|
||||
line-height: 1.4;
|
||||
"
|
||||
>
|
||||
{{ a.title_zh }}
|
||||
</div>
|
||||
|
||||
<!-- 原标题(灰色,辅助) -->
|
||||
<div
|
||||
v-if="a.title"
|
||||
style="font-size: 13px; color: var(--color-text-faint); line-height: 1.4;"
|
||||
>
|
||||
{{ a.title }}
|
||||
</div>
|
||||
|
||||
<!-- AI 插图(若有) -->
|
||||
<img
|
||||
v-if="a.image_ai_url || a.image_url"
|
||||
:src="a.image_ai_url || a.image_url || ''"
|
||||
style="
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
max-height: 280px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
margin: 4px 0;
|
||||
background: var(--color-surface-variant);
|
||||
"
|
||||
referrerpolicy="no-referrer"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- 翻译后正文摘要(列表钩子,详情页有完整版) -->
|
||||
<!-- 翻译后正文摘要 -->
|
||||
<div
|
||||
v-if="a.body_zh_text || a.summary_zh"
|
||||
style="
|
||||
margin-top: 6px;
|
||||
color: #444;
|
||||
margin-top: 4px;
|
||||
color: var(--color-letter);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
line-height: 1.75;
|
||||
"
|
||||
>
|
||||
{{ bodyExcerpt(a.body_zh_text || a.summary_zh, 220) }}
|
||||
</div>
|
||||
|
||||
<!-- 评论预览 -->
|
||||
<!-- 评论钩子(淡木色背景 + 木色左边框,与 Android 对齐) -->
|
||||
<div
|
||||
v-if="a.commentary"
|
||||
style="
|
||||
margin-top: 8px;
|
||||
padding: 8px 10px;
|
||||
background: #f6f8ff;
|
||||
border-left: 3px solid #2080f0;
|
||||
border-radius: 4px;
|
||||
color: #444;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
"
|
||||
class="commentary-box"
|
||||
>
|
||||
<NSpace align="center" :size="6" style="margin-bottom: 4px">
|
||||
<NText depth="2" style="font-size: 12px; font-weight: 600">💬 评论</NText>
|
||||
<NTag size="tiny" :type="commentaryStatusType(a.commentary_status)">
|
||||
<NSpace align="center" :size="6" style="margin-bottom: 6px">
|
||||
<span class="commentary-label">💬 评论</span>
|
||||
<NTag size="tiny" :type="commentaryStatusType(a.commentary_status)" round :bordered="false">
|
||||
{{ a.commentary_status || 'n/a' }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<span>{{ previewCommentary(a.commentary, 140) }}</span>
|
||||
<div class="commentary-text">
|
||||
{{ previewCommentary(a.commentary, 140) }}
|
||||
</div>
|
||||
</div>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
|
||||
<!-- 页码分页(替代无限滚动) -->
|
||||
<NSpace v-if="total > 0" justify="center" style="margin: 24px 0 16px">
|
||||
<!-- 页码分页 -->
|
||||
<NSpace v-if="total > 0" justify="center" style="margin: 32px 0 24px">
|
||||
<NPagination
|
||||
v-model:page="page"
|
||||
:page-count="totalPages"
|
||||
@@ -236,8 +252,34 @@ onMounted(async () => {
|
||||
@update:page="onPageChange"
|
||||
/>
|
||||
</NSpace>
|
||||
<NText v-else depth="3" style="display:block; text-align:center; padding: 16px">— 暂无数据 —</NText>
|
||||
<NText v-else :depth="3" style="display:block; text-align:center; padding: 16px">— 暂无数据 —</NText>
|
||||
</div>
|
||||
</NSpin>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 评论钩子(项目级统一样式,与 Android ArticleCard.kt 的 CommentaryBox 对齐) */
|
||||
.commentary-box {
|
||||
margin-top: 10px;
|
||||
padding: 12px 14px;
|
||||
background: var(--color-primary-soft);
|
||||
border-left: 3px solid var(--color-primary);
|
||||
border-radius: 6px;
|
||||
color: var(--color-letter);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.commentary-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.commentary-text {
|
||||
color: var(--color-letter);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
@@ -35,8 +35,14 @@ async function submit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);">
|
||||
<NCard title="📚 Diary News" style="width: 380px;">
|
||||
<div class="login-page">
|
||||
<NCard class="login-card">
|
||||
<template #header>
|
||||
<span class="login-title">📚 Diary News</span>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<span class="login-subtitle">私人新闻日报</span>
|
||||
</template>
|
||||
<NForm @submit.prevent="submit">
|
||||
<NFormItem label="账号">
|
||||
<NInput v-model:value="username" placeholder="owner" autofocus />
|
||||
@@ -47,9 +53,11 @@ async function submit() {
|
||||
<NAlert v-if="error" type="error" :show-icon="false" style="margin-bottom: 12px">
|
||||
{{ error }}
|
||||
</NAlert>
|
||||
<NSpace vertical>
|
||||
<NButton type="primary" block :loading="loading" @click="submit">登录</NButton>
|
||||
<NText depth="3" style="font-size: 12px">
|
||||
<NSpace vertical :size="14">
|
||||
<NButton type="primary" block :loading="loading" @click="submit" round size="large">
|
||||
登录
|
||||
</NButton>
|
||||
<NText :depth="3" style="font-size: 12px; text-align: center; display: block">
|
||||
私人系统 · 仅授权用户
|
||||
</NText>
|
||||
</NSpace>
|
||||
@@ -57,3 +65,33 @@ async function submit() {
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #F5E9D0 0%, #C9A876 100%);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 380px;
|
||||
border-radius: 16px !important;
|
||||
background: var(--color-bg-soft) !important;
|
||||
border: 1px solid var(--color-divider) !important;
|
||||
box-shadow: 0 8px 32px rgba(107, 79, 48, 0.15);
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 700;
|
||||
font-size: 22px;
|
||||
color: var(--color-letter);
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user