前言
在使用uniapp的富文本展示组件的时候会遇到几个问题:
- 在后台编辑的图片文字,在移动端展示效果不好,比如文字大小不统一,图片太长导致超出手机屏幕
- 点击富文本中的图片需要图片预览。
富文本展示问题
这边可以使用正则表达式,对富文本的数据进行一个格式化,对p标签和img标签设置对应css样式保证移动端的展示效果。
这边写了一个格式化的函数如下
js
export function formatRichText(html) {
if (!html) return '';
let formattedHtml = html;
let imgStyle = "max-width: 100%; height: auto; display: block;"
let pStyle = "font-size: 28rpx;"
// 1. 处理 img 标签:移除干扰属性,添加自适应样式
formattedHtml = formattedHtml.replace(/<img[^>]*>/gi, (match) => {
// 移除 style、width、height 属性(支持双引号和单引号)
let cleaned = match
.replace(/style="[^"]*"/gi, '')
.replace(/style='[^']*'/gi, '')
.replace(/width="[^"]*"/gi, '')
.replace(/width='[^']*'/gi, '')
.replace(/height="[^"]*"/gi, '')
.replace(/height='[^']*'/gi, '');
// 添加新样式(插入到 <img 之后)
return cleaned.replace(/<img/i, `<img style="${imgStyle}"`);
});
// 2. 处理 p 标签:设置字体大小为 28rpx,保留原有样式
formattedHtml = formattedHtml.replace(/<p(\s[^>]*)?>/gi, (match) => {
const styleRegex = /style=["']([^"']*)["']/i;
const hasStyle = match.match(styleRegex);
if (hasStyle) {
// 已有 style 属性:追加 font-size
const oldStyle = hasStyle[1];
const newStyle = oldStyle + `; ${pStyle}`;
return match.replace(styleRegex, `style="${newStyle}"`);
} else {
// 无 style 属性:新增 style
return match.replace(/<p/i, `<p style="${pStyle}"`);
}
});
return formattedHtml;
}
富文本图片预览问题-方案一
这边重点是要使用 @itemclick 事件,这样可以精确的获取到点击的是哪张图片,这个事件只有点击到富文本中的a标签和img标签才会触发。且在nvue 中是不支持的。
并且我们在rich-text的还存储了 富文本的源数据 在 :data-nodes中。js中可以使用 e.target.dataset.nodes 来获取到我们存储的数据
图片预览需要两个数据,一个是图片列表,一个是当前图片url
html
<rich-text :nodes="formatRichText(content)" @itemclick="showPreview" :data-nodes="content"></rich-text>
js
import {ref} from 'vue'
import {formatRichText} from '/common/utils/utils.js'
const content = ref(`<p>1.测试数据。</p><p>2.测试数据。</p><p>3.测试数据。</p><p><img src='https://picsum.photos/800/600'></p><p><img src='https://picsum.photos/600/800'></p><p>a标签测试</p><p><a href="https://www.baidu.com">https://www.baidu.com</a></p>`)
// 从html中提取所有img src
function matchImgUrlList(html){
const imgReg = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
const imgList = []
let match
while ((match = imgReg.exec(html)) !== null) {
imgList.push(match[1])
}
return imgList
}
function showPreview(e){
console.log(e)
if(e.detail.node.name !== 'img') {
return
}
let nodes = e.target.dataset.nodes;
let imgList = matchImgUrlList(nodes)
let current = e.detail.node.attrs.src
uni.previewImage({
current: current,
urls:imgList,
})
}
最后我们来看一下@itemclick事件到底给我们传了哪些数据:
json
{
"defaultPrevented": false,
"timeStamp": 4127.700000047684,
"_stop": false,
"_end": false,
"type": "onItemclick",
"bubbles": false,
"cancelable": false,
"target": {
"dataset": {
"nodes": "<p>1.测试数据。</p><p>2.测试数据。</p><p>3.测试数据。</p><p><img src='https://picsum.photos/800/600'></p><p><img src='https://picsum.photos/600/800'></p><p>a标签测试</p><p><a href=\"https://www.baidu.com\">https://www.baidu.com</a></p>"
},
"id": "",
"offsetLeft": 0,
"offsetTop": 19
},
"detail": {
"node": {
"attrs": {
"style": "max-width: 100%; height: auto; display: block;",
"src": "https://picsum.photos/800/600"
},
"name": "img"
}
},
"currentTarget": {
"dataset": {
"nodes": "<p>1.测试数据。</p><p>2.测试数据。</p><p>3.测试数据。</p><p><img src='https://picsum.photos/800/600'></p><p><img src='https://picsum.photos/600/800'></p><p>a标签测试</p><p><a href=\"https://www.baidu.com\">https://www.baidu.com</a></p>"
},
"id": "",
"offsetLeft": 0,
"offsetTop": 19
}
}
富文本图片预览问题-方案二
由于@itemclick的兼容性问题,这边又提出了另一个解决方案。
将富文本解析成如下的数据结构,然后v-for循环即可。区分为文本与图片,遇到文本则用rich-text进行展示,遇到图片则直接用原生的 <image> 标签展示
json
[
{
"type": "text",
"content": "<p>1.测试数据。</p><p>2.测试数据。</p><p>3.测试数据。</p><p>"
},
{
"type": "image",
"src": "https://picsum.photos/800/600"
},
]
代码如下
html
<template v-for="(seg, idx) in parseHtmlSegments(content)" :key="idx">
<rich-text v-if="seg.type === 'text'" :nodes="formatRichText(seg.content)"></rich-text>
<image v-else :src="seg.src" mode="widthFix" class="preview-image" @tap="onImageTap(seg.src, content)"></image>
</template>
解析富文本为上述json格式的工具方法如下
js
export function parseHtmlSegments(html){
console.log(html)
if (!html) return []
const segments = []
const imgReg = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
let lastIndex = 0
let match
while ((match = imgReg.exec(html)) !== null) {
if (match.index > lastIndex) {
const textContent = html.substring(lastIndex, match.index)
if (textContent.trim()) {
segments.push({ type: 'text', content: textContent })
}
}
segments.push({ type: 'image', src: match[1] })
lastIndex = match.index + match[0].length
}
if (lastIndex < html.length) {
const textContent = html.substring(lastIndex)
if (textContent.trim()) {
segments.push({ type: 'text', content: textContent })
}
}
return segments
}
js
import {ref} from 'vue'
import {formatRichText, parseHtmlSegments,matchImgUrlList} from '/common/utils/utils.js'
function onImageTap(currentSrc, html){
const imgList = matchImgUrlList(html)
if (rawUrls.length === 0) return
uni.previewImage({
urls: imgList,
current: currentSrc,
})
}