Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

复制word图文突然图片不显示了 #5990

Open
Swarz opened this issue Oct 28, 2024 · 7 comments
Open

复制word图文突然图片不显示了 #5990

Swarz opened this issue Oct 28, 2024 · 7 comments

Comments

@Swarz
Copy link

Swarz commented Oct 28, 2024

以前复制word图文,编辑框会在图片位置留下标签,所以我异步上传替换这个img便签可以实现图文复制。
fk,最近这个img标签突然没了,是不是sb微软打补丁了哇。

@cycleccc
Copy link

不太懂,有 word 附件 和 编辑框会在图片位置留下标签 这个相关的截图吗
和这个有关吗 #5593 (comment)

@Swarz
Copy link
Author

Swarz commented Oct 28, 2024

不太懂,有 word 附件 和 编辑框会在图片位置留下标签 这个相关的截图吗 和这个有关吗 #5593 (comment)

就是这个
image
我就是异步上传后替换路径的

但是现在占位图片突然没了,替换base64都不行

@KatyChenLu
Copy link

老师可以贴一下替换路径的代码吗?图文一起复制的话图片会变成‘ file:///’协议的,不允许被读取更不能上传了,我这里卡在“Not allowed to load local resource: file:///”

@cycleccc
Copy link

cycleccc commented Nov 1, 2024

老师可以贴一下替换路径的代码吗?图文一起复制的话图片会变成‘ file:///’协议的,不允许被读取更不能上传了,我这里卡在“Not allowed to load local resource: file:///”

本地路径,这是 wps、office 的锅,你可以 setHtml 全局替换相关的 file:/// 协议。具体成因看相关 issue。

@Swarz
Copy link
Author

Swarz commented Nov 4, 2024

老师可以贴一下替换路径的代码吗?图文一起复制的话图片会变成‘ file:///’协议的,不允许被读取更不能上传了,我这里卡在“Not allowed to load local resource: file:///”

我给你贴一下我网上看到的然后自己加了异步上传的,
强调一下,之前是有个img标签占位的,所以上传后替换就行。但是现在没这个img标签占位了。复制图文图片缺失了。

image

async function customUploadImg(file, insertFn) {
const size_limit = file.size / 1024 / 1024 > 5;
if (size_limit) {
const suffix = file.name.split('.').slice(-1)[0].toLowerCase();
const str = suffix === 'png' ? '建议改成jpg格式。' : '';
return message.error('上传图片大小不能超过 5MB!' + str);
}
// file 即选中的文件
const [date, time] = get_date_time();
try {
const data = await COS_upload(
file,
${sys_name}/image/${date}/${time}-${file.name},
);
console.log(106, 'Uploaded', data);

insertFn('https://' + data.Location, file.name, 'https://' + data.Location);

} catch (err) {
console.log(err);
ElMessage.error({
duration: 2000,
message: '上传失败,请联系管理员',
});
return { err: 1 };
}
}

async function customUploadVideo(file) {
const size_limit = file.size / 1024 / 1024 > 5;
if (size_limit) {
const suffix = file.name.split('.').slice(-1)[0].toLowerCase();
const str = suffix === 'png' ? '建议改成jpg格式。' : '';
return message.error('上传图片大小不能超过 5MB!' + str);
}
// file 即选中的文件
const [date, time] = get_date_time();
try {
const data = await COS_upload(
file,
${sys_name}/image/${date}/${time}-${file.name},
);
return {
err: 0,
data: {
url: 'https://' + data.Location,
name: file.name,
},
};
} catch (err) {
console.log(err);
return { err: 1, data: null };
}
}

const editorConfig = {
placeholder: '请输入内容...',
scroll: false,
// MENU_CONF['uploadImage'],
MENU_CONF: {
uploadImage: {
customUpload: customUploadImg,
// 小于该值就插入 base64 格式(而不上传),默认为 0
base64LimitSize, // 50kb
},
uploadVideo: {
async customUpload(file, insertFn) {
const res = await customUploadVideo(file);
if (res.err === 0) {
insertFn(res.url, '');
}
},
},
},
};

