【VUE+Element】vue-quill-editor的使用,上传图片+音频+视频+自定义上传方式

效果:

前提:

  •  quill-editor只支持三种标签p b span ,如果要用其他等标签,要用Quill中提供的register方法去注册标签。
  • 对于原生的视频上传,只能上传地址,现在要实现本地上传
  • 对于本地上传,需要组件辅助,上传时打开本地文件,如下。
  • 对于音频及其他,要组件工具栏中自定义,因为原生没有对音频图标进行封装(格外注意!)
  • 本文对编辑的文字大小做了格式扩展

实现: 

1、引入vue-quill-editor

cnpm install vue-quill-editor
cnpm install quill --save

2、使用vue-quill-editor

template:

<!-- 上传组件辅助-->
        <el-upload class="avatar-uploader-editor" :action="serverUrl" name="img" :headers="header" :show-file-list="false"
          :on-success="uploadSuccess" :on-error="uploadError"> </el-upload>
        <el-upload class="avatar-uploader-editor-video" :action="serverUrl" name="video" :headers="header" :show-file-list="false"
          :on-success="uploadSuccessVideo" :on-error="uploadError"> </el-upload>
        <el-upload class="avatar-uploader-editor-voice" :action="serverUrl" name="voice" :headers="header" :show-file-list="false"
          :on-success="uploadSuccessVoice" :on-error="uploadError"> </el-upload>

<quill-editor v-model="content" ref="myQuillEditor" style="height: 500px;margin-top: -22px;" :options="editorOption">
          <div id="toolbar" slot="toolbar"> 
            <button class="ql-bold" title="加粗">Bold</button>
            <button class="ql-italic" title="斜体">Italic</button>
            <button class="ql-underline" title="下划线">underline</button>
            <button class="ql-strike" title="删除线">strike</button>
            <button class="ql-blockquote" title="引用"></button>
            <button class="ql-code-block" title="代码"></button>
            <button class="ql-header" value="1" title="标题1"></button>
            <button class="ql-header" value="2" title="标题2"></button>
            <button class="ql-list" value="ordered" title="有序列表"></button>
            <button class="ql-list" value="bullet" title="无序列表"></button>
            <select class="ql-header" title="段落格式">
              <option selected>段落</option>
              <option value="1">标题1</option>
              <option value="2">标题2</option>
              <option value="3">标题3</option>
              <option value="4">标题4</option>
              <option value="5">标题5</option>
              <option value="6">标题6</option>
            </select>
            <select class="ql-size" title="字体大小">
              <option value="10px">10px</option>
              <option value="12px">12px</option>
              <option value="14px">14px</option>
              <option value="16px" selected>16px</option>
              <option value="18px">18px</option>
              <option value="20px">20px</option>
              <option value="30px">30px</option>
            </select>
            <select class="ql-font" title="字体">                                        
              <option value="SimSun">宋体</option>
              <option value="SimHei">黑体</option>
              <option value="Microsoft-YaHei">微软雅黑</option>
              <option value="KaiTi">楷体</option>
              <option value="FangSong">仿宋</option>
              <option value="Arial">Arial</option>
            </select>
            <select class="ql-color" value="color" title="字体颜色"></select>
            <select class="ql-background" value="background" title="背景颜色"></select>
            <select class="ql-align" value="align" title="对齐"></select> 
            <button class="ql-clean" title="清除字体样式"></button> 
            <button class="ql-image" title="图片"></button> 
            <button class="ql-video" title="视频"></button> 
            <button class="ql-audio" title="音频"><i class="el-icon-headset"></i></button> 
          </div>  
        </quill-editor>

注意1:

<button class="ql-audio" title="音频"><i class="el-icon-headset"></i></button>

此行为新加的标签,以此方式引用,简单又有页面效果!

注意2:

在上传视频、音频、图片时,用到了element上传文件组件

  • 先引入上传文件组件,再对组件进行隐藏
  • 对上传文件组件添加class,方便后期引用!
  • 对三个组件的上传成功的方法分别写,因为处理方式不同

script:

import {
    Quill,quillEditor
  } from 'vue-quill-editor'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'

  // 自定义字体大小
  let Size = Quill.import('attributors/style/size')
  Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px', '30px']
  Quill.register(Size, true)

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

//视频标签插入(样式保持宽度100%)
import Video from './video.js'
Quill.register(Video, true)

  //音频标签插入
  import Audio from './audio.js'
  Quill.register(Audio, true) 
