CKEditor5 Vue 入门使用教程

9,254 阅读7分钟

CKEditor 介绍

CKEditor是一款非常全面的富文本编辑器,也出来很多年了,各方面支持得都比较好(对vue、react、angular都做了相应的封装),今天主要给大家总结下自己在使用过程中的经验。本文主要总结的是 CKEditor5 在vue中的用法,包括classic、inline、ballon和document editor等。

相关连接

使用方法

CKEditor对Vue做了开箱即用的封装,不过功能上肯定做了相应的缩减,具体的使用方法可以参考说明文档,使用跟其他的插件都差不多。这里主要说明下原生的使用方法。

Classic (Inline/Ballon/Ballon block) Editor

Document Editor略微特殊,后面单独做说明

  • 安装
npm install @ckeditor/ckeditor5-build-classic --save
npm install @ckeditor/ckeditor5-build-inline --save
npm install @ckeditor/ckeditor5-build-ballon --save
npm install @ckeditor/ckeditor5-build-ballon-block --save
  • 创建
// 以Classic Editor为例,其他类型的Editor使用方法类似,换一下引入的包即可
<template>
	<div class="classic-editor">
		<div id="editor-classic" />
	</div>
</template>

<script lang='ts'>
	import { Component, Vue } from "vue-property-decorator";
	import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
	import "@ckeditor/ckeditor5-build-classic/build/translations/zh-cn";
	export default class extends Vue {
		// 编辑器显示的初始内容
		private editorDataCopy = "";
		// 存储编辑器的实例
		private editor: any = null;
		// 语言改为中文
		private editorConfig = {
			language: "zh-cn"
		}

		mounted() {
			this.initEditor()
		}

		/**
		* 初始化容器
		*/
		public initEditor() {
			ClassicEditor.create(
				document.getElementById("editor-classic"),
				this.editorConfig
			)
			.then((editor: any) => {
				this.editor = editor;
				// 初始化设置编辑器里面的内容
				editor.setData(this.editorDataCopy);})
			.catch((error: any) => {
				console.error("There was a problem initializing the editor.", error);
			});
		}

		// 销毁editor
		beforeDestroy() {
			if(this.editor) {
				this.editor.destroy();
			}
		}
	}
</script>

<style lang="scss">
.classic-editor {
  .ck-editor__editable {
    height: 300px;
    border: 1px solid #ddd !important;
    box-shadow: none !important;
  }
}
</style>

Document Editor

  • 安装
npm install @ckeditor/ckeditor5-build-decoupled-document --save
  • 创建
<template>
  <div style="height: 100%">
    <div id="toolbar-container" class="toolbar-container" />
    <div id="editable-container" class="editable-container" />
  </div>
</template>

<script lang='ts'>
import DecoupledEditor from "@ckeditor/ckeditor5-build-decoupled-document";
import "@ckeditor/ckeditor5-build-decoupled-document/build/translations/zh-cn";

// 前面的代码都一样,不赘述了,主要是创建editor的时候有点变化
/**
* 初始化容器
*/
public initEditor() {
    DecoupledEditor.create(this.editorDataCopy, this.editorConfig)
      .then((editor: any) => {
        this.editor = editor
	// document editor需要单独添加一下toolbar
        const toolbarContainer = document.getElementById("toolbar-container");
        if (toolbarContainer) {
          toolbarContainer.appendChild(editor.ui.view.toolbar.element);
        }
      })
      .catch((error: any) => {
        console.error("There was a problem initializing the editor.", error);
      });
}
</script>

<style lang="scss">
// 这个是官方demo的默认样式(会好看点)
.editable-container {
	position: relative;
	border: 1px solid #ddd;
	border-top: 0;
	background: #eee;
	padding: 3em;
	overflow-y: scroll;
	height: calc(100% - 40px);
	.ck-editor__editable {
		width: 21cm;
		max-width: 100%;
		min-height: 29.7cm;
		margin: 0 auto;
		padding: 1cm 1.2cm;
		border: 1px #d3d3d3 solid !important;
		background-color: #fff;
		box-shadow: 0 0 5px rgba(0, 0, 0, 0.1) !important;
	}
}
</style>

自定义图片上传适配器(image upload adapter)

CKEditor5 可以支持自定义图片上传适配器,主要就是定义上传图片的方法,传递参数以及返回参数等。如果上传的方式比较简单,也可以考虑使用官方定义好的几种插件:Easy ImageSimple adapterBase64 adapter。下面主要介绍下自定义上传插件的方法。

图片上传的处理过程

  • 将图片放置到编辑器中(从剪贴板中粘贴,拖拽或从系统文件夹中上传)
  • 这些图片会被image upload插件拦截,对于每张上传的图片,image upload会创建一个file loader的实例
    • file loader 是将文件从磁盘中读取,并通过upload adapter上传到服务器
    • upload adapter则是提交http请求将图片上传至服务器上,并返回相应url
  • 在图片上传的过程中,image upload插件会进行如下操作:
    • 为图片创建占位符
    • 将它插入到编辑器中
    • 展示图片上传的进度条
    • 当图片被删除时,中止文件的上传
  • 当图片上传成功时,upload adapter会解析Promise对象返回的值,并将对应的url设置到图片的src中

上传适配器定义

官方文档中使用的是XMLHttpRequest,由于公司项目中使用的是axios,所以这里主要介绍一下axios的用法,若不符合需求,可以参考官方文档

class ImgUploadAdapter {
	private loader: any;

	constructor(loader: any) {
		this.loader = loader
	}

