feat: add task management and progress reporting

- Implemented task and subtask structures with progress tracking.
- Added reporting functionality to log progress at various stages in hotel room status management scripts.
- Created a task store to manage tasks and their states, including persistence to local storage.
- Updated UI components to display task lists and handle task actions (retry, remove).
- Removed deprecated TaskCard and TaskList components, replacing them with a new structure for better maintainability.
- Enhanced script execution service to emit progress events for UI updates.
This commit is contained in:
DEV_DSW
2026-04-16 16:59:49 +08:00
parent b1f589a674
commit 210e8eb363
24 changed files with 788 additions and 237 deletions

View File

@@ -4,6 +4,10 @@ import tabsPkg from './common/tabs.js';
const { preparePage, safeDisconnectBrowser } = tabsPkg;
function reportProgress(step, percent) {
console.log('__ZN_PROGRESS__' + JSON.stringify({ step, percent }));
}
const parseDateInput = (dateStr) => {
if (!dateStr) return null;
if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr);
@@ -158,6 +162,7 @@ const navigateToRoomStatusManagement = async (page) => {
let browser;
try {
reportProgress('连接本地浏览器', 10);
const groupId = '1816249020842116';
const homeUrl = `https://life.douyin.com/p/home?groupid=${groupId}`;
const priceAmountStateUrl = `https://life.douyin.com/p/travel-ari/hotel/price_amount_state?groupid=${groupId}`;
@@ -175,6 +180,7 @@ const navigateToRoomStatusManagement = async (page) => {
} catch (e2) {}
}
reportProgress('定位目标页面', 30);
// Navigation logic (User provided)
try {
// Try to click the store/login button if visible
@@ -199,12 +205,14 @@ const navigateToRoomStatusManagement = async (page) => {
if (!roomType || !startDate) {
log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.');
reportProgress('执行完成', 100);
return;
}
// Wait for table rows to appear
await page.locator('.lifep-table-row').first().waitFor({ state: 'visible', timeout: 15000 });
reportProgress('操作房态数据', 60);
const dateList = buildDateList(startDate, endDate);
for (const dateStr of dateList) {
try {
@@ -215,11 +223,13 @@ const navigateToRoomStatusManagement = async (page) => {
await page.waitForTimeout(500 + Math.random() * 500);
}
reportProgress('保存并校验', 90);
} catch (error) {
log.error(error);
process.exitCode = 1;
} finally {
await safeDisconnectBrowser(browser);
reportProgress('执行完成', 100);
process.exit(process.exitCode || 0);
}
})();

View File

@@ -4,6 +4,10 @@ import tabsPkg from './common/tabs.js';
const { preparePage, safeDisconnectBrowser } = tabsPkg;
function reportProgress(step, percent) {
console.log('__ZN_PROGRESS__' + JSON.stringify({ step, percent }));
}
const parseDateInput = (dateStr) => {
if (!dateStr) return null;
if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr);
@@ -158,6 +162,7 @@ const navigateToRoomStatusManagement = async (page) => {
let browser;
try {
reportProgress('连接本地浏览器', 10);
const groupId = '1816249020842116';
const homeUrl = `https://life.douyin.com/p/home?groupid=${groupId}`;
const priceAmountStateUrl = `https://life.douyin.com/p/travel-ari/hotel/price_amount_state?groupid=${groupId}`;
@@ -175,6 +180,7 @@ const navigateToRoomStatusManagement = async (page) => {
} catch (e2) {}
}
reportProgress('定位目标页面', 30);
// Navigation logic (User provided)
try {
// Try to click the store/login button if visible
@@ -199,12 +205,14 @@ const navigateToRoomStatusManagement = async (page) => {
if (!roomType || !startDate) {
log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.');
reportProgress('执行完成', 100);
return;
}
// Wait for table rows to appear
await page.locator('.lifep-table-row').first().waitFor({ state: 'visible', timeout: 15000 });
reportProgress('操作房态数据', 60);
const dateList = buildDateList(startDate, endDate);
for (const dateStr of dateList) {
try {
@@ -215,11 +223,13 @@ const navigateToRoomStatusManagement = async (page) => {
await page.waitForTimeout(500 + Math.random() * 500);
}
reportProgress('保存并校验', 90);
} catch (error) {
log.error(error);
process.exitCode = 1;
} finally {
await safeDisconnectBrowser(browser);
reportProgress('执行完成', 100);
process.exit(process.exitCode || 0);
}
})();

View File

@@ -4,6 +4,10 @@ import tabsPkg from './common/tabs.js';
const { preparePage, safeDisconnectBrowser } = tabsPkg;
function reportProgress(step, percent) {
console.log('__ZN_PROGRESS__' + JSON.stringify({ step, percent }));
}
const parseDateInput = (dateStr) => {
if (!dateStr) return null;
if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr);
@@ -137,6 +141,7 @@ const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation
let browser;
try {
reportProgress('连接本地浏览器', 10);
const targetUrl = 'https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1';
const prepared = await preparePage(chromium, { targetUrl });
browser = prepared.browser;
@@ -144,6 +149,7 @@ const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation
await page.waitForTimeout(4000 + Math.random() * 300);
reportProgress('定位目标页面', 30);
await ensureRoomPriceCalendar(page);
const roomType = process.env.ROOM_TYPE;
@@ -153,12 +159,14 @@ const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation
if (!roomType || !startDate) {
log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.');
reportProgress('执行完成', 100);
return;
}
const container = page.locator('#price-reserve-table-container');
await container.waitFor({ state: 'visible' });
reportProgress('操作房态数据', 60);
const dateList = buildDateList(startDate, endDate);
for (const mmdd of dateList) {
const dateIndex = await findHeaderDateIndex(container, mmdd);
@@ -167,12 +175,15 @@ const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation
}
await toggleRoomByDateIndex(container, { roomType, dateIndex, operation });
await page.waitForTimeout(600 + Math.random() * 600);
}
}
reportProgress('保存并校验', 90);
} catch (error) {
log.error(error);
process.exitCode = 1;
} finally {
await safeDisconnectBrowser(browser);
reportProgress('执行完成', 100);
process.exit(process.exitCode || 0);
}
})();

