feat: 飞猪自动化调试完成

This commit is contained in:
duanshuwen
2026-03-10 21:26:17 +08:00
parent 15a7d115cb
commit 8a6544ede2
7 changed files with 124 additions and 130 deletions

View File

@@ -3,6 +3,7 @@ import { ipcMain, app } from 'electron';
import { IPC_EVENTS } from '@common/constants'; import { IPC_EVENTS } from '@common/constants';
import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome' import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome'
import { executeScriptService } from '@main/service/execute-script-service'; import { executeScriptService } from '@main/service/execute-script-service';
import fs from 'fs'
import path from 'path' import path from 'path'
import log from 'electron-log'; import log from 'electron-log';
@@ -11,81 +12,48 @@ export function runTaskOperationService() {
ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => { ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => {
try { try {
/**
* options参数包括房型、日期范围
* 房型:亲子房、雅致套房
* 日期范围2023-10-01至2023-10-07
* 备注:操作的渠道有:飞猪、美团、抖音来客
* 这里需要一个排队任务,用队列来处理各渠道的操作路径自动化,先将任务加入队列,然后按顺序执行脚本
*/
await launchLocalChrome(options) await launchLocalChrome(options)
// 脚本路径处理 const channels = ['feizhu', 'meituan', 'douyin', 'xiaocheng']
let scriptPath = ''
if (app.isPackaged) { const scriptMap: Record<string, string> = {
scriptPath = path.join(process.resourcesPath, 'scripts/fg_trace.js') feizhu: 'fg_trace.mjs',
} else { meituan: 'mt_trace.mjs',
scriptPath = path.join(process.cwd(), 'src/main/scripts/fg_trace.js') douyin: 'dy_trace.mjs',
xiaocheng: 'xc_trace.mjs',
} }
log.info(`Launching script with path: ${scriptPath}`); const scriptsDir = app.isPackaged
const result = await executeScriptServiceInstance.executeScript(scriptPath, options) ? path.join(process.resourcesPath, 'scripts')
: path.join(process.cwd(), 'src/main/scripts')
return { success: true, result }; const scriptPaths = channels.map((channel: string) => {
const fileName = scriptMap[channel]
if (!fileName) {
throw new Error(`Unknown channel: ${channel}`)
}
const p = path.join(scriptsDir, fileName)
if (!fs.existsSync(p)) {
throw new Error(`Script not found for channel ${channel}: ${p}`)
}
return { channel, scriptPath: p }
})
const results: any[] = []
for (const item of scriptPaths) {
log.info(`Launching script for channel ${item.channel}: ${item.scriptPath}`)
const result = await executeScriptServiceInstance.executeScript(item.scriptPath, options)
results.push({
channel: item.channel,
scriptPath: item.scriptPath,
...result,
})
}
return { success: true, result: results };
} catch (error: any) { } catch (error: any) {
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
}); });
} }
// export function runTaskOperationService() {
// ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (event, params) => {
// logManager.info('Received task operation:', params);
// return new Promise((resolve, reject) => {
// // 脚本路径
// const scriptPath = path.join(__dirname, '../../scripts/fg_trace.js');
// const child = fork(scriptPath, [], {
// stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
// env: { ...process.env, ...params } // 传递环境变量
// });
// let output = '';
// let errorOutput = '';
// if (child.stdout) {
// child.stdout.on('data', (data) => {
// const msg = data.toString();
// logManager.info(`[Task Script]: ${msg}`);
// output += msg;
// });
// }
// if (child.stderr) {
// child.stderr.on('data', (data) => {
// const msg = data.toString();
// logManager.error(`[Task Script Error]: ${msg}`);
// errorOutput += msg;
// });
// }
// child.on('close', (code) => {
// logManager.info(`Task script exited with code ${code}`);
// if (code === 0) {
// resolve({ success: true, output });
// } else {
// // 如果是因为模块找不到或语法错误退出,这里会捕获
// reject(new Error(`Script exited with code ${code}. Error: ${errorOutput}`));
// }
// });
// child.on('error', (err) => {
// logManager.error('Failed to start task script:', err);
// reject(err);
// });
// });
// });
// }

View File

