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'
      });
相关推荐
Highcharts.js1 小时前
缺失数据可视化图表开发实战|Highcharts创建人员出生统计面积图表示例
开发语言·前端·javascript·信息可视化·highcharts·图表开发
LaughingZhu8 小时前
Product Hunt 每日热榜 | 2026-05-21
前端·人工智能·经验分享·chatgpt·html
怕浪猫8 小时前
Electron 开发实战(一):从零入门核心基础与环境搭建
前端·electron·ai编程
小鹏linux9 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水10 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger10 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)10 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态10 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态10 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart10 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter