quill富文本编辑器处理复制图像的解决方法
作为前端开发人员,一提出富文本编辑器,我们就能马上想起quill-eidtor,它功能齐全,操作方便,上手容易等优点。大部分的功能我们是可以实现无需修改即可使用的,但是,有些功能我们又不得不进行再次开发,不然上被领导质疑能力,下被新入行的小弟看不起,左被客户不信任,右被同事小瞧…废话不多说,直接说场景吧,项目使用技术为vue3+element-plus+axios。
那是一个风和日丽的上午,客户发来一张图片,上面一串提示"请求超时,请重新再试",于时后台的同事们开始检查代码,均说无问题。虽然相信自己的代码怎么可能出问题,还是查了一下,呃。。。图片上传的是base64,导致上传时间过长,超时了,没办法,赶紧修复:
1. 修改图片上传方式为先上传,再将上传成功后的src插入到目标位置
在初始化富文本对象时添加对图片按钮的监听,并开发上传功能
<template>
<!-- 此处省略其他代码... -->
<div class="editor">
<quill-editor
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
<input ref="imageInput" type="file" id="imgFrom" @change="imageFormChange" style="position: absolute;left: -1000px;" />
</div>
</template>
<script setup>
const quillEditorRef = ref(null);
const imageInput = ref(null);
function initQuill() {
let quill = quillEditorRef.value.getQuill();
let tools = quill.getModule('toolbar');
// 监听点击图片按钮的操作
tools.addHandler('image', val => {
if (val) {
imageInput.value.click();
} else {
quill.format("image", false);
}
})
}
// 获取文件对象
function imageFormChange(e) {
let file = e.target.files[0];
uploadImg(file).then(res => {
inserDom(res);
})
}
// 向光标位置插入dom
function inserDom(data) {
let quill = quillEditorRef.value.getQuill();
// 获取光标位置
let pos = quill.selection.savedRange.index;
// 将上传成功后的图片路径组装成img标签,插入到光标后
quill.insertEmbed(pos, 'image', data.src);
// 光标后移
quill.setSelection(pos + 1)
}
// 上传接口
function uploadIMg(file) {
let fm = new FormData();
fm.append('file', file);
return new Promise((resolve, reject) => {
axios({
url: 'xxxxxxxxxx',
method: 'post',
data: fm,
headers: {
'Authorization': '' // token
}
}).then(res => {
let data = res.data;
if (data.code == 200) {
resolve(data)
} else {
reject(data.msg)
}
}).catch(e => {
reject('图片上传失败')
})
})
}
</script>
通过上面的方式,已经实现了上传图片时将图片从base64的形式转换为通过上传文件并将文件上传后的路径保存到文本中的功能,测试没问题后,项目部署,然后通知客户已经可以了,让他们再用用。不料片刻之后,收到客户的微信说,还是不行,我就纳闷了,怎么可能,我测试没问题,同事也试了试没问题,怎么就客户那儿有问题呢,于是就问他是如何操作的,用户说是他复制粘贴的内容。好吧,我马上截了个图试了试,果真是base64的。emmm…好吧,客户就是金主,甲方就是爸爸嘛,总不能教他做事不是?
2. 监听粘贴事件
同样是在初始化编辑器时,监听window的paste事件,将复制来的图片进行文件上传并将上传后得到的路径插入到相应的位置,这里只需要改写initQuill
函数即可。
function initQuill() {
let quill = quillEditorRef.value.getQuill();
let tools = quill.getModule('toolbar');
// 监听点击图片按钮的操作
tools.addHandler('image', val => {
if (val) {
imageInput.value.click();
} else {
quill.format("image", false);
}
})
// 监听到粘贴事件
window.addEventListener('paste', e => {
if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length) {
e.preventDefault();
uploadClipBoardFile(e.clipboardData);
}
})
}
// 将复制到的图片上传到服务器并显示
function uploadClipBoardFile(data) {
[].forEach.call(data.files, (file => {
uploadImg(file).then(res => {
inserDom(res);
})
}))
}
经过上面的修改,已经成功的实现了复制一张图片到编辑器并实现将该图片上传的功能,经过测试等操作后,又一次的通知客户,这下没问题了,心道这下应该是没什么妖蛾子了吧,然后,客户就又又的给我说不行,我心想又是什么情况,于是就问了客户,答曰:从文档中直接复制的全文。打印一看,确实,如果复制的是一张图片,clipboardData.files是有这张图片的数据的,但是,如果复制的是从文档中来的有图片和文字的,在上面的files中无数据的,于是,这下连图片都不显示了,这下好了,本来想修修脚指甲,结果给人截肢了,于是就有了第三次修改。
3. 处理从文本中粘贴来的图片
function initQuill() {
let quill = quillEditorRef.value.getQuill();
let tools = quill.getModule('toolbar');
// 监听点击图片按钮的操作
tools.addHandler('image', val => {
if (val) {
imageInput.value.click();
} else {
quill.format("image", false);
}
})
// 监听到粘贴事件
window.addEventListener('paste', e => {
if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length) {
e.preventDefault();
uploadClipBoardFile(e.clipboardData);
} else {
renderQuillContent(e.clipboardData.getData('text/rtf'));
}
})
}
// 从rtf中提取图片数据
function extractImageDataFromRtf(data) {
if(!data) return;
// 下面的正则是为了从rtf中提取到图片信息,方法固定,我也是从网上搜来的...
const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g');
const images = data.match(regexPicture);
const result = [];
if (images) {
for (const image of images) {
let mimeType = '';
if (image.includes('\\pngblip')) {
mimeType = 'image/png';
} else if (image.includes('\\jpegblip')) {
mimeType = 'image/jpeg';
}
if (mimeType) {
result.push({
hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
type: mimeType
});
}
}
}
return result;
}
// 将hex数据转换面base64的数据
function hex2base64(hex) {
return btoa(hex.match(/\w{2}/g).match(char => {
return String.fromCharCode(parseInt(char, 16))
}).join(''));
}
// 生成n位的uuid
function uuid(n) {
let result = '';
let str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
while(n-- >= 0) {
result += str.charAt(~~(Math.random() * str.length));
}
return result;
}
// 将base64的数据封装成file对象
function base64_file(basestr, mimeType) {
let filename = uuid(8) + Date().now();
let charcters = atob(basestr);
let baseArr = new Array(charcters.length);
for(let i = 0;i < charcters.length;i++) {
baseArr[i] = charcters.charCodeAt(i);
}
let u8a = new Uint8Array(baseArr);
return new File([u8a], filename, {type: mimeType})
}
function renderQuillContent(data) {
let quill = quillEditorRef.value.getQuill();
let quillDoms = quill.root;
let hexList = extractImageDataFromRtf(data);
hexList.forEach(ele => {
let file = base64_file(hex2base64(ele.hex), ele.mimeType);
uploadImg(file).then(res => {
setTimeout(() => {
let imgDoms = quillDom.querySelectorAll('img[src*="//:0"]');
imgDoms.forEach((item, index) => {
item.src = `${list[index]}`;
})
}, 200)
})
})
}
经由上面的操作,可以实现我们从word文档中直接复制写好的内容到富文本中了,又给过测试,又告知客户这下又行了,这下终于没问题了,于是我静下心来,想着,这些功能其实是可以写一个工具类的,于是,但是没有了,后面的内容只有等有空的时候再来提取了,不过,这一次的开发经历和思路是可以先记录下来的,相信后来用得到富文本编辑器(quill)的地方,就必定会用得到的。