前言
- 前言
上文介绍了quill的基础使用,这里主要针对我们不想以图片base64文本格式提交,而是上传服务器获取图片url,以url提交的实现方式以及视频上传的实现 - 思路
1、隐藏一个input框,使用quill的handler事件实现点击上传到服务器
这种方式可以参考作者再见紫罗兰https://www.cnblogs.com/linxiyue/p/10305047.html
2、由于项目使用饿了么,所以我直接使用其提供的upload组件和Quill自定义定制组合实现上传图片和视频的功能
两个思路方法一直,都可以实现
上传图片
import Quill from "quill";
import {quillRedefine} from 'vue-quill-editor-upload'
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
components:{ quillRedefine },
options: {
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
toolbar: {
container:[
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: ["small", false, "large", "huge"] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video"],
]
},
},
placeholder: "请输入内容",
readOnly: false,
lenIndex: 0
},
export function getToken() {
return Cookies.get(TokenKey)
}
1、引入token
import { getToken } from "@/utils/auth";
2、上传参数
upload: {
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/file?type=1"
},
<div
class="editor"
ref="editor"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)"
:style="styles">
</div>
<el-upload
class="uploadder-ed"
name="file"
:headers="upload.headers"
:action="upload.url"
:show-file-list="false"
:on-success="handleFileSuccess"
>
</el-upload>
解释:
@focus="onEditorFocus($event)" 获取焦点事件
@change="onEditorChange($event)"// 内容改变事件
:on-success="handleFileSuccess"// 文件上传成功执行函数
onEditorFocus(e){ this.lenIndex = e.getSelection().index; },
onEditorChange(e){ this.lenIndex = e.quill.getSelection().index; },
handlers: {
'image': function (value) {
if (value) {
document.querySelector('.uploadder-ed input').click()
} else {
this.quill.format('image', false);
}
},
}
- el-upload执行handleFileSuccess将地址插入到编辑器中:
handleFileSuccess(response, file, fileList) {
if (response.code == 200) {
this.Quill.insertEmbed(this.lenIndex, 'image', response.data.url);
this.Quill.setSelection(this.lenIndex + 1)
}
},
上传视频
同样步骤
upload组件
<el-upload
class="upload-demo"
name="file"
:headers="upload.headers"
:action="upload.url"
:show-file-list="false"
:on-success="handleFileSuccess1"
>
</el-upload>
option下handlers里面添加
'video': function (value) {
if (value) {
document.querySelector('.upload-demo input').click()
} else {
this.Quill.format('video', false);
}
}
handleFileSuccess1(response, file, fileList) {
if (response.code == 200) {
const addImageRange = this.Quill.getSelection()
const newRange = 0 + (addImageRange !== null ? addImageRange.index : 0)
this.Quill.insertEmbed(newRange, 'video', response.data.url)
this.Quill.setSelection(1 + newRange)
}
- 上传后video是放在iframe中的,一般情况下是没有问题的,但在小程序中使用h5页面时,iframe中的域名需要添加到小程序业务域名中,否则会禁止访问。本地http与远程服务https地址也会出现禁止访问
定制Video
更好的解决方法是简单的添加一个video元素,而不是iframe,我们需要定制一个Video Embed
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)
handleFileSuccess1(response, file, fileList) {
if (response.code == 200) {
const addImageRange = this.Quill.getSelection()
const newRange = 0 + (addImageRange !== null ? addImageRange.index : 0)
this.Quill.insertEmbed(newRange, 'simpleVideo', {
url:response.data.url,
controls: 'controls',
width: '100%',
height: '30%'
})
this.Quill.setSelection(1 + newRange)
}
全部代码
<template>
<div>
<div class="editor" ref="editor" @focus="onEditorFocus($event)"
@change="onEditorChange($event)" :style="styles"></div>
<el-upload
class="uploadder-ed"
name="file"
:headers="upload.headers"
:action="upload.url"
:show-file-list="false"
:on-success="handleFileSuccess"
>
</el-upload>
<el-upload
class="upload-demo"
name="file"
:headers="upload.headers"
:action="upload.url"
:show-file-list="false"
:on-success="handleFileSuccess1"
>
</el-upload>
</div>
</template>
<script>
import Quill from "quill";
import {quillRedefine} from 'vue-quill-editor-upload'
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { getToken } from "@/utils/auth";
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 default {
name: "Editor",
props: {
value: {
type: String,
default: "",
},
height: {
type: Number,
default: null,
},
minHeight: {
type: Number,
default: null,
},
},
components:{ quillRedefine },
data() {
return {
upload: {
headers: { Authorization: "Bearer " + getToken() },
url: process.env.VUE_APP_BASE_API + "/file?type=1"
},
Quill: null,
currentValue: "",
options: {
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
toolbar: {
container:[
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ size: ["small", false, "large", "huge"] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
["clean"],
["link", "image", "video"],
],
handlers: {
'image': function (value) {
if (value) {
document.querySelector('.uploadder-ed input').click()
} else {
this.quill.format('image', false);
}
},
'video': function (value) {
if (value) {
document.querySelector('.upload-demo input').click()
} else {
this.Quill.format('video', false);
}
}
}
},
},
placeholder: "请输入内容",
readOnly: false,
lenIndex: 0
},
};
},
computed: {
styles() {
let style = {};
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`;
}
if (this.height) {
style.height = `${this.height}px`;
}
return style;
},
},
watch: {
value: {
handler(val) {
if (val !== this.currentValue) {
this.currentValue = val === null ? "" : val;
if (this.Quill) {
this.Quill.pasteHTML(this.currentValue);
}
}
},
immediate: true,
},
},
mounted() {
this.init();
},
beforeDestroy() {
this.Quill = null;
},
methods: {
onEditorFocus(e){ this.lenIndex = e.getSelection().index; },
onEditorChange(e){ this.lenIndex = e.quill.getSelection().index; },
handleFileSuccess(response, file, fileList) {
if (response.code == 200) {
this.Quill.insertEmbed(this.lenIndex, 'image', response.data.url);
this.Quill.setSelection(this.lenIndex + 1)
}
},
handleFileSuccess1(response, file, fileList) {
if (response.code == 200) {
const addImageRange = this.Quill.getSelection()
const newRange = 0 + (addImageRange !== null ? addImageRange.index : 0)
this.Quill.insertEmbed(newRange, 'simpleVideo', {
url:response.data.url,
controls: 'controls',
width: '100%',
height: '30%'
})
this.Quill.setSelection(1 + newRange)
}
},
init() {
const editor = this.$refs.editor;
this.Quill = new Quill(editor, this.options);
this.Quill.pasteHTML(this.currentValue);
this.Quill.on("text-change", (delta, oldDelta, source) => {
const html = this.$refs.editor.children[0].innerHTML;
const text = this.Quill.getText();
const quill = this.Quill;
this.currentValue = html;
this.$emit("input", html);
this.$emit("on-change", { html, text, quill });
});
this.Quill.on("text-change", (delta, oldDelta, source) => {
this.$emit("on-text-change", delta, oldDelta, source);
});
this.Quill.on("selection-change", (range, oldRange, source) => {
this.$emit("on-selection-change", range, oldRange, source);
});
this.Quill.on("editor-change", (eventName, ...args) => {
this.$emit("on-editor-change", eventName, ...args);
});
},
},
};
</script>
<style>
.editor, .ql-toolbar {
white-space: pre-wrap!important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
</style>