引用Quill组件

1.下载组件。在package中引入:

"@vueup/vue-quill": "^1.0.0-beta.8",
"vue-quill-editor": "^3.0.6",
"quill-image-resize-module": "^3.0.0",
"quill-image-drop-module": "^1.0.3",
"quill-image-paste-module": "^1.0.6",

2.在vue.config.js中添加以下代码,不然会报错。

chainWebpack: config => {
    config.plugin('provide').use(webpack.ProvidePlugin, [{
      'window.Quill': 'quill/dist/quill.js',
      'Quill': 'quill/dist/quill.js'
    }])
  }

3.引用组件

import { QuillEditor, Quill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { UPLOAD_IMAGE_PATH } from '@/api/BASE_MODULE/upload'//上传图片接口
import $ from 'jquery'
import ImageResize from 'quill-image-resize-module' // 调整大小组件。
Quill.register('modules/ImageResize', ImageResize)
import { ImageDrop } from 'quill-image-drop-module'
Quill.register('modules/imageDrop', ImageDrop)
import { ImageExtend } from 'quill-image-paste-module'
Quill.register('modules/ImageExtend', ImageExtend)

自定义富文本

引用位置

<div class="editor-wrap" id="editor-wrap">
    <QuillEditor ref="editorWrap" :options="options" theme="snow" @update:content="onEditorChange" />
</div>

添加工具栏

此处并不包含上传图片和汉化功能。使用时需要在此处引用:

components: {
   QuillEditor
}

引用工具栏:

data() {
    return {
      options: {
        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: ['small', false, 'large', 'huge'] }], // 字体大小
              [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
              //  [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
              //  [{ font: [] }], // 字体种类
              [{ align: [] }], // 对齐方式
              ['clean'], // 清除文本格式
              ['image'] // 链接、图片、视频
            ],
          }
        }
      },
    }
  },
// 内容改变事件
    onEditorChange(params) {
      const contentText = this.$refs.editorWrap.getText()
      if (contentText === '') {
        this.temp.typeDescribe = ''
      } else {
        //console.log(this.$refs.editorWrap.getHTML())
        //const str = this.$refs.editorWrap.getHTML()
        this.temp.typeDescribe = this.$refs.editorWrap.getHTML()
        //this.temp.content = str.replace(/"/g, '\"')
        // console.log(this.$refs.editorWrap.getHTML())
      }
    },

添加汉化功能,鼠标放在图标上会出现提示

1.把以下代码添加到options外,return内:

tooltips: [
   { choice: 'ql-bold', title: '加粗' },
   { choice: 'ql-italic', title: '斜体' },
   { choice: 'ql-size', title: '字体大小' },
   { choice: 'ql-underline', title: '下划线' },
   { choice: 'ql-strike', title: '删除线' },
   { choice: 'ql-link', title: '添加链接' },
   { choice: 'ql-image', title: '添加图片' },
   { choice: 'ql-color', title: '字体颜色' },
   { choice: 'ql-background', title: '背景颜色' },
   { choice: 'ql-clean', title: '清除格式' },
   { choice: 'super', title: '上标' },
   { choice: 'sub', title: '下标' },
   { choice: 'ordered', title: '编号列表' },
   { choice: 'bullet', title: '项目列表' },
   { choice: '-1', title: '向左缩进' },
   { choice: '+1', title: '向右缩进' }
 ],

2.把以下代码放在mounted中:

   const buttonList = $('.ql-toolbar').find('button[type="button"]')
   const _this = this
   buttonList.each(function(i) {
     const currentClass = $(this).attr('class')
     const currentValue = $(this).attr('value')
     for (const item of _this.tooltips) {
       if (item.choice === currentClass) {
         $(this).prop('title', item.title)
       } else if (item.choice === currentValue) {
         $(this).prop('title', item.title)
       } else {
         continue
       }
     }
   })
   $('span.ql-align .ql-picker-label').each(function(i) {
     $(this).prop('title', '对齐方式')
   })
   $('span.ql-align .ql-picker-options .ql-picker-item').each(function(i) {
     switch (i) {
       case 0:
         $(this).prop('title', '左对齐')
         break
       case 1:
         $(this).prop('title', '居中对齐')
         break
       case 2:
         $(this).prop('title', '右对齐')
         break
       case 3:
         $(this).prop('title', '两端对齐')
         break
     }
   })

