npm install quill --save
npm install vue-quill-editor --save
npm install quill-image-resize-module --save
<template>
<div>
<quill-editor
v-model="content"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)"
:style="styles"
>
quill-editor>
<el-upload
class="upload-demo"
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-error="handleUploadError"
:on-success="handleUploadSuccess"
:headers="headers"
style="display: none"
ref="upload"
>
<el-button id="uploadInput" type="primary">点击上传el-button>
el-upload>
div>
template>
<script>
import { quillEditor, Quill } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import imageResize from "quill-image-resize-module"; //引入图片编辑组件
Quill.register("modules/imageResize", imageResize);
import { getToken } from "@/utils/auth";
export default {
name: "Editor",
props: {
/* 编辑器的内容 */
value: {
type: String,
default: "",
},
/* 高度 */
height: {
type: Number,
default: 350,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
// 上传文件大小限制(MB)
fileSize: {
type: Number,
default: 5,
},
/* 类型(base64格式、url格式) */
type: {
type: String,
default: "url",
},
},
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,
},
},
data() {
return {
uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
headers: {
Authorization: "Bearer " + getToken(),
},
content: "",
editorOption: {
modules: {
//其他配置,如quill-image-extend-module
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"],
["voice"], //新添加的工具
],
handlers: {
voice: function (value) {
//添加工具方法,即点击时模仿点击上传组件的按钮
this.uploadType = "audio";
document.getElementById("uploadInput").click();
},
},
},
imageResize: {
//控制图片编辑的,实现功能就是这一段代码
displayStyles: {
backgroundColor: "black",
border: "none",
color: "white",
},
modules: ["Resize", "DisplaySize", "Toolbar"],
},
},
initVoiceButton: function () {
//初始化"voice"按钮样式
const voiceButton = document.querySelector(".ql-voice"); //"ql-" 是插件自动加的前缀
// 当然,可以直接手写样式,如:
// voiceButton.style.cssText =
// "width:10px;font-weight:400";
// voiceButton.innerText = "上传音频";
voiceButton.innerHTML = ""
const elIconMicrophone = document.querySelector("#microphone")
elIconMicrophone.style.cssText = "color:#444444;font-size:16px;font-weight:800" // 麦克风图标样式
},
},
addRange: [],
uploadData: {},
photoUrl: "",
uploadType: "",
};
},
components: {
quillEditor,
},
methods: {
onEditorBlur() {
//失去焦点事件
},
onEditorFocus() {
//获得焦点事件
},
onEditorChange() {
//内容改变事件
this.$emit("input", this.content);
},
// 上传前校检格式和大小
handleBeforeUpload(file) {
// console.log("before")
// 校检文件大小
if (this.fileSize) {
console.log("before");
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(
`上传文件大小不能超过 ${this.fileSize} MB!`
);
return false;
}
const fileSuffix = file.name.substring(
file.name.lastIndexOf(".") + 1
);
let tempwhiteList = [];
if (this.uploadType == "image") {
tempwhiteList = ["jpg", "jpeg", "png"];
} else if (this.uploadType == "video") {
tempwhiteList = ["avi", "wmv", "mpeg", "mp4", "mov"];
} else if (this.uploadType == "voice") {
tempwhiteList = ["mp3", "wma", "acc"];
}
// console.log(whiteList);
const whiteList = tempwhiteList;
if (whiteList.indexOf(fileSuffix) === -1) {
let errmsg = "";
whiteList.forEach((item, index) => {
errmsg += item + " ";
});
this.$message.error("只能是" + errmsg + "格式");
return false;
}
}
return true;
},
handleUploadSuccess(e, file, fileList) {
if (e.code == 200) {
// console.log(this.uploadType);
if (
this.uploadType == "image" ||
this.uploadType == "video" ||
this.uploadType == "link"
) {
let quill = this.$refs.myQuillEditor.quill;
let length = quill.getSelection().index; // 获取光标所在位置
let vm = this;
let url = e.url;
console.log("url", url);
if (url != null && url.length > 0) {
// 将文件上传后的URL地址插入到编辑器文本中
let value = url;
// API: https://segmentfault.com/q/1010000008951906
// this.$refs.myTextEditor.quillEditor.getSelection();
// 获取光标位置对象,里面有两个属性,一个是index 还有 一个length,这里要用range.index,即当前光标之前的内容长度,然后再利用 insertEmbed(length, 'image', imageUrl),插入图片即可。
vm.addRange =
vm.$refs.myQuillEditor.quill.getSelection();
value =
value.indexOf("http") !== -1
? value
: "http:" + value;
vm.$refs.myQuillEditor.quill.insertEmbed(
vm.addRange !== null ? vm.addRange.index : 0,
vm.uploadType,
value
); // 调用编辑器的 insertEmbed 方法,插入URL
} else {
this.$message.error(`${vm.uploadType}插入失败`);
}
this.$refs["upload"].clearFiles(); // 插入成功后清除input的内容
quill.setSelection(length + 1); //光标位置向后移动一位
} else {
let quill = this.$refs.myQuillEditor.quill;
let length = quill.getSelection().index; // 获取光标所在位置
let BlockEmbed = Quill.import("blots/block/embed");
class AudioBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute("src", e); //设置audio的src属性
node.setAttribute("controls", true); //设置audio的controls,否则他将不会显示
node.setAttribute("controlsList", "nodownload"); //设置audio的下载功能为不能下载
node.setAttribute("id", "voice"); //设置一个id
return node;
}
}
AudioBlot.blotName = "audio";
AudioBlot.tagName = "audio"; //自定义的标签为audio
Quill.register(AudioBlot);
// insertEmbed(index: Number(插入的位置), type: String(标签类型), value: any(参数,将传入到create的方法中去), source: String = 'api')
quill.insertEmbed(length, "audio", e, "api");
quill.setSelection(length + 1); //光标位置向后移动一位
}
} else if (e.code == 500) {
this.$message.error(e.msg);
} else {
this.$message.error(`${this.uploadType}插入失败`);
}
},
imgHandler(state) {
this.addRange = this.$refs.myQuillEditor.quill.getSelection();
if (state) {
let fileInput = document.getElementById("uploadInput");
fileInput.click(); // 加一个触发事件
}
this.uploadType = "image";
},
videoHandler(state) {
this.addRange = this.$refs.myQuillEditor.quill.getSelection();
if (state) {
let fileInput = document.getElementById("uploadInput");
fileInput.click(); // 加一个触发事件
}
this.uploadType = "video";
},
voiceHandler(state) {
this.addRange = this.$refs.myQuillEditor.quill.getSelection();
if (state) {
let fileInput = document.getElementById("uploadInput");
fileInput.click(); // 加一个触发事件
}
this.uploadType = "voice";
},
handleUploadError() {
this.$message.error("插入失败");
},
// 截屏粘贴到富文本框
// pastePic() {
// // 页面加载之后就监听粘贴事件paste
// this.editor.root.addEventListener(
// "paste",
// (evt) => {
// if (
// evt.clipboardData &&
// evt.clipboardData.files &&
// evt.clipboardData.files.length
// ) {
// evt.preventDefault();
// [].forEach.call(evt.clipboardData.files, (file) => {
// if (
// !file.type.match(
// /^image\/(gif|jpe?g|a?png|bmp)/i
// )
// ) {
// return;
// }
// this.handleUpload(file);
// });
// }
// },
// false
// );
// },
},
mounted() {
this.editorOption.initVoiceButton();
this.$refs.myQuillEditor.quill
.getModule("toolbar")
.addHandler("voice", this.voiceHandler);
this.$refs.myQuillEditor.quill
.getModule("toolbar")
.addHandler("image", this.imgHandler);
this.$refs.myQuillEditor.quill
.getModule("toolbar")
.addHandler("video", this.videoHandler);
// this.pastePic();
},
};
script>
<style lang="scss" scoped>
.editor {
position: relative;
.ql-toolbar {
border: 1px solid #cdcfd4;
height: 40px;
background: #f0f2f5;
}
.ql-editor {
min-height: 240px;
padding: 0;
padding-top: 15px;
box-sizing: border-box;
}
.ql-container {
img {
display: block;
}
}
.wordNumber {
position: absolute;
right: 13px;
bottom: 15px;
color: #666666;
font-size: 14px;
}
// .microphone-style{
// color: red;
// background: red;
// }
}
style>
import Editor from '@/components/Editor'
export default {
components:{
Editor
},
}
可用属性可从组件的prop中查看
<editor class="edt-content" v-model="addParams.content"/>