You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
12 KiB
TypeScript

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<string>((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<ExcelJS.Image | null> => {
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),
};
};