html-docx-js和file-saver实现html导出word

依赖html-docx-js,file-saver,html2canvas

javascript 复制代码
import { asBlob } from 'html-docx-js/dist/html-docx';
import { saveAs } from 'file-saver';
import html2Canvas from 'html2canvas';

const handleImageToBase64 = (cloneEle) => {
  let imgElements = cloneEle.getElementsByTagName('img');
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  const promises = Array.from(imgElements).map((item) => {
    return new Promise((resolve) => {
      item.onload = () => {
        ctx.clearRect(0, 0, item.width, item.height);
        canvas.width = item.width;
        canvas.height = item.height;
        ctx.drawImage(item, 0, 0, item.width, item.height);
        let ext = '';
        if (item.src.indexOf('data:image/svg+xml;base64') === 0) {
          ext = 'png';
        } else {
          ext = item.src.substring(item.src.lastIndexOf('.') + 1).toLowerCase();
        }
        let dataURL = canvas.toDataURL('image/' + ext);
        item.setAttribute('src', dataURL);
        resolve();
      };
    });
  });
  canvas.remove();
  return promises;
};

const handleCanvasToImage = (cloneEle) => {
  const canvasElements = cloneEle.getElementsByTagName('canvas');
  const promises = Array.from(canvasElements).map((ca, index) => {
    return new Promise((resolve) => {
      const url = ca.toDataURL('image/png', 1);
      const img = new Image();
      img.onload = () => {
        URL.revokeObjectURL(url);
        resolve();
      };
      img.src = url;
      // 生成img插入clone的dom的canvas之前
      canvasElements[index].parentNode.insertBefore(img, canvasElements[index]);
    });
  });
  // 移除原来的canvas
  Array.from(canvasElements).forEach((ca) => ca.parentNode.removeChild(ca));
  return promises;
};

const handleSvgToImage = (cloneEle) => {
  const svgElements = cloneEle.getElementsByTagName('svg');
  Array.from(svgElements).forEach((svg) => {
    // 将SVG元素转换为PNG图片
    const img = new Image();
    img.src = 'data:image/svg+xml;base64,' + window.btoa(encodeURIComponent(svg.outerHTML).replace(/%([0-9A-F]{2})/g, function(match, p1) {
      return String.fromCharCode('0x' + p1);
    }));
    // 将图片插入到SVG元素的位置
    svg.parentNode.insertBefore(img, svg);
    // 移除原来的SVG元素
    svg.parentNode.removeChild(svg);
  });
};

const handleCodeToImage = (ele, cloneEle) => {
  let codeElements = ele.querySelectorAll('pre code');
  let cloneCodeElements = cloneEle.querySelectorAll('pre code');
  const promises = Array.from(codeElements).map((item, index) => {
    return new Promise((resolve) => {
      const pre = item.parentNode;
      html2Canvas(pre, {
        imageTimeout: 2000,
        logging: false,
        scrollY: 0,
        scrollX: 0,
        scale: window.devicePixelRatio * 1.2, // 添加的scale 参数
        width: item.offsetWidth + 32,
        allowTaint: false,
        useCORS: true, // 开启跨域
      }).then(canvas => {
        // 将 Canvas 转换为图片
        let dataURL = canvas.toDataURL('image/png');
        // 创建一个新的 <img> 元素并设置其 src 属性
        const img = new Image();
        img.src = dataURL;
        // 将图片插入到SVG元素的位置
        const clonePre = cloneCodeElements[index].parentNode;
        clonePre.parentNode.insertBefore(img, clonePre);
        // 移除原来的SVG元素
        clonePre.parentNode.removeChild(clonePre);
        resolve();
      });
    });
  });
  return promises;
};

// 表格虚线 向右偏移10px左右
const handleTableStyle = (cloneEle) => {
  let tableElements = cloneEle.getElementsByTagName('table');
  Array.from(tableElements).forEach((table) => {
    table.style.borderCollapse = table.style.borderCollapse || 'collapse';
    table.border = table.border || '1';
    table.style.marginLeft = '10px';
  });
  let thElements = cloneEle.getElementsByTagName('th');
  Array.from(thElements).forEach((th) => {
    th.style.backgroundColor = '#f0f0f0';
  });
};

const getMarkdownCss = () => {
  return `
  body {
    color: #383c4a;
    font-family: -apple-system, blinkmacsystemfont, "Segoe UI", helvetica, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
    font-size: 12px;
    word-wrap: break-word;
  }
  `;
};

const handleHtml = async (ele, cloneEle, type) => {
  const canvasPromises = handleCanvasToImage(cloneEle);
  await Promise.all(canvasPromises);
  handleSvgToImage(cloneEle);
  const imagePromises = handleImageToBase64(cloneEle);
  const codePromises = handleCodeToImage(ele, cloneEle);
  await Promise.all(codePromises);
  await Promise.all(imagePromises);
  handleTableStyle(cloneEle);
  let cssString = '';
  if (type === 'markdown') {
    cssString = getMarkdownCss();
  }
  const innerHtml = cloneEle.outerHTML
    // strong在word中不生效问题
    .replace(/<strong class="(.*?)">(.*?)<\/strong>/g, '<b class="$1">$2</b>')
    .replace(/<mark/g, '<span')
    .replace(/<\/mark>/g, '</span>');
  const htmlString = `
  <html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
  <head>
  <style type="text/css">${cssString}</style> 
  </head>
  <body>
  ${innerHtml}
  </body>
  </html>`;
  return htmlString;
};

export default function exportWord({ selector, name = 'export', type = 'markdown' }) {
  if (!selector) return Promise.reject();
  // eslint-disable-next-line no-async-promise-executor
  return new Promise(async (resolve) => {
    const ele = document.querySelector(selector);
    const cloneEle = ele.cloneNode(true);
    const htmlString = await handleHtml(ele, cloneEle, type);
    const converted = asBlob(htmlString);
    saveAs(converted, `${name}.docx`);
    resolve();
  });

}


}

页面调用

javascript 复制代码
exportWord({
        selector: '.report-container'
      });
相关推荐
_AaronWong3 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode3 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441943 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo3 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭4 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木4 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮4 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati4 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉4 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n4 小时前
双端 Diff 算法详解
前端·javascript·vue.js