富文本编辑器 sp-editor
基于 uni-app 官方 editor
编辑器的富文本插件。
主要功能
- 样式调整
- 根据父盒子自动调整高度
- 工具栏配置(工具可按需引入;工具图标大小、列数可调;新增调色板、超链接等工具)
- 相对轻易实现对富文本初始数据预设
- 实时获取当前编辑器内容(html 文本,text 纯文本)
- 修复原 editor 组件部分缺陷(如开启只读后仍能操作图片、小程序长按无法粘贴的问题等)
- 相对轻易实现对插入图片的处理(上传至服务器等)
- 插入视频功能已发布,有需求的请保持改插件版本为最新
提示
本插件已趋于稳定版,群友们已然踩遍了各种坑,本插件也在不断优化中。目前若出现未见过的报错,大概率是代码哪里写错了或者编码不规范导致的。请仔细阅读文档,以及疑难解答部分。也可进群讨论。
强烈建议优先前往
插件市场
导入示例项目参考一下。 示例工程中插件可能不是最新版本,运行之前建议先将示例工程中插件更新至最新版本哦。
安装
- 插件市场 中点击
下载插件并导入HBuildeX
。
前言
插件兼容性
✔️ 实测可行
❌ 未兼容
➖ 未实测
Vue2 | Vue3 | H5 | App | 微信小程序 | 支付宝小程序 |
---|---|---|---|---|---|
✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
prop 参数
参数 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
editorId | String | editor | 否(使用 v-for 循环时建议填写) | 编辑器标签 id 属性,用于循环渲染编辑器时动态赋值,防止 id 重复 |
placeholder | String | 写点什么吧 ~ | 否 | 编辑器占位字样 |
readOnly | Boolean | false | 否 | 是否只读 |
maxlength | Number | -1 | 否 | 最大字数限制,-1 时不限制 |
toolbarConfig | Object | 详见 toolbarConfig 参数 | 否 | 工具栏配置 |
toolbarConfig 参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
keys | Array | [] | 要显示的工具,优先级最大,详见 toolbar 工具 |
excludeKeys | Array | [] | 除这些指定的工具外,其他都显示 |
iconSize | String | 18px | 工具栏字体大小 |
iconColumns | Number | 10 | 工具栏列数 |
toolbar 工具
工具 key | 说明 |
---|---|
header | 标题 |
H1 | 一级标题 |
H2 | 二级标题 |
H3 | 三级标题 |
H4 | 四级标题 |
H5 | 五级标题 |
H6 | 六级标题 |
bold | 加粗 |
italic | 斜体 |
underline | 下划线 |
strike | 删除线 |
align | 对齐方式 |
alignLeft | 左对齐 |
alignCenter | 居中对齐 |
alignRight | 右对齐 |
alignJustify | 两端对齐 |
lineHeight | 行间距 |
letterSpacing | 字间距 |
marginTop | 段前距 |
marginBottom | 段后距 |
fontFamily | 字体 |
fontSize | 字号 |
color | 文字颜色 |
backgroundColor | 背景颜色 |
date | 日期 |
listCheck | 待办 |
listOrdered | 有序列表 |
listBullet | 无序列表 |
indentInc | 增加缩进 |
indentDec | 减少缩进 |
divider | 分割线 |
scriptSub | 下标 |
scriptSuper | 上标 |
direction | 文本方向 |
image | 图片 |
video | 视频 |
link | 超链接 |
undo | 撤销 |
redo | 重做 |
removeFormat | 清除格式 |
clear | 清空 |
export | 导出 |
提示
建议按需引入对应的工具,尽量简化富文本工具
emit 事件
事件名 | 参数 | 说明 |
---|---|---|
init | editor:编辑器实例;id:编辑器 id(在 v-for 循环渲染编辑器时,可以通过 id 来作区分处理) | 编辑器就绪回调 |
upinImage | tempFiles:包含插入图片的本地临时路径的对象(开发者可用此路径上传文件至服务器);editor:编辑器实例;id:编辑器 id | 插入图片时回调 |
upinVideo | tempFilePath:本地视频临时地址(开发者可用此路径上传文件至服务器);其他同上 upinImage | 插入视频时回调 |
input | e:{ html, text } 当前内容的 html 文本、 text 文本;id:编辑器 id | 输入内容时回调 |
addLink | e:{ text, href } 添加的链接描述、链接地址;id:编辑器 id | 添加超链接回调,toolbar 需要开启 link 工具 |
exportHtml | e:导出的 html 内容;id:编辑器 id | 工具栏导出回调,toolbar 需要开启 export 工具 |
插件内置工具方法
方法名 | 参数 | 返回 | 说明 |
---|---|---|---|
handleHtmlWithVideo | html:要进行处理的富文本字符串 | 返回处理结果 | 将含有特殊占位图片形式视频的富文本转换成正常视频的富文本 |
convertImgStylesToAttributes | html:要进行处理的富文本字符串 | 返回处理结果 | 将 img 标签中内联 style 属性中的宽高样式提取出标签 width 与 height 属性 |
警告
你需要在 upinImage 回调函数中处理插入的图片,否则图片将不会显示在编辑器中。详情可见下列 使用示例 中。 插入视频 upinVideo 时同上。
使用示例
提示
此处仅为简单使用示例,更多详情请前往 插件市场
导入示例项目运行参考
<template>
<view class="home">
<view class="editor-box">
<sp-editor
:toolbar-config="{
excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck'],
iconSize: '18px'
}"
@init="initEditor"
@input="inputOver"
@upinImage="upinImage"
@overMax="overMax"
@addLink="addLink"
@exportHtml="exportHtml"
></sp-editor>
</view>
</view>
</template>
<script>
export default {
data() {
return {
editorIns: null
}
},
methods: {
/**
* 获取输入内容
* @param {Object} e {html,text} 内容的html文本,和text文本
*/
inputOver(e) {
// 可以在此处获取到编辑器已编辑的内容
console.log('==== inputOver :', e)
},
/**
* 超出最大内容限制
* @param {Object} e {html,text} 内容的html文本,和text文本
*/
overMax(e) {
// 若设置了最大字数限制,可在此处触发超出限制的回调
console.log('==== overMax :', e)
},
/**
* 编辑器就绪
* @param {Object} editor 编辑器实例,你可以自定义调用editor实例的方法
* @tutorial editor组件 https://uniapp.dcloud.net.cn/component/editor.html#editor-%E7%BB%84%E4%BB%B6
* @tutorial 相关api https://uniapp.dcloud.net.cn/api/media/editor-context.html
*/
initEditor(editor) {
this.editorIns = editor // 保存编辑器实例
// 保存编辑器实例后,可以在此处获取后端数据,并赋值给编辑器初始化内容
this.preRender()
},
preRender() {
setTimeout(() => {
// 异步获取后端数据后,初始化编辑器内容
this.editorIns.setContents({
html: `<div> 猫猫<img src="https://img.yzcdn.cn/vant/cat.jpeg"/></div>`
})
}, 1000)
},
/**
* 直接运行示例工程插入图片无法正常显示的看这里
* 因为插件默认采用云端存储图片的方式
* 以$emit('upinImage', tempFiles, this.editorCtx)的方式回调
* @param {Object} tempFiles
* @param {Object} editorCtx
*/
upinImage(tempFiles, editorCtx) {
/**
* 本地临时插入图片预览
* 注意:这里仅是示例本地图片预览,因为需要将图片先上传到云端,再将图片插入到编辑器中
* 正式开发时,还请将此处注释,并解开下面 使用 uniCloud.uploadFile 上传图片的示例方法 的注释
* @tutorial https://uniapp.dcloud.net.cn/api/media/editor-context.html#editorcontext-insertimage
*/
// #ifdef MP-WEIXIN
// 注意微信小程序的图片路径是在tempFilePath字段中
editorCtx.insertImage({
src: tempFiles[0].tempFilePath,
width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
success: function () {}
})
// #endif
// #ifndef MP-WEIXIN
editorCtx.insertImage({
src: tempFiles[0].path,
width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
success: function () {}
})
// #endif
/**
* 使用 uniCloud.uploadFile 上传图片的示例方法(可适用多选上传)
* 正式开发环境中,请将上面 本地临时插入图片预览 注释后,模仿以下写法
*/
// tempFiles.forEach(async (item) => {
// uni.showLoading({
// title: '上传中请稍后',
// mask: true
// })
// let upfile = await uniCloud.uploadFile({
// filePath: item.path,
// // 同名会导致报错 policy_does_not_allow_file_overwrite
// // cloudPath可由 想要存储的文件夹/文件名 拼接,若不拼文件夹名则默认存储在cloudstorage文件夹中
// cloudPath: `cloudstorage/${item.name}`,
// cloudPathAsRealPath: true
// })
// editorCtx.insertImage({
// src: upfile.fileID,
// width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
// success: function () {
// uni.hideLoading()
// }
// })
// })
},
/**
* 导出 - toolbar需要开启export工具
* @param {string} e 导出的html内容
*/
exportHtml(e) {
uni.navigateTo({
url: '/pages/out/out',
success(res) {
// 传至导出页面解析即可
res.eventChannel.emit('e-transmit-html', {
data: e
})
}
})
},
/**
* 添加超链接
* @param {Object} e { text: '链接描述', href: '链接地址' }
*/
addLink(e) {
console.log('==== addLink :', e)
}
}
}
</script>
<template>
<view class="home">
<view class="editor-box">
<sp-editor
:toolbar-config="{
excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck'],
iconSize: '18px'
}"
@init="initEditor"
@input="inputOver"
@upinImage="upinImage"
@overMax="overMax"
@addLink="addLink"
@exportHtml="exportHtml"
></sp-editor>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const editorIns = ref(null)
/**
* 获取输入内容
* @param {Object} e {html,text} 内容的html文本,和text文本
*/
function inputOver(e) {
// 可以在此处获取到编辑器已编辑的内容
console.log('==== inputOver :', e)
}
/**
* 超出最大内容限制
* @param {Object} e {html,text} 内容的html文本,和text文本
*/
function overMax(e) {
// 若设置了最大字数限制,可在此处触发超出限制的回调
console.log('==== overMax :', e)
}
/**
* 编辑器就绪
* @param {Object} editor 编辑器实例,你可以自定义调用editor实例的方法
* @tutorial editor组件 https://uniapp.dcloud.net.cn/component/editor.html#editor-%E7%BB%84%E4%BB%B6
* @tutorial 相关api https://uniapp.dcloud.net.cn/api/media/editor-context.html
*/
function initEditor(editor) {
editorIns.value = editor // 保存编辑器实例
// 保存编辑器实例后,可以在此处获取后端数据,并赋值给编辑器初始化内容
preRender()
}
function preRender() {
setTimeout(() => {
// 异步获取后端数据后,初始化编辑器内容
editorIns.value.setContents({
html: `<div> 猫猫<img src="https://img.yzcdn.cn/vant/cat.jpeg"/></div>`
})
}, 1000)
}
/**
* 直接运行示例工程插入图片无法正常显示的看这里
* 因为插件默认采用云端存储图片的方式
* 以$emit('upinImage', tempFiles, this.editorCtx)的方式回调
* @param {Object} tempFiles
* @param {Object} editorCtx
*/
function upinImage(tempFiles, editorCtx) {
/**
* 本地临时插入图片预览
* 注意:这里仅是示例本地图片预览,因为需要将图片先上传到云端,再将图片插入到编辑器中
* 正式开发时,还请将此处注释,并解开下面 使用 uniCloud.uploadFile 上传图片的示例方法 的注释
* @tutorial https://uniapp.dcloud.net.cn/api/media/editor-context.html#editorcontext-insertimage
*/
// #ifdef MP-WEIXIN
// 注意微信小程序的图片路径是在tempFilePath字段中
editorCtx.insertImage({
src: tempFiles[0].tempFilePath,
width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
success: function () {}
})
// #endif
// #ifndef MP-WEIXIN
editorCtx.insertImage({
src: tempFiles[0].path,
width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
success: function () {}
})
// #endif
/**
* 使用 uniCloud.uploadFile 上传图片的示例方法(可适用多选上传)
* 正式开发环境中,请将上面 本地临时插入图片预览 注释后,模仿以下写法
*/
// tempFiles.forEach(async (item) => {
// uni.showLoading({
// title: '上传中请稍后',
// mask: true
// })
// let upfile = await uniCloud.uploadFile({
// filePath: item.path,
// // 同名会导致报错 policy_does_not_allow_file_overwrite
// // cloudPath可由 想要存储的文件夹/文件名 拼接,若不拼文件夹名则默认存储在cloudstorage文件夹中
// cloudPath: `cloudstorage/${item.name}`,
// cloudPathAsRealPath: true
// })
// editorCtx.insertImage({
// src: upfile.fileID,
// width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
// success: function () {
// uni.hideLoading()
// }
// })
// })
}
/**
* 导出 - toolbar需要开启export工具
* @param {string} e 导出的html内容
*/
function exportHtml(e) {
uni.navigateTo({
url: '/pages/out/out',
success(res) {
// 传至导出页面解析即可
res.eventChannel.emit('e-transmit-html', {
data: e
})
}
})
}
/**
* 添加超链接
* @param {Object} e { text: '链接描述', href: '链接地址' }
*/
function addLink(e) {
console.log('==== addLink :', e)
}
</script>
提示
使用 v-for 循环渲染编辑器并分别处理的案例我写在示例工程的示例二中了,有需要的同学可以前往 插件市场
导入示例工程看下。
注意事项
该组件在使用过程中推荐在外层套上个父盒子,并给父盒子高度,组件在封装时进行了高度计算,会自动撑满父盒子。
如遇到在内置浏览器中发生无法拖动调节颜色板的问题,只需调出开发者调试面板,点击重置左上角选择 dom 的箭头后,便能调出模拟器手势光标,便可正常拖动了。
关于插入视频 🔥 一直是使用 uni-editor 让人头疼的问题,不少群友让我新增这么一项功能,在 🐦⬛ 了一段时间后,还是决定回应一下。本插件以在富文本编辑过程中使用图片进行替换,待编辑完成后发布富文本内容时,调用内部
handleHtmlWithVideo
方法进行一键处理视频的富文本节点进行实现。- 推荐在编辑完成后,确定发布富文本内容的时机进行
handleHtmlWithVideo
处理,而非在编辑过程中进行。 - 更多详见示例工程中的示例一。
- 推荐在编辑完成后,确定发布富文本内容的时机进行
在 H5 中,由于官方 editor 内部是使用 unpkg 加载 quill.min.js、image-resize.min.js 两个依赖,可能会存在国内无法访问 unpkg 的资源,导致 editor 不正常的问题,在此我已将该两个依赖包放在了组件的 uni_modules/sp-editor/static 文件夹下,希望在 H5 端正常且稳定使用的小伙伴可以在 index.html 或 template.h5.html 中引入该本地 cdn 资源。
- 官方已给出了H5 使用最佳实践,主要两种方式:
- 方案一:自行托管 CDN 资源,示例工程中使用该方案一(使用静态资源,或者把资源丢进自己服务器中,要注意的是,在 vue3 环境下直接在项目根目录下的 index.html 配置 script 标签引入资源即可,但是在 vue2 环境下 需要在根目录下创建 template.h5.html 自定义模板,在该模板中配置资源引入,详情可见博客uniapp 内网部署 editor 富文本报错问题)。
- 方案二:装 quill 的 1.3.7 的 node 包
npm i quill@1.3.7
。 - 关于此问题还可详见官方问答贴:在 H5 中使用了 uni-app 的 editor 组件异常的解决方案。
- 注意此解决方式主要针对 H5 端,其他端不要做该操作。
- 官方已给出了H5 使用最佳实践,主要两种方式:
由于官方 editor 组件中的 insertText 方法在不同平台是基于各平台对应的富文本技术实现,可能存在部分问题(可能算是特性?🤔):
- 在安卓 app 正常,但是其他平台在插入内容的时候会移动光标聚焦,导致焦点回滚到视口处(或许可以利用这个做一些功能 🤔)。
- 初始化内容时建议使用 setContents 方法,而非 insertText 方法。
- 尽量避免使用 insertText 方法,因为可能导致意外的光标移动问题。
请阅读了解 editor 支持的标签
关于添加插入超链接的问题:
- 在微信小程序中点击不跳转的问题,可见uniapp 的 editor 组件插入的 a 标签会被删除 href 属性,经官方实测是微信小程序的问题,可能需要等微信官方修复了。
- 光标问题,因为 uniapp 官方的 editor 组件并没有开放插入 a 标签的 api,我手搓了一个 addLink 的方法,该方法本质上使用的是 setContents 方法,将 delta 替换插入 link,由于 setContents 会使光标默认回到开头位置,故我做了插入链接后使富文本失焦的操作,需要用户再手动点击使光标聚焦(属实也是无奈之举,目前可能是实现手动添加链接的最优解,除非官方开放添加链接的 api)。
- 插入的超链接在编辑模式下不可跳转,因为此时还是正在编辑的富文本,需要使用好一点的富文本解析器来解析编辑好的富文本文档成和他们后才能点击跳转,如在示例工程中编辑完成后点击 export 工具导出解析后即可点击跳转。
报出警告
quill Overwriting modules/ImageResize with f t(e){...}
- 不用管,这是 quill 官方的问题,实测就算直接用官方原 demo 也会报这个警告,所以无所谓了。
疑难解答
添加的超链接无法跳转怎么办?
- 如果您是微信小程序的话,那可能有些抱歉,这是微信小程序官方的 bug,可前往问答帖 uniapp 的 editor 组件插入的 a 标签会被删除 href 属性,uniapp 官方人员实测是微信小程序本身的问题,可能需要等微信官方修复了。
- 不是微信小程序也不能跳转?那我猜你可能正处于编辑中,正在编辑的时候是无法跳转的,你需要导出当前编辑好的 html(建议用一个好些的解析器,比如
mp-html
,有的简陋的解析器或者官方自带的 rich-text 组件无法解析部分标签如 a 标签),或者开启编辑器的只读模式,才能正常跳转。(可以在示例工程的示例一中体验下) - 说实话,其实可能微信自动去除 a 标签的 href 属性是有意为之,毕竟小程序不允许如此随意的跳转外链。若实在有必要跳转,使用类似于插入视频那样的思路先将 href 属性保存起来,在确认完成编辑后进行替换或许能实现跳转。(其实已经有群友这样做了)
怎么添加视频?
- 请见上述
注意事项
第 3 点。
- 请见上述
字体怎么切换?
- 当前只支持切换成宋体,因为字体种类太多,我也不好囊括,所以只简单实现了切换宋体。
怎么使编辑器自动聚焦呢?
- 可以利用 insertText 的特性,在编辑器初始化完成后,使用
编辑器示例.insertText({text:''})
的方式使其自动聚焦(这不就利用上了 😏)。
- 可以利用 insertText 的特性,在编辑器初始化完成后,使用
手机长按复制为啥不能复制图片?
- 其实可以的,但是你得导出或者开启只读模式,编辑模式下暂时无法正常复制图片(我也未知原因),你可以在示例工程中示例一开启只读后长按复制试试。
提示
长按复制这种功能是手机系统级的,之前有人问长按的气泡框能不能改样式,给我问愣住了 💦。
- 其实可以的,但是你得导出或者开启只读模式,编辑模式下暂时无法正常复制图片(我也未知原因),你可以在示例工程中示例一开启只读后长按复制试试。
我在上传图片后,有些图片不需要上传,只需要上传最终保存提交的富文本里的图片怎么办?
因为编辑器是有撤销重做的功能,所以这里不能在删除图片时直接删除服务器上的图片,这里提供有两种逻辑思路:
一是在你插入图片的回调事件中(upinImage)对每次插入图片时都需要上传至服务器,并对每次操作的图片都保存至一个临时容器中(可以是数组),最后保存提交富文本内容至后端,之后你需要将实际用上的图片和临时容器作对比,找出那些实际上没有用到的图片,将其从服务器上再删除。
二是在你插入图片的回调事件中(upinImage)对每次插入图片都不进行上传至服务器,仅插入图片的临时本地路径,最后保存提交富文本内容前,先将最后用上的临时本地路径的图片上传至服务器,取到真实的网络路径后,再将富文本中临时路径的图片路径一一对应替换,最后将整个富文本内容上传至后端。
图片删除事件可以监听吗?
- 暂不可以,图片目前的 api 只有插入图片时的回调,关于图片尺寸调整以及删除图片按钮均为 quill 及其相关编辑器底层 js 的代码逻辑,基于上层的我们暂时无法触及 💦。
微信小程序真机上使用 setContents 进行内容初始化,没有使用 insertText 也会造成初始光标聚焦视口回滚,但是我不要它滚动怎么办?
- 通过在微信开发者社区中了解,实测这是微信小程序本身的特性(bug🐶),但是不要慌,有解决方法,详见示例工程中示例三。
自定义扩展
插件本身支持对各种自定义的工具进行扩展,可以模仿添加超链接或插入日期等工具的写法规范,进行自定义扩展开发
本插件内部封装了一个 addLink 的方法,用于插入超链接(a 标签)的工具,如若要自定义扩展功能:#话题#、 @别人,等类似的携带链接的功能,可以模仿该 addLink 的方式进行自定义扩展开发。
写在最后
若对插件有任何疑问或者优化建议,欢迎在 插件评论区 留言,在插件市场中的私信消息本人可能不经常留意,导致没能及时回复, 可以加入本人的插件问答 QQ 交流群: 852637893,欢迎 进群交流。
写文档码字感觉真累啊,果然最讨厌的两件事:一是找到合适的插件没文档,二是自己写的插件要自己写文档了*(:з」∠)*。如果有帮助到您,希望能 鼓励一下 吧~ 谢谢 ♪(・ω・)ノ