web前端导出word文档

web前端导出word文档

2023年05月30日 阅读:212 字数:1179 阅读时长:3 分钟

项目有个需求要前端预览和导出word文档,记录下技术选型和功能实现

1.前言

项目上有一个导出word文档的需求,由于有可视化图表,后端导出会比较麻烦,而且前端也需要把word报告展示出来。

所以在前端做预览和导出,记录下技术选型以及实现方案。

2.Word导出

2.1.docx

docx - Generate .docx documents with JavaScript

  • 需要使用指定的api生成和修改.docx文件
  • 功能强大,除了标题、段落、图片等基础,还支持封面、页眉、页脚、分页等等
import { Document, Packer, Paragraph, TextRun } from "docx";
import { saveAs } from 'file-saver'

const doc = new Document({
  sections: [
    {
      properties: {},
      children: [
        new Paragraph({
          children: [
            new TextRun("Hello World"),
            new TextRun({
              text: "Foo Bar",
              bold: true
            }),
            new TextRun({
              text: "\tGithub is the best",
              bold: true
            })
          ]
        })
      ]
    }
  ]
});

Packer.toBlob(doc).then((blob) => {
  saveAs(blob, "example.docx");
  console.log("Document created successfully");
});

2.2.html-docx-js

html-docx-js - npm (npmjs.com)

  • 可以根据html导出word
  • 图片、canvas需要转换成Data URL的图片
  • 不支持封面、页眉页脚等功能
import { saveAs } from 'file-saver'
import htmlDocx from 'html-docx-js'

// 导出图片的格式
const IMAGE_TYPE = 'image/jpeg'
// 导出图片的质量
const IMAGE_QUALITY = 0.8

/**
 * HTML导出Docx
 * @param {String} params.element 要导出的元素选择器
 * @param {String} params.styleString 样式字符串
 * @param {String} params.orientation 页面方向 portrait:竖向、landscape:横向
 * @param {String} params.filename 导出文件名称
 */
function exportHtmlToDocx({ element, styleString, margins, orientation = 'portrait', filename = 'htmlDocx' }) {
  const html = generateContent(element)

  const content = `
    <html>
      <head>
        <style>${styleString ? styleString.replace(/(\s{2,}|\n)/g, '') : ''}</style>
      </head>
      <body>${html}</body>
    </html>
  `
  const converted = htmlDocx.asBlob(content, { orientation, margins })

  saveAs(converted, filename)
}

/**
 * 生成导出的html字符串
 * @param {String} element 要导出的元素选择器
 * @returns {String}
 */
function generateContent(element) {
  const sourceElement = document.querySelector(element)
  let cloneElement = sourceElement.cloneNode(true)

  cloneElement = convertImagesToBase64(cloneElement, sourceElement)
  cloneElement = convertCanvasToBase64(cloneElement, sourceElement)

  return cloneElement.innerHTML
}

/**
 * 转换图片地址为Data URL
 * @param {Element} cloneElement 拷贝要导出的元素
 * @param {Element} sourceElement 要导出的元素
 * @returns {Element}
 */
function convertImagesToBase64(cloneElement, sourceElement) {
  const sourceImages = sourceElement.querySelectorAll('img')
  const cloneImages = cloneElement.querySelectorAll('img')
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')

  sourceImages.forEach((imgElement, index) => {
    const width = imgElement.width
    const height = imgElement.height

    // preparing canvas for drawing
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    canvas.width = width
    canvas.height = height

    ctx.drawImage(imgElement, 0, 0, width, height)
    // by default toDataURL() produces png image, but you can also export to jpeg
    // checkout function's documentation for more details
    const dataURL = canvas.toDataURL(IMAGE_TYPE, IMAGE_QUALITY)
    // 替换拷贝的元素而不是源图片
    const replaceElement = cloneImages[index]
    replaceElement.setAttribute('src', dataURL)
  })
  canvas.remove()

  return cloneElement
}

