diff --git a/src/main/process/runTaskOperationService.ts b/src/main/process/runTaskOperationService.ts index f3aa71a..d37a612 100644 --- a/src/main/process/runTaskOperationService.ts +++ b/src/main/process/runTaskOperationService.ts @@ -3,6 +3,7 @@ import { ipcMain, app } from 'electron'; import { IPC_EVENTS } from '@common/constants'; import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome' import { executeScriptService } from '@main/service/execute-script-service'; +import fs from 'fs' import path from 'path' import log from 'electron-log'; @@ -11,81 +12,48 @@ export function runTaskOperationService() { ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => { try { - /** - * options参数包括:房型、日期范围 - * 房型:亲子房、雅致套房 - * 日期范围:2023-10-01至2023-10-07 - * 备注:操作的渠道有:飞猪、美团、抖音来客 - * 这里需要一个排队任务,用队列来处理各渠道的操作路径自动化,先将任务加入队列,然后按顺序执行脚本 - */ - await launchLocalChrome(options) - // 脚本路径处理 - let scriptPath = '' - if (app.isPackaged) { - scriptPath = path.join(process.resourcesPath, 'scripts/fg_trace.js') - } else { - scriptPath = path.join(process.cwd(), 'src/main/scripts/fg_trace.js') + const channels = ['feizhu', 'meituan', 'douyin', 'xiaocheng'] + + const scriptMap: Record = { + feizhu: 'fg_trace.mjs', + meituan: 'mt_trace.mjs', + douyin: 'dy_trace.mjs', + xiaocheng: 'xc_trace.mjs', } - log.info(`Launching script with path: ${scriptPath}`); - const result = await executeScriptServiceInstance.executeScript(scriptPath, options) + const scriptsDir = app.isPackaged + ? 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) { 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); -// }); -// }); -// }); -// } \ No newline at end of file diff --git a/src/main/scripts/fg_trace.js b/src/main/scripts/fg_trace.mjs similarity index 98% rename from src/main/scripts/fg_trace.js rename to src/main/scripts/fg_trace.mjs index 09f690e..ec9eeea 100644 --- a/src/main/scripts/fg_trace.js +++ b/src/main/scripts/fg_trace.mjs @@ -1,7 +1,6 @@ import { chromium } from 'playwright'; import log from 'electron-log'; - const checkLoginStatus = async (page) => { try { const currentUrl = await page.url(); @@ -33,7 +32,7 @@ const checkLoginStatus = async (page) => { } (async () => { - const browser = await chromium.connectOverCDP('http://localhost:9222'); + const browser = await chromium.connectOverCDP(`http://localhost:9222`); const context = browser.contexts()[0]; await context.addInitScript(() => { @@ -127,4 +126,4 @@ const checkLoginStatus = async (page) => { // await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(2) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); // await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(3) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); // await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(4) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); -})(); \ No newline at end of file +})(); diff --git a/src/main/service/execute-script-service/index.ts b/src/main/service/execute-script-service/index.ts index be3c470..73fa5fd 100644 --- a/src/main/service/execute-script-service/index.ts +++ b/src/main/service/execute-script-service/index.ts @@ -5,34 +5,73 @@ import log from 'electron-log'; export class executeScriptService extends EventEmitter { // 执行脚本 - async executeScript(scriptPath: string, options: object): Promise<{ success: boolean; message?: string; error?: string }> { - try { - const child = spawn('node', [scriptPath], { - env: { - ...process.env, - ...options, - } - }); - - child.stdout.on('data', (data: Buffer) => { - log.info(`stdout: ${data.toString()}`); - }); - - child.stderr.on('data', (data: Buffer) => { - log.info(`stderr: ${data.toString()}`); - }); - - child.on('close', (code: number) => { - log.info(`子进程退出,退出码 ${code}`); - }); - - return { success: true, message: 'Node 脚本执行中' }; - } catch (error) { - return { success: false, message: '运行 Node 脚本时出错' }; - } + async executeScript( + scriptPath: string, + options: Record, + ): Promise<{ success: boolean; exitCode: number | null; stdoutTail: string; stderrTail: string; error?: string }> { + const MAX_TAIL = 32 * 1024; + + const appendTail = (current: string, chunk: string) => { + const next = current + chunk; + return next.length > MAX_TAIL ? next.slice(next.length - MAX_TAIL) : next; + }; + + return await new Promise((resolve) => { + try { + const child = spawn('node', [scriptPath], { + env: { + ...process.env, + ...options, + } + }); + + let stdoutTail = ''; + let stderrTail = ''; + + child.stdout?.on('data', (data: Buffer) => { + const text = data.toString(); + 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 脚本时出错', + }); + } + }); } } - diff --git a/src/main/utils/chrome/launchLocalChrome.ts b/src/main/utils/chrome/launchLocalChrome.ts index f302db9..9326f16 100644 --- a/src/main/utils/chrome/launchLocalChrome.ts +++ b/src/main/utils/chrome/launchLocalChrome.ts @@ -10,8 +10,8 @@ export async function launchLocalChrome (options: any) { const chromePath = getChromePath(); // 多账号隔离 - // const profileDir = getProfileDir(accountId); - // log.info(`Launching Chrome with user data dir: ${options}`); + const profileDir = getProfileDir('default'); + log.info(`Launching Chrome with user data dir: ${options}`); // 检查端口是否被占用 const portInUse = await isPortInUse(9222); @@ -32,8 +32,8 @@ export async function launchLocalChrome (options: any) { '--window-size=1920,1080', '--window-position=0,0', '--no-first-run', + `--user-data-dir=${profileDir}`, '--no-default-browser-check' - // `--user-data-dir=${profileDir}`, // '--window-maximized', ], { detached: true, diff --git a/src/renderer/constant/channel.ts b/src/renderer/constant/channel.ts index adc5aca..45e31d5 100644 --- a/src/renderer/constant/channel.ts +++ b/src/renderer/constant/channel.ts @@ -1,46 +1,27 @@ -import pms from '@assets/images/channel/pms.png' -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 { +export interface Item { id: number - name: string - icon: any + channelName: string } export const channel: Item[] = [ { id: 1, - name: 'PMS', - icon: pms, + channelName: 'pms', }, { id: 2, - name: '携程', - icon: xc, + channelName: 'xc', }, { id: 3, - name: '去哪儿', - icon: qne, + channelName: 'fz', }, { id: 4, - name: '飞猪', - icon: fz, + channelName: 'mt', }, { id: 5, - name: '美团', - icon: mt, + channelName: 'dy', }, - { - id: 6, - name: '抖音', - icon: dy, - } ] \ No newline at end of file diff --git a/src/renderer/views/home/components/TaskOperationDialog.vue b/src/renderer/views/home/components/TaskOperationDialog.vue index 4414225..2184a86 100644 --- a/src/renderer/views/home/components/TaskOperationDialog.vue +++ b/src/renderer/views/home/components/TaskOperationDialog.vue @@ -26,6 +26,7 @@ import { taskCenterItem } from '@constant/taskCenterList' import { hotelStaffTypeMappingListUsingPost } from '@api/index' const isVisible = ref(false) +const roomList: any = ref([]) const title = ref('') const formRef = ref() const form = ref({ @@ -83,13 +84,19 @@ const confirm = () => { } close() - console.log(form.value) + + // 将roomList.value数组处理成标准数组 + const newList = roomList.value.map((item: any) => ({ ...item })) + const options = { roomType: form.value.roomType, startTime: form.value.range[0], endTime: form.value.range[1], operation: form.value.operation, + roomList: newList } + + console.log(options) /** * 坑:传给进程的参数不能是ref包裹的reactive对象 */ @@ -100,7 +107,6 @@ const confirm = () => { } // 获取房型列表 -const roomList: any = ref([]) const getRoomTypeList = async () => { const res = await hotelStaffTypeMappingListUsingPost({ body: {} }) roomList.value = res.data @@ -110,4 +116,4 @@ defineExpose({ open, close, }) - \ No newline at end of file + diff --git a/tsconfig.app.json b/tsconfig.app.json index b141062..38bce8d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -48,6 +48,7 @@ "**/*.ts", "**/*.js", "src/renderer/permission.ts", - "src/main/scripts/*.js" + "src/main/scripts/*.js", + "src/main/scripts/*.mjs" ] }