UNPKG

unicorn-rpa

Version:

🦄 Unicorn RPA - 强大的跨域iframe自动化控制工具

1,110 lines (978 loc) 50.7 kB
const simulateHumanTyping = require('./simulateHumanTyping'); const { delay, log, errorLog, safeQuerySelector, safeQuerySelectorAll } = require('../utils'); const actionHandlers = { async fill(action, frameDoc, results) { const el = safeQuerySelector(action.selector, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); if (action.value) { log('执行文本输入:', action.selector, action.value); await simulateHumanTyping(el, action.value, action.speed || 60); await delay(action.delay !== undefined ? action.delay : 800); } }, async select(action, frameDoc, results) { const sel = safeQuerySelector(action.selector, frameDoc); if (!sel) throw new Error(`选择器 ${action.selector} 未找到`); log('执行下拉选择:', action.selector, action.value); sel.click() // sel.dispatchEvent(new MouseEvent("mousedown", { bubbles: true })); // sel.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); await delay(action.delay !== undefined ? action.delay : 800); const allElements = [].slice.call(safeQuerySelectorAll("*")); const options = allElements.filter((el) => { const text = (el.textContent && el.textContent.trim()) || ''; if (!text || !text.includes(action.value)) return false; if (el === sel || sel.contains(el)) return false; return ( el.tagName === "OPTION" || el.tagName === "LI" || (el.tagName === "DIV" && (el.textContent && el.textContent.trim()) === action.value) || (el.tagName === "SPAN" && (el.textContent && el.textContent.trim()) === action.value) || el.classList.contains("option") || el.classList.contains("item") || el.classList.contains("select-item") || el.classList.contains("ant-select-item") || el.classList.contains("el-select-dropdown__item") ); }); // 修改查找逻辑,增加对单个选项的特殊处理 let target = options.find(opt => { const text = (opt.textContent && opt.textContent.trim()) || ''; return text === action.value || text.includes(action.value); }); // 如果没有找到完全匹配的选项,尝试更宽松的匹配方式 if (!target && options.length > 0) { target = options[0]; // 使用第一个选项作为备选 log('使用备选方案选择第一个选项:', target.textContent && target.textContent.trim()); } if (target) { // 确保元素在视口中并可点击 if (target.scrollIntoViewIfNeeded) { target.scrollIntoViewIfNeeded(); } else if (target.scrollIntoView) { target.scrollIntoView(); } // 使用多种点击方式确保点击生效 target.click(); // 添加额外的事件触发确保表单更新 target.dispatchEvent(new Event('change', { bubbles: true })); target.dispatchEvent(new Event('input', { bubbles: true })); log('成功选择选项:', action.value); // 等待选项选择完成 await delay(500); // 验证选择是否成功 - 检查选择框的值是否已更新 const selectedValue = sel.textContent || sel.innerText || sel.value || ''; log('选择后的值:', selectedValue); // 如果选择框的值没有更新,尝试强制更新 if (!selectedValue.includes(action.value)) { log('选择框值未更新,尝试强制更新...'); // 方法1: 尝试通过属性设置值 if (sel.hasAttribute('data-value')) { sel.setAttribute('data-value', action.value); } // 方法2: 查找选择框内的文本元素并修改 const textElements = sel.querySelectorAll('span, div, p, .ant-select-selection-item'); let textModified = false; for (const textEl of textElements) { if (textEl.textContent && textEl.textContent.trim()) { textEl.textContent = action.value; textModified = true; log('直接修改选择框文本为:', action.value); break; } } if (textModified) { // 触发必要的事件 sel.dispatchEvent(new Event('change', { bubbles: true })); sel.dispatchEvent(new Event('input', { bubbles: true })); sel.dispatchEvent(new Event('blur', { bubbles: true })); log('强制更新完成'); } // 再次验证 await delay(200); const finalValue = sel.textContent || sel.innerText || sel.value || ''; log('强制更新后的值:', finalValue); if (finalValue.includes(action.value)) { log('强制更新成功'); } else { log('警告:强制更新后值仍未正确设置'); } } await delay(action.delay !== undefined ? action.delay : 800); } else { log('未找到匹配的选项:', action.value); } }, async click(action, frameDoc, results) { let btn = safeQuerySelector(action.selector, frameDoc); if (action.text) { const candidates = [].slice.call(safeQuerySelectorAll(action.selector, frameDoc)); btn = candidates.find(el => (el.innerText || el.textContent).trim() === action.text); } if (!btn) throw new Error(`选择器 ${action.selector} (text: ${action.text || ''}) 未找到`); log('执行点击:', action.selector, action.text || ''); btn.click(); await delay(action.delay !== undefined ? action.delay : 800); }, async doubleClick(action, frameDoc, results) { let btn = safeQuerySelector(action.selector, frameDoc); if (action.text) { const candidates = [].slice.call(safeQuerySelectorAll(action.selector, frameDoc)); btn = candidates.find(el => (el.innerText || el.textContent).trim() === action.text); } if (!btn) throw new Error(`选择器 ${action.selector} 未找到`); log('执行双击:', action.selector); btn.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })); await delay(action.delay !== undefined ? action.delay : 800); }, async rightClick(action, frameDoc, results) { let btn = safeQuerySelector(action.selector, frameDoc); if (action.text) { const candidates = [].slice.call(safeQuerySelectorAll(action.selector, frameDoc)); btn = candidates.find(el => (el.innerText || el.textContent).trim() === action.text); } if (!btn) throw new Error(`选择器 ${action.selector} 未找到`); log('执行右键点击:', action.selector); btn.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true })); await delay(action.delay !== undefined ? action.delay : 800); }, async hover(action, frameDoc, results) { const el = safeQuerySelector(action.selector, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); log('执行悬停:', action.selector); el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await delay(action.delay !== undefined ? action.delay : 800); }, async scroll(action, frameDoc, results) { const el = safeQuerySelector(action.selector, frameDoc) || window; log('执行滚动:', action.selector, { x: action.x, y: action.y }); el.scrollTo({ top: action.y || 0, left: action.x || 0, behavior: action.behavior || 'smooth' }); await delay(action.delay !== undefined ? action.delay : 800); }, async screenshot(action, frameDoc, results) { if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { try { log('开始截图...'); const stream = await navigator.mediaDevices.getDisplayMedia({ video: true }); const track = stream.getVideoTracks()[0]; const imageCapture = new ImageCapture(track); const bitmap = await imageCapture.grabFrame(); const canvas = document.createElement('canvas'); canvas.width = bitmap.width; canvas.height = bitmap.height; const context = canvas.getContext('2d'); context.drawImage(bitmap, 0, 0); const dataUrl = canvas.toDataURL('image/png'); results.push({ type: 'screenshot', data: dataUrl }); track.stop(); log('截图完成'); await delay(action.delay !== undefined ? action.delay : 800); } catch (error) { errorLog('截图失败:', error); } } else { errorLog('浏览器不支持截图功能'); } }, async waitFor(action, frameDoc, results) { const timeout = action.timeout || 5000; log('等待元素出现:', action.selector, `超时: ${timeout}ms`); await new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { observer.disconnect(); reject(new Error(`等待超时: ${action.selector}`)); }, timeout); const observer = new MutationObserver(() => { if (safeQuerySelector(action.selector, frameDoc)) { clearTimeout(timeoutId); observer.disconnect(); log('元素已出现:', action.selector); resolve(); } }); observer.observe(frameDoc, { childList: true, subtree: true }); if (safeQuerySelector(action.selector, frameDoc)) { clearTimeout(timeoutId); observer.disconnect(); log('元素已存在:', action.selector); resolve(); } }); }, async wait(action, frameDoc, results) { const duration = action.duration || 1000; log('等待固定时长:', duration + 'ms'); await delay(duration); }, async treeSelect(action, frameDoc, results) { const sel = safeQuerySelector(action.selector, frameDoc); if (!sel) throw new Error(`选择器 ${action.selector} 未找到`); log('执行树形选择:', action.selector, action.value); sel.click(); // sel.dispatchEvent(new MouseEvent("mousedown", { bubbles: true })); // sel.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })); await delay(action.delay !== undefined ? action.delay : 800); for (let i = 0; i < action.value.length; i++) { const element = action.value[i]; const node = safeQuerySelector(`[title="${element}"], [data-label="${element}"]`); if (!node) throw new Error(`找不到节点: ${element}`); node.click(); await delay(action.delay !== undefined ? action.delay : 800); } const trigger = safeQuerySelector(action.selector, frameDoc); if (trigger) { trigger.click(); await delay(action.delay !== undefined ? action.delay : 800); } }, async checkAllCheckboxes(action, frameDoc, results) { const isSpinning = () => { const selectors = [ '.ant-spin.ant-spin-spinning', '.el-loading-spinner', '.van-loading', '.spinner-border', '.spinner-grow' ]; const allLoading = [].slice.call(safeQuerySelectorAll(selectors.join(','))) .filter(el => el.offsetParent !== null); return allLoading.length > 0; }; if (isSpinning()) { log('检测到loading状态,等待结束...'); await new Promise((resolve, reject) => { const timeout = setTimeout( () => reject(new Error("等待loading转圈结束超时")), action.timeout || 10000 ); function check() { if (!isSpinning()) { clearTimeout(timeout); log('loading状态结束'); resolve(); } else { setTimeout(check, 100); } } check(); }); } let scope = frameDoc; if (action.selector) { const container = safeQuerySelector(action.selector, frameDoc); if (container) { scope = container; } } const lis = safeQuerySelectorAll('ul li, .checkbox-item, [class*="checkbox"]', scope); const count = typeof action.value === "number" ? action.value : lis.length; log('开始批量勾选checkbox,总数:', lis.length, '目标数量:', count); let checked = 0; for (let i = 0; i < lis.length; i++) { const li = lis[i]; if (checked >= count) break; const checkbox = li.querySelector( '.ant-checkbox, input[type="checkbox"], [class*="checkbox"]' ); if (checkbox) { checkbox.click(); checked++; await delay(action.delay !== undefined ? action.delay : 800); } } log('批量勾选完成,已勾选:', checked); }, async getText(action, frameDoc, results) { const el = safeQuerySelector(action.selector, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); const text = el.innerText || el.textContent || ''; results.push({ type: 'getText', selector: action.selector, value: text }); log('获取文本内容:', action.selector, text); await delay(action.delay !== undefined ? action.delay : 800); }, async getValue(action, frameDoc, results) { const el = safeQuerySelector(action.selector, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); const value = el.value || el.getAttribute('value') || ''; results.push({ type: 'getValue', selector: action.selector, value }); log('获取元素值:', action.selector, value); await delay(action.delay !== undefined ? action.delay : 800); }, async timeRange(action, frameDoc, results) { // 支持多种组件库的时间范围选择器 const el = safeQuerySelector(action.selector, frameDoc) || safeQuerySelector(`[data-testid="${action.selector}"], [data-cy="${action.selector}"], [data-id="${action.selector}"]`, frameDoc) || safeQuerySelector(`.${action.selector}, #${action.selector}`, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); log('执行时间范围选择:', action.selector, action.value); // 确保传入的值是数组格式 if (!Array.isArray(action.value) || action.value.length !== 2) { throw new Error('时间范围值必须是包含两个日期的数组'); } const [startDate, endDate] = action.value; // 点击时间范围选择器打开日期面板 - 支持多种组件库 el.click(); await delay(action.delay !== undefined ? action.delay : 800); // 查找日期面板 - 支持多种组件库(移除 ant-calendar 相关内容) const datePanel = safeQuerySelector(` .ant-picker-dropdown, .ant-picker-panel, .ant-picker-panel-container, .el-date-picker, .el-date-editor, .el-calendar, .van-datetime-picker, .van-picker, .mui-datepicker, .mui-calendar, .bootstrap-datepicker, .datepicker, .flatpickr-calendar, .flatpickr-dropdown, .react-datepicker, .react-datepicker__container, .date-picker, .calendar-picker, .date-dropdown, .el-popper, .el-date-picker__popper, .van-popup, .van-picker-popup, .mui-popover, .mui-datepicker-popup, .bootstrap-datepicker-dropdown, .datepicker-dropdown, .flatpickr-calendar.open, .flatpickr-calendar.arrowTop, .react-datepicker__popper, .react-datepicker__portal, .calendar-popup, .date-picker-popup, .picker-popup, [data-popper-placement], [role="dialog"], [role="menu"] `, frameDoc); if (!datePanel) { throw new Error('未找到日期选择面板'); } // 辅助函数:查找并点击日期,如果找不到则翻页 const findAndClickDate = async (targetDate, isStartDate = true) => { const maxAttempts = 24; // 最多翻24页(2年) let attempts = 0; while (attempts < maxAttempts) { // 查找目标日期 - 支持多种组件库的日期单元格(移除 ant-calendar 相关内容) const dateCell = safeQuerySelector(` td[title="${targetDate}"], td[data-value="${targetDate}"], .ant-picker-cell[title="${targetDate}"], .ant-picker-cell[data-value="${targetDate}"], .el-date-table td[title="${targetDate}"], .el-date-table td[data-value="${targetDate}"], .van-picker-column__item[title="${targetDate}"], .van-picker-column__item[data-value="${targetDate}"], .mui-calendar-day[title="${targetDate}"], .mui-calendar-day[data-value="${targetDate}"], .bootstrap-datepicker td[title="${targetDate}"], .bootstrap-datepicker td[data-value="${targetDate}"], .flatpickr-day[title="${targetDate}"], .flatpickr-day[data-value="${targetDate}"], .react-datepicker__day[title="${targetDate}"], .react-datepicker__day[data-value="${targetDate}"], .calendar-day[title="${targetDate}"], .calendar-day[data-value="${targetDate}"], [data-date="${targetDate}"], [data-day="${targetDate}"], [data-value="${targetDate}"] `, datePanel); if (dateCell) { dateCell.click(); log(`${isStartDate ? '点击开始日期' : '点击结束日期'}:`, targetDate); await delay(action.delay !== undefined ? action.delay : 800); return true; } // 如果找不到,获取当前显示的月份信息 const targetDateObj = new Date(targetDate); // 尝试多种选择器获取当前月份 - 支持多种组件库(移除 ant-calendar 相关内容) const currentMonthText = safeQuerySelector(` .ant-picker-header-view .ant-picker-month-panel, .ant-picker-header-view .ant-picker-year-panel, .ant-picker-header-view, .el-date-picker__header, .el-date-editor__header, .el-calendar__header, .van-picker__title, .van-datetime-picker__title, .mui-datepicker-header, .mui-calendar-header, .bootstrap-datepicker-header, .datepicker-header, .flatpickr-current-month, .flatpickr-header, .react-datepicker__header, .react-datepicker__current-month, .calendar-header, .date-picker-header, .picker-header `, datePanel); if (currentMonthText) { const currentText = currentMonthText.textContent.trim(); log('当前显示月份文本:', currentText); // 尝试从所有可能的元素中获取月份信息 - 支持多种组件库(移除 ant-calendar 相关内容) const allHeaderElements = safeQuerySelectorAll(` .ant-picker-header-view *, .el-date-picker__header *, .el-date-editor__header *, .el-calendar__header *, .van-picker__title *, .van-datetime-picker__title *, .mui-datepicker-header *, .mui-calendar-header *, .bootstrap-datepicker-header *, .datepicker-header *, .flatpickr-current-month *, .flatpickr-header *, .react-datepicker__header *, .react-datepicker__current-month *, .calendar-header *, .date-picker-header *, .picker-header * `, datePanel); log('找到的头部元素数量:', allHeaderElements.length); for (let i = 0; i < allHeaderElements.length; i++) { const element = allHeaderElements[i]; const text = (element.textContent && element.textContent.trim()) || ''; if (text && (text.includes('年') || text.includes('月') || text.includes('202') || text.includes('2024') || text.includes('2025') || text.includes('2026'))) { log(`找到可能的月份元素 ${i}:`, text); } } // 解析当前显示的月份 let currentDate; if (currentText.includes('年') && currentText.includes('月')) { // 中文格式:2024年7月 const match = currentText.match(/(\d{4})年(\d{1,2})月/); if (match) { currentDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, 1); } } else if (currentText.includes('-')) { // 英文格式:Jul 2024 currentDate = new Date(currentText + ' 1'); } else { // 其他格式,尝试直接解析 currentDate = new Date(currentText + ' 1'); } if (currentDate && !isNaN(currentDate.getTime())) { // 比较目标日期与当前显示月份 const targetMonth = new Date(targetDateObj.getFullYear(), targetDateObj.getMonth(), 1); log('目标月份:', targetMonth.toISOString().slice(0, 7)); log('当前显示月份:', currentDate.toISOString().slice(0, 7)); log('目标月份时间戳:', targetMonth.getTime()); log('当前月份时间戳:', currentDate.getTime()); const nextButton = safeQuerySelector(` .ant-picker-header-next-btn, .ant-picker-next-icon, .el-date-picker__header-btn--next, .el-date-editor__header-btn--next, .van-picker__title--next, .van-datetime-picker__title--next, .mui-datepicker-next, .mui-calendar-next, .bootstrap-datepicker-next, .datepicker-next, .flatpickr-next-month, .flatpickr-next, .react-datepicker__navigation--next, .react-datepicker__next-button, .calendar-next, .date-picker-next, .picker-next, [data-action="next"], [data-direction="next"], .next-btn, .next-button `, datePanel); const prevButton = safeQuerySelector(` .ant-picker-header-prev-btn, .ant-picker-prev-icon, .el-date-picker__header-btn--prev, .el-date-editor__header-btn--prev, .van-picker__title--prev, .van-datetime-picker__title--prev, .mui-datepicker-prev, .mui-calendar-prev, .bootstrap-datepicker-prev, .datepicker-prev, .flatpickr-prev-month, .flatpickr-prev, .react-datepicker__navigation--prev, .react-datepicker__previous-button, .calendar-prev, .date-picker-prev, .picker-prev, [data-action="prev"], [data-direction="prev"], .prev-btn, .prev-button `, datePanel); if (targetMonth.getTime() > currentDate.getTime() && nextButton) { // 目标月份在当前月份之后,点击下一页 nextButton.click(); log('目标月份更晚,翻到下一页查找日期:', targetDate); } else if (targetMonth.getTime() < currentDate.getTime() && prevButton) { // 目标月份在当前月份之前,点击上一页 prevButton.click(); log('目标月份更早,翻到上一页查找日期:', targetDate); } else { log(`无法找到${isStartDate ? '开始' : '结束'}日期:`, targetDate); return false; } } else { log('无法解析当前月份,尝试点击下一页'); const nextButton = safeQuerySelector(` .ant-picker-header-next-btn, .ant-picker-next-icon, .el-date-picker__header-btn--next, .el-date-editor__header-btn--next, .van-picker__title--next, .van-datetime-picker__title--next, .mui-datepicker-next, .mui-calendar-next, .bootstrap-datepicker-next, .datepicker-next, .flatpickr-next-month, .flatpickr-next, .react-datepicker__navigation--next, .react-datepicker__next-button, .calendar-next, .date-picker-next, .picker-next, [data-action="next"], [data-direction="next"], .next-btn, .next-button `, datePanel); if (nextButton) { nextButton.click(); } } } else { log('无法获取当前月份信息,根据目标日期判断翻页方向'); // 根据目标日期判断翻页方向 const targetYear = targetDateObj.getFullYear(); const currentYear = new Date().getFullYear(); log('目标年份:', targetYear, '当前年份:', currentYear); const nextButton = safeQuerySelector(` .ant-picker-header-next-btn, .ant-picker-next-icon, .ant-calendar-next-month-btn, .ant-calendar-panel .ant-calendar-next-month-btn, .ant-calendar-dropdown .ant-calendar-next-month-btn, .el-date-picker__header-btn--next, .el-date-editor__header-btn--next, .van-picker__title--next, .van-datetime-picker__title--next, .mui-datepicker-next, .mui-calendar-next, .bootstrap-datepicker-next, .datepicker-next, .flatpickr-next-month, .flatpickr-next, .react-datepicker__navigation--next, .react-datepicker__next-button, .calendar-next, .date-picker-next, .picker-next, [data-action="next"], [data-direction="next"], .next-btn, .next-button `, datePanel); const prevButton = safeQuerySelector(` .ant-picker-header-prev-btn, .ant-picker-prev-icon, .ant-calendar-prev-month-btn, .ant-calendar-panel .ant-calendar-prev-month-btn, .ant-calendar-dropdown .ant-calendar-prev-month-btn, .el-date-picker__header-btn--prev, .el-date-editor__header-btn--prev, .van-picker__title--prev, .van-datetime-picker__title--prev, .mui-datepicker-prev, .mui-calendar-prev, .bootstrap-datepicker-prev, .datepicker-prev, .flatpickr-prev-month, .flatpickr-prev, .react-datepicker__navigation--prev, .react-datepicker__previous-button, .calendar-prev, .date-picker-prev, .picker-prev, [data-action="prev"], [data-direction="prev"], .prev-btn, .prev-button `, datePanel); if (targetYear < currentYear && prevButton) { // 目标年份早于当前年份,向前翻页 prevButton.click(); log('目标年份更早,强制向前翻页查找日期:', targetDate); } else if (targetYear > currentYear && nextButton) { // 目标年份晚于当前年份,向后翻页 nextButton.click(); log('目标年份更晚,向后翻页查找日期:', targetDate); } else if (nextButton) { // 默认向后翻页 nextButton.click(); log('默认向后翻页查找日期:', targetDate); } } await delay(action.delay !== undefined ? action.delay : 800); attempts++; } log(`查找${isStartDate ? '开始' : '结束'}日期超时:`, targetDate); return false; }; // 查找并点击开始日期 await findAndClickDate(startDate, true); // 查找并点击结束日期 await findAndClickDate(endDate, false); // 点击确定按钮确认选择 - 支持多种组件库 const okButton = safeQuerySelector(` .ant-picker-ok, .ant-btn-primary, button[type="button"], .el-date-picker__link-btn, .el-date-editor__confirm-btn, .van-picker__confirm, .van-datetime-picker__confirm, .mui-datepicker-ok, .mui-calendar-ok, .bootstrap-datepicker-ok, .datepicker-ok, .flatpickr-confirm, .flatpickr-ok, .react-datepicker__close-button, .react-datepicker__ok-button, .calendar-ok, .date-picker-ok, .picker-ok, [data-action="confirm"], [data-action="ok"], .confirm-btn, .ok-btn, .ok-button `, datePanel); if (okButton) { okButton.click(); log('点击确定按钮确认时间选择'); } else { // 如果没有找到确定按钮,尝试按回车键 const activeElement = document.activeElement; if (activeElement) { activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); activeElement.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })); log('按回车键确认时间选择'); } } await delay(action.delay !== undefined ? action.delay : 800); log('时间范围选择完成'); }, async antCalendar(action, frameDoc, results) { // 专门处理 Ant Design 日历组件的动作 const el = safeQuerySelector(action.selector, frameDoc); if (!el) throw new Error(`选择器 ${action.selector} 未找到`); log('执行 Ant Design 日历操作:', action.selector, action.value); // 确保传入的值是数组格式 if (!Array.isArray(action.value) || action.value.length !== 2) { throw new Error('Ant Design 日历值必须是包含两个日期的数组'); } const [startDate, endDate] = action.value; // 转换中文日期格式为标准格式 const convertChineseDate = (chineseDate) => { // 处理 'YYYY年MM月DD日' 格式 const match = chineseDate.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/); if (match) { const year = match[1]; const month = match[2].padStart(2, '0'); const day = match[3].padStart(2, '0'); return `${year}-${month}-${day}`; } return chineseDate; // 如果不是中文格式,直接返回 }; const startDateStandard = convertChineseDate(startDate); const endDateStandard = convertChineseDate(endDate); log('转换后的日期:', { startDate: startDateStandard, endDate: endDateStandard }); // 点击日历组件打开面板 el.click(); await delay(action.delay !== undefined ? action.delay : 800); // 查找 Ant Design 日历面板 const calendarPanel = safeQuerySelector(` .ant-calendar-panel, .ant-calendar-dropdown, .ant-calendar-picker-panel, .ant-picker-dropdown, .ant-picker-panel, .ant-picker-panel-container `, frameDoc); if (!calendarPanel) { throw new Error('未找到 Ant Design 日历面板'); } log('找到日历面板:', calendarPanel.className); // 查找日历主体 const calendarBody = safeQuerySelector(` .ant-calendar-body, .ant-picker-body, .ant-picker-content `, calendarPanel); if (!calendarBody) { throw new Error('未找到日历主体'); } // 辅助函数:在日历中查找并点击日期 const findAndClickDateInCalendar = async (targetDate, isStartDate = true) => { const maxAttempts = 240; // 最多翻24页(2年) let attempts = 0; // 生成中文格式的 title 属性(不包含前导零) const targetDateObj = new Date(targetDate); const year = targetDateObj.getFullYear(); const month = targetDateObj.getMonth() + 1; // 月份从0开始 const day = targetDateObj.getDate(); const chineseTitle = `${year}${month}${day}日`; log(`开始查找${isStartDate ? '开始' : '结束'}日期: ${chineseTitle} (${targetDate})`); while (attempts < maxAttempts) { log(`查找日期单元格: ${chineseTitle} (${targetDate}) - 第${attempts + 1}次尝试`); // 查找日期单元格 - 支持多种 Ant Design 选择器 const dateCell = safeQuerySelector(` td[title="${chineseTitle}"], td[title="${targetDate}"], .ant-calendar-cell[title="${chineseTitle}"], .ant-calendar-cell[title="${targetDate}"], .ant-calendar-date[title="${chineseTitle}"], .ant-calendar-date[title="${targetDate}"], .ant-picker-cell[title="${chineseTitle}"], .ant-picker-cell[title="${targetDate}"], [data-value="${targetDate}"], [data-date="${targetDate}"] `, calendarBody); if (dateCell) { // 检查是否被禁用 if (dateCell.classList.contains('ant-calendar-disabled-cell') || dateCell.classList.contains('ant-picker-cell-disabled') || dateCell.hasAttribute('disabled')) { log(`日期单元格被禁用: ${chineseTitle}`); return false; } // 点击日期单元格 dateCell.click(); log(`${isStartDate ? '点击开始日期' : '点击结束日期'}: ${chineseTitle}`); await delay(action.delay !== undefined ? action.delay : 800); return true; } // 如果找不到日期,需要翻页 log(`未找到日期 ${chineseTitle},尝试翻页`); // 获取当前显示的月份信息 const currentMonthElement = safeQuerySelector(` .ant-calendar-header .ant-calendar-header-view, .ant-picker-header-view, .ant-calendar-header-view `, calendarPanel); if (currentMonthElement) { const currentText = currentMonthElement.textContent.trim(); log('当前显示月份:', currentText); // 解析当前月份 let currentDate; if (currentText.includes('年') && currentText.includes('月')) { // 中文格式:2024年7月 const match = currentText.match(/(\d{4})年(\d{1,2})月/); if (match) { currentDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, 1); } } else { // 英文格式或其他格式 currentDate = new Date(currentText + ' 1'); } if (currentDate && !isNaN(currentDate.getTime())) { // 比较目标日期与当前显示月份 const targetMonth = new Date(targetDateObj.getFullYear(), targetDateObj.getMonth(), 1); log('目标月份:', targetMonth.toISOString().slice(0, 7)); log('当前显示月份:', currentDate.toISOString().slice(0, 7)); // 查找翻页按钮 const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon, .ant-calendar-header-next-btn, .ant-calendar-header-next `, calendarPanel); const prevButton = safeQuerySelector(` .ant-calendar-prev-month-btn, .ant-calendar-prev-icon, .ant-picker-header-prev-btn, .ant-picker-prev-icon, .ant-calendar-header-prev-btn, .ant-calendar-header-prev `, calendarPanel); log('找到的翻页按钮:', { nextButton: !!nextButton, prevButton: !!prevButton }); if (targetMonth.getTime() > currentDate.getTime() && nextButton) { // 目标月份在当前月份之后,点击下一页 nextButton.click(); log('目标月份更晚,翻到下一页'); } else if (targetMonth.getTime() < currentDate.getTime() && prevButton) { // 目标月份在当前月份之前,点击上一页 prevButton.click(); log('目标月份更早,翻到上一页'); } else { log(`无法找到${isStartDate ? '开始' : '结束'}日期: ${chineseTitle}`); return false; } } else { log('无法解析当前月份,尝试点击下一页'); const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon `, calendarPanel); if (nextButton) { nextButton.click(); } } } else { log('无法获取当前月份信息,根据目标日期判断翻页方向'); // 根据目标日期判断翻页方向 const targetYear = targetDateObj.getFullYear(); const currentYear = new Date().getFullYear(); log('目标年份:', targetYear, '当前年份:', currentYear); const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon `, calendarPanel); const prevButton = safeQuerySelector(` .ant-calendar-prev-month-btn, .ant-calendar-prev-icon, .ant-picker-header-prev-btn, .ant-picker-prev-icon `, calendarPanel); if (targetYear < currentYear && prevButton) { // 目标年份早于当前年份,向前翻页 prevButton.click(); log('目标年份更早,向前翻页'); } else if (targetYear > currentYear && nextButton) { // 目标年份晚于当前年份,向后翻页 nextButton.click(); log('目标年份更晚,向后翻页'); } else if (nextButton) { // 默认向后翻页 nextButton.click(); log('默认向后翻页'); } } await delay(500); // 等待页面更新 attempts++; } log(`查找${isStartDate ? '开始' : '结束'}日期超时: ${chineseTitle}`); return false; }; // 查找并点击开始日期 const startResult = await findAndClickDateInCalendar(startDateStandard, true); if (!startResult) { throw new Error(`无法找到开始日期: ${startDate}`); } // 对于结束日期,需要查找第二个日历面板(如果是双面板范围选择器) log('开始查找结束日期,尝试定位第二个日历面板'); // 查找所有日历面板 const allCalendarPanels = safeQuerySelectorAll(` .ant-calendar-panel, .ant-calendar-dropdown, .ant-calendar-picker-panel, .ant-picker-dropdown, .ant-picker-panel, .ant-picker-panel-container `, frameDoc); log(`找到 ${allCalendarPanels.length} 个日历面板`); let endResult = false; if (allCalendarPanels.length >= 2) { // 双面板范围选择器,在第二个面板中查找结束日期 log('检测到双面板范围选择器,在第二个面板中查找结束日期'); const secondPanel = allCalendarPanels[1]; const secondPanelBody = safeQuerySelector(` .ant-calendar-body, .ant-picker-body, .ant-picker-content `, secondPanel); if (secondPanelBody) { // 在第二个面板中查找结束日期 const findEndDateInSecondPanel = async () => { const maxAttempts = 24; let attempts = 0; while (attempts < maxAttempts) { log(`在第二个面板中查找结束日期: ${chineseTitle} - 第${attempts + 1}次尝试`); // 在第二个面板中查找日期单元格 const dateCell = safeQuerySelector(` td[title="${chineseTitle}"], td[title="${endDateStandard}"], .ant-calendar-cell[title="${chineseTitle}"], .ant-calendar-cell[title="${endDateStandard}"], .ant-calendar-date[title="${chineseTitle}"], .ant-calendar-date[title="${endDateStandard}"], .ant-picker-cell[title="${chineseTitle}"], .ant-picker-cell[title="${endDateStandard}"], [data-value="${endDateStandard}"], [data-date="${endDateStandard}"] `, secondPanelBody); if (dateCell) { // 检查是否被禁用 if (dateCell.classList.contains('ant-calendar-disabled-cell') || dateCell.classList.contains('ant-picker-cell-disabled') || dateCell.hasAttribute('disabled')) { log(`结束日期单元格被禁用: ${chineseTitle}`); return false; } // 点击日期单元格 dateCell.click(); log(`点击结束日期: ${chineseTitle}`); await delay(action.delay !== undefined ? action.delay : 800); return true; } // 如果找不到日期,需要在第二个面板中翻页 log(`在第二个面板中未找到结束日期 ${chineseTitle},尝试翻页`); // 获取第二个面板的当前月份信息 const currentMonthElement = safeQuerySelector(` .ant-calendar-header .ant-calendar-header-view, .ant-picker-header-view, .ant-calendar-header-view `, secondPanel); if (currentMonthElement) { const currentText = currentMonthElement.textContent.trim(); log('第二个面板当前显示月份:', currentText); // 解析当前月份 let currentDate; if (currentText.includes('年') && currentText.includes('月')) { const match = currentText.match(/(\d{4})年(\d{1,2})月/); if (match) { currentDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, 1); } } else { currentDate = new Date(currentText + ' 1'); } if (currentDate && !isNaN(currentDate.getTime())) { const targetMonth = new Date(targetDateObj.getFullYear(), targetDateObj.getMonth(), 1); log('目标月份:', targetMonth.toISOString().slice(0, 7)); log('第二个面板当前显示月份:', currentDate.toISOString().slice(0, 7)); // 查找第二个面板的翻页按钮 const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon, .ant-calendar-header-next-btn, .ant-calendar-header-next `, secondPanel); const prevButton = safeQuerySelector(` .ant-calendar-prev-month-btn, .ant-calendar-prev-icon, .ant-picker-header-prev-btn, .ant-picker-prev-icon, .ant-calendar-header-prev-btn, .ant-calendar-header-prev `, secondPanel); log('第二个面板找到的翻页按钮:', { nextButton: !!nextButton, prevButton: !!prevButton }); if (targetMonth.getTime() > currentDate.getTime() && nextButton) { nextButton.click(); log('目标月份更晚,在第二个面板中翻到下一页'); } else if (targetMonth.getTime() < currentDate.getTime() && prevButton) { prevButton.click(); log('目标月份更早,在第二个面板中翻到上一页'); } else { log(`无法在第二个面板中找到结束日期: ${chineseTitle}`); return false; } } else { log('无法解析第二个面板当前月份,尝试点击下一页'); const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon `, secondPanel); if (nextButton) { nextButton.click(); } } } else { log('无法获取第二个面板当前月份信息,尝试点击下一页'); const nextButton = safeQuerySelector(` .ant-calendar-next-month-btn, .ant-calendar-next-icon, .ant-picker-header-next-btn, .ant-picker-next-icon `, secondPanel); if (nextButton) { nextButton.click(); } } await delay(500); attempts++; } log(`在第二个面板中查找结束日期超时: ${chineseTitle}`); return false; }; endResult = await findEndDateInSecondPanel(); } else { log('未找到第二个面板的主体,回退到单面板模式'); endResult = await findAndClickDateInCalendar(endDateStandard, false); } } else { // 单面板模式,使用原来的方法 log('使用单面板模式查找结束日期'); endResult = await findAndClickDateInCalendar(endDateStandard, false); } if (!endResult) { throw new Error(`无法找到结束日期: ${endDate}`); } // 点击确定按钮确认选择 const okButton = safeQuerySelector(` .ant-picker-ok, .ant-btn-primary, button[type="button"], .ant-calendar-ok-btn, .ant-calendar-footer-ok `, calendarPanel); if (okButton) { okButton.click(); log('点击确定按钮确认日期选择'); } else { // 如果没有找到确定按钮,尝试按回车键 const activeElement = document.activeElement; if (activeElement) { activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); activeElement.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })); log('按回车键确认日期选择'); } } await delay(action.delay !== undefined ? action.delay : 800); log('Ant Design 日历操作完成'); }, async interfaceReturn(action, frameDoc, results) { const element = document.querySelector(action.selector); if (element) { const interfaceFragment = action.interfaceFragment; // 拦截 fetch 请求 const originalFetch = window.fetch; console.log('开始拦截 fetch 请求'); window.fetch = function (...args) { const url = typeof args[0] === "string" ? args[0] : args[0].toString(); if (interfaceFragment && url.includes(interfaceFragment)) { console.log('拦截到 fetch 请求:', url); const fetchPromise = originalFetch.apply(this, args); fetchPromise.then((response) => { response .clone() .text() .then((result) => { console.log("fetch 接口返回:", result); window.parent !== window && window.parent.postMessage({ type: "apiResult", result }, "*"); }); }); return fetchPromise; } return originalFetch.apply(this, args); }; // 拦截 XMLHttpRequest 请求 const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; console.log('开始拦截 XHR 请求'); XMLHttpRequest.prototype.open = function(method, url, async, user, password) { this._method = method; this._url = url; this._async = async; this._user = user; this._password = password; return originalXHROpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(data) { if (interfaceFragment && this._url && this._url.includes(interfaceFragment)) { console.log('拦截到 XHR 请求:', this._url); // 监听响应 this.addEventListener('load', function() { if (this.status >= 200 && this.status < 300) { console.log("XHR 接口返回:", this.responseText); window.parent !== window && window.parent.postMessage({ type: "apiResult", result: this.responseText }, "*"); } }); this.addEventListener('error', function() { console.log("XHR 请求失败:", this.status, this.statusText); }); } return originalXHRSend.apply(this, arguments); }; element.click(); // 2秒后恢复原始方法 setTimeout(() => { window.fetch = originalFetch; XMLHttpRequest.prototype.open = originalXHROpen; XMLHttpRequest.prototype.send = originalXHRSend; console.log('恢复原始请求方法'); }, 2000); } else { throw new Error(`选择器 ${action.selector} 未找到`); } }, async waitForNext(action, frameDoc, results) { const nextSelector = action.nextSelector; const maxWaitTime = action.maxWaitTime || 30000; // 默认最大等待30秒 const checkInterval = action.checkInterval || 500; // 默认每秒检查一次 if (!nextSelector) { throw new Error('waitForNext 动作需要 nextSelector 参数'); } log('开始等待下一个元素出现:', nextSelector); const startTime = Date.now(); let elementFound = false; while (!elementFound && (Date.now() - startTime) < maxWaitTime) { // 查找下一个步骤的元素 const nextElement = safeQuerySelector(nextSelector, frameDoc); if (nextElement) { log('找到下一个元素:', nextSelector); elementFound = true; break; } // 等待指定间隔后再次检查 await delay(checkInterval); } if (!elementFound) { throw new Error(`等待超时,未找到元素: ${nextSelector}`); } log('等待完成,元素已出现:', nextSelector); } }; module.exports = actionHandlers;