若依RuoYi-Vue分离版—富文本Quill的图片支持伸缩大小及布局、工具栏带中文提示
- 1.在vue.config.js 文件中添加 一下内容
- 2.下载安装插件
- 3.在Editor组件中引入插件
- 4.使用Editor组件(特别注意要的加 v-if )
- 5.bug 之 imageResize的 img的style丢失
- 1.先创建一个image.js的文件
- 2.引入并注册 image.js 到Editor的vue文件中
- 6.配置监听黏贴事件(看个人需求)
1.在vue.config.js 文件中添加 一下内容
const webpack = require('webpack');
new webpack.ProvidePlugin({'window.Quill': 'quill/dist/quill.js','Quill': 'quill/dist/quill.js'}),
2.下载安装插件
// 拖拽上传
npm install quill-image-drop-module --save
// 调整上传图片大小
npm i quill-image-resize-module --save
// 粘贴图片上传
npm install quill-image-extend-module --save
3.在Editor组件中引入插件
修改ruoyi-ui\src\components\Editor
下的 index.vue 文件
<template><div><el-upload:action="uploadUrl":before-upload="handleBeforeUpload":on-success="handleUploadSuccess":on-error="handleUploadError"name="file":show-file-list="false":headers="headers"style="display: none"ref="upload"v-if="this.type == 'url'"></el-upload><div class="editor" ref="editor" :style="styles"></div></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 { getToken } from "@/utils/auth";// 拖拽上传
import {ImageDrop} from 'quill-image-drop-module';
// 调整上传图片大小
import ImageResize from 'quill-image-resize-module';
// 粘贴图片上传
import {ImageExtend} from 'quill-image-extend-module';
// 注册事件~~~~
Quill.register('modules/imageDrop', ImageDrop);
Quill.register('modules/imageResize', ImageResize);
Quill.register('modules/imageExtend', ImageExtend);/*标题*/
const titleConfig = {'ql-bold': '加粗','ql-font': '字体','ql-code': '插入代码','ql-italic': '斜体','ql-link': '添加链接','ql-color': '字体颜色','ql-background': '背景颜色','ql-size': '字体大小','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-video': '视频','ql-clean': '清除字体样式'
}export default {name: "Editor",props: {/* 编辑器的内容 */value: {type: String,default: "",},/* 高度 */height: {type: Number,default: null,},/* 最小高度 */minHeight: {type: Number,default: null,},/* 只读 */readOnly: {type: Boolean,default: false,},/* 上传文件大小限制(MB) */fileSize: {type: Number,default: 5,},/* 类型(base64格式、url格式) */type: {type: String,default: "url",}},data() {return {uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址headers: {Authorization: "Bearer " + getToken()},Quill: null,currentValue: "",options: {theme: "snow",bounds: document.body,debug: "warn",modules: {//图片放大缩小拖拽imageDrop: false,// 拖拽上传(建议关闭它)imageResize: {// 调整图片大小displayStyles: {backgroundColor: 'black',border: 'none',color: 'white'},modules: ['Resize', 'DisplaySize', 'Toolbar']// Resize 允许缩放, DisplaySize 缩放时显示像素 Toolbar 显示工具栏},// 工具栏配置toolbar: [["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: this.readOnly,},};},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: {init() {const editor = this.$refs.editor;this.Quill = new Quill(editor, this.options);// 如果设置了上传地址则自定义图片上传事件if (this.type == 'url') {let toolbar = this.Quill.getModule("toolbar");toolbar.addHandler("image", (value) => {if (value) {this.$refs.upload.$children[0].$refs.input.click();} else {this.quill.format("image", false);}});}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);});},// 上传前校检格式和大小handleBeforeUpload(file) {const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];const isJPG = type.includes(file.type);// 检验文件格式if (!isJPG) {this.$message.error(`图片格式错误!`);return false;}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSize;if (!isLt) {this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);return false;}}return true;},handleUploadSuccess(res, file) {// 如果上传成功if (res.code == 200) {// 获取富文本组件实例let quill = this.Quill;// 获取光标所在位置let length = quill.getSelection().index;// 插入图片 res.url为服务器返回的图片地址quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName);// 调整光标到最后quill.setSelection(length + 1);} else {this.$message.error("图片插入失败");}},handleUploadError() {this.$message.error("图片插入失败");},//鼠标移动加提示addQuillTitle() {const oToolBar = document.querySelector('.ql-toolbar')const aButton = oToolBar.querySelectorAll('button')const aSelect = oToolBar.querySelectorAll('select')aButton.forEach(function (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 {item.title = titleConfig[item.className]}})// 字体颜色和字体背景特殊处理,两个在相同的盒子aSelect.forEach(function (item) {if (item.className.indexOf('ql-background') > -1) {item.previousSibling.title = titleConfig['ql-background']} else if (item.className.indexOf('ql-color') > -1) {item.previousSibling.title = titleConfig['ql-color']} else {item.parentNode.title = titleConfig[item.className]}})},},
};
</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>
4.使用Editor组件(特别注意要的加 v-if )
<template><div class="app-container"><!-- 此处一定要加上这个v-if判断,目的:保证那边只执行一次不然 editor 那边会触发两次(初始化rtfContent一次,rtfContent值改变一次),导致出现quil会出现一些问题。 --><div label="内容" v-if="form.rtfContent!=undefined"><editor v-model="form.rtfContent" :min-height="192"/></div></div>
</template><script>
import {addProfile, delProfile, getProfile, listProfile, updateProfile} from "@/api/yangdong/centerprofile";export default {name: "Profile",data() {return {rtfContent: undefined,},};},created() {this.getList();},mounted() {},methods: {/** 查询中心简介列表 */getList() {this.loading = true;listProfile(this.queryParams).then(response => {this.rtfContent= response.rows.rtfContent;});},}
};
</script><style scoped>
.testParent{/*width: 700px;*//*height: 200px;*/display: flex;flex-direction: row; /*默认也是row可以不写*//*background-color: #428BCA;*/align-items: center; /*这种情况是垂直居中*/justify-content: center;/*这种情况是水平居中*/}/deep/ .el-table th.must>.cell:before {content: '*';color: #ff1818;
}/* 以下为详情遮住样式*/
::v-deep .avue-crud__dialog__header {display: -webkit-box;display: -ms-flexbox;display: flex;-webkit-box-align: center;-ms-flex-align: center;align-items: center;-webkit-box-pack: justify;-ms-flex-pack: justify;justify-content: space-between;
}
/* 以下为缩放按钮样式*/
::v-deep .avue-crud__dialog__menu {padding-right: 20px;float: left;
}
::v-deep .avue-crud__dialog__menu i {color: #909399;font-size: 15px;
}
::v-deep .el-icon-full-screen{cursor: pointer;
}
::v-deep .el-icon-full-screen:before {content: "\e719";
}
</style>
5.bug 之 imageResize的 img的style丢失
原因:
执行this.Quill.pasteHTML(this.currentValue) 的时候,
会把htm的代码渲染在富文本(Quill) ,但是却把在img上面的style被清除掉了,于是就出现了再次编辑时图片位置异常
内部存在某些规则,导致
解决方式:
直接也研究了半天,最后虽然实现了,但是过程很辅助,采取了,DOM 操作(加点击事件,重新渲染html、quil等等),过程极其难受
本人不是纯弄前端,心累的很。。。。。
后面发现了一篇博客,方式简单(使用了 自定义的Quill.js的Blot(块)),果断用了他的。在此非常感谢
博客地址如下:vue使用quill编辑器自定义图片上传方法、quill-image-resize-module修改图片、监测粘贴的是图片上传到后端、重新进入编辑器img标签丢失style属性
1.先创建一个image.js的文件
由于我这边是若依框架里面的。所以的位置为 ruoyi-ui\src\components\Editor
,和index.vue 并级
import Quill from 'quill'
const EmbedBlot = Quill.import('formats/image')
// 添加style,解决重进编辑器ima标签丢掉style属性问题
const ATTRIBUTES = ['alt', 'height', 'width', 'style']class Image extends EmbedBlot {static blotName = 'image';static tagName = 'IMG';static create(value) {const node = super.create(value)if (typeof value === 'string') {// 保留原来的图片方法node.setAttribute('src', this.sanitize(value))} else {// 自定义图片方法node.setAttribute('src', value.src)// 使用了vue-photo-preview所以添加preview,preview-text这两个属性node.setAttribute('preview', '1') // preview相同的为一组node.setAttribute('preview-text', value.title) // 图片名称,预览时显示使用}return node}static formats(domNode) {return ATTRIBUTES.reduce((formats, attribute) => {if (domNode.hasAttribute(attribute)) {formats[attribute] = domNode.getAttribute(attribute)}return formats}, {})}static match(url) {return /\.(jpe?g|gif|png)$/.test(url) || /^data:image\/.+;base64/.test(url)}static register() {if (/Firefox/i.test(navigator.userAgent)) {setTimeout(() => {// Disable image resizing in Firefox// @ts-expect-errordocument.execCommand('enableObjectResizing', false, false)}, 1)}}static sanitize(url) {return sanitize(url, ['http', 'https', 'data']) ? url : '//:0'}static value(domNode) {return domNode.getAttribute('src')}format(name, value) {if (ATTRIBUTES.indexOf(name) > -1) {if (value) {this.domNode.setAttribute(name, value)} else {this.domNode.removeAttribute(name)}} else {super.format(name, value)}}
}
function sanitize(url, protocols) {const anchor = document.createElement('a')anchor.href = urlconst protocol = anchor.href.slice(0, anchor.href.indexOf(':'))return protocols.indexOf(protocol) > -1
}export default Image
2.引入并注册 image.js 到Editor的vue文件中
import Image from "./image";
Quill.register(Image, true);
完整的代码如下
<template><div><el-upload:action="uploadUrl":before-upload="handleBeforeUpload":on-success="handleUploadSuccess":on-error="handleUploadError"name="file":show-file-list="false":headers="headers"style="display: none"ref="upload"v-if="this.type == 'url'"></el-upload><div class="editor" ref="editor" :style="styles"></div></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 { getToken } from "@/utils/auth";// 拖拽上传
import {ImageDrop} from 'quill-image-drop-module';
// 调整上传图片大小
import ImageResize from 'quill-image-resize-module';
// 粘贴图片上传
import {ImageExtend} from 'quill-image-extend-module';
// 注册事件~~~~
Quill.register('modules/imageDrop', ImageDrop);
Quill.register('modules/imageResize', ImageResize);
Quill.register('modules/imageExtend', ImageExtend);import Image from "./image";
Quill.register(Image, true);/*标题*/
const titleConfig = {'ql-bold': '加粗','ql-font': '字体','ql-code': '插入代码','ql-italic': '斜体','ql-link': '添加链接','ql-color': '字体颜色','ql-background': '背景颜色','ql-size': '字体大小','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-video': '视频','ql-clean': '清除字体样式'
}export default {name: "Editor",props: {/* 编辑器的内容 */value: {type: String,default: "",},/* 高度 */height: {type: Number,default: null,},/* 最小高度 */minHeight: {type: Number,default: null,},/* 只读 */readOnly: {type: Boolean,default: false,},/* 上传文件大小限制(MB) */fileSize: {type: Number,default: 5,},/* 类型(base64格式、url格式) */type: {type: String,default: "url",}},data() {return {uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址headers: {Authorization: "Bearer " + getToken()},Quill: null,currentValue: "",options: {theme: "snow",bounds: document.body,debug: "warn",modules: {//图片放大缩小拖拽imageDrop: false,// 拖拽上传(建议关闭它)imageResize: {// 调整图片大小displayStyles: {backgroundColor: 'black',border: 'none',color: 'white'},modules: ['Resize', 'DisplaySize', 'Toolbar']// Resize 允许缩放, DisplaySize 缩放时显示像素 Toolbar 显示工具栏},// 工具栏配置toolbar: [["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: this.readOnly,},};},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: {init() {const editor = this.$refs.editor;this.Quill = new Quill(editor, this.options);// 如果设置了上传地址则自定义图片上传事件if (this.type == 'url') {let toolbar = this.Quill.getModule("toolbar");toolbar.addHandler("image", (value) => {if (value) {this.$refs.upload.$children[0].$refs.input.click();} else {this.quill.format("image", false);}});}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);});},// 上传前校检格式和大小handleBeforeUpload(file) {const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];const isJPG = type.includes(file.type);// 检验文件格式if (!isJPG) {this.$message.error(`图片格式错误!`);return false;}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSize;if (!isLt) {this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);return false;}}return true;},handleUploadSuccess(res, file) {// 如果上传成功if (res.code == 200) {// 获取富文本组件实例let quill = this.Quill;// 获取光标所在位置let length = quill.getSelection().index;// 插入图片 res.url为服务器返回的图片地址quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName);// 调整光标到最后quill.setSelection(length + 1);} else {this.$message.error("图片插入失败");}},handleUploadError() {this.$message.error("图片插入失败");},//鼠标移动加提示addQuillTitle() {const oToolBar = document.querySelector('.ql-toolbar')const aButton = oToolBar.querySelectorAll('button')const aSelect = oToolBar.querySelectorAll('select')aButton.forEach(function (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 {item.title = titleConfig[item.className]}})// 字体颜色和字体背景特殊处理,两个在相同的盒子aSelect.forEach(function (item) {if (item.className.indexOf('ql-background') > -1) {item.previousSibling.title = titleConfig['ql-background']} else if (item.className.indexOf('ql-color') > -1) {item.previousSibling.title = titleConfig['ql-color']} else {item.parentNode.title = titleConfig[item.className]}})},},
};
</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>
6.配置监听黏贴事件(看个人需求)
参考:若依框架前后分离版本富文本增加图片上传及移动和放大缩小
(1)首先得安装jq
npm install jquery --save-dev
(2)引入jq
//引入jq
import $ from 'jquery';
(3)修改以下代码
- 原来
<div class="editor" ref="editor" :style="styles"></div>
- 改为
<div class="editor" ref="editor" :style="styles" @paste="handlePaste($event)"></div>
增加
handlePaste(evt) {let that = thisif (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;}const formData = new FormData();formData.append("file", file);//后台上传接口的参数名// 实现上传$.ajax({type: "post",url: process.env.VUE_APP_BASE_API +'/common/upload', // 上传的图片服务器地址data: formData,headers:{'Authorization': "Bearer " + getToken()},//必须dataType: "json",processData: false,contentType: false,//设置文件上传的type值,必须success: (response) => {if (response.code == 200 || response.code == 0) {//当编辑器中没有输入文本时,这里获取到的 range 为 null// 获取富文本组件实例let quill = that.Quill;var range = quill.selection.savedRange;//图片插入在富文本中的位置var index = 0;if (range == null) {index = 0;} else {index = range.index;}//将图片链接插入到当前的富文本当中quill.insertEmbed(index, "image", response.url);// 调整光标到最后quill.setSelection(index + 1); //光标后移一位}},error: function () {this.$message.error('上传失败!')},});});}},
完整的代码如下
<template><div><el-upload:action="uploadUrl":before-upload="handleBeforeUpload":on-success="handleUploadSuccess":on-error="handleUploadError"name="file":show-file-list="false":headers="headers"style="display: none"ref="upload"v-if="this.type == 'url'"></el-upload><div class="editor" ref="editor" :style="styles" @paste="handlePaste($event)"></div></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 { getToken } from "@/utils/auth";// 拖拽上传
import {ImageDrop} from 'quill-image-drop-module';
// 调整上传图片大小
import ImageResize from 'quill-image-resize-module';
// 粘贴图片上传
import {ImageExtend} from 'quill-image-extend-module';
// 注册事件~~~~
Quill.register('modules/imageDrop', ImageDrop);
Quill.register('modules/imageResize', ImageResize);
Quill.register('modules/imageExtend', ImageExtend);import Image from "./image";
Quill.register(Image, true);//引入jq
import $ from 'jquery';/*标题*/
const titleConfig = {'ql-bold': '加粗','ql-font': '字体','ql-code': '插入代码','ql-italic': '斜体','ql-link': '添加链接','ql-color': '字体颜色','ql-background': '背景颜色','ql-size': '字体大小','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-video': '视频','ql-clean': '清除字体样式'
}export default {name: "Editor",props: {/* 编辑器的内容 */value: {type: String,default: "",},/* 高度 */height: {type: Number,default: null,},/* 最小高度 */minHeight: {type: Number,default: null,},/* 只读 */readOnly: {type: Boolean,default: false,},/* 上传文件大小限制(MB) */fileSize: {type: Number,default: 5,},/* 类型(base64格式、url格式) */type: {type: String,default: "url",}},data() {return {uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址headers: {Authorization: "Bearer " + getToken()},Quill: null,currentValue: "",options: {theme: "snow",bounds: document.body,debug: "warn",modules: {//图片放大缩小拖拽imageDrop: false,// 拖拽上传(建议关闭它)imageResize: {// 调整图片大小displayStyles: {backgroundColor: 'black',border: 'none',color: 'white'},modules: ['Resize', 'DisplaySize', 'Toolbar']// Resize 允许缩放, DisplaySize 缩放时显示像素 Toolbar 显示工具栏},// 工具栏配置toolbar: [["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: this.readOnly,},};},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: {init() {const editor = this.$refs.editor;this.Quill = new Quill(editor, this.options);// 如果设置了上传地址则自定义图片上传事件if (this.type == 'url') {let toolbar = this.Quill.getModule("toolbar");toolbar.addHandler("image", (value) => {if (value) {this.$refs.upload.$children[0].$refs.input.click();} else {this.quill.format("image", false);}});}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);});},// 上传前校检格式和大小handleBeforeUpload(file) {const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];const isJPG = type.includes(file.type);// 检验文件格式if (!isJPG) {this.$message.error(`图片格式错误!`);return false;}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSize;if (!isLt) {this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);return false;}}return true;},handleUploadSuccess(res, file) {// 如果上传成功if (res.code == 200) {// 获取富文本组件实例let quill = this.Quill;// 获取光标所在位置let length = quill.getSelection().index;// 插入图片 res.url为服务器返回的图片地址quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName);// 调整光标到最后quill.setSelection(length + 1);} else {this.$message.error("图片插入失败");}},handleUploadError() {this.$message.error("图片插入失败");},//鼠标移动加提示addQuillTitle() {const oToolBar = document.querySelector('.ql-toolbar')const aButton = oToolBar.querySelectorAll('button')const aSelect = oToolBar.querySelectorAll('select')aButton.forEach(function (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 {item.title = titleConfig[item.className]}})// 字体颜色和字体背景特殊处理,两个在相同的盒子aSelect.forEach(function (item) {if (item.className.indexOf('ql-background') > -1) {item.previousSibling.title = titleConfig['ql-background']} else if (item.className.indexOf('ql-color') > -1) {item.previousSibling.title = titleConfig['ql-color']} else {item.parentNode.title = titleConfig[item.className]}})},handlePaste(evt) {let that = thisif (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;}const formData = new FormData();formData.append("file", file);//后台上传接口的参数名// 实现上传$.ajax({type: "post",url: process.env.VUE_APP_BASE_API +'/common/upload', // 上传的图片服务器地址data: formData,headers:{'Authorization': "Bearer " + getToken()},//必须dataType: "json",processData: false,contentType: false,//设置文件上传的type值,必须success: (response) => {if (response.code == 200 || response.code == 0) {//当编辑器中没有输入文本时,这里获取到的 range 为 null// 获取富文本组件实例let quill = that.Quill;var range = quill.selection.savedRange;//图片插入在富文本中的位置var index = 0;if (range == null) {index = 0;} else {index = range.index;}//将图片链接插入到当前的富文本当中quill.insertEmbed(index, "image", response.url);// 调整光标到最后quill.setSelection(index + 1); //光标后移一位}},error: function () {this.$message.error('上传失败!')},});});}},},
};
</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>