Vue導出Html為Word中包含圖片在Microsoft Word顯示異常問題

問題背景

碰到一個問題:將包含圖片和SVG數學公式的HTML內容導出為Word文檔時,將圖片都轉為ase64格式導出,在WPS Word中顯示正常,但是在Microsoft Word中出現圖片示異常。
在這里插入圖片描述

具體問題表現

  1. WPS兼容性:在WPS中顯示正常,說明是Microsoft Word特有的兼容性問題
  2. SVG數學公式:在Word中顯示為"當前無法顯示此圖片"
  3. 普通圖片:顯示不正常或無法顯示

技術方案設計

三重兜底機制

我們設計了一個三層處理機制,確保在各種情況下都能提供最佳的用戶體驗:

第一層:前端Canvas轉Base64
const imageUrlToWordCompatibleBase64 = async (imageUrl: string): Promise<string> => {return new Promise((resolve, reject) => {try {const img = new Image();img.onload = () => {try {const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');if (!ctx) {reject(new Error('can not create canvas context'));return;}// 設置Canvas尺寸canvas.width = img.width;canvas.height = img.height;// 設置白色背景ctx.fillStyle = 'white';ctx.fillRect(0, 0, canvas.width, canvas.height);// 繪制圖片ctx.drawImage(img, 0, 0);// 轉換為PNG base64,使用更兼容的格式const base64 = canvas.toDataURL('image/png, 0.9');resolve(base64);} catch (error) {reject(error);}};img.onerror = () => {reject(new Error(`image load failed: ${imageUrl}`));};img.src = imageUrl;} catch (error) {reject(error);}});
};
第二層:后端代理轉Base64
const getImageBase64ViaProxy = async ({imageUrl, imageId}: {imageUrl: string, imageId: number}): Promise<string> => {try {// 添加隨機延遲避免緩存const randomDelay = Math.random() * 2000 + 1000;await new Promise(resolve => setTimeout(resolve, randomDelay));const response = await adminApi.getImageBase64Api({ imageId: imageId,imagePath: imageUrl,});const data = response.data;if (!data) {throw new Error('image base64 is null');}// 處理后端返回的新結構 { imageId: string, imageBase64: string }if (data && typeof data === 'object' && 'imageId' in data && 'imageBase64' in data) {const imageBase64 = data.imageBase64;if (typeof imageBase64 === 'string') {if (imageBase64.startsWith('data:image/')) {return imageBase64;} else {return `data:image/png;base64,${imageBase64}`;}}}// 兼容舊格式:如果后端返回的是base64字符串,直接返回if (typeof data === 'string') {const dataStr = data as string;if (dataStr.startsWith('data:image/')) {return dataStr;} else {return `data:image/png;base64,${dataStr}`;}}throw new Error('Unsupported data format from backend');} catch (error) {console.error(`處理圖片失敗 ImageId: ${imageId}`, error);throw error;}
};
第三層:降級為Alt提示

當圖片處理失敗時,提供一個友好的降級方案:

// 降級為alt提示
imgElement.style.maxWidth = '100%';
imgElement.style.height = 'auto';
imgElement.style.display = 'inline-block';
imgElement.style.margin = '8px 0';
imgElement.style.borderRadius = '4px';
imgElement.style.border = '1px solid #ddd';
imgElement.style.padding = '4px';
imgElement.style.backgroundColor = '#f9f9f9';
if (!imgElement.alt) {imgElement.alt = `image: ${src}`;
}

SVG數學公式特殊處理

針對SVG數學公式,我們設計了專門的轉換方案:

const svgToWordCompatiblePng = async (svgElement: SVGElement, width: number, height: number): Promise<string> => {return new Promise((resolve, reject) => {try {// 克隆SVG元素const clonedSvg = svgElement.cloneNode(true) as SVGElement;// 設置SVG尺寸clonedSvg.setAttribute('width', width.toString());clonedSvg.setAttribute('height', height.toString());// 設置viewBox以保持比例if (!clonedSvg.getAttribute('viewBox')) {clonedSvg.setAttribute('viewBox', `0 0 ${width} ${height}`);}// 序列化SVGconst serializer = new XMLSerializer();const svgString = serializer.serializeToString(clonedSvg);// 創建SVG的data URLconst svgDataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));// 創建Image對象const img = new Image();img.onload = () => {try {// 創建Canvasconst canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');if (!ctx) {reject(new Error('can not create canvas context'));return;}// 設置Canvas尺寸canvas.width = width;canvas.height = height;// 設置白色背景(確保透明度問題)ctx.fillStyle = 'white';ctx.fillRect(0, 0, width, height);// 繪制圖片到Canvasctx.drawImage(img, 0, 0, width, height);// 轉換為PNG base64,使用更兼容的格式const pngBase64 = canvas.toDataURL('image/png, 0.9');resolve(pngBase64);} catch (error) {reject(error);}};img.onerror = () => {reject(new Error('image load failed'));};img.src = svgDataUrl;} catch (error) {reject(error);}});
};

并發處理優化

問題分析

在初期實現中,我們遇到了并發請求導致的重復處理問題。相同imageId被請求了兩次,導致資源浪費和性能問題。

解決方案

  1. 串行處理:將圖片處理改為串行處理,避免并發問題
  2. 隨機延遲:為每個請求添加隨機延遲,避免緩存和并發沖突
  3. 重復檢測:添加已處理imageId集合,避免重復請求
// 串行處理圖片,避免并發問題
const results: Array<{success: boolean;method: string;index: number;imgElement: HTMLImageElement;base64?: string;error?: string;
}> = [];for (let i = 0; i < imageProcessingTasks.length; i++) {const task = imageProcessingTasks[i];const { imgElement, src, index, originalWidth, originalHeight } = task;try {// 1. 嘗試前端canvas轉base64try {const base64 = await imageUrlToWordCompatibleBase64(src);imgElement.src = base64;results.push({ success: true, method: 'canvas', index, imgElement, base64 });} catch (error: any) {// 2. 嘗試后端代理try {const base64 = await getImageBase64ViaProxy({ imageUrl: src, imageId: index});imgElement.src = base64;results.push({ success: true, method: 'proxy', index, imgElement, base64 });} catch (proxyError: any) {// 3. 降級為alt提示// ... 降級處理代碼}}} catch (error: any) {console.error(`Error processing image ${index + 1}:`, error);results.push({ success: false, method: 'error', index, imgElement, error: error?.message || error });}
}

重復Base64檢測和修復

問題識別

我們發現相同圖片可能產生相同的base64結果,這可能導致Word中的顯示問題。

解決方案

// 檢查是否有重復的base64結果
const base64Results = results.filter(r => r.success && r.method === 'proxy').map(r => r.base64).filter(Boolean);const uniqueBase64 = new Set(base64Results);
if (uniqueBase64.size !== base64Results.length) {// 詳細分析重復的base64const base64Count = new Map<string, number>();base64Results.forEach((base64) => {if (base64) {base64Count.set(base64, (base64Count.get(base64) || 0) + 1);}});// 嘗試修復重復問題:為重復的圖片重新請求const duplicateTasks: Array<{imgElement: HTMLImageElement;src: string;index: number;originalWidth: number;originalHeight: number;originalResult: any;needsReprocessing: boolean;}> = [];// 記錄已經處理過的imageId,避免重復處理const processedImageIds = new Set<number>();base64Count.forEach((count, base64) => {if (count > 1) {const duplicateResults = results.filter(r => r.success && r.method === 'proxy' && r.base64 === base64).map((r, idx) => ({ result: r, taskIndex: idx })).filter(({ result, taskIndex }) => {const task = imageProcessingTasks[taskIndex];return task !== undefined;});// 保留第一個,其余的需要重新處理duplicateResults.slice(1).forEach(({ result, taskIndex }) => {const task = imageProcessingTasks[taskIndex];if (task && !processedImageIds.has(task.index)) {processedImageIds.add(task.index);duplicateTasks.push({...task,originalResult: result,needsReprocessing: true});}});}});if (duplicateTasks.length > 0) {// 重新處理重復的圖片,添加隨機延遲避免并發問題for (const duplicateTask of duplicateTasks) {try {// 添加隨機延遲await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 500));const newBase64 = await getImageBase64ViaProxy({ imageUrl: duplicateTask.src, imageId: duplicateTask.index });// 更新對應的img元素const imgElement = duplicateTask.imgElement;if (imgElement && newBase64 !== duplicateTask.originalResult.base64) {imgElement.src = newBase64;}} catch (error) {console.error(`重新處理失敗: ImageId ${duplicateTask.index}`, error);}}}
}

Word模板優化

為了確保在Microsoft Word中的最佳顯示效果,我們設計了專門的HTML模板:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible content=IE=edge"><style>body {font-family: 'Times New Roman', Times, serif;  /* Microsoft Word默認字體 */line-height: 1.5;font-size: 12pt;  /* Word默認字號 */margin: 0;padding: 20px;}img { max-width: 100%; height: auto; display: inline-block;vertical-align: middle;margin: 4px;}/* Microsoft Word兼容的圖片樣式 */img[src^="data:image/"] {border: none;outline: none;}/* 確保圖片在Word中正確顯示 */.word-image {display: inline-block;vertical-align: middle;margin: 4px;}</style>
</head>
<body><!-- 內容占位符 -->
</body>
</html>

導出功能實現

主要導出函數

export const exportDocx = async (className: string,title = 'document',type = 'docx'
): Promise<void> => {const baseTemplate = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible content=IE=edge"><style>body {font-family: Times New Roman', Times, serif;line-height: 1.5;font-size: 12pt;margin: 0;padding: 20px;}img { max-width: 100%; height: auto; display: inline-block;vertical-align: middle;margin: 4px;}img[src^="data:image/"] {border: none;outline: none;}.word-image {display: inline-block;vertical-align: middle;margin: 4px;}</style></head><body>${getHTMLContentByClassName(className)}</body></html>`;const htmlSvgContent = await handleSvgToBase64(baseTemplate);try {const options = {orientation: 'portrait',margins: { top: 720, right: 720, bottom: 720, left: 720 }, // Word默認邊距header: false,footer: false,pageSize: 'A4'};const data = await asBlob(htmlSvgContent, options as any);const fileName = `${title.replace(/[<>:"/\\|?*]/g, '')}-${Date.now()}.${type}`; // 移除非法字符saveAs(data as Blob, fileName);} catch (error) {console.error('export docx error:', error);}
};

關鍵注意事項

1. 圖片格式兼容性

  • PNG格式:Microsoft Word對PNG格式支持最好
  • Base64編碼:確保使用正確的MIME類型前綴
  • 白色背景:為透明圖片設置白色背景,避免顯示問題

2. 并發處理

  • 串行處理:避免并發請求導致的重復處理
  • 隨機延遲:防止緩存和并發沖突
  • 重復檢測:識別并修復重復的base64結果

3. 錯誤處

  • 三層兜底:確保在各種情況下都有降級方案
  • 詳細日志:記錄處理過程,便于調試
  • 用戶友好:提供清晰的錯誤提示

最終效果

經過優化后,我們的解決方案實現了:
在這里插入圖片描述

測試結果

  • ? Microsoft Word 2016/2019/365:圖片正常顯示
  • ? WPS Office:完全兼容
  • ? 數學公式:SVG轉PNG后正常顯示
  • ? 復雜布局:保持原有格式和樣式

總結

這個方案成功解決了Microsoft Word導出中的圖片顯示問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89360.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89360.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89360.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

橢圓曲線密碼學 Elliptic Curve Cryptography

密碼學是研究在存在對抗行為的情況下還能安全通信的技術。即算法加密信息&#xff0c;再算法解密出信息。加密分為兩類 1. Symmetric-key Encryption (secret key encryption) 即一種密鑰&#xff0c;加密和解密使用同一密鑰&#xff0c;可相互轉換 2. Asymmetric-key Encry…

wedo牛-----第47節(免費分享圖紙)

夸克網盤&#xff1a;https://pan.quark.cn/s/4b40a8d18979 高清圖紙源文件&#xff0c;需要的請自取

Unity | AmplifyShaderEditor插件基礎(第十集:噪聲的種類+火焰制作-下)

目錄 一、&#x1f44b;&#x1f3fb;前言 二、圓火焰 三、制作梯度 梯度成品預覽 1.GradientSample節點 2.gradient的用法 3.time節點 四、添加顏色 Color節點 五、火焰搖擺 1.X方向的移動 2.Y方向的移動 3.Z方向的移動 4.把xyz組合起來 Panner節點 六、擺放和…

黑馬Node.js全套入門教程,nodejs新教程含es6模塊化+npm+express+webpack+promise等_ts對象筆記

1.1 什么是運行環境&#xff1f; 運行環境是指代碼正常運行所需的必要環境&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; V8引擎負責解析和執行JavaScript代碼。內置API是由運行環境提供的特殊接口&#xff0c;只能在所屬的運行環境中被調用 1.2 JavaScrip…

React 項目環境變量使用指南

在 React 項目中正確使用環境變量是管理不同環境配置的關鍵技術。以下是完整的解決方案&#xff1a; 1. 創建環境變量文件 React 項目支持以下環境變量文件&#xff08;按優先級從高到低&#xff09;&#xff1a; .env.development.local (本地開發環境).env.development (開發…

Oracle 關于一些連接故障的總結

積累了幾次Oracle客戶端連接故障&#xff0c;做下總結。 文章目錄1、案例案例1&#xff1a;客戶端連接報錯ORA-12514案例2&#xff1a;客戶端連接報錯ORA-28547案例3&#xff1a;客戶端連接報錯&#xff1a;Got minus one from a read call案例4&#xff1a;客戶端連接報錯&…

V-USB USB設備模擬原理分析

V-USB USB設備模擬原理分析 通過分析V-USB項目的核心文件&#xff0c;詳細解釋這個項目是如何在AVR微控制器上模擬USB設備的&#xff1a; 1. 整體架構 V-USB是一個純軟件實現的USB低速設備驅動&#xff0c;主要由以下幾個核心文件組成&#xff1a; usbdrv.c : USB協議棧的C語言…

kafka3.6下載安裝(傳統架構/KRaft模式)+實例測試

知識補充&#xff1a; Kafka 和 ZooKeeper 的關系可以用 “協作依賴” 來概括。在 Kafka 的早期版本&#xff08;Kafka 2.8.0 之前&#xff09;中&#xff0c;ZooKeeper 是 Kafka 的核心依賴&#xff0c;用于管理集群元數據、協調 Broker 和 Controller 選舉等關鍵功能。但從 …

華控智能產品特點——產品生態全景與場景化創新

公司構建 “3X”產品戰略&#xff0c;以三大核心場景為基礎持續拓展技術外延&#xff1a; 1. 智能安防產品線軍工級指紋槍盒&#xff1a;采用6061-T6航空鋁材&#xff0c;內嵌震動報警模塊&#xff0c;非法開箱觸發90dB警鳴。為軍工企業定制的雙人認證版本需兩位授權人員同時驗…

爬蟲核心原理與入門技巧分析

一、爬蟲核心原理&#xff1a;模擬人類瀏覽的“自動化工具” 簡單來說&#xff0c;網絡爬蟲&#xff08;Web Crawler&#xff09;是一種按照一定規則&#xff0c;自動抓取互聯網信息的程序或腳本。其核心原理可以類比人類瀏覽網頁的過程&#xff0c;只不過將手動操作轉化為了代…

spring-cloud微服務部署-feign服務間調用

1 準備工作 需要安裝并啟動nacos&#xff0c;作為服務注冊中心。地址&#xff1a;https://nacos.io/ 2 項目結構 parent的pom.xml聲明依賴&#xff1a; <dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</gr…

IDEA高效開發:Database Navigator插件安裝與核心使用指南

目錄 1.前言 2.正文 2.1安裝流程 2.1.1IDE內部安裝 2.1.2手動下載安裝 ?? 避坑指南 2.2使用教程 2.2.1連接數據庫 2.2.2查看數據庫/表 2.2.3查詢數據 2.2.4修改表結構 2.2.5生成代碼 2.2.6常見故障排除 3.小結 1.前言 “作為Java開發者&#xff0c;日常與數據…

Maven私服倉庫,發布jar到私服倉庫,依賴的版本號如何設置,規范是什么

Maven私服倉庫&#xff0c;發布jar到私服倉庫&#xff0c;依賴的版本號如何設置&#xff0c;規范是什么

量子卷積神經網絡:量子計算與深度學習的融合革命

引言&#xff1a;當卷積神經網絡遇上量子計算在人工智能與量子計算雙重浪潮的交匯處&#xff0c;量子卷積神經網絡&#xff08;Quantum Convolutional Neural Network, QCNN&#xff09;正成為突破經典算力瓶頸的關鍵技術。傳統卷積神經網絡&#xff08;CNN&#xff09;在圖像識…

線程(三) linux 同步

目錄 概念補充 條件變量 操作 例:多線程搶票 封裝 生產者消費者模型 生產者和消費者之間的關系 BlockQueue(阻塞隊列) 單生產單消費 信號量 簡介 操作 多生產者多消費者RingQueue(環形隊列)代碼 sem封裝 信號量與鎖 小知識 概念補充 同步:在保證數據安全的前…

Eclipse 生成 jar 包

Eclipse 生成 jar 包 引言 Eclipse 是一款功能強大的集成開發環境&#xff08;IDE&#xff09;&#xff0c;廣泛應用于 Java 開發領域。在 Java 開發過程中&#xff0c;將源代碼編譯成可執行的 jar 包是常見的需求。本文將詳細介紹在 Eclipse 中生成 jar 包的方法&#xff0c;包…

kafka--基礎知識點--0

kafka 架構 https://cloud.tencent.com/developer/article/2307892 19張圖 生產者架構 消息的磁盤存儲文件結構 https://cloud.tencent.com/developer/article/2307892 19張圖 produce消息分區策略 kafka–基礎知識點–5–生產者分區策略 ISR、OSR、AR 是什么&#xff1…

替換ngnix ssl 證書

1. 阿里云數字證書管理服務 -》SSL 證書管理 -》個人測試證書&#xff08;原免費證書&#xff09;-》查找相應域名的證書/新建證書&#xff0c;申請 -》下載證書&#xff0c;如果是ngnix服務器&#xff0c;就下載pem/key格式2.遠程連接服務器a.nginx -t :查看ngnix 配置文件在哪…

rabbitmq ACK

在消息隊列&#xff08;如 RabbitMQ&#xff09;中&#xff0c;**ACK&#xff08;Acknowledgement&#xff09;是消息確認機制**&#xff0c;用于確保消息被消費者成功處理。其核心作用是解決以下問題&#xff1a;mermaid復制代碼導出svg&#x1f4cc; ACK 的兩種模式1. 自動確…

性能遠超Spring Cloud Gateway!Apache ShenYu如何重新定義API網關!

Apache ShenYu Apache ShenYu是一個異步的&#xff0c;高性能的&#xff0c;跨語言的&#xff0c;響應式的 API 網關。 特點 ? 代理&#xff1a;支持Apache Dubbo&#xff0c;Spring Cloud&#xff0c;gRPC&#xff0c;Motan&#xff0c;SOFA&#xff0c;TARS&#xff0c;We…