前言
在工作中遇到一个需要使用富文本编辑器的场景,当时经过考虑使用了 vue-quill-editor (基于 Quill、适用于 Vue 的富文本编辑器,支持服务端渲染和单页应用。) 当时自己也没做富文本编辑器的经验,在上传图片的时候就遇到了一个坑:
通过使用富文本编辑的上传图片后其实是 base64 的图片,图片稍微大点,就是很长很长一串url。提交给后端时图片地址长度就超过了限制。
为了解决这个问题,就二次封装了下 vue-quill-editor,将图片上传到了七牛云。
安装 + 引入
npm install vue-quill-editor --save
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor, /* { default global options } */)
一般来说富文本样式都会根据具体 ui 再次重写的。
实现步骤
分析组件需求(props)
- 上传图片的地址 uploadUrl,默认是七牛云地址
- 上传图片的 file 控件 name
- 动态ref,一个页面多次使用该组件 ref 名相同可能会产生问题,需要每次外部引用时传入不同的的 editorName,来初始化 eidtor
- 富文本的内容 editorContent (v-model 值)
- 为了可扩展,如果图片上传之后的处理方式不同,可以传入一个 promise 的回调函数 那么组件的 props 是
props: {
editorContent: {
type: String,
required: true
},
editorName: {
type: String,
required: true
},
/* 上传图片的地址 */
uploadUrl: {
type: String,
default: "http://up-z0.qiniu.com" //默认为七牛的地址
},
/* 上传图片的file控件 name */
fileName: {
type: String,
default: "file"
},
uploadImageCb: {
type: Function
}
}
利用 vue-quill-editor 的自定义插槽 toolbar。
<template>
<div>
<quilleditor v-model="content" :ref="editorName" :options="editorOption" @change="onChange">
<div :id="name" slot="toolbar">
<span class="ql-formats">
<button type="button" class="ql-bold"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-italic"></button>
</span>
<span class="ql-formats">
<button type="button" @click="imgClick" style="outline:none">
<svg viewBox="0 0 18 18">
<rect class="ql-stroke" height="10" width="12" x="3" y="4" />
<circle class="ql-fill" cx="6" cy="7" r="1" />
<polyline class="ql-even ql-fill" points="5 12 5 11 7 9 8 10 11 7 13 9 13 12 5 12" />
</svg>
</button>
</span>
<span class="ql-formats">
<button type="button" class="ql-video"></button>
</span>
</div>
</quilleditor>
</div>
</template>
如上代码,自定义需要的工具图标。如上添加了 imgClick 事件的图标的就是我们的上传图片工具图标。
通过 computed 实例化 editor
引入 vue-quill-editor
import { quillEditor } from "vue-quill-editor";
components: {
quilleditor: quillEditor
}
其他初始化代码
data() {
return {
editorOption: {
modules: {
toolbar: {
container: `#${this.editorName}`, // 容器Id
}
}
},
content: '*' // 默认的 editorContent 内容
};
},
computed: {
...mapGetters("admin", { configData: "config/getData" }), // 这里是为了使用 store 中的七牛云 token
editor() {
return this.$refs[this.editorName].quill;
} // 根据不同的 editorName 生成 editor 实例
},
mounted() {
this.content = this.editorContent; // 初始化 富文本编辑器的内容
},
重写点击上传图片按钮
imgClick() {
if (!this.uploadUrl) {
console.log("no editor uploadUrl");
return;
}
/*内存创建input file*/
var input = document.createElement("input");
input.type = "file";
input.name = this.fileName;
input.accept = "image/jpeg,image/png,image/jpg,image/gif";
input.onchange = this.onFileChange;
input.click();
this.editor.focus();
}
/*监听 onchange*/
onFileChange(e) {
const file = e.target.files[0];
if (file.length === 0) {
return;
}
/* 图片限制 */
if (this.beforeUpload(file)) {
/* 上传图片 */
// 如果上传到其他服务器而不是七牛云,这里可能需要做一个判断
if (notQiniu) {
// 自定义cb函数
this.uploadImageCb().then(() => {
//do something
})
} else {
this.uploadImage(file);
}
}
},
beforeUpload(file) {
const limits = this.$store.getters.limits.logoSizeLimit;
const size = Math.floor(Number(limits) / 1024);
const isLimit = file.size < limits;
if (!isLimit) {
this.$message.error(`上传图片大小不可超过 ${size} KB`);
}
return isLimit;
},
// 上传重点部分
uploadImage(file) {
let data = new FormData();
data.append("token", this.configData.upload_data.token); // 添加七牛云 token
data.append(this.fileName, file);
request({
url: this.uploadUrl, // url
method: "post",
data: data,
headers: {
"content-type": "application/x-www-form-urlencoded"
}
}).then(res => {
this.editor.insertEmbed(
this.editor.getSelection().index,
"image",
"https://www.abc.com/" + res.key
); // 利用 getSelection 和 insertEmbed,根据业务需求重新设置 上传后图片的 url
});
},
监听 editor 的 change 事件,将内容 emit
结合 template 看 @change="onChange"
onChange() {
this.$emit("input", this.content);
},
如何使用该组件
<ql-editor v-model="editorContent" editorName="requestContent" ></ql-editor>
v-model 内容和 editorName 是必填的,其他的 props 可选择性传入, 上传到其他 url 上的处理可能需要结合 uploadImageCb 来解决,因为默认的处理是按照上传到七牛的方式来处理的。所以上传到其他服务器上需要结合 uploadImageCb 函数来做扩展。
总结
主要就是结合 vue-quill-editor 的 slot 和 使用 getSelection 和 insertEmbed 重写一些图标工具。然后在此基础上考虑更多问题,比如上传视频到七牛云,考虑封装的组件的扩展性等问题。