若依RuoYi-Vue分离版—富文本Quill的图片支持伸缩大小及布局

若依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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/354166.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第九届信也科技杯全球AI算法大赛——语音深度鉴伪识别参赛A榜 0.968961分

遗憾没有进复赛&#xff0c;只是第41名。先贴个A榜的成绩。A榜的前三十名晋级&#xff0c;个个都是99分的大佬&#xff0c;但是B榜的成绩就有点低了&#xff0c;应该是数据不同源的问题&#xff0c;第一名0.78分。官网链接&#xff1a;语音深度鉴伪识别 官方baselin:https://g…

企业设备管理现状与解决方案

在当今企业运营中&#xff0c;设备管理作为保障生产稳定、提升效率的重要环节&#xff0c;其复杂性和挑战性日益凸显。无论是生产车间、石油化工、物业小区&#xff0c;还是消防器材、建筑施工等领域&#xff0c;都面临着设备故障频发、维修流程繁琐等共性问题。 为了帮助企业…

一个简单、快速用于训练和微调中等规模GPT模型的开源项目

大家好&#xff0c;今天给大家分享的是一个简单、快速用于训练和微调中等规模GPT模型的开源项目&#xff0c;该项目旨在拓宽深度学习领域的应用&#xff0c;特别是为深度学习的入门者提供便利。 Nano GPT是一个基于PyTorch的开源项目&#xff0c;由前特斯拉AI负责人Andrej Ka…

干货丨好用的上网行为管理软件有哪些?全网行为管理系统详解

在当今数字化办公环境中&#xff0c;员工的上网行为直接关系到企业信息安全、工作效率以及合规性。 为了有效管理网络资源&#xff0c;确保工作环境的高效与安全&#xff0c;一套强大的上网行为管理软件变得尤为重要。 本文将为您详细介绍几款市场上口碑良好的上网行为管理软件…

【mysql】排错和调优

通用的一些排错方法。 1、查看进程信息 mysql> show full processlist;mysql> show processlist; 2、information_schema information_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名&#xff0c;数据库的表&#xff0c;表栏的数据类型与访问权限等。…

企业数据资产入表操作指引

在数字经济时代&#xff0c;数据已成为企业的关键生产要素&#xff0c;其管理和应用至关重要。数据资产入表操作涉及复杂的评估和管理过程&#xff0c;企业需要遵循合规性、真实性和透明性的原则。本指南旨在为企业提供系统化的操作指导&#xff0c;帮助实现数据资产的高效管理…

数据分析必备:一步步教你如何用matplotlib做数据可视化(6)

1、Matplotlib 网格 axes对象的grid()函数将图中网格的可见性设置为on或off。还可以显示网格的主要/次要(或两者)刻度。另外&#xff0c;可以在grid()函数中设置color&#xff0c;linestyle和linewidth属性。 参考以下示例代码 import matplotlib.pyplot as plt import numpy…

【DAMA】掌握数据管理核心:CDGA考试指南

引言&#xff1a;        在当今快速发展的数字化世界中&#xff0c;数据已成为组织最宝贵的资产之一。有效的数据管理不仅能够驱动业务决策&#xff0c;还能提升竞争力和市场适应性。DAMA国际一直致力于数据管理和数字化的研究、实践及相关知识体系的建设。秉承公益、志愿…

【吊打面试官系列-Mysql面试题】SQL 语言包括哪几部分?每部分都有哪些操作关键字?

大家好&#xff0c;我是锋哥。今天分享关于 【SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f; SQL 语言包括数据定义(DDL)、…

终极版本的Typora上传到博客园和csdn

激活插件 下载网址是这个&#xff1a; https://codeload.github.com/obgnail/typora_plugin/zip/refs/tags/1.9.4 解压之后这样的&#xff1a; 解压之后将plugin&#xff0c;复制到自己的安装目录下的resources 点击安装即可&#xff1a; 更改配置文件 "dependencies&q…

Android采用Scroller实现底部二楼效果

