Quill富文本的使用 官网 https://quilljs.com/docs/quickstart/
1、安装quill
使用 mpn i quill -S
2、新建myquil.vue文件,内容如下
<template>
<div class="quill-editor">
<slot name="toolbar"></slot>
<div ref="editor"></div>
<div id="editor"></div>
<!-- 测试video标签 -->
<!-- <video src="https://www.w3school.com.cn/i/movie.mp4" controls="controls" width="100%" height="100%" webkit-playsinline="true" playsinline="true" x5-playsinline="true"></video> -->
<input id="uploadImg" ref="uploadImg" type="file" style="display:none" accept="image/jpeg, image/png"
@change="uploadImage">
<input id="uploadVideo" ref="uploadVideo" type="file" style="display:none" accept="video/*" @change="uploadVideo">
</div>
</template>
<script>
// require sources
import _Quill from 'quill'
import defaultOptions from './options'
const Quill = window.Quill || _Quill
// pollfill
if (typeof Object.assign != 'function') {
Object.defineProperty(Object, 'assign', {
value(target, varArgs) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
const to = Object(target)
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index]
if (nextSource != null) {
for (const nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey]
}
}
}
}
return to
},
writable: true,
configurable: true
})
}
//视频处理
const BlockEmbed = Quill.import('blots/block/embed')
class VideoBlot extends BlockEmbed {
static create(value) {
let node = super.create()
node.setAttribute('src', value.url)
node.setAttribute('controls', value.controls)
node.setAttribute('width', value.width)
node.setAttribute('height', value.height)
node.setAttribute('webkit-playsinline', true)
node.setAttribute('playsinline', true)
node.setAttribute('x5-playsinline', true)
return node
}
static value(node) {
return {
url: node.getAttribute('src'),
controls: node.getAttribute('controls'),
width: node.getAttribute('width'),
height: node.getAttribute('height')
}
}
}
VideoBlot.blotName = 'simpleVideo'
VideoBlot.tagName = 'video'
Quill.register(VideoBlot)
// export
export default {
name: 'myquill',
data() {
return {
_options: {},
_content: '',
defaultOptions
}
},
props: {
content: String,
value: String,
disabled: {
type: Boolean,
default: false
},
options: {
type: Object,
required: false,
default: () => ({})
},
globalOptions: {
type: Object,
required: false,
default: () => ({})
}, //文件大小阈值,单位字节B,大于1M=1024B
threshold: {
type: Number,
default: 1025
},
//宽度
width: {
type: Number,
default: 1080
},
//高度
height: {
type: Number,
default: 100
},
//高度
quality: {
type: Number,
default: 0.2
}
},
mounted() {
this.initialize()
},
beforeDestroy() {
this.quill = null
delete this.quill
},
methods: {
// Init Quill instance
initialize() {
if (this.$el) {
// Options
this._options = Object.assign({}, this.defaultOptions, this.globalOptions, this.options)
// Instance
this.quill = new Quill(this.$refs.editor, this._options)
this.quill.getModule('toolbar').addHandler('image', this.uploadImageHandler)
this.quill.getModule('toolbar').addHandler('video', this.uploadVideoHandler)
this.quill.enable(false)
// Set editor content
if (this.value || this.content) {
this.quill.pasteHTML(this.value || this.content)
}
// Disabled editor
if (!this.disabled) {
this.quill.enable(true)
}
// Mark model as touched if editor lost focus
this.quill.on('selection-change', (range) => {
if (!range) {
this.$emit('blur', this.quill)
} else {
this.$emit('focus', this.quill)
}
})
// Update model if text changes
this.quill.on('text-change', (delta, oldDelta, source) => {
let html = this.$refs.editor.children[0].innerHTML
const quill = this.quill
const text = this.quill.getText()
if (html === '<p><br></p>') html = ''
this._content = html
this.$emit('input', this._content)
this.$emit('change', { html, text, quill })
})
// Emit ready event
this.$emit('ready', this.quill)
}
},
uploadImageHandler() {
const input = document.querySelector('#uploadImg')
input.value = ''
input.click()
// this.$refs.uploadImage.value = "";
// this.$refs.uploadImage.click();
},
//el-upload文件上传的处理逻辑
beforeUpload(file) {
const isJPGorPNG = file.type === 'image/jpeg' || file.type === 'image/png'
const isLessthan2M = file.size / 1024 / 1024 < 2 //最大限制2M
if (!isJPGorPNG) {
this.$message.error('上传图片只能是 JPG,PNG 格式!')
}
if (!isLessthan2M) {
this.$message.error('上传图片大小不能超过 2MB!')
}
return isJPGorPNG && isLessthan2M
},
uploadImage(event) {
var file = event.target.files[0]
const that = this
if (that.beforeUpload(file)) {
//上传图片大于1M进行压缩
if (file.size / 1024 > that.threshold) {
this.condenseFile(file, function(base64Codes) {
that.uploadImageSucess(1, base64Codes)
})
} else {
this.convertBase64Url(file)
}
this.uploadImageSucess(1, this.condenseBase64)
}
},
uploadVideoHandler() {
const input = document.querySelector('#uploadVideo')
input.value = ''
input.click()
// this.$refs.uploadVideo.value = "";
// this.$refs.uploadVideo.click();
},
uploadVideo(event) {
if (typeof XMLHttpRequest === 'undefined') {
return
}
var xhr = new XMLHttpRequest()
const formData = new FormData()
// formData.append('upload_file', event.target.files[0])
var text = '成功'
const that = this
xhr.onload = function onload() {
if (xhr.status < 200 || xhr.status >= 300) {
// return option.onError(this.getError(action, option, xhr));
text = xhr.responseText || xhr.response
alert('失败' + text)
}
// console.log(text)
that.uploadImageSucess(2, 'https://www.w3school.com.cn/i/movie.mp4')
}
xhr.open('post', 'http://localhost:8088/test', true)
xhr.send(formData)
},
uploadImageSucess(type, url) {
// const addImageRange = this.quill.getSelection()
// const newRange = 0 + (addImageRange !== null ? addImageRange.index : 0)
let newRange = this.quill.selection.savedRange.index
if (type === 1) {
// const url =
// 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
this.quill.insertEmbed(newRange, 'image', url)
this.quill.setSelection(1 + newRange)
console.log(
'$ uploadImageSucess 7777 his.quill.selection.savedRange.index:{this.quill.selection.savedRange.index}'
)
} else {
// const url = 'https://www.w3school.com.cn/i/movie.mp4'
this.quill.insertEmbed(newRange, 'simpleVideo', {
url,
controls: 'controls',
width: '320',
height: '240',
autoplay: 'autoplay'
})
this.quill.setSelection(1 + newRange)
}
//获取内容
// console.log(this.quill.getContents())
// console.log(this.quill.getText())
// var html = this.quill.root.innerHTML
// alert(html);
// console.log(html)
// var xx = this.quillGetHTML(this.quill.getContents())
// alert(xx);
// console.log(xx)
},
//获取富文本信息
quillGetHTML(inputDelta) {
//护球编辑器的内容
var tempCont = document.createElement('div')
new Quill(tempCont).setContents(inputDelta)
return tempCont.getElementsByClassName('ql-editor')[0].innerHTML
},
//文件转换成base64字符串
convertBase64Url(file) {
var ready = new FileReader()
ready.readAsDataURL(file)
const that = this
ready.onload = function() {
var fileResult = this.result
that.uploadImageSucess(1, fileResult)
}
},
//压缩文件第一步
condenseFile(file, callback) {
var ready = new FileReader()
ready.readAsDataURL(file)
const that = this
ready.onload = function() {
var fileResult = this.result
that.condenseCanvasDataURL(fileResult, callback)
}
},
//压缩文件第二步,重新绘制图片,并返回压缩以后的文件的base64的值,可以把base64的值作为参数传给回调接口
condenseCanvasDataURL(path, callback) {
var img = new Image()
img.src = path
const that1 = this
img.onload = function() {
var that = this
//默认压缩后图片规格
var quality = 0.5
var w = that.width
var h = that.height
var scale = w / h
//计算图片的实际大小,设置宽高比例
w = w > that1.width ? that1.width : w
h = w / scale
if (that1.quality && that1.quality > 0 && that1.quality <= 1) {
quality = that1.quality
}
//生成canvas
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
// 创建属性节点
var anw = document.createAttribute('width')
anw.nodeValue = w
var anh = document.createAttribute('height')
anh.nodeValue = h
canvas.setAttributeNode(anw)
canvas.setAttributeNode(anh)
ctx.drawImage(that, 0, 0, w, h)
var base64 = canvas.toDataURL('image/jpeg', quality)
// 回调函数返回压缩以后的文件的base64的值
callback(base64)
}
},
condenseconvertBase64UrlToBlob(urlData) {
var arr = urlData.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
},
//压缩文件第一步
uploadFile(event) {
if (typeof XMLHttpRequest === 'undefined') {
return
}
var xhr = new XMLHttpRequest()
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = (e.loaded / e.total) * 100
}
// option.onProgress(e);吧上传进度回调给其他的接口
}
}
const formData = new FormData()
// formData.append('upload_file', event.target.files[0]);
var file = event.target.files[0]
const that = this
//上传图片大于1M进行压缩
if (file.size / 1024 > 1025) {
that.photoCompress(file, { quality: 0.2 }, function(base64Codes) {
var bl = that.convertBase64UrlToBlob(base64Codes)
formData.append('files', bl, file.name)
})
} else {
formData.append('files', file, file.name)
}
xhr.onerror = function error(e) {
alert('失败' + e)
}
var text = '成功'
xhr.onload = function onload() {
if (xhr.status < 200 || xhr.status >= 300) {
text = xhr.responseText || xhr.response
alert('失败' + text)
}
// console.log(text)
//上传成功,根据xhr.response的返回信息中的url,将url插入到编辑区
that.uploadImageSucess(1)
}
xhr.open('post', 'http://localhost:8084/test', true)
xhr.send(formData)
},
//图片压缩处理 压缩文件第一步
photoCompress(file, objCompressed, objDiv) {
var ready = new FileReader()
ready.readAsDataURL(file)
const that = this
ready.onload = function() {
var fileResult = this.result
that.canvasDataURL(fileResult, objCompressed, objDiv)
}
},
//压缩文件第二步
canvasDataURL(path, objCompressed, callback) {
var img = new Image()
img.src = path
img.onload = function() {
var that = this
//默认压缩后图片规格
var quality = 0.5
var w = that.width
var h = that.height
var scale = w / h
//实际要求
w = objCompressed.width || w
h = objCompressed.height || w / scale
if (objCompressed.quality && objCompressed.quality > 0 && objCompressed.quality <= 1) {
quality = objCompressed.quality
}
//生成canvas
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
// 创建属性节点
var anw = document.createAttribute('width')
anw.nodeValue = w
var anh = document.createAttribute('height')
anh.nodeValue = h
canvas.setAttributeNode(anw)
canvas.setAttributeNode(anh)
ctx.drawImage(that, 0, 0, w, h)
var base64 = canvas.toDataURL('image/jpeg', quality)
// 回调函数返回base64的值
callback(base64)
}
},
//上传文件压缩的处理完成之后,回调方法 压缩文件第三步
convertBase64UrlToBlob(urlData) {
var arr = urlData.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
},
watch: {
// Watch content change
content(newVal, oldVal) {
if (this.quill) {
if (newVal && newVal !== this._content) {
this._content = newVal
this.quill.pasteHTML(newVal)
} else if (!newVal) {
this.quill.setText('')
}
}
},
// Watch content change
value(newVal, oldVal) {
if (this.quill) {
if (newVal && newVal !== this._content) {
this._content = newVal
this.quill.pasteHTML(newVal)
} else if (!newVal) {
this.quill.setText('')
}
}
},
// Watch disabled change
disabled(newVal, oldVal) {
if (this.quill) {
this.quill.enable(!newVal)
}
}
}
}
</script>
options.js文件内容如下
export default {
theme: 'snow',
boundary: document.body,
modules: {
toolbar: [
['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: [] }],
['link', 'image', 'video', 'formula'],
['clean']
]
},
// placeholder: 'Insert text here ...',
placeholder: '我的编辑器占位符...',
readOnly: false
}
3、再其他的vue文件中引用,内容如下
<template>
<!-- 使用 quill富文本编辑器组件-->
<myquill v-model="content" ref="myQuillEditor"></myquill>
</template>
<script>
import myquill from "./myquil.vue"; //导入定义的myquil.vue文件
export default {
name: "App",
components: {
myquill //注册quill富文本编辑器组件
},
data() {
return {
content: "quill富文本编辑器初始值"
};
}
};
</script>
<style scoped>
.ql-container.ql-snow {
margin-bottom: 22px;
}
.ql-editor {
height: 500px;
}
</style>