1.前言
富文本的使用十分频繁,我简单讲一讲关于在小程序端实现富文本的编辑,上传与回显。
本文借助官方提供的富文本编辑插件editor
来实现富文本的编辑
本文将会提供两种实现回显方式,一种是rich-text
,这是官方提供的插件,另一种是uv-ui
插件中的uv-parse
,这是比较热门的uniapp插件,并说明两者的区别和使用场景。
另外,本文不仅提供前端(vue3)的实现方式,并且会提供node.js的后端实现。
2.使用rich-text
和editor
插件实现富文本的编辑,上传与回显
2.1 富文本的编辑与上传
editor
是官方提供的一个简单的富文本编辑器,虽然简单,但也能够满足很多需求了。
具体的用法如下:
下面是Edit.vue文件
借鉴了王二红大佬的实现
html
<!-- 首先我们设置一个工具栏用于选择富文本的样式 -->
<template>
<view class='toolbar' @tap="format">
<!-- 加粗按钮,.bold是否为true -->
<view :class="formats.bold ? 'active' : ''" class="iconfont icon-zitijiacu" data-name="bold"></view>
<!-- 斜体按钮 -->
<view :class="formats.italic ? 'active' : ''" class="iconfont icon-zitixieti" data-name="italic"></view>
<!-- 下划线按钮 -->
<view :class="formats.underline ? 'active' : ''" class="iconfont icon-zitixiahuaxian" data-name="underline">
</view>
<!-- 图片插入按钮 -->
<view class="iconfont icon-addpicture" @tap="insertImage"></view>
</view>
<button @tap="upload">上传</button>
<editor id="editor" class="container" placeholder="请输入...." show-img-size show-img-toolbar show-img-resize='true' @ready="onEditorReady" @input="updateContent">
</template>
这里我简单列了三个按钮,分别是加粗,斜体,下划线,我们可以通过点击按钮来改变富文本的样式。 通过自定义属性,我们可以知道用户点击了哪个按钮,从而改变富文本的样式。
ts
// 初始化富文本编辑器
const editorCtx = ref(null)
const richText = ref('')
const onEditorReady = ()=>{
uni.createSelectorQuery().select('#editor').context((res)=>{
editorCtx.value = res.context
if(richText.value && editorCtx.value!= null){
// 初始化富文本的内容
(editorCtx.value as any).setContents({
html:richText.value,
})
}
})
}
// 获取富文本的内容(方便我们提交到后端)
const updateContent = (e:any)=>{
richText.value = e.detail.html
}
// 改变富文本的样式
const format = (e:any)=>{
const {name,value} = e.target.dataset
// 规定只有点击到能改变样式的按钮才能生效
if(!name) return
if(editorCtx.value != null){
// 改变对应富文本的样式
(editorCtx.value as any).format(name,value)
}
}
// 插入图片
const insertImage =async ()=>{
try{
const res = await uni.chooseImage({
count:1,
soureType:['album','camera'],
})
const filePath = res.tempFilePaths[0]
uni.showLoading({title:'上传中...'})
// 上传图片到服务器
const uploadRes = await uni.uploadFile({
url:``, // 上传图片的接口地址
filePath:filePath,
name:'images', // 后端设置的字段名
})
// 我这里假设后端返回的结构是[{url:'图片地址',name:'图片名称'}]
const imageUrl = JSON.parse((uploadRes as any).data)[0].url
// 将拿到的图片插入到富文本中
if(editorCtx.value!= null){
(editorCtx.value as any).insertImage({
src:imageUrl,
alt:'图片',
success:()=>{
console.log('图片插入成功')
},
fail:(err:any)=>{
console.log(err)
}
})
}
uni.hideLoading()
}catch(err){
uni.hideLoading()
console.log(err)
uni.showToast({
title:'上传失败',
icon:'none',
})
}
}
const upload =async ()=>{
try{
if(richText === '') {
uni.showToast({
title:'请输入内容',
icon:'none',
})
return
}
const results = await uni.request({
url:'',
method:'POST',
data:{
richText:richText.value, // 富文本的内容
}
})
console.log(results)
}catch(err){
console.log(err)
}
}
这里我只提供html的格式和ts代码,样式可以自行定义。
2.2 关于富文本的回显
刚才我们在一个文件中,借助官方提供的editor
实现了富文本的编辑,可以实现图片和文字的混排 并且可以改变文字的样式。
但是在实际开发中,我们往往需要将富文本的内容回显到页面上,这里我们就需要用到rich-text
组件了。
由于rich-text
插件是官方内置的组件,我们并不需要额外的引入就能直接使用。
这里我简单介绍一下rich-text
,官方文档的讲解有点难懂,这里我简单总结它的特点就是:
- 在小程序中只读,所有生成的富文本是一个整体。
- rich-text 组件内屏蔽所有节点的事件。所以如果内容中有链接、图片需要点击,则不能使用rich-text。
- 可以使用Html String 或 node数组来作为
rich-text
的内容,node数组性能更好。(node节点的结构见下文) - 非 App 平台 img 标签仅支持网络图片。
- attrs 属性不支持 id ,支持 class 。
下面是node数组的结构:
js
[
{ type: 'text', text: '"' },
{ name: 'p', attrs: {}, children: [ [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object], [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object], [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object], [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object], [Object] ] },
{ name: 'p', attrs: {}, children: [ [Object] ] },
... 275 more items
]
在小程序中使用rich-text
只需要将后端回显回来的Html String 或者是 node节点数组作为rich-text
中的node属性的值就可以
下面是showRichText.vue文件
html
<rich-text :node="htmlString"></rich-text>
ts
// 富文本的内容
const htmlString = ref('')
onLoad(()=>{
uni.request({
url:'', // 获取富文本的接口地址
method:'GET',
success:(res:any)=>{
htmlString.value = res.data.richText
}
})
})
通过这种方式,就能将富文本的内容回显到页面上了。
2.3 后端对富文本的处理
在前端,我们通过editor
插件实现了富文本的编辑,上传与回显。
但是在后端,我们需要对富文本的内容进行处理,才能将其存储到数据库中。
editor
提交到后端的富文本的内容是一个Html String ,由于官方的rich-text
组件中直接传入nodes数组,性能会更好,所以我打算在后端将其处理成nodes数组存入数据库, 再这之后,前端通过接口请求到的数据就是nodes数组了。
下面是后端对富文本的处理:
js
const htmlparser2 = require('htmlparser2');
const domhandler = require('domhandler')
const domutils = require('domutils')
// 将HTML字符串转换为rich-text需要的节点数组
async function htmlToNodes(htmlString) {
const nodes = []
const handler = new domhandler.DomHandler((error, dom) => {
if (error) {
console.error('Error parsing HTML:', error)
return
}
nodes.push(...domToRichText(dom)) // 将DOM节点转换为rich-text节点格式
});
const parser = new htmlparser2.Parser(handler); // 创建HTML解析器
parser.write(htmlString); // 解析HTML字符串
parser.end(); // 结束解析
return nodes;
}
// 将DOM节点转换为rich-text节点格式
function domToRichText(domNodes) {
return domNodes.map(node => {
if (node.type === 'text') { // 文本节点
return { type: 'text', text: node.data }
} else if (node.type === 'tag'){
return {
name:node.name,
attrs:Object.entries(node.attribs).reduce((acc,[key,value])=>{
if(key === 'class'){
acc.class = value;
}else if(key === 'style'){
acc.style = value;
}else {
acc[key] = value;
}
return acc;
},{}),
children:node.children ? domToRichText(node.children) : []
};
}
return {
type:'text',
text:''
}
})
}
module.exports = {
htmlToNodes
}
在处理完成之后,在用户上传富文本的时候,我们只需要将富文本的内容传入到htmlToNodes函数中,就可以将其转换为nodes数组了,并将其存入数据库中。
js
const {htmlToNodes} = require('./utils')
const db = require('./db') // 数据库操作模块
const express = require('express')
const router = express.Router()
router.post('/upload',async (req,res)=>{
try{
const {richText} = req.body; // 富文本的内容
const nodes = JSON.stringify(await htmlToNodes(richText)) // 将富文本的内容转换为nodes数组
const sql = `insert into content set(richText) values(?)` // 插入数据的sql语句
db.query(sql,[nodes],(err,results)=>{
if(err){
console.log(err)
res.status(500).json({message:'服务器错误'})
}
res.json({message:'上传成功'})
})
}
})
3.使用uv-ui
插件中的uv-parse
实现富文本的回显
想要使用这个插件,建议在插件市场中使用HBuilderX进行安装,原因就是我之前在vscode中尝试使用npm安装无法使用,查看该插件的文档, 的解决方案也没有解决,后面迫不得已下载了HBuilderX来进行安装 但后面发现,直接去Gitte上引入uni-moudle到项目中也能解决。
编辑依然使用的是官方的editor
,回显使用的是uv-parse
但是与rich-text
不同的是,uv-parse
要求传入html string,而不是nodes数组,所以后端不需要额外再处理了。 关于uv-parse
的使用,详情见https://www.uvui.cn/components/parse.html
下面是showRichText.vue文件
html
<template>
<view>
<uv-parse :content="htmlString"></uv-parse>
</view>
</template>
4.关于两者的抉择
既然两者都能实现富文本的回显,那么我们应该选择哪一个呢?
如果你只要求富文本只读,而不做任何要求,那么使用官方提供的rich-text
即可, 但是如果你要求你富文本内部的事件不被屏蔽,推荐使用uv-parse
,而且uv-parse
默认提供图片预览功能,而且uv-parse
的功能更加丰富。
5.总结
这篇文章借鉴了网上的一些方案,并且上述实现是我从自己的项目中简化出来的,可能会有一些问题。 如果有更好的实现方式,欢迎留言交流。