/**
 * 转换canvas画布为img + Base64 URL
 * @param {Element} cloneElement 拷贝要导出的元素
 * @param {Element} sourceElement 要导出的元素
 * @returns {Element}
 */
function convertCanvasToBase64(cloneElement, sourceElement) {
  const sourceCanvas = sourceElement.querySelectorAll('canvas')
  const cloneCanvas = cloneCanvas.querySelectorAll('canvas')

  sourceCanvas.forEach((canvasElement, index) => {
    const dataURL = canvasElement.toDataURL(IMAGE_TYPE, IMAGE_QUALITY)
    const imgElement = document.createElement('img')
    imgElement.src = dataURL

    const replaceElement = cloneCanvas[index]
    // 把canvas元素替换成img
    replaceElement.parentNode.replaceChild(imgElement, replaceElement)
  })

  return cloneElement
}

export default exportHtmlToDocx

使用:

import exportHtmlToDocx from 'export-html-to-docx.js'

exportHtmlToDocx({
  element: '#htmlDocx',
  styleString: 'table td{border: 1px solid #e2e2e2}',
  margin: { // 页边距
    top: 1440, // eq 2.54cm
    right: 1440,
    bottom: 1440,
    left: 1440
  },
  orientation: 'portrait',
  filename: 'word报告'
})

2.2.1.常见问题

  • 样式不生效,不支持多个选择器设置样式:每个标签只使用一个选择器,通过不同的选择器设置样式规则,如正常段落、首行缩进段落、首行缩进且加粗段落
<p class="para-indent">首行缩进</p>
<p class="para-bold">文本加粗</p>
<p class="para-indent-bold">首行缩进并加粗</p>
.para{font-size:12pt;}
.para-indent{font-size:12pt;text-indent:2em;}
.para-bold{font-size:12pt;font-weight:bold;}
.para-indent-bold{font-size:12pt;text-indent:2em;font-weight:bold;}
  • 表格内的字体无法设置宋体:给文本嵌套一层P标签
<table>
  <tr>
    <td>
      <p class="para-bold">表格内的段落</p>
    </td>
  </tr>
</table>
  • 表格边框样式设置

不能这样(边框样式会有问题):

table {
	border-top: 1px solid #000;
	border-left: 1px solid #000;
}

table td,
table th {
	border-right: 1px solid #000;
	border-bottom: 1px solid #000;
}

要用下面这种:

table {
  border: 1px solid #000000;
  border-collapse: collapse;
  border-spacing: 0;
}

table td,
table th {
  border: 1px solid #000000;
}
  • 导出图片、Canvas为空:等待图片、DOM加载完成再进行导出

2.3.html-docx

html-docx - npm (npmjs.com)

这是基于html-docx-js实现的,封装了一些常用的方法,比如上面的canvas转换、文档封面、页眉页脚等。

  • 图片使用网络地址,如果要转成Data URL,需要自己改逻辑
  • 文档封面、页眉页脚、分页等是通过修改html-docx-js源码实现的
import HtmlToDocx from 'html-docx'

HtmlToDocx({
    exportElement: '#html-content', // 需要转换为word的html标签
    exportFileName: 'list.docx', // 转换之后word文档的文件名称
    StringStyle: '', // css样式以字符串的形式插入进去
    margins:{top: 1440,right: 1440,bottom: 1440,left: 1440,header: 720,footer: 720} // word的边距配置
})

3.HTML预览

这里的预览不是使用Office的服务来预览Word文档,只是用HTML展示出来。

尽量使用原生而不是UI库,因为UI库不好控制组件内的层级、样式等,可能影响最终的导出。

页面尺寸可以参考:https://github.com/cognitom/paper-css

字号换算:(53条消息) Word字体大小对照换算表(字号、磅、英寸、像素)_QAQ_King的博客-CSDN博客

图片、Canvas导出:根据HTML展示的图片尺寸导出

推荐阅读

恰饭区

评论区 (0)

0/500

还没有评论,快来抢第一吧