This commit is contained in:
cheney
2026-07-25 20:33:34 +08:00
parent c1e378a631
commit 11451aac40
5 changed files with 442 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
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}`);