elementui加入富文本控件quill-editor

发布时间 2023-06-30 16:00:59作者: 妞妞猪

参考https://blog.csdn.net/qq_45830276/article/details/126021692这位大牛的帖子,他把视频的上传和图片的上传也加入进富文本编辑器中

1.安装依赖

npm install vue-quill-editor --save
npm install quill-image-drop-module --save
npm install quill-image-resize-module --save

2.在main.js中配置全局变量

//引入quill-editor编辑器
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)

//实现quill-editor编辑器拖拽上传图片
import * as Quill from 'quill'
import { ImageDrop } from 'quill-image-drop-module'
Quill.register('modules/imageDrop', ImageDrop)

//实现quill-editor编辑器调整图片尺寸
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageResize', ImageResize)

3.源码展示 因为原作者上传文件时需要传递token参数,我不需要所以去掉了相关代码

<!-- 富文本编辑器控件 可上传图片和视频 -->
<template>
    <div>
      <div id='quillEditorQiniu'>
        <!-- 基于elementUi的上传组件 el-upload begin-->
        <el-upload
            class="avatar-uploader"
            :action="uploadImgUrl"
            :accept="'image/*,video/*'"
            :show-file-list="false"
            :on-success="uploadEditorSuccess"
            :on-error="uploadEditorError"
            :before-upload="beforeEditorUpload">
        </el-upload>
        <!-- 基于elementUi的上传组件 el-upload end-->
        <quill-editor  class="editor"  v-model="content" ref="customQuillEditor" :options="editorOption" >
        </quill-editor>
      </div>
      <div>
        <el-button @click="openContent" type="primary" style="margin-top: 30px;">看看你写了什么</el-button>
      </div>
    </div>
</template>
<script>
import * as Quill from 'quill'
// 这里引入修改过的video模块并注册
import Video from './video'
Quill.register(Video, true)
const toolbarOptions = [
    ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
    ['blockquote', 'code-block'],
 
    [{'header': 1}, {'header': 2}],               // custom button values
    [{'list': 'ordered'}, {'list': 'bullet'}],
    [{'script': 'sub'}, {'script': 'super'}],      // superscript/subscript
    [{'indent': '-1'}, {'indent': '+1'}],          // outdent/indent
    [{'direction': 'rtl'}],                         // text direction
 
    [{'size': ['small', false, 'large', 'huge']}],  // custom dropdown
    [{'header': [1, 2, 3, 4, 5, 6, false]}],
 
    [{'color': []}, {'background': []}],          // dropdown with defaults from theme
    [{'font': []}],
    [{'align': []}],
    ['link', 'image', 'video'],
    ['clean']                                         // remove formatting button
];
export default {
    data(){
      return {
        uploadImgUrl:"/v1/admin/common/upload",  //图片上传地址在线地址
        uploadUrlPath:"没有文件上传",
        quillUpdateImg:false,
        content:'',    //最终保存的内容
        editorOption:{
          placeholder:'你想说什么?',
          modules: {
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            },
            toolbar: {
              container: toolbarOptions,  // 工具栏
              handlers: {
                'image': function (value) {
                  if (value) {
                    document.querySelector('#quillEditorQiniu .avatar-uploader input').click()
                  } else {
                    this.quill.format('image', false);
                  }
                },
                'video': function (value) {
                  if (value) {
                    document.querySelector('#quillEditorQiniu .avatar-uploader input').click()
                  } else {
                    this.quill.format('video', false);
                  }
                },
              }
            }
          }
        },
      }
    },
    methods:{
      //上传图片之前async
      beforeEditorUpload(res, file){
        //显示上传动画
        this.quillUpdateImg = true;
        //  const res1 = await uploadImage()
        // console.log(res1,'=====');
        // this.$emit('before',res, file)
        console.log(res);
        console.log(file);
      },
      // 上传图片成功
      uploadEditorSuccess(res, file) {
        console.log("上传成功")
        // this.$emit('upload',res, file)
        console.log(res, file);
        //拼接出上传的图片在服务器的完整地址
        let imgUrl=res.data.url;
        let type=imgUrl.substring(imgUrl.lastIndexOf(".")+1);
        console.log(type);
        // 获取富文本组件实例
        let quill = this.$refs.customQuillEditor.quill;
        // 获取光标所在位置
        let length = quill.getSelection().index;
        // 插入图片||视频  res.info为服务器返回的图片地址
        if(type=='mp4' || type=='MP4'){
          window.jsValue=imgUrl;
          quill.insertEmbed(length, 'video', imgUrl)
        }else{
          quill.insertEmbed(length, 'image', imgUrl)
        }
        // 调整光标到最后
        quill.setSelection(length + 1)
        //取消上传动画
        this.quillUpdateImg = false;
      },
      // 上传(文件)图片失败
      uploadEditorError(res, file) {
        console.log(res);
        console.log(file);
        //页面提示
        this.$message.error('上传图片失败')
        //取消上传动画
        this.quillUpdateImg = false;
      },
      //上传组件返回的结果
      uploadResult:function (res){
        this.uploadUrlPath=res;
      },
      openContent:function (){
        console.log(this.content)
      },
 
    },
    //只执行一次,加载执行
    mounted () {
      console.log("开始加载")
      // 初始给编辑器设置title
    },
    watch:{
      content(newVal, oldVal) {
        //this.$emit('input', newVal);
        console.log(newVal)
        console.log(oldVal)
      }
    },
}
</script>
<style>
.editor {
    line-height: normal !important;
    height: 400px;
    margin-bottom: 50px;
}
.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";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
    content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
    content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
    content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
    content: "文本";
}
.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";
}
.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";
}
.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";
}
.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";
}
.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";
}
.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";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
    content: "标准字体";
}
.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: "衬线字体";
}
.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: "等宽字体";
}
</style>
 另外,作者写了一个video文件用来处理视频内容在上面的代码中标黄处引用了,video文件路径跟富文本控件放在一个根目录下

