当前位置: 首页 > news >正文

react+html-docx-js将页面导出为docx

1.主要使用:html-docx-js进行前端导出
2.只兼容到word,wps兼容不太好
3.处理分页换行
4.处理页眉

index.tsx

import { saveAs } from 'file-saver';
import htmlToDocxGenerate from './HtmlToDocx';const handleExportByHtml = async () => {const exportConfig = getSaveParams({currentTemplate,formConfigParams: formParams});const filename = getFileName(exportConfig, formParams);try {const content = document.getElementById('docx-container');if (!content) {message.error('未找到导出内容');return;}const docxBlob = await htmlToDocxGenerate(content.outerHTML, {containerId: 'docx-container',pageHeaderId: 'page-header',pageBreakClassName: 'page-break'});saveAs(docxBlob, `${filename ?? '分析报告'}.docx`);message.success('材料已下载成功,请查看下载文件夹');} catch (err) {message.error('导出失败:' + (err as Error).message);}};

HtmlToDocx/index.ts

import HtmlDocx from 'html-docx-js/dist/html-docx';  // 使用浏览器版本
import _ from 'lodash';
import docxHtml from './pageHtml';interface DocumentOptions {orientation: 'portrait' | 'landscape';margins: {top: number;right: number;bottom: number;left: number;header: number;footer: number;gutter: number;};
}interface HtmlToDocxOptions {containerId?: string;chartClassName?: string;pageBreakClassName?: string;pageHeaderId?: string;document?: Partial<DocumentOptions>;
}const htmlToDocxGenerate = async (originalHtml: string, options: HtmlToDocxOptions = {}): Promise<Blob> => {const defaultDocument: DocumentOptions = {orientation: 'portrait',margins: {top: 1440,right: 1440,bottom: 1440,left: 1440,header: 720,footer: 720,gutter: 0,},};const defaultOptions = {containerId: 'docx-container',chartClassName: 'docx-chart',pageBreakClassName: 'page-break',pageHeaderId: 'page-header',};const pageBreakReplaceSymbol = '<div class="page-break-div"></div>';const finalOptions = _.merge(defaultOptions, options);const { containerId, pageBreakClassName, pageHeaderId } = finalOptions;// 创建一个临时的 div 来解析 HTMLconst tempDiv = document.createElement('div');tempDiv.innerHTML = originalHtml;// 处理页眉const pageHeaderElem = tempDiv.querySelector(`#${pageHeaderId}`);const pageHeaderHtml = pageHeaderElem?.innerHTML || '';pageHeaderElem?.remove();// 处理图片尺寸const images = tempDiv.querySelectorAll('img');images.forEach((img) => {const originalWidth = Number(img.getAttribute('width')?.replace('px', '') || 0);const originalHeight = Number(img.getAttribute('height')?.replace('px', '') || 0);const docxWidth = originalWidth * 0.73;img.setAttribute('width', docxWidth.toString());img.setAttribute('height', (docxWidth * (originalHeight / originalWidth)).toString());});// 处理分页符const pageBreaks = tempDiv.querySelectorAll(`.${pageBreakClassName}`);pageBreaks.forEach((elem) => {elem.innerHTML = pageBreakReplaceSymbol;});// 获取最终的 HTMLconst container = tempDiv.querySelector(`#${containerId}`);const html = container?.innerHTML || '';const pageBreak = "<span><br clear=all style='page-break-before:always'></span>";const finalHtml = _.replace(_.replace(docxHtml, '{{pageHeaderHtml}}', pageHeaderHtml),'{{docxHtml}}',_.replace(html, pageBreakReplaceSymbol, pageBreak));const blob = HtmlDocx.asBlob(finalHtml, _.defaultsDeep(options.document || {}, defaultDocument));return blob;
};export default htmlToDocxGenerate;

pageHtml.ts。纯样式,视情况修改,因为我用了富文本,所以还引入了quillCoreCss和quillSnowCss,不需要的可不加。

'use strict';
import quillCoreCss from './quill-core-css';
import quillSnowCss from './quill-snow-css';const pageHtml = `
<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><style>${quillCoreCss}${quillSnowCss}<!--p.MsoHeader, li.MsoHeader, div.MsoHeader{margin:0in;margin-top:.0001pt;mso-pagination:widow-orphan;tab-stops:center 3.0in right 6.0in;}@page Section1{mso-header:h1;mso-paper-source:0;}body div.Section1{page:Section1;<!-- padding: 0; --><!-- margin-top: 0; --><!-- margin-bottom: 0; -->}#h1 {<!-- margin-left: 100in; -->}-->html, body, div {margin: auto;padding: 0;font-size: 14px;color: #000;font-family: SimSun, Calibri, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;}.Section1 {max-width: 616px;margin-top: 24px;margin-bottom: 24px;padding: 0.2in 0.7in 0.7in 0.7in;background-color: #fff;}.Section1 h2,.Section1 h3 {color: #31487f;/* 由于文字字体不一样大,不能用em,直接用14*14 *//* padding-left: 196px;  */}.Section1 table th,.Section1 table td {padding: 3px;}p {line-height: 28px;text-indent: 24px;text-align:justify;text-justify:inter-word;}h1,h1 .ql-editor p {font-size: 24px !important;}h2,h2 .ql-editor p,h2 span {font-size: 20px !important;}h3,h3 .ql-editor p,h3 span {font-size: 18px !important;}h4 .ql-editor p,h4 span {font-size: 16px !important;}h4 {font-size: 14px !important;}.MsoHeader td {padding: 8px 0;}.docx-hidden {display: none;}</style>
</head>
<body><div class="Section1"><div style="mso-element:header" id="h1" ><span class="MsoHeader">{{pageHeaderHtml}}</span></div>{{docxHtml}}</div></body>
</html>
`;export default pageHtml;

相关文章:

  • 圈奶牛--二维凸包
  • HarmonyOs开发之———使用HTTP访问网络资源
  • 【Vue 3 + Vue Router 4】如何正确重置路由实例(resetRouter)——避免“VueRouter is not defined”错误
  • 前端面试每日三题 - Day 34
  • 【SSL部署与优化​】​​TLS 1.3的核心改进与性能优化​​
  • 模态参数识别中的特征实现算法
  • 嵌入式自学第二十一天(5.14)
  • 如何利用大模型对文章进行分段,提高向量搜索的准确性?
  • PyTorch 的自动微分和动态计算图
  • 信息化项目绩效管理办法V5.0
  • Seed1.5-VL:高效通用的视觉-语言基础模型
  • 基于 TensorFlow 框架的联邦学习可穿戴设备健康数据个性化健康管理平台研究
  • 单片机-STM32部分:14、SPI
  • 【计算机视觉】OpenCV实战项目:Face-Mask-Detection 项目深度解析:基于深度学习的口罩检测系统
  • 自然语言处理入门级项目——文本分类
  • MQTT 在Spring Boot 中的使用
  • Oracle — PL-SQL
  • 使用深度学习预训练模型检测物体
  • lesson01-PyTorch初见(理论+代码实战)
  • 在线黑白图像转换:简单却强大的视觉表达工具
  • 普京批准俄方与乌克兰谈判代表团人员名单
  • 山东:小伙为救同学耽误考试属实,启用副题安排考试
  • 乌总统:若与普京会谈,全面停火和交换战俘是主要议题
  • 习近平会见哥伦比亚总统佩特罗
  • 哈佛新论文揭示 Transformer 模型与人脑“同步纠结”全过程!AI也会犹豫、反悔?
  • 历史地理学者成一农重回母校北京大学,担任历史系教授