Editor组件代码
<template>
<div class="box">
<el-upload
style="display: none"
class="avatar"
:action="uploadPath"
:headers="headers"
:show-file-list="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
accept=".jpg,.jpeg,.png,.gif"
>
</el-upload>
<el-upload
:show-file-list="false"
:on-success="handleFileSuccess"
:before-upoad="handleFileBeforeUpload"
type="drag"
:headers="headers"
:action="uploadPath"
class="uploadFile"
></el-upload>
<quill-editor
class="ql-editor"
v-model="content"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)"
>
</quill-editor>
</div>
</template>
<script>
import { Quill } from "vue-quill-editor";
import "../../../node_modules/quill/dist/quill.snow.css";
import { getToken } from "@/utils/auth";//请求头
import { getStore } from "@/utils/storage";//请求头
import { addQuillTitle } from "@/utils/quill/quill-title.js";
import { lineHeightStyle } from "@/utils/quill/lineHeight";
import ImageResize from "quill-image-resize-module"; // 引用,调整图片大小
Quill.register("modules/imageResize", ImageResize);
// 自定义插入a链接
var Link = Quill.import("formats/link");
class FileBlot extends Link {
// 继承Link Blot
static create(value) {
let node = undefined;
if (value && !value.href) {
// 适应原本的Link Blot
node = super.create(value);
} else {
// 自定义Link Blot
node = super.create(value.href);
node.setAttribute("download", value.innerText); // 左键点击即下载
node.innerText = value.innerText;
node.download = value.innerText;
}
return node;
}
}
FileBlot.blotName = "link";
FileBlot.tagName = "A";
Quill.register(FileBlot);
let fonts = [
"MicrosoftYaHei",
"SimSun",
"SimHei",
"KaiTi",
"FangSong",
"Arial",
"TimesNewRoman",
"sansSerif",
];
Quill.imports["formats/font"].whitelist = fonts;
Quill.register(Quill.imports["formats/font"]);
let fontSizeStyle = Quill.import("attributors/style/size");
fontSizeStyle.whitelist = ["16px", "20px", "24px", "30px", "36px", "42px"];
Quill.register(fontSizeStyle, true);
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 切换按钮
[{ header: 1 }, { header: 2 }], //自定义按钮的值
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }], //缩进
[{ direction: "rtl" }], // 文本方向
[{ size: fontSizeStyle.whitelist }], //自定义字号
[{ lineheight: ["initial", "1", "1.5", "1.75", "2", "3", "4", "5"] }], // 行高
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // 下拉菜单,带有主题的默认值
[{ font: fonts }],
[{ align: [] }],
["link", "image", "upload"],
["clean"],
];
Quill.register({ "formats/lineHeight": lineHeightStyle }, true);
export default {
name: "Editor",
components: { Quill },
props: {
/* 编辑器的内容 */
value: {
type: String,
},
/* 图片大小 */
maxSize: {
type: Number,
default: 4000, //kb
},
},
data() {
return {
content: this.value,
uploadPath: "",
editorOption: {
placeholder: "",
theme: "snow", // bubble/snow
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
image: function (value) {
if (value) {
// 触发input框选择图片文件
document.querySelector(".avatar input").click();
} else {
this.quill.format("image", false);
}
},
upload: (value) => {
if (value) {
document.querySelector(".uploadFile input").click();
}
},
lineheight: function (value) {
if (value) {
this.quill.format("lineHeight", value);
} else {
console.log(value);
}
},
},
},
imageResize: {
displayStyles: {
backgroundColor: "black",
border: "none",
color: "white",
},
modules: ["Resize", "DisplaySize", "Toolbar"],
},
},
},
uploadPath: process.env.VUE_APP_BASE_API + "/common/upload/file", // 上传的图片服务器地址
headers: {
// 请求头
Authorization: getToken() ? getToken() : getStore("h5Token"),
},
};
},
watch: {
value: function () {
this.content = this.value;
},
},
mounted() {
addQuillTitle();
},
methods: {
onEditorBlur() {
//失去焦点事件
},
onEditorFocus() {},
handleAvatarSuccess(res, file) {
this.form.thumbnail = res.data;
this.thumbnail = res.data;
},
uploadSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill;
// 如果上传成功
if (res.status == 200) {
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片 res.data为服务器返回的图片地址
quill.insertEmbed(length, "image", res.data.full);
// 调整光标到最后
quill.setSelection(length + 1);
} else {
this.$message.error("图片插入失败");
}
// loading动画消失
this.quillUpdateImg = false;
},
handleFileSuccess(res, file) {
let fileNameLength = file.name.length;
let quill = this.$refs.myQuillEditor.quill;
let length = quill.getSelection().index;
quill.insertEmbed(
length,
"link",
{ href: res.data.full, innerText: file.name },
"api"
);
quill.setSelection(length + fileNameLength);
},
// 富文本图片上传失败
uploadError() {
// loading动画消失
this.quillUpdateImg = false;
this.$message.error("图片插入失败");
},
// 富文本图片上传前
beforeUpload() {
// 显示loading动画
this.quillUpdateImg = true;
},
handleFileBeforeUpload() {
// 显示loading动画
this.quillUploadFile = true;
},
onEditorChange({ editor, html, text }) {
//内容改变事件
this.$emit("input", html);
},
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 5;
if (!isLt2M) {
this.$message.error("上传图片大小不能超过 5MB!");
}
return isLt2M;
},
},
};
</script>
<style >
@import "../../assets/styles/font.css";
.box {
margin-top: -20px;
}
.uploadFile {
height: 0;
}
.ql-editor {
width: 100%;
}
.ql-editor img {
max-width: 100%;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
/* .avatar {
width: 178px;
height: 178px;
display: block;
} */
.ql-container {
overflow-y: auto;
height: 20rem !important;
}
.ql-container ::-webkit-scrollbar {
width: 10px; /*竖向滚动条的宽度*/
height: 10px; /*横向滚动条的高度*/
}
.ql-container ::-webkit-scrollbar-thumb {
/*滚动条里面的小方块*/
background: #666666;
border-radius: 5px;
}
.ql-container ::-webkit-scrollbar-track {
/*滚动条轨道的样式*/
background: #ccc;
border-radius: 5px;
}
.editor {
line-height: normal !important;
height: 300px;
}
.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: "等宽字体";
}
.ql-snow.ql-toolbar .ql-upload {
background: url("../../assets/image/upload.png");
background-size: 16px 16px;
background-position: center center;
background-repeat: no-repeat;
/* background: red; */
}
</style>
组件中引入的各个文件代码
1、import { addQuillTitle } from “@/utils/quill/quill-title.js”;
2、import { lineHeightStyle } from “@/utils/quill/lineHeight”;
3、 @import “…/…/assets/styles/font.css”;
1、quill-title.js
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '字体颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-lineheight': '行高',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-upload': '附件',
'ql-video': '视频',
'ql-clean': '清除字体样式'
};
export function addQuillTitle () {
const oToolBar = document.querySelector('.ql-toolbar'),
aButton = oToolBar.querySelectorAll('button'),
aSelect = oToolBar.querySelectorAll('select'),
aSpan = oToolBar.querySelectorAll('span');
aButton.forEach((item) => {
if (item.className === 'ql-script') {
item.value === 'sub' ? item.title = '下标' : item.title = '上标';
} else if (item.className === 'ql-indent') {
item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进';
} else if (item.className === 'ql-list') {
item.value === 'ordered' ? item.title = '有序列表' : item.title = '无序列表'
} else if (item.className === 'ql-header') {
item.value === '1' ? item.title = '标题H1' : item.title = '标题H2';
} else {
item.title = titleConfig[item.classList[0]];
}
});
aSelect.forEach((item) => {
if (item.className != 'ql-color' && item.className != 'ql-background') {
item.parentNode.title = titleConfig[item.classList[0]];
}
});
aSpan.forEach((item) => {
if (item.classList[0] === 'ql-color') {
item.title = titleConfig[item.classList[0]];
} else if (item.classList[0] === 'ql-background') {
item.title = titleConfig[item.classList[0]];
}
});
}
2、lineHeight
import Quill from "quill";
let Parchment = Quill.import("parchment");
// console.log('Parchment', Parchment);
class lineHeightAttributor extends Parchment.Attributor.Style { }
const lineHeightStyle = new lineHeightAttributor("lineHeight", "line-height", {
scope: Parchment.Scope.INLINE,
whitelist: ["initial", "1", "1.5", "1.75", "2", "3", "4","5"]
});
export { lineHeightStyle };
3.font.css
.ql-editor{
font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before {
content: "宋体";
font-family: "SimSun";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
content: "黑体";
font-family: "SimHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=MicrosoftYaHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=MicrosoftYaHei]::before {
content: "微软雅黑";
font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
content: "楷体";
font-family: "KaiTi";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
content: "仿宋";
font-family: "FangSong";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
content: "Arial";
font-family: "Arial";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=TimesNewRoman]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=TimesNewRoman]::before {
content: "Times New Roman";
font-family: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sansSerif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sansSerif]::before {
content: "sans-serif";
font-family: "sans-serif";
}
.ql-font-SimSun {
font-family: "SimSun";
}
.ql-font-SimHei {
font-family: "SimHei";
}
.ql-font-MicrosoftYaHei {
font-family: "Microsoft YaHei";
}
.ql-font-KaiTi {
font-family: "KaiTi";
}
.ql-font-FangSong {
font-family: "FangSong";
}
.ql-font-Arial {
font-family: "Arial";
}
.ql-font-TimesNewRoman {
font-family: "Times New Roman";
}
.ql-font-sansSerif {
font-family: "sans-serif";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {
content: '16px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {
content: '20px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='24px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='24px']::before {
content: '24px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='30px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='30px']::before {
content: '30px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='36px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='36px']::before {
content: '36px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='42x']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='42px']::before {
content: '42px';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-label::before {
content: '行高';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='initial']::before {
content: '默认';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1']::before {
content: '1';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.5']::before {
content: '1.5';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.75']::before {
content: '1.75';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='2']::before {
content: '2';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='3']::before {
content: '3';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='4']::before {
content: '4';
}
.ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='5']::before {
content: '5';
}
.ql-snow .ql-picker.ql-lineheight {
width: 70px;
}
全局引用Editor组件
// Quill富文本引入
import VueQuillEditor from "vue-quill-editor";
Vue.use(VueQuillEditor);
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
// 封装的富文本组件
import quill from "@/components/Editor/index.vue";
Vue.component("quill", quill);
使用
在组件中使用
<template>
<quill @input="input" :value="form.content"></quill>
</template>
<script>
export default {
methods: {
// 富文本
input(content) {
this.form.content = content;
},
}
}
</script>