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

    技术2022-07-12  76

    效果:

    前提:

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

    实现: 

    1、引入vue-quill-editor

    cnpm install vue-quill-editor

    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久,拿走不谢!

    Processed: 0.011, SQL: 9