vue-quill-editor封装组件

一、解决问题

1、图片上传、视频上传

2、图片复制、图片拖拽和缩放

3、字体、字号

4、居中不生效

二、解决方式

1.组件内容 index.vue

<!--富文本编辑器-->
<template>
  <div class="RichTextEditor-Wrap" v-loading="loading">
    <quill-editor :content="content"
                  :options="editorOption"
                  class="ql-editor"
                  ref="myQuillEditor"
                  @change="onEditorChange($event)">
    </quill-editor>
    <!-- 图片上传组件辅助       :file-list="fileList"-->
    <el-upload
      v-show="false"
      :show-file-list="false"
      :name="uploadImgConfig.name"
      :multiple="false"
      :action="uploadImgConfig.uploadUrl"
      :before-upload="onBeforeUpload"
      :on-success="onSuccess"
      :on-error="onError">
      <button ref="myinput">上传文件</button>
    </el-upload>

    <!--视频上传组件辅助-->
    <el-upload
      v-show="false"
      :show-file-list="false"
      :action="uploadVideoConfig.uploadUrl"
      accept="video/*"
      :name="uploadVideoConfig.name"
      :before-upload="onBeforeUploadVideo"
      :on-success="onSuccessVideo"
      :on-error="onErrorVideo"
      :multiple="false">
        <button ref="myvideo">上传文件</button>
    </el-upload>
  </div>