export default {
    components: {
      quillEditor
    },
    data() {
      return {
        content: '',
        editorOption: {
          placeholder: "请输入", //文章初始化提示语
          theme: "snow", // or 'bubble' 
          modules: {
            toolbar: {
              container: '#toolbar',
              handlers: {
                'image': function (value) {
                  if (value) {
                    // 触发input框选择图片文件                    
                    document.querySelector('.avatar-uploader-editor input').click()
                  } else {
                    this.quill.format('image', false);
                  }
                },
                'video': function (value) {
                  if (value) {
                    // 触发input框选择视频文件                    
                    document.querySelector('.avatar-uploader-editor-video input').click()
                  } else {
                    this.quill.format('video', false);
                  }
                },
                'audio': function (value) {
                  if (value) {
                    // 触发input框选择音频文件                    
                    document.querySelector('.avatar-uploader-editor-voice input').click()
                  } else {
                    this.quill.format('audio', false);
                  }
                },
              }
            }
          }
        },
        serverUrl: '/api/files/headUpload', //上传的图片服务器地址
      }
    },
methods: {
      // 富文本图片上传成功
      uploadSuccess(res,file) {
        // console.log(res)
        let quill = this.$refs.myQuillEditor.quill
        // 如果上传成功        
        if (res.code == 0) {
          // 获取光标所在位置          
          let length = quill.getSelection().index;
          // 插入图片res.url为服务器返回的图片地址
          quill.insertEmbed(length, 'image', res.data.url)
          // 调整光标到最后         
          quill.setSelection(length + 1)
        } else {
          this.$message.error('图片插入失败')
        }
      },
//上传视频
      uploadSuccessVideo(res,file) {
        let quill = this.$refs.myQuillEditor.quill
        // 如果上传成功        
        if (res.code == 0) {
          // 获取光标所在位置          
          let length = quill.getSelection().index;
          // 插入图片res.url为服务器返回的地址  
          quill.insertEmbed(length, 'video', res.data.url) 
          // 调整光标到最后            
          quill.setSelection(length + 1) 
        } else {
          this.$message.error('视频插入失败')
        }
      },
//上传音频-处理很重要!!!!
      uploadSuccessVoice(res,file) {
          let quill = this.$refs.myQuillEditor.quill
        // 如果上传成功        
        if (res.code == 0) {
          // 获取光标所在位置          
          let length = quill.getSelection().index;
          let BlockEmbed = Quill.import('blots/block/embed');
          class AudioBlot extends BlockEmbed {
            static create(value) {
                let node = super.create();
                node.setAttribute('src', res.data.url);      //设置audio的src属性
                node.setAttribute('controls', true);      //设置audio的controls,否则他将不会显示
                node.setAttribute('controlsList', 'nodownload');      //设置audio的下载功能为不能下载
                node.setAttribute('id', 'voice');         //设置一个id
                return node;
            }
          }
        AudioBlot.blotName = 'audio';
        AudioBlot.tagName = 'audio';      //自定义的标签为audio
        Quill.register(AudioBlot);
        
        // insertEmbed(index: Number(插入的位置), type: String(标签类型), value: any(参数,将传入到create的方法中去), source: String = 'api')
        quill.insertEmbed(length, 'audio', res.data.url);
        quill.setSelection(length + 1);  //光标位置向后移动一位
        
        } else {
          this.$message.error('音频插入失败')
        }
      },
      // 富文本图片/视频/音频上传失败   
      uploadError() {
        this.$message.error('插入失败')
      },
}
}

style:

.avatar-uploader-editor{
    display: inline-block;
  }
  .avatar-uploader-editor-video{
    display: inline-block;
  }
  .avatar-uploader-editor-voice{
    display: inline-block;
  }

引入资源:video.js

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)
    // 添加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', this.sanitize(value))
    return node
  }

  static formats (domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute)
      }
      return formats
    }, {})
  }

  static sanitize (url) {
    return Link.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

audio.js

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 audio extends BlockEmbed {
  static create (value) {
    const node = super.create(value)
    // 添加audio标签所需的属性
    node.setAttribute('controls', 'controls')
    node.setAttribute('type', 'audio/mp4')
    node.setAttribute('src', this.sanitize(value))
    return node
  }

  static formats (domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute)
      }
      return formats
    }, {})
  }

  static sanitize (url) {
    return Link.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 { audio } = this.value()
    return `<a href="${audio}">${audio}</a>`
  }
}
audio.blotName = 'audio' // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
audio.className = 'ql-audio'
audio.tagName = 'audio' // 用audio标签替换iframe

export default audio

font.css(此文件为修改编辑器的样式,必引!)

.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: "宋体";
  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: "黑体";
  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: "微软雅黑";
  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: "楷体";
  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: "仿宋";
  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";
  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";
  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";
  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";
}

复制粘贴即可实现!调研N久,拿走不谢!

  • 13
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BMG-Princess

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值