添加上传图片,支持粘贴

由于富文本在上传图片时,默认存储的为base64格式,这样会占用很大的内存空间,通过劫持上传按钮,我们可以自定义上传功能,把地址存储到数据库,并插入image标签,通过图片地址回显。
1、添加上传按钮,并加以隐藏

<!-- 上传图片组件 隐藏 -->
<el-upload
class="avatar-uploader"
:action="upload.url"
name='upfile'
:headers="headerObj"//可选
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload">
</el-upload>

2、在modules中加入以下代码:

// 新增下面
imageDrop: false, // 拖动加载图片组件。
imageResize: { //调整大小组件。
  displayStyles: {
    backgroundColor: 'black',
    border: 'none',
    color: 'white'
  },
  modules: ['Resize', 'DisplaySize', 'Toolbar']
},
clipboard: {
  // 粘贴版,处理粘贴时候带图片
  matchers: [[Node.ELEMENT_NODE, this.handleCustomMatcher]]
},

劫持点击上传图标事件:

handlers: {
   'image': function(value) {
      if (value) {
        document.querySelector('.avatar-uploader input').click()
      } else {
        this.Quill.format('image', false)
      }
    }
  }

3、在mounted中添加以下代码:

//  自定义粘贴图片功能
    const quill = this.$refs.editorWrap.getQuill()
    quill.root.addEventListener(
      'paste',
      evt => {
        if (
          evt.clipboardData &&
          evt.clipboardData.files &&
          evt.clipboardData.files.length
        ) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return false
            }
            const formData = new FormData()
            formData.append('upfile', file) //后台上传接口的参数名
            // 实现上传
            $.ajax({
              type: 'post',
              url: UPLOAD_IMAGE_PATH, // 上传的图片服务器地址
              data: formData,
              dataType: 'json',
              processData: false,
              contentType: false, //设置文件上传的type值,必须
              success: (response) => {
                if (response.code === 2000) {
                  console.log(response)
                  // 获取光标所在位置
                  const length = quill.getSelection().index
                  // 插入图片 dt.url为服务器返回的图片地址
                  quill.insertEmbed(length, 'image', process.env.VUE_APP_API_URL + response.data.filePath)
                  // 调整光标到最后
                  quill.setSelection(length + 1)
                }
              },
              error: function() {
                this.$message.error('上传失败!')
              }
            })
          })
        }
      }
    )

4、在methods中添加以下代码:

// 上传前校检格式和大小
    beforeUpload(file) {
      const regArr = ['.gif', '.jpg', '.jpeg', '.png']
      const lastName = file.name.slice(file.name.lastIndexOf('.'))
      if (regArr.indexOf(lastName) === -1) {
        this.$message.error(`仅支持.gif/.jpg/.jpeg/.png格式!`)
        return false
      }
      // 校检文件大小
      const isLt = file.size / 1024 / 1024 < 5
      if (!isLt) {
        this.$message.error(`上传文件大小不能超过5MB!`)
        return false
      }
      return true
    },
    //上传成功或者失败
    uploadSuccess(res) {
      // console.log(res)
      // console.log(res.data.filePath)
      const quill = this.$refs.editorWrap.getQuill()
      // 如果上传成功
      if (res.code === 2000 && res.filePath !== null) {
        // 获取光标所在位置
        const length = quill.getSelection().index
        // 插入图片 dt.url为服务器返回的图片地址
        quill.insertEmbed(length, 'image', process.env.VUE_APP_API_URL + res.data.filePath)
        // 调整光标到最后
        quill.setSelection(length + 1)
      } else {
        this.$message.error('图片插入失败')
      }
      // loading加载隐藏
      this.quillUpdateImg = false
    },
    //上传失败
    uploadError() {
      this.$message.error('图片插入失败')
    },
    handleCustomMatcher(node, Delta) {
      // 文字,从别处复制而来,清除自带样式,转为纯文本
      if (node.src && node.src.indexOf('data:image/png') > -1) {
        Delta.ops = []
        return Delta
      }
      const ops = []
      Delta.ops.forEach(op => {
        if (op.insert && typeof op.insert === 'string') {
          ops.push({
            insert: op.insert
          })
        } else if (op.insert && typeof op.insert.image === 'string') {
          ops.push({
            insert: op.insert
          })
        }
      })
      Delta.ops = ops
      return Delta
    },
    // 内容改变事件
    onEditorChange(params) {
      const contentText = this.$refs.editorWrap.getText()
      if (contentText === '') {
        this.temp.typeDescribe = ''
      } else {
        //console.log(this.$refs.editorWrap.getHTML())
        //const str = this.$refs.editorWrap.getHTML()
        this.temp.typeDescribe = this.$refs.editorWrap.getHTML()
        //this.temp.content = str.replace(/"/g, '\"')
        // console.log(this.$refs.editorWrap.getHTML())
      }
    },

完整示例:

