feat(manuscripts): 正文按后端 contentFormat 渲染

- content-format 增加 normalizeFormat;toPlainText / getRenderPayload 支持显式格式
- 列表行与阅读抽屉透传 contentFormat,字段缺失时仍回退到按内容猜
This commit is contained in:
2026-10-22 14:37:47 +08:00
parent 63295e98c5
commit 3309818160
3 changed files with 80 additions and 25 deletions
@@ -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: '暂无正文可复制',
+6 -3
View File
@@ -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) {
+67 -18
View File
@@ -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 插件;
* - 纯文本按空行分段、转义后包成 `<p>`,否则 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) => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
.join(''),
html: textToHtml(text),
markdown: false
}
}
function textToHtml(text) {
return escapeHtml(text)
.split(/\n{2,}/)
.map((paragraph) => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
.join('')
}