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;