View File

@@ -4,6 +4,10 @@ import tabsPkg from './common/tabs.js';
const { preparePage, safeDisconnectBrowser } = tabsPkg;
function reportProgress(step, percent) {
console.log('__ZN_PROGRESS__' + JSON.stringify({ step, percent }));
}
const parseDateInput = (dateStr) => {
if (!dateStr) return null;
if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr);
@@ -173,6 +177,7 @@ const toggleRoom = async (page, { roomType, mmdd, operation }) => {
let browser;
try {
reportProgress('连接本地浏览器', 10);
const targetUrl = 'https://me.meituan.com/ebooking/merchant/product#/index';
const prepared = await preparePage(chromium, { targetUrl });
browser = prepared.browser;
@@ -180,6 +185,7 @@ const toggleRoom = async (page, { roomType, mmdd, operation }) => {
await page.waitForTimeout(4000 + Math.random() * 300);
reportProgress('定位目标页面', 30);
// Navigation logic from user snippet
try {
const menu = page.locator('#vina-menu-item-100114001 > .lz-submenu-title');
@@ -194,7 +200,7 @@ const toggleRoom = async (page, { roomType, mmdd, operation }) => {
await page.waitForTimeout(1000);
}
}
const calendarLink = page.locator('a').filter({ hasText: '房价房量日历' });
if (await calendarLink.isVisible()) {
await calendarLink.click();
@@ -211,12 +217,14 @@ const toggleRoom = async (page, { roomType, mmdd, operation }) => {
if (!roomType || !startDate) {
log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.');
reportProgress('执行完成', 100);
return;
}
// Wait for table
await page.locator('.vxe-table--body-wrapper').waitFor({ state: 'visible', timeout: 15000 });
reportProgress('操作房态数据', 60);
const dateList = buildDateList(startDate, endDate);
for (const mmdd of dateList) {
try {
@@ -227,11 +235,13 @@ const toggleRoom = async (page, { roomType, mmdd, operation }) => {
await page.waitForTimeout(600 + Math.random() * 600);
}
reportProgress('保存并校验', 90);
} catch (error) {
log.error(error);
process.exitCode = 1;
} finally {
await safeDisconnectBrowser(browser);
reportProgress('执行完成', 100);
process.exit(process.exitCode || 0);
}
})();