vue项目中使用富文本编辑器vue-quill-editor

2,397 阅读2分钟

记录一下工作中做的富文本编辑器功能,写的内容不是很详细,但是贴的代码完整,且亲测有效!

1,需要安装插件

npm install vue-quill-editor -S
npm install quill -S
// 自定义控制图片大小
npm i quill-image-resize-module -S
// 图片拖拽
npm i quill-image-drop-module -S
// 图片压缩
npm install compressorjs -S
  1. 引入到项目中

有两种挂载方式: 全局挂载 和 在组件中挂载,根据自己的项目需求选择,一般用到富文本编辑都是在某一个项目中,我们这里为了方便大家选择,两种引用方案都写下来以便大家参考:

(1),页面中引用

import { quillEditor } from 'vue-quill-editor'
 
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
 
export default {
  components: {
    quillEditor
  }
}

(2),全局中引用

import Vue from 'vue'
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, /* { 默认全局 } */)

3,组件(事例中挂载到全局,所以没有引入quillEditor。以下代码包含自定义样式,图片压缩,控制图片大小,图片拖拽)中的具体代码为

<template>
  <div>
    <quill-editor 
       class="ql-editor"
       v-model="content" 
       ref="myQuillEditor" 
       :options="editorOption" 
       @blur="onEditorBlur($event)" 
       @focus="onEditorFocus($event)"
       @change="onEditorChange($event)">
    </quill-editor>
    <el-upload
      v-show="false"
      class="avatar-uploader-edit"
      action="#"
      :show-file-list="false"
      accept=".jpg,.png,.jpeg"
      :before-upload="compressAndUpload"
      :http-request="onUploadHandler"
    />
  </div>
</template>
 
<script>
import Quill from 'quill' // 引入编辑器
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageResize', ImageResize)
Quill.register('modules/imageDrop', ImageDrop)
// 自定义字体大小
const Size = Quill.import('attributors/style/size')
Size.whitelist = ['10px', '12px', '16px', '18px', '20px', '30px', '32px']
Quill.register(Size, true)

// 自定义字体类型
var fonts = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'sans-serif']
var Font = Quill.import('formats/font')
Font.whitelist = fonts
Quill.register(Font, true)

