【Uniapp】【rich-text】富文本展示以及图片预览功能解决方案

前言

在使用uniapp的富文本展示组件的时候会遇到几个问题:

  1. 在后台编辑的图片文字,在移动端展示效果不好,比如文字大小不统一,图片太长导致超出手机屏幕
  2. 点击富文本中的图片需要图片预览。

富文本展示问题

这边可以使用正则表达式,对富文本的数据进行一个格式化,对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,
	})
}
相关推荐
程序员Sunday6 小时前
JavaScript 事件循环面试题,宏任务与微任务怎么执行|Sunday面试指南
开发语言·javascript·面试·校招·事件循环·程序员sunday
Setsuna_F_Seiei6 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程
zhangzeyuaaa7 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
默_笙7 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
优选资讯8 小时前
标签打印软件怎么对接 Excel 数据
前端·excel
思无邪668 小时前
用 AI 做 JS 逆向:从抓包到复现的完整方法论
开发语言·javascript·人工智能
一个风轻云淡9 小时前
GCC 和 GDB命令简单解读
java·linux·前端
u01110267510 小时前
图片处理接口如何统一错误信息 前端提示与后端错误码设计
前端·状态模式
福兮说10 小时前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
libokaifa10 小时前
把语音模型塞进手机:sherpa-onnx 在 Android 上的 ASR / TTS
前端