</template>
<script>
  //main.js
  import VueQuillEditor from 'vue-quill-editor'
  Vue.use(VueQuillEditor);
  //样式
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'

  import {quillEditor, Quill} from 'vue-quill-editor'
  // 设置title
  import {addQuillTitle} from './quill-title.js'
  import ImageResize from 'quill-image-resize-module'; // 图片缩小放大插件
  import {ImageDrop}  from 'quill-image-drop-module'; // 拖动加载图片组件。
  import {ImageExtend} from 'quill-image-paste-module'
  Quill.register('modules/imageDrop', ImageDrop);
  Quill.register('modules/imageResize', ImageResize);
  Quill.register('modules/ImageExtend', ImageExtend)
  //------------------------------------------------------工具栏配置项
  // 自定义字体大小
  let 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)
  
  import Parchment from 'parchment';
  //目的是修改富文本内容居中不了的问题
  let config = {
    scope: Parchment.Scope.BLOCK,
    whitelist: ['right', 'center', 'justify']
  };
  let AlignStyle = new Parchment.Attributor.Style('align', 'text-align', config);
  export { AlignStyle };
  var Align = Quill.import('attributors/style/align');
  Align.whitelist = ['right', 'center', 'justify'];
  Quill.register(Align, true);
  // 工具栏
  const toolbarOptions = [
    ['bold', 'italic', 'underline', 'strike'], // toggled buttons
    ['blockquote', 'code-block'],
    [{'header': 1}, {'header': 2}],
    [{'list': 'ordered'}, {'list': 'bullet'}],
    [{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
    [{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
    [{'direction': 'rtl'}],
    [{ 'size': ['10px', '12px', false, '16px', '18px', '20px', '30px', '32px'] }], // 字体大小
    // [{'size': fontSizeStyle.whitelist }], // [{'size': ['small', false, 'large', 'huge']}],// 
    [{'header': [1, 2, 3, 4, 5, 6, false]}],
    // [{'font':fonts}],//[{'font': []}],
    [{ 'font': [false, 'SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'sans-serif'] }], // 字体种类
    [{'color': []}, {'background': []}], // dropdown with defaults from theme
    [{'align': []}],
    [{'clean': '清除'}], // remove formatting button
    // ['link', 'image', 'video']
    ['image', 'video']
  ]
  export default {
    name: 'RichTextEditor',
    model: {
      prop: 'content',
      event: 'change'
    },
    components: {
      quillEditor
    },
    props: {
      content: { // 返回的html片段
        type: String,
        default: ''
      },
      uploadImgConfig: { // 图片上传配置 - 若不配置则使用quillEditor默认方式,即base64方式
        type: Object,
        default(){
          return {
            uploadUrl: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb', // 图片上传地址
            maxSize: 100, // 图片上传大小限制,默认不超过2M
            name: 'file' // 图片上传字段
          }
        }
      },
      uploadVideoConfig: { // 视频上传配置
        type: Object,
        default(){
          return {
            uploadUrl: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb', // 上传地址
            maxSize: 100, // 图片上传大小限制,默认不超过2M
            name: 'file' // 图片上传字段
          }
        }
      }
    },
    data() {
      let _self = this;
      return {
        loading: false, // 加载loading
        editorOption: {
          placeholder: '',
          theme: 'snow', // or 'bubble'
          modules: {
            toolbar: {
              container: toolbarOptions, // 工具栏
              handlers: {
                'video': function (value) {
                  _self.videoDialog.show = true;
                },
                'image': function () {  // 劫持原来的图片点击按钮事件
                    document.querySelector('.quill-image-input').click()
                }
              }
            },
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            },
            clipboard: {
                // 粘贴过滤
                matchers: [[Node.ELEMENT_NODE, this.HandleCustomMatcher]]
            },
            ImageExtend: {
                loading: true,
                name: 'file',
                change: (xhr, FormData) => {
                  FormData.append('token', '123443')
                },
                action: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb',
                response: (res) => {
                    return res.redata;
                },
            }
          }
        },

        // 图片上传变量
        // fileList: [],
        // 视频上传变量
        // videoDialog: {
        //   show: false,
        //   videoLink: '',
        //   activeName: 'first'
        // }
      }
    },
    mounted () {
      // 初始给编辑器设置title
      addQuillTitle()

      let toolbar = this.$refs['myQuillEditor'].quill.getModule('toolbar');
      // 是否开启图片上传到服务器功能
      if (this.uploadImgConfig.uploadUrl) {
        toolbar.addHandler('image', this.addImageHandler);
      }
      // 是否开启视频上传到服务器功能
      if (this.uploadVideoConfig.uploadUrl) {
        toolbar.addHandler('video', this.addvideoHandler);
      }
    },
    methods: {
      HandleCustomMatcher (node, Delta) {
        // 文字,从别处复制而来,革除自带款式,转为纯文本
          if(node.src && node.src.indexOf('data:image/png') > -1){
              Delta.ops = [];
              return Delta;
          }
          let ops = []
          let img=[]
          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'){
              // console.log(op.insert);
            }
          })
          Delta.ops = ops
          return Delta
      },
      // 文本编辑
      onEditorChange ({quill, html, text}) {
        // console.log('editor change!', quill, html, text)
        // console.log(html.replace(/<[^>]*>|/g, ''), 33333333)
        this.$emit('update:content', html)
        this.$emit('change', html)
      },
      hideLoading(){
        this.loading = false
      },
      // --------- 图片上传相关 start ---------

      addImageHandler(value){
        if (value) {
          // 触发input框选择图片文件
          this.$refs['myinput'].click();
        } else {
          this.quill.format('image', false)
        }
      },
      // 把已经上传的图片显示回富文本编辑框中
      uploadSuccess (imgurl) {
        let quill = this.$refs['myQuillEditor'].quill
        let range = quill.getSelection()
        let index = 0;
        if (range == null) {
          index = 0;
        } else {
          index = range.index; // 获取光标所在位置
        }
        // 插入
        quill.insertEmbed(index, 'image', imgurl) // imgurl是服务器返回的图片链接地址
        // 调整光标到最后
        quill.setSelection(index + 1)
      },
      // el-文件上传组件
      onBeforeUpload (file) {
        this.loading = true
        let acceptArr = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']
        const isIMAGE = acceptArr.includes(file.type)
        const isLt1M = file.size / 1024 / 1024 < this.uploadImgConfig.maxSize
        if (!isIMAGE) {
          this.hideLoading()
          this.$message.error('只能插入图片格式!')
        }
        if (!isLt1M) {
          this.hideLoading()
          this.$message.error(`上传文件大小不能超过 ${this.uploadImgConfig.maxSize}MB!`)
        }
        return isLt1M && isIMAGE
      },
      // 文件上传成功时的钩子
      onSuccess (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
        this.hideLoading()
        if (response.recode =='200') {
          this.uploadSuccess(response.redata)
        } else {
          this.$message.error('上传失败')
        }
      },
      // 文件上传失败时的钩子
      onError (file, fileList) {
        this.hideLoading()
        this.$message.error('上传失败')
      },

      // --------- 图片上传相关 end ---------

      // --------- 视频上传相关 start ---------
      addvideoHandler(value){
        if (value) {
          // 触发input框选择视频文件
          this.$refs['myvideo'].click();
        } else {
          this.quill.format('video', false)
        }
      },
      addVideoLink(videoLink) {
        let quill = this.$refs['myQuillEditor'].quill
        let range = quill.getSelection()
        let index = 0;
        if (range == null) {
          index = 0;
        } else {
          index = range.index;
        }
        // 插入
        quill.insertEmbed(index, 'video', videoLink)
        // 调整光标到最后
        quill.setSelection(index + 1)
      },

      // el-文件上传组件
      onBeforeUploadVideo (file) {
        this.loading = true
        let acceptArr = ['video/mp4']
        const isVideo = acceptArr.includes(file.type)
        const isLt1M = file.size / 1024 / 1024 < this.uploadVideoConfig.maxSize
        if (!isVideo) {
          this.hideLoading()
          this.$message.error('只能上传mp4格式文件!')
        }
        if (!isLt1M) {
          this.hideLoading()
          this.$message.error(`上传文件大小不能超过 ${this.uploadVideoConfig.maxSize}MB!`)
        }
        return isLt1M && isVideo
      },
      // 文件上传成功时的钩子
      onSuccessVideo (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
        this.hideLoading()
        if (response.recode =='200') {
          console.log(response.redata);
          this.addVideoLink(response.redata)
        } else {
          this.$message.error('上传失败')
        }
      },
      // 文件上传失败时的钩子
      onErrorVideo (file, fileList) {
        this.hideLoading()
        this.$message.error('上传失败')
      },

      // --------- 视频上传相关 end ---------
    }
  }
</script>
<style>
  .RichTextEditor-Wrap .ql-container {
    height: 300px;
  }

  .RichTextEditor-Wrap .ql-editor {
    padding: 0;
    white-space:normal !important;
  }

  .RichTextEditor-Wrap .ql-tooltip {
    left: 5px !important;
  }
  .editor {
  line-height: normal !important;
  height: 300px;
}
.SizeTiShi{
  font-size: 12px;
  color: #999999;
  text-align: right;
  /* margin-right: 20px; */
  /* margin-top: 60px; */
}
.ql-snow .ql-tooltip[data-mode=link]::before {
  content: "请输入链接地址:";
}
.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";
}
</style>

2.富文本框菜单title-----quill-title.js

const titleConfig = {
    'ql-bold': '加粗',
    'ql-font': '字体',
    'ql-code': '插入代码',
    'ql-italic': '斜体',
    'ql-link': '添加链接',
    'ql-color': '字体颜色',
    'ql-background': '背景颜色',
    'ql-size': '字体大小',
    'ql-strike': '删除线',
    'ql-script': '上标/下标',
    'ql-underline': '下划线',
    'ql-blockquote': '引用',
    'ql-header': '标题',
    'ql-indent': '缩进',
    'ql-list': '列表',
    'ql-align': '文本对齐',
    'ql-direction': '文本方向',
    'ql-code-block': '代码块',
    'ql-formula': '公式',
    'ql-image': '图片',
    'ql-video': '视频',
    'ql-clean': '清除字体样式'
  }
  
  export function addQuillTitle () {
    const oToolBar = document.querySelector('.ql-toolbar')
    const aButton = oToolBar.querySelectorAll('button')
    const aSelect = oToolBar.querySelectorAll('select')
    aButton.forEach(function (item) {
      if (item.className === 'ql-script') {
        item.value === 'sub' ? item.title = '下标' : item.title = '上标'
      } else if (item.className === 'ql-indent') {
        item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进'
      } else {
        item.title = titleConfig[item.className]
      }
    })
    // 字体颜色和字体背景特殊处理,两个在相同的盒子
    aSelect.forEach(function (item) {
      if (item.className.indexOf('ql-background') > -1) {
        item.previousSibling.title = titleConfig['ql-background']
      } else if (item.className.indexOf('ql-color') > -1) {
        item.previousSibling.title = titleConfig['ql-color']
      } else {
        item.parentNode.title = titleConfig[item.className]
      }
    })
  }

3.使用组件

 <el-form-item label="内容" v-if="dialogVisible">

              <quill-editor @change="editerChange"></quill-editor>

 </el-form-item>

4.参考

vue-quill-editor 富文本编辑器封装,可上传图片,视频(附源码) - 简书https://www.jianshu.com/p/5fba48c07debvue-quill-editor的详细配置与使用 - 简书最近工作中需求使用一款富文本编辑器,经过再三比较选择了vue-quill-editor,之所以选择vue-quill-editor,是看上了它的轻量以及外观简洁的优势。打开官...https://www.jianshu.com/p/efcedcabb192vue-quill-editor 样式不生效的解决方法_西坦的博客-CSDN博客在main.js引入import 'quill/dist/quill.snow.css'在需要展示的页面中用如下元素包裹即可<div class="ql-container ql-snow"> <div class="ql-editor" v-html="list.content"></div>...https://blog.csdn.net/qq_41144623/article/details/104876294vue-quill-editor编辑器文本不居中问题处理_吃范范的博客-CSDN博客一、问题出现的原因 由于 使用这个样式 class="ql-align-center",但是在app中显示时没有这个样式,所以就不居中,我们可以修改一下,改成 style="text-align: center;"这样就能完美显示了二、修改<script>import Quill from 'quill';import Parchment from 'parchment';//目的是修改富文本内容居中不了的问题let config = { scope: P...https://blog.csdn.net/fanhuiixa/article/details/118540311

5.追加

解决问题:编辑给富文本赋值样式丢失(原因:富文本保护机制会自动过滤样式)

解决方式:

 <quill-editor ref='myQuill'  @change="editerChange"></quill-editor>

//调用组件的方法

this.$nextTick(e=>{

             this.$refs.myQuill.setContent(content);

})

 //组件中给元素赋值

setContent(content){

        this.$refs.myQuillEditor.$el.lastChild.childNodes[0].innerHTML=content;

},

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值