浏览代码

登录页开发

chd 4 月之前
父节点
当前提交
7a26269f44

+ 2 - 1
.env

@@ -1,2 +1,3 @@
 VITE_OPENAI_API_KEY_TONG=sk-e9855234f47346049809ce23ed3ebe3f
-VITE_MAX_FILE_NUMBER=10
+VITE_MAX_FILE_NUMBER=10
+VITE_APP_BASE_API='http://192.168.1.100:8088'

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "vant": "^4.9.17",
     "vite-plugin-svg-icons": "^2.0.1",
     "vue": "^3.5.12",
+    "vue-qrcode": "^2.2.2",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {

+ 54 - 0
src/api/index.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+export function getChatList(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'post',
+        data: data
+    })
+}
+export function getChatDetail(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'post',
+        data: data
+    })
+}
+
+export function deleteChat(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'delete',
+        data: data
+    })
+}
+
+export function putChat(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'put',
+        data: data
+    })
+}
+
+export function getQRcodeResult(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'put',
+        data: data
+    })
+}
+export function getQRcode(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'put',
+        data: data
+    })
+}
+export function getCode(data) {
+    return request({
+        url: '/statistics/frontList',
+        method: 'put',
+        data: data
+    })
+}
+

二进制
src/assets/images/user.png


+ 44 - 6
src/entrypoints/background.js

