在项目中遇到富文本编辑器需要实现粘贴图片的功能,使用场景:如用户在其他地方截图可以直接在富文本编辑器内粘贴。
找了一圈市面上开源免费的富文本编辑器,最后选中CKEditor。ckeditor document build 版本默认是可以粘贴图片的, 其他build版本没有尝试。
安装: 有好几种build版本可以选,我选用的是@ckeditor/ckeditor5-build-decoupled-document ,官方也有基于Vue框架的使用方法,尝试了一下一直报错,作罢。
npm install --save @ckeditor/ckeditor5-build-decoupled-document
使用:我是把ckeditor封装成一个业务组件,方便在各个页面中使用
ckeditor上传文件一般需要自己写一个文件适配器
//ckeditor 富文本编辑器自定义上传图片adapter
//文件上传到七牛云 后端提供上传接口
import { qiniuInfo, uploadFile } from '@/api/common';
export default class UploadImageAdapter {
constructor(loader) {
this.loader = loader
this.imgURL = "xxxxxx" //这个是图片域名
}
async upload() {
//获取到用户上传的文件
const img = await this.loader.file;
const promise = new Promise(async (resolve, reject) => {
const { data, status } = await qiniuInfo();
if (status !== 200) reject(false)
const formData = new FormData();
//token为上传到七牛云所需要的
formData.append('token', data.token);
formData.append('file', img);
const res = await uploadFile(formData).catch(() => {});
if (res.status === 200) {
//必须要要以 default: imgurl形式
let response = {
default: `${this.imgURL}${res.data.hash}` //图片全链接
}
resolve(response)
} else {
reject(false)
}
})
return promise
}
abort() {
//可以书写删除服务器图片的逻辑
}
}
<template>
<div>
<!-- 工具栏容器 -->
<div id="toolbar-container"></div>
<!-- 编辑器容器 -->
<div id="editor"></div>
</div>
</template>
<script>
import CKEditor from '@ckeditor/ckeditor5-build-decoupled-document';
//中文包
import '@ckeditor/ckeditor5-build-decoupled-document/build/translations/zh-cn';
//上传图片所需的适配器
import UploadImageAdapter from '@/jslibs/UploadImageAdapter';
export default {
name: 'ckeditor',
props: {
value: {
required: true,
}
},
data() {
return {
editor: null,
}
},
mounted(){
this.initCKEditor()
},
methods:{
initCKEditor() {
CKEditor.create(document.querySelector('#editor'),
toolbar: [
'heading',
'|',
'bold',
'italic',
'Underline',
'fontSize',
'numberedList',
'bulletedList',
'blockQuote',
'|',
'alignment:left',
'alignment:right',
'alignment:center',
'alignment:justify',
'|',
'imageUpload',
'Link',
'|',
'undo', //撤销
'redo',//重做
],
heading: {
options: [
{ model: 'paragraph', title: '段落', class: 'ck-heading_paragraph' },
{ model: 'heading6', view: 'h6', title: '标题6', class: 'ck-heading_heading6' },
{ model: 'heading5', view: 'h5', title: '标题5', class: 'ck-heading_heading5' },
{ model: 'heading4', view: 'h4', title: '标题4', class: 'ck-heading_heading4' },
{ model: 'heading3', view: 'h3', title: '标题3', class: 'ck-heading_heading3' },
{ model: 'heading2', view: 'h2', title: '标题2', class: 'ck-heading_heading2' },
{ model: 'heading1', view: 'h1', title: '标题1', class: 'ck-heading_heading1' }
]
},
language: 'zh-cn'
fontSize: {
options: ['default',14, 16, 18, 20, 22, 24]
}
}).then(editor => {
const toolbarContainer = document.querySelector('#toolbar-container');
toolbarContainer.appendChild(editor.ui.view.toolbar.element);
this.editor = editor //将编辑器保存起来,用来随时获取编辑器中的内容等,执行一些操作
//使用图片上传适配器
editor.plugins.get('FileRepository').createUploadAdapter = ( loader ) => {
return new UploadImageAdapter(loader)
};
editor.model.document.on('change:data', () => {
//把富文本编辑器获取到的内容传出去
this.$emit('input', editor.getData())
});
//编辑的时候设置富文本编辑器的内容
editor.setData(this.value);
}).catch(error => {
console.error(error);
});
},
}
}
</script>
<style lang="less" scoped>
//设置编辑器内容区域最小高度
#editor {
min-height: 380px;
border: 1px solid #c4c4c4;
}
/deep/.ck-content {
min-height: 380px;
}
</style>
图片上传成功后 我们也许会有在编辑器中拖拽改变图片大小的需求,ckeditor中也是比较方便的,自带ImageResize插件,该插件在@ckeditor/ckeditor5-image 下面,安装该插件使用即可。
踩坑1:当我们使用build版本时,可能已经安装了@ckeditor/ckeditor5-image 但是没有引入ImageResize,这时我们再安装就会报错 ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated.
如何解决呢? 我的解决办法是 从github上clone一份对应版本没有打包的源代码,修改配置,重新打包,替换项目依赖中的build文件夹
git clone https://github.com/ckeditor/ckeditor5-editor-decoupled.git
//clone完成后在项目目录src下的ckeditor.js文件中
import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize';
//把ImageResize加到DecoupledEditor.builtinPlugins中
//npm run build
踩坑2: 由于我是模拟v-model的实现,在富文本内容发生变化的时候向外触发input事件并发送内容,富文本组件接受名为value的props,监听value值改变就调用setData方法,这会导致一个问题, 每次输入change都会被触发两次(一次是用户输入内容触发的,一次是value改变setData触发),不知到是不是这个原因,每次输入内容都会导致光标回到内容的开始位置。
解决方法:不在采用类似v-model方式,而是向外暴露两个方法
//数据变化时把值保存到content字段
editor.model.document.on('change:data', () => {
this.content = editor.getData();
});
//向外暴露获取值和设置值的方法
getContent() {
return this.content
}
setContent(val) {
this.editor.setData(val)
}
//在使用页面
<ck-editor ref="editor"></ck-editor>
//获取值
let content = this.$refs.editor.getContent()
//设置值
this.$refs.editor.setContent(data)