背景
公司最近升级项目,要求富文本支持含多图片的word的内容复制粘贴,没办法,为了用户体验只能去找解决方案,通过各种查资料,整理出来亲测有效的方案。
方案:1、监听粘贴事件,通过e.clipboardData 获取剪贴板里的信息
2、获取图片的本地路径,同时通过rtf内容中查找图片数据
3、把图片转为base64(没有做上传到服务器,后期有需要再加上)
实现步骤(代码都是在ueditor.all.js文件里修改)
1、定义变量并且找到ueditor初始化事件的代码(可以直接搜索“_initEvents”方法)
var wordImg = [];
_initEvents: function () {
var me = this,
doc = me.document,
win = me.window;
//*****************新增内容**********************************
//监听word复制粘贴功能
doc.addEventListener("paste", function (e) {
if (!(e.clipboardData && e.clipboardData.items)) {
return;
}
/***
* 粘贴复制的图片,或是粘贴截图 *
*/
const items = (e.clipboardData || window.clipboardData).items;
let file = null;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
file = items[i].getAsFile();
break;
}
}
me.fileToBase64(file,me)
/**
* 粘贴word文档
* */
const clipboardData = e.clipboardData;
//粘贴板中的HTML文本
let copyStr = clipboardData.getData('text/html');
//粘贴板中的RTF数据
let rtf = clipboardData.getData('text/rtf');
//获取粘贴板中图片的数量
let imgs = me.findAllImageElementsWithLocalSource(copyStr);
me.replaceImagesFileSourceWithInlineRepresentation(imgs, me.extractImageDataFromRtf(rtf))
})
//*****************新增内容**********************************
me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);
domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);
domUtils.on(me.body, 'drop', function (e) {
//阻止ff下默认的弹出新页面打开图片
if (browser.gecko && e.stopPropagation) { e.stopPropagation(); }
me.fireEvent('contentchange')
});
domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {
//特殊键不触发selectionchange
if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
return;
}
if (evt.button == 2) return;
me._selectionChange(250, evt);
});
},
2、把方法复制进去
//获取粘贴板中图片数量
findAllImageElementsWithLocalSource: function (htmlData) {
let imgReg = /<img.*?(?:>|\/>)/gi; //匹配图片中的img标签
let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src
let arr = htmlData.match(imgReg); //筛选出所有的img
if (!arr || (Array.isArray(arr) && !arr.length)) {
return false;
}
let srcArr = [];
for (let i = 0; i < arr.length; i++) {
let src = arr[i].match(srcReg);
// 获取图片地址
srcArr.push(src[1]);
}
return srcArr;
},
//处理图片信息--从rtf内容中查找图片数据
extractImageDataFromRtf: function (rtfData, ignoreHeadersFooters = true) {
if (!rtfData) {
return [];
}
const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g');
const images = rtfData.match(regexPicture);
const result = [];
if (images) {
for (const image of images) {
let imageType = false;
if (image.includes('\\pngblip')) {
imageType = 'image/png';
} else if (image.includes('\\jpegblip')) {
imageType = 'image/jpeg';
}
if (imageType) {
//是否跳过页眉页脚
if (ignoreHeadersFooters) {
const headerFooterRegex = /{\\header[\s\S]+?}\\par|{\\footer[\s\S]+?}\\par/g;
if (headerFooterRegex.test(image)) {
continue;
}
}
result.push({
hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
type: imageType
});
}
}
}
console.log(result)
return result;
},
//16进制转换为base64
_convertHexToBase64: function (hexString) {
return btoa(hexString.match(/\w{2}/g).map(char => {
return String.fromCharCode(parseInt(char, 16));
}).join(''));
},
//存储图片资源
replaceImagesFileSourceWithInlineRepresentation: function (imageElements, imagesHexSources) {
//如果有等量的图像元素和图像HEX源,可以根据现有顺序相应地匹配。
if (imageElements.length === imagesHexSources.length) {
for (let i = 0; i < imageElements.length; i++) {
const newSrc = `data:${imagesHexSources[i].type};base64,${this._convertHexToBase64(imagesHexSources[i].hex)}`;
wordImg.push(newSrc);
}
}
},
fileToBase64:function(file,_me) {
return new Promise((resolve, reject) => {
// 创建一个新的 FileReader 对象
const reader = new FileReader();
// 读取 File 对象
reader.readAsDataURL(file);
// 加载完成后
reader.onload = function () {
// 将读取的数据转换为 base64 编码的字符串
const base64String ='data:image/jpeg;base64,'+ reader.result.split(",")[1];
// 解析为 Promise 对象,并返回 base64 编码的字符串
// resolve(base64String);
_me.execCommand('inserthtml',`<img src=${base64String}>`);
};
// 加载失败时
reader.onerror = function () {
reject(new Error("Failed to load file"));
};
});
},
3、在文件搜索 //todo base64 暂时去掉
case 'img':
//todo base64暂时去掉,后边做远程图片上传后,干掉这个
if (val = node.getAttr('src')) {
}
node.setAttr('_src', node.getAttr('src'));
break;
4、替换inputRule内容
inputRule : function (root) {
utils.each(root.getNodesByTagName('img'), function (img, key) {
var attrs = img.attrs,
flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,
opt = me.options,
src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif';
if (attrs['src'] && /^(?:(file:\/+))/.test(attrs['src'])) {
img.setAttr({
width:attrs.width,
height:attrs.height,
alt:attrs.alt,
src:wordImg[key],
'style':'background:url(' + ( flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd'
})
}
})
//清空调用记录
wordImg = [];}
至此,整个UE编辑器可以支持word图文粘贴了。需要注意的是:本次修改没有将图片上传到后端服务器。