import  { Quill }  from 'vue-quill-editor'
// 源码中是import直接倒入,这里要用Quill.import引入
const BlockEmbed = Quill.import('blots/block/embed')
// const Link = Quill.import('formats/link')
const ATTRIBUTES = ['height', 'width']
class Video extends BlockEmbed {
    static create (value) {
        const node = super.create(value)
        // console.log("js文件"+ window.jsValue)
        // 添加video标签所需的属性
        node.setAttribute('controls', 'controls') // 控制播放器
        //删除原生video的控制条的下载或者全屏按钮的方法
        //<video controls controlsList='nofullscreen nodownload noremote footbar' ></video>
        //不用哪个在下面加上哪个
        node.setAttribute('controlsList', 'nofullscreen') // 控制删除
        node.setAttribute('type', 'video/mp4')
        node.setAttribute('style', 'object-fit:fill;width: 100%;')
        node.setAttribute('preload', 'auto')    // auto - 当页面加载后载入整个视频  meta - 当页面加载后只载入元数据  none - 当页面加载后不载入视频
        node.setAttribute('playsinline', 'true')
        node.setAttribute('x-webkit-airplay', 'allow')
        // node.setAttribute('x5-video-player-type', 'h5') // 启用H5播放器,是wechat安卓版特性
        node.setAttribute('x5-video-orientation', 'portraint') // 竖屏播放 声明了h5才能使用  播放器支付的方向,landscape横屏,portraint竖屏,默认值为竖屏
        node.setAttribute('x5-playsinline', 'true') // 兼容安卓 不全屏播放
        node.setAttribute('x5-video-player-fullscreen', 'true')    // 全屏设置,设置为 true 是防止横屏
        node.setAttribute('src', window.jsValue)
        return node
    }
    static formats (domNode) {
        return ATTRIBUTES.reduce((formats, attribute) => {
            if (domNode.hasAttribute(attribute)) {
                formats[attribute] = domNode.getAttribute(attribute)
            }
            return formats
        }, {})
    }
    // static sanitize (url) {
    //
    //      // eslint-disable-line import/no-named-as-default-member
    // }
    static value (domNode) {
        return domNode.getAttribute('src')
    }
    format (name, value) {
        if (ATTRIBUTES.indexOf(name) > -1) {
            if (value) {
                this.domNode.setAttribute(name, value)
            } else {
                this.domNode.removeAttribute(name)
            }
        } else {
            super.format(name, value)
        }
    }
    html () {
        const { video } = this.value()
        return `<a href="${video}">${video}</a>`
    }
}
Video.blotName = 'video' // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
Video.className = 'ql-video'
Video.tagName = 'video' // 用video标签替换iframe
export default Video
到这里重新运行发现报错!

Cannot read property 'imports' of undefined"

Failed to mount component: template or render function not defined.

Cannot read property 'registerof undefined’

根据另一位博主的解决办法(https://blog.csdn.net/weixin_43909743/article/details/121285048)
 1.在build文件夹下找到webpack.base.conf.js。

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      // 在这儿添加下面两行
      'window.Quill': 'quill/dist/quill.js',
      'Quill': 'quill/dist/quill.js'
    })
  ]
}

2.基于vue-cli创建,找不到webpack.base.conf.js;在vue.config.js中设置

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

或者

module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        'window.Quill': 'quill/dist/quill.js',
        'Quill': 'quill/dist/quill.js',
      })
    ]
  },
}

重启,成功运行!

感谢参考:

https://blog.csdn.net/qq_45830276/article/details/126021692

https://blog.csdn.net/weixin_43909743/article/details/121285048