官網使用教程:github.com/surmon-chin…css
npm install vue-quill-editor --save
複製代碼
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor, /* { default global options } */)
複製代碼
// register quill modules, you need to introduce and register before the vue program is instantiated
import Quill from 'quill'
import yourQuillModule from '../yourModulePath/yourQuillModule.js'
Quill.register('modules/yourQuillModule', yourQuillModule)
複製代碼
<template>
<!-- bidirectional data binding(雙向數據綁定) -->
<quill-editor v-model="content"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)">
</quill-editor>
<!-- Or manually control the data synchronization(或手動控制數據流) -->
<quill-editor :content="content"
:options="editorOption"
@change="onEditorChange($event)">
</quill-editor>
</template>
<script>
// you can also register quill modules in the component
import Quill from 'quill'
import { someModule } from '../yourModulePath/someQuillModule.js'
Quill.register('modules/someModule', someModule)
export default {
data () {
return {
content: '<h2>I am Example</h2>',
editorOption: {
// some quill options
}
}
},
// manually control the data synchronization
// 若是須要手動控制數據同步,父組件須要顯式地處理changed事件
methods: {
onEditorBlur(quill) {
console.log('editor blur!', quill)
},
onEditorFocus(quill) {
console.log('editor focus!', quill)
},
onEditorReady(quill) {
console.log('editor ready!', quill)
},
onEditorChange({ quill, html, text }) {
console.log('editor change!', quill, html, text)
this.content = html
}
},
computed: {
editor() {
return this.$refs.myQuillEditor.quill
}
},
mounted() {
console.log('this is current quill instance object', this.editor)
}
}
</script>
複製代碼
<template >
<div title="拖動右下角,改變高度" class="quill-editor-container">
<quill-editor
ref="quillEditor"
v-model="editorValue"
:options="editorOption"
:disabled="disabled">
</quill-editor>
</div>
</template>
<script>
import Vue from 'vue'
import { quillEditor, Quill } from 'vue-quill-editor';
import ImageResize from 'quill-image-resize-module';
import { container, ImageExtend, QuillWatch } from './quill-image-extend-module';
import { VideoExtend, QuillVideoWatch } from './quill-video-extend-module'
import FileBlot from './quill-blot/file-blot.js';
import MagicUrl from './quill-magic-url/src/index.js';
// import MagicUrl from 'quill-magic-url';
import 'quill/dist/quill.snow.css';
Quill.debug('error');
Quill.register('modules/imageResize', ImageResize);
Quill.register('modules/ImageExtend', ImageExtend);
Quill.register('modules/VideoExtend', VideoExtend);
Quill.register('modules/magicUrl', MagicUrl);
Quill.register(FileBlot);
let icons = Quill.import('ui/icons');
icons['video'] = '<i class="iconfont icon-fujian2" style="font-size: 14px;"></i>';
export default {
components: {
quillEditor
},
props: {
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: ''
}
},
data: function () {
return {
editorValue: this.value,
editorOption: {
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike', 'clean', 'blockquote'],
[{ 'header': 1 }, { 'header': 2 }],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'color': [] }],
[{ 'align': [] }],
['link', 'image', 'video']
],
handlers: {
'image': function () {
QuillWatch.emit(this.quill.id);
},
'video': function() {
QuillVideoWatch.emit(this.quill.fileId);
}
}
},
imageResize: {
displaySize: true
},
ImageExtend: {
loading: true,
name: 'file',
action: '/api/v1.0/external/image',
response: (res) => {
if (res.location) return res.location;
console.log('upload image err: ', res.error);
return;
}
},
VideoExtend: {
loading: true,
name: 'file',
action: '/api/v1.0/external/file',
response: (res) => {
if (res.location) return res.location;
console.log('upload file err: ', res.error);
return;
}
},
magicUrl: {
globalRegularExpression: /(https?:\/\/|www\.|mailto:|tel:)[\S]+/g,
urlRegularExpression: /(https?:\/\/[\S]+)|(www.[\S]+)|(mailto:[\S]+)|(tel:[\S]+)/
}
},
theme: 'snow',
placeholder: this.placeholder
}
};
},
mount () {
// API.generateS3url({file_name: '12.txt'})
},
methods: {
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//能夠添加一些本身的自定義事件,如清空內容
clear() {
this.editorValue = ''
}
},
watch: {
value(newValue) {
if (newValue.indexOf('<img') > -1 && newValue.indexOf('src="https://km.sankuai.com') > -1) {
let reg = /<img.*?(?:>|\/>)/gi;
let removeImgValue = newValue.replace(reg, '');
this.editorValue = removeImgValue;
} else {
this.editorValue = newValue;
}
},
editorValue(newValue) {
this.$emit('input', newValue)
}
}
};
</script>
<style lang="postcss">
.quill-editor-container {
.quill-editor {
div::after {
content: '';
display: block;
width: 10px;
height: 10px;
/* background-color: gray; */
/* background-image:
linear-gradient(hsla(0,0%,80%,1) 50%, transparent 0),
linear-gradient(90deg,hsla(0,0%,80%,1) 50%, transparent 0); */
position: absolute;
right: 0;
bottom: 0;
cursor: grab;
}
.ql-container {
/* height: 400px; */
resize: vertical;
overflow-y: scroll;
.ql-editor {
color: #464646;
font-size: 14px;
font-family: PingFangSC-Regular,PingFangSC,"Helvetica Neue","Hiragino Sans GB","Arial","Microsoft YaHei","sans-serif";
blockquote {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 40px;
margin-inline-end: 40px;
padding-left: 16px;
border-left: 2px solid #ddd;
margin: 0px 0px 0px 8px;
color: #999;
::after ::before {
box-sizing: border-box;
}
}
}
}
}
}
</style>
複製代碼
/**
*@description 觀察者模式 全局監聽富文本編輯器
*/
export const QuillWatch = {
watcher: {}, // 登記編輯器信息
active: null, // 當前觸發的編輯器
on: function (imageExtendId, ImageExtend) { // 登記註冊使用了ImageEXtend的編輯器
if (!this.watcher[imageExtendId]) {
this.watcher[imageExtendId] = ImageExtend
}
},
emit: function (activeId, type = 1) { // 事件發射觸發
this.active = this.watcher[activeId]
if (type === 1) {
imgHandler()
}
}
}
/**
* @description 圖片功能拓展: 增長上傳 拖動 複製
*/
export class ImageExtend {
/**
* @param quill {Quill}富文本實例
* @param config {Object} options
* config keys: action, headers, editForm start end error size response
*/
constructor(quill, config = {}) {
this.id = Math.random()
this.quill = quill
this.quill.id = this.id
this.config = config
this.file = '' // 要上傳的圖片
this.imgURL = '' // 圖片地址
quill.root.addEventListener('paste', this.pasteHandle.bind(this), false)
quill.root.addEventListener('drop', this.dropHandle.bind(this), false)
quill.root.addEventListener('dropover', function (e) {
e.preventDefault()
}, false)
this.cursorIndex = 0
QuillWatch.on(this.id, this)
}
/**
* @description 粘貼
* @param e
*/
pasteHandle(e) {
// e.preventDefault()
QuillWatch.emit(this.quill.id, 0)
let clipboardData = e.clipboardData
let i = 0
let items, item, types
if (clipboardData) {
items = clipboardData.items;
if (!items) {
return;
}
item = items[0];
types = clipboardData.types || [];
for (; i < types.length; i++) {
if (types[i] === 'Files') {
item = items[i];
break;
}
}
if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
this.file = item.getAsFile()
let self = this
// 若是圖片限制大小
if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
if (self.config.sizeError) {
self.config.sizeError()
}
return
}
if (this.config.action) {
this.uploadImg()
} else {
QuillWatch.active.uploading();
QuillWatch.active.uploadSuccess();
this.toBase64()
}
}
}
}
/**
* 拖拽
* @param e
*/
dropHandle(e) {
QuillWatch.emit(this.quill.id, 0)
const self = this
e.preventDefault()
// 若是圖片限制大小
if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
if (self.config.sizeError) {
self.config.sizeError()
}
return
}
self.file = e.dataTransfer.files[0]; // 獲取到第一個上傳的文件對象
if (this.config.action) {
self.uploadImg()
} else {
self.toBase64()
}
}
/**
* @description 將圖片轉爲base4
*/
toBase64() {
const self = this
const reader = new FileReader()
reader.onload = (e) => {
// 返回base64
self.imgURL = e.target.result
self.insertImg()
}
reader.readAsDataURL(self.file)
}
/**
* @description 上傳圖片到服務器
*/
uploadImg() {
const self = this
let quillLoading = self.quillLoading
let config = self.config
// 構造表單
let formData = new FormData()
formData.append(config.name, self.file)
// 自定義修改表單
if (config.editForm) {
config.editForm(formData)
}
// 建立ajax請求
let xhr = new XMLHttpRequest()
xhr.open('post', config.action, true)
// 若是有設置請求頭
if (config.headers) {
config.headers(xhr)
}
if (config.change) {
config.change(xhr, formData)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
//success
let res = JSON.parse(xhr.responseText)
self.imgURL = config.response(res)
QuillWatch.active.uploadSuccess()
self.insertImg()
if (self.config.success) {
self.config.success()
}
} else {
//error
if (self.config.error) {
self.config.error()
}
QuillWatch.active.uploadError()
}
}
}
// 開始上傳數據
xhr.upload.onloadstart = function (e) {
QuillWatch.active.uploading()
// let length = (self.quill.getSelection() || {}).index || self.quill.getLength()
// self.quill.insertText(length, '[uploading...]', { 'color': 'red'}, true)
if (config.start) {
config.start()
}
}
// 上傳過程
xhr.upload.onprogress = function (e) {
let complete = (e.loaded / e.total * 100 | 0) + '%'
QuillWatch.active.progress(complete)
}
// 當發生網絡異常的時候會觸發,若是上傳數據的過程還未結束
xhr.upload.onerror = function (e) {
QuillWatch.active.uploadError()
if (config.error) {
config.error()
}
}
// 上傳數據完成(成功或者失敗)時會觸發
xhr.upload.onloadend = function (e) {
if (config.end) {
config.end()
}
}
xhr.send(formData)
}
/**
* @description 往富文本編輯器插入圖片
*/
insertImg() {
const self = QuillWatch.active
if (!this.config.timeline) {
self.quill.insertEmbed(QuillWatch.active.cursorIndex, 'image', self.imgURL)
self.quill.update()
}
// self.quill.blur()
self.quill.setSelection(self.cursorIndex);
}
/**
* @description 顯示上傳的進度
*/
progress(pro) {
pro = '[' + 'uploading' + pro + ']'
QuillWatch.active.quill.root.innerHTML
= QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, pro)
}
/**
* 開始上傳
*/
uploading() {
let length = (QuillWatch.active.quill.getSelection() || {}).index || QuillWatch.active.quill.getLength()
QuillWatch.active.cursorIndex = length
if (this.config.timeline) {
QuillWatch.active.quill.insertText(QuillWatch.active.cursorIndex - 1, '[uploading...]', {'color': 'red'}, true)
} else {
QuillWatch.active.quill.insertText(QuillWatch.active.cursorIndex, '[uploading...]', {'color': 'red'}, true)
}
}
/**
* 上傳失敗
*/
uploadError() {
QuillWatch.active.quill.root.innerHTML
= QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, '[upload error]')
}
uploadSuccess() {
QuillWatch.active.quill.root.innerHTML
= QuillWatch.active.quill.root.innerHTML.replace(/\[uploading.*?\]/, '')
}
}
/**
* @description 點擊圖片上傳
*/
export function imgHandler() {
let fileInput = document.querySelector('.quill-image-input');
if (fileInput === null) {
fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.classList.add('quill-image-input');
fileInput.style.display = 'none'
// 監聽選擇文件
fileInput.addEventListener('change', function () {
let self = QuillWatch.active
self.file = fileInput.files[0]
fileInput.value = ''
// 若是圖片限制大小
if (self.config.size && self.file.size >= self.config.size * 1024 * 1024) {
if (self.config.sizeError) {
self.config.sizeError()
}
return
}
if (self.config.action) {
self.uploadImg()
} else {
self.toBase64()
}
})
document.body.appendChild(fileInput);
}
fileInput.click();
}
/**
*@description 所有工具欄
*/
export const container = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}],
[{'indent': '-1'}, {'indent': '+1'}],
[{'direction': 'rtl'}],
[{'size': ['small', false, 'large', 'huge']}],
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}],
[{'font': []}],
[{'align': []}],
['clean'],
['link', 'image', 'video']
]
複製代碼