@@ -1,7 +1,6 @@
import { chromium } from 'playwright'; import { chromium } from 'playwright';
import log from 'electron-log'; import log from 'electron-log';
const checkLoginStatus = async (page) => { const checkLoginStatus = async (page) => {
try { try {
const currentUrl = await page.url(); const currentUrl = await page.url();
@@ -33,7 +32,7 @@ const checkLoginStatus = async (page) => {
} }
(async () => { (async () => {
const browser = await chromium.connectOverCDP('http://localhost:9222'); const browser = await chromium.connectOverCDP(`http://localhost:9222`);
const context = browser.contexts()[0]; const context = browser.contexts()[0];
await context.addInitScript(() => { await context.addInitScript(() => {

View File

@@ -5,34 +5,73 @@ import log from 'electron-log';
export class executeScriptService extends EventEmitter { export class executeScriptService extends EventEmitter {
// 执行脚本 // 执行脚本
async executeScript(scriptPath: string, options: object): Promise<{ success: boolean; message?: string; error?: string }> { async executeScript(
try { scriptPath: string,
const child = spawn('node', [scriptPath], { options: Record<string, any>,
env: { ): Promise<{ success: boolean; exitCode: number | null; stdoutTail: string; stderrTail: string; error?: string }> {
...process.env, const MAX_TAIL = 32 * 1024;
...options,
}
});
child.stdout.on('data', (data: Buffer) => { const appendTail = (current: string, chunk: string) => {
log.info(`stdout: ${data.toString()}`); const next = current + chunk;
}); return next.length > MAX_TAIL ? next.slice(next.length - MAX_TAIL) : next;
};
child.stderr.on('data', (data: Buffer) => { return await new Promise((resolve) => {
log.info(`stderr: ${data.toString()}`); try {
}); const child = spawn('node', [scriptPath], {
env: {
...process.env,
...options,
}
});
child.on('close', (code: number) => { let stdoutTail = '';
log.info(`子进程退出,退出码 ${code}`); let stderrTail = '';
});
return { success: true, message: 'Node 脚本执行中' }; child.stdout?.on('data', (data: Buffer) => {
} catch (error) { const text = data.toString();
return { success: false, message: '运行 Node 脚本时出错' }; stdoutTail = appendTail(stdoutTail, text);
} log.info(`stdout: ${text}`);
});
child.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
stderrTail = appendTail(stderrTail, text);
log.info(`stderr: ${text}`);
});
child.on('error', (err: any) => {
resolve({
success: false,
exitCode: null,
stdoutTail,
stderrTail,
error: err?.message || 'Failed to start script process',
});
});
child.on('close', (code: number | null) => {
log.info(`子进程退出,退出码 ${code}`);
resolve({
success: code === 0,
exitCode: code,
stdoutTail,
stderrTail,
...(code === 0 ? {} : { error: `Script exited with code ${code}` }),
});
});
} catch (error: any) {
resolve({
success: false,
exitCode: null,
stdoutTail: '',
stderrTail: '',
error: error?.message || '运行 Node 脚本时出错',
});
}
});
} }
} }

View File

@@ -10,8 +10,8 @@ export async function launchLocalChrome (options: any) {
const chromePath = getChromePath(); const chromePath = getChromePath();
// 多账号隔离 // 多账号隔离
// const profileDir = getProfileDir(accountId); const profileDir = getProfileDir('default');
// log.info(`Launching Chrome with user data dir: ${options}`); log.info(`Launching Chrome with user data dir: ${options}`);
// 检查端口是否被占用 // 检查端口是否被占用
const portInUse = await isPortInUse(9222); const portInUse = await isPortInUse(9222);
@@ -32,8 +32,8 @@ export async function launchLocalChrome (options: any) {
'--window-size=1920,1080', '--window-size=1920,1080',
'--window-position=0,0', '--window-position=0,0',
'--no-first-run', '--no-first-run',
`--user-data-dir=${profileDir}`,
'--no-default-browser-check' '--no-default-browser-check'
// `--user-data-dir=${profileDir}`,
// '--window-maximized', // '--window-maximized',
], { ], {
detached: true, detached: true,

View File

@@ -1,46 +1,27 @@
import pms from '@assets/images/channel/pms.png' export interface Item {
import xc from '@assets/images/channel/xc.png'
import qne from '@assets/images/channel/qne.png'
import fz from '@assets/images/channel/fz.png'
import mt from '@assets/images/channel/mt.png'
import dy from '@assets/images/channel/dy.png'
// 菜单列表申明
interface Item {
id: number id: number
name: string channelName: string
icon: any
} }
export const channel: Item[] = [ export const channel: Item[] = [
{ {
id: 1, id: 1,
name: 'PMS', channelName: 'pms',
icon: pms,
}, },
{ {
id: 2, id: 2,
name: '携程', channelName: 'xc',
icon: xc,
}, },
{ {
id: 3, id: 3,
name: '去哪儿', channelName: 'fz',
icon: qne,
}, },
{ {
id: 4, id: 4,
name: '飞猪', channelName: 'mt',
icon: fz,
}, },
{ {
id: 5, id: 5,
name: '美团', channelName: 'dy',
icon: mt,
}, },
{
id: 6,
name: '抖音',
icon: dy,
}
] ]

View File

@@ -26,6 +26,7 @@ import { taskCenterItem } from '@constant/taskCenterList'
import { hotelStaffTypeMappingListUsingPost } from '@api/index' import { hotelStaffTypeMappingListUsingPost } from '@api/index'
const isVisible = ref(false) const isVisible = ref(false)
const roomList: any = ref([])
const title = ref('') const title = ref('')
const formRef = ref() const formRef = ref()
const form = ref({ const form = ref({
@@ -83,13 +84,19 @@ const confirm = () => {
} }
close() close()
console.log(form.value)
// 将roomList.value数组处理成标准数组
const newList = roomList.value.map((item: any) => ({ ...item }))
const options = { const options = {
roomType: form.value.roomType, roomType: form.value.roomType,
startTime: form.value.range[0], startTime: form.value.range[0],
endTime: form.value.range[1], endTime: form.value.range[1],
operation: form.value.operation, operation: form.value.operation,
roomList: newList
} }
console.log(options)
/** /**
* 坑传给进程的参数不能是ref包裹的reactive对象 * 坑传给进程的参数不能是ref包裹的reactive对象
*/ */
@@ -100,7 +107,6 @@ const confirm = () => {
} }
// 获取房型列表 // 获取房型列表
const roomList: any = ref([])
const getRoomTypeList = async () => { const getRoomTypeList = async () => {
const res = await hotelStaffTypeMappingListUsingPost({ body: {} }) const res = await hotelStaffTypeMappingListUsingPost({ body: {} })
roomList.value = res.data roomList.value = res.data

View File

@@ -48,6 +48,7 @@
"**/*.ts", "**/*.ts",
"**/*.js", "**/*.js",
"src/renderer/permission.ts", "src/renderer/permission.ts",
"src/main/scripts/*.js" "src/main/scripts/*.js",
"src/main/scripts/*.mjs"
] ]
} }