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