目录
- 效果展示:(可以上传图片及其视频)
- 第一步、首先安装富文本编辑器插件
- 第二步、然后在main.js文件中,全局注册
- 第三步、在vue界面中使用quill-editor
- 第四步:配置video.js(要有上传视频且回显的功能需要配置)
- 总结
效果展示:(可以上传图片及其视频)
可以拖拽图片大小及其位置
第一步、首先安装富文本编辑器插件
cnpm install vue-quill-editor --save | |
cnpm install quill-image-drop-module --save | |
cnpm install quill-image-resize-module --save |
第二步、然后在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) |
第三步、在vue界面中使用quill-editor
为了便于大家直接使用,直接把script以及css放在一个页面里,之际copy就可以使用
<template> | |
<div> | |
<div> | |
<el-button @click="openContent" type="primary">查看元素</el-button> | |
</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" | |
:headers="headers"> | |
</el-upload> | |
<!-- 基于elementUi的上传组件 el-upload end--> | |
<quill-editor class="editor" v-model="content" ref="customQuillEditor" :options="editorOption" > | |
</quill-editor> | |
</div> | |
</div> | |
</template> | |
<script> | |
import * as Quill from 'quill' | |
// 这里引入修改过的video模块并注册 | |
import Video from './video' | |
Quill.register(Video, true) | |
//获取登录token,引入文件,如果只是简单测试,没有上传文件是否登录的限制的话, | |
//这个token可以不用获取,文件可以不引入,把上面对应的上传文件携带请求头 :headers="headers" 这个代码删掉即可 | |
import {getToken} from "@/utils/auth"; | |
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 { | |
headers: { | |
Authorization: "Bearer " + getToken(), | |
}, | |
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'){ | |
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) | |
}, | |
}, | |
created () { | |
}, | |
//只执行一次,加载执行 | |
mounted () { | |
console.log("开始加载") | |
// 初始给编辑器设置title | |
}, | |
watch:{ | |
content(newVal, oldVal) { | |
//this.$emit('input', newVal); | |
console.log(newVal) | |
console.log(oldVal) | |
} | |
}, | |
} | |
</script> | |
<style> | |
.editor { | |
line-height: normal ; | |
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> |
注意:
1、我是在elementUi使用的,上传图片以及页面的访问需要有登录权限,所以我的上传图片视频的组件里有:headers=“headers”,携带登录权限
2、需要更改自己的上传文件的路径(改成自己的)
3、import {getToken} from “@/utils/auth”;
获取登录token,引入文件,如果只是简单测试,没有上传文件是否登录的限制的话,这个token可以不用获取,文件可以不引入,把上面对应的上传文件携带请求头 :headers=“headers” 这个代码删掉即可。根据自己的情况来就行。
所引入的文件:(获取登录token)
如果上传文件有登录全选验证,可以按照自己框架的token来写,如果没有则删掉即可。
展示:
此时就quill-editor富文本编辑器就能使用了,除了基本功能之外也能够上传图片
第四步:配置video.js(要有上传视频且回显的功能需要配置)
因为quill-editor富文本编辑器当我们上传mp4等视频类型的文件时,quill-editor会自动的用iframe标签来创建,此时视频文件将不能显示出来,我们需要改写quill-editor提供能video.js文件,更改为video的标签来展示视频
------>此时视频链接已经生成,但是因为iframe标签将不能展示
当我们手动的把页面iframe标签改为video标签时视频就显示出来了,找到问题,开搞
1、创建video.js的空文件
为了方便讲解,我这里放在了页面同一目录下
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) | |
// 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}" rel="external nofollow" >${video}</a>` | |
} | |
} | |
Video.blotName = 'video' // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot | |
Video.className = 'ql-video' | |
Video.tagName = 'video' // 用video标签替换iframe | |
export default Video |
2、更改vue界面(引入video.js文件)
(1) 引入video.js
import * as Quill from 'quill' | |
// 这里引入修改过的video模块并注册 | |
import Video from './video' | |
Quill.register(Video, true) |
(2) 引入全局变量(供video.js调用)
window.jsValue=imgUrl;
更好好后的vue文件:
<template> | |
<div> | |
<div> | |
<el-button @click="openContent" type="primary">查看元素</el-button> | |
</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" | |
:headers="headers"> | |
</el-upload> | |
<!-- 基于elementUi的上传组件 el-upload end--> | |
<quill-editor class="editor" v-model="content" ref="customQuillEditor" :options="editorOption" > | |
</quill-editor> | |
</div> | |
</div> | |
</template> | |
<script> | |
import * as Quill from 'quill' | |
// 这里引入修改过的video模块并注册 | |
import Video from './video' | |
Quill.register(Video, true) | |
//自定义编辑器的工作条 | |
import {getToken} from "@/utils/auth"; | |
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 { | |
headers: { | |
Authorization: "Bearer " + getToken(), | |
}, | |
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) | |
}, | |
}, | |
created () { | |
}, | |
//只执行一次,加载执行 | |
mounted () { | |
console.log("开始加载") | |
// 初始给编辑器设置title | |
}, | |
watch:{ | |
content(newVal, oldVal) { | |
//this.$emit('input', newVal); | |
console.log(newVal) | |
console.log(oldVal) | |
} | |
}, | |
} | |
</script> | |
<style> | |
.editor { | |
line-height: normal ; | |
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> |
功能展示