Browse Source

feat(sidepanel): 优化表单选择和处理逻辑

- 新增表单选择功能,用户可通过点击选择需要填充的表单
- 重构消息处理逻辑,优化智能填表流程
- 增加错误处理和用户提示,提升用户体验
- 调整表单信息获取和处理方式,提高灵活性和可扩展性
chd 5 months ago
parent
commit
1957e92fd6

+ 13 - 1
src/entrypoints/background.js

@@ -12,6 +12,14 @@ export default defineBackground(() => {
         data: message.data
       })
     }
+    if (message.type === 'FROM_CONTENT_TO_SEND_PAGE_FORM') {
+      console.log(message)
+      chrome.runtime.sendMessage({
+        type: 'TO_SIDE_PANEL_FORM_INFO',
+        data: message.data
+      })
+      return true
+    }
 
     if (message.type === 'FROM_SIDE_PANEL_TO_ACTION') {
       console.log(565888)
@@ -39,7 +47,7 @@ export default defineBackground(() => {
       return true
     }
     if (message.type === 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM') {
-      chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+      chrome.tabs.query({ active: true}, (tabs) => {
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
         chrome.tabs.sendMessage(
@@ -52,6 +60,10 @@ export default defineBackground(() => {
               console.log('收到 content script 响应:', response)
               sendResponse(response)
             }
+
+
+            
+            return true
           }
         )
       })

+ 46 - 265
src/entrypoints/content.js

@@ -1,6 +1,6 @@
 // entrypoints/content.ts
 import PageAnalyzer, { cleanPage } from '../utils/page-analyzer'
-
+import { formatDate, simulateCompleteUserAction, simulateUserInput, findLabelForInput, findLabelForTag, findLabelForSpan, findLabelForTextarea, getPageInfo } from '../utils/contentUtils'
 export default defineContentScript({
   matches: ['<all_urls>'],
   main(ctx) {
@@ -46,25 +46,58 @@ export default defineContentScript({
           sendResponse({ data: '完成' })
           return true
         }
-
         if (message.type === 'GET_PAGE_FORM') {
-          const len = document.querySelectorAll('form').length
-          if (document.title === '智能招采' && len === 1) {
+          const forms = document.querySelectorAll('form')
+          if (forms.length === 0) {
             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]
+          for (const form of forms) {
+            form.style.border = '2px solid red'
+            function handleClick(e) {
+              e.stopPropagation()
+              console.log(this.outerHTML);
+              for (const form of forms) {
+                form.style.border = 'none '
+                form.removeEventListener('click', handleClick, true)
+              }
+              const form = this.cloneNode(true)
+              form.querySelectorAll('svg').forEach((el) => el.remove())
+              // sendResponse({
+              //   type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
+              //   data: this.outerHTML
+              // })
+              chrome.runtime.sendMessage({
+                type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
+                data: form.outerHTML
+              })
+            }
+            form.addEventListener("click", handleClick,true)
           }
+        
+          // 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) {
+            sendResponse({
+              status: 'error',
+              message: '没有找到表单'
+            })
+            return
+          }
+          // sendResponse({
+          //   status: 'ok',
+          //   data: form.outerHTML
+          // })
           // if (!form && document.querySelector("input")) {
           //     const arr = []
           //     const inputs = document.querySelectorAll("input")
@@ -75,17 +108,6 @@ export default defineContentScript({
           //     }
           //     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
@@ -97,44 +119,9 @@ export default defineContentScript({
       }
     )
 
-    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') {
@@ -271,211 +258,5 @@ export default defineContentScript({
         }
       }
     }
-    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}`
-    }
   }
 })

+ 9 - 15
src/entrypoints/sidepanel/Chat.vue

@@ -122,11 +122,10 @@ const {
   type,
   streamRes,
   getFormKeyAndValue,
-  handleSend
+  fetchRes 
 } = useMsg(scrollbar)
 const inputMessage = ref('')
 const pageInfo = ref('')
-
 const addMessage = (msg, raw) => {
   if (!msg) return
   const newMessage = reactive({
@@ -149,14 +148,12 @@ const addMessage = (msg, raw) => {
 const handleSummary = async () => {
   const res = await getPageInfo()
   addMessage('总结页面', getSummaryPrompt(res.content))
-  handleSend()
+  streamRes()
 }
-
 function handelIntelligentFillingClick() {
   inputMessage.value = '/智能填表 '
   type.value = '2'
 }
-
 function handleCurrentData(e) {
   drawerRef.value.drawer = false
   if (!e) {
@@ -172,7 +169,6 @@ function handleCurrentData(e) {
     })
   })
 }
-
 async function readClick() {
   isShowPage.value = true
   taklToHtml.value = true
@@ -180,16 +176,13 @@ async function readClick() {
   pageInfo.value = tempPageInfo
   pageInfoList.value.push(tempPageInfo)
 }
-
 function deletePageInfo(i) {
   pageInfoList.value.splice(i, 1)
   if (pageInfoList.value.length === 0) {
     isShowPage.value = false
     taklToHtml.value = false
   }
-
 }
-
 function addNewDialogue() {
   messages.value = []
   msgUuid.value = 'D' + Date.now().toString()
@@ -204,11 +197,12 @@ function addNewDialogue() {
 async function handleAsk() {
   if (sendLoading.value) return
   addMessage(inputMessage.value.trim())
-  handleSend(inputMessage.value.trim())
+  // handleSend(inputMessage.value.trim())
+  if (type.value === '2') fetchRes(inputMessage.value.trim())
+  else streamRes(true)
   inputMessage.value = ''
   // streamRes(taklToHtml)
 }
-
 function handleCapture() {
   ElMessage({
     message: '开发中...',
@@ -216,11 +210,9 @@ function handleCapture() {
     showClose: true
   })
 }
-
 function hisRecords() {
   drawerRef.value.drawer = true
 }
-
 const handleUpload = async (file) => {
   if (type.value === '2') {
     chrome.runtime.sendMessage({
@@ -282,7 +274,7 @@ async function getFileValue(file) {
     msg
   }
 }
-}
+
 const isHoveringTitle = ref(false)
 // 组件挂载时滚动到底部
 onMounted(() => {
@@ -299,8 +291,10 @@ onMounted(() => {
     if (message.type === 'TO_SIDE_PANEL_PAGE_CHANGE') {
       pageInfo.value = message.data
     }
+     if (message.type === 'TO_SIDE_PANEL_FORM_INFO') {
+      
+    }
   })
-
   // 添加标题悬停事件监听
   nextTick(() => {
     const titleWrapper = document.querySelector('.title-wrapper')

+ 55 - 34
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -50,20 +50,20 @@ export function useMsg(scrollbar?: any) {
     }
   }
   const getFormKeyAndValue = async (file: any, form?: any) => {
-    const obj = reactive({
-      id: moment(),
-      username: '用户1',
-      rawContent: '',
-      content: '解析文件中',
-      timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      isSelf: false,
-      avatar: avator,
-      addToHistory: !taklToHtml.value
-    })
+    // const obj = reactive({
+    //   id: moment(),
+    //   username: '用户1',
+    //   rawContent: '',
+    //   content: '解析文件中',
+    //   timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
+    //   isSelf: false,
+    //   avatar: avator,
+    //   addToHistory: !taklToHtml.value
+    // })
 
     try {
       sendLoading.value = true
-      messages.value.push(obj)
+      // messages.value.push(obj)
       const response = await getFormKey({
         body: form,
         input_data: file
@@ -74,32 +74,31 @@ export function useMsg(scrollbar?: any) {
       console.log(type.value)
       await streamRes(buildObjPrompt(response.data, form))
     } catch (error) {
-      obj.content = '解析出错'
+      // obj.content = '解析出错'
     } finally {
       sendLoading.value = false
     }
   }
   // 发送消息
-  const handleSend = async (msg: any, addHtml = false) => {
-    if ((type.value === '2' && msg.startsWith('/')) || msg.startsWith('请')) {
-      if (!taklToHtml.value)
-        return messages.value.push({
-          id: messages.value.length + 1,
-          username: '用户1',
-          content: '请打开与页面对话!',
-          rawContent: '请打开与页面对话!',
-          timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-          isSelf: false,
-          avatar: avator,
-          addToHistory: false
-        })
-      indexTemp.value = 0
-      fetchRes(msg)
-    } else {
-      streamRes(addHtml)
-    }
-  }
+  // const handleSend = async (msg: any) => {
+  //   if ( msg?.startsWith('/')) {
+  //     if (!taklToHtml.value)
+  //       return messages.value.push({
+  //         id: messages.value.length + 1,
+  //         username: '用户1',
+  //         content: '请打开与页面对话!',
+  //         rawContent: '请打开与页面对话!',
+  //         timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
+  //         isSelf: false,
+  //         avatar: avator,
+  //         addToHistory: false
+  //       })
+  //     indexTemp.value = 0
+  //     fetchRes(msg)
+  //   } 
+  // }
   const fetchRes = async (msg: any) => {
+    indexTemp.value = 0
     sendLoading.value = true
     const obj: any = reactive({
       id: messages.value.length + 1,
@@ -111,7 +110,26 @@ export function useMsg(scrollbar?: any) {
       addToHistory: !taklToHtml.value
     })
     messages.value.push(obj)
+    msg = msg.split('/智能填表')[1]
     nextTick(() => scrollbar.value?.setScrollTop(99999))
+    if (!msg) {
+      obj.content = `请选择表单`
+      // ElMessage({
+      //   message: '请选择表单',
+      //   type: 'success',
+      //   duration: 4 * 1000,
+      //   grouping: true
+      // })
+      chrome.runtime.sendMessage({
+        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
+      }, (response) => {
+        if (response.status === 'error') {
+          obj.content = '当前页面未找到表单'
+          sendLoading.value = false
+        }
+      })
+      return
+    }
     await fetchDataAndProcess(msg, obj)
     sendLoading.value = false
   }
@@ -186,13 +204,16 @@ export function useMsg(scrollbar?: any) {
                   resolve(1)
                 }, 2000)
               })
-              msgObj.content = `请上传数据`
+              msgObj.content = `请选择表单`
               ElMessage({
-                message: '请上传数据',
+                message: '请选择表单',
                 type: 'success',
                 duration: 4 * 1000,
                 grouping: true
               })
+              chrome.runtime.sendMessage({
+                type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
+              })
             } else {
               msgObj.content = `执行完毕`
             }
@@ -274,7 +295,7 @@ export function useMsg(scrollbar?: any) {
     sendLoading,
     formMap,
     type,
-    handleSend,
+    fetchRes,
     streamRes,
     getFormKeyAndValue,
     getFileSummary

+ 91 - 4
src/entrypoints/sidepanel/utils/index.js

@@ -1,6 +1,6 @@
 import * as XLSX from 'xlsx'
-
-export const getPageInfo = () => {
+import { ElMessage } from 'element-plus'
+export function getPageInfo (){
   return new Promise((res, rej) => {
     chrome.runtime.sendMessage(
       {
@@ -17,7 +17,7 @@ export const getPageInfo = () => {
     )
   })
 }
-export const handleInput = (xlsxData, formMap) => {
+export function handleInput (xlsxData, formMap) {
   chrome.runtime.sendMessage({
     type: 'FROM_SIDE_PANEL_TO_INPUT_FORM',
     data: {
@@ -26,7 +26,7 @@ export const handleInput = (xlsxData, formMap) => {
     }
   })
 }
-export const getXlsxValue = (file) => {
+export function getXlsxValue (file) {
   return new Promise((res, rej) => {
     try {
       const reader = new FileReader()
@@ -56,3 +56,90 @@ export const getXlsxValue = (file) => {
     }
   })
 }
+let indexTemp = 0
+export async function handleClick(res, msgObj,input) {
+  await new Promise((resolve) => setTimeout(resolve, 2000))
+  msgObj.content = `点击${res.tag}元素`
+  chrome.runtime.sendMessage(
+    {
+      type: 'FROM_SIDE_PANEL_TO_ACTION',
+      data: res
+    },
+    async ({ data, status }) => {
+      if (chrome.runtime.lastError) {
+        console.error('消息发送错误:', chrome.runtime.lastError)
+      } else {
+        if (status === 'error') {
+          msgObj.content = data
+          return
+        }
+        if (res.next === '是') {
+          const arr = input.split(',')
+          arr.shift()
+          const input = arr.join(',')
+          indexTemp++
+          fetchDataAndProcess(input, msgObj)
+        } else {
+          if (type.value === '2') {
+            await new Promise((resolve, reject) => {
+              setTimeout(() => {
+                resolve(1)
+              }, 2000)
+            })
+            msgObj.content = `请选择表单`
+            ElMessage({
+              message: '请选择表单',
+              type: 'success',
+              duration: 4 * 1000,
+              grouping: true
+            })
+            chrome.runtime.sendMessage({
+              type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
+            })
+          } else {
+            msgObj.content = `执行完毕`
+          }
+        }
+      }
+      return true
+    }
+  )
+}
+async function fetchDataAndProcess(input, obj,pageInfo) {
+  if (input.startsWith('/智能填表')) {
+    input = input.split('/智能填表')[1]
+  }
+  str = input
+  console.log(str)
+  const pageInfo = await getPageInfo()
+  await new Promise((res) =>
+    setTimeout(() => {
+      res()
+    }, 2000)
+  )
+  // const res = await hepl({
+  //   input_data: input,
+  //   body: pageInfo.content.mainContent
+  // })
+  const res = await new Promise((resolve, reject) => {
+    setTimeout(() => {
+      resolve({
+        data:
+          pageInfo.title === '智能招采'
+            ? mockData[indexTemp]
+            : mockData2[indexTemp]
+      })
+    }, 1000)
+  })
+  if (!res.data.tag || res.data.tag === 'undefined') {
+    ElMessage({
+      message: '未找到标签,请重试',
+      type: 'error',
+      duration: 4 * 1000,
+      grouping: true
+    })
+    obj.content = '未找到标签,请重试'
+    return
+  }
+  await handleClick(res.data, obj,input)
+}

+ 233 - 0
src/utils/contentUtils.js

@@ -0,0 +1,233 @@
+export 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}`
+}
+export async  function simulateCompleteUserAction  (
+    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
+    }
+}
+export async function simulateUserInput (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' }))
+}
+export function 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
+}
+export function 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
+}
+export function 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
+}
+export function 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
+}
+export function getPageInfo() {
+    const favIconUrl = getFavicon()
+    return {
+        type: 'PAGE_INFO',
+        data: {
+            favIconUrl,
+            title: document.title,
+            url: window.location.href,
+            content: window.pageAnalyzer.analyzePage()
+        }
+    }
+}
+export 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`
+}