【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,
	})
}
相关推荐
z19408920661 小时前
在线生成背景:字号层级怎么做才像「正式物料」
前端·javascript·html
skilllite作者1 小时前
GEO 是什么:从搜索引擎到「对话式答案」的信息可见性
java·前端·笔记·安全·搜索引擎·agentskills
Hello--_--World1 小时前
React:useState 函数式更新、useContext 全解析、useReducer 深度解析
前端·react.js·前端框架
李白的天不白1 小时前
vue优化建议
前端·javascript·vue.js
前端老石人1 小时前
Chrome DevTools 调试入门:从零开始排查 CSS 问题
前端·css·chrome devtools
恋猫de小郭1 小时前
经典,Flutter iOS 又修复了一个构建问题,还是很抽象
android·前端·flutter
invicinble2 小时前
前端框架使用vue-cli(总篇章介绍)
前端·vue.js·前端框架
QD_ANJING2 小时前
普及一下五月AI前端面试需要达到的强度....
前端·javascript·vue.js·人工智能·面试·职场和发展
AI自动化工坊2 小时前
Chrome DevTools MCP:让AI编码代理获得浏览器调试能力
前端·人工智能·chrome devtools