From 3309818160392feb9c44d864a57b49dff9c831eb Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:37:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(manuscripts):=20=E6=AD=A3=E6=96=87?= =?UTF-8?q?=E6=8C=89=E5=90=8E=E7=AB=AF=20contentFormat=20=E6=B8=B2?= =?UTF-8?q?=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - content-format 增加 normalizeFormat;toPlainText / getRenderPayload 支持显式格式 - 列表行与阅读抽屉透传 contentFormat,字段缺失时仍回退到按内容猜 --- .../manuscript-reader/manuscript-reader.vue | 11 ++- src/packageMine/manuscripts.vue | 9 +- src/utils/content-format.js | 85 +++++++++++++++---- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/components/manuscript-reader/manuscript-reader.vue b/src/components/manuscript-reader/manuscript-reader.vue index ba09c9e..ee95c00 100644 --- a/src/components/manuscript-reader/manuscript-reader.vue +++ b/src/components/manuscript-reader/manuscript-reader.vue @@ -39,8 +39,9 @@ * 稿件阅读抽屉:列表只放元数据,正文在这里用 mp-html 渲染。 * * item 的形状(由列表页整理好,缺内容时传 null 即关闭): - * { title, content, cover, statusText, timeLabel, chars } - * content 可能是 HTML、markdown 或纯文本,由 getRenderPayload 判断怎么渲染。 + * { title, content, format, cover, statusText, timeLabel, chars } + * content 可能是 HTML、markdown 或纯文本;format 来自后端 contentFormat, + * 有值就以它为准,没值由 getRenderPayload 按内容猜。 */ import mpHtml from '@/uni_modules/zero-markdown-view/components/mp-html/mp-html.vue' import { getRenderPayload, toPlainText } from '@/utils/content-format' @@ -75,7 +76,8 @@ }, computed: { payload() { - return getRenderPayload(this.item ? this.item.content : '') + const item = this.item || {} + return getRenderPayload(item.content, item.format) } }, methods: { @@ -108,7 +110,8 @@ }) }, copyContent() { - const text = toPlainText(this.item ? this.item.content : '') + const item = this.item || {} + const text = toPlainText(item.content, item.format) if (!text) { uni.showToast({ title: '暂无正文可复制', diff --git a/src/packageMine/manuscripts.vue b/src/packageMine/manuscripts.vue index 9adc07c..7888bcc 100644 --- a/src/packageMine/manuscripts.vue +++ b/src/packageMine/manuscripts.vue @@ -136,11 +136,13 @@ formatDateTime, /** * 稿件 → 列表行视图模型。 - * 正文可能是 HTML / markdown / 纯文本,这里只做一次纯文本化: + * 正文可能是 HTML / markdown / 纯文本,优先信后端的 contentFormat, + * 没有这个字段才按内容猜。这里只做一次纯文本化: * 摘要、搜索、字数都基于它,避免每张卡片反复解析富文本。 */ toRow(item, index) { - const plainText = toPlainText(item.content) + const format = item.contentFormat || item.content_format || '' + const plainText = toPlainText(item.content, format) const statusMeta = getManuscriptStatusMeta(item) return { key: item.id || `${index}-${item.title || ''}`, @@ -152,7 +154,8 @@ timeLabel: `更新 ${formatDateTime(item.updateTime || item.createTime)}`, chars: plainText.replace(/\s/g, '').length, cover: item.cover || '', - content: item.content || '' + content: item.content || '', + format } }, applyManuscripts(list, user) { diff --git a/src/utils/content-format.js b/src/utils/content-format.js index d019fca..ab4c807 100644 --- a/src/utils/content-format.js +++ b/src/utils/content-format.js @@ -78,19 +78,54 @@ function markdownToText(markdown) { .trim() } -/** 无论来源是 HTML 还是 markdown,都压成可读纯文本(列表摘要、搜索、复制正文都用它)。 */ -export function toPlainText(content) { +/** + * 后端 `contentFormat` 的规范值:`html` / `markdown` / `text`。 + * 认不出来的(包括老接口没有这个字段)返回空串,交给内容猜测兜底。 + */ +export function normalizeFormat(format) { + const value = String(format || '') + .trim() + .toLowerCase() + if (value === 'html' || value === 'text') { + return value + } + if (value === 'markdown' || value === 'md') { + return 'markdown' + } + return '' +} + +function plainText(text) { + return text.replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim() +} + +function guessFormat(text) { + if (isHtmlContent(text)) { + return 'html' + } + if (isMarkdownContent(text)) { + return 'markdown' + } + return 'text' +} + +/** + * 无论来源是 HTML 还是 markdown,都压成可读纯文本(列表摘要、搜索、复制正文都用它)。 + * 传了后端格式标记就以标记为准,没传才按内容猜。 + */ +export function toPlainText(content, format) { const text = String(content || '') if (!text.trim()) { return '' } - if (isHtmlContent(text)) { - return htmlToText(text) + const kind = normalizeFormat(format) || guessFormat(text) + if (kind === 'html') { + return isHtmlContent(text) ? htmlToText(text) : plainText(text) } - if (isMarkdownContent(text)) { + if (kind === 'markdown') { return markdownToText(text) } - return text.replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim() + return plainText(text) } /** 摘要不能直接 slice 原始内容——会把 HTML 标签切一半;这里输入的已是纯文本。 */ @@ -107,8 +142,11 @@ export function truncateText(text, maxLength = 60) { * - HTML 原样渲染; * - markdown 打开 mp-html 的 markdown 插件; * - 纯文本按空行分段、转义后包成 `

`,否则 mp-html 会把换行吃掉。 + * + * 后端给了 `contentFormat` 就以它为准(那是写入时定下的真实格式), + * 没给或给了不认识的值才按内容猜,兼容尚未升级的后端。 */ -export function getRenderPayload(content) { +export function getRenderPayload(content, format) { const text = String(content || '') if (!text.trim()) { return { @@ -116,23 +154,34 @@ export function getRenderPayload(content) { markdown: false } } - if (isHtmlContent(text)) { + const kind = normalizeFormat(format) || guessFormat(text) + if (kind === 'html') { return { - html: text, + html: isHtmlContent(text) ? text : textToHtml(text), markdown: false } } - if (isMarkdownContent(text)) { - return { - html: text, - markdown: true - } + if (kind === 'markdown') { + // 标成 markdown 但内容其实被 wangEditor 存成过 HTML(历史编辑过的行):按 HTML 渲染 + return text.trim().startsWith('<') + ? { + html: text, + markdown: false + } + : { + html: text, + markdown: true + } } return { - html: escapeHtml(text) - .split(/\n{2,}/) - .map((paragraph) => `

${paragraph.replace(/\n/g, '
')}

`) - .join(''), + html: textToHtml(text), markdown: false } } + +function textToHtml(text) { + return escapeHtml(text) + .split(/\n{2,}/) + .map((paragraph) => `

${paragraph.replace(/\n/g, '
')}

`) + .join('') +}