feat: 自动化执行逻辑开发
This commit is contained in:
@@ -1,38 +1,97 @@
|
||||
import { chromium } from 'playwright';
|
||||
import log from 'electron-log';
|
||||
import checkLoginStatusPkg from './common/checkLoginStatus.js';
|
||||
|
||||
const checkLoginStatus = async (page) => {
|
||||
try {
|
||||
const currentUrl = await page.url();
|
||||
const { checkLoginStatus } = checkLoginStatusPkg;
|
||||
|
||||
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;
|
||||
const parseDateInput = (dateStr) => {
|
||||
if (!dateStr) return null;
|
||||
if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr);
|
||||
if (dateStr instanceof Date && !Number.isNaN(dateStr.getTime())) return dateStr;
|
||||
const raw = String(dateStr).trim();
|
||||
const ymdMatch = raw.match(/(\d{4}-\d{2}-\d{2})/);
|
||||
if (ymdMatch) return new Date(`${ymdMatch[1]}T00:00:00`);
|
||||
const mmddMatch = raw.match(/(\d{2}-\d{2})/);
|
||||
if (mmddMatch) {
|
||||
const year = new Date().getFullYear();
|
||||
return new Date(`${year}-${mmddMatch[1]}T00:00:00`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatMMDD = (d) => {
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
return `${mm}-${dd}`;
|
||||
};
|
||||
|
||||
const buildDateList = (startDate, endDate) => {
|
||||
const start = parseDateInput(startDate);
|
||||
const end = parseDateInput(endDate || startDate);
|
||||
if (!start || !end) return [];
|
||||
|
||||
const from = start <= end ? start : end;
|
||||
const to = start <= end ? end : start;
|
||||
|
||||
const dates = [];
|
||||
const cur = new Date(from.getTime());
|
||||
while (cur <= to) {
|
||||
dates.push(formatMMDD(cur));
|
||||
cur.setDate(cur.getDate() + 1);
|
||||
}
|
||||
return dates;
|
||||
};
|
||||
|
||||
const findHeaderDateIndex = async (container, mmdd) => {
|
||||
for (let attempt = 0; attempt < 10; attempt++) {
|
||||
const headerCells = container.locator(
|
||||
"xpath=.//div[contains(@class,'headerRow')]/div[contains(@class,'headerContent')]",
|
||||
);
|
||||
const count = await headerCells.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const text = (await headerCells.nth(i).innerText()).replace(/\s+/g, ' ').trim();
|
||||
if (text.includes(mmdd)) return i;
|
||||
}
|
||||
|
||||
await container.evaluate((el) => {
|
||||
el.scrollLeft += 600;
|
||||
});
|
||||
await container.page().waitForTimeout(120);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const toggleRoomByDateIndex = async (page, container, { roomType, dateIndex, operation }) => {
|
||||
const roomAnchor = container.getByText(roomType, { exact: true }).first();
|
||||
await roomAnchor.scrollIntoViewIfNeeded();
|
||||
|
||||
const row = roomAnchor.locator(
|
||||
"xpath=ancestor::*[.//div[contains(@class,'boardRow')]][1]",
|
||||
);
|
||||
const boardRow = row.locator("xpath=.//div[contains(@class,'boardRow')]").first();
|
||||
|
||||
const boardColPosition = dateIndex + 2;
|
||||
const dayColumn = boardRow.locator(`xpath=./div[${boardColPosition}]`);
|
||||
|
||||
const targetStateClass = operation === 'open' ? 'error' : 'success';
|
||||
const expectedCurrentLabel = operation === 'open' ? '满' : '有';
|
||||
const barByLabel = dayColumn
|
||||
.locator(
|
||||
`xpath=.//*[ (contains(@class,'success') or contains(@class,'error')) and .//*[contains(normalize-space(),'${expectedCurrentLabel}')] ]//*[contains(@class,'bar')]`,
|
||||
)
|
||||
.first();
|
||||
const barByClass = dayColumn
|
||||
.locator(`xpath=.//*[contains(@class,'${targetStateClass}')]//*[contains(@class,'bar')]`)
|
||||
.first();
|
||||
const bar = (await barByLabel.count()) > 0 ? barByLabel : barByClass;
|
||||
|
||||
await bar.waitFor({ state: 'visible' });
|
||||
await bar.scrollIntoViewIfNeeded();
|
||||
await bar.click();
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.connectOverCDP(`http://localhost:9222`);
|
||||
const browser = await chromium.connectOverCDP(`http://127.0.0.1:9222`);
|
||||
const context = browser.contexts()[0];
|
||||
|
||||
await context.addInitScript(() => {
|
||||
@@ -102,55 +161,26 @@ const checkLoginStatus = async (page) => {
|
||||
await page.waitForTimeout(4000 + Math.random() * 1000);
|
||||
await page.getByText('房价房量日历').click();
|
||||
|
||||
// 获取网页接口(监听所有请求)
|
||||
await page.on('request', response => {
|
||||
const url = response.url()
|
||||
const roomType = process.env.ROOM_TYPE;
|
||||
const startDate = process.env.START_DATE;
|
||||
const endDate = process.env.END_DATE;
|
||||
const operation = process.env.OPERATION === 'close' ? 'close' : 'open';
|
||||
|
||||
// log.info('request response:', url)
|
||||
if (!roomType || !startDate) {
|
||||
log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.includes('/heinventory/queryRoomTypeAndRatePlanLite')) {
|
||||
// const data = await response.json()
|
||||
const container = page.locator('#price-reserve-table-container');
|
||||
await container.waitFor({ state: 'visible' });
|
||||
|
||||
log.info('interface response:', JSON.parse(response.postData()))
|
||||
const dateList = buildDateList(startDate, endDate);
|
||||
for (const mmdd of dateList) {
|
||||
const dateIndex = await findHeaderDateIndex(container, mmdd);
|
||||
if (dateIndex < 0) {
|
||||
throw new Error(`Date not found in header: ${mmdd}`);
|
||||
}
|
||||
// log.info('URL:', request.url())
|
||||
// log.info('Method:', request.method())
|
||||
// log.info('PostData:', request.postData())
|
||||
})
|
||||
|
||||
// 获取接口返回数据
|
||||
await page.on('response', async (response) => {
|
||||
const url = response.url()
|
||||
|
||||
if (url.includes('/heinventory/queryRoomTypeAndRatePlanLite')) {
|
||||
const data = await response.json()
|
||||
|
||||
log.info('interface response:', data)
|
||||
}
|
||||
})
|
||||
|
||||
// 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();
|
||||
await toggleRoomByDateIndex(page, container, { roomType, dateIndex, operation });
|
||||
await page.waitForTimeout(600 + Math.random() * 600);
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user