Procházet zdrojové kódy

更新依赖版本,优化代码格式,提升可读性,修复部分样式问题,确保组件正常工作。

wui před 7 měsíci
rodič
revize
23312ffe35

+ 56 - 63
package-lock.json

@@ -19,7 +19,7 @@
         "pinia": "^3.0.1",
         "primeflex": "^4.0.0",
         "primeicons": "^7.0.0",
-        "primevue": "^4.3.3",
+        "primevue": "^4.3.5",
         "tailwindcss-primeui": "^0.6.1",
         "vue": "^3.5.13",
         "vue-router": "^4.5.0",
@@ -861,12 +861,22 @@
       }
     },
     "node_modules/@primeuix/styles": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/@primeuix/styles/-/styles-1.0.0.tgz",
-      "integrity": "sha512-j/TlbqihLNMP37zFNjxac5dTRaQEf5Ldrv0P7NwKigCCc/+MI5j4MddxDw1LnxkGhWCJ1Gjbt9uwyQteWtSv7A==",
-      "license": "MIT",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/@primeuix/styles/-/styles-1.1.1.tgz",
+      "integrity": "sha512-oguFY2Rs4ZqdcFqEKxBt2d8trTkAjJ5BGTaDV0zbwdqRCRcZp9+di3K3Wv57CW/+tFDA0z1Dg7Dpm7wmkOII9Q==",
       "dependencies": {
-        "@primeuix/styled": "^0.5.0"
+        "@primeuix/styled": "^0.6.1"
+      }
+    },
+    "node_modules/@primeuix/styles/node_modules/@primeuix/styled": {
+      "version": "0.6.4",
+      "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.6.4.tgz",
+      "integrity": "sha512-7ePLwqazLV0x269YlPMeE4wtQKT0NScY2/gEin0/96krTiGiElmlzKMMbH69bVApm/sfen5DZGuCEEwPiBJJ5g==",
+      "dependencies": {
+        "@primeuix/utils": "^0.5.3"
+      },
+      "engines": {
+        "node": ">=12.11.0"
       }
     },
     "node_modules/@primeuix/themes": {
@@ -879,22 +889,20 @@
       }
     },
     "node_modules/@primeuix/utils": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.2.tgz",
-      "integrity": "sha512-fHL0DGnyhL/9toBoV0cO6L+Xg/uaxmOHJW4SrDNMq6GQ7cDXR3Y0vDLvX5j/8kIsaC7LB3329sQLNHgtEETnWw==",
-      "license": "MIT",
+      "version": "0.5.4",
+      "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.4.tgz",
+      "integrity": "sha512-8LggV3Jz59pymHQD10e/u63z/GemQ22RBeu2Gb1eJgBYVwn1iOb82LR+daeAc/LxrXCC5pHnftnCmnZO6vInLA==",
       "engines": {
         "node": ">=12.11.0"
       }
     },
     "node_modules/@primevue/core": {
-      "version": "4.3.3",
-      "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.3.3.tgz",
-      "integrity": "sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ==",
-      "license": "MIT",
+      "version": "4.3.5",
+      "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.3.5.tgz",
+      "integrity": "sha512-YBlSr/EbXsnsTOyfgqmbrJQ7AI5EThaeGZvfDFjPIIEpokEK+Q32++9xPn3MH8rcM8zPsfMeBOWi4/OJkOqG4w==",
       "dependencies": {
-        "@primeuix/styled": "^0.5.0",
-        "@primeuix/utils": "^0.5.1"
+        "@primeuix/styled": "^0.6.4",
+        "@primeuix/utils": "^0.5.3"
       },
       "engines": {
         "node": ">=12.11.0"
@@ -903,20 +911,7 @@
         "vue": "^3.5.0"
       }
     },
-    "node_modules/@primevue/forms": {
-      "version": "4.3.5",
-      "resolved": "https://registry.npmmirror.com/@primevue/forms/-/forms-4.3.5.tgz",
-      "integrity": "sha512-szMwme/1nCLnIdJDykkxFXtp4hVCxGiX8+EHZ18a0FAEQC6JzyhJ+mHh+S34nqMjtUXJntkAAFW4Jk3uxOqIBg==",
-      "dependencies": {
-        "@primeuix/forms": "^0.0.4",
-        "@primeuix/utils": "^0.5.3",
-        "@primevue/core": "4.3.5"
-      },
-      "engines": {
-        "node": ">=12.11.0"
-      }
-    },
-    "node_modules/@primevue/forms/node_modules/@primeuix/styled": {
+    "node_modules/@primevue/core/node_modules/@primeuix/styled": {
       "version": "0.6.4",
       "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.6.4.tgz",
       "integrity": "sha512-7ePLwqazLV0x269YlPMeE4wtQKT0NScY2/gEin0/96krTiGiElmlzKMMbH69bVApm/sfen5DZGuCEEwPiBJJ5g==",
@@ -927,37 +922,26 @@
         "node": ">=12.11.0"
       }
     },
