diff --git a/global.d.ts b/global.d.ts index ff00914..4b9c6ea 100644 --- a/global.d.ts +++ b/global.d.ts @@ -80,6 +80,8 @@ declare global { warn: (message: string, ...meta?: any[]) => void; error: (message: string, ...meta?: any[]) => void; }, + // 任务操作 + taskOperation: (params: any) => Promise<{success: boolean, error?: string}>, } interface Window { diff --git a/package-lock.json b/package-lock.json index 71be609..777fc94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "lodash-es": "^4.17.21", "log4js": "^6.9.1", "markdown-it": "^14.1.0", + "mitt": "^3.0.1", "openai": "^6.14.0", "pinia": "^2.3.1", "uuid": "^13.0.0", @@ -8640,6 +8641,12 @@ "node": ">= 8" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", diff --git a/package.json b/package.json index edcd67d..8916481 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "lodash-es": "^4.17.21", "log4js": "^6.9.1", "markdown-it": "^14.1.0", + "mitt": "^3.0.1", "openai": "^6.14.0", "pinia": "^2.3.1", "uuid": "^13.0.0", diff --git a/src/common/constants.ts b/src/common/constants.ts index a958ff3..76d2705 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -52,6 +52,9 @@ export enum IPC_EVENTS { GET_THEME_MODE = 'get-theme-mode', IS_DARK_THEME = 'is-dark-theme', THEME_MODE_UPDATED = 'theme-mode-updated', + + // 任务操作 + TASK_OPERATION = 'task-operation', } export const MAIN_WIN_SIZE = { diff --git a/src/main/main.ts b/src/main/main.ts index 87cbf73..44f9604 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -4,6 +4,7 @@ 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' // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { @@ -20,6 +21,11 @@ process.on('unhandledRejection', (reason, promise) => { app.whenReady().then(() => { setupMainWindow(); + + // 开启任务操作子进程 + runTaskOperationService() + + // 开启subagent子进程 }); // Quit when all windows are closed, except on macOS. There, it's common diff --git a/src/main/process/runAgentService.ts b/src/main/process/runAgentService.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/main/process/runTaskOperationService.ts b/src/main/process/runTaskOperationService.ts new file mode 100644 index 0000000..b92a98c --- /dev/null +++ b/src/main/process/runTaskOperationService.ts @@ -0,0 +1,2 @@ + +export function runTaskOperationService() {} \ No newline at end of file diff --git a/src/main/service/task-operation/index.ts b/src/main/service/task-operation/index.ts new file mode 100644 index 0000000..1630412 --- /dev/null +++ b/src/main/service/task-operation/index.ts @@ -0,0 +1,7 @@ +import { ipcMain } from 'electron' +import { IPC_EVENTS } from '@common/constants' +import { spawn } from 'child_process' + + + +export function runTaskOperationService() {} \ No newline at end of file diff --git a/src/preload.ts b/src/preload.ts index 1fa92ff..38fb929 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -54,7 +54,10 @@ const api: WindowApi = { info: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_INFO, message, ...meta), warn: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_WARN, message, ...meta), error: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_ERROR, message, ...meta), - } + }, + + // 任务操作 + taskOperation: (params: any) => ipcRenderer.invoke(IPC_EVENTS.TASK_OPERATION, params), } contextBridge.exposeInMainWorld('api', api) \ No newline at end of file diff --git a/src/renderer/constant/taskCenterList.ts b/src/renderer/constant/taskCenterList.ts index d6ce56c..09fdee8 100644 --- a/src/renderer/constant/taskCenterList.ts +++ b/src/renderer/constant/taskCenterList.ts @@ -4,7 +4,8 @@ export interface taskCenterItem { title: string desc: string, id: string, - icon: string + icon: string, + type: 'sale' | 'close' | 'open' } export const taskCenterList: taskCenterItem[] = [ @@ -12,18 +13,21 @@ export const taskCenterList: taskCenterItem[] = [ title: '每日销售数据', desc: '分析用于销售渠道每日数据汇总及简要展示', id: uuidv4(), - icon: '销' + icon: '销', + type: 'sale' }, { title: '关渠道房型', desc: '关闭销售渠道下的指定房型', id: uuidv4(), - icon: '关' + icon: '关', + type: 'close' }, { title: '开渠道房型', desc: '开启销售渠道下的指定房型', id: uuidv4(), - icon: '开' + icon: '开', + type: 'open' }, ] \ No newline at end of file diff --git a/src/renderer/utils/emitter.ts b/src/renderer/utils/emitter.ts new file mode 100644 index 0000000..176bd08 --- /dev/null +++ b/src/renderer/utils/emitter.ts @@ -0,0 +1,7 @@ +import mitt from 'mitt' + +const emitter = mitt(); + +(window as any).emitter = emitter + +export default emitter diff --git a/src/renderer/views/home/ChatBox.vue b/src/renderer/views/home/ChatBox.vue index 5a57446..894d202 100644 --- a/src/renderer/views/home/ChatBox.vue +++ b/src/renderer/views/home/ChatBox.vue @@ -69,8 +69,7 @@ diff --git a/src/renderer/views/home/components/TaskOperationDialog.vue b/src/renderer/views/home/components/TaskOperationDialog.vue new file mode 100644 index 0000000..9fb0be2 --- /dev/null +++ b/src/renderer/views/home/components/TaskOperationDialog.vue @@ -0,0 +1,79 @@ + + + \ No newline at end of file diff --git a/src/renderer/views/home/index.vue b/src/renderer/views/home/index.vue index efd48e3..67a674d 100644 --- a/src/renderer/views/home/index.vue +++ b/src/renderer/views/home/index.vue @@ -2,28 +2,42 @@
+
+
+ +
diff --git a/src/scripts/fg_trace.js b/src/scripts/fg_trace.js new file mode 100644 index 0000000..c7d07f3 --- /dev/null +++ b/src/scripts/fg_trace.js @@ -0,0 +1,132 @@ +import { chromium } from 'playwright'; +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]; + + await context.addInitScript(() => { + Object.defineProperty(navigator, 'webdriver', { get: ()=> undefined }); + }); + + const pages = await context.pages(); + + const page = pages.length ? pages[0] : await context.newPage(); + + await page.goto('https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1'); + + const isLogin = await checkLoginStatus(page); + + if(!isLogin) { + await page.getByRole('textbox', { name: '请输入账号' }).dblclick(); + await page.getByRole('textbox', { name: '请输入账号' }).click(); + await page.getByRole('textbox', { name: '请输入账号' }).fill(process.env.FZ_USERNAME, { delay: 80 + Math.random() * 120 }); + await page.waitForTimeout(1000 + Math.random() * 1000); + await page.getByRole('button', { name: '下一步' }).click(); + + const frame_1 = await page.frameLocator('#alibaba-login-box'); + await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).dblclick(); + await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).fill(process.env.FZ_PASSWORD, { delay: 80 + Math.random() * 120 }); + await page.waitForTimeout(1000 + Math.random() * 1000); + await page.locator('#alibaba-login-box').contentFrame().getByRole('button', { name: '登录' }).click(); + + // 等待滑块真正出现在 DOM 并可见 + await page.waitForTimeout(4000 + Math.random() * 1000); + const frame_2 = await frame_1.frameLocator('#baxia-dialog-content'); + const container = await frame_2.locator('#nc_1_nocaptcha'); + const slider = await frame_2.locator('#nc_1_n1z'); + const isVisible = await slider.isVisible(); + + if (isVisible) { + // 重新获取滑块按钮(可能嵌套在 iframe 里) + const containerBox = await container.boundingBox(); + const sliderBox = await slider.boundingBox(); + + const startX = sliderBox.x + sliderBox.width / 2; + const startY = sliderBox.y + sliderBox.height / 2; + const distance = containerBox.width - sliderBox.width; // 适当拉长拖动距离 + const steps = 20; // 分多步模拟人手拖动 + + await page.mouse.move(startX, startY); + // 等待随机时间再开始滑动(模拟人类反应) + await page.waitForTimeout(200 + Math.random() * 300); + await page.mouse.down(); + await page.waitForTimeout(100 + Math.random() * 200); + + // 按轨迹滑动 + for (let i = 0; i < steps; i++) { + await page.mouse.move( + sliderBox.x + sliderBox.width / 2 + (distance * (i + 1) / steps), + sliderBox.y + sliderBox.height / 2 + (Math.random() * 5 - 2.5), // 模拟轻微Y轴抖动 + { steps: 5 } + ); + } + + await page.mouse.up(); + } + } + + await page.waitForTimeout(4000 + Math.random() * 300); + + await page.getByRole('menuitem', { name: '房价房量管理' }).click(); + await page.waitForTimeout(4000 + Math.random() * 1000); + await page.getByText('房价房量日历').click(); + + // await page.pause(); + /* + * 1、我要知道日期 + * 2、我要知道房型 + * 3、我要知道是关闭或开启操作 + * + * 存在以下影响自动化情况: + * 1、房型重新拖拽排序,会影响后续操作 + * 2、筛选日期下存在没有安排的房型 + */ + + 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(); + // 关闭房型,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(); +})(); \ No newline at end of file