	// postFile是基于axios封装的一个文件上传请求函数,请求参数和返回参数类型都和具体的接口实现方式相关,大家可以基于接口文档自行定义。由于本文的接口返回的是相对路径,因此需要加上对应的头部url
	upload() {
		return new Promise((resolve, reject) => {
			const data = new FormData()
			this.loader.file.then((file: any) => {
				data.append('file', file)
				postFile(FileTypeEnum.image, data).then((response: IResponseData<{ url: string }>) => {
					resolve({
						default: `${process.env.VUE_APP_BASE_API}/api/v1/auth/file/get/image?url=${response.data.url}`
					})
				}).catch(error => {
					reject(error)
				})
			})
		})
	}

	// 中止文件上传的方法,可以自行实现(axios中有取消请求的方法,由于代码里axios做了一层封装,这里就不贴详细代码了)
	abort() {}
}

其他配置

到这里基本上可以简单地使用CKEditor5了,这里也只介绍了其中很小的一部分内容,其他的一些基础配置可以参考官方文档,写得蛮详细的(缺点就是全英文的,耐下性子慢慢看,哈哈)

常见问题

ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated

这里提一下自己在使用过程中遇到的一个问题,就是多种类型编辑器同时使用时会出现下面这样的报错,官方文档中提到不要同时使用两种编译好的编辑器,因为会出现各种冲突:

avatar

  • 出现冲突原因

    • 代码的重复度太高,不同编译版本的编辑器,可能会共享超过99%的代码,同时加载2次及以上,会让整个页面变得很笨重
    • 一些重复的css样式可能会让编辑器显示出现问题
    • 编译版本代码中包含的翻译代码重复可能会导致翻译结果不准确
  • 解决办法

    官方提供了两种解决办法,一种是在webpack config中构建,另一种是重新构建CKEditor5的代码,本文主要介绍后一种方法,主要的步骤如下:

    • 克隆官方的代码(这里以ckeditor5-build-classic为例)
    git clone -b stable https://github.com/ckeditor/ckeditor5-build-classic.git
    cd ckeditor5-build-classic
    npm install
    
    • 安装其他版本的编辑器(这里将inline/ballon/document都放进来),注意这里安装是不是构建好的版本,是editor不是build
    npm install --save-dev @ckeditor/ckeditor5-editor-inline @ckeditor/ckeditor5-editor-ballon @ckeditor/ckeditor5-editor-decoupled
    
    • 重写webpack入口文件src/ckeditor.js
    // 需要用到的编辑器
    import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
    import InlineEditorBase from '@ckeditor/ckeditor5-editor-inline/src/inlineeditor';
    import DecoupledEditorBase from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
    import BalloonEditorBase from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor';
    
    class ClassicEditor extends ClassicEditorBase { }
    class InlineEditor extends InlineEditorBase { }
    class DecoupledEditor extends DecoupledEditorBase { }
    class BalloonEditor extends BalloonEditorBase { }
    
    // 初始加载的插件
    const plugins = [...]
    ClassicEditor.builtinPlugins = plugins
    InlineEditor.builtinPlugins = plugins
    DecoupledEditor.builtinPlugins = plugins
    BalloonEditor.builtinPlugins = plugins
    
    // 初始配置
    const config = {...}
    ClassicEditor.defaultConfig = config;
    InlineEditor.defaultConfig = config;
    DecoupledEditor.defaultConfig = config;
    BalloonEditor.defaultConfig = config;
    
    export default {
        ClassicEditor, InlineEditor, DecoupledEditor, BalloonEditor
    }
    
    • 重新定义webpack中全局变量名
    // webpack.config.js
    output: {
    // library: 'ClassicEditor'
    library: 'CKEditor'
    }
    
    • 重新编译
    // 当完成src/ckeditor.js和webpack.config.js的改变后,需要执行npm build,重新编译下代码
    npm run build
    
    • 使用测试
    // 完成后可以在samples/index.html中进行测试
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="utf-8">
    <title>CKEditor 5 – super build</title>
    <style>
    body {
        max-width: 800px;
        margin: 20px auto;
    }
    </style>
    </head>
    
    <body>
    <h1>CKEditor 5 – super build</h1>
    
    <div id="classic-editor">
        <h2>classic editor</h2>
        <p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a>.</p>
    </div>
    
    <div id="inline-editor">
        <h2>inline editor</h2>
        <p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#inline-editor">inline editor build</a>.</p>
    </div>
    
    <div id="document-editor">
        <h2>document editor</h2>
        <p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#document-editor">document editor build</a>.</p>
    </div>
    
    <div id="balloon-editor">
        <h2>balloon editor</h2>
        <p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#balloon-editor">balloon editor build</a>.</p>
    </div>
    
    <script src="../build/ckeditor.js"></script>
    <script>
    CKEditor.ClassicEditor
        .create(document.querySelector('#classic-editor'))
        .catch(err => {
            console.error(err.stack);
        });
    
    CKEditor.InlineEditor
        .create(document.querySelector('#inline-editor'))
        .catch(err => {
            console.error(err.stack);
        });
    
    CKEditor.DecoupledEditor
        .create(document.querySelector('#document-editor'))
        .catch(err => {
            console.error(err.stack);
        });
    
    CKEditor.BalloonEditor
        .create(document.querySelector('#balloon-editor'))
        .catch(err => {
            console.error(err.stack);
        });
    </script>
    </body>
    </html>
    
    • 完成编译后的使用方式
      • 发布到npm仓库
      • 直接在script中引入编译好的js文件

    如果你没有同时使用多种编辑器,那很有可能是版本的问题,把CKEditor相关的版本都升到最新,然后删掉package-lock.json和node_modules文件夹,重新执行下`npm install`