// entrypoints/content.ts import PageAnalyzer, { cleanPage } from '../utils/page-analyzer' export default defineContentScript({ matches: [''], main(ctx) { let page = document.getElementsByTagName('body')[0] const src = chrome.runtime.getURL('images/begin.png') window.pageAnalyzer = new PageAnalyzer() window.onload = () => { chrome.runtime.sendMessage(getPageInfo()) console.log(document.title) } let form = null let formChildren = [] let excelDataA = {} chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => { if (message.type === 'GET_PAGE_INFO') { sendResponse({ data: getPageInfo() }) } let dom = null if (message.type === 'GET_TAG_ACTION') { const data = message.data if (0) { dom = document.getElementById(data.id) } else { dom = [...document.getElementsByTagName(data.tag.toLowerCase())] // .filter(_ => _.className.includes(data.class)) .filter(_ => _.innerText.includes(data.text))[0] } console.log(dom) if (!dom) { sendResponse({ data: '未找到元素,请重试', statue: 'error' }) return } // 添加红色边框,500ms后移除边框,然后点击 const originalBorder = dom.style.border dom.style.border = '2px solid red' setTimeout(() => { dom.style.border = originalBorder dom.click() }, 1000) sendResponse({ data: '完成' }) return true } if (message.type === 'GET_PAGE_FORM') { const len = document.querySelectorAll('form').length if (document.title === '智能招采' && len === 1) { sendResponse({ status: 'error', message: '没有找到表单' }) return } if (len) { const forms = document.querySelectorAll('form') if (len > 1) { form = forms[len - 1] } else form = forms[0] form.querySelectorAll('svg') .forEach((el) => el.remove()) formChildren = [...form.elements] } // if (!form && document.querySelector("input")) { // const arr = [] // const inputs = document.querySelectorAll("input") // formChildren = [...inputs] // for (const element of inputs) { // arr.push(element.outerHTML) // } // form = { outerHTML: JSON.stringify(arr) } // } if (!form) { sendResponse({ status: 'error', message: '没有找到表单' }) return } sendResponse({ status: 'ok', data: form.outerHTML }) } if (message.type === 'INPUT_FORM') { const { formData, excelData } = message.data excelDataA = excelData console.log(formData, excelDataA) await handleFillInput(formData, 0) } return true }) function getPageInfo() { const favIconUrl = getFavicon() return { type: 'PAGE_INFO', data: { favIconUrl, title: document.title, url: window.location.href, content: window.pageAnalyzer.analyzePage() } } } function getFavicon() { // 尝试获取动态favicon const iconLinks = Array.from( document.querySelectorAll('link[rel*="icon"]') ) const favIconUrl = iconLinks .sort((a, b) => { // 优先使用大尺寸图标 const sizeA = parseInt(a.sizes?.value) || 0 const sizeB = parseInt(b.sizes?.value) || 0 return sizeB - sizeA }) .map((link) => link.href) .find(Boolean) if (favIconUrl) return favIconUrl // 如果没有找到,返回网站根目录的favicon.ico const url = new URL(window.location.href) return `${url.protocol}//${url.hostname}/favicon.ico` } const handleFillInput = async (data, index) => { console.log(data) for (let i = 0; i < data.length; i++) { const item = data[i] if (item.findBy === 'id') { const input = formChildren.find(child => child.id === item.findByValue) if (item.type === 'text' || item.type === 'textarea' || item.type === 'number') { if (!input) { if (item.label) { const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label)) if (label) { const input = findLabelForInput(label) if (input) { await simulateUserInput(input, excelDataA[item.excelColumn]) } } } } if (input) { await simulateUserInput(input, excelDataA[item.excelColumn]) } } if (item.type === 'radio' || item.type === 'checkbox') { if (item.label) { const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label)) if (label) { const span = findLabelForSpan(label) span.forEach(span => { span.innerText === excelDataA[item.excelColumn] && span.click() }) } } } if (item.type === 'date') { const input = formChildren.find(child => child.id === item.findByValue) if (excelDataA[item.excelColumn]) { await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn]), formatDate(excelDataA[item.excelColumn])) } } } if (item.findBy === 'placeholder') { const input = formChildren.find(child => child.placeholder === item.findByValue) if (input) { simulateUserInput(input, excelDataA[item.excelColumn]) } } if (item.findBy === 'label') { if (!excelDataA[item.excelColumn]) continue const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.findByValue) || item.findByValue.includes(label.innerText)) console.log(label) if (!label) continue if (item.type === 'radio' || item.type === 'checkbox') { console.log(excelDataA[item.excelColumn]) if (label) { const span = findLabelForSpan(label) span.forEach(span => { console.log(span.innerText) if (span.innerText) { if (span.innerText.includes(excelDataA[item.excelColumn]) || excelDataA[item.excelColumn].includes(span.innerText)) { console.log(span) span.click() } } }) } } if (item.type === 'date') { const input = findLabelForInput(label) console.log(input, excelDataA[item.excelColumn]) if (input) { await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn]), formatDate(excelDataA[item.excelColumn])) } } if (item.type === 'input' || item.type === 'text' || item.type === 'number' || item.type === 'tel' || item.type === 'email' || item.type === 'textarea') { let input if (item.type === 'textarea') { input = findLabelForTextarea(label) } else input = findLabelForInput(label) if (input) { if (input.value) continue console.log(excelDataA[item.excelColumn]) await simulateUserInput(input, excelDataA[item.excelColumn]) } } if (item.type === 'select') { const input = findLabelForInput(label) if (input) { await simulateUserInput(input, excelDataA[item.excelColumn]) } } } } } const simulateUserInput = async (element, value) => { // 设置值 if (element.tagName.toLowerCase() === 'textarea') { element.focus() element.value = value element.blur() return } element.value = value // 创建并触发 input 事件 const inputEvent = new Event('input', { bubbles: true }) element.dispatchEvent(inputEvent) // 创建并触发 change 事件 const changeEvent = new Event('change', { bubbles: true }) element.dispatchEvent(changeEvent) // 可选:如果表单使用了 React/Vue 等框架,可能还需要触发以下事件 element.dispatchEvent(new Event('blur')) element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })) } const findLabelForInput = (label) => { let p = label.parentElement for (let i = 0; i < 5; i++) { const input = p.getElementsByTagName('input') if (input.length > 0) { return input[0] } p = p.parentElement } return null } const findLabelForTag = (label, tag) => { let p = label.parentElement for (let i = 0; i < 10; i++) { const input = p.getElementsByTagName('input') if (input.length > 0) { return input[0] } p = p.parentElement } return null } const findLabelForSpan = (label) => { let p = label.parentElement for (let i = 0; i < 10; i++) { const span = p.getElementsByTagName('span') if (span.length > 0) { return [...span] } p = p.parentElement } return null } const findLabelForTextarea = (label) => { let p = label.parentElement for (let i = 0; i < 10; i++) { const span = p.getElementsByTagName('textarea') if (span.length > 0) { return [...span] } p = p.parentElement } return null } const simulateCompleteUserAction = async (clickElement, inputElement, inputText, tdTitle) => { // 1. 模拟鼠标弹起事件 const simulateMouseUp = (element) => { const mouseUpEvent = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window, button: 0, buttons: 0, clientX: 0, clientY: 0, detail: 1 }) element.dispatchEvent(mouseUpEvent) } // 123456qq? // 2. 模拟键盘输入 const simulateTyping = async (element, text, delay = 50) => { element.focus() for (let char of text) { await new Promise(resolve => setTimeout(resolve, delay)) // 按键按下 const keydownEvent = new KeyboardEvent('keydown', { key: char, code: `Key${char.toUpperCase()}`, bubbles: true, cancelable: true }) element.dispatchEvent(keydownEvent) // 更新输入值 element.value += char // 触发输入事件 const inputEvent = new InputEvent('input', { bubbles: true, cancelable: true, data: char, inputType: 'insertText' }) element.dispatchEvent(inputEvent) // 按键弹起 const keyupEvent = new KeyboardEvent('keyup', { key: char, code: `Key${char.toUpperCase()}`, bubbles: true, cancelable: true }) element.dispatchEvent(keyupEvent) } // 触发change事件 element.dispatchEvent(new Event('change', { bubbles: true })) } // 3. 查找td元素 const findTdByTitle = (title, timeout = 5000) => { return new Promise((resolve, reject) => { const startTime = Date.now() const find = () => { const td = document.querySelector(`td[title="${title}"]`) if (td) { resolve(td) return } if (Date.now() - startTime > timeout) { reject(new Error(`未找到title为"${title}"的td元素`)) return } requestAnimationFrame(find) } find() }) } // 4. 模拟点击事件 const simulateClick = (element) => { const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window, detail: 1 }) element.dispatchEvent(clickEvent) } try { // 执行操作序列 // 1. 触发鼠标弹起 simulateMouseUp(clickElement) await new Promise(resolve => setTimeout(resolve, 100)) // 2. 模拟键盘输入 await simulateTyping(inputElement, inputText) await new Promise(resolve => setTimeout(resolve, 200)) // 3. 查找并点击td元素 // const tdElement = await findTdByTitle(tdTitle); const enterKeyEvent = new KeyboardEvent('keydown', { key: 'Enter', // 事件键名 code: 'Enter', // 物理按键编码 keyCode: 13, // 传统键码(Enter键为13) which: 13, // 同keyCode bubbles: true, // 允许事件冒泡 cancelable: true // 允许事件被取消 }) setTimeout(() => { inputElement.dispatchEvent(enterKeyEvent) }, 0) await new Promise(resolve => setTimeout(resolve, 500)) return true } catch (error) { throw error } } function formatDate(date) { // 直接创建北京时间的日期对象 const d = new Date(date) // 获取年、月、日 const year = d.getFullYear() const month = String(d.getMonth() + 1).padStart(2, '0') // 月份从0开始,需要加1 const day = String(d.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } } })