import { resolveFileUrl } from '@/utils/image' /** * 解码常见 HTML 实体(后台若存了 <p> 会原样显示标签) */ export function decodeHtmlEntities(html) { if (!html || typeof html !== 'string') { return '' } let s = html if (!/&(?:lt|gt|amp|quot|nbsp|#)/i.test(s)) { return s } s = s.replace(/ /gi, '\u00A0') s = s.replace(/</gi, '<') s = s.replace(/>/gi, '>') s = s.replace(/&/gi, '&') s = s.replace(/"/gi, '"') s = s.replace(/'/g, "'") s = s.replace(/&#(\d+);/g, (_, code) => { const n = Number(code) return Number.isNaN(n) ? '' : String.fromCharCode(n) }) s = s.replace(/&#x([0-9a-f]+);/gi, (_, hex) => { const n = parseInt(hex, 16) return Number.isNaN(n) ? '' : String.fromCharCode(n) }) return s } /** 富文本 img 相对路径转完整 URL(Quill 常存 /dev-api/profile/...) */ export function fixHtmlImageSrc(html) { if (!html) return '' return html.replace(/src=(["'])([^"']+)\1/gi, (match, quote, src) => { // 去掉代理前缀,再走 resolveFileUrl 拼成小程序/rich-text 可用的完整地址 const path = String(src).trim().replace(/^\/(dev-api|prod-api|shop-api)/i, '') const full = resolveFileUrl(path) return full ? `src=${quote}${full}${quote}` : match }) } /** 去掉 Quill 常见空段落,避免占高度 */ export function trimEmptyParagraphs(html) { if (!html) return '' return html .replace(/

(\s| |)*<\/p>/gi, '') .replace(/

<\/p>/gi, '') } /** * 协议/商品详情等富文本统一预处理(供 rich-text :nodes 使用) */ export function prepareRichHtml(html) { let s = (html || '').trim() if (!s) return '' s = decodeHtmlEntities(s) s = fixHtmlImageSrc(s) s = trimEmptyParagraphs(s) return s }