import ExcelJS from "exceljs"; interface TrainReportData { train_id: string; arrive_at: string; leave_at: string | null; train_data: { carriage_number: string }[]; appearance_data: { carriage_number: string; carriage_type: string; occurrence_time: string; warning_type: string; fault_type: string; defect_image: string; }[]; } const formatDateTime = (dateTimeStr: string) => { const date = new Date(dateTimeStr); return date.toLocaleString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); }; // 使用您指定的图片URL const DEFAULT_IMAGE_URL = "https://img1.baidu.com/it/u=967792105,51180745&fm=253&fmt=auto?w=1200&h=800"; const convertImageToBase64 = async (url: string) => { try { const response = await fetch(url); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(blob); }); } catch (error) { console.error("Error loading image:", error); return null; } }; const getImageDimensions = ( url: string ): Promise<{ width: number; height: number }> => { return new Promise((resolve) => { const img = new Image(); const timer = setTimeout(() => { resolve({ width: 80, height: 60 }); // 默认尺寸 }, 3000); img.crossOrigin = "Anonymous"; img.onload = () => { clearTimeout(timer); resolve({ width: img.width, height: img.height }); }; img.onerror = () => { clearTimeout(timer); resolve({ width: 80, height: 60 }); }; img.src = url; }); }; // 下载图片并转换为ArrayBuffer const downloadImage = async (url: string): Promise => { try { const finalUrl = url || DEFAULT_IMAGE_URL; let response: Response; try { response = await fetch(finalUrl); if (!response.ok) throw new Error("直接下载失败"); } catch (directError) { // 尝试使用CORS代理 try { const proxyUrl = `https://corsproxy.io/?${encodeURIComponent( finalUrl )}`; response = await fetch(proxyUrl); if (!response.ok) throw new Error("代理下载失败"); } catch (proxyError) { console.error("图片下载失败:", proxyError); return null; } } const arrayBuffer = await response.arrayBuffer(); const extension = finalUrl.split(".").pop()?.toLowerCase() || "jpeg"; const { width, height } = await getImageDimensions(finalUrl); // 计算缩放比例(保持宽高比) const maxWidth = 80; const maxHeight = 60; const scale = Math.min(maxWidth / width, maxHeight / height, 1); return { buffer: arrayBuffer, // 直接使用ArrayBuffer extension: extension === "png" ? "png" : "jpeg", width: width * scale, height: height * scale, }; } catch (error) { console.error("图片处理失败:", error); return null; } }; // 像素转换为Excel的EMU单位 (Excel使用EMU作为度量单位) const pixelsToEMU = (pixels: number) => Math.round(pixels * 9525); export const exportTrainReport = async (data: TrainReportData) => { const workbook = new ExcelJS.Workbook(); workbook.creator = "Railway Inspection System"; workbook.created = new Date(); // 1. 创建"整车信息"工作表 (无表头行) const trainSheet = workbook.addWorksheet("整车信息"); trainSheet.getColumn(1).width = 20; trainSheet.getColumn(2).width = 30; // 直接添加数据行 const trainInfoData = [ { key: "车头编号", value: data.train_id }, { key: "进场时间", value: formatDateTime(data.arrive_at) }, { key: "出场时间", value: data.leave_at ? formatDateTime(data.leave_at) : "未出场", }, { key: "车厢列表", value: "" }, ]; trainInfoData.forEach((info, index) => { const row = index + 1; trainSheet.getCell(`A${row}`).value = info.key; trainSheet.getCell(`B${row}`).value = info.value; // 设置第一列加粗 trainSheet.getCell(`A${row}`).font = { bold: true }; // 设置第二列加粗(除车厢列表行) if (row < 4) { trainSheet.getCell(`B${row}`).font = { bold: true }; } }); // 添加车厢列表 const startRow = trainInfoData.length + 1; data.train_data.forEach((carriage, index) => { const row = startRow + index; trainSheet.getCell(`A${row}`).value = `车厢 ${index + 1}`; trainSheet.getCell(`B${row}`).value = carriage.carriage_number; }); // 2. 创建"车身缺陷记录"工作表 const defectSheet = workbook.addWorksheet("车身缺陷记录"); // 修改列定义 defectSheet.columns = [ { header: "车厢号", key: "carriage_number", width: 15 }, // { header: "车型", key: "carriage_type", width: 20 }, { header: "发生时间", key: "occurrence_time", width: 20 }, { header: "告警类型", key: "warning_type", width: 15 }, { header: "缺陷类型", key: "fault_type", width: 15 }, { header: "缺陷图片", key: "thumbnail", width: 15 }, // 缩略图列 // { header: "查看原图", key: "defect_image", width: 15 }, // 原图链接列 ]; // 设置表头样式 defectSheet.getRow(1).eachCell((cell) => { cell.font = { bold: true }; cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFDDEBF7" }, // 浅蓝色背景 }; cell.border = { top: { style: "thin" }, left: { style: "thin" }, bottom: { style: "thin" }, right: { style: "thin" }, }; }); // 设置所有行高(60像素≈45磅) defectSheet.properties.defaultRowHeight = 45; // 添加所有缺陷记录 for (let i = 0; i < data.appearance_data.length; i++) { const item = data.appearance_data[i]; const rowIndex = i + 2; // 从第二行开始(第一行是表头) // 添加文本数据 defectSheet.addRow({ carriage_number: item.carriage_number, carriage_type: item.carriage_type, occurrence_time: formatDateTime(item.occurrence_time), warning_type: item.warning_type, fault_type: item.fault_type, }); // 使用指定图片URL const imageUrl = item.defect_image || DEFAULT_IMAGE_URL; try { // 下载图片 // const image = await downloadImage(imageUrl); // console.log("图片下载成功", image); const base64Image = await convertImageToBase64(imageUrl); if (base64Image) { const imageId = workbook.addImage({ base64: base64Image, extension: "png", // Adjust extension if needed (jpeg, etc.) }); // TODO : 调整图片大小和位置 (根据需要调整) // // 计算缩放比例和显示尺寸 // const maxSize = 30; // 最大边长30像素 // const scale = Math.min(maxSize / image.width, maxSize / image.height); // const displayWidth = image.width * scale; // const displayHeight = image.height * scale; // // 列宽(像素)和行高(像素) // const colWidthPixels = 105; // 15字符 * 7 // const rowHeightPixels = 60; // 45磅 * (96/72) = 60像素 // // 计算偏移量(居中) // const offsetXPixels = (colWidthPixels - displayWidth) / 2; // const offsetYPixels = (rowHeightPixels - displayHeight) / 2; // // 转换为EMU // const offsetXEMU = pixelsToEMU(offsetXPixels); // const offsetYEMU = pixelsToEMU(offsetYPixels); // const widthEMU = pixelsToEMU(displayWidth); // const heightEMU = pixelsToEMU(displayHeight); defectSheet.addImage(imageId, { tl: { col: defectSheet.columns.length - 1, row: rowIndex - 1 }, // F列是第5列(0-based) // br: { col: 6, row: rowIndex }, ext: { width: 80, height: 45, }, editAs: "oneCell", }); } } catch (error) { console.error(`图片添加失败 (行 ${rowIndex}):`, error); const errorCell = defectSheet.getCell(`E${rowIndex}`); errorCell.value = "图片加载失败"; errorCell.font = { color: { argb: "FFFF0000" }, // 红色 italic: true, }; } } // 3. 生成Excel文件 const buffer = await workbook.xlsx.writeBuffer(); try { const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "车辆信息.xlsx"; link.click(); URL.revokeObjectURL(url); } catch (error) { console.error("导出 Excel 文件时出错:", error); } }; // ... formatDateTime 函数保持不变 ... export const useTrainSaveToExcel = (data) => { // 模拟数据 (实际应用中应通过API获取) const reportData = { train_id: "DF4-1234", arrive_at: "2023-06-15 08:30:00", leave_at: "2023-06-15 16:45:22", train_data: [ { carriage_number: "CK-1001" }, { carriage_number: "CK-1002" }, { carriage_number: "CK-1003" }, ], appearance_data: [ { carriage_number: "CK-1001", carriage_type: "C80型运煤敞车", occurrence_time: "2023-06-15 09:15:34", warning_type: "AI智能识别告警", fault_type: "车体变形", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1002", carriage_type: "P70型棚车", occurrence_time: "2023-06-15 10:22:18", warning_type: "人工巡检报告", fault_type: "油漆剥落", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1001", carriage_type: "C80型运煤敞车", occurrence_time: "2023-06-15 09:15:34", warning_type: "AI智能识别告警", fault_type: "车体变形", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1002", carriage_type: "P70型棚车", occurrence_time: "2023-06-15 10:22:18", warning_type: "人工巡检报告", fault_type: "油漆剥落", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1001", carriage_type: "C80型运煤敞车", occurrence_time: "2023-06-15 09:15:34", warning_type: "AI智能识别告警", fault_type: "车体变形", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1002", carriage_type: "P70型棚车", occurrence_time: "2023-06-15 10:22:18", warning_type: "人工巡检报告", fault_type: "油漆剥落", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1001", carriage_type: "C80型运煤敞车", occurrence_time: "2023-06-15 09:15:34", warning_type: "AI智能识别告警", fault_type: "车体变形", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1002", carriage_type: "P70型棚车", occurrence_time: "2023-06-15 10:22:18", warning_type: "人工巡检报告", fault_type: "油漆剥落", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1001", carriage_type: "C80型运煤敞车", occurrence_time: "2023-06-15 09:15:34", warning_type: "AI智能识别告警", fault_type: "车体变形", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, { carriage_number: "CK-1002", carriage_type: "P70型棚车", occurrence_time: "2023-06-15 10:22:18", warning_type: "人工巡检报告", fault_type: "油漆剥落", defect_image: "http://192.168.10.14:8123/sftp/2025-07-01/20250701_131010.jpg", }, ], }; return { saveToExcel: () => exportTrainReport(reportData), }; };