This commit is contained in:
cheney 2026-07-13 21:44:53 +08:00
parent 359fd38bd6
commit 0ad25ce261
10 changed files with 631 additions and 2 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/*
~$*

11
Readme.md Normal file
View File

@ -0,0 +1,11 @@
## 开票业务
excle 结算状态过滤
登录
总进度
## 避免锁屏
打开目录D:\Cheney\2\geely-kaipiao\
地址栏删空,输入 cmd打开黑框。
输入命令bun.exe keep-awake.js

View File

View File

@ -62,7 +62,13 @@ function readTeamIds() {
const teamIds = [];
for (const row of rows) {
const val = row['团号'];
if (val && /^TEAM-\d+$/.test(String(val).trim())) teamIds.push(String(val).trim());
const kaipiaoStatus = row['开票状态'];
if (!val || !/^TEAM-\d+$/.test(String(val).trim())) continue;
if (kaipiaoStatus && String(kaipiaoStatus).trim() !== '未开票') {
console.log(`${String(val).trim()} 开票状态: "${kaipiaoStatus}",跳过`);
continue;
}
teamIds.push(String(val).trim());
}
return teamIds;
}
@ -168,4 +174,4 @@ async function main() {
}
}
main().catch(err => { console.error('Error:', err); process.exit(1); });
main().catch(err => { console.error('Error:', err); process.exit(1); });

164
package-lock.json generated Normal file
View File

@ -0,0 +1,164 @@
{
"name": "geely-kaipiao",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "geely-kaipiao",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"playwright": "^1.61.0",
"xlsx": "^0.18.5"
}
},
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.61.0",
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.0.tgz",
"integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.61.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.61.0",
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.0.tgz",
"integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"license": "Apache-2.0",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
}
}
}

39
read_team_ids.js Normal file
View File

@ -0,0 +1,39 @@
const XLSX = require('xlsx');
const path = require('path');
// 读取 Excel 文件(项目根目录)
const filePath = path.join(__dirname, '新建 XLSX 工作表.xlsx');
const workbook = XLSX.readFile(filePath);
// 目标 sheet
const sheetName = '2026';
const sheet = workbook.Sheets[sheetName];
if (!sheet) {
console.error(`Sheet "${sheetName}" 不存在`);
process.exit(1);
}
// 将 sheet 转为 JSON 数组(以第一行为表头)
const rows = XLSX.utils.sheet_to_json(sheet, { defval: null });
// 查找"团号"列(兼容表头可能的空格差异)
const headerRow = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: null })[0];
const tuanHaoIndex = headerRow.findIndex(h => h && String(h).trim() === '团号');
if (tuanHaoIndex === -1) {
console.error('未找到"团号"列');
process.exit(1);
}
// 提取所有 TEAM-xxxx 格式的 ID
const teamIds = [];
for (const row of rows) {
const val = row['团号'];
if (val && /^TEAM-\d+$/.test(String(val).trim())) {
teamIds.push(String(val).trim());
}
}
console.log(`找到 ${teamIds.length} 个 TEAM ID:`);
teamIds.forEach(id => console.log(id));

65
src/login.js Normal file
View File

@ -0,0 +1,65 @@
const { chromium } = require('playwright');
const path = require('path');
const LOGIN_URL = 'https://superstar.geelytravel.com/new-tmc/login';
const STATE_PATH = process.env.STATE_PATH || path.resolve(__dirname, 'state.json');
const USER_AGENT =
process.env.USER_AGENT ||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
async function run() {
// 1. 启动本地 Chrome 浏览器,并添加排除自动化检测的参数
const browser = await chromium.launch({
headless: false,
channel: 'chrome',
args: [
'--disable-blink-features=AutomationControlled',
'--start-maximized',
],
});
// 2. 设置上下文,伪造 User-Agent
const context = await browser.newContext({
noViewport: true,
userAgent: USER_AGENT,
});
const page = await context.newPage();
try {
// 3. 访问登录页
await page.goto(LOGIN_URL);
// 输入信息:如需自动填写,可通过环境变量 USERNAME / PASSWORD 传入
if (process.env.USERNAME) {
await page.getByPlaceholder('用户名').fill(process.env.USERNAME);
}
if (process.env.PASSWORD) {
await page.getByPlaceholder('密码').fill(process.env.PASSWORD);
}
// 4. 进入暂停状态,此时手动滑动验证码并完成登录
console.log('请在浏览器中手动完成滑块验证,登录进入系统首页后回到终端按 Enter...');
await page.pause();
// 5. 关键:等待页面跳转完成,确保已进入登录后的状态
// 如需按业务页面确认登录成功,可打开下面一行并按实际 URL 片段调整。
// await page.waitForURL('**/report/**', { timeout: 60000 });
// 额外等待 3 秒,确保 Cookie 和 LocalStorage 完整写入内存
await page.waitForTimeout(3000);
// 6. 保存状态
await context.storageState({ path: STATE_PATH });
console.log(`登录状态已保存至:${STATE_PATH}`);
} finally {
await context.close();
await browser.close();
}
}
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});

270
src/state.json Normal file

File diff suppressed because one or more lines are too long

BIN
新建 XLSX 工作表.xlsx Normal file

Binary file not shown.

72
脚本/unlock.js Normal file
View File

@ -0,0 +1,72 @@
const { exec } = require('child_process');
const CHECK_INTERVAL = 30 * 1000; // 每 30 秒检查一次
const IDLE_LIMIT = 60 * 1000; // 空闲超过 60 秒则触发
// 封装执行 PowerShell 的方法(使用 UTF-16LE 编码避免引号转义问题)
function runPs(script) {
return new Promise((resolve, reject) => {
const encoded = Buffer.from(script, 'utf16le').toString('base64');
exec(`powershell -NoProfile -EncodedCommand "${encoded}"`, (err, stdout) => {
if (err) reject(err);
else resolve(stdout.trim());
});
});
}
// 获取当前系统空闲毫秒数
async function getIdleTime() {
const ps = `
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class IdleChecker {
[DllImport("user32.dll")]
public static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
public struct LASTINPUTINFO {
public uint cbSize;
public uint dwTime;
}
public static uint GetIdleMilliseconds() {
LASTINPUTINFO lii = new LASTINPUTINFO();
lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
GetLastInputInfo(ref lii);
return (uint)Environment.TickCount - lii.dwTime;
}
}
"@
[IdleChecker]::GetIdleMilliseconds()
`;
const result = await runPs(ps);
return parseInt(result, 10);
}
// 模拟按下 F15 键(无任何副作用)
async function pressF15() {
const ps = `
$wshell = New-Object -ComObject wscript.shell;
$wshell.SendKeys('{F15}');
`;
await runPs(ps);
}
console.log('✅ 防锁屏脚本已启动 (PowerShell 模式)');
console.log(`⏱️ 检测到空闲 ${IDLE_LIMIT/1000} 秒后,自动模拟按键防止锁屏。`);
setInterval(async () => {
try {
const idleMs = await getIdleTime();
if (idleMs > IDLE_LIMIT) {
console.log(`🔄 已空闲 ${Math.round(idleMs/1000)} 秒,模拟 F15 重置计时器...`);
await pressF15();
}
} catch (e) {
console.error('⚠️ 执行出错:', e.message);
}
}, CHECK_INTERVAL);
// 按 Ctrl+C 退出时提示
process.on('SIGINT', () => {
console.log('\n🛑 脚本已停止');
process.exit(0);
});