export default {
  data() {
    return {
      content: `<p>这是 vue-quill-editor 的内容!</p>`, //双向数据绑定数据
      // 富文本编辑器配置
      editorOption: {
        modules: {
          toolbar: {
            container: [
              ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
              ['blockquote', 'code-block'], // 引用  代码块
              [{ header: 1 }, { header: 2 }], // 1、2 级标题
              [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
              [{ script: 'sub' }, { script: 'super' }], // 上标/下标
              [{ indent: '-1' }, { indent: '+1' }], // 缩进
              [{ direction: 'rtl' }], // 文本方向
              [{ size: ['12px', false, '16px', '18px', '20px', '30px'] }], // 字体大小
              [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
              [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
              [{ font: [false, 'SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial'] }], // 字体种类
              [{ align: [] }], // 对齐方式
              ['clean'], // 清除文本格式
              ['link', 'image'] // 链接、图片
            ],
            // 上传图片时,处理图片(图片压缩)
            handlers: {
              'image': function(value) {
                if (value) { // value === true
                  document.querySelector('.avatar-uploader-edit input').click()
                } else {
                  this.quill.format('image', false)
                }
              }
            }
          },
          // 放大缩小图片的配置选项
          imageResize: {
            modules: ['Resize', 'DisplaySize', 'Toolbar'],
          },
          // 启用拖拽图片功能
          imageDrop: true 
        },
        placeholder: '请输入正文'
      },
    }
  },
  methods: {
    // 失去焦点事件
    onEditorBlur(quill) {
      console.log('editor blur!', quill)
    },
    // 获得焦点事件
    onEditorFocus(quill) {
      console.log('editor focus!', quill)
    },
    // 准备富文本编辑器
    onEditorReady(quill) {
      console.log('editor ready!', quill)
    },
    // 内容改变事件
    onEditorChange({ quill, html, text }) {
      console.log('editor change!', quill, html, text)
      this.content = html
    },
    // 图片压缩上传方法
    compressAndUpload(file) {
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isLt2M) {
        this.$message.error('上传图片大小不能超过 2MB!')
        return
      }
      return new Promise((resolve, reject) => {
        new Compressor(file, {
          // 压缩质量,0-1之间。数字越小,压缩比越高
          quality: 0.2,
          convertSize: 50000, // 大于50k就需要压缩(必须要加上这个属性,否则png格式不压缩)
          success(result) {
            // 默认返回result是blob类型的文件,可以转成file并返回,交给afterRead钩子进行上传
            const newFile = new File([result], file.name, { type: file.type })
            resolve(newFile)
          },
          error(err) {
            reject(err)
          }
        })
      })
    },
    // 将压缩后的图片转化为base64,插入到编辑器中
    async onUploadHandler(e) {
      const reader = new FileReader()
      reader.readAsDataURL(e.file)
      reader.onload = event => {
        const base64 = event.target.result
        // 获取光标所在位置
        const quill = this.$refs.myQuillEditor.quill
        const length = quill.getSelection().index
        // 插入图片
        quill.insertEmbed(length, 'image', base64)
        // 调整光标到最后
        quill.setSelection(length + 1)
      }
    }
  }
}
</script>
<style lang="scss" scoped>
// 给文本内容加高度,滚动条
.quill-editor /deep/ .ql-container {
    min-height: 220px;
  }
 
  .ql-container {
    min-height: 230px;
  }
 
 /deep/ {
    .ql-snow .ql-tooltip [data-mode="link"]::before {
      content: "请输入链接地址:";
      left: 0;
    }
 
    .ql-snow .ql-tooltip.ql-editing {
      left: 0 !important;
    }
 
    .ql-snow .ql-tooltip {
      left: 0 !important;
    }
 
    .ql-snow .ql-editor {
      max-height: 300px;
    }
 
    .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
      border-right: 0px;
      content: "保存";
      padding-right: 0px;
    }
 
    .ql-snow .ql-tooltip[data-mode="video"]::before {
      content: "请输入视频地址:";
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label::before, .ql-snow .ql-picker.ql-size .ql-picker-item::before {
      content: "14px" !important;
      font-size: 14px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before {
      content: "10px" !important;
      font-size: 10px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before {
      content: "12px" !important;
      font-size: 12px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
      content: "16px" !important;
      font-size: 16px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
      content: "18px" !important;
      font-size: 18px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
      content: "20px" !important;
      font-size: 20px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="30px"]::before {
      content: "30px" !important;
      font-size: 30px;
    }
 
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="32px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="32px"]::before {
      content: "32px" !important;
      font-size: 32px;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label::before, .ql-snow .ql-picker.ql-header .ql-picker-item::before {
      content: "文本" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
      content: "标题1" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
      content: "标题2" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
      content: "标题3" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
      content: "标题4" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
      content: "标题5" !important;
    }
 
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
      content: "标题6" !important;
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label::before, .ql-snow .ql-picker.ql-font .ql-picker-item::before {
      content: "标准字体" !important;
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
      content: "衬线字体" !important;
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
      content: "等宽字体" !important;
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimSun"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimSun"]::before {
      content: "宋体" !important;
      font-family: "SimSun";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimHei"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimHei"]::before {
      content: "黑体" !important;
      font-family: "SimHei";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Microsoft-YaHei"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Microsoft-YaHei"]::before {
      content: "微软雅黑" !important;
      font-family: "Microsoft YaHei";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="KaiTi"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="KaiTi"]::before {
      content: "楷体" !important;
      font-family: "KaiTi";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="FangSong"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="FangSong"]::before {
      content: "仿宋" !important;
      font-family: "FangSong";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Arial"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Arial"]::before {
      content: "Arial" !important;
      font-family: "Arial";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Times-New-Roman"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Times-New-Roman"]::before {
      content: "Times New Roman" !important;
      font-family: "Times New Roman";
    }
 
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="sans-serif"]::before, .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="sans-serif"]::before {
      content: "sans-serif" !important;
      font-family: "sans-serif";
    }
 
    .ql-font-SimSun {
      font-family: "SimSun";
    }
 
    .ql-font-SimHei {
      font-family: "SimHei";
    }
 
    .ql-font-Microsoft-YaHei {
      font-family: "Microsoft YaHei";
    }
 
    .ql-font-KaiTi {
      font-family: "KaiTi";
    }
 
    .ql-font-FangSong {
      font-family: "FangSong";
    }
 
    .ql-font-Arial {
      font-family: "Arial";
    }
 
    .ql-font-Times-New-Roman {
      font-family: "Times New Roman";
    }
 
    .ql-font-sans-serif {
      font-family: "sans-serif";
    }
 
    .ql-snow.ql-toolbar .ql-formats .ql-revoke {
      background-image: url("../../../../assets/images/icons8-rotate-left-18.png");
      width: 20px;
      height: 20px;
      filter: grayscale(100%);
      opacity: 1;
    }
 
    .ql-snow.ql-toolbar .ql-formats .ql-revoke:hover {
      background-image: url("../../../../assets/images/icons8-rotate-left-18.png");
      width: 20px;
      height: 20px;
      filter: none;
      opacity: 0.9;
    }
    /*加上height和滚动属性就可以,滚动条样式是系统默认样式,可能不同*/
    .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
      border-color: #ccc;
      height: 125px;
      overflow: auto;
    }
  }
  
</style>

4,完成以上操作后,会发现控制台有一个quill-image-resize-module imports错误,解决办法也很简单

// vue.config.js 文件中配置
const webpack = require('webpack')  //别忘了这个
// vue 引入quill - image - resize - module 插件报错
module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        'window.Quill': 'quill',
      })
    ],
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules(?!\/quill-image-drop-module|quill-image-resize-module)/,
          loader: 'babel-loader'
        }
      ]
    }
  },
}