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 来解析 HTML
const 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;
});
// 获取最终的 HTML
const 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;