/** * 侧边栏管理类 */ class SidebarManager { /** * @constant {Object} 配置常量 */ static CONFIG = { SIDEBAR_ID: "paiwise-sidebar", WRAPPER_ID: "paiwise-main-wrapper", STORAGE_KEY: "sidebarOpen", }; constructor() { this.sidebarId = SidebarManager.CONFIG.SIDEBAR_ID; this.wrapperId = SidebarManager.CONFIG.WRAPPER_ID; this.isOpen = false; this.currentUrl = window.location.href; this.currentTitle = document.title; this.init(); } /** * 初始化侧边栏 */ async init() { try { await this.checkAndRestoreState(); this.setupEventListeners(); } catch (error) { console.error("Sidebar initialization failed:", error); } } /** * 设置事件监听器 */ setupEventListeners() { // 监听页面加载完成事件 window.addEventListener("load", () => this.checkAndRestoreState()); // 监听来自 sidebar 的消息 window.addEventListener("message", this.handleMessage.bind(this)); // 监听窗口大小变化 window.addEventListener( "resize", Utils.debounce(() => this.handleResize(), 100) ); // 监听页面可见性变化 document.addEventListener("visibilitychange", () => { if (!document.hidden) { this.checkAndRestoreState(); } }); // 监听 history 变化 window.addEventListener("popstate", () => { this.checkAndRestoreState(); }); // 监听标题变化 const titleObserver = new MutationObserver(() => { if (document.title !== this.currentTitle) { this.currentTitle = document.title; this.updatePageInfo(); } }); titleObserver.observe( document.querySelector("head > title") || document.head, { subtree: true, characterData: true, childList: true, } ); 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 < 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 } // 监听页面 URL 变化 new MutationObserver(() => { const url = location.href; if (url !== this.currentUrl) { this.currentUrl = url; this.updatePageInfo(); this.checkAndRestoreState(); } }).observe(document, { subtree: true, childList: true }); const handleFillInput = async (data, index) => { 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][index]) } } } } if (input) { await simulateUserInput(input, excelDataA[item.excelColumn][index]) } } 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][index] && span.click() }) } } } if (item.type === 'date') { formatDate const input = formChildren.find(child => child.id === item.findByValue) if (excelDataA[item.excelColumn][index]) { await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn][index]), formatDate(excelDataA[item.excelColumn][index])) } } } else if (item.findBy === 'placeholder') { const input = formChildren.find(child => child.placeholder === item.findByValue) if (input) { simulateUserInput(input, excelDataA[item.excelColumn][index]) } } } } let form = null let formChildren = [] let excelDataA = [] // 监听来自sidebar的消息 window.addEventListener("message", async (event) => { if (event.data.type === "COPY_TO_CLIPBOARD") { try { await navigator.clipboard.writeText(event.data.text); // 可选:发送成功消息回sidebar event.source.postMessage( { type: "COPY_SUCCESS", }, "*" ); } catch (err) { console.error("Failed to copy text:", err); // 可选:发送失败消息回sidebar event.source.postMessage( { type: "COPY_ERROR", error: err.message, }, "*" ); } } if (event.data.type === "HANDLE_FILL_INPUT") { const { formData, excelData } = event.data.data excelDataA = excelData console.log(formChildren, excelDataA); await handleFillInput(formData, 0) } }); // 监听来自iframe的消息 window.addEventListener("message", (event) => { // 确保消息来自我们的iframe if ( event.source === document.getElementById("paiwise-sidebar")?.contentWindow ) { if (event.data.type === "FILL_INPUT") { event.source.postMessage( { type: "GE_T", pageInfo: pageInfo, }, "*" ); } if (event.data.type === "ANALYZE_PAGE") { form = document.querySelectorAll("form")[0]; formChildren = [...form.elements] // 分析页面并返回结果 const pageInfo = window.pageAnalyzer.analyzePage(); // 发送分析结果回iframe event.source.postMessage( { type: "PAGE_ANALYSIS_RESULT", pageInfo: form.outerHTML, }, "*" ); } } }); } handleUserName(input) { return input.placeholder.includes('账号') || input.placeholder.includes('用户名') } /** * 处理接收到的消息 * @param {MessageEvent} event */ handleMessage(event) { if (event.data.action === "closeSidebar") { this.removeSidebar(); } } /** * 处理窗口大小变化 */ handleResize() { const sidebar = document.getElementById(this.sidebarId); if (sidebar && this.isOpen) { sidebar.style.height = `${window.innerHeight}px`; } } /** * 检查并恢复侧边栏状态 */ async checkAndRestoreState() { try { const isOpen = await Utils.getStorageData( SidebarManager.CONFIG.STORAGE_KEY ); if (isOpen && !this.isOpen) { // 只有当应该打开且当前未打开时才创建 this.createSidebar(); } else if (!isOpen && this.isOpen) { // 只有当应该关闭且当前打开时才移除 this.removeSidebar(); } } catch (error) { console.error("Failed to restore sidebar state:", error); } } /** * 包装页面内容 */ wrapPageContent() { if (document.getElementById(this.wrapperId)) return; const wrapper = document.createElement("div"); wrapper.id = this.wrapperId; // 保存body的原始样式 this.originalBodyStyle = { width: document.body.style.width, margin: document.body.style.margin, position: document.body.style.position, overflow: document.body.style.overflow, }; // 包装内容 while (document.body.firstChild) { if (document.body.firstChild.id !== this.sidebarId) { wrapper.appendChild(document.body.firstChild); } else { document.body.removeChild(document.body.firstChild); } } document.body.appendChild(wrapper); } /** * 解除页面内容包装 */ unwrapPageContent() { const wrapper = document.getElementById(this.wrapperId); if (!wrapper) return; // 恢复body的原始样式 if (this.originalBodyStyle) { Object.assign(document.body.style, this.originalBodyStyle); } while (wrapper.firstChild) { document.body.insertBefore(wrapper.firstChild, wrapper); } wrapper.remove(); } /** * 发送页面信息到iframe */ sendPageInfo() { this.updatePageInfo(); } /** * 更新页面信息 */ updatePageInfo() { const iframe = document.getElementById(this.sidebarId); if (!iframe) return; // 获取最新的favicon const favicon = this.getFavicon(); // 发送更新后的页面信息 iframe.contentWindow.postMessage( { type: "PAGE_INFO", pageInfo: { favicon, title: document.title, url: window.location.href, iframe: window.pageAnalyzer.analyzePage() }, }, "*" ); } /** * 获取最新的favicon */ getFavicon() { // 尝试获取动态favicon const iconLinks = Array.from( document.querySelectorAll('link[rel*="icon"]') ); const favicon = 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 (favicon) return favicon; // 如果没有找到,返回网站根目录的favicon.ico const url = new URL(window.location.href); return `${url.protocol}//${url.hostname}/favicon.ico`; } /** * 创建侧边栏 */ async createSidebar() { if (document.getElementById(this.sidebarId)) return; try { this.wrapPageContent(); const iframe = document.createElement("iframe"); iframe.id = this.sidebarId; iframe.src = chrome.runtime.getURL("sidebar.html"); // 先添加到DOM,但不添加show类 document.body.appendChild(iframe); // 等待iframe加载完成 await new Promise((resolve) => { iframe.onload = () => { // 发送页面信息 this.sendPageInfo(); resolve(); }; }); // 等待一帧以确保DOM更新 await new Promise((resolve) => requestAnimationFrame(resolve)); // 触发动画 document.body.classList.add("sidebar-open"); iframe.classList.add("show"); // 等待动画完成 await new Promise((resolve) => { iframe.addEventListener("transitionend", resolve, { once: true }); }); this.isOpen = true; await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, true); } catch (error) { console.error("Failed to create sidebar:", error); this.unwrapPageContent(); } } /** * 移除侧边栏 */ async removeSidebar() { if (!this.isOpen) return; try { const sidebar = document.getElementById(this.sidebarId); if (sidebar) { document.body.classList.remove("sidebar-open"); sidebar.classList.remove("show"); // 等待动画完成后再移除元素 await new Promise((resolve) => { sidebar.addEventListener( "transitionend", () => { this.unwrapPageContent(); sidebar.remove(); resolve(); }, { once: true } ); }); } this.isOpen = false; await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, false); } catch (error) { console.error("Failed to remove sidebar:", error); } } /** * 切换侧边栏显示状态 */ toggle() { if (this.isOpen) { this.removeSidebar(); } else { this.createSidebar(); } } } // 初始化侧边栏管理器 const sidebarManager = new SidebarManager(); // 监听来自背景脚本的消息 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "toggleSidebar") { sidebarManager.toggle(); sendResponse({ success: true }); } sendResponse({ success: true }); }); window.onload = () => { inputs = [...document.querySelectorAll('input')] } let inputs = [] // 创建一个新的js文件用于处理侧边栏内部的关闭按钮事件 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); }; // 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); setTimeout(() => { tdElement.click() }, 100) 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}`; }