需求 在移动应用开发中&#xff0c;有时我们希望实现一种特殊的布局效果&#xff0c;即“底部二楼”效果。这个效果类似于在列表底部拖动时出现额外的内容区域&#xff0c;用户可以继续向上拖动查看更多内容。这种效果可以用于展示广告、推荐内容或其他信息。 效果 实现后的…

论文阅读:基于谱分析的全新早停策略

来自JMLR的一篇论文&#xff0c;https://www.jmlr.org/papers/volume24/21-1441/21-1441.pdf 这篇文章试图通过分析模型权重矩阵的频谱来解释模型&#xff0c;并在此基础上提出了一种用于早停的频谱标准。 1&#xff0c;分类难度对权重矩阵谱的影响 1.1 相关研究 在最近针对…

Matlab|基于V图的配电网电动汽车充电站选址定容-可视化

1主要内容 基于粒子群算法的电动汽车充电站和光伏最优选址和定容 关键词&#xff1a;选址定容 电动汽车 充电站位置 仿真平台&#xff1a;MATLAB 主要内容&#xff1a;代码主要做的是一个电动汽车充电站和分布式光伏的选址定容问题&#xff0c;提出了能够计及地理因素和服…

XL5300 dTOF测距模块 加镜头后可达7.6米测距距离 ±4%测距精度

XL5300 直接飞行时间&#xff08;dToF&#xff09;传感器是一个整体方案dTOF 模组&#xff0c;应用设计简单。片内集成了单光子雪崩二极管&#xff08;SPAD&#xff09;接收阵列以及VCSEL激光发射器。利用自主研发的 SPAD 和独特的ToF 采集与处理技术&#xff0c;XL5300模块可实…

Linux相关

⽹络的性能指标 Linux ⽹络协议栈是根据 TCP/IP 模型来实现的&#xff0c; TCP/IP 模型由应⽤层、传输层、⽹络层和⽹络接⼝层&#xff0c;共四层组成&#xff0c;每⼀层都有各⾃的职责。 基本指标&#xff1a; 带宽&#xff0c;表示链路的最⼤传输速率&#xff0c;单位是 b…

手持风扇哪个品牌好?五大手持风扇品牌推荐!

随着炎热夏季的到来&#xff0c;手持风扇已成为人们出行的必备清凉神器。然而&#xff0c;面对市场上众多品牌的手持风扇&#xff0c;如何选择一款既时尚又高效的产品成为了许多消费者的难题。为了解决这个困扰&#xff0c;我们精心挑选了五大手持风扇品牌进行推荐。这些品牌不…

[Linux] vi编辑器

命令模式&文本模式 命令模式就输入命令然后执行&#xff0c;文本模式就是系统把你的输入都当成写进文件里的字符 切换模式&#xff1a; 刚进入默认是命令模式&#xff0c;按: i I a A o O 进入文本模式&#xff0c; 通过他们进入文本模式有什么不同&#xff1f; 然后按esc进…

STM32学习记录(八)————定时器输出PWM及舵机的控制

文章目录 前言一、PWM1.工作原理2.内部运作机制3. PWM工作模式4.PWM结构体及库函数 二、PWM控制舵机 前言 一个学习STM32的小白~ 有错误评论区或私信指出提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、PWM 1.工作原理 以向上计数为例&#xff0…

创建一个electron桌面备忘录

Sound Of Silence 1.创建electron项目命令&#xff1a; npm create quick-start/electron my-new-project 2选择&#xff1a;√ Select a framework: vue √ Add TypeScript? ... No √ Add Electron updater plugin? ... Yes √ Enable Electron download mirror proxy? .…

11.6.k8s实战-节点扩缩容

目录 一&#xff0c;需求描述 二、集群缩容-节点下线 1&#xff0c;节点下线案例说明 2&#xff0c;查看现有节点 3&#xff0c;查看所有名称空间下的pod ​编辑4&#xff0c;驱逐下线节点的pod 5&#xff0c;驱逐后再次查看pod 6&#xff0c;驱逐pod后再次查看节点信息…