| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- 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<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 {
- 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();
|