import type { SheetConfig } from '@/client/member/components/ExcelToJson/types'; import * as XLSX from 'xlsx'; import { performance } from 'node:perf_hooks' // Excel数据行类型 export interface ExcelRow { [key: string]: string | number | null | undefined; _id?: string; tableIndex?: number; } // Excel原始数据类型 type ExcelJsonData = Array>; /** * 服务器端Excel解析器 * 从useExcelParser.ts中抽取的核心功能,移除了React和UI相关代码 */ export class ExcelParser { /** * 处理数据用于导出 * @param data 原始数据行 * @param sheetConfig 工作表配置 * @param tableIndex 表格索引 * @returns 处理后的数据 */ processDataForExport( data: ExcelRow[], sheetConfig: SheetConfig, tableIndex: number = 0 ): ExcelRow[] { console.log(`[ExcelParser] 开始处理导出数据,数据行数: ${data.length},表格索引: ${tableIndex}`); // 记录exportFields和requiredFields配置 console.log(`[ExcelParser] 导出字段配置: ${sheetConfig.exportFields.length} 个字段,必需字段: ${sheetConfig.requiredFields.length} 个`); const result = data .map((row, index) => { const processedRow: ExcelRow = {}; processedRow._id = `table-${tableIndex}-row-${index}`; // 如果exportFields为空,保留所有原始字段 if (sheetConfig.exportFields.length === 0) { console.log(`[ExcelParser] 导出字段为空,保留行 ${index} 的所有原始字段`); Object.keys(row).forEach(field => { processedRow[field] = row[field] ?? null; }); } else { // 按照exportFields配置处理 sheetConfig.exportFields.forEach(field => { const mappedField = sheetConfig.fieldMappings[field] || field; processedRow[mappedField] = row[field] ?? null; }); } return processedRow; }) .filter(row => { // 如果没有设置必需字段或exportFields为空,则不过滤 if (sheetConfig.requiredFields.length === 0 || sheetConfig.exportFields.length === 0) { return true; } // 检查必需字段是否有值 const isValid = sheetConfig.requiredFields.every(field => { const value = row[sheetConfig.fieldMappings[field] || field]; return value !== null && value !== undefined && value !== ''; }); if (!isValid) { console.log(`[ExcelParser] 过滤掉不满足必需字段的行: ${row._id}`); } return isValid; }); console.log(`[ExcelParser] 处理后的导出数据行数: ${result.length}`); return result; } /** * 处理所有字段数据 * @param data 原始数据行 * @param tableIndex 表格索引 * @returns 处理后的数据 */ processAllFieldsData( data: ExcelRow[], tableIndex: number = 0 ): ExcelRow[] { console.log(`[ExcelParser] 处理所有字段数据,数据行数: ${data.length},表格索引: ${tableIndex}`); const result = data.map((row, index) => ({ ...row, _id: `table-${tableIndex}-row-${index}` })); return result; } /** * 获取每个工作表中的所有可用字段 * @param data 工作表数据 * @returns 按工作表分类的可用字段 */ getAllAvailableFields(data: { [key: string]: ExcelRow[] }): { [key: string]: string[] } { console.log(`[ExcelParser] 获取所有可用字段,工作表数: ${Object.keys(data).length}`); const result: { [key: string]: string[] } = {}; // 遍历每个工作表的数据 Object.keys(data).forEach(sheetName => { const sheetData = data[sheetName]; if (sheetData.length === 0) { console.log(`[ExcelParser] 工作表 ${sheetName} 没有数据,返回空字段列表`); result[sheetName] = []; return; } // 获取该工作表中的所有唯一字段名 const uniqueFields = new Set(); sheetData.forEach(row => { Object.keys(row).forEach(key => { // 排除特殊字段和空值 if (key !== '_id' && key !== 'tableIndex') { uniqueFields.add(key); } }); }); // 转换为数组 result[sheetName] = Array.from(uniqueFields); console.log(`[ExcelParser] 工作表 ${sheetName} 的可用字段数: ${result[sheetName].length}`); }); return result; } /** * 解析单个表格数据 */ parseSingleTable( json: ExcelJsonData, headers: string[], startRow: number, endMarker: string, orderNumberRow: number, orderNumberCol: number, productNameRow?: number, productNameCol?: number ): { data: ExcelRow[]; endIndex: number } { console.log(`[ExcelParser] 开始解析单个表格,起始行: ${startRow},结束标记: ${endMarker || '无'}`); console.log(`[ExcelParser] 表头行数: ${headers.length},订单号位置: 行=${orderNumberRow}, 列=${orderNumberCol}`); if (productNameRow !== undefined && productNameCol !== undefined) { console.log(`[ExcelParser] 产品名称位置: 行=${productNameRow}, 列=${productNameCol}`); } const data: ExcelRow[] = []; let endIndex = json.length - 1; let orderNumber = ''; let productName = ''; // 获取订单号 if (orderNumberRow >= 0 && orderNumberRow < json.length && orderNumberCol >= 0 && orderNumberCol < (json[orderNumberRow]?.length || 0)) { orderNumber = String(json[orderNumberRow][orderNumberCol] || ''); console.log(`[ExcelParser] 获取到订单号: ${orderNumber}`); } else { console.log(`[ExcelParser] 未获取到订单号,参数可能无效`); } // 获取产品名称(如果配置了产品名称行列号) if (productNameRow !== undefined && productNameCol !== undefined && productNameRow >= 0 && productNameRow < json.length && productNameCol >= 0 && productNameCol < (json[productNameRow]?.length || 0)) { productName = String(json[productNameRow][productNameCol] || ''); console.log(`[ExcelParser] 获取到产品名称: ${productName}`); } else if (productNameRow !== undefined || productNameCol !== undefined) { console.log(`[ExcelParser] 未获取到产品名称,参数可能无效`); } // 找到结束标记行(如果有) if (endMarker && endMarker.trim() !== '') { console.log(`[ExcelParser] 搜索结束标记: ${endMarker}`); let found = false; for (let i = startRow; i < json.length; i++) { if (!json[i]) continue; const firstCell = json[i][0]; if (firstCell && String(firstCell).includes(endMarker)) { endIndex = i - 1; found = true; console.log(`[ExcelParser] 找到结束标记,结束行索引: ${endIndex}`); break; } } if (!found) { console.log(`[ExcelParser] 未找到结束标记,将使用表格最后一行`); } } // 从起始行到结束行,提取数据并映射到列标题 console.log(`[ExcelParser] 开始解析数据行,范围: ${startRow} - ${endIndex}`); let validRowCount = 0; for (let i = startRow; i <= endIndex; i++) { if (!json[i] || json[i].length === 0) { console.log(`[ExcelParser] 跳过空行: ${i}`); continue; } const row: ExcelRow = {}; // 检查行是否有效(非空行) let hasValidData = false; // 映射列标题和值 headers.forEach((header, columnIndex) => { if (header) { const value = json[i][columnIndex]; row[header] = value ?? null; // 如果有任何一个单元格有值,则认为这行有效 if (value !== null && value !== undefined && value !== '') { hasValidData = true; } } }); // 添加订单号 if (orderNumber) { row['订单号'] = orderNumber; } // 添加产品名称 if (productName) { row['产品名称'] = productName; } // 只添加有效的行数据 if (hasValidData) { data.push(row); validRowCount++; } else { console.log(`[ExcelParser] 跳过无效行: ${i} (没有有效数据)`); } } console.log(`[ExcelParser] 解析完成,有效数据行数: ${validRowCount}`); return { data, endIndex }; } /** * 解析多表格数据 */ parseMultiTable( json: ExcelJsonData, sheetConfig: SheetConfig ): ExcelRow[][] { console.log(`[ExcelParser] 开始解析多表格数据,配置: 标题行=${sheetConfig.headerRowIndex}, 数据起始行=${sheetConfig.dataStartRow}`); const tableDataSets: ExcelRow[][] = []; let currentRow = sheetConfig.dataStartRow - 1; const { headerRowIndex, endMarker, multiTableHeaderOffset = 2, multiTableDataOffset = 1, multiTableOrderNumberOffset = -1, multiTableProductNameOffset = -1 } = sheetConfig; console.log(`[ExcelParser] 多表格配置: headerOffset=${multiTableHeaderOffset}, dataOffset=${multiTableDataOffset}, orderNumberOffset=${multiTableOrderNumberOffset}`); if (sheetConfig.productNameRow !== undefined && sheetConfig.productNameCol !== undefined) { console.log(`[ExcelParser] 产品名称配置: productNameOffset=${multiTableProductNameOffset}`); } // 解析第一个表格 console.log(`[ExcelParser] 开始解析第一个表格`); const { data: firstTableData, endIndex: firstEndIndex } = this.parseSingleTable( json, json[headerRowIndex - 1] as string[], currentRow, endMarker, sheetConfig.orderNumberRow - 1, sheetConfig.orderNumberCol - 1, sheetConfig.productNameRow !== undefined ? sheetConfig.productNameRow - 1 : undefined, sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined ); if (firstTableData.length > 0) { tableDataSets.push(firstTableData); console.log(`[ExcelParser] 第一个表格解析完成,数据行数: ${firstTableData.length}`); } else { console.log(`[ExcelParser] 第一个表格没有有效数据`); } currentRow = firstEndIndex + 1; console.log(`[ExcelParser] 更新当前行索引: ${currentRow}`); // 继续解析后续表格 let tableCount = 1; while (currentRow < json.length - 1 && currentRow + multiTableHeaderOffset < json.length) { tableCount++; console.log(`[ExcelParser] 开始解析表格 #${tableCount}`); const newHeaderRowIndex = currentRow + multiTableHeaderOffset; const newDataStartRowIndex = newHeaderRowIndex + multiTableDataOffset; const newOrderNumberRow = newHeaderRowIndex + multiTableOrderNumberOffset; console.log(`[ExcelParser] 表格 #${tableCount} 计算位置: 标题行=${newHeaderRowIndex}, 数据起始行=${newDataStartRowIndex}, 订单号行=${newOrderNumberRow}`); // 计算产品名称行(如果配置了产品名称行列号) let newProductNameRow = undefined; if (sheetConfig.productNameRow !== undefined && sheetConfig.productNameCol !== undefined) { newProductNameRow = newHeaderRowIndex + multiTableProductNameOffset; console.log(`[ExcelParser] 表格 #${tableCount} 产品名称行=${newProductNameRow}`); } if (newDataStartRowIndex >= json.length) { console.log(`[ExcelParser] 表格 #${tableCount} 数据起始行超出范围,停止解析`); break; } // 获取新表格的列标题 const newHeaders = json[newHeaderRowIndex] as string[]; if (!newHeaders || newHeaders.length === 0) { console.log(`[ExcelParser] 表格 #${tableCount} 无有效标题行,停止解析`); break; } console.log(`[ExcelParser] 表格 #${tableCount} 获取到标题列数: ${newHeaders.filter(Boolean).length}`); // 解析新表格 const { data: tableData, endIndex } = this.parseSingleTable( json, newHeaders, newDataStartRowIndex, endMarker, newOrderNumberRow, sheetConfig.orderNumberCol - 1, newProductNameRow, sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined ); if (tableData.length > 0) { tableDataSets.push(tableData); console.log(`[ExcelParser] 表格 #${tableCount} 解析完成,数据行数: ${tableData.length}`); } else { console.log(`[ExcelParser] 表格 #${tableCount} 没有有效数据`); } currentRow = endIndex + 1; console.log(`[ExcelParser] 更新当前行索引: ${currentRow}`); } console.log(`[ExcelParser] 多表格解析完成,共 ${tableDataSets.length} 个表格`); return tableDataSets; } /** * 解析Excel文件 * @param buffer Excel文件的ArrayBuffer数据 * @param sheetConfigs 工作表配置 * @returns 解析结果 */ async parseExcelBuffer(buffer: ArrayBuffer, sheetConfigs: SheetConfig[]) { console.log(`[ExcelParser] 开始解析Excel文件,文件大小: ${buffer.byteLength} 字节,工作表配置数: ${sheetConfigs.length}`); try { const workbook = XLSX.read(buffer); console.log(`[ExcelParser] Excel文件读取成功,工作表数: ${workbook.SheetNames.length}`); console.log(`[ExcelParser] 工作表列表: ${workbook.SheetNames.join(', ')}`); const rawData: { [key: string]: ExcelRow[] } = {}; const warnings: string[] = []; let totalTables = 0; // 处理每个配置的工作表 for (const sheetConfig of sheetConfigs) { console.log(`[ExcelParser] 开始处理工作表: ${sheetConfig.sheetName}`); const worksheet = workbook.Sheets[sheetConfig.sheetName]; if (!worksheet) { const warning = `未找到工作表: ${sheetConfig.sheetName}`; console.log(`[ExcelParser] 警告: ${warning}`); warnings.push(warning); continue; } console.log(`[ExcelParser] 转换工作表为JSON数据`); const json = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as ExcelJsonData; console.log(`[ExcelParser] 转换完成,行数: ${json.length}`); const headers = json[sheetConfig.headerRowIndex - 1] as string[]; if (!headers || headers.length === 0) { const warning = `工作表 ${sheetConfig.sheetName} 的表头数据无效`; console.log(`[ExcelParser] 警告: ${warning}`); warnings.push(warning); continue; } console.log(`[ExcelParser] 获取到表头,列数: ${headers.filter(Boolean).length}`); console.log(`[ExcelParser] 是否为多表格模式: ${sheetConfig.isMultiTable ? '是' : '否'}`); if (sheetConfig.isMultiTable) { // 处理多表格模式 const tableDataSets = this.parseMultiTable(json, sheetConfig); if (tableDataSets.length > 0) { console.log(`[ExcelParser] 多表格模式解析完成,共 ${tableDataSets.length} 个表格`); // 保存所有原始字段数据 rawData[sheetConfig.sheetName] = tableDataSets.flatMap((tableData, tableIndex) => { console.log(`[ExcelParser] 处理表格 #${tableIndex + 1} 的所有字段数据,行数: ${tableData.length}`); return this.processAllFieldsData(tableData, tableIndex + 1).map(row => ({ ...row, tableIndex: tableIndex + 1, })); }); totalTables += tableDataSets.length; console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 处理完成,原始数据行数: ${rawData[sheetConfig.sheetName].length}`); } else { const warning = `未在 "${sheetConfig.sheetName}" 中找到有效的数据表`; console.log(`[ExcelParser] 警告: ${warning}`); warnings.push(warning); } } else { // 处理单表格模式 console.log(`[ExcelParser] 开始处理单表格模式`); const { data } = this.parseSingleTable( json, headers, sheetConfig.dataStartRow - 1, sheetConfig.endMarker, sheetConfig.orderNumberRow - 1, sheetConfig.orderNumberCol - 1, sheetConfig.productNameRow !== undefined ? sheetConfig.productNameRow - 1 : undefined, sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined ); if (data.length > 0) { console.log(`[ExcelParser] 单表格模式解析完成,数据行数: ${data.length}`); // 保存所有原始字段数据 rawData[sheetConfig.sheetName] = this.processAllFieldsData(data, 1); totalTables += 1; console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 处理完成,原始数据行数: ${rawData[sheetConfig.sheetName].length}`); } else { const warning = `未在 "${sheetConfig.sheetName}" 中找到有效数据`; console.log(`[ExcelParser] 警告: ${warning}`); warnings.push(warning); } } } // 生成导出数据 console.log(`[ExcelParser] 开始生成导出数据`); const exportData = this.generateExportData(rawData, sheetConfigs); // 计算可用字段(按工作表分类) console.log(`[ExcelParser] 计算可用字段`); const availableFieldsBySheet = this.getAllAvailableFields(rawData); console.log(`[ExcelParser] Excel解析完成,总表格数: ${totalTables},警告数: ${warnings.length}`); return { rawData, exportData, availableFieldsBySheet, warnings, totalTables }; } catch (error) { console.error(`[ExcelParser] Excel解析错误:`, error); throw error; } } /** * 从URL或base64字符串获取ArrayBuffer */ async getBufferFromUrlOrBase64(input: string): Promise { console.log(`[ExcelParser] 开始从输入获取数据`); if (input.startsWith('data:')) { console.log(`[ExcelParser] 检测到Base64编码数据`); // 处理Base64编码 const base64 = input.split(',')[1]; if (!base64) { console.error(`[ExcelParser] Base64格式错误,无法提取数据部分`); throw new Error('Base64格式错误,无法提取数据部分'); } console.log(`[ExcelParser] 正在解码Base64数据`); const binaryString = atob(base64); const len = binaryString.length; console.log(`[ExcelParser] Base64解码完成,二进制数据长度: ${len} 字节`); const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } console.log(`[ExcelParser] 成功获取ArrayBuffer,大小: ${bytes.buffer.byteLength} 字节`); return bytes.buffer; } else if (input.startsWith('http')) { console.log(`[ExcelParser] 检测到URL: ${input.substring(0, 50)}...`); // 处理URL try { const startTime = performance.now(); console.log(`[ExcelParser] 开始从URL获取文件`); const response = await fetch(input); if (!response.ok) { console.error(`[ExcelParser] 获取文件失败: ${response.status} ${response.statusText}`); throw new Error(`获取文件失败: ${response.statusText}`); } const buffer = await response.arrayBuffer(); const endTime = performance.now(); const duration = (endTime - startTime).toFixed(2); console.log(`[ExcelParser] 成功从URL获取文件,大小: ${buffer.byteLength} 字节,耗时: ${duration}ms`); return buffer; } catch (error) { console.error(`[ExcelParser] 从URL获取文件时出错:`, error); throw new Error(`从URL获取文件时出错: ${error instanceof Error ? error.message : String(error)}`); } } else { console.error(`[ExcelParser] 不支持的输入格式: ${input.substring(0, 50)}...`); throw new Error('不支持的输入格式,请提供URL或Base64编码的数据'); } } /** * 根据当前配置动态生成导出数据 */ generateExportData( allFieldsData: { [key: string]: ExcelRow[] }, sheetConfigs: SheetConfig[] ): { [key: string]: ExcelRow[] } { console.log(`[ExcelParser] 开始生成导出数据,工作表数: ${Object.keys(allFieldsData).length}`); // 如果没有原始数据,直接返回空对象 if (Object.keys(allFieldsData).length === 0) { console.log(`[ExcelParser] 没有原始数据,返回空导出数据`); return {}; } const exportData: { [key: string]: ExcelRow[] } = {}; // 处理每个工作表的数据 for (const sheetConfig of sheetConfigs) { console.log(`[ExcelParser] 处理工作表 ${sheetConfig.sheetName} 的导出数据`); const rawDataForSheet = allFieldsData[sheetConfig.sheetName]; if (!rawDataForSheet || rawDataForSheet.length === 0) { console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 没有原始数据,跳过处理`); continue; } // 根据当前配置处理数据 // 使用exportSheetName作为导出的工作表名称,默认为sheetName const exportSheetName = sheetConfig.exportSheetName || sheetConfig.sheetName; exportData[exportSheetName] = this.processDataForExport( rawDataForSheet, sheetConfig, 1 ); console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 导出数据处理完成,行数: ${exportData[sheetConfig.sheetName].length}`); } console.log(`[ExcelParser] 导出数据生成完成,工作表数: ${Object.keys(exportData).length}`); return exportData; } } // 导出单例实例,便于服务端使用 export const excelParser = new ExcelParser();