Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

95 wiersze
2.8 KiB

  1. /**
  2. * 后台富文本清洗(协议、公告等静态内容)
  3. *
  4. * 背景:html 表里的正文是 Word / 旧编辑器导出的,直接渲染有两类问题:
  5. * 1. 大量 `<p><span>&nbsp;</span></p>` 空段落,叠加段间距后出现大片空白;
  6. * 2. 几乎每个 span 都带内联 `font-family:宋体;font-size:14px;color:#000`,
  7. * 内联样式优先级最高,会盖掉页面样式,暗色模式下黑字配深底完全不可读。
  8. *
  9. * 处理方式:删掉空段落,剥掉会影响排版与主题的内联属性,保留加粗、下划线等语义样式。
  10. * 与 ai_uniapp_v2/utils/richtext.js 保持同一套规则(两个仓库独立,无法共享模块)。
  11. */
  12. const DROP_STYLE_PROPS = [
  13. 'font-family',
  14. 'font-size',
  15. 'color',
  16. 'background',
  17. 'background-color',
  18. 'line-height',
  19. 'margin',
  20. 'margin-top',
  21. 'margin-bottom',
  22. 'margin-left',
  23. 'margin-right',
  24. 'padding',
  25. 'padding-top',
  26. 'padding-bottom',
  27. 'width',
  28. 'height',
  29. ]
  30. /** 判断一段 html 是否只有空白内容(空格、&nbsp;、<br>、空标签) */
  31. function isBlankFragment(html) {
  32. const text = String(html || '')
  33. .replace(/<br\s*\/?>/gi, '')
  34. .replace(/<[^>]+>/g, '')
  35. .replace(/&nbsp;|&#160;|\u00a0/gi, '')
  36. .replace(/\s/g, '')
  37. return text.length === 0
  38. }
  39. function cleanStyleValue(style) {
  40. return String(style || '')
  41. .split(';')
  42. .map(item => item.trim())
  43. .filter(Boolean)
  44. .filter((item) => {
  45. const prop = item.split(':')[0].trim().toLowerCase()
  46. return prop && !DROP_STYLE_PROPS.includes(prop)
  47. })
  48. .join('; ')
  49. }
  50. /**
  51. * @param {string} html 后台返回的富文本
  52. * @return {string} 清洗后的富文本
  53. */
  54. export function cleanRichText(html) {
  55. if (!html) return ''
  56. let text = String(html)
  57. // Word 导出的无用标签与内嵌样式表
  58. text = text
  59. .replace(/<\/?o:p[^>]*>/gi, '')
  60. .replace(/<!--[\s\S]*?-->/g, '')
  61. .replace(/<meta[^>]*>/gi, '')
  62. .replace(/<style[\s\S]*?<\/style>/gi, '')
  63. .replace(/<script[\s\S]*?<\/script>/gi, '')
  64. // 剥掉内联的字体/颜色/间距声明,style 清空后连属性一起去掉
  65. text = text.replace(/\sstyle\s*=\s*"([^"]*)"/gi, (match, style) => {
  66. const kept = cleanStyleValue(style)
  67. return kept ? ` style="${kept}"` : ''
  68. })
  69. text = text.replace(/\sstyle\s*=\s*'([^']*)'/gi, (match, style) => {
  70. const kept = cleanStyleValue(style)
  71. return kept ? ` style="${kept}"` : ''
  72. })
  73. text = text.replace(/<\/?font[^>]*>/gi, '')
  74. text = text.replace(/\sclass\s*=\s*"(?:Mso|xl)[^"]*"/gi, '')
  75. // 只剩空白的段落 / 标题直接删掉
  76. text = text.replace(/<(p|div|h[1-6])\b[^>]*>([\s\S]*?)<\/\1>/gi, (match, tag, inner) => {
  77. return isBlankFragment(inner) ? '' : match
  78. })
  79. // 连续 <br> 压缩为一个
  80. text = text.replace(/(?:<br\s*\/?>\s*){2,}/gi, '<br>')
  81. return text.trim()
  82. }
  83. export default cleanRichText