| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- import { useState } from 'react';
- import * as XLSX from 'xlsx';
- import { message } from 'antd';
- import { ExcelRow, SheetConfig } from '../types';
- type ExcelJsonData = Array<Array<string | number | null>>;
- export const useExcelParser = () => {
- const [jsonData, setJsonData] = useState<{ [key: string]: ExcelRow[] }>({});
- // 存储所有字段的原始数据,不受exportFields配置影响
- const [allFieldsData, setAllFieldsData] = useState<{ [key: string]: ExcelRow[] }>({});
- const processDataForExport = (
- data: ExcelRow[],
- sheetConfig: SheetConfig,
- tableIndex: number = 0
- ): ExcelRow[] => {
- return data
- .map((row, index) => {
- const processedRow: ExcelRow = {};
- processedRow._id = `table-${tableIndex}-row-${index}`;
-
- // 如果exportFields为空,保留所有原始字段
- if (sheetConfig.exportFields.length === 0) {
- 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;
- }
-
- // 检查必需字段是否有值
- return sheetConfig.requiredFields.every(field => {
- const value = row[sheetConfig.fieldMappings[field] || field];
- return value !== null && value !== undefined && value !== '';
- });
- });
- };
- // 处理所有原始字段数据,不过滤
- const processAllFieldsData = (
- data: ExcelRow[],
- tableIndex: number = 0
- ): ExcelRow[] => {
- return data.map((row, index) => {
- const processedRow: ExcelRow = { ...row };
- processedRow._id = `table-${tableIndex}-row-${index}`;
- return processedRow;
- });
- };
- // 获取所有可用字段
- const getAllAvailableFields = (data: { [key: string]: ExcelRow[] }): { [key: string]: string[] } => {
- // 按工作表名收集字段
- const fieldsMap: { [key: string]: Set<string> } = {};
-
- // 遍历每个工作表
- Object.entries(data).forEach(([sheetName, rows]) => {
- if (!fieldsMap[sheetName]) {
- fieldsMap[sheetName] = new Set<string>();
- }
-
- // 从每行中收集字段
- rows.forEach(row => {
- Object.keys(row).forEach(key => {
- // 过滤掉内部属性(如_id, tableIndex等)
- if (!key.startsWith('_')) {
- fieldsMap[sheetName].add(key);
- }
- });
- });
- });
-
- // 将Set转换为数组
- const result: { [key: string]: string[] } = {};
- Object.entries(fieldsMap).forEach(([sheetName, fieldsSet]) => {
- result[sheetName] = Array.from(fieldsSet);
- });
-
- return result;
- };
- // 处理单个表格的数据
- const parseSingleTable = (
- json: ExcelJsonData,
- headers: string[],
- startRow: number,
- endMarker: string,
- orderNumberRow: number,
- orderNumberCol: number,
- productNameRow?: number,
- productNameCol?: number
- ): { data: ExcelRow[]; endIndex: number } => {
- const tableData: ExcelRow[] = [];
- let currentOrderNumber: string | null = null;
- let currentProductName: string | null = null;
- let endIndex = json.length;
- // 获取订单号
- if (orderNumberRow > 0 && orderNumberCol > 0) {
- currentOrderNumber = String(json[orderNumberRow - 1]?.[orderNumberCol - 1] ?? '');
- }
- // 获取产品名称(如果配置了产品名称行列号)
- if (productNameRow && productNameCol && productNameRow > 0 && productNameCol > 0) {
- currentProductName = String(json[productNameRow - 1]?.[productNameCol - 1] ?? '');
- }
- for (let i = startRow; i < json.length; i++) {
- const row = json[i] || [];
- const rowString = row.join('');
- if (rowString.includes(endMarker)) {
- endIndex = i;
- break;
- }
- const obj: ExcelRow = {};
- let hasData = false;
- headers.forEach((header, index) => {
- if (header) {
- const value = row[index];
- if (value !== undefined) {
- obj[header] = value;
- hasData = true;
- } else {
- obj[header] = null;
- }
- }
- });
- if (currentOrderNumber) {
- obj['订单号'] = currentOrderNumber;
- }
- if (currentProductName) {
- obj['产品名称'] = currentProductName;
- }
- if (hasData) {
- tableData.push(obj);
- }
- }
- return { data: tableData, endIndex };
- };
- // 处理多表格模式
- const parseMultiTable = (
- json: ExcelJsonData,
- sheetConfig: SheetConfig
- ): ExcelRow[][] => {
- const tableDataSets: ExcelRow[][] = [];
- let currentIndex = sheetConfig.dataStartRow - 1;
- const headerOffset = sheetConfig.multiTableHeaderOffset || 1;
- const dataOffset = sheetConfig.multiTableDataOffset || 1;
- const orderNumberOffset = sheetConfig.multiTableOrderNumberOffset || -1;
- const productNameOffset = sheetConfig.multiTableProductNameOffset || -1;
- while (currentIndex < json.length) {
- // 获取当前表格的表头行号
- const headerIndex = currentIndex - (sheetConfig.dataStartRow - sheetConfig.headerRowIndex);
- // 获取当前表格的表头
- const headers = (json[headerIndex] || []) as string[];
- if (!headers || headers.length === 0) break;
- // 计算订单号行号
- const orderNumberIndex = headerIndex + (orderNumberOffset || 0);
-
- // 计算产品名称行号(如果配置了产品名称行列号)
- let productNameIndex = undefined;
- if (sheetConfig.productNameRow && sheetConfig.productNameCol) {
- productNameIndex = headerIndex + (productNameOffset || 0);
- }
- // 解析当前表格
- const { data, endIndex } = parseSingleTable(
- json,
- headers,
- currentIndex,
- sheetConfig.endMarker,
- orderNumberIndex + 1, // +1 因为 parseSingleTable 内部会 -1
- sheetConfig.orderNumberCol,
- productNameIndex !== undefined ? productNameIndex + 1 : undefined, // +1 因为 parseSingleTable 内部会 -1
- sheetConfig.productNameCol
- );
- if (data.length > 0) {
- tableDataSets.push(data);
- }
- // 如果没有找到结束标记,退出循环
- if (endIndex === json.length) break;
- // 更新下一个表格的起始位置
- currentIndex = endIndex + headerOffset + dataOffset;
- }
- return tableDataSets;
- };
- const parseExcelFile = async (file: File, sheetConfigs: SheetConfig[]) => {
- try {
- // 显示开始处理的消息
- const loadingMessage = message.loading({ content: `正在解析Excel文件: ${file.name}`, key: 'excelParsing', duration: 0 });
-
- console.log('开始处理文件:', file.name);
- const data = await file.arrayBuffer();
- // 更新进度提示
- message.loading({ content: '正在读取Excel数据...', key: 'excelParsing', duration: 0 });
-
- const workbook = XLSX.read(data);
-
- const rawData: { [key: string]: ExcelRow[] } = {};
- let totalTables = 0;
- let processedSheets = 0;
-
- // 处理每个配置的工作表
- for (const sheetConfig of sheetConfigs) {
- // 更新进度提示,显示当前处理的工作表
- message.loading({
- content: `正在处理工作表 "${sheetConfig.sheetName}" (${processedSheets + 1}/${sheetConfigs.length})`,
- key: 'excelParsing',
- duration: 0
- });
-
- const worksheet = workbook.Sheets[sheetConfig.sheetName];
-
- if (!worksheet) {
- console.error('未找到工作表:', sheetConfig.sheetName);
- message.error({
- content: `未找到工作表"${sheetConfig.sheetName}"!`,
- key: 'excelParsing'
- });
- processedSheets++;
- continue;
- }
- const json = XLSX.utils.sheet_to_json<string[]>(worksheet, { header: 1 }) as ExcelJsonData;
- const headers = json[sheetConfig.headerRowIndex - 1] as string[];
-
- if (!headers || headers.length === 0) {
- console.error(`工作表 ${sheetConfig.sheetName} 的表头数据无效`);
- message.warning({
- content: `工作表 "${sheetConfig.sheetName}" 的表头数据无效,已跳过`,
- key: 'excelParsing'
- });
- processedSheets++;
- continue;
- }
- if (sheetConfig.isMultiTable) {
- // 处理多表格模式
- message.loading({
- content: `正在处理 "${sheetConfig.sheetName}" 的多表格数据...`,
- key: 'excelParsing',
- duration: 0
- });
-
- const tableDataSets = parseMultiTable(json, sheetConfig);
-
- if (tableDataSets.length > 0) {
- message.loading({
- content: `"${sheetConfig.sheetName}" 中发现 ${tableDataSets.length} 个数据表,正在处理...`,
- key: 'excelParsing',
- duration: 0
- });
-
- // 保存所有原始字段数据
- rawData[sheetConfig.sheetName] = tableDataSets.flatMap((tableData, tableIndex) => {
- return processAllFieldsData(tableData, tableIndex + 1).map(row => ({
- ...row,
- tableIndex: tableIndex + 1,
- }));
- });
-
- totalTables += tableDataSets.length;
- } else {
- message.warning({
- content: `未在 "${sheetConfig.sheetName}" 中找到有效的数据表`,
- duration: 2
- });
- }
- } else {
- // 处理单表格模式
- message.loading({
- content: `正在处理 "${sheetConfig.sheetName}" 的单表格数据...`,
- key: 'excelParsing',
- duration: 0
- });
-
- const { data } = parseSingleTable(
- json,
- headers,
- sheetConfig.dataStartRow - 1,
- sheetConfig.endMarker,
- sheetConfig.orderNumberRow,
- sheetConfig.orderNumberCol,
- sheetConfig.productNameRow || undefined,
- sheetConfig.productNameCol || undefined
- );
-
- if (data.length > 0) {
- // 保存所有原始字段数据
- rawData[sheetConfig.sheetName] = processAllFieldsData(data, 1);
-
- totalTables += 1;
- } else {
- message.warning({
- content: `未在 "${sheetConfig.sheetName}" 中找到有效数据`,
- duration: 2
- });
- }
- }
-
- processedSheets++;
-
- // 更新进度提示
- if (processedSheets < sheetConfigs.length) {
- message.loading({
- content: `已处理 ${processedSheets}/${sheetConfigs.length} 个工作表...`,
- key: 'excelParsing',
- duration: 0
- });
- }
- }
- // 更新进度提示
- message.loading({
- content: '正在整理数据字段...',
- key: 'excelParsing',
- duration: 0
- });
- // 更新状态
- setAllFieldsData(rawData);
-
- // 计算可用字段(按工作表分类)
- const availableFieldsBySheet = getAllAvailableFields(rawData);
-
- // 完成解析,显示成功消息
- message.success({
- content: `Excel文件解析成功!共处理 ${Object.keys(rawData).length} 个工作表,${totalTables} 个数据表`,
- key: 'excelParsing',
- duration: 3
- });
-
- console.log('所有字段原始数据:', rawData);
- console.log('按工作表分类的可用字段:', availableFieldsBySheet);
-
- // 返回原始数据、分类字段
- return {
- rawData,
- availableFieldsBySheet,
- };
- } catch (error) {
- console.error('文件处理错误:', error);
- message.error({
- content: '文件处理失败,请检查文件格式是否正确!',
- key: 'excelParsing'
- });
- throw error;
- }
- };
- // 根据当前配置动态生成导出数据
- const generateExportData = (sheetConfigs: SheetConfig[]) => {
- // 如果没有原始数据,直接返回空对象
- if (Object.keys(allFieldsData).length === 0) {
- return {};
- }
-
- const exportData: { [key: string]: ExcelRow[] } = {};
-
- // 处理每个工作表的数据
- for (const sheetConfig of sheetConfigs) {
- const rawDataForSheet = allFieldsData[sheetConfig.sheetName];
-
- if (!rawDataForSheet || rawDataForSheet.length === 0) {
- continue;
- }
-
- // 根据当前配置处理数据
- exportData[sheetConfig.sheetName] = processDataForExport(
- rawDataForSheet,
- sheetConfig,
- 1
- );
- }
-
- // 只有当数据确实发生变化时才更新状态,避免不必要的渲染
- const currentJsonData = JSON.stringify(jsonData);
- const newJsonData = JSON.stringify(exportData);
-
- if (currentJsonData !== newJsonData) {
- // 更新 jsonData 状态
- setJsonData(exportData);
- console.log('生成的导出数据:', exportData);
- }
-
- return exportData;
- };
- const getFieldsBySheet = getAllAvailableFields;
- return {
- jsonData,
- allFieldsData,
- parseExcelFile,
- generateExportData,
- getFieldsBySheet
- };
- };
|