import fs from "fs-extra"; import path from "path"; import PizZip from "pizzip"; import Docxtemplater from "docxtemplater"; import { fileURLToPath } from "url"; import ImageModule from "docxtemplater-image-module-free"; import axios from "axios"; import sizeOf from "image-size"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 读取本地或远程图片,返回 Buffer const getImageFromUrl = async (url) => { if (typeof url !== "string") { throw new TypeError(`图片 URL 应为字符串,但实际是 ${typeof url}`); } if (url.startsWith("http")) { const safeUrl = url.replace(/#/g, "%23"); // 处理 URL 中的 # const response = await axios.get(safeUrl, { responseType: "arraybuffer" }); const buffer = Buffer.from(response.data, "binary"); if (buffer.length < 1000) { console.warn(`⚠️ 图像可能无效或过小: ${url}`); } return buffer; } else { return fs.readFileSync(url); // 本地图片 } }; // 预加载所有图片为 Buffer const preloadAllImages = async (chartsImages) => { const allUrls = Object.values(chartsImages).flat(); // 扁平化为图片 URL 列表 const imageMap = {}; for (const url of allUrls) { if (typeof url !== "string" || !url.trim()) { console.warn("⚠️ 非法图片 URL:", url); continue; } try { const buffer = await getImageFromUrl(url); if (!buffer || buffer.length === 0) { console.warn("⚠️ 获取的图片 buffer 长度为 0:", url); continue; } imageMap[url] = buffer; } catch (e) { console.warn("⚠️ 加载图片失败:", url, e.message); } } return imageMap; }; // 生成 Word 文件 export const copyFileDocx = async (fieldInfo, chartsImages) => { try { const sourceFilePath = path.join( process.cwd(), "src", "public", "file", "副本XXX风电场可靠性和能效双提升数据分析报告(模板).docx" ); const targetFilePath = path.join( process.cwd(), "src", "public", "file", `${fieldInfo.companyName}${fieldInfo.fieldName}可靠性和能效双提升数据分析报告.docx` ); // 拷贝模板 await fs.copy(sourceFilePath, targetFilePath); const content = await fs.readFile(targetFilePath, "binary"); const zip = new PizZip(content); // 预加载所有图片 const imageBufferMap = await preloadAllImages(chartsImages); // 配置 image module const imageModule = new ImageModule({ centered: true, getImage: (tagValue) => { const buffer = imageBufferMap[tagValue]; if (!buffer) { throw new Error(`未找到图片 Buffer: ${tagValue}`); } return buffer; }, getSize: (imgBuffer) => { try { const dimensions = sizeOf(imgBuffer); const maxWidth = 500; const ratio = maxWidth / dimensions.width; return [maxWidth, dimensions.height * ratio]; } catch (e) { console.error("❌ 获取图片尺寸失败", e); console.error("buffer 长度:", imgBuffer.length); throw new Error("图片尺寸获取失败,可能是非法图片或 Buffer 损坏"); } }, }); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, modules: [imageModule], }); const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); // 构建模板图像字段对象 const imageTagData = {}; for (const [tag, urlList] of Object.entries(chartsImages)) { if (Array.isArray(urlList)) { imageTagData[tag] = urlList.map((url) => ({ image: url })); } else { console.warn(`⚠️ 无效的 urlList: ${tag}`, urlList); imageTagData[tag] = []; // 或 throw error } } console.log(chartsImages.rows, "chartsImages"); const renderData = { Year_now: year, Month_now: month, Province: fieldInfo.provinceName, Wind_farm: fieldInfo.fieldName, Wind_turbine: fieldInfo.engineMillTypes.join(","), Overview_of_the_Wind_Farm: `${fieldInfo.fieldName}位于${ fieldInfo.provinceName }${fieldInfo.cityName},海拔高度为${fieldInfo.elevationHeight} 米,经度${ fieldInfo.longitude }°,纬度${fieldInfo.latitude}°。风场总容量为${ fieldInfo.ratedCapacityNumber } MW,共安装${ fieldInfo.engineCount } 台风机,机型包括${fieldInfo.engineMillTypes?.join(",")}`, ...imageTagData, // 图像数据动态插入 rows: chartsImages.rows, windTurbineRows: chartsImages.windTurbineRows, faultRows: chartsImages.faultRows, yawErrorRows: chartsImages.yawErrorRows, }; // 渲染 Word 模板 doc.render(renderData); // 保存生成的文档 const buffer = doc.getZip().generate({ type: "nodebuffer" }); await fs.writeFile(targetFilePath, buffer); return { url: targetFilePath, message: `✅ 文档生成成功:${targetFilePath}`, }; } catch (error) { console.error("❌ 生成 Word 文档失败:", error); throw error; } };