From 3ab6e4c7d03e32bfcd2ec223c12e2b3417af847e Mon Sep 17 00:00:00 2001 From: Mavis Date: Thu, 11 Jun 2026 23:54:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(detail):=20=E5=8E=9F=E5=A7=8B=E8=AF=91?= =?UTF-8?q?=E6=96=87/=E5=8E=9F=E6=96=87=E6=A0=87=E7=AD=BE=E5=AD=97?= =?UTF-8?q?=E5=8F=B7=2016=E2=86=9219px,=E5=8A=A0=E6=AE=B5=E8=90=BD?= =?UTF-8?q?=E5=88=87=E5=88=86+=E8=A1=8C=E9=AB=98=201.95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:'文章译文(原始)' 和 '文章原文' 标签下,内容是 body_zh_text / body_text 一坨没分段, 走 .article-body-fallback 16px,排版难看。 修复: 1) .article-body-fallback 字号 16→19,行高 1.85→1.95,加 max-width 720px 居中 + 0.02em letter-spacing 2) 加 splitIntoParagraphs: 已经是 HTML 标签的不动;否则按 \n 切;一坨纯文字按 [。!?!?] 切 3) 段首 text-indent 2em(中文书报排版) 4) translationBody / originalBody computed 替代内联三元 LLM 排版过的 (body_zh_formatted) 已经

段标签,不受影响。 --- frontend/src/views/ArticleDetail.vue | 86 ++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/frontend/src/views/ArticleDetail.vue b/frontend/src/views/ArticleDetail.vue index 9336800..484a03f 100644 --- a/frontend/src/views/ArticleDetail.vue +++ b/frontend/src/views/ArticleDetail.vue @@ -64,6 +64,64 @@ const publishedAt = computed(() => article.value?.published_at || article.value? const isOwner = computed(() => auth.isOwner) const categories = computed(() => (article.value?.category || '').split(',').filter(Boolean)) +/** 把"一坨"译文/原文按"中文句号"切成

段,改善"挤在一起"的观感。 + * 优先按 \n 切(LLM 排版过的),没有换行再按句号/问号/感叹号切。 + * 句中常见的"Mr./U.S."等缩写不会出现在中文译文里,按 6+ 字符才切,避免半句话被切。 + */ +function splitIntoParagraphs(html: string): string { + // 已经是 HTML 标签(LLM 排版过),不动 + if (/<(p|div|br)\b/i.test(html)) return html + // 按 \n 切 + if (/\n/.test(html)) { + return html + .split(/\n+/) + .map((p) => p.trim()) + .filter(Boolean) + .map((p) => `

${escapeHtml(p)}

`) + .join('') + } + // 一坨纯文字:按句号/问号/感叹号切,每段最多 140 字 + const text = html.trim() + if (!text) return '' + const chunks: string[] = [] + const re = /([。!?!?])(?=[^。!?!?]{0,140})/g + let last = 0 + let m: RegExpExecArray | null + while ((m = re.exec(text)) !== null) { + const end = m.index + 1 + if (end - last > 6) { + chunks.push(text.slice(last, end)) + last = end + } + } + if (last < text.length) chunks.push(text.slice(last)) + return chunks.map((p) => `

${escapeHtml(p.trim())}

`).join('') +} + +function escapeHtml(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') +} + +const translationBody = computed(() => { + const a = article.value + if (!a) return '' + if (a.body_zh_formatted) return a.body_zh_formatted + if (a.body_zh_html) return splitIntoParagraphs(a.body_zh_html) + if (a.body_zh_text) return splitIntoParagraphs(a.body_zh_text) + return '' +}) + +const originalBody = computed(() => { + const a = article.value + if (!a) return '' + if (a.body_html) return splitIntoParagraphs(a.body_html) + if (a.body_text) return splitIntoParagraphs(a.body_text) + return '' +}) + async function rerunTranslation() { if (!article.value) return if (!confirm('重新翻译会消耗配额,确认?')) return @@ -234,8 +292,7 @@ onMounted(load) -
-
{{ article.body_zh_text }}
+
暂无译文
@@ -254,8 +311,7 @@ onMounted(load) -
-
{{ article.body_text }}
+
@@ -293,10 +349,28 @@ onMounted(load) .article-body-fallback { white-space: pre-wrap; - line-height: 1.85; + line-height: 1.95; color: var(--color-letter); - font-size: 16px; + font-size: 19px; font-family: var(--font-sans); + max-width: 720px; + margin: 0 auto; + letter-spacing: 0.02em; + text-align: justify; + text-justify: inter-ideograph; +} + +/* 原始译文里的"中文句号"或"换行"在浏览器里经常被压成一坨, + 强制段间加空行 + 段首缩进 2 字符,接近纸质书阅读感 */ +.article-body-fallback :deep(p), +.article-body-fallback :deep(div) { + margin: 0 0 1.1em 0; + text-indent: 2em; +} + +.article-body-fallback :deep(p:last-child), +.article-body-fallback :deep(div:last-child) { + margin-bottom: 0; } .article-image {