const XLSX = require('xlsx'); const fs = require('fs'); const path = require('path'); /** * 合并「团队开票申请表」脚本 * * 目录结构假设: * <根目录>/ * 7.xxx/ * *团队开票申请表*.xlsx (含4个sheet,其中一个含"商旅",一个含"吉智") * 其它两个表格... * 7.yyy/ * ... * * 用法: * node merge_kaipiao.js <根目录> [输出文件路径] * * 例如: * node merge_kaipiao.js "D:\\某个目录" "D:\\合并结果.xlsx" */ // ---------- 参数 ---------- const rootDir = process.argv[2]; const outFile = process.argv[3] || path.join(process.cwd(), '团队开票申请表-合并.xlsx'); if (!rootDir) { console.error('用法: node merge_kaipiao.js <根目录> [输出文件路径]'); process.exit(1); } if (!fs.existsSync(rootDir) || !fs.statSync(rootDir).isDirectory()) { console.error(`根目录不存在或不是文件夹: ${rootDir}`); process.exit(1); } // ---------- 工具函数 ---------- // 判断是否为 Excel 文件(排除临时文件 ~$ 开头) function isExcelFile(name) { return /\.(xlsx|xls)$/i.test(name) && !name.startsWith('~$'); } // 在一个工作簿里按关键字查找 sheet 名 function findSheetName(workbook, keyword) { return workbook.SheetNames.find(n => n.includes(keyword)); } // 把一个 sheet 读成二维数组(含表头行) function sheetToRows(sheet) { return XLSX.utils.sheet_to_json(sheet, { header: 1, defval: null, blankrows: false }); } // ---------- 收集子文件夹 ---------- const subDirs = fs.readdirSync(rootDir, { withFileTypes: true }) .filter(d => d.isDirectory() && d.name.startsWith('7.')) .map(d => d.name) .sort(); if (subDirs.length === 0) { console.error('未找到以 "7." 开头的子文件夹'); process.exit(1); } console.log(`找到 ${subDirs.length} 个 "7." 子文件夹`); // 合并结果:分别累积 商旅 / 吉智 两组数据 let shangluHeader = null; const shangluRows = []; let jizhiHeader = null; const jizhiRows = []; let processedCount = 0; for (const sub of subDirs) { const subPath = path.join(rootDir, sub); // 找到名字含「团队开票申请表」的表格 const files = fs.readdirSync(subPath).filter(isExcelFile); const targetFile = files.find(f => f.includes('团队开票申请表')); if (!targetFile) { console.warn(` [跳过] ${sub}: 未找到含"团队开票申请表"的表格`); continue; } const filePath = path.join(subPath, targetFile); let workbook; try { workbook = XLSX.readFile(filePath); } catch (e) { console.warn(` [跳过] ${sub}: 读取失败 - ${e.message}`); continue; } const shangluName = findSheetName(workbook, '商旅'); const jizhiName = findSheetName(workbook, '吉智'); if (!shangluName) console.warn(` [警告] ${sub}: 未找到含"商旅"的sheet`); if (!jizhiName) console.warn(` [警告] ${sub}: 未找到含"吉智"的sheet`); // 处理 商旅 if (shangluName) { const rows = sheetToRows(workbook.Sheets[shangluName]); if (rows.length > 0) { const [header, ...body] = rows; if (!shangluHeader) shangluHeader = header; shangluRows.push(...body); } } // 处理 吉智 if (jizhiName) { const rows = sheetToRows(workbook.Sheets[jizhiName]); if (rows.length > 0) { const [header, ...body] = rows; if (!jizhiHeader) jizhiHeader = header; jizhiRows.push(...body); } } processedCount++; console.log(` [完成] ${sub}: ${targetFile}`); } if (processedCount === 0) { console.error('没有可合并的数据'); process.exit(1); } // ---------- 生成输出工作簿 ---------- const outWb = XLSX.utils.book_new(); if (shangluHeader) { const data = [shangluHeader, ...shangluRows]; const ws = XLSX.utils.aoa_to_sheet(data); XLSX.utils.book_append_sheet(outWb, ws, '商旅'); } else { console.warn('未收集到任何"商旅"数据'); } if (jizhiHeader) { const data = [jizhiHeader, ...jizhiRows]; const ws = XLSX.utils.aoa_to_sheet(data); XLSX.utils.book_append_sheet(outWb, ws, '吉智'); } else { console.warn('未收集到任何"吉智"数据'); } if (outWb.SheetNames.length === 0) { console.error('没有生成任何 sheet,未写出文件'); process.exit(1); } XLSX.writeFile(outWb, outFile); console.log('\n合并完成:'); console.log(` 商旅: ${shangluRows.length} 行数据`); console.log(` 吉智: ${jizhiRows.length} 行数据`); console.log(` 输出文件: ${outFile}`);