feat: 打开渠道与脚本自动化分离
This commit is contained in:
2
global.d.ts
vendored
2
global.d.ts
vendored
@@ -82,6 +82,8 @@ declare global {
|
|||||||
},
|
},
|
||||||
// 执行脚本
|
// 执行脚本
|
||||||
executeScript: (options: any) => Promise<{success: boolean, error?: string}>,
|
executeScript: (options: any) => Promise<{success: boolean, error?: string}>,
|
||||||
|
// 打开渠道
|
||||||
|
openChannel: (channels: any) => Promise<{success: boolean, error?: string}>,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ export enum IPC_EVENTS {
|
|||||||
|
|
||||||
// 执行脚本
|
// 执行脚本
|
||||||
EXECUTE_SCRIPT = 'execute-script',
|
EXECUTE_SCRIPT = 'execute-script',
|
||||||
|
|
||||||
|
// 打开渠道
|
||||||
|
OPEN_CHANNEL = 'open-channel',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAIN_WIN_SIZE = {
|
export const MAIN_WIN_SIZE = {
|
||||||
|
|||||||
@@ -3,17 +3,74 @@ 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 { chromium } from 'playwright';
|
||||||
|
import { addStealthInit, connectCdpContext, safeDisconnectBrowser } from '@main/utils/chrome/cdp'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
const openedTabIndexByChannelName = new Map<string, number>()
|
||||||
|
|
||||||
export function runTaskOperationService() {
|
export function runTaskOperationService() {
|
||||||
const executeScriptServiceInstance = new executeScriptService();
|
const executeScriptServiceInstance = new executeScriptService();
|
||||||
|
// 打开渠道
|
||||||
|
ipcMain.handle(IPC_EVENTS.OPEN_CHANNEL, async (_event, channels: any) => {
|
||||||
|
try {
|
||||||
|
await launchLocalChrome()
|
||||||
|
|
||||||
|
const arr = Array.isArray(channels) ? channels : []
|
||||||
|
const keys = arr
|
||||||
|
.map((it: any) => typeof it === 'string' ? it : it.channelName)
|
||||||
|
.filter(Boolean)
|
||||||
|
if (keys.length === 0) return { success: true }
|
||||||
|
|
||||||
|
const urlMap: Record<string, string> = {}
|
||||||
|
|
||||||
|
for (const it of arr) {
|
||||||
|
const name = typeof it === 'string' ? it : it.channelName
|
||||||
|
const url = typeof it === 'string' ? undefined : it?.channelUrl
|
||||||
|
if (name && url) urlMap[name] = url
|
||||||
|
}
|
||||||
|
|
||||||
|
const { browser, context } = await connectCdpContext(chromium)
|
||||||
|
await addStealthInit(context)
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i]
|
||||||
|
const targetUrl = urlMap[key]
|
||||||
|
|
||||||
|
let pages = await context.pages()
|
||||||
|
|
||||||
|
while (pages.length <= i) {
|
||||||
|
await context.newPage()
|
||||||
|
pages = await context.pages()
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = pages[i]
|
||||||
|
await page.bringToFront()
|
||||||
|
|
||||||
|
if (targetUrl) {
|
||||||
|
const current = page.url()
|
||||||
|
|
||||||
|
if (!current || !current.startsWith(targetUrl)) {
|
||||||
|
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openedTabIndexByChannelName.set(key, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
await safeDisconnectBrowser(browser)
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: (error as any)?.message || 'open channel failed' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => {
|
ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => {
|
||||||
try {
|
try {
|
||||||
await launchLocalChrome(options)
|
|
||||||
|
|
||||||
// 从options.roomList列表中找到对应的名称
|
// 从options.roomList列表中找到对应的名称
|
||||||
const roomType = options.roomList.find((item: any) => item.id === options.roomType);
|
const roomType = options.roomList.find((item: any) => item.id === options.roomType);
|
||||||
|
|
||||||
@@ -44,13 +101,21 @@ export function runTaskOperationService() {
|
|||||||
const results: any[] = []
|
const results: any[] = []
|
||||||
for (let i = 0; i < scriptPaths.length; i++) {
|
for (let i = 0; i < scriptPaths.length; i++) {
|
||||||
const item = scriptPaths[i]
|
const item = scriptPaths[i]
|
||||||
|
const channelNameMap: Record<string, string> = {
|
||||||
|
fzName: 'fliggy',
|
||||||
|
mtName: 'meituan',
|
||||||
|
dyHotelName: 'douyin',
|
||||||
|
dyHotSpringName: 'douyin',
|
||||||
|
}
|
||||||
|
const mappedName = channelNameMap[item.channel]
|
||||||
|
const tabIndex = mappedName ? (openedTabIndexByChannelName.get(mappedName) ?? i) : i
|
||||||
log.info(`Launching script for channel ${item.channel}: ${item.scriptPath}`)
|
log.info(`Launching script for channel ${item.channel}: ${item.scriptPath}`)
|
||||||
const result = await executeScriptServiceInstance.executeScript(item.scriptPath, {
|
const result = await executeScriptServiceInstance.executeScript(item.scriptPath, {
|
||||||
roomType: roomType[item.channel],
|
roomType: roomType[item.channel],
|
||||||
startTime: options.startTime,
|
startTime: options.startTime,
|
||||||
endTime: options.endTime,
|
endTime: options.endTime,
|
||||||
operation: options.operation,
|
operation: options.operation,
|
||||||
tabIndex: i,
|
tabIndex,
|
||||||
})
|
})
|
||||||
results.push({
|
results.push({
|
||||||
channel: item.channel,
|
channel: item.channel,
|
||||||
|
|||||||
39
src/main/scripts/common/tabs.js
Normal file
39
src/main/scripts/common/tabs.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const preparePage = async (chromium, { tabIndex = Number(process.env.TAB_INDEX), endpoint = process.env.CDP_ENDPOINT || 'http://127.0.0.1:9222' } = {}) => {
|
||||||
|
const browser = await chromium.connectOverCDP(endpoint);
|
||||||
|
const context = browser.contexts()[0];
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('No browser context available');
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.addInitScript(() => {
|
||||||
|
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
const idx = Number.isFinite(tabIndex) && tabIndex >= 0 ? Math.floor(tabIndex) : 0;
|
||||||
|
let pages = await context.pages();
|
||||||
|
while (pages.length <= idx) {
|
||||||
|
await context.newPage();
|
||||||
|
pages = await context.pages();
|
||||||
|
}
|
||||||
|
const page = pages[idx];
|
||||||
|
await page.bringToFront();
|
||||||
|
|
||||||
|
return { browser, context, page };
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeDisconnectBrowser = async (browser) => {
|
||||||
|
if (!browser) return;
|
||||||
|
try {
|
||||||
|
if (typeof browser.disconnect === 'function') {
|
||||||
|
await browser.disconnect();
|
||||||
|
} else {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
preparePage,
|
||||||
|
safeDisconnectBrowser,
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
import checkLoginStatusPkg from './common/checkLoginStatus.js';
|
import tabsPkg from './common/tabs.js';
|
||||||
|
|
||||||
const { checkLoginStatus } = checkLoginStatusPkg;
|
const { preparePage, safeDisconnectBrowser } = tabsPkg;
|
||||||
|
|
||||||
const parseDateInput = (dateStr) => {
|
const parseDateInput = (dateStr) => {
|
||||||
if (!dateStr) return null;
|
if (!dateStr) return null;
|
||||||
@@ -61,7 +61,7 @@ const findHeaderDateIndex = async (container, mmdd) => {
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleRoomByDateIndex = async (page, container, { roomType, dateIndex, operation }) => {
|
const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation }) => {
|
||||||
const roomAnchor = container.getByText(roomType, { exact: true }).first();
|
const roomAnchor = container.getByText(roomType, { exact: true }).first();
|
||||||
await roomAnchor.scrollIntoViewIfNeeded();
|
await roomAnchor.scrollIntoViewIfNeeded();
|
||||||
|
|
||||||
@@ -91,70 +91,17 @@ const toggleRoomByDateIndex = async (page, container, { roomType, dateIndex, ope
|
|||||||
};
|
};
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
|
let browser;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const context = browser.contexts()[0];
|
const prepared = await preparePage(chromium);
|
||||||
|
browser = prepared.browser;
|
||||||
|
const page = prepared.page;
|
||||||
|
|
||||||
await context.addInitScript(() => {
|
const targetUrl = 'https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1';
|
||||||
Object.defineProperty(navigator, 'webdriver', { get: ()=> undefined });
|
const currentUrl = page.url();
|
||||||
});
|
if (!currentUrl || !currentUrl.startsWith(targetUrl)) {
|
||||||
|
await page.goto(targetUrl);
|
||||||
const pages = await context.pages();
|
|
||||||
const tabIndex = Number(process.env.TAB_INDEX);
|
|
||||||
const page = pages.length ? pages[tabIndex] : 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('hoteltmwq123', { 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('Tmwq654321', { 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.waitForTimeout(4000 + Math.random() * 300);
|
||||||
@@ -182,21 +129,13 @@ const toggleRoomByDateIndex = async (page, container, { roomType, dateIndex, ope
|
|||||||
if (dateIndex < 0) {
|
if (dateIndex < 0) {
|
||||||
throw new Error(`Date not found in header: ${mmdd}`);
|
throw new Error(`Date not found in header: ${mmdd}`);
|
||||||
}
|
}
|
||||||
await toggleRoomByDateIndex(page, container, { roomType, dateIndex, operation });
|
await toggleRoomByDateIndex(container, { roomType, dateIndex, operation });
|
||||||
await page.waitForTimeout(600 + Math.random() * 600);
|
await page.waitForTimeout(600 + Math.random() * 600);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
}finally {
|
}finally {
|
||||||
if (browser) {
|
await safeDisconnectBrowser(browser);
|
||||||
try {
|
|
||||||
if (typeof browser.disconnect === 'function') {
|
|
||||||
await browser.disconnect();
|
|
||||||
} else {
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,48 +1,24 @@
|
|||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
|
import tabsPkg from './common/tabs.js';
|
||||||
const getOrCreateTab = async (context, targetIndex) => {
|
const { preparePage, safeDisconnectBrowser } = tabsPkg;
|
||||||
const idx =
|
|
||||||
Number.isFinite(targetIndex) && targetIndex >= 0 ? Math.floor(targetIndex) : 1;
|
|
||||||
|
|
||||||
let pages = await context.pages();
|
|
||||||
while (pages.length <= idx) {
|
|
||||||
await context.newPage();
|
|
||||||
pages = await context.pages();
|
|
||||||
}
|
|
||||||
const page = pages[idx];
|
|
||||||
await page.bringToFront();
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
|
let browser;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const context = browser.contexts()[0];
|
const prepared = await preparePage(chromium);
|
||||||
if (!context) {
|
browser = prepared.browser;
|
||||||
throw new Error('No browser context available');
|
const page = prepared.page;
|
||||||
|
const targetUrl = 'https://me.meituan.com/ebooking/merchant/product#/index';
|
||||||
|
const currentUrl = page.url();
|
||||||
|
if (!currentUrl || !currentUrl.startsWith(targetUrl)) {
|
||||||
|
await page.goto(targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
||||||
});
|
|
||||||
|
|
||||||
const tabIndex = Number(process.env.TAB_INDEX);
|
|
||||||
const page = await getOrCreateTab(context, tabIndex);
|
|
||||||
await page.goto('https://me.meituan.com/ebooking/merchant/product#/index');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
} finally {
|
} finally {
|
||||||
if (browser) {
|
await safeDisconnectBrowser(browser);
|
||||||
try {
|
|
||||||
if (typeof browser.disconnect === 'function') {
|
|
||||||
await browser.disconnect();
|
|
||||||
} else {
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
30
src/main/utils/chrome/cdp.ts
Normal file
30
src/main/utils/chrome/cdp.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { Browser, BrowserContext } from 'playwright'
|
||||||
|
|
||||||
|
export async function connectCdpContext(chromium: any, endpoint = 'http://127.0.0.1:9222'): Promise<{ browser: Browser, context: BrowserContext }> {
|
||||||
|
const browser = await chromium.connectOverCDP(endpoint)
|
||||||
|
const context = browser.contexts()[0]
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('No browser context available')
|
||||||
|
}
|
||||||
|
return { browser, context }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addStealthInit(context: BrowserContext) {
|
||||||
|
await context.addInitScript(() => {
|
||||||
|
Object.defineProperty(navigator, 'webdriver', { get: () => undefined })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function safeDisconnectBrowser(browser: any) {
|
||||||
|
if (!browser) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof browser.disconnect === 'function') {
|
||||||
|
await browser.disconnect()
|
||||||
|
} else {
|
||||||
|
await browser.close()
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@ import { spawn } from 'child_process';
|
|||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
|
|
||||||
// 启动本地浏览器
|
// 启动本地浏览器
|
||||||
export async function launchLocalChrome(options: any) {
|
export async function launchLocalChrome() {
|
||||||
const chromePath = getChromePath();
|
const chromePath = getChromePath();
|
||||||
|
|
||||||
// 多账号隔离
|
// 多账号隔离
|
||||||
const profileDir = getProfileDir('default');
|
const profileDir = getProfileDir('default');
|
||||||
log.info(`Launching Chrome with user data dir: ${options}`);
|
log.info(`Launching Chrome with user data dir: ${profileDir}`);
|
||||||
|
|
||||||
// 检查端口是否被占用
|
// 检查端口是否被占用
|
||||||
const portInUse = await isPortInUse(9222);
|
const portInUse = await isPortInUse(9222);
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ const api: WindowApi = {
|
|||||||
|
|
||||||
// 执行脚本
|
// 执行脚本
|
||||||
executeScript: (params: any) => ipcRenderer.invoke(IPC_EVENTS.EXECUTE_SCRIPT, params),
|
executeScript: (params: any) => ipcRenderer.invoke(IPC_EVENTS.EXECUTE_SCRIPT, params),
|
||||||
|
|
||||||
|
// 打开渠道
|
||||||
|
openChannel: (channels: any) => ipcRenderer.invoke(IPC_EVENTS.OPEN_CHANNEL, channels),
|
||||||
}
|
}
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('api', api)
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: number
|
id: string
|
||||||
channelName: string
|
channelName: string
|
||||||
|
channelUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const channel: Item[] = [
|
export const channels: Item[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: uuidv4(),
|
||||||
channelName: 'pms',
|
channelName: 'fliggy',
|
||||||
|
channelUrl: 'https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: uuidv4(),
|
||||||
channelName: 'xc',
|
channelName: 'meituan',
|
||||||
|
channelUrl: 'https://me.meituan.com/ebooking/merchant/product#/index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: uuidv4(),
|
||||||
channelName: 'fz',
|
channelName: 'douyin',
|
||||||
},
|
channelUrl: 'https://life.douyin.com/p/goods_winetour/physical_room_list?groupid=1816249020842116',
|
||||||
{
|
}
|
||||||
id: 4,
|
|
||||||
channelName: 'mt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
channelName: 'dy',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
@@ -5,7 +5,7 @@ export interface taskCenterItem {
|
|||||||
desc: string,
|
desc: string,
|
||||||
id: string,
|
id: string,
|
||||||
icon: string,
|
icon: string,
|
||||||
type: 'sale' | 'close' | 'open'
|
type: 'sale' | 'close' | 'open' | 'channel'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const taskCenterList: taskCenterItem[] = [
|
export const taskCenterList: taskCenterItem[] = [
|
||||||
@@ -16,6 +16,13 @@ export const taskCenterList: taskCenterItem[] = [
|
|||||||
icon: '销',
|
icon: '销',
|
||||||
type: 'sale'
|
type: 'sale'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '一键打开各渠道',
|
||||||
|
desc: '人工账号登录,为自动化操作做好准备',
|
||||||
|
id: uuidv4(),
|
||||||
|
icon: '渠',
|
||||||
|
type: 'channel'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '关渠道房型',
|
title: '关渠道房型',
|
||||||
desc: '关闭销售渠道下的指定房型',
|
desc: '关闭销售渠道下的指定房型',
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { taskCenterList, taskCenterItem } from '@constant/taskCenterList'
|
import { taskCenterList, taskCenterItem } from '@constant/taskCenterList'
|
||||||
|
import { channels } from '@constant/channel'
|
||||||
import emitter from '@utils/emitter'
|
import emitter from '@utils/emitter'
|
||||||
|
|
||||||
const taskList = computed(() => taskCenterList)
|
const taskList = computed(() => taskCenterList)
|
||||||
@@ -43,6 +44,12 @@ const handleTaskItem = (item: taskCenterItem) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一键打开各渠道
|
||||||
|
if (item.type === 'channel') {
|
||||||
|
window.api.openChannel(channels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 操作房型
|
// 操作房型
|
||||||
emitter.emit('OPERATION_CHANNEL', item)
|
emitter.emit('OPERATION_CHANNEL', item)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user