《vue实现动态生成并导出world功能》

1. 安装需要的插件并引入

import PizZip from 'pizzip'
import Docxtemplater from 'docxtemplater'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
import ImageModule from 'docxtemplater-image-module-free'
import { Buffer } from 'buffer'

2. 工具utils中新建 exportFile.js 文件,用于将图片转换为base64格式

// getBase64Sync
export function getBase64Sync(imgUrl) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.onload = () => {
      const canvas = document.createElement('canvas');
      // 固定画布尺寸为90x90
      canvas.width = 90;
      canvas.height = 90;
      const ctx = canvas.getContext('2d');
      // 计算缩放比例后居中绘制
      const scale = Math.min(90 / image.width, 90 / image.height);
      const scaledWidth = image.width * scale;
      const scaledHeight = image.height * scale;
      const dx = (90 - scaledWidth) / 2;
      const dy = (90 - scaledHeight) / 2;
      ctx.drawImage(image, dx, dy, scaledWidth, scaledHeight);

      let ext = imgUrl.split('.').pop().toLowerCase();
      if (ext === 'jpg') ext = 'jpeg';
      const mime = `image/${ext}`;
      try {
        const dataurl = canvas.toDataURL(mime, 0.8);
        resolve(dataurl);
      } catch (e) {
        reject(e);
      }
    };
    image.onerror = () => reject(new Error('图片加载失败'));
    image.src = imgUrl + (imgUrl.includes('?') ? '&' : '?') + 't=' + Date.now();
  });
}

// base64DataURLToArrayBuffer
export function base64DataURLToArrayBuffer(dataURL) {
  const base64 = dataURL.split(',')[1];
  const binaryString = window.atob(base64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes.buffer;
}

3.引入 exportFile中的函数

import { getBase64Sync,base64DataURLToArrayBuffer } from '@/utils/exportFile.js'

4.设置.docx模板文件,位置放在public文件下

数组使用{#data}{/data}包裹起来

展示的字段使用{item}

图片赋值用到{%img}

模板例如:

 

5.导出按钮具体功能实现

/** 方案1,不考虑图片直接导出 world */
function submitForm() {
  // const htmlContent = document.getElementById('htmlcontent');
  // const blob = new Blob([htmlContent.innerHTML], { type: 'application/msword' });
  // const url = window.URL.createObjectURL(blob);
  // const a = document.createElement('a');
  // a.href = url;
  // a.download = '包装码.docx';
  // a.click();
  // window.URL.revokeObjectURL(url);
}
/** 方案2,考虑图片,导出world */
async function exportWord() {
  try {
    const content = await new Promise((resolve, reject) => {
//world模板'/boxCode.docx' JSZipUtils.getBinaryContent('/boxCode.docx?random=' + Math.random(), (error, data) => { error ? reject(error) : resolve(data); }); }); const zip = new PizZip(content); let doc = new Docxtemplater(); // 注册图片模块 const imageOptions = { getImage: (tag) => { const base64Data = tag.split(',')[1]; return Buffer.from(base64Data, 'base64'); }, // 设置Word中图片显示尺寸为90x90 getSize: () => [90, 90] }; doc.attachModule(new ImageModule(imageOptions)); doc.loadZip(zip); const processedData = await Promise.all( currentData.value.map(async (item) => { const base64 = await getBase64Sync(item.boxCodeUrl); const matches = base64.match(/^data:image\/(\w+);base64,/); const validBase64 = matches ? base64 : `data:image/png;base64,${base64}`; //图片转码 return { itemCode:item.itemCode?item.itemCode:'', batchNumber:item.batchNumber?item.batchNumber:'', itemName:item.itemName?item.itemName:'', itemModel:item.itemModel?item.itemModel:'', itemNumber:item.itemNumber?item.itemNumber:'', projectName:item.projectName?item.projectName:'', specification:item.specification?item.specification:'', vendorName:item.vendorName?item.vendorName:'', boxCodeUrl: validBase64 || '' }; }) ) doc.setData({ currentData: processedData });//数据赋值 try { doc.render(); } catch (error) { console.error('模板渲染错误:', error.properties?.errors); throw error; } const out = doc.getZip().generate({ type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); saveAs(out, currentItemName.value + '包装码.docx'); } catch (error) { console.error('导出失败:', error); throw error; }finally{ visible.value = false } }

6.导出的world页面中若需要分页,在需要分页的光标位置设置分页符

 注:因为我用到了标签打印,生成的world打印时纵向横向样式展示没办法解决,所以将生成的world通过接口转为pdf输出进行打印
// 生成Word Blob 发送到后端转换服务
    const selectedFile = doc.getZip().generate({ 
      type: 'blob',
      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    });
    const formData = new FormData();
    formData.append('file', selectedFile);
    fetch(VITE_APP_BASE_API + VITE_AGENT_API+`/md/purchaseorder/convertWordToPDF`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${getToken()}`
        },
        body: formData
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`服务器返回错误: ${response.status}`);
        }
        return response.blob();
    })
    .then(blob => {
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute('download', `${currentItemName.value}` + '包装码.pdf' );
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    })
    .catch(error => {
      console.error('导出失败:', error);
      loadingSubmit.value = false
      throw error;
    });

 

 

 

posted @ 2025-05-08 09:29  爱听书的程序猿  阅读(89)  评论(0)    收藏  举报