diff --git a/global.d.ts b/global.d.ts index 4b9c6ea..6866735 100644 --- a/global.d.ts +++ b/global.d.ts @@ -80,8 +80,8 @@ declare global { warn: (message: string, ...meta?: any[]) => void; error: (message: string, ...meta?: any[]) => void; }, - // 任务操作 - taskOperation: (params: any) => Promise<{success: boolean, error?: string}>, + // 执行脚本 + executeScript: (options: any) => Promise<{success: boolean, error?: string}>, } interface Window { diff --git a/package-lock.json b/package-lock.json index 777fc94..b3aaf80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,8 @@ "mitt": "^3.0.1", "openai": "^6.14.0", "pinia": "^2.3.1", + "playwright": "^1.58.2", + "ts-node": "^10.9.2", "uuid": "^13.0.0", "vue": "^3.5.22", "vue-i18n": "^11.1.9", @@ -218,6 +220,28 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -2094,7 +2118,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3003,6 +3026,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -3128,7 +3175,6 @@ "version": "24.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz", "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -3665,7 +3711,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3698,6 +3743,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3828,6 +3885,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4590,6 +4653,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-dirname": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", @@ -4844,6 +4913,15 @@ "integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==", "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -8299,6 +8377,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/make-fetch-happen": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", @@ -9671,6 +9755,50 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/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/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -11328,6 +11456,49 @@ "code-block-writer": "^13.0.3" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11389,7 +11560,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11416,7 +11586,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unimport": { @@ -11636,6 +11805,12 @@ "uuid": "dist-node/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -12153,6 +12328,15 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 8916481..bfb3481 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,8 @@ "mitt": "^3.0.1", "openai": "^6.14.0", "pinia": "^2.3.1", + "playwright": "^1.58.2", + "ts-node": "^10.9.2", "uuid": "^13.0.0", "vue": "^3.5.22", "vue-i18n": "^11.1.9", diff --git a/src/common/constants.ts b/src/common/constants.ts index 76d2705..dfa3050 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -53,8 +53,8 @@ export enum IPC_EVENTS { IS_DARK_THEME = 'is-dark-theme', THEME_MODE_UPDATED = 'theme-mode-updated', - // 任务操作 - TASK_OPERATION = 'task-operation', + // 执行脚本 + EXECUTE_SCRIPT = 'execute-script', } export const MAIN_WIN_SIZE = { diff --git a/src/main/main.ts b/src/main/main.ts index 44f9604..f058d28 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -3,21 +3,23 @@ import { CONFIG_KEYS } from '@common/constants' import { setupMainWindow } from './wins'; import started from 'electron-squirrel-startup' import configManager from '@main/service/config-service' -import logManager from '@main/service/logger' import { runTaskOperationService } from '@main/process/runTaskOperationService' +import log from 'electron-log'; +// import logManager from '@main/service/logger' + // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { app.quit(); } -process.on('uncaughtException', (err) => { - logManager.error('uncaughtException', err); -}); +// process.on('uncaughtException', (err) => { +// logManager.error('uncaughtException', err); +// }); -process.on('unhandledRejection', (reason, promise) => { - logManager.error('unhandledRejection', reason, promise); -}); +// process.on('unhandledRejection', (reason, promise) => { +// logManager.error('unhandledRejection', reason, promise); +// }); app.whenReady().then(() => { setupMainWindow(); @@ -33,7 +35,7 @@ app.whenReady().then(() => { // explicitly with Cmd + Q. app.on('window-all-closed', () => { if (process.platform !== 'darwin' && !configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY)) { - logManager.info('app closing due to all windows being closed'); + log.info('app closing due to all windows being closed'); app.quit(); } }); diff --git a/src/main/process/runTaskOperationService.ts b/src/main/process/runTaskOperationService.ts index b92a98c..d1c32d2 100644 --- a/src/main/process/runTaskOperationService.ts +++ b/src/main/process/runTaskOperationService.ts @@ -1,2 +1,80 @@ -export function runTaskOperationService() {} \ No newline at end of file +import { ipcMain } from 'electron'; +import { IPC_EVENTS } from '@common/constants'; +import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome' +import { executeScriptService } from '@main/service/execute-script-service'; +import path from 'path' + +export function runTaskOperationService() { + const executeScriptServiceInstance = new executeScriptService(); + + ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => { + try { + /** + * options参数包括:房型、日期范围 + * 房型:亲子房、雅致套房 + * 日期范围:2023-10-01至2023-10-07 + * 备注:操作的渠道有:飞猪、美团、抖音来客 + * 这里需要一个排队任务,用队列来处理各渠道的操作路径自动化,先将任务加入队列,然后按顺序执行脚本 + */ + + await launchLocalChrome(options) + const result = await executeScriptServiceInstance.executeScript(path.join(__dirname, '../../scripts/fg_trace.js'), options) + + return { success: true, result }; + } 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/scripts/fg_trace.js b/src/main/scripts/fg_trace.js similarity index 60% rename from src/scripts/fg_trace.js rename to src/main/scripts/fg_trace.js index c7d07f3..0128f42 100644 --- a/src/scripts/fg_trace.js +++ b/src/main/scripts/fg_trace.js @@ -1,39 +1,10 @@ import { chromium } from 'playwright'; +import { checkLoginStatus } from '@utils/checkLoginStatus'; import dotenv from 'dotenv'; import log from 'electron-log'; dotenv.config(); -const checkLoginStatus = async (page) => { - try { - const currentUrl = await page.url(); - - log.info('current url==========>:', currentUrl); - - const loginPagePatterns = ['/login', '/signin', '/auth']; - const isLoginPage = loginPagePatterns.some(pattern => currentUrl.includes(pattern)); - - if(!isLoginPage) { - return true; - } - - // 检查页面内容中的关键字 - const pageContent = await page.content(); - const loginKeywords = ['退出', '房价房量管理']; - const hasLoginKeyword = loginKeywords.some(keyword => - pageContent.includes(keyword) - ); - - if (hasLoginKeyword) { - return true; - } - - return false; - } catch (error) { - return false; - } -} - (async () => { const browser = await chromium.connectOverCDP('http://localhost:9222'); const context = browser.contexts()[0]; @@ -116,17 +87,17 @@ const checkLoginStatus = async (page) => { * 2、筛选日期下存在没有安排的房型 */ - await page.getByRole('textbox', { name: '-02-27' }).click(); - await page.getByText('27').nth(3).click(); + // await page.getByRole('textbox', { name: '-02-27' }).click(); + // await page.getByText('27').nth(3).click(); // 开启房型,div:nth-child(动态变化的) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r - await page.locator('div:nth-child(7) > div > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').first().click(); - 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 > .success___VQjXR > .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 > .success___VQjXR > .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 > .success___VQjXR > .bar___i4k4r').click(); - await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(5) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); + // await page.locator('div:nth-child(7) > div > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').first().click(); + // 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 > .success___VQjXR > .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 > .success___VQjXR > .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 > .success___VQjXR > .bar___i4k4r').click(); + // await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(5) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); // 关闭房型,div:nth-child(动态变化的) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r - await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').first().click(); - 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(); + // await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').first().click(); + // 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 new file mode 100644 index 0000000..be3c470 --- /dev/null +++ b/src/main/service/execute-script-service/index.ts @@ -0,0 +1,38 @@ +import { EventEmitter } from 'events'; +import { spawn } from 'child_process'; +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 脚本时出错' }; + } + } +} + + + + diff --git a/src/main/utils/checkLoginStatus.ts b/src/main/utils/checkLoginStatus.ts new file mode 100644 index 0000000..8eadde1 --- /dev/null +++ b/src/main/utils/checkLoginStatus.ts @@ -0,0 +1,31 @@ +import log from 'electron-log'; + +export const checkLoginStatus = async (page: any) => { + try { + const currentUrl = await page.url(); + + log.info('current url==========>:', currentUrl); + + const loginPagePatterns = ['/login', '/signin', '/auth']; + const isLoginPage = loginPagePatterns.some(pattern => currentUrl.includes(pattern)); + + if(!isLoginPage) { + return true; + } + + // 检查页面内容中的关键字 + const pageContent = await page.content(); + const loginKeywords = ['退出', '房价房量管理']; + const hasLoginKeyword = loginKeywords.some(keyword => + pageContent.includes(keyword) + ); + + if (hasLoginKeyword) { + return true; + } + + return false; + } catch (error) { + return false; + } +} \ No newline at end of file diff --git a/src/main/utils/chrome/getChromePath.ts b/src/main/utils/chrome/getChromePath.ts new file mode 100644 index 0000000..0f3ea63 --- /dev/null +++ b/src/main/utils/chrome/getChromePath.ts @@ -0,0 +1,17 @@ + + +// 启动本地Chrome +export function getChromePath () { + if (process.platform === 'win32') { + // "C:\Program Files\Google\Chrome\Application\chrome.exe" + return 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'; + } + + if (process.platform === 'darwin') { + return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; + } + + if (process.platform === 'linux') { + return 'google-chrome'; + } +} \ No newline at end of file diff --git a/src/main/utils/chrome/getProfileDir.ts b/src/main/utils/chrome/getProfileDir.ts new file mode 100644 index 0000000..58f0f66 --- /dev/null +++ b/src/main/utils/chrome/getProfileDir.ts @@ -0,0 +1,7 @@ +import path from "node:path"; +import { app } from 'electron'; + +// 多账号隔离 +export function getProfileDir (accountId: string) { + return path.join(app.getPath('userData'), `profiles`, accountId); +} \ No newline at end of file diff --git a/src/main/utils/chrome/isChromeRunning.ts b/src/main/utils/chrome/isChromeRunning.ts new file mode 100644 index 0000000..3c3e099 --- /dev/null +++ b/src/main/utils/chrome/isChromeRunning.ts @@ -0,0 +1,21 @@ +import http from 'http'; + +// Chrome是否已运行 +export async function isChromeRunning (): Promise { + try { + return new Promise((resolve) => { + const req = http.get('http://localhost:9222/json/version', (res: any) => { + resolve(res.statusCode === 200); + }); + + req.on('error', () => resolve(false)); + + req.setTimeout(1000, () => { + req.destroy(); + resolve(false); + }); + }); + } catch (error) { + return false + } +} \ No newline at end of file diff --git a/src/main/utils/chrome/isPortInUse.ts b/src/main/utils/chrome/isPortInUse.ts new file mode 100644 index 0000000..3f0b90c --- /dev/null +++ b/src/main/utils/chrome/isPortInUse.ts @@ -0,0 +1,17 @@ +import net from 'net'; + +// 检查端口占用 +export function isPortInUse (port: number) { + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', (err: any) => resolve(true)); + + server.once('listening', () => { + server.close(); + resolve(false); + }); + + server.listen(port); + }); +} \ No newline at end of file diff --git a/src/main/utils/chrome/launchLocalChrome.ts b/src/main/utils/chrome/launchLocalChrome.ts new file mode 100644 index 0000000..63ee08d --- /dev/null +++ b/src/main/utils/chrome/launchLocalChrome.ts @@ -0,0 +1,48 @@ +import { getChromePath } from './getChromePath'; +import { getProfileDir } from './getProfileDir'; +import { isPortInUse } from './isPortInUse'; +import { isChromeRunning } from './isChromeRunning'; +import { spawn } from 'child_process'; +import log from 'electron-log'; + +// 启动本地浏览器 +export async function launchLocalChrome (options: any) { + const chromePath = getChromePath(); + + // 多账号隔离 + // const profileDir = getProfileDir(accountId); + log.info(`Launching Chrome with user data dir: ${options}`); + + // 检查端口是否被占用 + const portInUse = await isPortInUse(9222); + + if (portInUse) { + log.info('Chrome already running on port 9222, skip launching.'); + return; + } + + if (await isChromeRunning()) { + log.info('Chrome already running, skip launching.'); + return; + } + + return new Promise((resolve, reject) => { + const chromeProcess = spawn(chromePath as string, [ + '--remote-debugging-port=9222', + '--window-size=1920,1080', + '--window-position=0,0', + '--no-first-run', + '--no-default-browser-check' + // `--user-data-dir=${profileDir}`, + // '--window-maximized', + ], { + detached: true, + stdio: 'ignore' + }); + + chromeProcess.on('error', reject); + + // 等浏览器起来 + resolve(0); + }); +} \ No newline at end of file diff --git a/src/preload.ts b/src/preload.ts index 38fb929..4478da5 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -56,8 +56,8 @@ const api: WindowApi = { error: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_ERROR, message, ...meta), }, - // 任务操作 - taskOperation: (params: any) => ipcRenderer.invoke(IPC_EVENTS.TASK_OPERATION, params), + // 执行脚本 + executeScript: (params: any) => ipcRenderer.invoke(IPC_EVENTS.EXECUTE_SCRIPT, params), } contextBridge.exposeInMainWorld('api', api) \ 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 9fb0be2..7ede35a 100644 --- a/src/renderer/views/home/components/TaskOperationDialog.vue +++ b/src/renderer/views/home/components/TaskOperationDialog.vue @@ -8,7 +8,7 @@ - @@ -29,17 +29,19 @@ const isVisible = ref(false) const title = ref('') const form = ref({ roomType: '', - date: '', + startTime: '', + endTime: '', operation: '', }) const rules = ref({ roomType: [ { required: true, message: '请选择房型', trigger: 'blur' }, ], - date: [ - { required: true, message: '请选择日期', trigger: 'blur' }, + ranger: [ + { required: true, message: '请选择日期范围', trigger: 'blur' }, ], }) +const ranger = ref([]) // 打开弹窗 const open = ({ type }: taskCenterItem) => { @@ -56,7 +58,9 @@ const close = () => { // 重置form const reset = () => { form.value.roomType = '' - form.value.date = '' + form.value.startTime = '' + form.value.endTime = '' + ranger.value = [] } // 取消操作 @@ -68,8 +72,13 @@ const cancel = () => { // 确认操作 const confirm = () => { close() + form.value.startTime = ranger.value[0] + form.value.endTime = ranger.value[1] console.log(form.value) - window.api.taskOperation(form.value) + /** + * 坑:传给进程的参数不能是ref包裹的reactive对象 + */ + window.api.executeScript({ ...form.value }) } defineExpose({ diff --git a/tsconfig.json b/tsconfig.json index ad40439..935121e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ "@assets/*": ["src/assets/*"], "@common/*": ["src/common/*"], "@service/*": ["src/main/service/*"], - "@locales/*": ["locales/*"] + "@locales/*": ["locales/*"], + "@utils/*": ["src/main/utils/*"] } } } diff --git a/vite.main.config.ts b/vite.main.config.ts index e80b8ab..7c3ffb7 100644 --- a/vite.main.config.ts +++ b/vite.main.config.ts @@ -14,6 +14,7 @@ export default defineConfig( async () => { '@renderer': resolve(__dirname, './src/renderer'), '@locales': resolve(__dirname, 'locales'), "@service": resolve(__dirname, "./src/main/service"), + "@utils": resolve(__dirname, "./src/main/utils"), }, }, }