const handleCreated = (editor) => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
const handleChange = (editor) => {
console.log('change:', editor.getHtml());
valueText.value = editor.getText();
};
const handleDestroyed = (editor) => {
// console.log('destroyed', editor);
};
const handleFocus = (editor) => { };
const handleBlur = (editor) => { };
const customAlert = (info, type) => {
alert(【自定义提示】${type} - ${info});
};
const customPaste = (editor, event, callback) => {
console.log('ClipboardEvent 粘贴事件对象');
const text = event.clipboardData.getData('text/plain'); // 获取粘贴的纯文本
let html = event.clipboardData.getData('text/html'); // 获取粘贴的 html
let rtf = event.clipboardData.getData('text/rtf'); // 获取 rtf 数据(如从 word wsp 复制粘贴)
console.log(html);

if (html && rtf) {
// 列表缩进会超出边框,直接过滤掉
html = html.replace(/text-indent:-(.*?)pt/gi, '')

// html = html.replace(/<img width=(\d+) height=(\d+)\\r\\nsrc=[\'\"]?([^\'\"]*)[\'\"]?\\r\\n/gi, '<img style="width=$1px;height=$2px;"');

// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
const imgSrcs = findAllImgSrcsFromHtml(html);
// 如果有
if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length) {
  // 从rtf内容中查找图片数据
  const rtfImageData = extractImageDataFromRtf(rtf);
  // 如果找到
  if (rtfImageData.length) {
    // 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
    html = replaceImagesFileSourceWithInlineRepresentation(
      html,
      imgSrcs,
      rtfImageData,
    );
    editor.dangerouslyInsertHtml(html);
  }
}

event.preventDefault();
// 自定义插入内容
// editor.insertText('xxx');
// false 阻止默认的粘贴行为
callback(false);

} else {
callback(true)
}
};

/**

  • 从html代码中匹配返回图片标签img的属性src的值的集合
  • @param htmlData
  • @return Array
    /
    function findAllImgSrcsFromHtml(htmlData) {
    let imgReg = /<img[\s\S]
    ?(?:>|/>)/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内容中匹配返回图片数据的集合
  • @param rtfData
  • @return Array
    */
    function extractImageDataFromRtf(rtfData) {
    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) {
    result.push({
      hex: image
        .replace(regexPictureHeader, '')
        .replace(/[^\da-fA-F]/g, ''),
      type: imageType,
    });
  }
}

}

return result;
}
/**

  • 将html内容中img标签的属性值替换
  • @param htmlData html内容
  • @param imageSrcs html中img的属性src的值的集合
  • @param imagesHexSources rtf中图片数据的集合,与html内容中的img标签对应
  • @param isBase64Data 是否是Base64的图片数据
  • @return String
    */
    function replaceImagesFileSourceWithInlineRepresentation(
    htmlData,
    imageSrcs,
    imagesHexSources,
    isBase64Data = true,
    ) {
    console.log(303, imageSrcs.length, imagesHexSources.length, imageSrcs, imagesHexSources);
    if (imageSrcs.length === imagesHexSources.length) {
    for (let i = 0; i < imageSrcs.length; i++) {
    const byteCount = Math.ceil(imagesHexSources[i].hex.length / 2);
    let newSrc = '';
    if (byteCount > base64LimitSize) {
    // 这里无法使用异步,所以先做标记,然后在change里修改
    const file = _convertHexToFile(
    imagesHexSources[i].hex,
    imagesHexSources[i].type,
    );
    const newStr = imageSrcs[i] + ' from-clipboard';
    htmlData = htmlData.replace(imageSrcs[i], newStr);
    customUploadImg(file, replaceImgUrlAfterUpload(newStr));
    } else {
    const newStr = data:${imagesHexSources[i].type};base64,${_convertHexToBase64(imagesHexSources[i].hex)};
    htmlData = htmlData.replaceAll(imageSrcs[i], newStr);
    console.log(imageSrcs[i], newStr);
    // const newStr = imageSrcs[i] + ' from-clipboard';
    // htmlData = htmlData.replace(imageSrcs[i], newStr);
    // setTimeout(() => {
    // const lastSrc = data:${imagesHexSources[i].type};base64,${_convertHexToBase64(imagesHexSources[i].hex)};
    // replaceImgUrlAfterUpload(newStr)(lastSrc)
    // }, 100);
    }
    }
    }
    console.log(328, htmlData);

return htmlData;
}

function replaceImgUrlAfterUpload(targetStr) {
return (newUrl) => {
let htmlData = editorRef.value.getHtml();
htmlData = htmlData.replace(targetStr, newUrl);
editorRef.value.setHtml(htmlData);
};
}
/**

  • 十六进制转base64
    */
    function _convertHexToBase64(hexString) {
    return btoa(
    hexString
    .match(/\w{2}/g)
    .map((char) => {
    return String.fromCharCode(parseInt(char, 16));
    })
    .join(''),
    );
    }

/**

  • 十六进制转file
    */
    function _convertHexToFile(hexString, type) {
    const binaryData = hexString
    .match(/\w{2}/g)
    .map((byte) => parseInt(byte, 16));
    const suffix = type.split('/').slice(-1)[0].toLowerCase();
    // 创建File对象
    // let blob = new Blob([new Uint8Array(binaryData)], {type});
    // let file = new File([blob], 'zt.'+ suffix, { type });
    let file = new File([new Uint8Array(binaryData)], 'zt' + suffix, { type });
    return file;
    }

@cycleccc
Copy link

cycleccc commented Nov 5, 2024

试了下,确实 Microsoft world 的 图片丢失了,需要兼容一下。

@cycleccc
Copy link

cycleccc commented Nov 9, 2024

已在 wangeditor-next 解决 Microsoft World 不展示占位图片的 bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants