有两种方法 拿到本地图片后解析成base64并上传回显 /只拿base64回显
从 Word/WPS 复制长表格到 TinyMCE 时,图片往往显示为碎图。这是因为图片的 src 指向了本地路径(如 file:///C:/...),而浏览器出于安全考虑禁止加载本地资源,控制台会报出一堆红色的 Not allowed to load local resource 错误。如果手动一张张重新上传,遇到百行长表格简直是"天塌了"的体力活,话不多说以下是我的代码 请看
一、处理图片
在 utils下 新建 rtfParser.js
javascript
/**
* 从 RTF 字符串中提取图片并返回 Hex 数组
*/
/**
* 从 RTF 字符串中提取图片并返回 Hex 数组
*/
export function extractHexImagesFromRTF(rtfData) {
const images = [];
if (!rtfData) return images;
// 1. 按照图片起始符切分
const blocks = rtfData.split('\\pict');
for (let i = 1; i < blocks.length; i++) {
const block = blocks[i];
// 2. 识别格式
let mimeType = '';
if (block.includes('\\pngblip')) mimeType = 'image/png';
else if (block.includes('\\jpegblip')) mimeType = 'image/jpeg';
else continue;
// 3. 暴力匹配:寻找块中所有连续的 0-9a-f 字符(忽略空格和换行)
// 我们找的是最短长度超过 128 位的字符串,这通常就是图片主体
const allHexGroups = block.match(/[0-9a-fA-F\s\r\n]{128,}/g);
if (allHexGroups) {
// 取这一组里最长的那段(防止误删中间的控制符)
const longestHex = allHexGroups.sort((a, b) => b.length - a.length)[0];
const cleanHex = longestHex.replace(/[\s\r\n]/g, '');
if (cleanHex.length > 256) { // 再次校验长度
images.push({
mimeType: mimeType,
hex: cleanHex
});
}
}
}
return images;
}
/**
* 将 Hex 转换为 Blob
*/
export function hexToBlob(hexString, type) {
const bytes = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
bytes[i / 2] = parseInt(hexString.substr(i, 2), 16);
}
return new Blob([bytes], { type });
}
二、在tinymce中引入文件
1、import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser';
方法一:解析图片并上传后台
1、修改setuo中 paste 事件中的逻辑
2、修改paste_preprocess逻辑
javascript
// 1、修改setuo中 paste 事件中的逻辑
ed.on('paste', (event) => {
const rtf = event.clipboardData.getData('text/rtf');
if (rtf && rtf.includes('\\pict')) {
// 快速提取 Hex 列表
const extracted = extractHexImagesFromRTF(rtf);
extracted.forEach((img, i) => {
// 解析单张(耗时极短,不到 100ms)
const base64 = _this.hexToBase64Sync(img.hex, img.mimeType);
// 解析完一张立刻上传一张
_this.uploadSingleImage(base64, i);
});
}
});
//
paste_preprocess: function (plugin, args) {
let imgIdx = 0;
// 使用 silentPlaceholder 预先占位,避免请求图片资源时出现报错(文末附有报错截图)
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
// 你的 paste_preprocess 里的替换逻辑改为:
let content = args.content.replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
// 依然保留 data-idx,但不强制给宽高,让它沿用 Word 里的尺寸
return `${p1}src="${silentPlaceholder}" data-idx="${imgIdx++}"`;
});
let tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
if (tempDiv.querySelector('table')) {
console.log('粘贴的内容包含表格');
if (_this.type == 'table') {
_this.$commonJS.parseTableToArray(content, (tableList) => {
var contentHtml = `
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
<table border="1" style="width: auto; border-collapse: collapse; text-align: center;">
${tableList
.map((row) => {
return `
<tr>
${row
.map((cell) => {
return `
<td colspan="${cell.colspan || 1}" rowspan="${cell.rowspan || 1}">
<span>${cell.text || ''}</span>
</td>
`;
})
.join('')}
</tr>
`;
})
.join('')}
</table>
</div>
`;
const container = document.createElement('div');
container.innerHTML = contentHtml;
// _this.updateTableStyles(container); // 根据需要应用额外的样式
args.content = container.innerHTML; // 更新处理后的内容
});
} else {
}
}
// 阻止默认的粘贴行为,确保自定义处理优先执行
if (args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
},
// 按照你要求的 XMLHttpRequest 格式编写
uploadSingleImage(base64Data, index) {
const _this = this;
// 1. 将解析出来的单张 Base64 转为 Blob
const blob = _this.dataURLtoBlob(base64Data);
const xhr = new XMLHttpRequest();
const formData = new FormData();
// 按照你截图中的参数名,这里假设是 'file'
formData.append('file', blob, `word_img_${index}.png`);
xhr.withCredentials = false;
xhr.open('POST', '接口地址');
xhr.onload = function () {
if (xhr.status !== 200) {
console.error('HTTP Error: ' + xhr.status);
return;
}
try {
const json = JSON.parse(xhr.responseText);
if (json.code === 0) {
// 2. 拼接服务器返回的 URL
const finalUrl = json.data.url;//自己接口返回的完整
// 3. 找到对应的加载中占位图并替换
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
if (placeholder) {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx'); // 任务完成,移除标记
}
}
} catch (e) {
console.error('解析响应失败', e);
}
};
xhr.send(formData);
},
// 辅助工具:Base64 转 Blob
dataURLtoBlob(dataurl) {
const arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
方法二 直接显示base64的图
在 tinymce.init 外层声明两个变量,用于存储图片数据标记
javascript
let globalImgCounter = 0; // 放在 init 闭包内,确保计数器持续增长
let currentPasteBase64Images = [];//base64的数据
3、 在 tinymce.init setup 方法中 添加以下代码
javascript
//
ed.on('paste', (event) => {
const clipboardData = event.clipboardData || window.clipboardData;
const rtf = clipboardData.getData('text/rtf');
if (rtf && rtf.includes('\\pict')) {
// 同步提取 Hex
const extracted = extractHexImagesFromRTF(rtf);
// 【关键】同步转为 Base64 并存入闭包变量
currentPasteBase64Images = extracted.map((img) => {
return _this.hexToBase64Sync(img.hex, img.mimeType);
});
} else {
currentPasteBase64Images = [];
}
});
4、和setup 同级的 paste_preprocess方法
1、使用 silentPlaceholder 预先占位,避免请求图片资源时出现报错(文末附有报错截图)
2、由于表格需要自定义样式,可以不使用默认表格渲染逻辑
javascript
paste_preprocess: function (plugin, args) {
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
args.content = args.content.replace(/src="file:\/\/\/[^" ]*"/gi, `src="${silentPlaceholder}"`);
globalImgCounter = 0;
let content = args.content; // 获取粘贴的内容
let tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
if (tempDiv.querySelector('table')) {
console.log('粘贴的内容包含表格');
if (_this.type == 'table') {
// 3. 在这里直接消费外部变量 currentPasteBase64Images
content = content.replace(new RegExp(`src="${silentPlaceholder}"`, 'gi'), () => {
// 按顺序取图
const base64Data = currentPasteBase64Images[globalImgCounter] || '';
globalImgCounter++;
return `src="${base64Data}"`;
});
_this.$commonJS.parseTableToArray(content, (tableList) => {
var contentHtml = `
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
<table border="1" style="width: auto; border-collapse: collapse; text-align: center;">
${tableList
.map((row) => {
return `
<tr>
${row
.map((cell) => {
return `
<td colspan="${cell.colspan || 1}" rowspan="${cell.rowspan || 1}">
<span>${cell.text || ''}</span>
</td>
`;
})
.join('')}
</tr>
`;
})
.join('')}
</table>
</div>
`;
const container = document.createElement('div');
container.innerHTML = contentHtml;
args.content = container.innerHTML; // 更新处理后的内容
});
} else {
}
}