<template>
  <div class="">
    <el-form
      :rules="rules"
      ref="dataForm"
      :model="temp"
      label-width="100px"
      style='width:100%;'
    >
      <el-form-item label="上级分类:">
        {{temp.parentName}}
      </el-form-item>

      <el-form-item label="分类名称:" prop="softwareTypeName">
        <el-input v-model="temp.softwareTypeName" placeholder="请输入软件分类名称"></el-input>
      </el-form-item>

      <el-form-item label="分类说明:" prop="typeDescribe">
        <!-- <textarea v-model="temp.typeDescribe" placeholder="请输入分类说明" style="height:80px;width:100%;"></textarea> -->
        <div class="editor-wrap" id="editor-wrap">
          <QuillEditor ref="editorWrap" :options="options" theme="snow" @update:content="onEditorChange" />
        </div>
        <!-- 上传图片组件隐藏 -->
        <el-upload
        class="avatar-uploader"
        :action="upload.url"
        name='upfile'
        :headers="headerObj"//可选
        :show-file-list="false"
        :on-success="uploadSuccess"
        :on-error="uploadError"
        :before-upload="beforeUpload">
        </el-upload>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import { QuillEditor, Quill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { mapGetters } from 'vuex'
// import { toRaw } from 'vue'
import { UPLOAD_IMAGE_PATH } from '@/api/BASE_MODULE/upload'
import $ from 'jquery'
import ImageResize from 'quill-image-resize-module' // 调整大小组件。
Quill.register('modules/ImageResize', ImageResize)
import { ImageDrop } from 'quill-image-drop-module'
Quill.register('modules/imageDrop', ImageDrop)
import { ImageExtend } from 'quill-image-paste-module'
Quill.register('modules/ImageExtend', ImageExtend)

export default {
  name: 'Create',
  computed: {
    ...mapGetters([
      'drawerCurrent'
    ]),
    drawerType: function() {
      return this.drawerCurrent.drawerType
    }
  },
  components: {
    QuillEditor
  },
  data() {
    return {
      // 用户导入参数
      upload: {
        // 设置上传的请求头部
        // headers: { Authorization: "Bearer " + getToken() },
        // 上传的地址
        url: UPLOAD_IMAGE_PATH
      },
      options: {
        modules: {
          // 新增下面
          imageDrop: false, // 拖动加载图片组件。
          imageResize: { //调整大小组件。
            displayStyles: {
              backgroundColor: 'black',
              border: 'none',
              color: 'white'
            },
            modules: ['Resize', 'DisplaySize', 'Toolbar']
          },
          clipboard: {
            // 粘贴版,处理粘贴时候带图片
            matchers: [[Node.ELEMENT_NODE, this.handleCustomMatcher]]
          },
          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: ['small', false, 'large', 'huge'] }], // 字体大小
              [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
              //  [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
              //  [{ font: [] }], // 字体种类
              [{ align: [] }], // 对齐方式
              ['clean'], // 清除文本格式
              ['image'] // 链接、图片、视频
            ],
            handlers: {
              'image': function(value) {
                if (value) {
                  document.querySelector('.avatar-uploader input').click()
                } else {
                  this.Quill.format('image', false)
                }
              }
            }
          }
        }
      },
      tooltips: [
        { choice: 'ql-bold', title: '加粗' },
        { choice: 'ql-italic', title: '斜体' },
        { choice: 'ql-size', title: '字体大小' },
        { choice: 'ql-underline', title: '下划线' },
        { choice: 'ql-strike', title: '删除线' },
        { choice: 'ql-link', title: '添加链接' },
        { choice: 'ql-image', title: '添加图片' },
        { choice: 'ql-color', title: '字体颜色' },
        { choice: 'ql-background', title: '背景颜色' },
        { choice: 'ql-clean', title: '清除格式' },
        { choice: 'super', title: '上标' },
        { choice: 'sub', title: '下标' },
        { choice: 'ordered', title: '编号列表' },
        { choice: 'bullet', title: '项目列表' },
        { choice: '-1', title: '向左缩进' },
        { choice: '+1', title: '向右缩进' }
      ],
      temp: {
        status: 1,
        parentName: '',
        parentId: ''
      },
      rules: {
        softwareTypeName: [
          { required: true, message: '请输入软件类型名称', trigger: 'change' },
          { max: 50, message: '名称最大长度50位', trigger: 'change' }
        ],
        describe: [
          { required: true, message: '请输入软件类型描述', trigger: 'change' }
        ]
      }
    }
  },
  watch: {
    // 表单内容体
    temp: {
      handler: function(val, oldVal) {
      },
      deep: true
    }
  },
  created() {
  
  },
  mounted() {
    //  自定义粘贴图片功能
    const quill = this.$refs.editorWrap.getQuill()
    quill.root.addEventListener(
      'paste',
      evt => {
        if (
          evt.clipboardData &&
          evt.clipboardData.files &&
          evt.clipboardData.files.length
        ) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return false
            }
            const formData = new FormData()
            formData.append('upfile', file) //后台上传接口的参数名
            // 实现上传
            $.ajax({
              type: 'post',
              url: UPLOAD_IMAGE_PATH, // 上传的图片服务器地址
              data: formData,
              dataType: 'json',
              processData: false,
              contentType: false, //设置文件上传的type值,必须
              success: (response) => {
                if (response.code === 2000) {
                  console.log(response)
                  // 获取光标所在位置
                  const length = quill.getSelection().index
                  // 插入图片 dt.url为服务器返回的图片地址
                  quill.insertEmbed(length, 'image', process.env.VUE_APP_API_URL + response.data.filePath)
                  // 调整光标到最后
                  quill.setSelection(length + 1)
                }
              },
              error: function() {
                this.$message.error('上传失败!')
              }
            })
          })
        }
      }
    )
    const temp = this.drawerCurrent.params
    // console.log(temp)
    if (this.drawerType === 'CREATE_SECTION') {
      this.temp = Object.assign({}, this.temp, temp)
    } else {
      this.temp.parentId = temp.parentId
      this.temp.parentName = temp.parentName
      this.temp.softwareTypeName = temp.softwareTypeName
      this.temp.typeDescribe = temp.typeDescribe
      this.temp.id = temp.id
      this.$refs.editorWrap.setHTML(temp.typeDescribe)
    }
    const buttonList = $('.ql-toolbar').find('button[type="button"]')
    const _this = this
    buttonList.each(function(i) {
      const currentClass = $(this).attr('class')
      const currentValue = $(this).attr('value')
      for (const item of _this.tooltips) {
        if (item.choice === currentClass) {
          $(this).prop('title', item.title)
        } else if (item.choice === currentValue) {
          $(this).prop('title', item.title)
        } else {
          continue
        }
      }
    })
    $('span.ql-align .ql-picker-label').each(function(i) {
      $(this).prop('title', '对齐方式')
    })
    $('span.ql-align .ql-picker-options .ql-picker-item').each(function(i) {
      switch (i) {
        case 0:
          $(this).prop('title', '左对齐')
          break
        case 1:
          $(this).prop('title', '居中对齐')
          break
        case 2:
          $(this).prop('title', '右对齐')
          break
        case 3:
          $(this).prop('title', '两端对齐')
          break
      }
    })
  },
  methods: {
    // 上传前校检格式和大小
    beforeUpload(file) {
      const regArr = ['.gif', '.jpg', '.jpeg', '.png']
      const lastName = file.name.slice(file.name.lastIndexOf('.'))
      if (regArr.indexOf(lastName) === -1) {
        this.$message.error(`仅支持.gif/.jpg/.jpeg/.png格式!`)
        return false
      }
      // 校检文件大小
      const isLt = file.size / 1024 / 1024 < 5
      if (!isLt) {
        this.$message.error(`上传文件大小不能超过5MB!`)
        return false
      }
      return true
    },
    //上传成功或者失败
    uploadSuccess(res) {
      // console.log(res)
      // console.log(res.data.filePath)
      const quill = this.$refs.editorWrap.getQuill()
      // 如果上传成功
      if (res.code === 2000 && res.filePath !== null) {
        // 获取光标所在位置
        const length = quill.getSelection().index
        // 插入图片 dt.url为服务器返回的图片地址
        quill.insertEmbed(length, 'image', process.env.VUE_APP_API_URL + res.data.filePath)
        // 调整光标到最后
        quill.setSelection(length + 1)
      } else {
        this.$message.error('图片插入失败')
      }
      // loading加载隐藏
      this.quillUpdateImg = false
    },
    //上传失败
    uploadError() {
      this.$message.error('图片插入失败')
    },
    handleCustomMatcher(node, Delta) {
      // 文字,从别处复制而来,清除自带样式,转为纯文本
      if (node.src && node.src.indexOf('data:image/png') > -1) {
        Delta.ops = []
        return Delta
      }
      const ops = []
      Delta.ops.forEach(op => {
        if (op.insert && typeof op.insert === 'string') {
          ops.push({
            insert: op.insert
          })
        } else if (op.insert && typeof op.insert.image === 'string') {
          ops.push({
            insert: op.insert
          })
        }
      })
      Delta.ops = ops
      return Delta
    },
    // 内容改变事件
    onEditorChange(params) {
      const contentText = this.$refs.editorWrap.getText()
      if (contentText === '') {
        this.temp.typeDescribe = ''
      } else {
        //console.log(this.$refs.editorWrap.getHTML())
        //const str = this.$refs.editorWrap.getHTML()
        this.temp.typeDescribe = this.$refs.editorWrap.getHTML()
        //this.temp.content = str.replace(/"/g, '\"')
        // console.log(this.$refs.editorWrap.getHTML())
      }
    },
    // 提交表单
    submitForm() {
      return this.$refs['dataForm'].validate()
    },
    // 获取表单数据
    getFormData() {
      // console.log(this.temp)
      return this.temp
    }
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.editor-wrap {
  display: block;
  width: 100%;
  height: 450px;

  ::v-deep .ql-container {
    height: calc(100% - 80px);
  }
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: '文本';
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  content: '标题1' !important;
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  content: '标题2' !important;
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  content: '标题3' !important;
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  content: '标题4' !important;
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  content: '标题5' !important;
}

::v-deep .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
::v-deep .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: '标题6' !important;
}
</style>
Logo

前往低代码交流专区

更多推荐