|
|
@@ -0,0 +1,587 @@
|
|
|
+import * as XLSX from 'xlsx';
|
|
|
+import type { SheetConfig } from '@/share/exceltypes';
|
|
|
+
|
|
|
+// Excel数据行类型
|
|
|
+export interface ExcelRow {
|
|
|
+ [key: string]: string | number | null | undefined;
|
|
|
+ _id?: string;
|
|
|
+ tableIndex?: number;
|
|
|
+}
|
|
|
+
|
|
|
+// Excel原始数据类型
|
|
|
+type ExcelJsonData = Array<Array<string | number | null>>;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 服务器端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<string>();
|
|
|
+
|
|
|
+ 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<string[]>(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<ArrayBuffer> {
|
|
|
+ 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 {
|
|
|
+ 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();
|
|
|
+ console.log(`[ExcelParser] 成功从URL获取文件,大小: ${buffer.byteLength} 字节`);
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据当前配置处理数据
|
|
|
+ exportData[sheetConfig.sheetName] = 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();
|