最近有需求需要用到富文本编辑器,于是调研了一下,发现wangEditor的呼声很高,于是就开始了探索之路,文章会分别使用4.x和5.x版本进行实践,最后会总结4.x和5.x在使用上的一些区别。
4.x
安装
yarn add wangeditor
使用
安装后几行代码即可创建一个编辑器:
import E from "wangeditor"
const editor = new E("#div1")
editor.create()
常用方法
- editor.txt.getJSON() 获取JSON内容
- editor.txt.setJSON(xx) 设置JSON内容
- editor.txt.html() 获取html
- editor.txt.html(xx) html回显示
- editor.config.uploadImgHooks上传图片的回调函数
实践
下面demo中使用wangEditor创建富文本编辑器,编辑器里会有默认值(表格)
<template>
<div>
<div ref="editor" class="editor-wrapper"></div>
<button v-on:click="getContent" class="see-content">查看内容</button>
<!-- 动态插入表格 -->
<table
border="0"
width="100%"
cellpadding="0"
cellspacing="0"
ref="tableBox"
>
<tbody>
<tr>
<th v-for="(item, index) in tableHeader" :key="index">{{ item }}</th>
</tr>
<tr v-for="item in tableData" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.eventName }}</td>
<td>{{ item.eventNumber }}</td>
<td>{{ item.eventType }}</td>
<td>{{ item.eventLevel }}</td>
<td>{{ item.alarmNum }}</td>
<td>{{ item.attackNum }}</td>
<td>{{ item.victim }}</td>
<td>{{ item.isSendReport }}</td>
</tr>
</tbody>
</table>
<div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="eventNumber" label="编号"></el-table-column>
<el-table-column prop="eventName" label="名称"></el-table-column>
<el-table-column prop="eventType" label="分类"></el-table-column>
<el-table-column prop="eventLevel" label="级别"></el-table-column>
<el-table-column prop="alarmNum" label="数量"></el-table-column>
<el-table-column prop="attackNum" label="攻击IP数"></el-table-column>
<el-table-column prop="victim" label="受害IP数"></el-table-column>
</div>
</div>
</template>
<script>
import E from "wangeditor";
import { tableData, tableHeader } from "./config.js";
export default {
name: "editor",
data() {
return {
editorContent: "",
tableData: tableData,
tableHeader: tableHeader,
unableUpload: false
};
},
methods: {
getContent: function() {
console.log(this.editorContent);
},
// 获取自定义图片上传接口返回的uuidName集合
getImgUidFromContent(content, imageList = []) {
if (!content.length) return;
content.map(item => {
if (item.tag === "img") {
item.attrs?.map(item => {
if (item.name === "src") {
const uuid = item.value?.split("uuidName=")[1];
imageList.push(uuid);
}
});
} else {
if (item.children && item.children?.length) {
this.getImgUidFromContent(item.children, imageList);
}
}
});
}
},
beforeDestroy() {
// 销毁编辑器
this.editor.destroy();
this.editor = null;
},
mounted() {
this.$nextTick(() => {
var editor = new E(this.$refs.editor);
this.editor = editor; // 方便销毁,注意:editor不用在data中定义,否则会有问题,即输入框无法正常输入
// console.log(editor.config, "editor.customConfig--");
// 编辑器配置
editor.config.height = 500;
editor.config.zIndex = 500;
editor.config.placeholder = "";
editor.config.showFullScreen = false;
editor.config.showLinkImg = false;
// 关于图片上传 (wangeditor默认图片上传大小限制5M)
// editor.config.uploadImgAccept = ["png", "jpg", "jpeg", "tif"];
// editor.config.uploadImgMaxLength = 1;
// editor.config.uploadImgHeaders = { token: xxx };
// editor.config.uploadImgParams = { serviceName: xxx };
// editor.config.uploadImgServer = "url";
// editor.config.uploadFileName = "file"; // 为固定值,否则上传失败
// 已上传图片插入到编辑器中
editor.config.uploadImgHooks = {
before: () => {
// config中已对类型、大小做出限制,此处限制数量10个
if (this.unableUpload) {
return { prevent: true, msg: "上传图片数量不能超过10个" };
}
},
customInsert: (insertImgFn, result = {}) => {
const { data = {} } = result; // data为上传接口返回的数据结构
const downloadUrl = "xxx";
insertImgFn(downloadUrl, data.uuidName);
}
};
// 覆盖wangEditor默认原生alert
editor.config.customAlert = (s, t) => {
const { $message } = this;
switch (t) {
case "success":
$message.success(s);
break;
case "info":
$message.info(s);
break;
case "warning":
$message.warning(s);
break;
case "error":
$message.error(s);
break;
default:
$message.info(s);
break;
}
};
editor.config.onchange = html => {
// 编辑器内容变化时触发
this.editorContent = html;
};
editor.config.menus = [
"head", // 标题
"bold", // 粗体
"fontSize", // 字号
"fontName", // 字体
"italic", // 斜体
"underline", // 下划线
"strikeThrough", // 删除线
"foreColor", // 文字颜色
"backColor", // 背景颜色
"link", // 插入链接
"list", // 列表
"justify", // 对齐方式
"quote", // 引用
"emoticon", // 表情
// 'image', // 插入图片
"table", // 表格
// 'video', // 插入视频
// 'code', // 插入代码
"undo", // 撤销
"redo" // 恢复
];
editor.create();
// 数据回显
// editor.txt.html(this.content);
// 自定义onChange钩子触发的延迟时长
editor.config.onchangeTimeout = 300;
// 编辑器内容change事件
editor.config.onchange = async newHtml => {
const content = editor.txt.getJSON();
const imageList = [];
await this.getImgUidFromContent(content, imageList); // 获取图片上传后id
this.unableUpload = imageList.length >= 10;
this.$emit("change", { content: newHtml, imageList });
};
// 动态插入表格
const editDiv = document.getElementsByClassName("w-e-text")[0]; // 可编辑div
console.log(editDiv, Array.isArray(editDiv), "editDiv--");
const tableBox = this.$refs.tableBox;
editDiv.appendChild(tableBox);
console.log(this.editorContent, "获取表格元素----");
});
}
};
</script>
<style lang="less">
.editor-wrapper {
margin-top: 40px;
}
.see-content {
margin: 20px 0;
}
</style>
结果
5.x
安装
yarn add @wangeditor/editor
使用
// 引入css文件
import '@wangeditor/editor/dist/css/style.css'
// template
<div id="toolbar-container"></div>
<div id="editor-container"></div>
// script
import { createEditor, createToolbar } from '@wangeditor/editor'
// 编辑器
const editorConfig = {};
const toolbarConfig = {};
const editor = createEditor({
selector: '#editor-container',
config: editorConfig,
mode: 'default'
})
// 创建工具栏
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: toolbarConfig,
mode: 'default'
})
这样就创建了一个简单的编辑器
常用方法
editor.children获取JSON内容editor.children = xxx设置JSON内容editor.getHtml()获取HTML内容editor.setHtml()设置html内容editorConfig.MENU_CONF['uploadImage']图片上传回调
实践
// template
<div ref="toolbarEditor" class="toolbar-container" />
<div ref="contentEditor" class="editor-container" />
// script
import { createEditor, createToolbar } from '@wangeditor/editor';
data() {
return {
content: '',
unableUpload: false,
};
},
/**
* @desc 递归获取富文本编辑器内容中图片的uuidName
* @param {*} content 富文本编辑器对象格式文本内容
*/
getImgUidFromContent(content, imageList = []){
if (!content.length) return;
content.map(item => {
if (item.type === 'image') {
const uuid = item.src?.split('uuidName=')[1];
imageList.push(uuid);
} else {
if (item.children && item.children?.length) {
getImgUidFromContent(item.children, imageList);
}
}
});
}
// 富文本编辑器初始化
initEditor() {
const self = this;
// 编辑器配置
const editorConfig = {
placeholder: '',
MENU_CONF: {}
};
editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'file',
server: '服务端图片上传接口地址',
maxFileSize: 10 * 1024 * 1024,
maxNumberOfFiles: 10,
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg', 'image/tiff'],
headers: {
'Token': '业务token'
},
meta: {
serviceName: 'xxx'
},
timeout: 5 * 1000,
// 上传之前触发
onBeforeUpload() {
if (self.unableUpload) {
self.$message.warning('上传图片数量不能超过10个');
return false;
}
},
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
customInsert: (res = {}, insertFn)=> {
const { data = {} } = res;
const downloadUrl = 'xxx';
insertFn(downloadUrl);
},
// 上传错误,或者触发timeout超时
onError(file) {
const size = file.size;
if (size / 1024 / 1024 > 10) self.$message.warning('上传图片大小最大为10M!');
},
// 覆盖wangEditor默认原生alert
customAlert: (s, t)=> {
const { $message } = self;
switch (t) {
case 'success':
$message.success(s);
break;
case 'info':
$message.info(s);
break;
case 'warning':
$message.warning(s);
break;
case 'error':
$message.error(s);
break;
default:
$message.info(s);
break;
}
}
};
// 工具栏配置
const toolbarConfig = {
toolbarKeys: [
'headerSelect',
'blockquote',
'bold',
'underline',
'italic',
'color',
'bgColor',
'fontSize',
'bulletedList',
'numberedList',
{
key: 'group-justify',
title: '对齐',
menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>'
},
{
key: 'group-image',
title: '图片',
menuKeys: ['uploadImage'],
iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>'
},
{
key: 'group-indent',
title: '缩进',
menuKeys: ['indent', 'delIndent'],
iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>'
}
// 'insertTable'
],
};
// 创建编辑器
const editorEle = this.$refs.contentEditor;
const editor = createEditor({
selector: editorEle,
config: {
...editorConfig,
// 编辑器内容change事件
async onChange(editor) {
const content = editor.children;
const imageList = [];
await getImgUidFromContent(content, imageList);
self.unableUpload = imageList.length >= 10;
self.$emit('change', { content: editor.getHtml(), imageList });
},
},
mode: 'default'
});
editor.setHtml(this.content);
this.editor = editor;
// 创建工具栏
const toolbarEle = this.$refs.toolbarEditor;
createToolbar({
editor,
selector: toolbarEle,
config: toolbarConfig,
mode: 'default'
});
}
结果
注意事项
- editor实例上的属性为只读属性,不能直接修改某个属性,如果需要修改可以通过深拷贝间接处理
- editor可以不用在data中直接定义
- 生成多个编辑器实例时,可以将编辑器封装成一个公共组件,组件中进行编辑器创建、销毁,其他用的地方直接用,简单方便
总结
- 4.x在拖动图片时直接是复制,不支持拖拽
- 5.x在表格的支持上更加友好,支持图片拖拽
- 5.x在对图片的处理上更加友好,如果业务中用到的富文本编辑器对图片有很多操作,可以考虑使用wangEditor5版本
- 5.x在大文件内容复制粘贴时,会有卡顿问题,这是因为内部使用了虚拟滚动,这个问题官方暂时没有解决方案,github上已有人提issue
- 5.x版本做了架构上的改变(JSON数据格式,工具栏与编辑器分离,核心模块与扩展模块分离等)
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