vue-quill-editor 富文本编辑器封装,可上传图片,视频(附源码)
组件说明:
(1)支持图片上传到服务器,也可使用base64的方式
(2)支持视频上传,或者插入视频链接
【其实无论是上传图片到服务器还是上传视频到服务器,其实本质上都是上传文件然后后端返回一个地址插入到编辑器中】
- 安装
npm install vue-quill-editor --save
2.引入下面我封装的组件
<!--富文本编辑器-->
<template>
<div class="RichTextEditor-Wrap" v-loading="loading">
<quill-editor :content="content"
:options="editorOption"
class="ql-editor"
ref="myQuillEditor"
@change="onEditorChange($event)">
</quill-editor>
<!-- 图片上传组件辅助-->
<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"
:file-list="fileList">
<!--<i class="el-icon-upload"></i>-->
<!--<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>-->
<!--<div class="el-upload__tip" slot="tip">最多只能上传两个附件</div>-->
<button ref="myinput">上传文件</button>
</el-upload>
<!--视频上传-->
<div :visible.sync="videoDialog.show">
<el-dialog
:close-on-click-modal="false"
width="50%"
style="margin-top: 1px"
title="视频上传"
:visible.sync="videoDialog.show"
append-to-body>
<el-tabs v-model="videoDialog.activeName">
<el-tab-pane label="添加视频链接" name="first">
<el-input v-model="videoDialog.videoLink" placeholder="请输入视频链接" clearable></el-input>
<el-button type="primary" size="small" style="margin: 20px 0px 0px 0px "
@click="addVideoLink(videoDialog.videoLink)">添加
</el-button>
</el-tab-pane>
<el-tab-pane label="本地视频上传" name="second">
<el-upload
v-loading="loading"
style="text-align: center;"
drag
:action="uploadVideoConfig.uploadUrl"
accept="video/*"
:name="uploadVideoConfig.name"
:before-upload="onBeforeUploadVideo"
:on-success="onSuccessVideo"
:on-error="onErrorVideo"
:multiple="false">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传MP4文件,且不超过{{uploadVideoConfig.maxSize}}M</div>
</el-upload>
</el-tab-pane>
</el-tabs>
</el-dialog>
</div>
</div>
</template>
<script>
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import {quillEditor} from 'vue-quill-editor'
// 设置title
import {addQuillTitle} from './quill-title.js'
// 工具栏
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': ['small', false, 'large', 'huge']}],
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'font': []}],
[{'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: '', // 图片上传地址
maxSize: 2, // 图片上传大小限制,默认不超过2M
name: 'Filedata' // 图片上传字段
}
}
},
uploadVideoConfig: { // 视频上传配置
type: Object,
default(){
return {
uploadUrl: '', // 上传地址
maxSize: 10, // 图片上传大小限制,默认不超过2M
name: 'Filedata' // 图片上传字段
}
}
}
},
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;
}
}
}
}
},
// 图片上传变量
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);
}
},
methods: {
// 文本编辑
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.retCode === '00') {
this.uploadSuccess(response.url)
} else {
this.$message.error('上传失败')
}
},
// 文件上传失败时的钩子
onError (file, fileList) {
this.hideLoading()
this.$message.error('上传失败')
},
// --------- 图片上传相关 end ---------
// --------- 视频上传相关 start ---------
addVideoLink(videoLink) {
if (!videoLink) return this.$message.error('请输入视频地址')
this.videoDialog.show = false
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.retCode === '00') {
this.addVideoLink(response.url)
} 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;
}
.RichTextEditor-Wrap .ql-tooltip {
left: 5px !important;
}
</style>
quill-title.js这个文件是拿来给工具栏设置title的,代码如下:
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]
}
})
}
组件使用: 注意props,其中 uploadImgConfig 是针对上传图片的配置,如果不传则默认使用原始的base64方式,视频上传必须要有上传服务器的地址
组件复制过去后,还需更改下下面这部分代码,根据自己的接口返回值类型更改:
组件调用方式
支持v-model、.sync方式。
上传图片效果图:
上传视频效果图:
最后: 对于组件中上传图片、视频的格式判断可自行更改,也可提取到props中,整个组件代码逻辑比较简单,大家自由发挥吧。
组件中视频上传封装参考了这篇文章:
https://zhuanlan.zhihu.com/p/108705388
发表评论 (审核通过后显示评论):