-    "node_modules/@primevue/forms/node_modules/@primeuix/utils": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.4.tgz",
-      "integrity": "sha512-8LggV3Jz59pymHQD10e/u63z/GemQ22RBeu2Gb1eJgBYVwn1iOb82LR+daeAc/LxrXCC5pHnftnCmnZO6vInLA==",
-      "engines": {
-        "node": ">=12.11.0"
-      }
-    },
-    "node_modules/@primevue/forms/node_modules/@primevue/core": {
+    "node_modules/@primevue/forms": {
       "version": "4.3.5",
-      "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.3.5.tgz",
-      "integrity": "sha512-YBlSr/EbXsnsTOyfgqmbrJQ7AI5EThaeGZvfDFjPIIEpokEK+Q32++9xPn3MH8rcM8zPsfMeBOWi4/OJkOqG4w==",
+      "resolved": "https://registry.npmmirror.com/@primevue/forms/-/forms-4.3.5.tgz",
+      "integrity": "sha512-szMwme/1nCLnIdJDykkxFXtp4hVCxGiX8+EHZ18a0FAEQC6JzyhJ+mHh+S34nqMjtUXJntkAAFW4Jk3uxOqIBg==",
       "dependencies": {
-        "@primeuix/styled": "^0.6.4",
-        "@primeuix/utils": "^0.5.3"
+        "@primeuix/forms": "^0.0.4",
+        "@primeuix/utils": "^0.5.3",
+        "@primevue/core": "4.3.5"
       },
       "engines": {
         "node": ">=12.11.0"
-      },
-      "peerDependencies": {
-        "vue": "^3.5.0"
       }
     },
     "node_modules/@primevue/icons": {
-      "version": "4.3.3",
-      "resolved": "https://registry.npmmirror.com/@primevue/icons/-/icons-4.3.3.tgz",
-      "integrity": "sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w==",
-      "license": "MIT",
+      "version": "4.3.5",
+      "resolved": "https://registry.npmmirror.com/@primevue/icons/-/icons-4.3.5.tgz",
+      "integrity": "sha512-+V8XG6MEvczw3Ufz7+ABSSCaVdFCYKRHvVDmXpS65AUeQTDEqmJz3xx2UiYYdASA6Gb2yIKdVztTcRjHFtiAnw==",
       "dependencies": {
-        "@primeuix/utils": "^0.5.1",
-        "@primevue/core": "4.3.3"
+        "@primeuix/utils": "^0.5.3",
+        "@primevue/core": "4.3.5"
       },
       "engines": {
         "node": ">=12.11.0"
@@ -3555,20 +3539,29 @@
     "node_modules/primeicons": {
       "version": "7.0.0",
       "resolved": "https://registry.npmmirror.com/primeicons/-/primeicons-7.0.0.tgz",
-      "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==",
-      "license": "MIT"
+      "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
     },
     "node_modules/primevue": {
-      "version": "4.3.3",
-      "resolved": "https://registry.npmmirror.com/primevue/-/primevue-4.3.3.tgz",
-      "integrity": "sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q==",
-      "license": "MIT",
+      "version": "4.3.5",
+      "resolved": "https://registry.npmmirror.com/primevue/-/primevue-4.3.5.tgz",
+      "integrity": "sha512-KYjLrf7W96qVOFdX2nyap5IrJIEF8qEfLaHpMPw+H3SCd7zV6uiIrOYBNvovk677rhjBGpSjEbxTFY/K+i/DMA==",
       "dependencies": {
-        "@primeuix/styled": "^0.5.0",
-        "@primeuix/styles": "^1.0.0",
-        "@primeuix/utils": "^0.5.1",
-        "@primevue/core": "4.3.3",
-        "@primevue/icons": "4.3.3"
+        "@primeuix/styled": "^0.6.4",
+        "@primeuix/styles": "^1.1.1",
+        "@primeuix/utils": "^0.5.3",
+        "@primevue/core": "4.3.5",
+        "@primevue/icons": "4.3.5"
+      },
+      "engines": {
+        "node": ">=12.11.0"
+      }
+    },
+    "node_modules/primevue/node_modules/@primeuix/styled": {
+      "version": "0.6.4",
+      "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.6.4.tgz",
+      "integrity": "sha512-7ePLwqazLV0x269YlPMeE4wtQKT0NScY2/gEin0/96krTiGiElmlzKMMbH69bVApm/sfen5DZGuCEEwPiBJJ5g==",
+      "dependencies": {
+        "@primeuix/utils": "^0.5.3"
       },
       "engines": {
         "node": ">=12.11.0"

+ 1 - 1
package.json

@@ -22,7 +22,7 @@
     "pinia": "^3.0.1",
     "primeflex": "^4.0.0",
     "primeicons": "^7.0.0",
-    "primevue": "^4.3.3",
+    "primevue": "^4.3.5",
     "tailwindcss-primeui": "^0.6.1",
     "vue": "^3.5.13",
     "vue-router": "^4.5.0",

+ 1 - 1
src/App.vue

@@ -8,4 +8,4 @@ import ConfirmDialog from 'primevue/confirmdialog'
   <RouterView />
   <Toast />
   <ConfirmDialog />
-</template>
+</template>

+ 4 - 3
src/assets/main.css

@@ -1,7 +1,8 @@
-@import "tailwindcss";
-@import "tailwindcss-primeui";
+@import 'tailwindcss';
+@import 'tailwindcss-primeui';
 
-html, body {
+html,
+body {
   font-family:
     Inter,
     -apple-system,

+ 1 - 1
src/main.js

@@ -5,7 +5,7 @@ import { createPinia } from 'pinia'
 import PrimeVue from 'primevue/config'
 import ToastService from 'primevue/toastservice'
 import ConfirmService from 'primevue/confirmationservice'
-import Aura from '@primeuix/themes/aura';
+import Aura from '@primeuix/themes/aura'
 import 'primeicons/primeicons.css'
 
 import App from './App.vue'

+ 8 - 4
src/services/api.js

@@ -158,7 +158,7 @@ export const deleteRecord = async (id) => {
 export const uploadFile = async (file) => {
   const formData = new FormData()
   formData.append('file', file)
-  
+
   const response = await api.post('/files/upload', formData, {
     headers: {
       'Content-Type': 'multipart/form-data'
@@ -169,8 +169,12 @@ export const uploadFile = async (file) => {
 
 // 文件下载API
 export const downloadFile = async (key) => {
-  const response = await api.post('/files/download', { key }, {
-    responseType: 'blob'
-  })
+  const response = await api.post(
+    '/files/download',
+    { key },
+    {
+      responseType: 'blob'
+    }
+  )
   return response.data
 }

+ 49 - 52
src/views/ChatRecordsView.vue

@@ -23,25 +23,24 @@ const parseDate = (dateStr) => {
     // 处理形如 "2025/6/10 17:37:21" 的日期格式
     const parts = dateStr.split(' ')
     if (parts.length !== 2) return new Date(0)
-    
+
     const dateParts = parts[0].split('/')
     const timeParts = parts[1].split(':')
-    
+
     if (dateParts.length !== 3 || timeParts.length !== 3) return new Date(0)
-    
+
     const year = parseInt(dateParts[0])
     const month = parseInt(dateParts[1]) - 1 // 月份从0开始
     const day = parseInt(dateParts[2])
     const hour = parseInt(timeParts[0])
     const minute = parseInt(timeParts[1])
     const second = parseInt(timeParts[2])
-    
+
     // 检查是否为有效日期值
-    if (isNaN(year) || isNaN(month) || isNaN(day) || 
-        isNaN(hour) || isNaN(minute) || isNaN(second)) {
+    if (isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hour) || isNaN(minute) || isNaN(second)) {
       return new Date(0)
     }
-    
+
     return new Date(year, month, day, hour, minute, second)
   } catch (e) {
     console.error('日期解析错误:', e, dateStr)
@@ -52,25 +51,25 @@ const parseDate = (dateStr) => {
 // 创建导出摘要数据
 const createExportSummary = () => {
   if (!exportInfo.value) return
-  
+
   const { user, stats, date } = exportInfo.value
-  
+
   // 创建导出摘要数据对象
   exportSummaryData.value = {
-    name: "导出摘要",
+    name: '导出摘要',
     messages: [
       {
         id: 1,
-        title: "Telegram 聊天记录导出",
+        title: 'Telegram 聊天记录导出',
         exportTime: date.split('T')[0].replace(/-/g, '/') + ' ' + date.split('T')[1].substring(0, 8),
         userName: user.firstName + ' ' + user.lastName,
         userHandle: user.username,
         userId: user.id,
         dialogCount: stats.dialogs,
         imageCount: stats.images,
-        mediaDownload: stats.skipMediaDownload ? "未下载" : "已下载",
-        text: "",
-        mediaType: "None",
+        mediaDownload: stats.skipMediaDownload ? '未下载' : '已下载',
+        text: '',
+        mediaType: 'None',
         date: date.split('T')[0].replace(/-/g, '/') + ' ' + date.split('T')[1].substring(0, 8),
         imagePath: null
       }
@@ -92,10 +91,10 @@ const handleDragLeave = (e) => {
 const handleDrop = async (e) => {
   e.preventDefault()
   isDragOver.value = false
-  
+
   const files = e.dataTransfer?.files
   if (!files || files.length === 0) return
-  
+
   const file = files[0]
   if (!file.name.endsWith('.zip')) {
     toast.add({
@@ -106,50 +105,49 @@ const handleDrop = async (e) => {
     })
     return
   }
-  
+
   await processZipFile(file)
 }
 
 // 处理ZIP文件
 const processZipFile = async (file) => {
   isLoading.value = true
-  
+
   try {
     const zip = new JSZip()
     const zipDataResult = await zip.loadAsync(file)
     zipData.value = zipDataResult // 保存ZIP数据引用
-    
+
     // 查找并解析export_info.json
     const exportInfoFile = zipDataResult.file('export_info.json')
     if (!exportInfoFile) {
       throw new Error('未找到export_info.json文件')
     }
-    
+
     const exportInfoContent = await exportInfoFile.async('string')
     exportInfo.value = JSON.parse(exportInfoContent)
-    
+
     // 查找并解析telegram_export.json
     const exportDataFile = zipDataResult.file('telegram_export.json')
     if (!exportDataFile) {
       throw new Error('未找到telegram_export.json文件')
     }
-    
+
     const exportDataContent = await exportDataFile.async('string')
     exportData.value = JSON.parse(exportDataContent)
-    
+
     // 创建导出摘要
     createExportSummary()
-    
+
     // 默认选择导出摘要会话
     selectChat('export_summary')
-    
+
     toast.add({
       severity: 'success',
       summary: '成功',
       detail: '文件解析成功',
       life: 3000
     })
-    
   } catch (error) {
     console.error('解析文件失败:', error)
     toast.add({
@@ -173,9 +171,9 @@ const selectChat = async (chatId) => {
 // 获取聊天列表
 const getChatList = () => {
   if (!exportData.value || !exportSummaryData.value) return []
-  
+
   const chats = []
-  
+
   // 添加导出摘要作为第一个会话
   chats.push({
     chatId: 'export_summary',
@@ -183,7 +181,7 @@ const getChatList = () => {
     messageCount: exportSummaryData.value.messages.length,
     isSummary: true
   })
-  
+
   // 按最新消息时间排序聊天,过滤掉export_summary避免重复
   const sortedChats = Object.entries(exportData.value)
     .filter(([chatId]) => chatId !== 'export_summary') // 过滤掉export_summary
@@ -193,7 +191,7 @@ const getChatList = () => {
       return { chatId, chat, latestDate }
     })
     .sort((a, b) => b.latestDate.getTime() - a.latestDate.getTime())
-  
+
   sortedChats.forEach(({ chatId, chat }) => {
     chats.push({
       chatId,
@@ -202,7 +200,7 @@ const getChatList = () => {
       isSummary: false
     })
   })
-  
+
   return chats
 }
 
@@ -224,11 +222,11 @@ const getCurrentChatTitle = () => {
 const getCurrentMessages = () => {
   const chat = getCurrentChat()
   if (!chat) return []
-  
+
   if (selectedChatId.value === 'export_summary') {
     return chat.messages
   }
-  
+
   // 确保所有会话消息按日期排序(从旧到新)
   return [...chat.messages].sort((a, b) => {
     const dateA = parseDate(a.date)
@@ -247,14 +245,14 @@ const escapeHtml = (html) => {
 // 获取图片的base64数据
 const getImageBase64 = async (imagePath) => {
   if (!zipData.value || !imagePath) return null
-  
+
   try {
     const imageFile = zipData.value.file(imagePath)
     if (!imageFile) {
       console.warn('图片文件不存在:', imagePath)
       return null
     }
-    
+
     const imageData = await imageFile.async('base64')
     return imageData
   } catch (error) {
@@ -266,10 +264,10 @@ const getImageBase64 = async (imagePath) => {
 // 处理消息内容(同步版本,用于模板)
 const processMessageContent = (message) => {
   let content = ''
-  
+
   // 确保有时间显示,如果没有date字段则显示当前时间或默认值
   const messageTime = message.date || '未知时间'
-  
+
   if (message.mediaType === 'Photo' && message.imagePath) {
     content += `
       <div class="message-text">${escapeHtml(message.text)}</div>
@@ -282,7 +280,7 @@ const processMessageContent = (message) => {
     // 尝试提取URL
     const urlMatch = message.text.match(/https?:\/\/[^\s]+/)
     const url = urlMatch ? urlMatch[0] : message.text
-    
+
     content += `
       <div class="message-text">${escapeHtml(message.text)}</div>
       <div class="message-web-page">
@@ -296,7 +294,7 @@ const processMessageContent = (message) => {
       <div class="message-time">${messageTime}</div>
     `
   }
-  
+
   return content
 }
 
@@ -304,7 +302,7 @@ const processMessageContent = (message) => {
 const handleImagesAfterUpdate = async () => {
   await nextTick()
   const images = document.querySelectorAll('.message-image[data-image-path]')
-  
+
   for (const img of images) {
     const imagePath = img.dataset.imagePath
     if (imagePath && !img.src.includes('data:image')) {
@@ -335,7 +333,7 @@ const handleImagesAfterUpdate = async () => {
 <template>
   <div class="chat-records-container">
     <!-- 拖拽区域 -->
-    <div 
+    <div
       v-if="!exportData"
       ref="dropZoneRef"
       class="drop-zone"
@@ -360,7 +358,7 @@ const handleImagesAfterUpdate = async () => {
       <div class="chat-viewer-header">
         <h1 class="text-2xl font-bold">{{ getCurrentChatTitle() }}</h1>
       </div>
-      
+
       <div class="chat-viewer-content">
         <!-- 侧边栏 - 聊天列表 -->
         <div class="chat-sidebar">
@@ -369,7 +367,7 @@ const handleImagesAfterUpdate = async () => {
               v-for="chat in getChatList()"
               :key="chat.chatId"
               class="chat-item"
-              :class="{ 'active': selectedChatId === chat.chatId }"
+              :class="{ active: selectedChatId === chat.chatId }"
               @click="selectChat(chat.chatId)"
             >
               <div class="chat-item-name">{{ chat.name }}</div>
@@ -385,8 +383,8 @@ const handleImagesAfterUpdate = async () => {
           <div class="messages-container">
             <!-- 导出摘要消息 -->
             <div v-if="selectedChatId === 'export_summary'" class="export-summary">
-              <div 
-                v-for="message in getCurrentMessages()" 
+              <div
+                v-for="message in getCurrentMessages()"
                 :key="message.id"
                 class="message message-incoming export-summary-message"
               >
@@ -420,13 +418,12 @@ const handleImagesAfterUpdate = async () => {
 
             <!-- 普通聊天消息 -->
             <div v-else class="messages">
-              <div 
-                v-for="message in getCurrentMessages()" 
+              <div
+                v-for="message in getCurrentMessages()"
                 :key="message.id"
                 class="message message-incoming"
                 v-html="processMessageContent(message)"
-              >
-              </div>
+              ></div>
             </div>
           </div>
         </div>
@@ -637,16 +634,16 @@ const handleImagesAfterUpdate = async () => {
   .chat-viewer-content {
     flex-direction: column;
   }
-  
+
   .chat-sidebar {
     width: 100%;
     height: 200px;
     border-right: none;
     border-bottom: 1px solid #e5e7eb;
   }
-  
+
   .message {
     max-width: 95%;
   }
 }
-</style> 
+</style>

+ 4 - 14
src/views/Login.vue

@@ -3,21 +3,11 @@
     <form @submit.prevent="handleLogin">
       <div class="form-group">
         <label for="username">用户名</label>
-        <input
-          type="text"
-          id="username"
-          v-model="username"
-          required
-        />
+        <input type="text" id="username" v-model="username" required />
       </div>
       <div class="form-group">
         <label for="password">密码</label>
-        <input
-          type="password"
-          id="password"
-          v-model="password"
-          required
-        />
+        <input type="password" id="password" v-model="password" required />
       </div>
       <button type="submit" :disabled="loading">
         {{ loading ? '登录中...' : '登录' }}
@@ -84,7 +74,7 @@ input {
 button {
   width: 100%;
   padding: 10px;
-  background-color: #4CAF50;
+  background-color: #4caf50;
   color: white;
   border: none;
   border-radius: 4px;
@@ -100,4 +90,4 @@ button:disabled {
   color: red;
   margin-top: 10px;
 }
-</style> 
+</style>

+ 2 - 0
src/views/MainView.vue

@@ -215,6 +215,8 @@ const handleResetPassword = async ({ valid, values }) => {
 
 <style lang="less" scoped>
 .main-layout {
+  height: 100vh;
+  overflow: hidden;
 }
 
 .layout-header {

+ 74 - 133
src/views/RecordsView.vue

@@ -1,11 +1,18 @@
 <script setup>
-import { createRecord, deleteRecord, getRecordById, listRecords, updateRecord, uploadFile, downloadFile } from '@/services/api'
+import {
+  createRecord,
+  deleteRecord,
+  getRecordById,
+  listRecords,
+  updateRecord,
+  uploadFile,
+  downloadFile
+} from '@/services/api'
 import { Form } from '@primevue/forms'
 import { zodResolver } from '@primevue/forms/resolvers/zod'
 import { useDateFormat } from '@vueuse/core'
 import Button from 'primevue/button'
 import Column from 'primevue/column'
-import ConfirmDialog from 'primevue/confirmdialog'
 import DataTable from 'primevue/datatable'
 import Dialog from 'primevue/dialog'
 import FloatLabel from 'primevue/floatlabel'
@@ -69,10 +76,10 @@ const displayDescription = computed({
   set(value) {
     // 如果用户输入的是格式化后的文本,尝试转换回JSON格式
     try {
-      const lines = value.split('\n').filter(line => line.trim())
+      const lines = value.split('\n').filter((line) => line.trim())
       const obj = {}
       let isFormatted = true
-      
+
       for (const line of lines) {
         const colonIndex = line.indexOf(':')
         if (colonIndex === -1) {
@@ -83,7 +90,7 @@ const displayDescription = computed({
         const val = line.substring(colonIndex + 1).trim()
         obj[key] = val
       }
-      
+
       if (isFormatted && Object.keys(obj).length > 0) {
         // 如果看起来像是格式化后的JSON,转换为JSON字符串
         recordForm.value.description = JSON.stringify(obj, null, 2)
@@ -133,23 +140,21 @@ const formatDate = (date) => {
 // 格式化描述内容
 function formatDescription(description) {
   if (!description) return ''
-  
+
   try {
     // 尝试解析JSON
     const parsed = JSON.parse(description)
     if (typeof parsed === 'object' && parsed !== null) {
-      // 如果是JSON对象按行展示
+      // 如果是JSON对象,按行展示
       const formatted = Object.entries(parsed)
         .map(([key, value]) => `${key}: ${value}`)
         .join('\n')
-      console.log('JSON格式化结果:', formatted)
       return formatted
     }
   } catch {
-    // 如果不是JSON,返回原文本
-    console.log('非JSON文本:', description)
+    // 如果不是JSON,返回原文本
   }
-  
+
   return description
 }
 
@@ -236,19 +241,32 @@ const saveRecord = async ({ valid, values }) => {
 // 删除记录
 const handleDeleteRecord = (record) => {
   confirm.require({
-    message: `确定要删除记录 "${record.description}" 吗?`,
+    message: `确定要删除记录吗?`,
     header: '确认删除',
     icon: 'pi pi-exclamation-triangle',
+    acceptLabel: '确定',
+    rejectLabel: '取消',
     accept: async () => {
       try {
         await deleteRecord(record.id)
+
         toast.add({
           severity: 'success',
           summary: '成功',
           detail: '记录删除成功',
           life: 3000
         })
-        fetchData() // 刷新列表
+
+        // 检查删除后是否需要调整分页
+        const currentPage = tableData.value.metadata.page
+
+        // 如果当前页只有一条记录且不是第一页,删除后跳转到上一页
+        if (tableData.value.content.length === 1 && currentPage > 0) {
+          tableData.value.metadata.page = currentPage - 1
+        }
+
+        // 刷新数据
+        await fetchData()
       } catch {
         toast.add({
           severity: 'error',
@@ -257,6 +275,9 @@ const handleDeleteRecord = (record) => {
           life: 3000
         })
       }
+    },
+    reject: () => {
+      // 用户取消删除,不需要做任何操作
     }
   })
 }
@@ -264,12 +285,10 @@ const handleDeleteRecord = (record) => {
 // 从URL中提取OSS key
 const extractKeyFromUrl = (url) => {
   try {
-    // 假设URL格式为: https://bucket.oss-region.aliyuncs.com/path/to/file
     const urlObj = new URL(url)
     // 移除开头的斜杠
     return urlObj.pathname.substring(1)
   } catch {
-    console.error('无法解析URL:', url)
     return null
   }
 }
@@ -277,7 +296,7 @@ const extractKeyFromUrl = (url) => {
 // 从描述中提取文件名
 const extractFileNameFromDescription = (description) => {
   if (!description) return 'download'
-  
+
   try {
     // 尝试解析JSON
     const parsed = JSON.parse(description)
@@ -285,7 +304,7 @@ const extractFileNameFromDescription = (description) => {
       // 提取指定字段
       const userName = parsed.userName || ''
       const userId = parsed.userId || ''
-      
+
       // 如果两个字段都有值,使用 userId_@userName 格式
       if (userId && userName) {
         return `${userId}_@${userName}`
@@ -303,7 +322,7 @@ const extractFileNameFromDescription = (description) => {
     // 如果不是JSON,返回原描述(去除特殊字符)
     return description.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50)
   }
-  
+
   return 'download'
 }
 
@@ -314,13 +333,13 @@ const handleFileDownload = async (url, description) => {
     if (!key) {
       throw new Error('无法解析文件路径')
     }
-    
+
     // 从描述中提取文件名
     const fileName = extractFileNameFromDescription(description)
-    
+
     // 通过API服务下载文件
     const blob = await downloadFile(key)
-    
+
     const downloadUrl = window.URL.createObjectURL(blob)
     const a = document.createElement('a')
     a.href = downloadUrl
@@ -329,7 +348,7 @@ const handleFileDownload = async (url, description) => {
     a.click()
     document.body.removeChild(a)
     window.URL.revokeObjectURL(downloadUrl)
-    
+
     toast.add({
       severity: 'success',
       summary: '下载成功',
@@ -356,20 +375,16 @@ const handleFileUpload = async (event) => {
     const response = await uploadFile(file)
     // 根据实际返回的数据结构提取URL
     const newUrl = response.data?.url || ''
-    
+
     // 重新设置整个表单对象来触发验证
     recordForm.value = {
       ...recordForm.value,
       url: newUrl
     }
-    
+
     // 更新formKey来强制表单重新渲染
     formKey.value++
-    
-    // 添加调试信息
-    console.log('文件上传成功,URL已设置:', newUrl)
-    console.log('当前表单值:', recordForm.value)
-    
+
     toast.add({
       severity: 'success',
       summary: '上传成功',
@@ -404,34 +419,21 @@ onMounted(() => {
 
 <template>
   <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
-    <DataTable
-      :value="tableData.content"
-      :paginator="true"
+    <DataTable :value="tableData.content" :paginator="true"
       paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
-      currentPageReportTemplate="{totalRecords} 条记录 "
-      :rows="tableData.metadata.size"
-      :rowsPerPageOptions="[10, 20, 50, 100]"
-      :totalRecords="tableData.metadata.total"
-      @page="handlePageChange"
-      lazy
-      scrollable
-    >
+      currentPageReportTemplate="{totalRecords} 条记录 " :rows="tableData.metadata.size"
+      :rowsPerPageOptions="[10, 20, 50, 100]" :totalRecords="tableData.metadata.total" @page="handlePageChange" lazy
+      scrollable>
       <template #header>
         <div class="flex flex-wrap items-center">
           <Button icon="pi pi-refresh" @click="fetchData" label="刷新" size="small" />
-          <Button
-            icon="pi pi-plus"
-            @click="openNewRecordDialog"
-            label="新增记录"
-            severity="success"
-            size="small"
-            class="ml-2"
-          />
+          <Button icon="pi pi-plus" @click="openNewRecordDialog" label="新增记录" severity="success" size="small"
+            class="ml-2" />
           <div class="flex-1"></div>
         </div>
       </template>
 
-      <Column field="id" header="ID" >
+      <Column field="id" header="ID">
         <template #body="slotProps">
           <span class="font-mono text-sm">{{ slotProps.data.id }}</span>
         </template>
@@ -439,11 +441,8 @@ onMounted(() => {
 
       <Column field="description" header="描述" style="min-width: 300px; max-width: 400px">
         <template #body="slotProps">
-          <div 
-            class="whitespace-pre-line text-sm" 
-            :title="formatDescription(slotProps.data.description)"
-            style="max-height: 100px; overflow-y: auto;"
-          >
+          <div class="whitespace-pre-line text-sm" :title="formatDescription(slotProps.data.description)"
+            style="max-height: 100px; overflow-y: auto">
             {{ formatDescription(slotProps.data.description) }}
           </div>
         </template>
@@ -452,32 +451,12 @@ onMounted(() => {
       <Column header="操作" style="width: 400px">
         <template #body="slotProps">
           <div class="flex gap-1">
-            <Button
-              icon="pi pi-download"
-              size="small"
-              text
-              rounded
-              @click="handleFileDownload(slotProps.data.url, slotProps.data.description)"
-              :title="'下载文件'"
-            />
-            <Button
-              icon="pi pi-pencil"
-              severity="info"
-              size="small"
-              text
-              rounded
-              aria-label="编辑"
-              @click="openEditRecordDialog(slotProps.data)"
-            />
-            <Button
-              icon="pi pi-trash"
-              severity="danger"
-              size="small"
-              text
-              rounded
-              aria-label="删除"
-              @click="handleDeleteRecord(slotProps.data)"
-            />
+            <Button icon="pi pi-download" size="small" text rounded
+              @click="handleFileDownload(slotProps.data.url, slotProps.data.description)" :title="'下载文件'" />
+            <Button icon="pi pi-pencil" severity="info" size="small" text rounded aria-label="编辑"
+              @click="openEditRecordDialog(slotProps.data)" />
+            <Button icon="pi pi-trash" severity="danger" size="small" text rounded aria-label="删除"
+              @click="handleDeleteRecord(slotProps.data)" />
           </div>
         </template>
       </Column>
@@ -490,36 +469,19 @@ onMounted(() => {
     </DataTable>
 
     <!-- 记录表单对话框 -->
-    <Dialog
-      v-model:visible="recordDialog"
-      :modal="true"
-      :header="isEditMode ? '编辑记录' : '创建记录'"
-      :style="{ width: '450px' }"
-      position="center"
-    >
-      <Form ref="formRef" :key="formKey" v-slot="$form" :resolver="recordFormResolver" :initialValues="recordForm" @submit="saveRecord" class="p-fluid">
+    <Dialog v-model:visible="recordDialog" :modal="true" :header="isEditMode ? '编辑记录' : '创建记录'"
+      :style="{ width: '450px' }" position="center">
+      <Form ref="formRef" :key="formKey" v-slot="$form" :resolver="recordFormResolver" :initialValues="recordForm"
+        @submit="saveRecord" class="p-fluid">
         <div class="field mt-4">
           <FloatLabel variant="on">
             <div class="flex gap-2">
               <IconField class="flex-1">
                 <InputIcon class="pi pi-link" />
-                <InputText 
-                  id="url" 
-                  name="url" 
-                  v-model="recordForm.url" 
-                  autocomplete="off" 
-                  fluid 
-                />
+                <InputText id="url" name="url" v-model="recordForm.url" autocomplete="off" fluid />
               </IconField>
-              <Button
-                icon="pi pi-upload"
-                @click="triggerFileSelect"
-                :loading="isUploading"
-                :disabled="isUploading"
-                size="small"
-                severity="secondary"
-                :title="'上传文件'"
-              />
+              <Button icon="pi pi-upload" @click="triggerFileSelect" :loading="isUploading" :disabled="isUploading"
+                size="small" severity="secondary" :title="'上传文件'" />
             </div>
             <label for="url">URL</label>
           </FloatLabel>
@@ -529,25 +491,12 @@ onMounted(() => {
         </div>
 
         <!-- 隐藏的文件输入框 -->
-        <input
-          ref="fileInputRef"
-          type="file"
-          @change="handleFileUpload"
-          style="display: none"
-          accept="*/*"
-        />
+        <input ref="fileInputRef" type="file" @change="handleFileUpload" style="display: none" accept="*/*" />
 
         <div class="field mt-4">
           <FloatLabel variant="on">
-            <Textarea 
-              id="description" 
-              name="description" 
-              v-model="displayDescription" 
-              autocomplete="off" 
-              fluid 
-              rows="4"
-              autoResize
-            />
+            <Textarea id="description" name="description" v-model="displayDescription" autocomplete="off" fluid rows="4"
+              autoResize />
             <label for="description">描述</label>
           </FloatLabel>
           <Message v-if="$form.description?.invalid" severity="error" size="small" variant="simple">
@@ -556,29 +505,21 @@ onMounted(() => {
         </div>
 
         <div class="flex justify-end gap-2 mt-4">
-          <Button
-            label="取消"
-            severity="secondary"
-            type="button"
-            @click="recordDialog = false"
-            :disabled="recordFormLoading"
-          />
+          <Button label="取消" severity="secondary" type="button" @click="recordDialog = false"
+            :disabled="recordFormLoading" />
           <Button label="保存" type="submit" :loading="recordFormLoading" />
         </div>
       </Form>
     </Dialog>
-
-    <!-- 确认对话框 -->
-    <ConfirmDialog />
   </div>
 </template>
 
 <style scoped>
-.p-datatable-sm .p-datatable-tbody > tr > td {
+.p-datatable-sm .p-datatable-tbody>tr>td {
   padding: 0.5rem;
 }
 
-.p-datatable-sm .p-datatable-thead > tr > th {
+.p-datatable-sm .p-datatable-thead>tr>th {
   padding: 0.5rem;
 }
-</style> 
+</style>

+ 28 - 34
yarn.lock

@@ -421,6 +421,13 @@
   dependencies:
     "@primeuix/utils" "^0.5.0"
 
+"@primeuix/styled@^0.6.1":
+  version "0.6.4"
+  resolved "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.6.4.tgz"
+  integrity sha512-7ePLwqazLV0x269YlPMeE4wtQKT0NScY2/gEin0/96krTiGiElmlzKMMbH69bVApm/sfen5DZGuCEEwPiBJJ5g==
+  dependencies:
+    "@primeuix/utils" "^0.5.3"
+
 "@primeuix/styled@^0.6.4":
   version "0.6.4"
   resolved "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.6.4.tgz"
@@ -428,12 +435,12 @@
   dependencies:
     "@primeuix/utils" "^0.5.3"
 
-"@primeuix/styles@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.npmmirror.com/@primeuix/styles/-/styles-1.0.0.tgz"
-  integrity sha512-j/TlbqihLNMP37zFNjxac5dTRaQEf5Ldrv0P7NwKigCCc/+MI5j4MddxDw1LnxkGhWCJ1Gjbt9uwyQteWtSv7A==
+"@primeuix/styles@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/@primeuix/styles/-/styles-1.1.1.tgz"
+  integrity sha512-oguFY2Rs4ZqdcFqEKxBt2d8trTkAjJ5BGTaDV0zbwdqRCRcZp9+di3K3Wv57CW/+tFDA0z1Dg7Dpm7wmkOII9Q==
   dependencies:
-    "@primeuix/styled" "^0.5.0"
+    "@primeuix/styled" "^0.6.1"
 
 "@primeuix/themes@^1.0.0":
   version "1.0.0"
@@ -447,24 +454,11 @@
   resolved "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.4.1.tgz"
   integrity sha512-5+1NLfyna+gLRPeFTo+xlR0tfPVLuVdidbeahAMLkQga5Rw0LxyUBCyD2/Zv2JkV69o2T+hpEDyddl3VdnYoBw==
 
-"@primeuix/utils@^0.5.0", "@primeuix/utils@^0.5.1":
-  version "0.5.2"
-  resolved "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.2.tgz"
-  integrity sha512-fHL0DGnyhL/9toBoV0cO6L+Xg/uaxmOHJW4SrDNMq6GQ7cDXR3Y0vDLvX5j/8kIsaC7LB3329sQLNHgtEETnWw==
-
-"@primeuix/utils@^0.5.3":
+"@primeuix/utils@^0.5.0", "@primeuix/utils@^0.5.3":
   version "0.5.4"
   resolved "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.4.tgz"
   integrity sha512-8LggV3Jz59pymHQD10e/u63z/GemQ22RBeu2Gb1eJgBYVwn1iOb82LR+daeAc/LxrXCC5pHnftnCmnZO6vInLA==
 
-"@primevue/core@4.3.3":
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/@primevue/core/-/core-4.3.3.tgz"
-  integrity sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ==
-  dependencies:
-    "@primeuix/styled" "^0.5.0"
-    "@primeuix/utils" "^0.5.1"
-
 "@primevue/core@4.3.5":
   version "4.3.5"
   resolved "https://registry.npmmirror.com/@primevue/core/-/core-4.3.5.tgz"
@@ -482,13 +476,13 @@
     "@primeuix/utils" "^0.5.3"
     "@primevue/core" "4.3.5"
 
-"@primevue/icons@4.3.3":
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/@primevue/icons/-/icons-4.3.3.tgz"
-  integrity sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w==
+"@primevue/icons@4.3.5":
+  version "4.3.5"
+  resolved "https://registry.npmmirror.com/@primevue/icons/-/icons-4.3.5.tgz"
+  integrity sha512-+V8XG6MEvczw3Ufz7+ABSSCaVdFCYKRHvVDmXpS65AUeQTDEqmJz3xx2UiYYdASA6Gb2yIKdVztTcRjHFtiAnw==
   dependencies:
-    "@primeuix/utils" "^0.5.1"
-    "@primevue/core" "4.3.3"
+    "@primeuix/utils" "^0.5.3"
+    "@primevue/core" "4.3.5"
 
 "@rollup/pluginutils@^5.1.3":
   version "5.1.4"
@@ -1948,16 +1942,16 @@ primeicons@^7.0.0:
   resolved "https://registry.npmmirror.com/primeicons/-/primeicons-7.0.0.tgz"
   integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==
 
-primevue@^4.3.3:
-  version "4.3.3"
-  resolved "https://registry.npmmirror.com/primevue/-/primevue-4.3.3.tgz"
-  integrity sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q==
+primevue@^4.3.5:
+  version "4.3.5"
+  resolved "https://registry.npmmirror.com/primevue/-/primevue-4.3.5.tgz"
+  integrity sha512-KYjLrf7W96qVOFdX2nyap5IrJIEF8qEfLaHpMPw+H3SCd7zV6uiIrOYBNvovk677rhjBGpSjEbxTFY/K+i/DMA==
   dependencies:
-    "@primeuix/styled" "^0.5.0"
-    "@primeuix/styles" "^1.0.0"
-    "@primeuix/utils" "^0.5.1"
-    "@primevue/core" "4.3.3"
-    "@primevue/icons" "4.3.3"
+    "@primeuix/styled" "^0.6.4"
+    "@primeuix/styles" "^1.1.1"
+    "@primeuix/utils" "^0.5.3"
+    "@primevue/core" "4.3.5"
+    "@primevue/icons" "4.3.5"
 
 process-nextick-args@~2.0.0:
   version "2.0.1"