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

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;
http://www.xdnf.cn/news/6100.html

相关文章:

  • 没经过我同意,flink window就把数据存到state里的了?
  • Java 大视界——Java 大数据在智慧交通智能停车诱导系统中的数据融合与实时更新
  • 命令行快速上传文件到SFTP服务器(附参考示例)
  • 灰度图像和RGB图像在数据大小和编码处理方式差别
  • lanqiaoOJ 652:一步之遥 ← 扩展欧几里得定理
  • ESP32-S3R8 使能PSRAM内存
  • 【嵌入式笔记】Modbus TCP
  • 鬼泣:蓄力攻击总结
  • 《AI大模型应知应会100篇》第63篇:AutoGPT 与 BabyAGI:自主代理框架探索
  • 计算机网络:怎么理解调制解调器的数字调制技术?
  • 《AI驱动的智能推荐系统:原理、应用与未来》
  • Java面试八股Spring篇(4500字)
  • 某某霸翻译逆向分析[JS逆向]
  • 计算机系统概述——了解冯诺伊曼 CPI相关公式
  • 基于Qt的OSG三维建模
  • 【Redis实战篇】秒杀优化
  • 使用 hover-class 实现触摸态效果 - uni-app 教程
  • 数字信号处理-大实验1.2
  • 一文掌握六个空转数据库
  • 编译支持CUDA-aware的OpenMPI
  • 数字化转型 - 标准化
  • MySQL锁机制全面解析:从原理到实践的死锁防治指南
  • C++23 ranges::to:范围转换函数 (P1206R7)
  • LeRobot 框架的核心架构概念和组件(中)
  • 深度学习中的查全率与查准率:如何实现有效权衡
  • CS4334立体声D/A转换器:为高品质音频设计提供低成本的解决方案
  • 音频分类的学习
  • css设置文字两端对齐text-align:justify不起作用的解决方法
  • HTML应用指南:利用POST请求获取全国圆通快递服务网点位置信息
  • ​​金融合规革命:​​R²AIN SUITE 如何重塑银行业务智能​