完整demo地址 点击
ui框架 iview
视频 图片 quill 已内部实现
在toolbar 加入相应配置即可
//地址插入
insertFile(url, type) {
if (url !== null) {
// 获取光标所在位置
let length = this.Quill.selection.savedRange.index;
// 插入图片
if (type == "image") {
this.Quill.insertEmbed(length, "image", url);
this.Quill.setSelection(length + 1);
} else if (type == "video") {
this.Quill.insertEmbed(length, "video", url);
this.Quill.setSelection(length + 1);
} else if (type == "audio") {
this.Quill.insertEmbed(length, "audio", { url: url }, "api");
this.Quill.setSelection(length + 1); //光标位置向后移动一位
}
}
},
//自定义点击事件 打开弹框(我需要上传oss 所以自定义了弹框)
🤷难点 音频插入
1.创建按钮
initAudioButton: function () {
//初始化"audio"按钮样式
const audioButtons = document.querySelectorAll(".ql-audio"); //"ql-" 是插件自动加的前缀
audioButtons.forEach((item) => {
item.innerHTML =
'<i class="ivu-icon ivu-icon-md-volume-up" style="font-size: 20px;float: left;"></i>';
});
},
2.注册按钮 toolbar
["link", "image", "video", "audio"],
3.自定义audio 上传组件弹框
<!-- 音频 -->
<audio-upload
ref="audioUpload"
@onSuccess="insertFile($event, 'audio')"
></audio-upload>
4.自定义 AudioBlot 坑点看下面注释
//AudioBlot.js
// 音频解析
import Quill from "quill";
const Base = Quill.import('blots/embed');
class AudioBlot extends Base {
static create(value) {
let node = super.create(value);
node.setAttribute('src', value.url);
node.setAttribute('controls', false);
//外层套入div (不套入会产生无法删除乱起八糟的问题😀)
let divNode = document.createElement("span");
divNode.appendChild(node)
return divNode;
}
// 添加value获取当前的audio元素。拿到audio元素的属性。
static value(domNode) {
const value = {
url: '',
};
// 这里要加判断。不然会显示undefined
if (domNode.getElementsByTagName('audio')[0] && domNode.getElementsByTagName('audio')[0].getAttribute('src')) {
value.url = domNode.getElementsByTagName('audio')[0].getAttribute('src')
}
return value;
}
static update(mutations, context) {
mutations.forEach((mutation) => {
if (
mutation.type === "childList" &&
(Array.from(mutation.removedNodes).includes(this.leftGuard) ||
Array.from(mutation.removedNodes).includes(this.rightGuard))
) {
let tag;
if (mutation.previousSibling) {
tag = mutation.previousSibling.innerText;
} else if (mutation.nextSibling) {
tag = mutation.nextSibling.innerText;
}
if (tag) {
super.replaceWith("audio", tag);
}
}
});
super.update(mutations, context);
}
}
AudioBlot.blotName = 'audio';
AudioBlot.tagName = 'audio';
export default AudioBlot;
5.引入 注册 blot
import AudioBlot from "./component/audio-blot";
Quill.register(AudioBlot);
//点击事件
audio: function (value) {
if (value) {
_this.$refs.audioUpload.open();
} else {
_this.quill.format("audio", false);
}
},
完整代码
<template>
<div class="my-quill">
<div ref="editor"></div>
<!-- 上传文件到OSS -->
<image-upload
ref="imageUpload"
@onSuccess="insertFile($event, 'image')"
></image-upload>
<!-- 视频 -->
<video-upload
ref="videoUpload"
@onSuccess="insertFile($event, 'video')"
></video-upload>
<!-- 音频 -->
<audio-upload
ref="audioUpload"
@onSuccess="insertFile($event, 'audio')"
></audio-upload>
</div>
</template>
<script>
import Quill from "quill";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { ImageDrop } from "quill-image-drop-module";
import ImageResize from "quill-image-resize-module";
import AudioBlot from "./component/audio-blot";
Quill.register(AudioBlot);
Quill.register("modules/imageDrop", ImageDrop);
Quill.register("modules/imageResize", ImageResize);
import imageUpload from "./component/image-upload.vue";
import videoUpload from "./component/video-upload.vue";
import audioUpload from "./component/audio-upload.vue";
export default {
name: "my-quill",
components: {
imageUpload,
videoUpload,
audioUpload,
},
props: {
value: {
type: String,
default: "",
},
toolbar: {
type: Array,
default: () => [
["bold", "italic", "underline", "strike"],
[
{
header: [1, 2, 3, 4, 5, 6, false],
},
],
[
{
size: ["small", false, "large", "huge"],
},
],
[
{
color: [],
},
{
background: [],
},
],
["blockquote", "code-block"],
[
{
list: "ordered",
},
{
list: "bullet",
},
],
// [{ 'script': 'sub' }, { 'script': 'super' }],
[
{
indent: "-1",
},
{
indent: "+1",
},
],
[
{
align: [],
},
],
[
{
direction: "rtl",
},
],
// [{ 'font': [] }],
["clean"],
["link", "image", "video", "audio"],
],
},
},
data() {
var _this = this;
return {
Quill: null,
currentValue: "",
options: {
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
toolbar: {
container: this.toolbar,
handlers: {
image: function (value) {
if (value) {
_this.$refs.imageUpload.open();
} else {
_this.quill.format("image", false);
}
},
video: function (value) {
if (value) {
_this.$refs.videoUpload.open();
} else {
_this.quill.format("video", false);
}
},
audio: function (value) {
if (value) {
_this.$refs.audioUpload.open();
} else {
_this.quill.format("audio", false);
}
},
},
},
history: {
delay: 1000,
maxStack: 50,
userOnly: false,
},
imageDrop: true,
imageResize: {
displayStyles: {
backgroundColor: "black",
border: "none",
color: "white",
},
modules: ["Resize", "DisplaySize", "Toolbar"],
},
},
initAudioButton: function () {
//初始化"audio"按钮样式
const audioButtons = document.querySelectorAll(".ql-audio"); //"ql-" 是插件自动加的前缀
audioButtons.forEach((item) => {
item.innerHTML =
'<i class="ivu-icon ivu-icon-md-volume-up" style="font-size: 20px;float: left;"></i>';
});
},
placeholder: "内容...",
readOnly: false,
},
};
},
watch: {
value: {
handler(val) {
if (val !== this.currentValue) {
this.currentValue = val;
if (this.Quill) {
// this.Quill.pasteHTML(this.value);
this.$refs.editor.children[0].innerHTML = newVal;
}
}
},
immediate: true,
},
},
methods: {
init() {
const editor = this.$refs.editor;
// 初始化编辑器
this.Quill = new Quill(editor, this.options);
// 默认值
this.Quill.pasteHTML(this.currentValue);
this.Quill.options.initAudioButton(); //初始化音频图标,这样才能显示
},
//插入图片音频视频
insertFile(url, type) {
if (url !== null) {
// 获取光标所在位置
let length = this.Quill.selection.savedRange.index;
// 插入图片
if (type == "image") {
this.Quill.insertEmbed(length, "image", url);
this.Quill.setSelection(length + 1);
} else if (type == "video") {
this.Quill.insertEmbed(length, "video", url);
this.Quill.setSelection(length + 1);
} else if (type == "audio") {
this.Quill.insertEmbed(length, "audio", { url: url }, "api");
this.Quill.setSelection(length + 1); //光标位置向后移动一位
}
}
},
},
mounted() {
this.init();
},
beforeDestroy() {
console.log("销毁");
// 在组件销毁后销毁实例
this.Quill = null;
},
};
</script>
<style lang="less">
</style>