組件說(shuō)明:
(1)支持圖片上傳到服務(wù)器惠况,也可使用base64的方式
(2)支持視頻上傳,或者插入視頻鏈接
【其實(shí)無(wú)論是上傳圖片到服務(wù)器還是上傳視頻到服務(wù)器宁仔,其實(shí)本質(zhì)上都是上傳文件然后后端返回一個(gè)地址插入到編輯器中】
(3)當(dāng)前版本為vue 2.x + element ui稠屠,暫不支持vue 3.0
- 安裝
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>點(diǎn)擊上傳</em></div>-->
<!--<div class="el-upload__tip" slot="tip">最多只能上傳兩個(gè)附件</div>-->
<button ref="myinput">上傳文件</button>
</el-upload>
<!--視頻上傳-->
<div>
<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="請(qǐng)輸入視頻鏈接" 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>點(diǎn)擊上傳</em></div>
<div class="el-upload__tip" slot="tip">只能上傳MP4文件权埠,且不超過(guò){{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'
// 設(shè)置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默認(rèn)方式榨了,即base64方式
type: Object,
default(){
return {
uploadUrl: '', // 圖片上傳地址
maxSize: 2, // 圖片上傳大小限制,默認(rèn)不超過(guò)2M
name: 'Filedata' // 圖片上傳字段
}
}
},
uploadVideoConfig: { // 視頻上傳配置
type: Object,
default(){
return {
uploadUrl: '', // 上傳地址
maxSize: 10, // 圖片上傳大小限制攘蔽,默認(rèn)不超過(guò)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 () {
// 初始給編輯器設(shè)置title
addQuillTitle()
let toolbar = this.$refs['myQuillEditor'].quill.getModule('toolbar');
// 是否開(kāi)啟圖片上傳到服務(wù)器功能
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
},
// --------- 圖片上傳相關(guān) start ---------
addImageHandler(value){
if (value) {
// 觸發(fā)input框選擇圖片文件
this.$refs['myinput'].click();
} else {
this.quill.format('image', false)
}
},
// 把已經(jīng)上傳的圖片顯示回富文本編輯框中
uploadSuccess (imgurl) {
let quill = this.$refs['myQuillEditor'].quill
let range = quill.getSelection()
let index = 0;
if (range == null) {
index = 0;
} else {
index = range.index; // 獲取光標(biāo)所在位置
}
// 插入
quill.insertEmbed(index, 'image', imgurl) // imgurl是服務(wù)器返回的圖片鏈接地址
// 調(diào)整光標(biāo)到最后
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(`上傳文件大小不能超過(guò) ${this.uploadImgConfig.maxSize}MB!`)
}
return isLt1M && isIMAGE
},
// 文件上傳成功時(shí)的鉤子
onSuccess (response, file, fileList) { // ---- 注意這部分需要改為對(duì)應(yīng)的返回格式
this.hideLoading()
if (response.retCode === '00') {
this.uploadSuccess(response.url)
} else {
this.$message.error('上傳失敗')
}
},
// 文件上傳失敗時(shí)的鉤子
onError (file, fileList) {
this.hideLoading()
this.$message.error('上傳失敗')
},
// --------- 圖片上傳相關(guān) end ---------
// --------- 視頻上傳相關(guān) start ---------
addVideoLink(videoLink) {
if (!videoLink) return this.$message.error('請(qǐng)輸入視頻地址')
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)
// 調(diào)整光標(biāo)到最后
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(`上傳文件大小不能超過(guò) ${this.uploadVideoConfig.maxSize}MB!`)
}
return isLt1M && isVideo
},
// 文件上傳成功時(shí)的鉤子
onSuccessVideo (response, file, fileList) { // ---- 注意這部分需要改為對(duì)應(yīng)的返回格式
this.hideLoading()
if (response.retCode === '00') {
this.addVideoLink(response.url)
} else {
this.$message.error('上傳失敗')
}
},
// 文件上傳失敗時(shí)的鉤子
onErrorVideo (file, fileList) {
this.hideLoading()
this.$message.error('上傳失敗')
},
// --------- 視頻上傳相關(guān) 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這個(gè)文件是拿來(lái)給工具欄設(shè)置title的阻逮,代碼如下:
const titleConfig = {
'ql-bold': '加粗',
'ql-font': '字體',
'ql-code': '插入代碼',
'ql-italic': '斜體',
'ql-link': '添加鏈接',
'ql-color': '字體顏色',
'ql-background': '背景顏色',
'ql-size': '字體大小',
'ql-strike': '刪除線',
'ql-script': '上標(biāo)/下標(biāo)',
'ql-underline': '下劃線',
'ql-blockquote': '引用',
'ql-header': '標(biāo)題',
'ql-indent': '縮進(jìn)',
'ql-list': '列表',
'ql-align': '文本對(duì)齊',
'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 = '下標(biāo)' : item.title = '上標(biāo)'
} else if (item.className === 'ql-indent') {
item.value === '+1' ? item.title = '向右縮進(jìn)' : item.title = '向左縮進(jìn)'
} else {
item.title = titleConfig[item.className]
}
})
// 字體顏色和字體背景特殊處理,兩個(gè)在相同的盒子
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]
}
})
}
title.gif
組件使用: 注意props秩彤,其中 uploadImgConfig 是針對(duì)上傳圖片的配置叔扼,如果不傳則默認(rèn)使用原始的base64方式,視頻上傳必須要有上傳服務(wù)器的地址
image.png
組件復(fù)制過(guò)去后漫雷,還需更改下下面這部分代碼瓜富,根據(jù)自己的接口返回值類型更改:
image.png
組件調(diào)用方式
支持v-model、.sync方式降盹。
上傳圖片效果圖:
image.png
上傳視頻效果圖:
image.png
image.png
最后: 對(duì)于組件中上傳圖片与柑、視頻的格式判斷可自行更改,也可提取到props中蓄坏,整個(gè)組件代碼邏輯比較簡(jiǎn)單价捧,大家自由發(fā)揮吧。
組件中視頻上傳封裝參考了這篇文章:
https://zhuanlan.zhihu.com/p/108705388
如果對(duì)你有幫助涡戳,點(diǎn)個(gè)贊支持下吧结蟋。