在开发需求中,需要生成报告并导出,本文是前端利用docxtemplater实现替换模板文件并导出文档功能。
Docxtemplater是一个强大的库,可以帮助我们在前端生成Word文档。以下是如何在React项目中使用Docxtemplater导出Word文档的步骤。
1.安装所需的依赖
npm install docxtemplater pizzip docxtemplater-image-module-free
docxtemplater:这个插件可以通过预先写好的word,excel等文件模板生成对应带数据的文件;
pizzip:这个插件用来创建,读取或编辑.zip的文件
docxtemplater-image-module-free:需要导出图片的话需要这个插件
2.创建模板文件
在项目的public目录下创建一个templater.docx模板文件
然后在模板文件中使用模板语法,对需要替换的内容进行编写,例如:
其中的语法:
{%img} 图片
{#list}{/list} 循环、if判断
{#list}{/list}{^list}{/list} if else
{str} 文字
完整代码:
const docxData = {
"projectName": project.projectName,
"projectType": "",
"index": [],
"files": [],
"MetalCaseTemplateDesignList": [],
"CompositeCaseTemplateDesignList": [],
"CommonNozzleTemplateDesignList": [],
"FlexibleNozzleTemplateDesignList": [],
"TemplateOverallList": [],
"GrainTemplateDesignList": [],
"IgniterTemplateDesignList": [],
"BallisticMCList": [],
imageUrls: []
};
const getData = async () => {
const projectId = props.projectId;
const token = localStorage.getItem('access_token');
const url = `${baseHost}9002/project/manager/query/${projectId}`;
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
}
})
const data = await response.json();
const projectConfigs = data.data.projectConfigs;
const step2Configs = projectConfigs.filter((item: any) => item.configType === 'step2');
console.log("step2Configs:", step2Configs);
const resultValues = {
case: '',
nozzle: '',
grain: ''
};
step2Configs.forEach((item: any) => {
if (item.configKey.endsWith('-case')) {
resultValues.case = item.configValue.replace("设计", "");
} else if (item.configKey.endsWith('-nozzle')) {
resultValues.nozzle = item.configValue.replace("设计工具", "");
} else if (item.configKey.endsWith('-grain')) {
resultValues.grain = item.configValue;
}
});
const projectType = `${resultValues.case}${resultValues.nozzle}${resultValues.grain}发动机`;
docxData.projectType = projectType;
const step1Configs = projectConfigs.filter((item: any) => item.configType === 'step1');
step1Configs.forEach((item: any) => {
const configKey = item.configKey;
const configValue = item.configValue;
const match = configKey.match(/^(.*?)\((.*?)\)$/); //匹配configKey参数和单位
if (match) {
const param = match[1].trim();
const unit = `(${match[2].trim()})`;
const value = configValue;
docxData.index.push({
param,
unit,
value,
});
} else {
docxData.index.push({
param: configKey,
unit: '',
value: configValue
});
}
});
try {
const res = await fetch(`${baseHost}9002/project/manager/project/node/cal/getAll?projectId=${projectId}`)
const resData = await res.json();
docxData.files = resData.data.map((item: any) => {
const parsedFileContent = JSON.parse(item.fileContent);
if (item.fileName === 'node_metal-case_template-design') {
docxData.MetalCaseTemplateDesignList = parsedFileContent.request.paramList;
}
// if (item.fileName === 'node_srm-ballistic-mc') {
// const mcList = parsedFileContent.request.propMap;
// docxData.BallisticMCList = mcList.map((key, value) => {
// const param = key;
// const unit = "";
//
// return{
// param,
// unit,
// value
// }
// })
// }
return {
fileName: item.fileName,
fileContent: parsedFileContent
};
});
// console.log("获取结果json数据: " + JSON.stringify(docxData.files));
} catch (error) {
console.error('获取数据时发生错误:', error);
}
} catch (error) {
console.error('获取数据时发生错误:', error);
}
//获取图片列表
try {
const response = await fetch(`${baseHost}9002/project/manager/project/node/img/list?projectId=${projectId}`)
const responseData = await response.json();
docxData.imageUrls = responseData.data;
console.log("获取图片列表:" + docxData.imageUrls);
} catch (error) {
console.log("获取图片失败!", error);
}
}
//导出结果文档
const exportDoc = async () => {
try {
await getData()
} catch (error) {
console.error('获取数据时发生错误:', error);
}
try {
const response = await fetch('/template.docx'); // 替换为你的模板文件路径
const content = await response.arrayBuffer();
const zip = new PizZip(content);
// 动态生成模板数据对象
const templateData = {
projectName: docxData.projectName,
projectType: docxData.projectType,
index: docxData.index,
MetalCaseTemplateDesignList: docxData.MetalCaseTemplateDesignList,
CompositeCaseTemplateDesignList: docxData.CompositeCaseTemplateDesignList,
CommonNozzleTemplateDesignList: docxData.CommonNozzleTemplateDesignList,
FlexibleNozzleTemplateDesignList: docxData.FlexibleNozzleTemplateDesignList,
TemplateOverallList: docxData.TemplateOverallList,
GrainTemplateDesignList: docxData.GrainTemplateDesignList,
IgniterTemplateDesignList: docxData.IgniterTemplateDesignList,
BallisticMCList: docxData.BallisticMCList,
OverallWizardDesign: false,
TemplateOverall: false,
GrainWizardDesign: false,
GrainTemplateDesign: false,
MetalCaseWizardDesign: false,
MetalCaseTemplateDesign: false,
MetalCaseStrengthCheck: false,
InsulationWizardDesign: false,
CompositeCaseWizardDesign: false,
CompositeCaseTemplateDesign: false,
CompositeCaseStrengthCheck: false,
CommonNozzleWizardDesign: false,
CommonNozzleTemplateDesign: false,
FlexibleNozzleWizardDesign: false,
FlexibleNozzleTemplateDesign: false,
CircleIgniterWizardDesign: false,
TubeIgniterWizardDesign: false,
RocketIgniterWizardDesign: false,
IgniterPowerSphereDesign: false,
IgniterPowerRingDesign: false,
IgniterPowerTabletDesign: false,
IgniterPowerStarDesign: false,
IgniterPowerWheelDesign: false,
DualGrainBallisticZero: false,
SingleGrainBallisticZero: false,
SingleGrainBallisticOne: false,
DualPulseBallisticZero: false,
InsulationThickness: false,
IspCalculate: false,
BallisticMC: false,
BurnRateRecognize: false,
NozzleErosionRecognize: false,
ExplodePc: false,
ScrewDesign: false,
FlangeDesign: false,
SealingDesign: false,
CommonTools: false,
FuselageDesign: false,
nodeSrmCommonToolsPc: false,
nodeSrmCommonToolsCd: false,
nodeSrmCommonToolsAb: false,
nodeSrmCommonToolsRt: false,
nodeSrmCommonToolsMb: false,
nodeSrmCommonToolsPa: false,
nodeSrmCommonToolsPe: false,
nodeSrmCommonToolsIsp: false,
nodeSrmCommonToolsF: false,
nodeSrmCommonToolsR: false,
nodeSrmCommonToolsEps: false,
DualGrainBallisticZeroPImage: null,
SingleGrainBallisticZeroPImage: null,
SingleGrainBallisticOnePImage: null,
DualPulseBallisticZeroPImage: null,
MetalCaseWizardDesignModel: null,
CompositeCaseWizardDesignModel: null,
InsulationWizardDesignModel: null,
GrainWizardDesignAb: null,
GrainTemplateDesignModel: null,
CircleIgniterWizardDesignModel: null,
TubeIgniterWizardDesignModel: null,
RocketIgniterWizardDesignModel: null,
IgniterPowerSphereDesignAbImage: null,
IgniterPowerSphereDesignQlistImage: null,
IgniterPowerRingDesignAbImage: null,
IgniterPowerRingDesignQlistImage: null,
IgniterPowerTabletDesignAbImage: null,
IgniterPowerTabletDesignQlistImage: null,
IgniterPowerStarDesignAbImage: null,
IgniterPowerStarDesignQlistImage: null,
IgniterPowerWheelDesignAbImage: null,
IgniterPowerWheelDesignQlistImage: null,
CommonNozzleWizardDesignShapeImage: null,
CommonNozzleWizardDesignFlowImage: null,
CommonNozzleWizardDesignModelImage: null,
FlexibleNozzleWizardDesignShapeImage: null,
FlexibleNozzleWizardDesignFlowImage: null,
FlexibleNozzleWizardDesignModel: null,
BurnRateRecognizePImage: null,
NozzleErosionRecognizeDtImage: null,
FuselageDesignModel: null,
BallisticMCP: null,
BallisticMCP2: null,
};
//遍历 data.data 数组,提取每个文件的内容并生成对应的占位符数据
for (const item of docxData.files) {
const { fileName, fileContent } = item;
const prefix = fileName.replace(/\.json$/, '').replace(/[^a-zA-Z0-9]/g, '_'); // 生成前缀
if (item.fileName === 'node_srm-interactive-overall') {
templateData.OverallWizardDesign = true;
}
if (item.fileName === 'node_srm-template-overall') {
templateData.TemplateOverall = true;
}
if (item.fileName === 'node_grain_interactive-design') {
templateData.GrainWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_grain_interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.GrainWizardDesignAb = imageBase[0];
}
if (item.fileName === 'node_grain_template-design') {
templateData.GrainTemplateDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_grain_template-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.GrainTemplateDesignModel = imageBase[0];
}
if (item.fileName === 'node_metal-case_interactive-design') {
templateData.MetalCaseWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_metal-case_interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.MetalCaseWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_metal-case_template-design') {
templateData.MetalCaseTemplateDesign = true;
}
if (item.fileName === 'node_metal-case_strength-check') {
templateData.MetalCaseStrengthCheck = true;
}
if (item.fileName === 'node_insulation_wizard_design') {
templateData.InsulationWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_insulation_wizard_design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.InsulationWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_composite-case_interactive-design') {
templateData.CompositeCaseWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_composite-case_interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.CompositeCaseWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_composite-case_template-design') {
templateData.CompositeCaseTemplateDesign = true;
}
if (item.fileName === 'node_composite-case_strength-check') {
templateData.CompositeCaseStrengthCheck = true;
}
if (item.fileName === 'node_common-nozzle-interactive-design') {
templateData.CommonNozzleWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_common-nozzle-interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.CommonNozzleWizardDesignShapeImage = imageBase[2];
templateData.CommonNozzleWizardDesignFlowImage = imageBase[0];
templateData.CommonNozzleWizardDesignModelImage = imageBase[1];
}
if (item.fileName === 'node_common-nozzle-template-design') {
templateData.CommonNozzleTemplateDesign = true;
}
if (item.fileName === 'node_composite-case_strength-check') {
templateData.CompositeCaseStrengthCheck = true;
}
if (item.fileName === 'node_flexible-nozzle-interactive-design') {
templateData.FlexibleNozzleWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_flexible-nozzle-interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.FlexibleNozzleWizardDesignShapeImage = imageBase[2];
templateData.FlexibleNozzleWizardDesignFlowImage = imageBase[0];
templateData.FlexibleNozzleWizardDesignModel = imageBase[1];
}
if (item.fileName === 'node_flexible-nozzle-template-design') {
templateData.FlexibleNozzleTemplateDesign = true;
}
if (item.fileName === 'node_circle-igniter-interactive-design') {
templateData.CircleIgniterWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_circle-igniter-interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.CircleIgniterWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_tube-igniter-interactive-design') {
templateData.TubeIgniterWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_tube-igniter-interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.TubeIgniterWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_rocket-igniter-interactive-design') {
templateData.RocketIgniterWizardDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_rocket-igniter-interactive-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.RocketIgniterWizardDesignModel = imageBase[0];
}
if (item.fileName === 'node_igniter-powder-sphere-design') {
templateData.IgniterPowerSphereDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_igniter-powder-sphere-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.IgniterPowerSphereDesignAbImage = imageBase[0];
templateData.IgniterPowerSphereDesignQlistImage = imageBase[1];
}
if (item.fileName === 'node_igniter-powder-ring-design') {
templateData.IgniterPowerRingDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_igniter-powder-ring-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.IgniterPowerRingDesignAbImage = imageBase[0];
templateData.IgniterPowerRingDesignQlistImage = imageBase[1];
}
if (item.fileName === 'node_igniter-powder-tablet-design') {
templateData.IgniterPowerTabletDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_igniter-powder-tablet-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.IgniterPowerTabletDesignAbImage = imageBase[0];
templateData.IgniterPowerTabletDesignQlistImage = imageBase[1];
}
if (item.fileName === 'node_igniter-powder-star-design') {
templateData.IgniterPowerStarDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_igniter-powder-star-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.IgniterPowerStarDesignAbImage = imageBase[0];
templateData.IgniterPowerStarDesignQlistImage = imageBase[1];
}
if (item.fileName === 'node_igniter-powder-wheel-design') {
templateData.IgniterPowerWheelDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_igniter-powder-wheel-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.IgniterPowerWheelDesignAbImage = imageBase[0];
templateData.IgniterPowerWheelDesignQlistImage = imageBase[1];
}
if (item.fileName === 'node_single-grain_ballistic-one') {
templateData.SingleGrainBallisticOne = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_single-grain_ballistic-one'))
const imageBase = await fetchImageAsBase64(urls);
templateData.SingleGrainBallisticOnePImage = imageBase[0];
}
if (item.fileName === 'node_single-grain_ballistic-zero') {
templateData.SingleGrainBallisticZero = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_single-grain_ballistic-zero'))
const imageBase = await fetchImageAsBase64(urls);
templateData.SingleGrainBallisticZeroPImage = imageBase[0];
}
if (item.fileName === 'node_dual-grain_ballistic-zero') {
templateData.DualGrainBallisticZero = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_dual-grain_ballistic-zero'))
const imageBase = await fetchImageAsBase64(urls);
templateData.DualGrainBallisticZeroPImage = imageBase[0];
}
if (item.fileName === 'node_dual-pulse_ballistic-zero') {
templateData.DualPulseBallisticZero = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_dual-pulse_ballistic-zero'))
const imageBase = await fetchImageAsBase64(urls);
templateData.DualPulseBallisticZeroPImage = imageBase[0];
}
if (item.fileName === 'node_srm-insulation-thickness') {
templateData.InsulationThickness = true;
}
if (item.fileName === 'node_srm-isp-calculate') {
templateData.IspCalculate = true;
}
if (item.fileName === 'node_srm-ballistic-mc') {
templateData.BallisticMC = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_srm-ballistic-mc'))
const imageBase = await fetchImageAsBase64(urls);
templateData.BallisticMCP = imageBase[0];
templateData.BallisticMCP2 = imageBase[1];
}
if (item.fileName === 'node_srm-burn-rate-recognize') {
templateData.BurnRateRecognize = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_srm-burn-rate-recognize'))
const imageBase = await fetchImageAsBase64(urls);
templateData.BurnRateRecognizePImage = imageBase[0];
}
if (item.fileName === 'node_srm-nozzle-erosion-recognize') {
templateData.NozzleErosionRecognize = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_srm-nozzle-erosion-recognize'))
const imageBase = await fetchImageAsBase64(urls);
templateData.NozzleErosionRecognizeDtImage = imageBase[0];
}
if (item.fileName === 'node_srm-explode-pc') {
templateData.ExplodePc = true;
}
if (item.fileName === 'node_srm-screw-design') {
templateData.ScrewDesign = true;
}
if (item.fileName === 'node_srm-flange-design') {
templateData.FlangeDesign = true;
}
if (item.fileName === 'node_srm-sealing-design') {
templateData.SealingDesign = true;
}
if (item.fileName === 'node_srm-common-tools') {
templateData.CommonTools = true;
}
if (item.fileName === 'node_srm-fuselage-design') {
templateData.FuselageDesign = true;
const urls = docxData.imageUrls.filter(url => url.includes('node_srm-fuselage-design'))
const imageBase = await fetchImageAsBase64(urls);
templateData.FuselageDesignModel = imageBase[0];
}
Object.keys(fileContent.request).forEach(key => {
templateData[`${prefix}_${key}`] = fileContent.request[key];
});
// 添加响应参数
Object.keys(fileContent.response).forEach(key => {
templateData[`${prefix}_${key}`] = fileContent.response[key];
});
};
// 手动解析模板文件中的占位符
const templatePlaceholders = extractPlaceholdersFromTemplate(content);
// 确保模板中定义的所有占位符在 templateData 中都有对应的键值对
templatePlaceholders.forEach(placeholder => {
if (!templateData.hasOwnProperty(placeholder)) {
templateData[placeholder] = ''; // 如果某个占位符在 templateData 中不存在,则赋值为空字符串
}
});
// function getImage(tagValue, tagName) {
// return templateData[tagName]; // 直接返回模板数据中的属性
// }
// // 加载图片模块
// const imageModule = new ImageModule({
// centered: true, // 是否居中对齐图片
// imageSource: 'base64', // 使用 Base64 格式
// getImage: getImage,
// getSize: (imgBuffer, tagValue) => {
// // 处理 tagValue 为 undefined 或空的情况
// if (!tagValue) {
// console.error("图片对象未定义,使用默认尺寸");
// return [400, 300]; // 返回默认尺寸
// }
// return [tagValue.width, tagValue.height];
// },
// });
const base64Regex =
/^(?:data:)?image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
const validBase64 =
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
function base64Parser(tagValue) {
if (
typeof tagValue !== "string" ||
!base64Regex.test(tagValue)
) {
return false;
}
const stringBase64 = tagValue.replace(base64Regex, "");
if (!validBase64.test(stringBase64)) {
throw new Error(
"Error parsing base64 data, your data contains invalid characters"
);
}
// For nodejs, return a Buffer
if (typeof Buffer !== "undefined" && Buffer.from) {
return Buffer.from(stringBase64, "base64");
}
// For browsers, return a string (of binary content) :
const binaryString = window.atob(stringBase64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
const ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes.buffer;
}
const imageOptions = {
getImage(tagValue) {
return base64Parser(tagValue);
},
getSize(img, tagValue, tagName, context) {
return [600, 420];
},
};
// 初始化 Docxtemplater 并添加图片模块
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
modules: [new ImageModule(imageOptions)],
});
try {
// 渲染模板
doc.render(templateData);
} catch (error) {
console.error('渲染模板时出错:', error);
return;
}
// 获取渲染后的文件内容
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
// 创建一个链接元素用于下载
const link = document.createElement('a');
link.href = URL.createObjectURL(out);
link.download = '发动机方案设计报告.docx'; // 设置下载文件名
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch(error) {
console.error('加载模板文件时出错:', error);
};
}
// 获取所有图片的 Blob 数据
async function fetchImageAsBase64(imageUrls) {
const imageBase64 = [];
for (const url of imageUrls) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('图片获取失败');
}
const blob = await response.blob(); // 使用 blob 获取图片数据
const base64 = await blobToBase64(blob); // 转换为 Base64
imageBase64.push(base64);
// console.log("base64: " + base64);
// // 动态获取图片尺寸
// const size = await getImageSize(blob); // 需要实现 getImageSize 函数
// imageBase64.push({
// src: base64,
// width: size.width || 400,
// height: size.height || 300,
// });
} catch (error) {
console.error(`获取图片失败:${url}`, error);
imageBase64.push(null); // 保留占位符,方便后续处理
}
}
return imageBase64;
}
//
// function getImageSize(blob) {
// return new Promise((resolve) => {
// const img = new Image();
// img.onload = () => {
// resolve({ width: img.width, height: img.height });
// };
// img.src = URL.createObjectURL(blob);
// });
// }
// 将 Blob 转换为 Base64 的辅助函数
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
//手动解析模板文件中的占位符
function extractPlaceholdersFromTemplate(content) {
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true
});
// 获取模板文件中的所有 XML 文件
const xmlFiles = doc.getZip().file(/\.xml$/);
const placeholders = new Set();
xmlFiles.forEach(file => {
let xmlContent;
// 检查文件内容的类型
if (typeof file._data === 'string') {
// 如果是字符串,直接使用
xmlContent = file._data;
} else if (file._data instanceof Uint8Array) {
// 如果是 Uint8Array,使用 TextDecoder 转换为字符串
const decoder = new TextDecoder('utf-8');
xmlContent = decoder.decode(file._data);
} else {
// 如果文件内容类型未知,跳过该文件
console.warn('无法解析文件内容:', file.name);
return;
}
// 使用正则表达式提取占位符
const matches = xmlContent.match(/{[^}]+}/g);
if (matches) {
matches.forEach(match => {
const placeholder = match.slice(1, -1); // 去掉大括号
placeholders.add(placeholder);
});
}
});
return Array.from(placeholders);
}
最后通过创建a标签下载文档,最终文档展示: