1. 程式人生 > >Vue Quill Editor自定義圖片/視訊上傳(Element UI + OSS)、字型、字型大小、段落等

Vue Quill Editor自定義圖片/視訊上傳(Element UI + OSS)、字型、字型大小、段落等

  近期專案中需要使用富文字編輯器,開始想到的富文字編輯器是百度的UEditor,UEditor功能齊全、外掛多,但是圖片只能上傳到本地伺服器,如果需要上傳到其他伺服器需要改動原始碼,而且是PHP、JSP、ASP、.Net版本,同時UEditor體積過大壓縮包有3.3M(jsp版本),載入速度慢。實際專案中並不需要那麼多的功能,只需要基礎的操作:字型、字型大小、標題、段落、圖片上傳、視訊上傳、居中對齊等;所以我們只需要一個輕量級的富文字編輯器就行,推薦使用Quill和TinyMCE,QuillTinyMCE都是輕量級,外掛多,功能強,編輯能力優秀,介面好看。選擇Quill的原因是它所有能看到的,不能看到的功能統統都是一個一個獨立的模組,全部都是可以替換的,可以自定義編輯。
  站在巨人

vue-quill-editor的肩膀上進行Quill editor的自定義圖片/視訊上傳(Element UI + OSS)、字型、字型大小、標題、段落等封裝。先看效果再來編碼:
這裡寫圖片描述

<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' ]
          }
        }
      }