Vue Quill Editor自定義圖片/視訊上傳(Element UI + OSS)、字型、字型大小、段落等
近期專案中需要使用富文字編輯器,開始想到的富文字編輯器是百度的UEditor,UEditor功能齊全、外掛多,但是圖片只能上傳到本地伺服器,如果需要上傳到其他伺服器需要改動原始碼,而且是PHP、JSP、ASP、.Net版本,同時UEditor體積過大壓縮包有3.3M(jsp版本),載入速度慢。實際專案中並不需要那麼多的功能,只需要基礎的操作:字型、字型大小、標題、段落、圖片上傳、視訊上傳、居中對齊等;所以我們只需要一個輕量級的富文字編輯器就行,推薦使用Quill和TinyMCE,Quill和TinyMCE都是輕量級,外掛多,功能強,編輯能力優秀,介面好看。選擇Quill的原因是它所有能看到的,不能看到的功能統統都是一個一個獨立的模組,全部都是可以替換的,可以自定義編輯。
站在巨人
<template>
<div>
<quill-editor ref="myTextEditor"
v-model="contentValue"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus ="onEditorFocus($event)"
@ready="onEditorReady($event)"
@change="onEditorChange($event)"
class="cfpa-quill-editor" :style="{ height: quillEditorHeight + 'px' }">
<div id="toolbar" slot="toolbar">
<!-- Add a bold button -->
<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>
<!--Add list -->
<button class="ql-list" value="ordered" title="有序列表"></button>
<button class="ql-list" value="bullet" title="無序列表"></button>
<!-- Add font size dropdown -->
<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>
</select>
<select class="ql-font" title="字型">
<option value="SimSun" selected="selected"></option>
<option value="SimHei"></option>
<option value="Microsoft-YaHei"></option>
<option value="KaiTi"></option>
<option value="FangSong"></option>
<option value="Arial"></option>
<!-- <option value="Times-New-Roman"></option>
<option value="sans-serif"></option> -->
</select>
<!-- Add subscript and superscript buttons -->
<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-link" title="超連結"></button>
<!-- You can also add your own -->
<button id="custom-button" @click.prevent="fnOpenUploadImage" title="圖片"><i class="iconfont icon-tupian"></i></button>
<button id="custom-button" @click.prevent="fnOpenUploadVideo" title="視訊"><i class="iconfont icon-video2"></i></button>
</div>
</quill-editor>
<div :style="wordCount" v-if="wordCount" class="cfpa-quill-wordCount">
<div class="cfpa-quill-wordCount-text">當前已經輸入<span style="color: red">{{contentLength}}</span>個字元</div>
</div>
<el-dialog :title="title" width="30%" :visible.sync="dialogFnOpenUpload" :close-on-click-modal="false">
<file-upload :accept="accept" :data_extra="data_extra" @fnUploadSucess="fnUploadSucess" @fnCloseDialog="dialogFnOpenUpload = false" ref="fileUpload"></file-upload>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogFnOpenUpload = false">取 消</el-button>
<el-button type="primary" @click="fnOpenUploadSubmit">確 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import FileUpload from '@/components/briefFileUpload'
import config from '@/config'
import { Quill, quillEditor } from 'vue-quill-editor'
// 圖片可收縮
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
// 自定義字型大小
let Size = Quill.import('attributors/style/size')
Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px']
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)
export default {
name: 'editor',
components: {
quillEditor,
FileUpload
},
props: {
value: {
type: String,
default: ''
},
editorHeight: {
type: Number,
default: 355
},
editorWordCount: {
type: Number,
default: 0
}
},
data () {
return {
contentValue: '',
preContent: '',
dialogFnOpenUpload: false,
accept: '',
uploadType: 'image',
editorOption: {
modules: {
toolbar: '#toolbar',
history: {
delay: 1000,
maxStack: 50,
userOnly: false
},
imageDrop: true,
imageResize: {
displayStyles: {
backgroundColor: 'black',
border: 'none',
color: 'white'
},
modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
}
},
placeholder: '請編寫內容...'
},
data_extra: {
parentId: 0,
fileName: ''
},
contentLength: 0,
wordCount: '',
title: '新增圖片',
quillEditorHeight: 300
}
},
computed: {
editor () {
return this.$refs.myTextEditor.quill
}
},
methods: {
/**
* @description [onEditorBlur 失去焦點]
* @author zoumiao
* @param {Object} editor 返回的quill物件
* @return {null} [沒有返回]
*/
onEditorBlur (editor) {
this.$emit('editorBlur')
},
/**
* @description [onEditorFocus 獲取焦點]
* @author zoumiao
* @param {Object} editor 返回的quill物件
* @return {null} [沒有返回]
*/
onEditorFocus (editor) {
this.$emit('editorFocus')
},
/**
* @description [onEditorReady 可以輸入]
* @author zoumiao
* @param {Object} editor 返回的quill物件
* @return {null} [沒有返回]
*/
onEditorReady (editor) {
},
/**
* @description [onEditorChange 輸入文字改變事件]
* @author zoumiao
* @param {Object} editor 返回的編輯物件{html, text, quill}
* @return {null} [沒有返回]
*/
onEditorChange (editor) {
let html = editor.html
this.preContent = html
this.$emit('input', html)
this.contentLength = this._.trim(editor.text).length
},
/**
* @description [fnOpenUploadImage 上傳圖片]
* @author zoumiao
* @return {null} [沒有返回]
*/
fnOpenUploadImage () {
this.uploadType = 'image'
this.accept = config.accept.image
this.title = '新增圖片'
this.dialogFnOpenUpload = true
},
/**
* @description [fnOpenUploadVideo 上傳視訊]
* @author zoumiao
* @return {null} [沒有返回]
*/
fnOpenUploadVideo () {
this.uploadType = 'video'
this.accept = config.accept.video
this.title = '新增視訊'
this.dialogFnOpenUpload = true
},
/**
* [fnOpenUploadSubmit 提交上傳檔案]
* @author zoumiao
* @return {null} [沒有返回]
*/
async fnOpenUploadSubmit () {
await this.$refs.fileUpload.$refs.upload.submit()
},
/**
* [fnUploadSucess 上傳檔案成功]
* @author zoumiao
* @param {Array} uploadFileUrlList [上傳檔案返回的url]
* @return {null} [沒有返回]
*/
fnUploadSucess (uploadFileUrlList) {
this.editor.focus()
for (let url of uploadFileUrlList) {
this.editor.insertEmbed(this.editor.getSelection().index, this.uploadType, url)
}
}
},
created () {
this.quillEditorHeight = document.body.clientHeight - this.editorHeight
this.contentValue = this.value
this.contentLength = this.editorWordCount || 0
},
mounted () {
let toolbar = document.querySelector('div.ql-toolbar.ql-snow')
if (toolbar) {
let toolbarHeight = toolbar.offsetHeight
this.wordCount = {
'top': `${toolbarHeight}px`
}
return
}
this.wordCount = {
'top': '42px'
}
},
watch: {
// Watch content change
value (newVal, oldVal) {
if (newVal && newVal !== this.preContent) {
this.preContent = newVal
this.contentValue = newVal
} else if (!newVal) {
this.contentValue = ''
}
}
}
}
</script>
<style lang="scss">
.cfpa-quill-editor {
line-height: 24px;
.ql-snow {
background-color: #ffffff;
}
}
.cfpa-quill-wordCount {
background-color: #ffffff;
position: relative;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
line-height: 20px;
font-size: 12px;
.cfpa-quill-wordCount-text{
text-align: right;
margin-right: 10px;
color: #aaa;
}
}
</style>
1、自定義字型、字型大小、段落
<div id="toolbar" slot="toolbar">
<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>
</select>
<select class="ql-font" title="字型">
<option value="SimSun" selected="selected"></option>
<option value="SimHei"></option>
<option value="Microsoft-YaHei"></option>
<option value="KaiTi"></option>
<option value="FangSong"></option>
<option value="Arial"></option>
<!-- <option value="Times-New-Roman"></option>
<option value="sans-serif"></option> -->
</select>
</div>
在Quill初始化之前進行註冊:
// 自定義字型大小
let Size = Quill.import('attributors/style/size')
Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px']
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)
自定義字型、字型大小、段落需要在使用Quill編輯器之前引入quill.css,可以在App.vue或者main.js中引入。
.ql-snow .ql-picker.ql-size,
.ql-snow .ql-picker.ql-header {
width: 75px !important;
}
.ql-snow .ql-picker.ql-font {
width: 80px !important;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before {
content: '10px';
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before {
content: '12px';
font-size: 12px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
content: '14px';
font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
content: '16px';
font-size: 16px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
content: '18px';
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
content: '20px';
font-size: 20px;
}
.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";
} */
2、自定義圖片和視訊上傳(Element UI + OSS)
<div id="toolbar" slot="toolbar">
<button id="custom-button" @click.prevent="fnOpenUploadImage" title="圖片"><i class="iconfont icon-tupian"></i></button>
<button id="custom-button" @click.prevent="fnOpenUploadVideo" title="視訊"><i class="iconfont icon-video2"></i></button>
</div>
自定義圖片和視訊上傳使用的是Element UI + OSS上傳元件,參考上一篇Vue Element UI + OSS上傳檔案,上傳成功之後需要把圖片或者視訊插入內容區域,通過檢視Quill文件,
insertEmbed(index: Number, type: String, value: any, source: String = 'api'): Delta
可以插入視訊和圖片:
/**
* [fnUploadSucess 提交上傳檔案函式]
* @author zoumiao
* @param {Array} uploadFileUrlList [上傳檔案返回的url]
* @return {null} [沒有返回]
*/
fnUploadSucess (uploadFileUrlList) {
this.editor.focus()
for (let url of uploadFileUrlList) {
this.editor.insertEmbed(this.editor.getSelection().index, this.uploadType, url)
}
}
3、字數統計
Quill沒有位元組提供字數統計,API提供了getLength方法,但是如果輸入空格,該方法也會計算為字元;通過editor-change事件來計算字元個數:
/**
* @description [onEditorChange 輸入文字改變事件]
* @author zoumiao
* @param {Object} editor 返回的編輯物件{html, text, quill}
* @return {null} [沒有返回]
*/
onEditorChange (editor) {
let html = editor.html
this.preContent = html
this.$emit('input', html)
this.contentLength = this._.trim(editor.text).length
},
4、圖片可收縮
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
Quill配置項中也要進行配置:
editorOption: {
modules: {
toolbar: '#toolbar',
history: {
delay: 1000,
maxStack: 50,
userOnly: false
},
imageDrop: true,
imageResize: {
displayStyles: {
backgroundColor: 'black',
border: 'none',
color: 'white'
},
modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
}
}
}