若依Ruoyi富文本编辑器Quill粘贴图片改成文件上传(不使用base64)
今天做数据库的转移工作,发现新闻模块的表数据特别大!
看了一下数据库才发现使用Quill默认的粘贴截图传进来的图片是base64格式,如下↓
粗略算一下这一长串有:78304个字符!!!!
正文:
我的做法是(在原代码中添加如下三个方法):
1、捕获粘贴这个行为
handlePaste(e) {const clipboardData = e.clipboardData || window.clipboardData;if (!clipboardData) return;const items = clipboardData.items;if (!items) return;let found = false;for (let i = 0; i < items.length; i++) {const item = items[i];// 1. 粘贴的图片文件(截图)if (item.kind === "file" && item.type.startsWith("image/")) {const file = item.getAsFile();e.preventDefault();this.uploadImage(file);found = true;}// 2. 粘贴的 HTML 里包含 base64 图片if (item.type === "text/html") {item.getAsString((html) => {const match = html.match(/<img[^>]+src=["']data:image\/(png|jpeg);base64,([^"']+)["']/);if (match) {const mimeType = `image/${match[1]}`;const base64Data = match[2];const file = this.base64ToFile(base64Data, `pasted.${match[1]}`, mimeType);e.preventDefault();this.uploadImage(file);}});found = true;}}},
2、判断粘贴的值,如果是base64格式图片则将图片转换成file格式 并进行上传。
base64ToFile(base64Data, filename, mimeType) {const byteString = atob(base64Data);const arrayBuffer = new ArrayBuffer(byteString.length);const intArray = new Uint8Array(arrayBuffer);for (let i = 0; i < byteString.length; i++) {intArray[i] = byteString.charCodeAt(i);}return new File([intArray], filename, { type: mimeType });},
3、封装的上传接口
// 上传图片文件,调用上传接口uploadImage(file) {const formData = new FormData();formData.append('file', file);import('axios').then(({default: axios}) => {axios.post(this.uploadUrl, formData, {headers: {'Content-Type': 'multipart/form-data',Authorization: this.headers.Authorization}}).then(res => {if (res.data.code === 200) {// 上传成功,插入图片const quill = this.Quill;const length = quill.getSelection()?.index || quill.getLength();quill.insertEmbed(length, 'image', process.env.VUE_APP_BASE_API + res.data.fileName);quill.setSelection(length + 1);} else {this.$message.error('图片上传失败');}}).catch(() => {this.$message.error('图片上传失败');});});},
mounted方法:
mounted() {this.init();this.$refs.editor.addEventListener("paste", this.handlePaste);},
beforeDestroy方法:
beforeDestroy() {this.Quill = null;this.$refs.editor.removeEventListener("paste", this.handlePaste);},
执行粘贴结果:
完整代码
<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";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: {// 工具栏配置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();this.$refs.editor.addEventListener("paste", this.handlePaste);},beforeDestroy() {this.Quill = null;this.$refs.editor.removeEventListener("paste", this.handlePaste);},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("图片插入失败");},handlePaste(e) {const clipboardData = e.clipboardData || window.clipboardData;if (!clipboardData) return;const items = clipboardData.items;if (!items) return;let found = false;for (let i = 0; i < items.length; i++) {const item = items[i];// 1. 粘贴的图片文件(截图)if (item.kind === "file" && item.type.startsWith("image/")) {const file = item.getAsFile();e.preventDefault();this.uploadImage(file);found = true;}// 2. 粘贴的 HTML 里包含 base64 图片if (item.type === "text/html") {item.getAsString((html) => {const match = html.match(/<img[^>]+src=["']data:image\/(png|jpeg);base64,([^"']+)["']/);if (match) {const mimeType = `image/${match[1]}`;const base64Data = match[2];const file = this.base64ToFile(base64Data, `pasted.${match[1]}`, mimeType);e.preventDefault();this.uploadImage(file);}});found = true;}}},// base64转Filebase64ToFile(base64Data, filename, mimeType) {const byteString = atob(base64Data);const arrayBuffer = new ArrayBuffer(byteString.length);const intArray = new Uint8Array(arrayBuffer);for (let i = 0; i < byteString.length; i++) {intArray[i] = byteString.charCodeAt(i);}return new File([intArray], filename, { type: mimeType });},// 上传图片文件,调用上传接口uploadImage(file) {const formData = new FormData();formData.append('file', file);import('axios').then(({default: axios}) => {axios.post(this.uploadUrl, formData, {headers: {'Content-Type': 'multipart/form-data',Authorization: this.headers.Authorization}}).then(res => {if (res.data.code === 200) {// 上传成功,插入图片const quill = this.Quill;const length = quill.getSelection()?.index || quill.getLength();quill.insertEmbed(length, 'image', process.env.VUE_APP_BASE_API + res.data.fileName);quill.setSelection(length + 1);} else {this.$message.error('图片上传失败');}}).catch(() => {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>