基于 vue-quill-editor 二次封装支持上传图片到七牛云或自己的服务器

3,314 阅读3分钟

前言

在工作中遇到一个需要使用富文本编辑器的场景,当时经过考虑使用了 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)

点击查看源码

  1. 上传图片的地址 uploadUrl,默认是七牛云地址
  2. 上传图片的 file 控件 name
  3. 动态ref,一个页面多次使用该组件 ref 名相同可能会产生问题,需要每次外部引用时传入不同的的 editorName,来初始化 eidtor
  4. 富文本的内容 editorContent (v-model 值)
  5. 为了可扩展,如果图片上传之后的处理方式不同,可以传入一个 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 重写一些图标工具。然后在此基础上考虑更多问题,比如上传视频到七牛云,考虑封装的组件的扩展性等问题。