@@ -1,10 +1,31 @@
 export default defineBackground(() => {
   let currentTabId
+  let arr = []
+  const url = 'http://192.168.1.166:7777/behavior/user/adds'
   // Executed when background is loaded
   chrome.sidePanel
     .setPanelBehavior({ openPanelOnActionClick: true })
     .catch((error) => console.error(error))
   chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+    if (message.type === 'CLICK_EVENT') {
+      if (token) {
+        arr.push({
+          ...message.data,
+          userId:token
+        })
+        if (arr.length > 10) {
+          fetch(url, {
+            method: 'POST',
+            headers: {
+              'Content-Type': 'application/json',
+              'Authorization': 'Bearer ' + token
+            },
+            body: JSON.stringify(arr.map(_ => ({ ..._, userId: token })))
+          }).then(res => 1)
+          arr = []
+        }
+      }
+    }
     if (message.type === 'PAGE_INFO') {
       // 转发到侧边栏
       chrome.runtime.sendMessage({
@@ -13,19 +34,14 @@ export default defineBackground(() => {
       })
     }
     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)
       chrome.tabs.query({ active: true }, (tabs) => {
-        console.log(tabs)
-
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
         chrome.tabs.sendMessage(
@@ -58,7 +74,6 @@ export default defineBackground(() => {
               console.log('收到 content script 响应:', response)
               sendResponse(response)
             }
-
             return true
           }
         )
@@ -106,6 +121,29 @@ export default defineBackground(() => {
       return true
     }
   })
+  let timer
+  // chrome.tabs.onRemoved.addListener(function (...a) {
+  //   clearTimeout(timer)
+  //   timer = setTimeout(() => {
+  //     chrome.tabs.query({}, function (tabs) {
+  //       fetch(url, {
+  //         method: 'POST',
+  //         headers: {
+  //           'Content-Type': 'application/json',
+  //         },
+  //         body: JSON.stringify(arr)
+  //       }).then(res => 1)
+  //       arr = []
+  //     });
+  //   }, 0)
+  // });
+  chrome.storage.onChanged.addListener((changes, areaName) => {
+    console.log('[Storage Changed] Area:', areaName);
+    // 遍历所有被修改的键
+    for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
+      if (key === 'token') token = newValue
+    }
+  });
   chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
     if (changeInfo.status === 'complete' && tab.url) {
       chrome.runtime.sendMessage({

+ 44 - 1
src/entrypoints/content.js

@@ -8,7 +8,8 @@ import {
   findLabelForTag,
   findLabelForSpan,
   findLabelForTextarea,
-  getPageInfo
+  getPageInfo,
+  cleanPage2
 } from '../utils/contentUtils'
 export default defineContentScript({
   matches: ['<all_urls>'],
@@ -23,6 +24,48 @@ export default defineContentScript({
     let form = null
     let formChildren = []
     let excelDataA = {}
+    let timer
+    function handleMouseover(e) {
+      clearTimeout(timer)
+      timer = setTimeout(() => {
+        if (tag === e.target.outerHTML && innerText === e.target.innerText) return
+        if (e.target.outerHTML.length > 1000) return
+        tag = e.target.outerHTML
+        innerText = e.target.innerText
+        console.log(e.target.outerHTML.length, e.target.outerHTML);
+        chrome.runtime.sendMessage({
+          type: 'CLICK_EVENT',
+          data: {
+            outerHTML: cleanPage2(e.target),
+            innerText: e.target.innerText,
+            url: window.location.href,
+          }
+        }, function (response) {
+        });
+      }, 1000);
+    }
+
+    let tag
+    let innerText
+    function handleClick(e) {
+      if (tag === e.target.outerHTML && innerText === e.target.innerText) return
+      if (e.target.outerHTML.length > 1000) return
+      tag = e.target.outerHTML
+      innerText = e.target.innerText
+      console.log(e.target.outerHTML.length, e.target.outerHTML);
+      clearTimeout(timer)
+      chrome.runtime.sendMessage({
+        type: 'CLICK_EVENT',
+        data: {
+          outerHTML: cleanPage2(e.target),
+          innerText: e.target.innerText,
+          url: window.location.href,
+        }
+      }, function (response) {
+      });
+    }
+    document.addEventListener('click', handleClick)
+    document.addEventListener('mouseover', handleMouseover)
     chrome.runtime.onMessage.addListener(
       async (message, sender, sendResponse) => {
         if (message.type === 'GET_PAGE_INFO') {

+ 19 - 72
src/entrypoints/sidepanel/App.vue

@@ -1,24 +1,22 @@
 <template>
-  <!--  <div>-->
-  <!--    <a href="https://wxt.dev" target="_blank">-->
-  <!--      <img src="/wxt.svg" class="logo" alt="WXT logo" />-->
-  <!--    </a>-->
-  <!--    <a href="https://vuejs.org/" target="_blank">-->
-  <!--      <img src="@/assets/vue.svg" class="logo vue" alt="Vue logo" />-->
-  <!--    </a>-->
-  <!--  </div>-->
-  <!--  <HelloWorld msg="WXT + Vue" />-->
-  <Chat />
-  <!-- <div @click="() => openFile()">按钮</div> -->
-
+  <div class="app-container"   v-if="userStore.isLogin">
+    <UserDropdown />
+    <Chat />
+  </div>
+    <Login v-else/> 
 </template>
 
 <script lang="ts" setup>
 import { provide } from 'vue'
 import Chat from '@/entrypoints/sidepanel/Chat.vue'
+import Login from '@/entrypoints/sidepanel/component/Login.vue'
+import UserDropdown from '@/entrypoints/sidepanel/component/UserDropdown.vue'
 import axios from '@/utils/request'
 import { onBeforeUnmount, onMounted } from 'vue'
 import { useIndexedDB } from '@/entrypoints/sidepanel/hook/useIndexedDB'
+import { useUserStore } from '@/store/modules/user'
+
+const userStore = useUserStore()
 
 const { openDB, ...args } = useIndexedDB({
   dbName: 'chatDB',
@@ -28,76 +26,25 @@ const { openDB, ...args } = useIndexedDB({
 // 提供实例给子组件
 provide('indexedDBHook', args)
 
+onMounted(async () => {
+  console.log(userStore);
+  await userStore.initStore()
 
-async function openFile() {
-  try {
-    const [fileHandle] = await window.showOpenFilePicker()
-    const file = await fileHandle.getFile()
-    const contents = await file.text()
-    console.log(file)
-  } catch (err) {
-    console.error('Error accessing file:', err)
-  }
-}
-
-onMounted(() => {
   // 初始化数据库连接(不包含任何Store)
   openDB()
   chrome.runtime.sendMessage({
     type: 'GET_CONTENT_INFO'
   })
-
 })
-
-async function saveFile() {
-  try {
-    const options = {
-      types: [
-        {
-          description: 'Text Files',
-          accept: { 'text/plain': ['.txt'] }
-        }
-      ]
-    }
-    const handle = await window.showSaveFilePicker(options)
-    const writable = await handle.createWritable()
-    await writable.write('Hello, World!')
-    await writable.close()
-    console.log('File saved successfully!')
-  } catch (err) {
-    console.error('Error saving file:', err)
-  }
-}
-
-async function openDirectory() {
-  try {
-    // 显示文件夹选择对话框
-    const directoryHandle = await window.showDirectoryPicker()
-    console.log(`Selected directory: ${directoryHandle.name}`)
-
-    // 读取文件夹内容
-    for await (const [name, handle] of directoryHandle.entries()) {
-      if (handle.kind === 'file') {
-        console.log(`File: ${name}`)
-        // 读取文件内容(可选)
-        const file = await handle.getFile()
-        console.log(`File content: ${await file.text()}`)
-      } else if (handle.kind === 'directory') {
-        console.log(`Sub-directory: ${name}`)
-      }
-    }
-  } catch (err) {
-    console.error('Error accessing directory:', err)
-  }
-}
-
-// 调用函数
-
-
 </script>
 
-
 <style scoped>
+.app-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
 .logo {
   height: 6em;
   padding: 1.5em;

+ 50 - 39
src/entrypoints/sidepanel/Chat.vue

@@ -9,10 +9,11 @@
       <el-scrollbar ref="scrollbar" @scroll="handleScroll">
         <div class="messages">
           <div v-for="(message, index) in messages" :key="index"
-            :class="['message-item', message.isSelf ? 'self' : 'other']">
-            <el-avatar :size="32" :src="message.avatar" />
+            :class="['message-item', message.role === 'user'  ? 'self' : 'other']">
+            <el-avatar :size="32" :src="message.role === 'user' ? userAvatar : avatar" />
             <div class="message-content">
-              <div class="content" v-if="!message.isSelf" :class="{ 'loading-content': message.content === '' }">
+              <div class="content" v-if="message.role ==='system'"
+                :class="{ 'loading-content': message.content === '' }">
                 <formTable v-if="message.type === 'form'" :content="message.rawContent" />
                 <span v-else v-html="message.content"></span>
                 <span class="loading-indicator" v-if="sendLoading && index === messages.length - 1">
@@ -21,7 +22,7 @@
                   <span class="dot"></span>
                 </span>
               </div>
-              <document v-else-if="message.type === 'document' && message.isSelf" :content="message.content"
+              <document v-else-if="message.type === 'document' && message.role === 'user'" :content="message.content"
                 :rawContent="message.rawContent" />
               <div v-else class="content">
                 <span v-if="message.type === ''">{{ message.content }}</span>
@@ -118,11 +119,13 @@ import document from '@/entrypoints/sidepanel/component/document.vue'
 import pageMask from '@/entrypoints/sidepanel/component/pageMask.vue'
 import ScrollToBottom from '@/entrypoints/sidepanel/component/ScrollToBottom.vue'
 import formTable from '@/entrypoints/sidepanel/component/formTable.vue'
-
+import userAvatar from '@/assets/images/user.png'
+import avatar from '@/public/icon/32.png'
 import { mockData, startMsg, mockData2, options, FunctionList } from '@/entrypoints/sidepanel/mock'
 import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResizeTextarea.ts'
 import { getPageInfo, getXlsxValue, handleInput } from './utils/index.js'
 import { useMsgStore } from '@/store/modules/msg.ts'
+import { getChatDetail } from '@/api/index.js'
 
 // 滚动条引用
 const scrollbar = ref(null)
@@ -148,8 +151,6 @@ const {
 } = useMsg(scrollbar)
 const inputMessage = ref('')
 const pageInfo = ref('')
-
-
 function handleStopAsk() {
   if (type.value === FunctionList.Intelligent_Form_filling) {
     inputMessage.value = ''
@@ -187,14 +188,11 @@ async function addMessage(msg, raw, type) {
     })
   }
   const newMessage = reactive({
-    id: messages.value.length + 1,
     type: type || '',
-    username: '我',
     rawContent: raw ?? msg,
     content: msg,
     timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-    isSelf: true,
-    avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    role:'user',
     addToHistory: !taklToHtml.value
   })
   if (type === 'document') {
@@ -243,7 +241,11 @@ const handleSummary = async () => {
       6. 对这几段内容进行综合分析及联想`
     }]
   }
-  await addMessage(pageInfoList.value, '总结', 'document')
+  const msg = await addMessage(JSON.stringify(pageInfoList.value), JSON.stringify(params.map(_ => _.content)), 'document')
+  // putChat({
+  //   id: msgUuid.value,
+  //   msg: msg
+  // })
   if (requestFlowFn) {
     isShowPage.value = false
     taklToHtml.value = false
@@ -285,6 +287,14 @@ function handleCurrentData(e) {
   }
   // 添加indexDB Store配置
   msgUuid.value = e
+  // getChatDetail(e).then(res => {
+  //   messages.value = reds
+  //   nextTick(() => {
+  //     scrollbar.value?.setScrollTop(99999)
+  //   })
+  // }).finally(res => {
+
+  // })
   useStore(e).getAll().then((res) => {
     messages.value = res
     nextTick(() => {
@@ -304,7 +314,11 @@ async function readClick() {
     ElMessage.warning(`最多添加${import.meta.env.VITE_MAX_FILE_NUMBER}个文件`)
     return
   }
+  console.log(45454);
+  
   const tempPageInfo = await getPageInfo()
+  console.log(tempPageInfo);
+  
   if (AIModel.value.file === true) {
     const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
     const file = new File([blob], tempPageInfo.title + '.txt', { type: 'text/plain' })
@@ -324,6 +338,10 @@ function deletePageInfo(i) {
 }
 
 function addNewDialogue() {
+  // if (messages.value.length === 0) {
+  //   ElMessage.warning('已经是新对话')
+  //   return
+  // }
   if (msgUuid.value === '') {
     ElMessage.warning('已经是新对话')
     return
@@ -339,7 +357,11 @@ async function handleAsk() {
   const str = inputMessage.value.trim()
   inputMessage.value = ''
   if (sendLoading.value) return
-  await addMessage(str)
+  const msg = await addMessage(str)
+  // putChat({
+  //   id: msgUuid.value,
+  //   msg: msg
+  // })
   if (type.value === FunctionList.Intelligent_Form_filling) {
     const res = await fetchRes(str)
     console.log(res);
@@ -367,7 +389,6 @@ function handleCapture() {
 function hisRecords() {
   drawerRef.value.drawer = true
 }
-
 const handleUpload = async (file) => {
   if (AIModel.value.file === true) {
     const id = await modelFileUpload(file)
@@ -389,7 +410,11 @@ const handleUpload = async (file) => {
         // if (!xlsxData.value[header]) xlsxData.value[header] = []
         xlsxData.value[header] = readData[1][i]
       })
-      addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
+      const msg = addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
+      // putChat({
+      //   id: msgUuid.value,
+      //   msg: msg
+      // })
       const { rawContent, status } = await streamRes()
       if (status === 'ok') {
         let form = []
@@ -399,16 +424,15 @@ const handleUpload = async (file) => {
       }
     } else {
       try {
-        const { data, msg } = await getFileValue(file)
+        sendLoading.value = true
+        const msg = await addMessage(`文件上传中`)
+        const data = await getFileValue(file)
+        msg.content = `已上传文件:${file.name}`
+        msg.rawContent = data
         const res2 = await getFormKeyAndValue(data, formInfo.value)
         xlsxData.value = res2.data
         msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
-        console.log(msg.rawContent);
-        console.log(messages);
-
         const { rawContent, status } = await streamRes()
-        console.log(22555);
-
         if (status === 'ok') {
           let form = []
           if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
@@ -419,6 +443,10 @@ const handleUpload = async (file) => {
         msg.content = '文件解析出错'
       } finally {
         sendLoading.value = false
+        // putChat({
+        //   id: msgUuid.value,
+        //   msg: msg
+        // })
       }
     }
     type.value = FunctionList.File_Operation
@@ -442,28 +470,12 @@ const handleUpload = async (file) => {
     isShowPage.value = true
   }
 }
-
 async function getFileValue(file) {
-  const msg = await addMessage(`文件上传中`)
-  console.log(msg, messages);
-
-  sendLoading.value = true
   let formData = new FormData()
   formData.append('file', file)
   const res = await getFileContent(formData)
-  console.log(res, 5555);
-
-  sendLoading.value = false
-  msg.content = `已上传文件:${file.name}`
-  console.log(msg.content);
-
-  msg.rawContent = res.data
-  return {
-    data: res.data,
-    msg
-  }
+  return res.data
 }
-
 // 组件挂载时滚动到底部
 onMounted(() => {
   msgStore.updateAIModel(options[0].options[0])
@@ -481,7 +493,6 @@ onMounted(() => {
       pageInfo.value = message.data
     }
   })
-
   nextTick(() => {
     scrollbar.value?.setScrollTop(99999)
   })

+ 532 - 0
src/entrypoints/sidepanel/component/Login.vue

@@ -0,0 +1,532 @@
+<template>
+    <div class="login-container">
+        <div class="login-header">
+            <img :src="logo" alt="Logo" class="logo">
+            <h1>欢迎登录</h1>
+        </div>
+        <div class="login-tabs">
+            <div class="tab" :class="{ active: activeTab === 'phone' }" @click="activeTab = 'phone'">手机号登录</div>
+            <div class="tab" :class="{ active: activeTab === 'wechat' }" @click="activeTab = 'wechat'">微信登录</div>
+        </div>
+
+        <div class="login-content">
+            <div class="login-form phone-form" v-show="activeTab === 'phone'">
+                <div class="form-group">
+                    <div class="input-with-icon">
+                        <el-icon>
+                            <Iphone />
+                        </el-icon>
+                        <el-input v-model="phone" placeholder="请输入手机号" maxlength="11" />
+                    </div>
+                </div>
+                <div class="form-group">
+                    <div class="input-with-icon verification-code">
+                        <el-icon>
+                            <Lock />
+                        </el-icon>
+                        <el-input v-model="code" placeholder="请输入验证码" maxlength="6" />
+                        <el-button class="code-btn" :disabled="codeBtnDisabled" @click="getVerificationCode">
+                            {{ codeBtnText }}
+                        </el-button>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <el-button class="login-btn" type="primary" :loading="loading" @click="handlePhoneLogin">
+                        登录
+                    </el-button>
+                </div>
+            </div>
+            <div class="login-form wechat-form" v-show="activeTab === 'wechat'">
+                <div class="qrcode-container">
+                    <div class="qrcode" :class="{ expired: isQrcodeExpired }"
+                        @click="isQrcodeExpired && refreshQrcode()">
+                        <!-- 使用vue-qrcode组件 -->
+                        <qrcode-vue :value="qrcodeValue" :width="200"/>
+                        <!-- 过期遮罩层 -->
+                        <div v-if="isQrcodeExpired" class="expired-mask">
+                            <el-icon :size="40">
+                                <Refresh />
+                            </el-icon>
+                            <p>点击刷新</p>
+                        </div>
+                    </div>
+                    <p class="qrcode-tip">请使用微信扫一扫登录</p>
+                    <p class="qrcode-refresh">二维码已失效?<a href="javascript:;" @click="refreshQrcode">点击刷新</a></p>
+                </div>
+            </div>
+            <div class="agreement">
+                <el-checkbox v-model="agreement">
+                    我已阅读并同意<a href="javascript:;">用户协议</a>和<a href="javascript:;">隐私政策</a>
+                </el-checkbox>
+            </div>
+        </div>
+
+        <div class="login-footer">
+
+            <!-- <div class="other-login">
+                <p>其他登录方式</p>
+                <div class="other-login-icons">
+                    <el-icon class="icon-item">
+                        <Connection />
+                    </el-icon>
+                    <el-icon class="icon-item">
+                        <Share />
+                    </el-icon>
+                </div>
+            </div> -->
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Iphone, Lock, Connection, Share } from '@element-plus/icons-vue'
+import QrcodeVue from 'vue-qrcode'
+import logo from '@/public/icon/icon128.png'
+import { useUserStore } from '@/store/modules/user'
+import { getCode, getQRcode, getQRcodeResult } from '@/api/index.js'
+// 获取user仓库实例
+const userStore = useUserStore()
+
+// 状态变量
+const activeTab = ref('phone')
+const isQrcodeExpired = ref(false)  // 添加二维码过期状态
+const phone = ref('')
+const code = ref('')
+const agreement = ref(false)
+const loading = ref(false)
+const codeBtnText = ref('获取验证码')
+const codeBtnDisabled = ref(false)
+// 二维码值
+const qrcodeValue = ref('https://example.com/login?token=' + Date.now())
+let countdown = 60
+let timer1 = null
+let timer2 = null
+// 刷新二维码
+const refreshQrcode = async () => {
+    // const result = await getQRcode()
+    qrcodeValue.value = 'https://example.com/login?token=' + Date.now()
+}
+
+// 获取验证码
+const getVerificationCode = async () => {
+    console.log(121);
+    
+    if (!validatePhone()) return
+
+    // 模拟发送验证码请求
+    try {
+    //     const res = await getCode({
+    //         phone: phone.value
+    //  })
+     ElMessage.success(`验证码已发送至 ${phone.value}`)
+     codeBtnDisabled.value = true
+     let timer = setInterval(() => {
+         countdown--
+         codeBtnText.value = `${countdown}秒后重新获取`
+         if (countdown <= 0) {
+             clearInterval(timer)
+             codeBtnDisabled.value = false
+             codeBtnText.value = '获取验证码'
+             countdown = 60
+         }
+     }, 1000)
+ } catch (error) {
+    console.log(error);
+    
+ }
+}
+
+// 手机号登录
+const handlePhoneLogin = async () => {
+    if (!validatePhone()) return
+    if (!code.value) {
+        ElMessage.warning('请输入验证码')
+        return
+    }
+    if (!agreement.value) {
+        ElMessage.warning('请阅读并同意用户协议和隐私政策')
+        return
+    }
+    loading.value = true
+    try {
+        // 调用user仓库的登录方法
+        const loginResult = await userStore.login({
+            phone: phone.value,
+            code: code.value,
+            type: 'phone'
+        })
+     
+    } catch (error) {
+        console.error('登录失败:', error)
+        ElMessage.error('登录失败,请稍后重试')
+    } finally {
+        loading.value = false
+    }
+}
+
+// 微信扫码登录处理
+const handleWechatLogin = async (token) => {
+    try {
+        loading.value = true
+        // 调用user仓库的登录方法,使用微信登录类型
+        const loginResult = await userStore.login({
+            type: 'wechat',
+            // 这里可以传递微信登录相关的参数
+            wechatToken: token || qrcodeValue.value.split('token=')[1]
+        })
+
+        if (loginResult) {
+            ElMessage.success('微信登录成功')
+            // 登录成功后可以执行其他操作
+        }
+    } catch (error) {
+        console.error('微信登录失败:', error)
+        ElMessage.error('微信登录失败,请稍后重试')
+    } finally {
+        loading.value = false
+    }
+}
+const checkQRcodeResult = async () => {
+    const res = await getQRcodeResult(qrcodeValue.value)
+    if (res.code === 200) {
+        await handleWechatLogin(res.data)
+    } else {
+        setTimeout(() => {
+            checkQRcodeResult()
+        }, 5000);
+    }
+}
+// 验证手机号
+const validatePhone = () => {
+    if (!phone.value) {
+        ElMessage.warning('请输入手机号')
+        return false
+    }
+    if (!/^1[3-9]\d{9}$/.test(phone.value)) {
+        ElMessage.warning('请输入正确的手机号码')
+        return false
+    }
+    return true
+}
+watch(activeTab, (newTab) => {
+  if (newTab === 'wechat') {
+      qrcodeValue.value = 'https://example.com/login?token=' + Date.now()
+        setTimeout(() => {
+            checkQRcodeResult()
+        }, 1000);
+  }
+})
+
+// 组件挂载时的处理
+onMounted(() => {
+})
+
+</script>
+
+<style lang="scss" scoped>
+.login-container {
+    max-width: 100%;
+    width: 100%;
+    background-color: #f9fafc;
+    padding: 1rem;
+    display: flex;
+    flex-direction: column;
+}
+
+.login-header {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin-bottom: 36px;
+    padding-top: 30px;
+
+    .logo {
+        width: 70px;
+        height: 70px;
+        margin-bottom: 16px;
+        border-radius: 12px;
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+    }
+
+    h1 {
+        font-size: 24px;
+        font-weight: 600;
+        color: #333;
+        letter-spacing: 0.5px;
+    }
+}
+
+.login-tabs {
+    display: flex;
+    border-bottom: 1px solid #eaedf2;
+    margin-bottom: 32px;
+    background-color: #fff;
+    border-radius: 8px 8px 0 0;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+
+    .tab {
+        flex: 1;
+        text-align: center;
+        padding: 16px 0;
+        font-size: 16px;
+        color: #666;
+        cursor: pointer;
+        position: relative;
+        transition: all 0.3s ease;
+
+        &.active {
+            color: #4361ee;
+            font-weight: 500;
+
+            &:after {
+                content: '';
+                position: absolute;
+                bottom: -1px;
+                left: 50%;
+                transform: translateX(-50%);
+                width: 40%;
+                height: 3px;
+                background-color: #4361ee;
+                border-radius: 3px 3px 0 0;
+                transition: all 0.3s ease;
+            }
+        }
+
+        &:hover:not(.active) {
+            color: #4361ee;
+            background-color: rgba(67, 97, 238, 0.04);
+        }
+    }
+}
+
+.login-content {
+    background-color: #fff;
+    border-radius: 0 0 8px 8px;
+    padding: 24px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+    margin-bottom: 24px;
+}
+
+.login-form {
+    .form-group {
+        margin-bottom: 24px;
+    }
+
+    .input-with-icon {
+        display: flex;
+        align-items: center;
+        border: 1px solid #e0e5ec;
+        border-radius: 8px;
+        padding: 0 16px;
+        transition: all 0.3s ease;
+        background-color: #f9fafc;
+
+        &:focus-within {
+            border-color: #4361ee;
+            box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
+        }
+
+        :deep(.el-input__wrapper) {
+            box-shadow: none;
+            padding: 0;
+            background-color: transparent;
+        }
+
+        :deep(.el-input__inner) {
+            height: 48px;
+            font-size: 15px;
+        }
+
+        .el-icon {
+            color: #a0aec0;
+            font-size: 18px;
+            margin-right: 8px;
+        }
+    }
+
+    .verification-code {
+        .code-btn {
+            border: none;
+            background: none;
+            color: #4361ee;
+            padding: 0 16px;
+            font-size: 14px;
+            font-weight: 500;
+            white-space: nowrap;
+            transition: all 0.3s ease;
+
+            &:hover:not(:disabled) {
+                color: #2d3fd9;
+            }
+
+            &:disabled {
+                color: #a0aec0;
+            }
+        }
+    }
+
+    .login-btn {
+        width: 100%;
+        height: 48px;
+        font-size: 16px;
+        font-weight: 500;
+        border-radius: 8px;
+        background: linear-gradient(135deg, #4361ee, #3a56d4);
+        border: none;
+        transition: all 0.3s ease;
+
+        &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 6px 15px rgba(67, 97, 238, 0.3);
+        }
+
+        &:active {
+            transform: translateY(0);
+        }
+    }
+}
+
+.qrcode-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 30px 0;
+
+    .qrcode {
+        width: 200px;
+        height: 200px;
+        margin-bottom: 20px;
+        padding: 10px;
+        background-color: #fff;
+        border-radius: 12px;
+        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+        img {
+            width:100%
+        }
+    }
+
+    .qrcode-tip {
+        font-size: 16px;
+        color: #333;
+        margin-bottom: 12px;
+        font-weight: 500;
+    }
+
+    .qrcode-refresh {
+        font-size: 14px;
+        color: #718096;
+
+        a {
+            color: #4361ee;
+            text-decoration: none;
+            font-weight: 500;
+            transition: all 0.3s ease;
+
+            &:hover {
+                color: #2d3fd9;
+                text-decoration: underline;
+            }
+        }
+    }
+}
+
+.login-footer {
+    margin-top: auto;
+    padding-top: 24px;
+
+    .agreement {
+        margin-bottom: 24px;
+        font-size: 14px;
+        color: #718096;
+        display: flex;
+        align-items: center;
+
+        :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+            background-color: #4361ee;
+            border-color: #4361ee;
+        }
+
+        a {
+            color: #4361ee;
+            text-decoration: none;
+            transition: all 0.3s ease;
+
+            &:hover {
+                color: #2d3fd9;
+                text-decoration: underline;
+            }
+        }
+    }
+
+    .other-login {
+        text-align: center;
+
+        p {
+            color: #a0aec0;
+            font-size: 14px;
+            margin-bottom: 20px;
+            position: relative;
+
+            &:before,
+            &:after {
+                content: '';
+                position: absolute;
+                top: 50%;
+                width: 70px;
+                height: 1px;
+                background-color: #e0e5ec;
+            }
+
+            &:before {
+                left: 40px;
+            }
+
+            &:after {
+                right: 40px;
+            }
+        }
+
+        .other-login-icons {
+            display: flex;
+            justify-content: center;
+            gap: 36px;
+
+            .icon-item {
+                font-size: 24px;
+                color: #718096;
+                cursor: pointer;
+                transition: all 0.3s ease;
+                background-color: #f9fafc;
+                width: 48px;
+                height: 48px;
+                border-radius: 50%;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+                &:hover {
+                    color: #4361ee;
+                    transform: translateY(-3px);
+                    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
+                }
+            }
+        }
+    }
+}
+
+@media (min-width: 768px) {
+    // .login-container {
+    //     max-width: 420px;
+    //     margin: 0 auto;
+    //     box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+    //     border-radius: 12px;
+    //     background-color: #fff;
+    //     min-height: auto;
+    //     margin-top: 40px;
+    //     margin-bottom: 40px;
+    // }
+}
+</style>

+ 73 - 0
src/entrypoints/sidepanel/component/UserDropdown.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="user-dropdown">
+    <el-dropdown trigger="hover" @command="handleCommand">
+      <div class="avatar-container">
+        <el-icon :size="20"><Menu /></el-icon>
+      </div>
+      <template #dropdown>
+        <el-dropdown-menu>
+          <!-- <el-dropdown-item command="profile">个人信息</el-dropdown-item>
+          <el-dropdown-item command="settings">设置</el-dropdown-item> -->
+          <el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
+        </el-dropdown-menu>
+      </template>
+    </el-dropdown>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { useUserStore } from '@/store/modules/user'
+import { ElMessageBox } from 'element-plus'
+import { Menu } from '@element-plus/icons-vue'
+
+const userStore = useUserStore()
+
+// 处理下拉菜单命令
+const handleCommand = (command: string) => {
+  if (command === 'logout') {
+    ElMessageBox.confirm(
+      '确定要退出登录吗?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    ).then(async () => {
+      await userStore.logout()
+    }).catch(() => {
+      // 取消退出
+    })
+  } else if (command === 'profile') {
+    // 处理个人信息
+    console.log('查看个人信息')
+  } else if (command === 'settings') {
+    // 处理设置
+    console.log('打开设置')
+  }
+}
+</script>
+
+<style scoped>
+.user-dropdown {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  z-index: 100;
+}
+
+.avatar-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  border-radius: 4px;
+  transition: background-color 0.3s;
+  color: #606266;
+}
+
+.avatar-container:hover {
+  background-color: rgba(0, 0, 0, 0.05);
+  color: #409EFF;
+}
+</style>

+ 5 - 5
src/entrypoints/sidepanel/component/document.vue

@@ -3,16 +3,16 @@ import { onMounted, computed } from 'vue'
 
 const props = defineProps({
   content: {
-    type: [String, Object],
+    type: String,
     default: ''
   },
   rawContent: {
-    type: [String, Object],
+    type: String,
     default: ''
   }
 })
 const docZil = computed(() => {
-  return props.content
+  return JSON.parse(props.content)
 })
 </script>
 
@@ -25,7 +25,7 @@ const docZil = computed(() => {
         <p class="els" :title="item.url" style="color: rgba(96,98,102,0.77)">{{ item.url }}</p>
       </div>
     </div>
-    <p class="document_content1">{{ props.rawContent }}</p>
+    <p class="document_content1">总结</p>
   </div>
 
 </template>
@@ -73,4 +73,4 @@ const docZil = computed(() => {
   border-radius: 6px;
   background-color: rgba(114, 118, 139, .06);
 }
-</style>
+</style>

+ 19 - 3
src/entrypoints/sidepanel/component/historyComponent.vue

@@ -2,6 +2,9 @@
 import { ref, inject } from 'vue'
 import { Search, Delete, Paperclip } from '@element-plus/icons-vue'
 import { ElMessageBox, ElMessage } from 'element-plus'
+import { getChatList,deleteChat } from '@/api/index.js'
+import { storeToRefs } from 'pinia';
+import { useMsgStore } from '@/store/modules/msg.ts'
 
 const drawer = ref(false)
 const count = ref(0)
@@ -11,7 +14,7 @@ const dataList = ref<any[]>([])
 // 获取父组件提供的 Hook 实例
 const { db, useStore, deleteDB } = inject('indexedDBHook') as any
 const emit = defineEmits(['currentData'])
-
+const { msgUuid, messages } = storeToRefs(useMsgStore())
 const props = defineProps({
   msgUuid: {
     type: String,
@@ -20,6 +23,11 @@ const props = defineProps({
 })
 watch(drawer, (newVal) => {
   if (newVal) {
+    // console.log(getChatList)
+    loading.value = true
+    // getChatList().then(res => {
+    //   console.log(res);
+    // }).finally(res => loading.value = false)
     loading.value = true
     getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
       dataList.value = data.filter((item: any) => item !== null).reverse()
@@ -132,6 +140,15 @@ function handleDeleteStore(e: any, item: any) {
       center: true
     }
   ).then(() => {
+    // loading.value = true
+    // deleteChat(item).then((res: any) => {
+    //   loading.value = true
+    //   if(item === msgUuid.value) messages.value = []
+    //   getChatList().then(res => {
+
+    //   }).finally(res => {
+    //     loading.value = false
+    //   })
     useStore(item).clearAll().then((res: any) => {
       loading.value = true
       getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
@@ -201,7 +218,6 @@ defineExpose({
               <el-tooltip effect="dark" content="删除" placement="top">
                 <el-button :icon="Delete" link @click="(e:any)=>handleDeleteStore(e,item.storeName)" />
               </el-tooltip>
-
             </p>
           </div>
         </template>
@@ -299,4 +315,4 @@ defineExpose({
     background-color: rgba(122, 89, 255, .2) !important;
   }
 }
-</style>
+</style>

+ 2 - 2
src/entrypoints/sidepanel/component/tools.vue

@@ -34,9 +34,9 @@ watchEffect(() => {
           <el-button class="tools_btn" link :icon="Paperclip" />
         </el-tooltip>
       </el-upload>
-      <el-tooltip effect="dark" content="截屏" placement="top">
+      <!-- <el-tooltip effect="dark" content="截屏" placement="top">
         <el-button class="tools_btn" link :icon="Scissor" @click="emit('handleCapture')" />
-      </el-tooltip>
+      </el-tooltip> -->
       <el-tooltip effect="dark" content="智能填表:选择后,在输入框描述填表流程" placement="top">
         <el-button class="tools_btn" link :icon="Edit" @click="emit('handelIntelligentFillingClick')" />
       </el-tooltip>

+ 24 - 15
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -15,6 +15,7 @@ import { getPageInfo, getFileValue } from '../utils/index.js'
 import { mockData, mockData2 } from '../mock'
 import { useMsgStore } from '@/store/modules/msg'
 import { FunctionList } from '../mock'
+import { putChat } from '@/api/index.js'
 
 export function useMsg(scrollbar?: any) {
   const { messages, msgUuid } = storeToRefs(useMsgStore())
@@ -38,7 +39,6 @@ export function useMsg(scrollbar?: any) {
     //   avatar: avator,
     //   addToHistory: !taklToHtml.value
     // })
-
     try {
       sendLoading.value = true
       // messages.value.push(obj)
@@ -53,6 +53,7 @@ export function useMsg(scrollbar?: any) {
       sendLoading.value = false
     }
   }
+
   // 发送消息
   // const handleSend = async (msg: any) => {
   //   if ( msg?.startsWith('/')) {
@@ -110,12 +111,11 @@ export function useMsg(scrollbar?: any) {
     indexTemp.value = 0
     sendLoading.value = true
     const obj: any = reactive({
-      id: messages.value.length + 1,
-      username: '用户1',
       content: '',
+      rawContent:'',
       timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      isSelf: false,
-      avatar: avator,
+      type:'',
+      rolw: 'system',
       addToHistory: !taklToHtml.value
     })
     messages.value.push(obj)
@@ -145,6 +145,11 @@ export function useMsg(scrollbar?: any) {
       return { status: 'error' }
     } finally {
       sendLoading.value = false
+      console.log(messages.value);
+      // putChat({
+      //   id: msgUuid.value,
+      //   msg:obj
+      // })
     }
   }
   let str = ''
@@ -221,14 +226,11 @@ export function useMsg(scrollbar?: any) {
     pageInfo.value = await getPageInfo()
     sendLoading.value = true
     const obj = reactive<any>({
-      id: messages.value.length + 1,
-      username: '用户1',
       content: '',
       type: '', // form 用于展示抽取的内容
       rawContent: '', // 存储原始内容
       timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      isSelf: false,
-      avatar: avator,
+      role:'system',
       addToHistory: !taklToHtml.value
     })
     let history = []
@@ -251,7 +253,7 @@ export function useMsg(scrollbar?: any) {
         .filter((item: any) => item.addToHistory)
         .slice(-20)
         .map((item: any) => ({
-          role: item.isSelf ? 'user' : 'system',
+          role: item.role,
           content: item.rawContent
         }))
     }
@@ -284,6 +286,11 @@ export function useMsg(scrollbar?: any) {
     } finally {
       //添加到存储历史
       useStore(msgUuid.value).add(cloneDeep(obj))
+      console.log(messages.value);
+      // putChat({
+      //   id: msgUuid.value,
+      //   msg:obj
+      // })
       // 处理最终内容
       sendLoading.value = false
       nextTick(() => {
@@ -291,7 +298,7 @@ export function useMsg(scrollbar?: any) {
       })
     }
   }
-
+  
   /**
    * @param data 输入数据
    * @returns true 成功
@@ -301,13 +308,10 @@ export function useMsg(scrollbar?: any) {
   async function requestFlowFn(data: any[]) {
     sendLoading.value = true
     const obj = reactive<any>({
-      id: messages.value.length + 1,
-      username: '用户1',
       content: '',
       rawContent: '', // 存储原始内容
       timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      isSelf: false,
-      avatar: avator,
+      role: 'system',
       addToHistory: !taklToHtml.value
     })
     messages.value.push(obj)
@@ -334,6 +338,11 @@ export function useMsg(scrollbar?: any) {
     }
     //添加到存储历史
     useStore(msgUuid.value).add(cloneDeep(obj))
+    console.log(messages.value);
+    // putChat({
+    //   id: msgUuid.value,
+    //   msg: obj
+    // })
     // 处理最终内容
     sendLoading.value = false
     await nextTick(() => {

+ 2 - 3
src/entrypoints/sidepanel/utils/index.js

@@ -1,14 +1,13 @@
 import * as XLSX from 'xlsx'
-import { ElMessage } from 'element-plus'
 export function getPageInfo() {
   return new Promise((res, rej) => {
     chrome.runtime.sendMessage(
       {
         type: 'FROM_SIDE_PANEL_TO_GET_PAGE_INFO'
       },
-      async (response) => {
+      (response) => {
         if (chrome.runtime.lastError) {
-          console.error('消息发送错误:', chrome.runtime.lastError)
+          console.error('消息发送错误:', chrome.runtime.lastError,555)
           rej(chrome.runtime.lastError)
         } else {
           res(response.data)

+ 255 - 0
src/store/modules/user.ts

@@ -0,0 +1,255 @@
+import { defineStore } from 'pinia'
+import { ElMessage } from 'element-plus'
+
+// 定义用户信息接口
+interface UserInfo {
+  id: string
+  username: string
+  avatar: string
+  phone: string
+  email: string
+  token: string
+  roles: string[]
+  permissions: string[]
+}
+
+// 定义登录参数接口
+interface LoginParams {
+  username?: string
+  password?: string
+  phone?: string
+  code?: string
+  type: 'account' | 'phone' | 'wechat'
+}
+
+// 从 chrome.storage.local 获取 token
+const getStoredToken = async (): Promise<string> => {
+  if (typeof chrome !== 'undefined' && chrome.storage) {
+    return new Promise((resolve) => {
+      chrome.storage.local.get('token', (result) => {
+        resolve(result.token || '')
+      })
+    })
+  }
+  return localStorage.getItem('token') || '' // 降级方案
+}
+
+// 初始化 token 为空字符串,后续异步获取
+export const useUserStore = defineStore('user', {
+  state: () => ({
+    token: '',
+    userInfo: <UserInfo | null>null,
+    roles: <string[]>[],
+    permissions: <string[]>[],
+    loginLoading: false
+  }),
+  
+  getters: {
+    isLogin: (state) => !!state.token,
+    hasRole: (state) => (role: string) => state.roles.includes(role),
+    hasPermission: (state) => (permission: string) => state.permissions.includes(permission)
+  },
+  
+  actions: {
+    // 初始化 store,从 chrome.storage 加载数据
+    async initStore() {
+      if (typeof chrome !== 'undefined' && chrome.storage) {
+        const data = await new Promise<any>((resolve) => {
+          chrome.storage.local.get(['token', 'userInfo', 'roles', 'permissions'], (result) => {
+            resolve(result)
+          })
+        })
+        
+        if (data.token) this.token = data.token
+        if (data.userInfo) this.userInfo = JSON.parse(data.userInfo)
+        if (data.roles) this.roles = JSON.parse(data.roles)
+        if (data.permissions) this.permissions = JSON.parse(data.permissions)
+      } else {
+        // 降级方案:使用 localStorage
+        this.token = localStorage.getItem('token') || ''
+        const userInfo = localStorage.getItem('userInfo')
+        if (userInfo) this.userInfo = JSON.parse(userInfo)
+        const roles = localStorage.getItem('roles')
+        if (roles) this.roles = JSON.parse(roles)
+        const permissions = localStorage.getItem('permissions')
+        if (permissions) this.permissions = JSON.parse(permissions)
+      }
+    },
+    
+    // 设置Token
+    async setToken(token: string) {
+      this.token = token
+      
+      if (typeof chrome !== 'undefined' && chrome.storage) {
+        await chrome.storage.local.set({ token })
+      } else {
+        localStorage.setItem('token', token)
+      }
+    },
+    
+    // 清除Token
+    async clearToken() {
+      this.token = ''
+      
+      if (typeof chrome !== 'undefined' && chrome.storage) {
+        await chrome.storage.local.remove('token')
+      } else {
+        localStorage.removeItem('token')
+      }
+    },
+    
+    // 登录
+    async login(params: LoginParams) {
+      try {
+        this.loginLoading = true
+        
+        // 这里替换为实际的API调用
+        // const res = await api.login(params)
+        
+        // 模拟登录成功
+        const res = {
+          code: 200,
+          data: {
+            token: 'mock_token_' + '1234567890',
+            userInfo: {
+              id: '1',
+              username: params.username || '用户' + Math.floor(Math.random() * 1000),
+              avatar: 'https://placeholder.com/150',
+              phone: params.phone || '13800138000',
+              email: 'user@example.com',
+              roles: ['user'],
+              permissions: ['read', 'write']
+            }
+          },
+          message: '登录成功'
+        }
+        
+        if (res.code === 200) {
+          await this.setToken(res.data.token)
+          this.userInfo = res.data.userInfo
+          this.roles = res.data.userInfo.roles
+          this.permissions = res.data.userInfo.permissions
+            chrome.runtime.sendMessage({
+                type: 'SET_TOKEN',
+                data: {
+                    token: res.data.token
+                }
+            }, function (response) {
+            });
+          // 存储用户信息到 chrome.storage
+          if (typeof chrome !== 'undefined' && chrome.storage) {
+            await chrome.storage.local.set({
+              userInfo: JSON.stringify(this.userInfo),
+              roles: JSON.stringify(this.roles),
+              permissions: JSON.stringify(this.permissions)
+            })
+          } else {
+            // 降级方案:使用 localStorage
+            localStorage.setItem('userInfo', JSON.stringify(this.userInfo))
+            localStorage.setItem('roles', JSON.stringify(this.roles))
+            localStorage.setItem('permissions', JSON.stringify(this.permissions))
+          }
+          
+          ElMessage.success(res.message)
+          return true
+        } else {
+          ElMessage.error(res.message || '登录失败')
+          return false
+        }
+      } catch (error: any) {
+        ElMessage.error(error.message || '登录出错')
+        return false
+      } finally {
+        this.loginLoading = false
+      }
+    },
+    
+    // 退出登录
+    async logout() {
+      try {
+        // 这里替换为实际的API调用
+        // await api.logout()
+        
+        // 清除用户数据
+        await this.clearUserData()
+        ElMessage.success('退出登录成功')
+        return true
+      } catch (error: any) {
+        ElMessage.error(error.message || '退出登录出错')
+        return false
+      }
+    },
+    
+    // 获取用户信息
+    async getUserInfo() {
+      try {
+        if (!this.token) return false
+        
+        // 这里替换为实际的API调用
+        // const res = await api.getUserInfo()
+        
+        // 模拟获取用户信息
+        const res = {
+          code: 200,
+          data: {
+            id: '1',
+            username: '当前用户',
+            avatar: 'https://placeholder.com/150',
+            phone: '13800138000',
+            email: 'user@example.com',
+            roles: ['user'],
+            permissions: ['read', 'write']
+          },
+          message: '获取用户信息成功'
+        }
+        if (res.code === 200) {
+          this.userInfo = res.data
+          this.roles = res.data.roles
+          this.permissions = res.data.permissions
+          
+          // 存储用户信息到 chrome.storage
+          if (typeof chrome !== 'undefined' && chrome.storage) {
+            await chrome.storage.local.set({
+              userInfo: JSON.stringify(this.userInfo),
+              roles: JSON.stringify(this.roles),
+              permissions: JSON.stringify(this.permissions)
+            })
+          } else {
+            // 降级方案:使用 localStorage
+            localStorage.setItem('userInfo', JSON.stringify(this.userInfo))
+            localStorage.setItem('roles', JSON.stringify(this.roles))
+            localStorage.setItem('permissions', JSON.stringify(this.permissions))
+          }
+          
+          return true
+        } else {
+          await this.clearUserData()
+          ElMessage.error(res.message || '获取用户信息失败')
+          return false
+        }
+      } catch (error: any) {
+        await this.clearUserData()
+        ElMessage.error(error.message || '获取用户信息出错')
+        return false
+      }
+    },
+    
+    // 清除用户数据
+    async clearUserData() {
+      await this.clearToken()
+      this.userInfo = null
+      this.roles = []
+      this.permissions = []
+      
+      if (typeof chrome !== 'undefined' && chrome.storage) {
+        await chrome.storage.local.remove(['userInfo', 'roles', 'permissions'])
+      } else {
+        localStorage.removeItem('userInfo')
+        localStorage.removeItem('roles')
+        localStorage.removeItem('permissions')
+      }
+    }
+    
+    // 其他方法保持不变...
+  }
+})

+ 108 - 0
src/utils/contentUtils.js

@@ -372,3 +372,111 @@ export function getFavicon() {
   const url = new URL(window.location.href)
   return `${url.protocol}//${url.hostname}/favicon.ico`
 }
+export function cleanPage2(body) {
+  const cloneBody = body.cloneNode(true)
+  // 移除所有行内样式
+  const elementsWithInlineStyle = cloneBody.querySelectorAll('[style]')
+  elementsWithInlineStyle.forEach((element) => {
+    element.removeAttribute('style')
+  })
+
+  // 移除所有注释节点
+  const comments = []
+  const walk = document.createTreeWalker(
+    cloneBody,
+    NodeFilter.SHOW_COMMENT,
+    null,
+    false
+  )
+  while (walk.nextNode()) {
+    comments.push(walk.currentNode)
+  }
+  comments.forEach((comment) => comment.remove())
+
+  // 移除所有SVG图标
+  cloneBody
+    .querySelectorAll('script, style,  svg')
+    .forEach((svg) => svg.remove())
+
+  // 移除所有图片的src属性
+  const images = cloneBody.querySelectorAll('img')
+  images.forEach((img) => {
+    img.removeAttribute('src')
+  })
+
+  // 可选择移除其他无关内容,比如脚本和广告等
+  // const scripts = body.querySelectorAll('script');
+  // scripts.forEach(script => script.remove());
+
+  // const iframes = body.querySelectorAll('iframe');
+  // iframes.forEach(iframe => iframe.remove());
+
+  const ads = cloneBody.querySelectorAll('.ad, .advertisement, .ads')
+  ads.forEach((ad) => ad.remove())
+
+  const regex = /\s+/g
+
+  // 定义标准 HTML 属性集合 排除id class style href src target
+  const standardAttributes = new Set([
+    'id',
+    'class',
+    'alt',
+    'title',
+    'type',
+    'value',
+    'name',
+    'placeholder',
+    'disabled',
+    'checked',
+    'selected',
+    'readonly',
+    'required',
+    'maxlength',
+    'min',
+    'max',
+    'step',
+    'pattern',
+    'autocomplete',
+    'autofocus',
+    'multiple',
+    'rows',
+    'cols',
+    'rel',
+    'aria-*'
+  ])
+
+  // 创建一个临时容器
+  const temp = document.createElement('div')
+  temp.innerHTML = cloneBody.outerHTML
+
+
+  // 删除 <link> 标签
+  const linkTags = temp.querySelectorAll('link')
+  linkTags.forEach((link) => {
+    link.remove()
+  })
+
+  // 遍历所有元素
+  const elements = temp.querySelectorAll('*')
+  elements.forEach((element) => {
+    // 获取所有属性
+    const attributes = Array.from(element.attributes)
+    attributes.forEach((attr) => {
+      // 如果属性不是标准属性,则移除
+      if (
+        !standardAttributes.has(attr.name) &&
+        !attr.name.startsWith('aria-')
+      ) {
+        element.removeAttribute(attr.name)
+      }
+    })
+  })
+
+  // 获取处理后的 HTML 字符串
+  const cleanedHtml = temp.innerHTML.trim().replace(regex, ' ')
+
+  // 销毁临时容器
+  temp.remove()
+  // content.outerHTML.trim().replace(/\s+/g, " ");
+  return cleanedHtml
+}

+ 6 - 0
src/utils/errorCode copy.js

@@ -0,0 +1,6 @@
+export default {
+  '401': '认证失败,无法访问系统资源',
+  '403': '当前操作没有权限',
+  '404': '访问资源不存在',
+  'default': '系统未知错误,请反馈给管理员'
+}

+ 110 - 1
src/utils/page-analyzer.js

@@ -163,7 +163,7 @@ export function cleanPage(body) {
   }
 
   // 从临时容器的子元素开始处理
-  removeEmpty(temp)
+  // removeEmpty(temp) //会导致标签元素被移除
 
   // 删除 <link> 标签
   const linkTags = temp.querySelectorAll('link')
@@ -195,3 +195,112 @@ export function cleanPage(body) {
   // content.outerHTML.trim().replace(/\s+/g, " ");
   return cleanedHtml
 }
+function cleanPage2(body) {
+  const cloneBody = body.cloneNode(true)
+  // 移除所有行内样式
+  const elementsWithInlineStyle = cloneBody.querySelectorAll('[style]')
+  elementsWithInlineStyle.forEach((element) => {
+    element.removeAttribute('style')
+  })
+
+  // 移除所有注释节点
+  const comments = []
+  const walk = document.createTreeWalker(
+    cloneBody,
+    NodeFilter.SHOW_COMMENT,
+    null,
+    false
+  )
+  while (walk.nextNode()) {
+    comments.push(walk.currentNode)
+  }
+  comments.forEach((comment) => comment.remove())
+
+  // 移除所有SVG图标
+  cloneBody
+    .querySelectorAll('script, style,  svg')
+    .forEach((svg) => svg.remove())
+
+  // 移除所有图片的src属性
+  const images = cloneBody.querySelectorAll('img')
+  images.forEach((img) => {
+    img.removeAttribute('src')
+  })
+
+  // 可选择移除其他无关内容,比如脚本和广告等
+  // const scripts = body.querySelectorAll('script');
+  // scripts.forEach(script => script.remove());
+
+  // const iframes = body.querySelectorAll('iframe');
+  // iframes.forEach(iframe => iframe.remove());
+
+  const ads = cloneBody.querySelectorAll('.ad, .advertisement, .ads')
+  ads.forEach((ad) => ad.remove())
+
+  const regex = /\s+/g
+
+  // 定义标准 HTML 属性集合 排除id class style href src target
+  const standardAttributes = new Set([
+    'id',
+    'class',
+    'alt',
+    'title',
+    'type',
+    'value',
+    'name',
+    'placeholder',
+    'disabled',
+    'checked',
+    'selected',
+    'readonly',
+    'required',
+    'maxlength',
+    'min',
+    'max',
+    'step',
+    'pattern',
+    'autocomplete',
+    'autofocus',
+    'multiple',
+    'rows',
+    'cols',
+    'rel',
+    'aria-*'
+  ])
+
+  // 创建一个临时容器
+  const temp = document.createElement('div')
+  temp.innerHTML = cloneBody.outerHTML
+
+
+  // 删除 <link> 标签
+  const linkTags = temp.querySelectorAll('link')
+  linkTags.forEach((link) => {
+    link.remove()
+  })
+
+  // 遍历所有元素
+  const elements = temp.querySelectorAll('*')
+  elements.forEach((element) => {
+    // 获取所有属性
+    const attributes = Array.from(element.attributes)
+    attributes.forEach((attr) => {
+      // 如果属性不是标准属性,则移除
+      if (
+        !standardAttributes.has(attr.name) &&
+        !attr.name.startsWith('aria-')
+      ) {
+        element.removeAttribute(attr.name)
+      }
+    })
+  })
+
+  // 获取处理后的 HTML 字符串
+  const cleanedHtml = temp.innerHTML.trim().replace(regex, ' ')
+
+  // 销毁临时容器
+  temp.remove()
+  // content.outerHTML.trim().replace(/\s+/g, " ");
+  return cleanedHtml
+}
+

+ 20 - 41
src/utils/request.js

@@ -1,65 +1,40 @@
 import axios, { CancelToken, isCancel } from 'axios'
 import { ElMessage, ElNotification } from 'element-plus'
 import errorCode from './errorCode.js'
+import { useUserStore } from '@/store/modules/user'
 
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
+
 // 创建axios实例
 const service = axios.create({
-  // axios中请求配置有baseURL选项,表示请求URL公共部分
   baseURL: import.meta.env.VITE_APP_BASE_API,
-  // 超时
   timeout: 60000
 })
 let cancel
 // request拦截器
 service.interceptors.request.use(
-  (config) => {
-    // console.log(config)
+ async (config) => {
     if (cancel) cancel('取消了')
     if (config.cancel) {
       config.cancelToken = new CancelToken((c) => {
-        //c是一个函数,调用c就可以关闭本次请求
         cancel = c
       })
     }
-    // // 是否需要设置 token
-    // const isToken = (config.headers || {}).isToken === false
-    // // 是否需要防止数据重复提交
-    // const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
-    let token = sessionStorage.getItem('token')
+
+    const {token} = await new Promise((resolve) => {
+      chrome.storage.local.get(['token'], (result) => {
+        resolve(result)
+      })
+    })
+    
+    // 当token不存在时,执行退出登录
+    // if (!token && !config.url.includes('/login')) {
+    //   return Promise.reject('请先登录')
+    // }
+
     if (token) {
-      config.headers['Authorization'] = 'Bearer ' + token // 让每个请求携带自定义token 请根据实际情况自行修改
+      config.headers['Authorization'] = 'Bearer ' + token
     }
-    // get请求映射params参数
-    // if (config.method === 'get' && config.params) {
-    //   let url = config.url + '?' + tansParams(config.params);
-    //   url = url.slice(0, -1);
-    //   config.params = {};
-    //   config.url = url;
-    // }
-    // if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
-    //   const requestObj = {
-    //     url: config.url,
-    //     data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
-    //     time: new Date().getTime()
-    //   }
-    //   const sessionObj = cache.session.getJSON('sessionObj')
-    //   if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
-    //     cache.session.setJSON('sessionObj', requestObj)
-    //   } else {
-    //     const s_url = sessionObj.url;                // 请求地址
-    //     const s_data = sessionObj.data;              // 请求数据
-    //     const s_time = sessionObj.time;              // 请求时间
-    //     const interval = 1000;                       // 间隔时间(ms),小于此时间视为重复提交
-    //     if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
-    //       const message = '数据正在处理,请勿重复提交';
-    //       console.warn(`[${s_url}]: ` + message)
-    //       return Promise.reject(new Error(message))
-    //     } else {
-    //       cache.session.setJSON('sessionObj', requestObj)
-    //     }
-    //   }
-    // }
     return config
   },
   (error) => {
@@ -94,6 +69,10 @@ service.interceptors.response.use(
       //     isRelogin.show = false;
       //   });
       // }
+      const userStore = useUserStore()
+
+      userStore.logout()
+
       return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
     } else if (code === 500) {
       ElMessage({ message: msg, type: 'error', grouping: true })