ys пре 2 недеља
родитељ
комит
0be8b09259
36 измењених фајлова са 4216 додато и 3850 уклоњено
  1. 55 0
      yushu-uivue3/src/assets/styles/ant-design-vue.scss
  2. 38 34
      yushu-uivue3/src/layout/components/Sidebar/index.vue
  3. 1 1
      yushu-uivue3/src/views/dashboard/PanelGroup.vue
  4. 1 1
      yushu-uivue3/src/views/message/index.vue
  5. 1 1
      yushu-uivue3/src/views/monitor/errorLog/test.vue
  6. 68 62
      yushu-uivue3/src/views/system/ai/chat/index.vue
  7. 300 313
      yushu-uivue3/src/views/system/ai/service/index.vue
  8. 174 144
      yushu-uivue3/src/views/system/dept/index.vue
  9. 166 175
      yushu-uivue3/src/views/system/dict/data.vue
  10. 162 169
      yushu-uivue3/src/views/system/dict/index.vue
  11. 4 4
      yushu-uivue3/src/views/system/file/index.vue
  12. 87 113
      yushu-uivue3/src/views/system/file/share.vue
  13. 270 249
      yushu-uivue3/src/views/system/mail/config/index.vue
  14. 138 151
      yushu-uivue3/src/views/system/mail/inbox/index.vue
  15. 121 88
      yushu-uivue3/src/views/system/mail/log/index.vue
  16. 82 81
      yushu-uivue3/src/views/system/mail/send/index.vue
  17. 167 137
      yushu-uivue3/src/views/system/mail/template/index.vue
  18. 270 244
      yushu-uivue3/src/views/system/menu/index.vue
  19. 45 49
      yushu-uivue3/src/views/system/message/components/SelectUserDialog.vue
  20. 65 68
      yushu-uivue3/src/views/system/message/components/SendMessageDialog.vue
  21. 158 148
      yushu-uivue3/src/views/system/message/index.vue
  22. 143 154
      yushu-uivue3/src/views/system/notice/index.vue
  23. 224 202
      yushu-uivue3/src/views/system/notification/index.vue
  24. 181 159
      yushu-uivue3/src/views/system/param/index.vue
  25. 134 144
      yushu-uivue3/src/views/system/post/index.vue
  26. 91 89
      yushu-uivue3/src/views/system/role/authUser.vue
  27. 299 306
      yushu-uivue3/src/views/system/role/index.vue
  28. 79 59
      yushu-uivue3/src/views/system/role/selectUser.vue
  29. 73 52
      yushu-uivue3/src/views/system/user/authRole.vue
  30. 478 288
      yushu-uivue3/src/views/system/user/index.vue
  31. 30 24
      yushu-uivue3/src/views/system/user/profile/index.vue
  32. 22 23
      yushu-uivue3/src/views/system/user/profile/resetPwd.vue
  33. 44 46
      yushu-uivue3/src/views/system/user/profile/userAvatar.vue
  34. 29 43
      yushu-uivue3/src/views/system/user/profile/userInfo.vue
  35. 14 27
      yushu-uivue3/src/views/tool/build/IconsDialog.vue
  36. 2 2
      yushu-uivue3/src/views/tool/build/index.vue

+ 55 - 0
yushu-uivue3/src/assets/styles/ant-design-vue.scss

@@ -62,3 +62,58 @@
     display: block;
   }
 }
+
+// 修复弹窗层级问题 - 确保所有弹窗在菜单上方
+// 侧边栏 z-index 是 1001,所以弹窗需要更高的 z-index
+.ant-modal,
+.ant-modal-mask {
+  z-index: 1050 !important;
+}
+
+.ant-modal-wrap {
+  z-index: 1050 !important;
+}
+
+.ant-drawer,
+.ant-drawer-mask {
+  z-index: 1050 !important;
+}
+
+.ant-drawer-wrap {
+  z-index: 1050 !important;
+}
+
+// Dropdown 下拉菜单也需要在侧边栏上方
+.ant-dropdown {
+  z-index: 1051 !important;
+}
+
+// Popover 气泡卡片
+.ant-popover {
+  z-index: 1051 !important;
+}
+
+// Tooltip 提示
+.ant-tooltip {
+  z-index: 1051 !important;
+}
+
+// Select 下拉选择器
+.ant-select-dropdown {
+  z-index: 1051 !important;
+}
+
+// DatePicker 日期选择器
+.ant-picker-dropdown {
+  z-index: 1051 !important;
+}
+
+// TreeSelect 树形选择器
+.ant-tree-select-dropdown {
+  z-index: 1051 !important;
+}
+
+// AutoComplete 自动完成
+.ant-select-dropdown {
+  z-index: 1051 !important;
+}

+ 38 - 34
yushu-uivue3/src/layout/components/Sidebar/index.vue

@@ -17,7 +17,7 @@
 </template>
 
 <script setup>
-import { h, onMounted } from 'vue'
+import { h, onMounted, computed, ref, watch } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import Logo from './Logo'
 import SvgIcon from '@/components/SvgIcon'
@@ -140,12 +140,14 @@ function convertRoutesToItems(routes, basePath = '') {
       // 有子菜单
       const path = resolvePath(routeItem.path, routeItem.query, basePath)
       const key = path
-      const children = convertRoutesToItems(routeItem.children, path)
+      // 对于有子菜单的情况,basePath 应该是当前路径,用于子路由的拼接
+      // 即使 redirect 是 noRedirect,也应该使用当前路径作为子路由的 basePath
+      const childrenBasePath = path || basePath
+      const children = convertRoutesToItems(routeItem.children, childrenBasePath)
       
       if (children.length > 0) {
-        // 查找第一个可跳转的子路由(叶子节点)
-        const firstChildRoute = findFirstRoute(children)
-        console.log(`Parent menu ${routeItem.meta?.title} (${path}) first child route:`, firstChildRoute)
+        // 查找第一个可跳转的子路由(叶子节点,且路径不等于父级路径)
+        const firstChildRoute = findFirstRoute(children, path)
         
         items.push({
           key,
@@ -159,7 +161,11 @@ function convertRoutesToItems(routes, basePath = '') {
         })
       }
     } else if (routeItem.meta) {
-      // 普通菜单项
+      // 普通菜单项(叶子节点)
+      // 确保 routeItem.path 存在,如果不存在则跳过
+      if (!routeItem.path || routeItem.path === '') {
+        return
+      }
       const path = resolvePath(routeItem.path, routeItem.query, basePath)
       const key = path
       
@@ -180,21 +186,19 @@ function convertRoutesToItems(routes, basePath = '') {
   return items
 }
 
-// 查找第一个可跳转的路由(只查找叶子节点,即没有子菜单的项)
-function findFirstRoute(items) {
+// 查找第一个可跳转的路由(只查找叶子节点,即没有子菜单的项,并且路径不等于父级路径
+function findFirstRoute(items, parentPath) {
   for (const item of items) {
-    // 如果是叶子节点(没有子菜单)且有路由,直接返回
-    if (!item.children && item._route) {
-      console.log('Found first route (leaf node):', item._route, 'for item:', item.label)
+    // 如果是叶子节点(没有子菜单)且有路由,并且路径不等于父级路径,直接返回
+    if (!item.children && item._route && item._route.path !== parentPath) {
       return item._route
     }
     // 如果有子菜单,递归查找第一个叶子节点
     if (item.children && item.children.length > 0) {
-      const found = findFirstRoute(item.children)
+      const found = findFirstRoute(item.children, parentPath)
       if (found) return found
     }
   }
-  console.log('No first route found in items:', items.map(i => ({ label: i.label, hasChildren: !!i.children, hasRoute: !!i._route })))
   return null
 }
 
@@ -226,7 +230,9 @@ function getOnlyChild(children = [], parent) {
   }
   
   if (showingChildren.length === 0) {
-    return { ...parent, path: '', noShowingChildren: true }
+    // 不要覆盖 path,保留 parent 的原始 path
+    // 这样 resolvePath 可以正确拼接路径
+    return { ...parent, noShowingChildren: true }
   }
   
   return null
@@ -237,23 +243,33 @@ function getOnlyOneChild(children = [], parent) {
 }
 
 function resolvePath(routePath, routeQuery, basePath) {
+  // 处理外部链接
   if (isExternal(routePath)) {
     return routePath
   }
   if (isExternal(basePath)) {
     return basePath
   }
-  if (!routePath) {
+  
+  // 如果 routePath 为空、null 或 undefined,返回 basePath
+  if (!routePath || routePath === '' || routePath === undefined || routePath === null) {
     return basePath || '/'
   }
+  
   // 如果 routePath 已经是绝对路径(以 / 开头),直接返回
-  if (routePath.startsWith('/')) {
+  if (typeof routePath === 'string' && routePath.startsWith('/')) {
     return getNormalPath(routePath)
   }
+  
   // 否则拼接 basePath
   if (basePath) {
-    return getNormalPath(basePath + '/' + routePath)
+    // 确保 basePath 不以 / 结尾,routePath 不以 / 开头
+    const normalizedBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath
+    const normalizedRoutePath = (typeof routePath === 'string' && routePath.startsWith('/')) ? routePath.slice(1) : String(routePath)
+    const fullPath = getNormalPath(`${normalizedBasePath}/${normalizedRoutePath}`)
+    return fullPath
   }
+  
   return getNormalPath('/' + routePath)
 }
 
@@ -262,30 +278,24 @@ const menuItems = computed(() => {
 })
 
 function handleMenuClick(info) {
-  const { key, keyPath } = info
-  console.log('Menu clicked:', { key, keyPath })
+  const { key } = info
   
   const findRoute = (items, targetKey) => {
     for (const item of items) {
       if (item.key === targetKey) {
-        console.log('Found menu item:', item)
         // 如果是有子菜单的项
         if (item.children && item.children.length > 0) {
           // 如果有 _route(第一个子路由),则跳转
           if (item._route) {
-            console.log('Found route for parent menu:', item._route)
             return item._route
           }
           // 如果没有 _route(没有可跳转的子路由),则不跳转,只展开/收起
-          console.log('Has children, but no route to navigate')
           return null
         }
         // 没有子菜单,直接返回路由
         if (item._route) {
-          console.log('Found route:', item._route)
           return item._route
         }
-        console.log('No route found for item')
       }
       if (item.children) {
         const found = findRoute(item.children, targetKey)
@@ -296,13 +306,11 @@ function handleMenuClick(info) {
   }
   
   const routeInfo = findRoute(menuItems.value, key)
-  console.log('Route info:', routeInfo)
   
   if (routeInfo) {
     if (isExternal(routeInfo.path)) {
       window.open(routeInfo.path, '_blank')
     } else {
-      console.log('Navigating to:', routeInfo.path, routeInfo.query)
       router.push({
         path: routeInfo.path,
         query: routeInfo.query
@@ -327,17 +335,13 @@ function handleMenuClick(info) {
           // 如果找不到,使用当前 key(可能是父级菜单跳转到子路由的情况)
           selectedKeys.value = [key]
         }
-        console.log('Navigation successful, selected key:', selectedKeys.value[0])
-      }).catch((err) => {
-        // 路由跳转失败,不做处理
-        console.error('Navigation failed:', err)
+      }).catch(() => {
+        // 路由跳转失败,静默处理
       })
     }
-  } else {
-    console.log('No route info found, might be parent menu with noRedirect')
-    // 如果没有找到路由信息,可能是点击了父级菜单(redirect 是 noRedirect),只更新 openKeys
-    // openKeys 会由 Ant Design Vue 自动管理
   }
+  // 如果没有找到路由信息,可能是点击了父级菜单(redirect 是 noRedirect),只展开/收起
+  // openKeys 会由 Ant Design Vue 自动管理
 }
 </script>
 

+ 1 - 1
yushu-uivue3/src/views/dashboard/PanelGroup.vue

@@ -48,7 +48,7 @@
 </template>
 
 <script setup>
-import { User, ChatDotRound, Wallet, ShoppingCart } from '@element-plus/icons-vue'
+import { UserOutlined, MessageOutlined, WalletOutlined, ShoppingCartOutlined } from '@ant-design/icons-vue'
 import CountTo from '@/components/CountTo'
 
 const emit = defineEmits(['handleSetLineChartData'])

+ 1 - 1
yushu-uivue3/src/views/message/index.vue

@@ -255,7 +255,7 @@
 </template>
 
 <script setup name="MessageCenter">
-import { ChatDotRound, ChatLineRound, Promotion, Bell, Delete } from '@element-plus/icons-vue'
+import { MessageOutlined, MessageOutlined as ChatLineRound, GiftOutlined, BellOutlined, DeleteOutlined } from '@ant-design/icons-vue'
 import { getConversationList, getConversationMessages, sendMessage as sendMessageAPI, setConversationRemark, createPrivateConversation, markConversationAsRead as markReadAPI, deleteConversation } from '@/api/system/message'
 import { listUser } from '@/api/system/user'
 import { getAllNotifications, markNotificationAsRead } from '@/api/system/notification'

+ 1 - 1
yushu-uivue3/src/views/monitor/errorLog/test.vue

@@ -85,7 +85,7 @@
 </template>
 
 <script setup name="ErrorTest">
-import { View } from '@element-plus/icons-vue'
+import { EyeOutlined } from '@ant-design/icons-vue'
 import request from '@/utils/request'
 
 const { proxy } = getCurrentInstance()

+ 68 - 62
yushu-uivue3/src/views/system/ai/chat/index.vue

@@ -3,9 +3,10 @@
     <!-- 侧边栏:会话列表 -->
     <div class="chat-sidebar">
       <div class="sidebar-header">
-        <el-button type="primary" icon="Plus" @click="handleNewChat" class="new-chat-btn">
+        <a-button type="primary" @click="handleNewChat" class="new-chat-btn">
+          <template #icon><PlusOutlined /></template>
           新对话
-        </el-button>
+        </a-button>
       </div>
 
       <div class="conversation-list">
@@ -21,12 +22,13 @@
             <span class="model-tag">{{ conv.serviceName || conv.modelName }}</span>
             <span class="time">{{ parseTime(conv.lastMessageTime, '{m}-{d} {h}:{i}') }}</span>
           </div>
-          <el-button
+          <a-button
             class="delete-btn"
-            link
-            icon="Delete"
+            type="link"
             @click.stop="handleDeleteConversation(conv.conversationId)"
-          />
+          >
+            <template #icon><DeleteOutlined /></template>
+          </a-button>
         </div>
       </div>
     </div>
@@ -36,40 +38,42 @@
       <!-- 顶部工具栏 -->
       <div class="chat-header">
         <div class="header-left">
-          <el-select 
-            v-model="selectedServiceId" 
+          <a-select 
+            v-model:value="selectedServiceId" 
             placeholder="请选择AI服务" 
             class="model-selector"
             @change="handleServiceChange"
-            filterable
+            :show-search="true"
+            :filter-option="false"
           >
-            <el-option-group
+            <a-select-opt-group
               v-for="group in groupedServices"
               :key="group.label"
               :label="group.label"
             >
-              <el-option
+              <a-select-option
                 v-for="service in group.options"
                 :key="service.serviceId"
-                :label="service.serviceName"
                 :value="service.serviceId"
               >
-                <span>{{ service.serviceName }}</span>
-              </el-option>
-            </el-option-group>
-          </el-select>
+                {{ service.serviceName }}
+              </a-select-option>
+            </a-select-opt-group>
+          </a-select>
         </div>
         <div class="header-right">
-          <el-tooltip content="导出对话" placement="bottom">
-            <el-button link icon="Download" @click="handleExportChat" />
-          </el-tooltip>
+          <a-tooltip title="导出对话" placement="bottom">
+            <a-button type="link" @click="handleExportChat">
+              <template #icon><DownloadOutlined /></template>
+            </a-button>
+          </a-tooltip>
         </div>
       </div>
 
       <!-- 消息列表 -->
       <div class="messages-container" ref="messagesContainerRef" @click="handleMessageAreaClick">
         <div v-if="messages.length === 0" class="empty-state">
-          <el-icon :size="64"><ChatDotRound /></el-icon>
+          <MessageOutlined :style="{ fontSize: '64px' }" />
           <p>开始新的对话</p>
         </div>
 
@@ -80,8 +84,8 @@
           :class="msg.role"
         >
           <div class="avatar">
-            <el-icon v-if="msg.role === 'user'"><UserFilled /></el-icon>
-            <el-icon v-else><ChatSquare /></el-icon>
+            <UserOutlined v-if="msg.role === 'user'" />
+            <MessageOutlined v-else />
           </div>
           
           <div class="message-wrapper">
@@ -89,9 +93,9 @@
               <!-- 思考内容(可折叠) -->
               <div v-if="msg.reasoning" class="reasoning-block">
                 <div class="reasoning-header" @click="msg.showReasoning = !msg.showReasoning">
-                  <el-icon><Reading /></el-icon>
+                  <ReadOutlined />
                   <span>思考过程</span>
-                  <el-icon class="arrow" :class="{ expanded: msg.showReasoning }"><ArrowDown /></el-icon>
+                  <DownOutlined class="arrow" :class="{ expanded: msg.showReasoning }" />
                 </div>
                 <div v-show="msg.showReasoning" class="reasoning-content" v-html="formatMessage(msg.reasoning)"></div>
               </div>
@@ -110,7 +114,7 @@
               <!-- 用户上传的文件 -->
               <div v-if="msg.files && msg.files.length > 0" class="message-files">
                 <div v-for="(fileName, fileIdx) in msg.files" :key="fileIdx" class="file-item">
-                  <el-icon><Document /></el-icon>
+                  <FileOutlined />
                   <span>{{ fileName }}</span>
                 </div>
               </div>
@@ -132,15 +136,15 @@
                 <span class="message-time">{{ parseTime(msg.createTime, '{h}:{i}:{s}') }}</span>
               </div>
               <div class="footer-right">
-                <el-button 
+                <a-button 
                   v-if="msg.role === 'assistant'" 
-                  type="primary" 
-                  link
+                  type="link"
                   class="action-btn"
                   @click="copyMessage(index)"
                 >
-                  <el-icon><DocumentCopy /></el-icon> 复制
-                </el-button>
+                  <template #icon><CopyOutlined /></template>
+                  复制
+                </a-button>
               </div>
             </div>
           </div>
@@ -152,7 +156,7 @@
         <!-- 功能选项栏 -->
         <div class="chat-options-bar">
           <!-- 深度思考 -->
-          <el-tooltip content="深度思考 (R1)" placement="top" :show-after="500">
+          <a-tooltip title="深度思考 (R1)" placement="top" :mouseEnterDelay="0.5">
             <div 
               class="option-item" 
               :class="{ 
@@ -161,13 +165,13 @@
               }"
               @click="toggleOption('thinking')"
             >
-              <el-icon><Cpu /></el-icon>
+              <ThunderboltOutlined />
               <span>深度思考</span>
             </div>
-          </el-tooltip>
+          </a-tooltip>
 
           <!-- 联网搜索 -->
-          <el-tooltip content="联网搜索" placement="top" :show-after="500">
+          <a-tooltip title="联网搜索" placement="top" :mouseEnterDelay="0.5">
             <div 
               class="option-item" 
               :class="{ 
@@ -176,13 +180,13 @@
               }"
               @click="toggleOption('search')"
             >
-              <el-icon><Search /></el-icon>
+              <SearchOutlined />
               <span>联网搜索</span>
             </div>
-          </el-tooltip>
+          </a-tooltip>
 
           <!-- 上传文件 -->
-          <el-tooltip content="上传文件" placement="top" :show-after="500">
+          <a-tooltip title="上传文件" placement="top" :mouseEnterDelay="0.5">
             <div 
               class="option-item" 
               :class="{ 
@@ -191,13 +195,13 @@
               }"
               @click="triggerUpload('file')"
             >
-              <el-icon><Paperclip /></el-icon>
+              <PaperClipOutlined />
               <span>上传文件</span>
             </div>
-          </el-tooltip>
+          </a-tooltip>
 
           <!-- 上传图片 -->
-          <el-tooltip content="上传图片" placement="top" :show-after="500">
+          <a-tooltip title="上传图片" placement="top" :mouseEnterDelay="0.5">
             <div 
               class="option-item" 
               :class="{ 
@@ -206,33 +210,35 @@
               }"
               @click="triggerUpload('image')"
             >
-              <el-icon><Picture /></el-icon>
+              <PictureOutlined />
               <span>上传图片</span>
             </div>
-          </el-tooltip>
+          </a-tooltip>
         </div>
 
         <!-- 文件预览区域 -->
         <div v-if="fileList.length > 0 || imageList.length > 0" class="file-preview-area">
-          <el-tag
+          <a-tag
             v-for="(file, index) in fileList"
             :key="'file-'+index"
             closable
             @close="removeFile('file', index)"
             class="preview-tag"
           >
-            <el-icon><Document /></el-icon> {{ file.name }}
-          </el-tag>
-          <el-tag
+            <template #icon><FileOutlined /></template>
+            {{ file.name }}
+          </a-tag>
+          <a-tag
             v-for="(img, index) in imageList"
             :key="'img-'+index"
             closable
             @close="removeFile('image', index)"
             class="preview-tag"
-            type="success"
+            color="green"
           >
-            <el-icon><Picture /></el-icon> {{ img.name }}
-          </el-tag>
+            <template #icon><PictureOutlined /></template>
+            {{ img.name }}
+          </a-tag>
         </div>
 
         <!-- 隐藏的上传输入框 -->
@@ -251,36 +257,36 @@
         />
 
         <div class="input-wrapper">
-          <el-input
-            v-model="inputMessage"
-            type="textarea"
+          <a-textarea
+            v-model:value="inputMessage"
             :rows="3"
             placeholder="有什么我能帮您的吗?(Ctrl + Enter 发送)"
             @keydown="handleKeyDown"
-            resize="none"
+            :auto-size="{ minRows: 3, maxRows: 6 }"
           />
-          <el-button
+          <a-button
             type="primary"
-            icon="Top"
             :loading="loading"
             :disabled="!inputMessage.trim() || !selectedServiceId"
             @click="handleSendMessage"
-            circle
+            shape="circle"
             class="send-btn"
-          />
+          >
+            <template #icon><UpOutlined /></template>
+          </a-button>
         </div>
       </div>
     </div>
     
     <!-- 图片预览对话框 -->
-    <el-dialog v-model="showImagePreview" title="图片预览" width="800px" append-to-body>
+    <a-modal v-model:open="showImagePreview" title="图片预览" width="800px" :footer="null">
       <img :src="previewImageUrl" style="width: 100%; max-height: 70vh; object-fit: contain;" />
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="AiChat">
-import { ChatDotRound, UserFilled, ChatSquare, DocumentCopy, Search, Cpu, Paperclip, Picture, Document, Delete, Plus, Top, Download, Reading, ArrowDown } from '@element-plus/icons-vue'
+import { MessageOutlined, UserOutlined, CopyOutlined, SearchOutlined, ThunderboltOutlined, PaperClipOutlined, PictureOutlined, FileOutlined, DeleteOutlined, PlusOutlined, UpOutlined, DownloadOutlined, ReadOutlined, DownOutlined } from '@ant-design/icons-vue'
 import { sendMessage, getHistory, createConversation, getConversations, deleteConversation } from '@/api/system/ai/chat'
 import { listEnabledService } from '@/api/system/ai/service'
 import { getToken } from '@/utils/auth'
@@ -1018,9 +1024,9 @@ loadConversations()
         justify-content: center;
         flex-shrink: 0;
         
-        .el-icon {
+        .anticon {
           font-size: 20px;
-          color: #909399;
+          color: #999;
         }
       }
 

+ 300 - 313
yushu-uivue3/src/views/system/ai/service/index.vue

@@ -1,145 +1,162 @@
 <template>
   <div class="app-container">
     <!-- 搜索区域 -->
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" class="search-form">
-      <el-form-item label="服务名称" prop="serviceName">
-        <el-input v-model="queryParams.serviceName" placeholder="请输入服务名称" clearable @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="厂商" prop="provider">
-        <el-select v-model="queryParams.provider" placeholder="请选择厂商" clearable>
-          <el-option v-for="item in providerOptions" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="状态" prop="enabled">
-        <el-select v-model="queryParams.enabled" placeholder="请选择状态" clearable>
-          <el-option label="启用" value="1" />
-          <el-option label="停用" value="0" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch" class="search-form">
+      <a-form-item label="服务名称" name="serviceName">
+        <a-input v-model:value="queryParams.serviceName" placeholder="请输入服务名称" allowClear @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="厂商" name="provider">
+        <a-select v-model:value="queryParams.provider" placeholder="请选择厂商" allowClear style="width: 200px">
+          <a-select-option v-for="item in providerOptions" :key="item.value" :value="item.value">{{ item.label }}</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="状态" name="enabled">
+        <a-select v-model:value="queryParams.enabled" placeholder="请选择状态" allowClear style="width: 200px">
+          <a-select-option value="1">启用</a-select-option>
+          <a-select-option value="0">停用</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
 
     <!-- 操作按钮 -->
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ai:service:add']">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['ai:service:edit']">修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['ai:service:remove']">删除</el-button>
-      </el-col>
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button type="primary" @click="handleAdd" v-hasPermi="['ai:service:add']">
+          <template #icon><PlusOutlined /></template>
+          新增
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['ai:service:edit']">
+          <template #icon><EditOutlined /></template>
+          修改
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['ai:service:remove']">
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
-    </el-row>
+    </a-row>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="serviceList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="服务名称" prop="serviceName" min-width="140">
-        <template #default="scope">
+    <a-table 
+      :loading="loading" 
+      :dataSource="serviceList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="serviceId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'serviceName'">
           <div class="service-name">
-            <el-tag v-if="scope.row.isDefault === '1'" type="success" size="small" class="default-tag">默认</el-tag>
-            <span>{{ scope.row.serviceName }}</span>
+            <a-tag v-if="record.isDefault === '1'" color="green" class="default-tag">默认</a-tag>
+            <span>{{ record.serviceName }}</span>
           </div>
         </template>
-      </el-table-column>
-      <el-table-column label="服务代码" prop="serviceCode" width="150" />
-      <el-table-column label="厂商" prop="providerName" width="100">
-        <template #default="scope">
-          <el-tag :type="getProviderTagType(scope.row.provider)">{{ scope.row.providerName || scope.row.provider }}</el-tag>
+        <template v-else-if="column.key === 'providerName'">
+          <a-tag :color="getProviderTagColor(record.provider)">{{ record.providerName || record.provider }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="模型" prop="modelCode" width="150" />
-      <el-table-column label="支持能力" width="180" align="center">
-        <template #default="scope">
+        <template v-else-if="column.key === 'capabilities'">
           <div class="capability-icons">
-            <el-tooltip content="流式输出" placement="top">
-              <el-icon v-if="scope.row.supportsStream === '1'" color="#67c23a"><VideoCameraFilled /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="视觉理解" placement="top">
-              <el-icon v-if="scope.row.supportsVision === '1'" color="#409eff"><PictureFilled /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="工具调用" placement="top">
-              <el-icon v-if="scope.row.supportsTools === '1'" color="#e6a23c"><SetUp /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="深度思考" placement="top">
-              <el-icon v-if="scope.row.supportsThinking === '1'" color="#9b59b6"><Cpu /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="联网搜索" placement="top">
-              <el-icon v-if="scope.row.supportsSearch === '1'" color="#3498db"><Search /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="文件解析" placement="top">
-              <el-icon v-if="scope.row.supportsFiles === '1'" color="#1abc9c"><Document /></el-icon>
-            </el-tooltip>
+            <a-tooltip title="流式输出">
+              <VideoCameraOutlined v-if="record.supportsStream === '1'" style="color: #52c41a; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
+            <a-tooltip title="视觉理解">
+              <PictureOutlined v-if="record.supportsVision === '1'" style="color: #1890ff; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
+            <a-tooltip title="工具调用">
+              <SettingOutlined v-if="record.supportsTools === '1'" style="color: #faad14; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
+            <a-tooltip title="深度思考">
+              <ThunderboltOutlined v-if="record.supportsThinking === '1'" style="color: #722ed1; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
+            <a-tooltip title="联网搜索">
+              <SearchOutlined v-if="record.supportsSearch === '1'" style="color: #1890ff; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
+            <a-tooltip title="文件解析">
+              <FileOutlined v-if="record.supportsFiles === '1'" style="color: #13c2c2; font-size: 18px; margin: 0 4px;" />
+            </a-tooltip>
           </div>
         </template>
-      </el-table-column>
-      <el-table-column label="状态" width="80" align="center">
-        <template #default="scope">
-          <el-switch v-model="scope.row.enabled" active-value="1" inactive-value="0" @change="handleStatusChange(scope.row)" />
+        <template v-else-if="column.key === 'enabled'">
+          <a-switch v-model:checked="record.enabled" :checkedValue="'1'" :unCheckedValue="'0'" @change="handleStatusChange(record)" />
         </template>
-      </el-table-column>
-      <el-table-column label="操作" width="150" align="center" fixed="right">
-        <template #default="scope">
-          <el-tooltip content="修改" placement="top">
-            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ai:service:edit']" />
-          </el-tooltip>
-          <el-tooltip content="设为默认" placement="top" v-if="scope.row.isDefault !== '1'">
-            <el-button link type="warning" icon="Star" @click="handleSetDefault(scope.row)" v-hasPermi="['ai:service:edit']" />
-          </el-tooltip>
-          <el-tooltip content="删除" placement="top">
-            <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ai:service:remove']" />
-          </el-tooltip>
+        <template v-else-if="column.key === 'action'">
+          <a-tooltip title="修改">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['ai:service:edit']">
+              <template #icon><EditOutlined /></template>
+            </a-button>
+          </a-tooltip>
+          <a-tooltip title="设为默认" v-if="record.isDefault !== '1'">
+            <a-button type="link" size="small" @click="handleSetDefault(record)" v-hasPermi="['ai:service:edit']">
+              <template #icon><StarOutlined /></template>
+            </a-button>
+          </a-tooltip>
+          <a-tooltip title="删除">
+            <a-button type="link" danger size="small" @click="handleDelete(record)" v-hasPermi="['ai:service:remove']">
+              <template #icon><DeleteOutlined /></template>
+            </a-button>
+          </a-tooltip>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
 
     <!-- 新增/修改对话框 -->
-    <el-dialog :title="title" v-model="open" width="750px" append-to-body destroy-on-close class="service-dialog">
-      <el-form ref="serviceRef" :model="form" :rules="rules" label-width="110px">
-        <el-divider content-position="left">基本信息</el-divider>
+    <a-modal :title="title" v-model:open="open" width="750px" :maskClosable="false" :destroyOnClose="true">
+      <a-form ref="serviceRef" :model="form" :rules="rules" :label-col="{ span: 6 }">
+        <a-divider orientation="left">基本信息</a-divider>
         
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="服务名称" prop="serviceName">
-              <el-input v-model="form.serviceName" placeholder="显示名称,如:GPT-4o" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="服务代码" prop="serviceCode">
-              <el-input v-model="form.serviceCode" placeholder="唯一标识,如:openai-gpt4o" :disabled="form.serviceId != null" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="厂商" prop="provider">
-              <el-select v-model="form.provider" placeholder="请选择厂商" @change="handleProviderChange" style="width: 100%">
-                <el-option v-for="item in providerOptions" :key="item.value" :label="item.label" :value="item.value">
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="服务名称" name="serviceName">
+              <a-input v-model:value="form.serviceName" placeholder="显示名称,如:GPT-4o" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="服务代码" name="serviceCode">
+              <a-input v-model:value="form.serviceCode" placeholder="唯一标识,如:openai-gpt4o" :disabled="form.serviceId != null" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="厂商" name="provider">
+              <a-select v-model:value="form.provider" placeholder="请选择厂商" @change="handleProviderChange" style="width: 100%">
+                <a-select-option v-for="item in providerOptions" :key="item.value" :value="item.value">
                   <span>{{ item.label }}</span>
                   <span class="provider-url">{{ item.url }}</span>
-                </el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="模型代码" prop="modelCode">
-              <el-input v-model="form.modelCode" placeholder="厂商模型标识,如:gpt-4o、glm-4" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-divider content-position="left">
+                </a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="模型代码" name="modelCode">
+              <a-input v-model:value="form.modelCode" placeholder="厂商模型标识,如:gpt-4o、glm-4" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-divider orientation="left">
           <span>API配置</span>
-          <el-tooltip placement="top">
-            <template #content>
+          <a-tooltip placement="top">
+            <template #title>
               <div style="max-width: 280px;">
                 <p style="margin-bottom: 8px;"><b>如何获取 API Key?</b></p>
                 <p>• OpenAI: platform.openai.com</p>
@@ -149,79 +166,79 @@
                 <p>• DeepSeek: platform.deepseek.com</p>
               </div>
             </template>
-            <el-icon class="help-icon"><QuestionFilled /></el-icon>
-          </el-tooltip>
-        </el-divider>
+            <QuestionCircleOutlined class="help-icon" />
+          </a-tooltip>
+        </a-divider>
 
-        <el-form-item label="API地址" prop="apiUrl">
-          <el-input v-model="form.apiUrl" placeholder="选择厂商后自动填充,也可手动修改" />
-        </el-form-item>
+        <a-form-item label="API地址" name="apiUrl">
+          <a-input v-model:value="form.apiUrl" placeholder="选择厂商后自动填充,也可手动修改" />
+        </a-form-item>
 
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="API密钥" prop="apiKey">
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="API密钥" name="apiKey">
               <div class="key-input-wrapper">
-                <el-input v-model="form.apiKey" placeholder="从厂商平台获取的 API Key" />
-                <el-button 
+                <a-input v-model:value="form.apiKey" placeholder="从厂商平台获取的 API Key" />
+                <a-button 
                   v-if="form.serviceId && isKeyMasked" 
                   class="view-key-btn"
-                  link 
+                  type="link" 
                   @click="handleViewKey"
                   v-hasPermi="['ai:service:viewkey']"
                 >
-                  <el-icon><View /></el-icon>
-                  <span>查看原文</span>
-                </el-button>
+                  <template #icon><EyeOutlined /></template>
+                  查看原文
+                </a-button>
               </div>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="API Secret" prop="apiSecret">
-              <el-input v-model="form.apiSecret" placeholder="部分厂商需要(如百度)" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-divider content-position="left">
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="API Secret" name="apiSecret">
+              <a-input v-model:value="form.apiSecret" placeholder="部分厂商需要(如百度)" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-divider orientation="left">
           <span>模型参数</span>
-          <el-tooltip placement="top">
-            <template #content>
+          <a-tooltip placement="top">
+            <template #title>
               <div style="max-width: 280px;">
                 <p><b>温度</b>: 控制随机性,0=确定,0.7=推荐,1.5+=创意</p>
                 <p style="margin-top: 6px;"><b>Top P</b>: 核采样,0.95推荐</p>
                 <p style="margin-top: 6px;"><b>最大Token</b>: 回复长度上限,1Token≈0.75中文字</p>
               </div>
             </template>
-            <el-icon class="help-icon"><QuestionFilled /></el-icon>
-          </el-tooltip>
-        </el-divider>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="温度" prop="temperature">
-              <el-input-number v-model="form.temperature" :min="0" :max="2" :step="0.1" :precision="2" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="Top P" prop="topP">
-              <el-input-number v-model="form.topP" :min="0" :max="1" :step="0.05" :precision="2" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="最大Token" prop="maxTokens">
-              <el-input-number v-model="form.maxTokens" :min="1" :max="128000" :step="1024" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-form-item label="系统提示词" prop="systemPrompt">
-          <el-input v-model="form.systemPrompt" type="textarea" :rows="3" placeholder="设定AI角色,如:你是一个专业、友好的AI助手" />
-        </el-form-item>
-
-        <el-divider content-position="left">
+            <QuestionCircleOutlined class="help-icon" />
+          </a-tooltip>
+        </a-divider>
+
+        <a-row :gutter="16">
+          <a-col :span="8">
+            <a-form-item label="温度" name="temperature">
+              <a-input-number v-model:value="form.temperature" :min="0" :max="2" :step="0.1" :precision="2" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="Top P" name="topP">
+              <a-input-number v-model:value="form.topP" :min="0" :max="1" :step="0.05" :precision="2" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="最大Token" name="maxTokens">
+              <a-input-number v-model:value="form.maxTokens" :min="1" :max="128000" :step="1024" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-form-item label="系统提示词" name="systemPrompt">
+          <a-textarea v-model:value="form.systemPrompt" :rows="3" placeholder="设定AI角色,如:你是一个专业、友好的AI助手" />
+        </a-form-item>
+
+        <a-divider orientation="left">
           <span>支持的能力</span>
-          <el-tooltip placement="top">
-            <template #content>
+          <a-tooltip placement="top">
+            <template #title>
               <div style="max-width: 320px;">
                 <p style="margin-bottom: 8px;">勾选此服务支持的能力,对话时会显示对应按钮</p>
                 <p><b>流式输出</b>: 逐字显示回复,体验更好</p>
@@ -232,83 +249,85 @@
                 <p><b>文件解析</b>: 上传文件分析(Kimi/通义)</p>
               </div>
             </template>
-            <el-icon class="help-icon"><QuestionFilled /></el-icon>
-          </el-tooltip>
-        </el-divider>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="流式输出">
-              <el-switch v-model="form.supportsStream" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="视觉理解">
-              <el-switch v-model="form.supportsVision" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="工具调用">
-              <el-switch v-model="form.supportsTools" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="深度思考">
-              <el-switch v-model="form.supportsThinking" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="联网搜索">
-              <el-switch v-model="form.supportsSearch" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="文件解析">
-              <el-switch v-model="form.supportsFiles" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-divider content-position="left">状态设置</el-divider>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="启用状态">
-              <el-switch v-model="form.enabled" active-value="1" inactive-value="0" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="排序" prop="sortOrder">
-              <el-input-number v-model="form.sortOrder" :min="0" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" placeholder="备注信息" />
-        </el-form-item>
-      </el-form>
+            <QuestionCircleOutlined class="help-icon" />
+          </a-tooltip>
+        </a-divider>
+
+        <a-row :gutter="16">
+          <a-col :span="8">
+            <a-form-item label="流式输出">
+              <a-switch v-model:checked="form.supportsStream" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="视觉理解">
+              <a-switch v-model:checked="form.supportsVision" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="工具调用">
+              <a-switch v-model:checked="form.supportsTools" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-row :gutter="16">
+          <a-col :span="8">
+            <a-form-item label="深度思考">
+              <a-switch v-model:checked="form.supportsThinking" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="联网搜索">
+              <a-switch v-model:checked="form.supportsSearch" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="文件解析">
+              <a-switch v-model:checked="form.supportsFiles" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-divider orientation="left">状态设置</a-divider>
+
+        <a-row :gutter="16">
+          <a-col :span="8">
+            <a-form-item label="启用状态">
+              <a-switch v-model:checked="form.enabled" checkedValue="1" unCheckedValue="0" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="8">
+            <a-form-item label="排序" name="sortOrder">
+              <a-input-number v-model:value="form.sortOrder" :min="0" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+
+        <a-form-item label="备注" name="remark">
+          <a-input v-model:value="form.remark" placeholder="备注信息" />
+        </a-form-item>
+      </a-form>
       
       <template #footer>
-        <el-button @click="cancel">取消</el-button>
-        <el-button type="primary" @click="submitForm">确定</el-button>
+        <a-button @click="cancel">取消</a-button>
+        <a-button type="primary" @click="submitForm">确定</a-button>
       </template>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="AiService">
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, StarOutlined } from '@ant-design/icons-vue'
+import { VideoCameraOutlined, PictureOutlined, SettingOutlined, QuestionCircleOutlined, ThunderboltOutlined, FileOutlined, EyeOutlined } from '@ant-design/icons-vue'
 import { listService, getService, addService, updateService, delService, setDefaultService, viewApiKey } from '@/api/system/ai/service'
-import { VideoCameraFilled, PictureFilled, SetUp, QuestionFilled, Cpu, Search, Document, View } from '@element-plus/icons-vue'
 
 const { proxy } = getCurrentInstance()
 
 const loading = ref(true)
 const showSearch = ref(true)
 const serviceList = ref([])
+const selectedRowKeys = ref([])
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
@@ -316,7 +335,16 @@ const total = ref(0)
 const title = ref('')
 const open = ref(false)
 
-// 厂商选项(含获取API Key的网址)
+const columns = [
+  { title: '服务名称', key: 'serviceName', dataIndex: 'serviceName', minWidth: 140 },
+  { title: '服务代码', dataIndex: 'serviceCode', key: 'serviceCode', width: 150 },
+  { title: '厂商', key: 'providerName', width: 100, align: 'center' },
+  { title: '模型', dataIndex: 'modelCode', key: 'modelCode', width: 150 },
+  { title: '支持能力', key: 'capabilities', width: 180, align: 'center' },
+  { title: '状态', key: 'enabled', width: 80, align: 'center' },
+  { title: '操作', key: 'action', width: 150, align: 'center', fixed: 'right' }
+]
+
 const providerOptions = ref([
   { value: 'openai', label: 'OpenAI', url: 'platform.openai.com' },
   { value: 'zhipu', label: '智谱AI', url: 'open.bigmodel.cn' },
@@ -328,7 +356,6 @@ const providerOptions = ref([
   { value: 'ollama', label: '本地部署', url: 'ollama.com' }
 ])
 
-// 厂商API地址映射
 const providerApiUrls = {
   openai: 'https://api.openai.com/v1/chat/completions',
   zhipu: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
@@ -350,7 +377,6 @@ const queryParams = ref({
 
 const form = ref({})
 
-// 判断API Key是否是脱敏状态
 const isKeyMasked = computed(() => {
   return form.value.apiKey && form.value.apiKey.includes('****')
 })
@@ -382,10 +408,11 @@ function resetQuery() {
   handleQuery()
 }
 
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.serviceId)
-  single.value = selection.length !== 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = !keys.length
 }
 
 function reset() {
@@ -432,7 +459,6 @@ function handleUpdate(row) {
   })
 }
 
-// 查看完整API Key
 function handleViewKey() {
   if (!form.value.serviceId) return
   proxy.$modal.confirm('查看完整API Key是敏感操作,确认继续?').then(() => {
@@ -447,11 +473,9 @@ function handleViewKey() {
 }
 
 function handleProviderChange(val) {
-  // 自动填充API地址
   if (providerApiUrls[val]) {
     form.value.apiUrl = providerApiUrls[val]
   }
-  // 自动填充厂商名称
   const provider = providerOptions.value.find(p => p.value === val)
   if (provider) {
     form.value.providerName = provider.label
@@ -459,21 +483,19 @@ function handleProviderChange(val) {
 }
 
 function submitForm() {
-  proxy.$refs['serviceRef'].validate(valid => {
-    if (valid) {
-      if (form.value.serviceId) {
-        updateService(form.value).then(() => {
-          proxy.$modal.msgSuccess('修改成功')
-          open.value = false
-          getList()
-        })
-      } else {
-        addService(form.value).then(() => {
-          proxy.$modal.msgSuccess('新增成功')
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs['serviceRef'].validate().then(() => {
+    if (form.value.serviceId) {
+      updateService(form.value).then(() => {
+        proxy.$modal.msgSuccess('修改成功')
+        open.value = false
+        getList()
+      })
+    } else {
+      addService(form.value).then(() => {
+        proxy.$modal.msgSuccess('新增成功')
+        open.value = false
+        getList()
+      })
     }
   })
 }
@@ -513,18 +535,18 @@ function handleSetDefault(row) {
   }).catch(() => {})
 }
 
-function getProviderTagType(provider) {
-  const typeMap = {
-    openai: 'success',
-    zhipu: 'primary',
-    alibaba: 'warning',
-    moonshot: 'info',
-    baidu: 'danger',
-    deepseek: '',
-    anthropic: 'success',
-    ollama: 'info'
+function getProviderTagColor(provider) {
+  const colorMap = {
+    openai: 'green',
+    zhipu: 'blue',
+    alibaba: 'orange',
+    moonshot: 'default',
+    baidu: 'red',
+    deepseek: 'default',
+    anthropic: 'green',
+    ollama: 'default'
   }
-  return typeMap[provider] || ''
+  return colorMap[provider] || 'default'
 }
 
 getList()
@@ -556,17 +578,8 @@ getList()
   flex-wrap: wrap;
 }
 
-:deep(.el-divider__text) {
-  font-weight: 600;
-  color: #606266;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-}
-
-/* 帮助图标样式 */
 .help-icon {
-  color: #909399;
+  color: #999;
   font-size: 14px;
   margin-left: 4px;
   cursor: help;
@@ -575,47 +588,21 @@ getList()
 }
 
 .help-icon:hover {
-  color: #409eff;
+  color: #1890ff;
 }
 
-/* 厂商下拉选项样式 */
 .provider-url {
   float: right;
-  color: #8492a6;
+  color: #999;
   font-size: 12px;
 }
 
-/* 表单提示文字 */
-.form-tip {
-  color: #909399;
-  font-size: 12px;
-  margin-left: 8px;
-}
-
-/* API Key 输入框容器 */
 .key-input-wrapper {
   width: 100%;
-  
-  .view-key-btn {
-    margin-top: 4px;
-    font-size: 12px;
-    color: #409eff;
-    
-    .el-icon {
-      margin-right: 4px;
-    }
-  }
 }
 
-/* 对话框表单样式优化 */
-:deep(.service-dialog) {
-  .el-form-item__label {
-    display: flex;
-    align-items: center;
-  }
-  
-  .el-divider {
-    margin: 20px 0 16px;
-  }
+.view-key-btn {
+  margin-top: 4px;
+  font-size: 12px;
 }
 </style>

+ 174 - 144
yushu-uivue3/src/views/system/dept/index.vue

@@ -1,147 +1,132 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="部门名称" prop="deptName">
-            <el-input
-               v-model="queryParams.deptName"
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="部门名称" name="deptName">
+            <a-input
+               v-model:value="queryParams.deptName"
                placeholder="请输入部门名称"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select v-model="queryParams.status" placeholder="部门状态" clearable style="width: 200px">
-               <el-option
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select v-model:value="queryParams.status" placeholder="部门状态" allow-clear style="width: 200px">
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:dept:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="info"
-               plain
-               icon="Sort"
-               @click="toggleExpandAll"
-            >展开/折叠</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd()" v-hasPermi="['system:dept:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button @click="toggleExpandAll"><SortAscendingOutlined />展开/折叠</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table
+      <a-table
          v-if="refreshTable"
-         v-loading="loading"
-         :data="deptList"
-         row-key="deptId"
-         :default-expand-all="isExpandAll"
-         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+         :loading="loading"
+         :data-source="deptList"
+         :columns="tableColumns"
+         :row-key="record => record.deptId"
+         :pagination="false"
+         :default-expand-all-rows="isExpandAll"
+         :expanded-row-keys="expandedRowKeys"
+         @expand="handleExpand"
       >
-         <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
-         <el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
-         <el-table-column prop="status" label="状态" width="100">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="创建时间" align="center" prop="createTime" width="200">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">修改</el-button>
-               <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">新增</el-button>
-               <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:dept:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" @click="handleAdd(record)" v-hasPermi="['system:dept:add']"><PlusOutlined />新增</a-button>
+            <a-button v-if="record.parentId != 0" type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:dept:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <!-- 添加或修改部门对话框 -->
-      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
-         <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
-            <el-row>
-               <el-col :span="24" v-if="form.parentId !== 0">
-                  <el-form-item label="上级部门" prop="parentId">
-                     <el-tree-select
-                        v-model="form.parentId"
-                        :data="deptOptions"
-                        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
-                        value-key="deptId"
+      <a-modal :title="title" v-model:open="open" width="600px" :footer="null">
+         <a-form ref="deptRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-row>
+               <a-col :span="24" v-if="form.parentId !== 0">
+                  <a-form-item label="上级部门" name="parentId">
+                     <a-tree-select
+                        v-model:value="form.parentId"
+                        :tree-data="deptOptions"
+                        :field-names="{ value: 'deptId', label: 'deptName', children: 'children' }"
                         placeholder="选择上级部门"
-                        check-strictly
+                        tree-default-expand-all
+                        allow-clear
                      />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="部门名称" prop="deptName">
-                     <el-input v-model="form.deptName" placeholder="请输入部门名称" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="显示排序" prop="orderNum">
-                     <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="负责人" prop="leader">
-                     <el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="联系电话" prop="phone">
-                     <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="邮箱" prop="email">
-                     <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="部门状态">
-                     <el-radio-group v-model="form.status">
-                        <el-radio
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="部门名称" name="deptName">
+                     <a-input v-model:value="form.deptName" placeholder="请输入部门名称" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="显示排序" name="orderNum">
+                     <a-input-number v-model:value="form.orderNum" :min="0" style="width: 100%" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="负责人" name="leader">
+                     <a-input v-model:value="form.leader" placeholder="请输入负责人" :maxlength="20" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="联系电话" name="phone">
+                     <a-input v-model:value="form.phone" placeholder="请输入联系电话" :maxlength="11" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="邮箱" name="email">
+                     <a-input v-model:value="form.email" placeholder="请输入邮箱" :maxlength="50" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="部门状态">
+                     <a-radio-group v-model:value="form.status">
+                        <a-radio
                            v-for="dict in sys_normal_disable"
                            :key="dict.value"
                            :value="dict.value"
-                        >{{ dict.label }}</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                        >{{ dict.label }}</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+            </a-row>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
 <script setup name="Dept">
 import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, SortAscendingOutlined } from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -154,6 +139,15 @@ const title = ref("")
 const deptOptions = ref([])
 const isExpandAll = ref(true)
 const refreshTable = ref(true)
+const expandedRowKeys = ref([])
+
+const tableColumns = [
+  { title: '部门名称', dataIndex: 'deptName', key: 'deptName', width: 260 },
+  { title: '排序', dataIndex: 'orderNum', key: 'orderNum', width: 200 },
+  { title: '状态', key: 'status', width: 100 },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 200 },
+  { title: '操作', key: 'action', align: 'center' }
+]
 
 const data = reactive({
   form: {},
@@ -172,11 +166,38 @@ const data = reactive({
 
 const { queryParams, form, rules } = toRefs(data)
 
+/** 获取所有部门ID(用于展开) */
+function getAllDeptIds(depts) {
+  let ids = []
+  depts.forEach(dept => {
+    ids.push(dept.deptId)
+    if (dept.children && dept.children.length > 0) {
+      ids = ids.concat(getAllDeptIds(dept.children))
+    }
+  })
+  return ids
+}
+
+/** 处理展开 */
+function handleExpand(expanded, record) {
+  if (expanded) {
+    expandedRowKeys.value.push(record.deptId)
+  } else {
+    const index = expandedRowKeys.value.indexOf(record.deptId)
+    if (index > -1) {
+      expandedRowKeys.value.splice(index, 1)
+    }
+  }
+}
+
 /** 查询部门列表 */
 function getList() {
   loading.value = true
   listDept(queryParams.value).then(response => {
     deptList.value = proxy.handleTree(response.data, "deptId")
+    if (isExpandAll.value) {
+      expandedRowKeys.value = getAllDeptIds(deptList.value)
+    }
     loading.value = false
   })
 }
@@ -199,7 +220,6 @@ function reset() {
     email: undefined,
     status: "0"
   }
-  proxy.resetForm("deptRef")
 }
 
 /** 搜索按钮操作 */
@@ -209,7 +229,10 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    deptName: undefined,
+    status: undefined
+  }
   handleQuery()
 }
 
@@ -219,7 +242,7 @@ function handleAdd(row) {
   listDept().then(response => {
     deptOptions.value = proxy.handleTree(response.data, "deptId")
   })
-  if (row != undefined) {
+  if (row != undefined && row.deptId) {
     form.value.parentId = row.deptId
   }
   open.value = true
@@ -230,6 +253,11 @@ function handleAdd(row) {
 function toggleExpandAll() {
   refreshTable.value = false
   isExpandAll.value = !isExpandAll.value
+  if (isExpandAll.value) {
+    expandedRowKeys.value = getAllDeptIds(deptList.value)
+  } else {
+    expandedRowKeys.value = []
+  }
   nextTick(() => {
     refreshTable.value = true
   })
@@ -250,33 +278,35 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["deptRef"].validate(valid => {
-    if (valid) {
-      if (form.value.deptId != undefined) {
-        updateDept(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addDept(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["deptRef"].validate().then(() => {
+    if (form.value.deptId != undefined) {
+      updateDept(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addDept(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
-  proxy.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {
-    return delDept(row.deptId)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除名称为"' + row.deptName + '"的数据项?',
+    onOk() {
+      return delDept(row.deptId).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 getList()

+ 166 - 175
yushu-uivue3/src/views/system/dict/data.vue

@@ -1,120 +1,83 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="字典名称" prop="dictType">
-            <el-select v-model="queryParams.dictType" style="width: 200px">
-               <el-option
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="字典名称" name="dictType">
+            <a-select v-model:value="queryParams.dictType" style="width: 200px">
+               <a-select-option
                   v-for="item in typeOptions"
                   :key="item.dictId"
-                  :label="item.dictName"
                   :value="item.dictType"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item label="字典标签" prop="dictLabel">
-            <el-input
-               v-model="queryParams.dictLabel"
+               >{{ item.dictName }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item label="字典标签" name="dictLabel">
+            <a-input
+               v-model:value="queryParams.dictLabel"
                placeholder="请输入字典标签"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
-               <el-option
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select v-model:value="queryParams.status" placeholder="数据状态" allow-clear style="width: 200px">
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:dict:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:dict:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:dict:remove']"
-            >删除</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Download"
-               @click="handleExport"
-               v-hasPermi="['system:dict:export']"
-            >导出</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Close"
-               @click="handleClose"
-            >关闭</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd" v-hasPermi="['system:dict:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:dict:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:dict:remove']"><DeleteOutlined />删除</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:dict:export']"><DownloadOutlined />导出</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleClose"><CloseOutlined />关闭</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="字典编码" align="center" prop="dictCode" />
-         <el-table-column label="字典标签" align="center" prop="dictLabel">
-            <template #default="scope">
-               <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
-               <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
-            </template>
-         </el-table-column>
-         <el-table-column label="字典键值" align="center" prop="dictValue" />
-         <el-table-column label="字典排序" align="center" prop="dictSort" />
-         <el-table-column label="状态" align="center" prop="status">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">修改</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="dataList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.dictCode"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'dictLabel'">
+            <span v-if="(record.listClass == '' || record.listClass == 'default') && (record.cssClass == '' || record.cssClass == null)">{{ record.dictLabel }}</span>
+            <a-tag v-else :color="getTagColor(record.listClass)" :class="record.cssClass">{{ record.dictLabel }}</a-tag>
+          </template>
+          <template v-else-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:dict:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:dict:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -125,53 +88,50 @@
       />
 
       <!-- 添加或修改参数配置对话框 -->
-      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-         <el-form ref="dataRef" :model="form" :rules="rules" label-width="80px">
-            <el-form-item label="字典类型">
-               <el-input v-model="form.dictType" :disabled="true" />
-            </el-form-item>
-            <el-form-item label="数据标签" prop="dictLabel">
-               <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
-            </el-form-item>
-            <el-form-item label="数据键值" prop="dictValue">
-               <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
-            </el-form-item>
-            <el-form-item label="样式属性" prop="cssClass">
-               <el-input v-model="form.cssClass" placeholder="请输入样式属性" />
-            </el-form-item>
-            <el-form-item label="显示排序" prop="dictSort">
-               <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
-            </el-form-item>
-            <el-form-item label="回显样式" prop="listClass">
-               <el-select v-model="form.listClass">
-                  <el-option
+      <a-modal :title="title" v-model:open="open" width="500px" :footer="null">
+         <a-form ref="dataRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-form-item label="字典类型">
+               <a-input v-model:value="form.dictType" disabled />
+            </a-form-item>
+            <a-form-item label="数据标签" name="dictLabel">
+               <a-input v-model:value="form.dictLabel" placeholder="请输入数据标签" />
+            </a-form-item>
+            <a-form-item label="数据键值" name="dictValue">
+               <a-input v-model:value="form.dictValue" placeholder="请输入数据键值" />
+            </a-form-item>
+            <a-form-item label="样式属性" name="cssClass">
+               <a-input v-model:value="form.cssClass" placeholder="请输入样式属性" />
+            </a-form-item>
+            <a-form-item label="显示排序" name="dictSort">
+               <a-input-number v-model:value="form.dictSort" :min="0" style="width: 100%" />
+            </a-form-item>
+            <a-form-item label="回显样式" name="listClass">
+               <a-select v-model:value="form.listClass">
+                  <a-select-option
                      v-for="item in listClassOptions"
                      :key="item.value"
-                     :label="item.label + '(' + item.value + ')'"
                      :value="item.value"
-                  ></el-option>
-               </el-select>
-            </el-form-item>
-            <el-form-item label="状态" prop="status">
-               <el-radio-group v-model="form.status">
-                  <el-radio
+                  >{{ item.label }}({{ item.value }})</a-select-option>
+               </a-select>
+            </a-form-item>
+            <a-form-item label="状态" name="status">
+               <a-radio-group v-model:value="form.status">
+                  <a-radio
                      v-for="dict in sys_normal_disable"
                      :key="dict.value"
                      :value="dict.value"
-                  >{{ dict.label }}</el-radio>
-               </el-radio-group>
-            </el-form-item>
-            <el-form-item label="备注" prop="remark">
-               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  >{{ dict.label }}</a-radio>
+               </a-radio-group>
+            </a-form-item>
+            <a-form-item label="备注" name="remark">
+               <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+            </a-form-item>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
@@ -179,6 +139,8 @@
 import useDictStore from '@/store/modules/dict'
 import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type"
 import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined, CloseOutlined } from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -188,6 +150,7 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
@@ -195,6 +158,18 @@ const title = ref("")
 const defaultDictType = ref("")
 const typeOptions = ref([])
 const route = useRoute()
+
+const tableColumns = [
+  { title: '字典编码', dataIndex: 'dictCode', key: 'dictCode', align: 'center' },
+  { title: '字典标签', key: 'dictLabel', align: 'center' },
+  { title: '字典键值', dataIndex: 'dictValue', key: 'dictValue', align: 'center' },
+  { title: '字典排序', dataIndex: 'dictSort', key: 'dictSort', align: 'center' },
+  { title: '状态', key: 'status', align: 'center' },
+  { title: '备注', dataIndex: 'remark', key: 'remark', align: 'center', ellipsis: true },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 },
+  { title: '操作', key: 'action', align: 'center', width: 160 }
+]
+
 // 数据标签回显样式
 const listClassOptions = ref([
   { value: "default", label: "默认" }, 
@@ -205,6 +180,19 @@ const listClassOptions = ref([
   { value: "danger", label: "危险" }
 ])
 
+// 转换 listClass 为 a-tag 的 color
+function getTagColor(listClass) {
+  const colorMap = {
+    'primary': 'blue',
+    'success': 'green',
+    'info': 'cyan',
+    'warning': 'orange',
+    'danger': 'red',
+    'default': 'default'
+  }
+  return colorMap[listClass] || 'default'
+}
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -267,7 +255,6 @@ function reset() {
     status: "0",
     remark: undefined
   }
-  proxy.resetForm("dataRef")
 }
 
 /** 搜索按钮操作 */
@@ -284,7 +271,8 @@ function handleClose() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.value.dictLabel = undefined
+  queryParams.value.status = undefined
   queryParams.value.dictType = defaultDictType.value
   handleQuery()
 }
@@ -298,10 +286,11 @@ function handleAdd() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.dictCode)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 修改按钮操作 */
@@ -317,37 +306,39 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["dataRef"].validate(valid => {
-    if (valid) {
-      if (form.value.dictCode != undefined) {
-        updateData(form.value).then(response => {
-          useDictStore().removeDict(queryParams.value.dictType)
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addData(form.value).then(response => {
-          useDictStore().removeDict(queryParams.value.dictType)
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["dataRef"].validate().then(() => {
+    if (form.value.dictCode != undefined) {
+      updateData(form.value).then(response => {
+        useDictStore().removeDict(queryParams.value.dictType)
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addData(form.value).then(response => {
+        useDictStore().removeDict(queryParams.value.dictType)
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const dictCodes = row.dictCode || ids.value
-  proxy.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function() {
-    return delData(dictCodes)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-    useDictStore().removeDict(queryParams.value.dictType)
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除字典编码为"' + dictCodes + '"的数据项?',
+    onOk() {
+      return delData(dictCodes).then(() => {
+        getList()
+        message.success("删除成功")
+        useDictStore().removeDict(queryParams.value.dictType)
+      })
+    }
+  })
 }
 
 /** 导出按钮操作 */

+ 162 - 169
yushu-uivue3/src/views/system/dict/index.vue

@@ -1,135 +1,95 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-         <el-form-item label="字典名称" prop="dictName">
-            <el-input
-               v-model="queryParams.dictName"
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="字典名称" name="dictName">
+            <a-input
+               v-model:value="queryParams.dictName"
                placeholder="请输入字典名称"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="字典类型" prop="dictType">
-            <el-input
-               v-model="queryParams.dictType"
+         </a-form-item>
+         <a-form-item label="字典类型" name="dictType">
+            <a-input
+               v-model:value="queryParams.dictType"
                placeholder="请输入字典类型"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select
-               v-model="queryParams.status"
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select
+               v-model:value="queryParams.status"
                placeholder="字典状态"
-               clearable
+               allow-clear
                style="width: 240px"
             >
-               <el-option
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item label="创建时间" style="width: 308px">
-            <el-date-picker
-               v-model="dateRange"
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item label="创建时间" style="width: 308px">
+            <a-range-picker
+               v-model:value="dateRange"
                value-format="YYYY-MM-DD"
-               type="daterange"
-               range-separator="-"
-               start-placeholder="开始日期"
-               end-placeholder="结束日期"
-            ></el-date-picker>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+            />
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:dict:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:dict:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:dict:remove']"
-            >删除</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Download"
-               @click="handleExport"
-               v-hasPermi="['system:dict:export']"
-            >导出</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Refresh"
-               @click="handleRefreshCache"
-               v-hasPermi="['system:dict:remove']"
-            >刷新缓存</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd" v-hasPermi="['system:dict:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:dict:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:dict:remove']"><DeleteOutlined />删除</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:dict:export']"><DownloadOutlined />导出</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger @click="handleRefreshCache" v-hasPermi="['system:dict:remove']"><ReloadOutlined />刷新缓存</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="字典编号" align="center" prop="dictId" />
-         <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true"/>
-         <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
-            <template #default="scope">
-               <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
-                  <span>{{ scope.row.dictType }}</span>
-               </router-link>
-            </template>
-         </el-table-column>
-         <el-table-column label="状态" align="center" prop="status">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">修改</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="typeList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.dictId"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'dictType'">
+            <router-link :to="'/system/dict-data/index/' + record.dictId" class="link-type">
+              <span>{{ record.dictType }}</span>
+            </router-link>
+          </template>
+          <template v-else-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:dict:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:dict:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -140,40 +100,40 @@
       />
 
       <!-- 添加或修改参数配置对话框 -->
-      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-         <el-form ref="dictRef" :model="form" :rules="rules" label-width="80px">
-            <el-form-item label="字典名称" prop="dictName">
-               <el-input v-model="form.dictName" placeholder="请输入字典名称" />
-            </el-form-item>
-            <el-form-item label="字典类型" prop="dictType">
-               <el-input v-model="form.dictType" placeholder="请输入字典类型" />
-            </el-form-item>
-            <el-form-item label="状态" prop="status">
-               <el-radio-group v-model="form.status">
-                  <el-radio
+      <a-modal :title="title" v-model:open="open" width="500px" :footer="null">
+         <a-form ref="dictRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-form-item label="字典名称" name="dictName">
+               <a-input v-model:value="form.dictName" placeholder="请输入字典名称" />
+            </a-form-item>
+            <a-form-item label="字典类型" name="dictType">
+               <a-input v-model:value="form.dictType" placeholder="请输入字典类型" />
+            </a-form-item>
+            <a-form-item label="状态" name="status">
+               <a-radio-group v-model:value="form.status">
+                  <a-radio
                      v-for="dict in sys_normal_disable"
                      :key="dict.value"
                      :value="dict.value"
-                  >{{ dict.label }}</el-radio>
-               </el-radio-group>
-            </el-form-item>
-            <el-form-item label="备注" prop="remark">
-               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  >{{ dict.label }}</a-radio>
+               </a-radio-group>
+            </a-form-item>
+            <a-form-item label="备注" name="remark">
+               <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+            </a-form-item>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
 <script setup name="Dict">
 import useDictStore from '@/store/modules/dict'
 import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined } from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -183,12 +143,23 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 const dateRange = ref([])
 
+const tableColumns = [
+  { title: '字典编号', dataIndex: 'dictId', key: 'dictId', align: 'center' },
+  { title: '字典名称', dataIndex: 'dictName', key: 'dictName', align: 'center', ellipsis: true },
+  { title: '字典类型', key: 'dictType', align: 'center', ellipsis: true },
+  { title: '状态', key: 'status', align: 'center' },
+  { title: '备注', dataIndex: 'remark', key: 'remark', align: 'center', ellipsis: true },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 },
+  { title: '操作', key: 'action', align: 'center', width: 160 }
+]
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -209,7 +180,12 @@ const { queryParams, form, rules } = toRefs(data)
 /** 查询字典类型列表 */
 function getList() {
   loading.value = true
-  listType(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+  const params = { ...queryParams.value }
+  if (dateRange.value && dateRange.value.length === 2) {
+    params.beginTime = dateRange.value[0]
+    params.endTime = dateRange.value[1]
+  }
+  listType(params).then(response => {
     typeList.value = response.rows
     total.value = response.total
     loading.value = false
@@ -231,7 +207,6 @@ function reset() {
     status: "0",
     remark: undefined
   }
-  proxy.resetForm("dictRef")
 }
 
 /** 搜索按钮操作 */
@@ -243,7 +218,13 @@ function handleQuery() {
 /** 重置按钮操作 */
 function resetQuery() {
   dateRange.value = []
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    dictName: undefined,
+    dictType: undefined,
+    status: undefined
+  }
   handleQuery()
 }
 
@@ -255,10 +236,11 @@ function handleAdd() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.dictId)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 修改按钮操作 */
@@ -274,34 +256,36 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["dictRef"].validate(valid => {
-    if (valid) {
-      if (form.value.dictId != undefined) {
-        updateType(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addType(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["dictRef"].validate().then(() => {
+    if (form.value.dictId != undefined) {
+      updateType(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addType(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const dictIds = row.dictId || ids.value
-  proxy.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?').then(function() {
-    return delType(dictIds)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除字典编号为"' + dictIds + '"的数据项?',
+    onOk() {
+      return delType(dictIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 /** 导出按钮操作 */
@@ -314,10 +298,19 @@ function handleExport() {
 /** 刷新缓存按钮操作 */
 function handleRefreshCache() {
   refreshCache().then(() => {
-    proxy.$modal.msgSuccess("刷新成功")
+    message.success("刷新成功")
     useDictStore().cleanDict()
   })
 }
 
 getList()
 </script>
+
+<style scoped>
+.link-type {
+  color: #1890ff;
+}
+.link-type:hover {
+  text-decoration: underline;
+}
+</style>

+ 4 - 4
yushu-uivue3/src/views/system/file/index.vue

@@ -477,10 +477,10 @@
 
 <script setup name="FileManager">
 import { 
-  HomeFilled, Folder, Document, FolderAdd, Upload, UploadFilled, 
-  ArrowDown, ArrowLeft, ArrowRight, Top, Menu, List, Edit, Download, Delete, Search,
-  Share, CopyDocument, Scissor, DocumentAdd, Refresh, CircleCheck
-} from '@element-plus/icons-vue'
+  HomeOutlined, FolderOutlined, FileOutlined, FolderAddOutlined, UploadOutlined, UploadOutlined as UploadFilled, 
+  DownOutlined, LeftOutlined, RightOutlined, UpOutlined, MenuOutlined, UnorderedListOutlined, EditOutlined, DownloadOutlined, DeleteOutlined, SearchOutlined,
+  ShareAltOutlined, CopyOutlined, ScissorOutlined, FileAddOutlined, ReloadOutlined, CheckCircleOutlined
+} from '@ant-design/icons-vue'
 import { 
   listFolder, addFolder, updateFolder, delFolder,
   listFile, downloadFile, delFile, updateFile, updateFileContent,

+ 87 - 113
yushu-uivue3/src/views/system/file/share.vue

@@ -4,30 +4,30 @@
       <div class="share-box" :class="{ 'full-width': verified && fileInfo && fileInfo.fileList }">
         <!-- 加载中 -->
         <div v-if="loading" class="loading-box">
-          <el-icon class="is-loading" :size="48" color="#409EFF"><Loading /></el-icon>
+          <LoadingOutlined :spin="true" :style="{ fontSize: '48px', color: '#1890ff' }" />
           <p style="margin-top: 20px;">正在加载分享文件...</p>
         </div>
         
         <!-- 需要密码 -->
         <div v-else-if="needPassword && !verified" class="password-box">
           <div class="share-header">
-            <el-icon :size="64" color="#409EFF"><FolderOpened /></el-icon>
+            <FolderOpenOutlined :style="{ fontSize: '64px', color: '#1890ff' }" />
             <h1>文件分享</h1>
             <p>请输入提取码访问,如无提取码请直接点击确定</p>
           </div>
           
           <div class="password-input-box">
-            <el-input
-              v-model="password"
+            <a-input
+              v-model:value="password"
               placeholder="请输入提取码"
               size="large"
-              maxlength="4"
+              :maxlength="4"
               class="password-input"
-              @keyup.enter="verifyPassword"
+              @pressEnter="verifyPassword"
             />
-            <el-button type="primary" size="large" @click="verifyPassword" class="submit-btn">
+            <a-button type="primary" size="large" @click="verifyPassword" class="submit-btn">
               访问文件
-            </el-button>
+            </a-button>
           </div>
         </div>
 
@@ -36,22 +36,25 @@
           <!-- 单文件分享 -->
           <div v-if="!fileInfo.fileList">
             <div class="file-icon">
-              <el-icon :size="80" color="#409EFF"><Document /></el-icon>
+              <FileOutlined :style="{ fontSize: '80px', color: '#1890ff' }" />
             </div>
             <h2>{{ fileInfo.fileName }}</h2>
             <div class="file-meta">
-              <span><el-icon><Document /></el-icon> {{ formatFileSize(fileInfo.fileSize) }}</span>
-              <span style="margin-left: 20px;"><el-icon><Clock /></el-icon> {{ parseTime(fileInfo.createTime) }}</span>
+              <span><FileOutlined /> {{ formatFileSize(fileInfo.fileSize) }}</span>
+              <span style="margin-left: 20px;"><ClockCircleOutlined /> {{ parseTime(fileInfo.createTime) }}</span>
             </div>
             <div class="action-buttons">
-              <el-button type="primary" icon="Download" @click="downloadFile">下载文件</el-button>
+              <a-button type="primary" @click="downloadFile">
+                <template #icon><DownloadOutlined /></template>
+                下载文件
+              </a-button>
             </div>
           </div>
           
           <!-- 文件夹分享 -->
           <div v-else class="folder-share">
             <div class="folder-header">
-              <el-icon :size="48" color="#409EFF"><FolderOpened /></el-icon>
+              <FolderOpenOutlined :style="{ fontSize: '48px', color: '#1890ff' }" />
               <h2>{{ fileInfo.fileName }}</h2>
               <p>共 {{ folderList.length }} 个文件夹,{{ fileInfo.fileList.length }} 个文件</p>
             </div>
@@ -59,73 +62,79 @@
             <!-- 工具栏 -->
             <div class="toolbar">
               <div class="toolbar-left">
-                <el-breadcrumb separator="/" v-if="breadcrumb.length > 0">
-                  <el-breadcrumb-item @click="goToFolder(-1)" style="cursor: pointer;">根目录</el-breadcrumb-item>
-                  <el-breadcrumb-item 
+                <a-breadcrumb separator="/" v-if="breadcrumb.length > 0">
+                  <a-breadcrumb-item @click="goToFolder(-1)" style="cursor: pointer;">根目录</a-breadcrumb-item>
+                  <a-breadcrumb-item 
                     v-for="(item, index) in breadcrumb" 
                     :key="index"
                     @click="goToFolder(index)"
                     style="cursor: pointer;">
                     {{ item.folderName }}
-                  </el-breadcrumb-item>
-                </el-breadcrumb>
-                <span v-else style="color: #909399;">根目录</span>
+                  </a-breadcrumb-item>
+                </a-breadcrumb>
+                <span v-else style="color: #999;">根目录</span>
               </div>
               <div class="toolbar-right">
-                <el-button type="primary" icon="Download" @click="downloadAll">下载全部</el-button>
-                <el-button 
+                <a-button type="primary" @click="downloadAll">
+                  <template #icon><DownloadOutlined /></template>
+                  下载全部
+                </a-button>
+                <a-button 
                   v-if="selectedItems.length > 0"
-                  type="success" 
-                  icon="Download"
                   @click="downloadSelected">
+                  <template #icon><DownloadOutlined /></template>
                   下载选中 ({{ selectedItems.length }})
-                </el-button>
+                </a-button>
               </div>
             </div>
             
             <!-- 文件列表 -->
-            <el-table :data="allItems" style="width: 100%; margin-top: 20px;" @selection-change="handleSelectionChange">
-              <el-table-column type="selection" width="55" />
-              <el-table-column label="名称" min-width="300">
-                <template #default="scope">
-                  <div v-if="scope.row.isFolder" @click="enterFolder(scope.row)" style="cursor: pointer; display: flex; align-items: center;">
-                    <el-icon color="#409EFF" style="margin-right: 8px;"><Folder /></el-icon>
-                    <span style="color: #409EFF;">{{ scope.row.folderName }}</span>
+            <a-table 
+              :dataSource="allItems" 
+              style="width: 100%; margin-top: 20px;" 
+              :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+              :pagination="false"
+              rowKey="id"
+            >
+              <a-table-column title="名称" dataIndex="name" key="name" :min-width="300">
+                <template #default="{ record }">
+                  <div v-if="record.isFolder" @click="enterFolder(record)" style="cursor: pointer; display: flex; align-items: center;">
+                    <FolderOutlined style="color: #1890ff; margin-right: 8px;" />
+                    <span style="color: #1890ff;">{{ record.folderName }}</span>
                   </div>
                   <div v-else style="display: flex; align-items: center;">
-                    <el-icon style="margin-right: 8px;"><Document /></el-icon>
-                    <span>{{ scope.row.fileName }}</span>
+                    <FileOutlined style="margin-right: 8px;" />
+                    <span>{{ record.fileName }}</span>
                   </div>
                 </template>
-              </el-table-column>
-              <el-table-column label="大小" width="150">
-                <template #default="scope">
-                  <span v-if="!scope.row.isFolder">{{ formatFileSize(scope.row.fileSize) }}</span>
-                  <span v-else style="color: #909399;">-</span>
+              </a-table-column>
+              <a-table-column title="大小" width="150">
+                <template #default="{ record }">
+                  <span v-if="!record.isFolder">{{ formatFileSize(record.fileSize) }}</span>
+                  <span v-else style="color: #999;">-</span>
                 </template>
-              </el-table-column>
-              <el-table-column label="上传时间" width="200">
-                <template #default="scope">
-                  {{ parseTime(scope.row.createTime) }}
+              </a-table-column>
+              <a-table-column title="上传时间" width="200">
+                <template #default="{ record }">
+                  {{ parseTime(record.createTime) }}
                 </template>
-              </el-table-column>
-              <el-table-column label="操作" width="120">
-                <template #default="scope">
-                  <el-button 
-                    link
-                    type="primary"
-                    icon="Download" 
-                    @click="scope.row.isFolder ? downloadFolder(scope.row) : downloadFileById(scope.row.fileId)">
+              </a-table-column>
+              <a-table-column title="操作" width="120">
+                <template #default="{ record }">
+                  <a-button 
+                    type="link"
+                    @click="record.isFolder ? downloadFolder(record) : downloadFileById(record.fileId)">
+                    <template #icon><DownloadOutlined /></template>
                     下载
-                  </el-button>
+                  </a-button>
                 </template>
-              </el-table-column>
-            </el-table>
+              </a-table-column>
+            </a-table>
           </div>
           
           <div class="share-info">
             <p>
-              <el-icon><Warning /></el-icon> 
+              <WarningOutlined /> 
               分享有效期至: {{ parseTime(shareInfo.expireTime) }}
             </p>
           </div>
@@ -133,7 +142,7 @@
 
         <!-- 错误提示 -->
         <div v-else class="error-box">
-          <el-icon :size="64" color="#F56C6C"><Warning /></el-icon>
+          <WarningOutlined :style="{ fontSize: '64px', color: '#f5222d' }" />
           <h2>{{ errorMessage }}</h2>
           <p>{{ errorDetail }}</p>
         </div>
@@ -143,7 +152,7 @@
 </template>
 
 <script setup name="FileShare">
-import { Loading, FolderOpened, Document, Clock, Folder, Warning } from '@element-plus/icons-vue'
+import { LoadingOutlined, FolderOpenOutlined, FileOutlined, ClockCircleOutlined, FolderOutlined, WarningOutlined, DownloadOutlined } from '@ant-design/icons-vue'
 import { verifyFileShare, getShareFolderFiles, listFolder } from '@/api/system/file'
 import { parseTime } from '@/utils/yushu'
 
@@ -163,10 +172,11 @@ const currentFolderId = ref(null)
 const folderList = ref([])
 const breadcrumb = ref([])
 const selectedItems = ref([])
+const selectedRowKeys = ref([])
 
 const allItems = computed(() => {
-  const folders = folderList.value.map(folder => ({ ...folder, isFolder: true }))
-  const files = fileInfo.value?.fileList?.map(file => ({ ...file, isFolder: false })) || []
+  const folders = folderList.value.map(folder => ({ ...folder, isFolder: true, id: `folder_${folder.folderId}` }))
+  const files = fileInfo.value?.fileList?.map(file => ({ ...file, isFolder: false, id: `file_${file.fileId}` })) || []
   return [...folders, ...files]
 })
 
@@ -280,8 +290,9 @@ async function goToFolder(index) {
   }
 }
 
-function handleSelectionChange(selection) {
-  selectedItems.value = selection
+function handleSelectionChange(keys, rows) {
+  selectedRowKeys.value = keys
+  selectedItems.value = rows
 }
 
 function downloadAll() {
@@ -404,12 +415,12 @@ function formatFileSize(bytes) {
   text-align: center;
   margin-bottom: 40px;
   
-  .el-icon {
+  .anticon {
     margin-bottom: 16px;
     padding: 16px;
-    background: #ecf5ff;
+    background: #e6f7ff;
     border-radius: 50%;
-    color: #409eff;
+    color: #1890ff;
   }
   
   h1 {
@@ -421,7 +432,7 @@ function formatFileSize(bytes) {
   }
   
   p {
-    color: #909399;
+    color: #999;
     font-size: 15px;
     margin-top: 8px;
   }
@@ -432,18 +443,7 @@ function formatFileSize(bytes) {
   margin: 0 auto;
   
   .password-input {
-    :deep(.el-input__wrapper) {
-      padding: 4px 12px;
-      border-radius: 12px;
-      box-shadow: 0 0 0 1px #dcdfe6 inset;
-      transition: all 0.2s;
-      
-      &.is-focus {
-        box-shadow: 0 0 0 2px #409eff inset;
-      }
-    }
-    
-    :deep(.el-input__inner) {
+    :deep(.ant-input) {
       text-align: center;
       font-size: 24px;
       font-weight: 600;
@@ -467,12 +467,12 @@ function formatFileSize(bytes) {
     font-size: 16px;
     font-weight: 600;
     letter-spacing: 1px;
-    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+    box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
     transition: all 0.2s;
     
     &:hover {
       transform: translateY(-1px);
-      box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4);
+      box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
     }
     
     &:active {
@@ -484,7 +484,7 @@ function formatFileSize(bytes) {
 .file-icon {
   margin-bottom: 24px;
   
-  .el-icon {
+  .anticon {
     padding: 24px;
     background: #f0f2f5;
     border-radius: 20px;
@@ -528,9 +528,9 @@ h2 {
     border-bottom: 1px solid #ebeef5;
     margin-bottom: 20px;
     
-    .el-icon {
+    .anticon {
       padding: 16px;
-      background: #ecf5ff;
+      background: #e6f7ff;
       border-radius: 16px;
       margin-bottom: 16px;
     }
@@ -541,7 +541,7 @@ h2 {
     }
     
     p {
-      color: #909399;
+      color: #999;
       margin-top: 8px;
       font-size: 14px;
     }
@@ -556,10 +556,6 @@ h2 {
     .toolbar-left {
       flex: 1;
       overflow: hidden;
-      
-      .el-breadcrumb {
-        font-size: 14px;
-      }
     }
     
     .toolbar-right {
@@ -571,32 +567,10 @@ h2 {
   }
 }
 
-:deep(.el-table) {
-  --el-table-header-bg-color: #f5f7fa;
-  --el-table-row-hover-bg-color: #f0f9eb;
-  border-radius: 8px;
-  overflow: hidden;
-  
-  th.el-table__cell {
-    font-weight: 600;
-    color: #606266;
-  }
-  
-  .el-button--link {
-    padding: 4px 8px;
-    font-size: 13px;
-    
-    &:hover {
-      background: #ecf5ff;
-      border-radius: 4px;
-    }
-  }
-}
-
 .action-buttons {
   margin: 40px 0;
   
-  .el-button {
+  .ant-btn {
     padding: 12px 32px;
     font-size: 16px;
     border-radius: 8px;
@@ -610,7 +584,7 @@ h2 {
   border-top: 1px solid #ebeef5;
   
   p {
-    color: #909399;
+    color: #999;
     font-size: 13px;
     display: flex;
     align-items: center;
@@ -620,8 +594,8 @@ h2 {
 }
 
 .error-box {
-  .el-icon {
-    background: #fef0f0;
+  .anticon {
+    background: #fff1f0;
     padding: 20px;
     border-radius: 50%;
     margin-bottom: 24px;
@@ -633,7 +607,7 @@ h2 {
   }
   
   p {
-    color: #909399;
+    color: #999;
   }
 }
 </style>

+ 270 - 249
yushu-uivue3/src/views/system/mail/config/index.vue

@@ -1,231 +1,250 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="配置名称" prop="configName">
-        <el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="邮箱类型" prop="mailType">
-        <el-select v-model="queryParams.mailType" placeholder="请选择邮箱类型" clearable style="width: 200px">
-          <el-option v-for="item in mailTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
-          <el-option label="正常" value="0" />
-          <el-option label="停用" value="1" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:mail:config:add']">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:mail:config:edit']">修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:config:remove']">删除</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:mail:config:export']">导出</el-button>
-      </el-col>
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch" class="mb8">
+      <a-form-item label="配置名称" name="configName">
+        <a-input v-model:value="queryParams.configName" placeholder="请输入配置名称" allowClear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="邮箱类型" name="mailType">
+        <a-select v-model:value="queryParams.mailType" placeholder="请选择邮箱类型" allowClear style="width: 200px">
+          <a-select-option v-for="item in mailTypeOptions" :key="item.value" :value="item.value">{{ item.label }}</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="状态" name="status">
+        <a-select v-model:value="queryParams.status" placeholder="请选择状态" allowClear style="width: 200px">
+          <a-select-option value="0">正常</a-select-option>
+          <a-select-option value="1">停用</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
+
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button type="primary" @click="handleAdd" v-hasPermi="['system:mail:config:add']">
+          <template #icon><PlusOutlined /></template>
+          新增
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:mail:config:edit']">
+          <template #icon><EditOutlined /></template>
+          修改
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:config:remove']">
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button @click="handleExport" v-hasPermi="['system:mail:config:export']">
+          <template #icon><DownloadOutlined /></template>
+          导出
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="配置ID" align="center" prop="configId" width="80" />
-      <el-table-column label="配置名称" align="center" prop="configName" :show-overflow-tooltip="true" />
-      <el-table-column label="邮箱类型" align="center" prop="mailType" width="100">
-        <template #default="scope">
-          <el-tag :type="getMailTypeTag(scope.row.mailType)">{{ getMailTypeName(scope.row.mailType) }}</el-tag>
+    </a-row>
+
+    <a-table 
+      :loading="loading" 
+      :dataSource="configList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="configId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'mailType'">
+          <a-tag :color="getMailTypeTag(record.mailType)">{{ getMailTypeName(record.mailType) }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="发件人邮箱" align="center" prop="senderEmail" :show-overflow-tooltip="true" />
-      <el-table-column label="发件人名称" align="center" prop="senderName" width="120" />
-      <el-table-column label="是否默认" align="center" prop="isDefault" width="80">
-        <template #default="scope">
-          <el-tag :type="scope.row.isDefault === '1' ? 'success' : 'info'">{{ scope.row.isDefault === '1' ? '是' : '否' }}</el-tag>
+        <template v-else-if="column.key === 'isDefault'">
+          <a-tag :color="record.isDefault === '1' ? 'green' : 'default'">{{ record.isDefault === '1' ? '是' : '否' }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="状态" align="center" prop="status" width="80">
-        <template #default="scope">
-          <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag>
+        <template v-else-if="column.key === 'status'">
+          <a-tag :color="record.status === '0' ? 'green' : 'red'">{{ record.status === '0' ? '正常' : '停用' }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
-        <template #default="scope">
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:mail:config:edit']">修改</el-button>
-          <el-button link type="primary" icon="Position" @click="handleTest(scope.row)" v-hasPermi="['system:mail:config:test']">测试</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:mail:config:remove']">删除</el-button>
+        <template v-else-if="column.key === 'action'">
+          <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:mail:config:edit']">
+            <template #icon><EditOutlined /></template>
+            修改
+          </a-button>
+          <a-button type="link" size="small" @click="handleTest(record)" v-hasPermi="['system:mail:config:test']">
+            <template #icon><SendOutlined /></template>
+            测试
+          </a-button>
+          <a-button type="link" danger size="small" @click="handleDelete(record)" v-hasPermi="['system:mail:config:remove']">
+            <template #icon><DeleteOutlined /></template>
+            删除
+          </a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
 
     <!-- 添加或修改邮箱配置对话框 -->
-    <el-dialog :title="title" v-model="open" width="700px" append-to-body>
-      <el-form ref="configRef" :model="form" :rules="rules" label-width="100px">
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="配置名称" prop="configName">
-              <el-input v-model="form.configName" placeholder="请输入配置名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="邮箱类型" prop="mailType">
-              <el-select v-model="form.mailType" placeholder="请选择邮箱类型" @change="handleMailTypeChange">
-                <el-option v-for="item in mailTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="SMTP服务器" prop="host">
-              <el-input v-model="form.host" placeholder="请输入SMTP服务器地址" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="端口" prop="port">
-              <el-input-number v-model="form.port" :min="1" :max="65535" controls-position="right" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="用户名" prop="username">
-              <el-input v-model="form.username" placeholder="请输入用户名/发件邮箱" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="密码" prop="password">
-              <el-input v-model="form.password" type="password" placeholder="请输入密码/授权码" show-password />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="发件人邮箱" prop="senderEmail">
-              <el-input v-model="form.senderEmail" placeholder="请输入发件人邮箱" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="发件人名称" prop="senderName">
-              <el-input v-model="form.senderName" placeholder="请输入发件人名称" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="加密方式" prop="encryption">
-              <el-select v-model="form.encryption" placeholder="请选择加密方式">
-                <el-option label="SSL" value="ssl" />
-                <el-option label="TLS" value="tls" />
-                <el-option label="无" value="none" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="是否默认" prop="isDefault">
-              <el-radio-group v-model="form.isDefault">
-                <el-radio value="1">是</el-radio>
-                <el-radio value="0">否</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="状态" prop="status">
-              <el-radio-group v-model="form.status">
-                <el-radio value="0">正常</el-radio>
-                <el-radio value="1">停用</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="启用收信" prop="enableReceive">
-              <el-radio-group v-model="form.enableReceive" @change="handleEnableReceiveChange">
-                <el-radio value="1">是</el-radio>
-                <el-radio value="0">否</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
+    <a-modal :title="title" v-model:open="open" width="700px" :maskClosable="false">
+      <a-form ref="configRef" :model="form" :rules="rules" :label-col="{ span: 5 }">
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="配置名称" name="configName">
+              <a-input v-model:value="form.configName" placeholder="请输入配置名称" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="邮箱类型" name="mailType">
+              <a-select v-model:value="form.mailType" placeholder="请选择邮箱类型" @change="handleMailTypeChange">
+                <a-select-option v-for="item in mailTypeOptions" :key="item.value" :value="item.value">{{ item.label }}</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="SMTP服务器" name="host">
+              <a-input v-model:value="form.host" placeholder="请输入SMTP服务器地址" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="端口" name="port">
+              <a-input-number v-model:value="form.port" :min="1" :max="65535" style="width: 100%" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="用户名" name="username">
+              <a-input v-model:value="form.username" placeholder="请输入用户名/发件邮箱" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="密码" name="password">
+              <a-input-password v-model:value="form.password" placeholder="请输入密码/授权码" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="发件人邮箱" name="senderEmail">
+              <a-input v-model:value="form.senderEmail" placeholder="请输入发件人邮箱" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="发件人名称" name="senderName">
+              <a-input v-model:value="form.senderName" placeholder="请输入发件人名称" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="加密方式" name="encryption">
+              <a-select v-model:value="form.encryption" placeholder="请选择加密方式">
+                <a-select-option value="ssl">SSL</a-select-option>
+                <a-select-option value="tls">TLS</a-select-option>
+                <a-select-option value="none">无</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="是否默认" name="isDefault">
+              <a-radio-group v-model:value="form.isDefault">
+                <a-radio value="1">是</a-radio>
+                <a-radio value="0">否</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="状态" name="status">
+              <a-radio-group v-model:value="form.status">
+                <a-radio value="0">正常</a-radio>
+                <a-radio value="1">停用</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="启用收信" name="enableReceive">
+              <a-radio-group v-model:value="form.enableReceive" @change="handleEnableReceiveChange">
+                <a-radio value="1">是</a-radio>
+                <a-radio value="0">否</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
         
         <!-- IMAP配置 -->
-        <el-divider content-position="left" v-if="form.enableReceive === '1'">收信配置(IMAP)</el-divider>
+        <a-divider v-if="form.enableReceive === '1'">收信配置(IMAP)</a-divider>
         <template v-if="form.enableReceive === '1'">
-          <el-row>
-            <el-col :span="12">
-              <el-form-item label="IMAP服务器" prop="imapHost">
-                <el-input v-model="form.imapHost" placeholder="如:imap.qiye.aliyun.com" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="IMAP端口" prop="imapPort">
-                <el-input-number v-model="form.imapPort" :min="1" :max="65535" controls-position="right" />
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-row>
-            <el-col :span="12">
-              <el-form-item label="IMAP加密" prop="imapEncryption">
-                <el-select v-model="form.imapEncryption" placeholder="请选择加密方式">
-                  <el-option label="SSL" value="ssl" />
-                  <el-option label="TLS" value="tls" />
-                  <el-option label="无" value="none" />
-                </el-select>
-              </el-form-item>
-            </el-col>
-          </el-row>
+          <a-row :gutter="16">
+            <a-col :span="12">
+              <a-form-item label="IMAP服务器" name="imapHost">
+                <a-input v-model:value="form.imapHost" placeholder="如:imap.qiye.aliyun.com" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="IMAP端口" name="imapPort">
+                <a-input-number v-model:value="form.imapPort" :min="1" :max="65535" style="width: 100%" />
+              </a-form-item>
+            </a-col>
+          </a-row>
+          <a-row :gutter="16">
+            <a-col :span="12">
+              <a-form-item label="IMAP加密" name="imapEncryption">
+                <a-select v-model:value="form.imapEncryption" placeholder="请选择加密方式">
+                  <a-select-option value="ssl">SSL</a-select-option>
+                  <a-select-option value="tls">TLS</a-select-option>
+                  <a-select-option value="none">无</a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
+          </a-row>
         </template>
 
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+        <a-row :gutter="16">
+          <a-col :span="24">
+            <a-form-item label="备注" name="remark" :label-col="{ span: 2 }">
+              <a-textarea v-model:value="form.remark" placeholder="请输入备注" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
       <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
+        <a-button @click="cancel">取 消</a-button>
+        <a-button type="primary" @click="submitForm">确 定</a-button>
       </template>
-    </el-dialog>
+    </a-modal>
 
     <!-- 测试邮件对话框 -->
-    <el-dialog title="发送测试邮件" v-model="testOpen" width="400px" append-to-body>
-      <el-form ref="testRef" :model="testForm" :rules="testRules" label-width="100px">
-        <el-form-item label="配置名称">
-          <el-input v-model="testForm.configName" disabled />
-        </el-form-item>
-        <el-form-item label="测试邮箱" prop="testEmail">
-          <el-input v-model="testForm.testEmail" placeholder="请输入接收测试邮件的邮箱" />
-        </el-form-item>
-      </el-form>
+    <a-modal title="发送测试邮件" v-model:open="testOpen" width="400px" :maskClosable="false">
+      <a-form ref="testRef" :model="testForm" :rules="testRules" :label-col="{ span: 6 }">
+        <a-form-item label="配置名称">
+          <a-input v-model:value="testForm.configName" disabled />
+        </a-form-item>
+        <a-form-item label="测试邮箱" name="testEmail">
+          <a-input v-model:value="testForm.testEmail" placeholder="请输入接收测试邮件的邮箱" />
+        </a-form-item>
+      </a-form>
       <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitTest" :loading="testLoading">发送测试</el-button>
-          <el-button @click="testOpen = false">取 消</el-button>
-        </div>
+        <a-button @click="testOpen = false">取 消</a-button>
+        <a-button type="primary" @click="submitTest" :loading="testLoading">发送测试</a-button>
       </template>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="MailConfig">
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined, SendOutlined } from '@ant-design/icons-vue'
 import { listMailConfig, getMailConfig, addMailConfig, updateMailConfig, delMailConfig, testMailConfig } from "@/api/system/mail"
 
 const { proxy } = getCurrentInstance()
@@ -236,13 +255,25 @@ const testOpen = ref(false)
 const loading = ref(true)
 const testLoading = ref(false)
 const showSearch = ref(true)
+const selectedRowKeys = ref([])
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 
-// 邮箱类型选项
+const columns = [
+  { title: '配置ID', dataIndex: 'configId', key: 'configId', width: 80, align: 'center' },
+  { title: '配置名称', dataIndex: 'configName', key: 'configName', ellipsis: true },
+  { title: '邮箱类型', key: 'mailType', width: 120, align: 'center' },
+  { title: '发件人邮箱', dataIndex: 'senderEmail', key: 'senderEmail', ellipsis: true },
+  { title: '发件人名称', dataIndex: 'senderName', key: 'senderName', width: 120, align: 'center' },
+  { title: '是否默认', key: 'isDefault', width: 80, align: 'center' },
+  { title: '状态', key: 'status', width: 80, align: 'center' },
+  { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160, align: 'center' },
+  { title: '操作', key: 'action', width: 200, align: 'center' }
+]
+
 const mailTypeOptions = ref([
   { value: 'aliyun', label: '阿里云企业邮箱' },
   { value: 'tencent', label: '腾讯企业邮箱' },
@@ -300,8 +331,8 @@ function getMailTypeName(type) {
 
 /** 获取邮箱类型标签样式 */
 function getMailTypeTag(type) {
-  const map = { aliyun: 'warning', tencent: 'primary', netease: 'success', custom: 'info' }
-  return map[type] || 'info'
+  const map = { aliyun: 'orange', tencent: 'blue', netease: 'green', custom: 'default' }
+  return map[type] || 'default'
 }
 
 /** 邮箱类型变更时设置默认值 */
@@ -329,20 +360,13 @@ function getMailTypeDefaults(type) {
 }
 
 /** 启用收信变更时自动填充IMAP配置 */
-function handleEnableReceiveChange(val) {
-  if (val === '1' && form.value.mailType) {
-    // 如果启用收信且IMAP配置为空,自动根据邮箱类型填充
+function handleEnableReceiveChange(e) {
+  if (e.target.value === '1' && form.value.mailType) {
     const defaults = getMailTypeDefaults(form.value.mailType)
     if (defaults) {
-      if (!form.value.imapHost) {
-        form.value.imapHost = defaults.imapHost
-      }
-      if (!form.value.imapPort) {
-        form.value.imapPort = defaults.imapPort
-      }
-      if (!form.value.imapEncryption) {
-        form.value.imapEncryption = defaults.imapEncryption
-      }
+      if (!form.value.imapHost) form.value.imapHost = defaults.imapHost
+      if (!form.value.imapPort) form.value.imapPort = defaults.imapPort
+      if (!form.value.imapEncryption) form.value.imapEncryption = defaults.imapEncryption
     }
   }
 }
@@ -390,10 +414,11 @@ function resetQuery() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.configId)
-  single.value = selection.length !== 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = !keys.length
 }
 
 /** 新增按钮操作 */
@@ -416,21 +441,19 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["configRef"].validate(valid => {
-    if (valid) {
-      if (form.value.configId != null) {
-        updateMailConfig(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addMailConfig(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["configRef"].validate().then(() => {
+    if (form.value.configId != null) {
+      updateMailConfig(form.value).then(response => {
+        proxy.$modal.msgSuccess("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addMailConfig(form.value).then(response => {
+        proxy.$modal.msgSuccess("新增成功")
+        open.value = false
+        getList()
+      })
     }
   })
 }
@@ -456,19 +479,17 @@ function handleTest(row) {
 
 /** 发送测试邮件 */
 function submitTest() {
-  proxy.$refs["testRef"].validate(valid => {
-    if (valid) {
-      testLoading.value = true
-      testMailConfig({
-        configId: testForm.value.configId,
-        remark: testForm.value.testEmail  // 临时使用remark传递测试邮箱
-      }).then(response => {
-        proxy.$modal.msgSuccess(response.msg || "测试邮件发送成功")
-        testOpen.value = false
-      }).finally(() => {
-        testLoading.value = false
-      })
-    }
+  proxy.$refs["testRef"].validate().then(() => {
+    testLoading.value = true
+    testMailConfig({
+      configId: testForm.value.configId,
+      remark: testForm.value.testEmail
+    }).then(response => {
+      proxy.$modal.msgSuccess(response.msg || "测试邮件发送成功")
+      testOpen.value = false
+    }).finally(() => {
+      testLoading.value = false
+    })
   })
 }
 

+ 138 - 151
yushu-uivue3/src/views/system/mail/inbox/index.vue

@@ -3,33 +3,39 @@
     <!-- 左侧账户与文件夹栏 -->
     <div class="mail-sidebar">
       <div class="sidebar-header">
-        <el-button type="primary" class="compose-btn" icon="Edit" @click="handleCompose">写邮件</el-button>
+        <a-button type="primary" class="compose-btn" @click="handleCompose">
+          <template #icon><EditOutlined /></template>
+          写邮件
+        </a-button>
       </div>
       
-      <el-scrollbar class="account-list">
+      <div class="account-list" style="overflow: auto; flex: 1;">
         <div v-for="config in configList" :key="config.configId" class="account-group">
           <div class="account-title" @click="toggleAccount(config.configId)">
-            <el-icon><Message /></el-icon>
+            <MailOutlined />
             <span class="text-truncate">{{ config.configName }}</span>
           </div>
           
           <div v-if="activeAccount === config.configId" class="folder-list">
             <div class="folder-item" :class="{ active: currentFolder === 'all' }" @click="switchFolder('all')">
-              <span class="folder-name"><el-icon><Files /></el-icon> 全部邮件</span>
+              <span class="folder-name"><FolderOutlined /> 全部邮件</span>
             </div>
             <div class="folder-item" :class="{ active: currentFolder === 'unread' }" @click="switchFolder('unread')">
-              <span class="folder-name"><el-icon><Bell /></el-icon> 未读邮件</span>
-              <el-badge :value="config.unreadCount" v-if="config.unreadCount > 0" class="unread-badge" type="danger" />
+              <span class="folder-name"><BellOutlined /> 未读邮件</span>
+              <a-badge :count="config.unreadCount" v-if="config.unreadCount > 0" />
             </div>
             <div class="folder-item" :class="{ active: currentFolder === 'starred' }" @click="switchFolder('starred')">
-              <span class="folder-name"><el-icon><Star /></el-icon> 星标邮件</span>
+              <span class="folder-name"><StarOutlined /> 星标邮件</span>
             </div>
           </div>
         </div>
-      </el-scrollbar>
+      </div>
       
       <div class="sidebar-footer">
-        <el-button link icon="Setting" @click="$router.push('/system/mail/config')">邮箱配置</el-button>
+        <a-button type="link" @click="$router.push('/system/mail/config')">
+          <template #icon><SettingOutlined /></template>
+          邮箱配置
+        </a-button>
       </div>
     </div>
 
@@ -37,37 +43,41 @@
     <div class="mail-list-panel">
       <div class="list-header">
         <div class="search-box">
-          <el-input 
-            v-model="queryParams.subject" 
+          <a-input 
+            v-model:value="queryParams.subject" 
             placeholder="搜索邮件主题..." 
-            prefix-icon="Search"
-            clearable
-            @keyup.enter="handleQuery"
-            @clear="handleQuery"
-          />
+            allowClear
+            @pressEnter="handleQuery"
+          >
+            <template #prefix><SearchOutlined /></template>
+          </a-input>
         </div>
         <div class="list-actions">
-          <el-tooltip content="全部标为已读" placement="bottom">
-            <el-button plain size="small" @click="markAllAsRead">全部已读</el-button>
-          </el-tooltip>
-          <el-tooltip :content="syncing ? '正在同步中...' : '同步邮件'" placement="bottom">
-            <el-button class="sync-btn" circle @click="handleSync" :disabled="syncing">
-              <el-icon :class="{ 'is-syncing': syncing }"><Refresh /></el-icon>
-            </el-button>
-          </el-tooltip>
+          <a-tooltip title="全部标为已读">
+            <a-button size="small" @click="markAllAsRead">全部已读</a-button>
+          </a-tooltip>
+          <a-tooltip :title="syncing ? '正在同步中...' : '同步邮件'">
+            <a-button class="sync-btn" shape="circle" @click="handleSync" :disabled="syncing">
+              <template #icon><ReloadOutlined :spin="syncing" /></template>
+            </a-button>
+          </a-tooltip>
         </div>
       </div>
 
       <div class="list-filter" v-if="total > 0">
         <span>共 {{ total }} 封邮件</span>
         <div class="pagination-simple">
-          <el-button link size="small" :disabled="queryParams.pageNum <= 1" @click="prevPage"><el-icon><ArrowLeft /></el-icon></el-button>
+          <a-button type="link" size="small" :disabled="queryParams.pageNum <= 1" @click="prevPage">
+            <template #icon><LeftOutlined /></template>
+          </a-button>
           <span>{{ queryParams.pageNum }}</span>
-          <el-button link size="small" :disabled="queryParams.pageNum * queryParams.pageSize >= total" @click="nextPage"><el-icon><ArrowRight /></el-icon></el-button>
+          <a-button type="link" size="small" :disabled="queryParams.pageNum * queryParams.pageSize >= total" @click="nextPage">
+            <template #icon><RightOutlined /></template>
+          </a-button>
         </div>
       </div>
 
-      <el-scrollbar class="mail-list-scroll" v-loading="loading">
+      <div class="mail-list-scroll" style="flex: 1; overflow: auto;" v-loading="loading">
         <div v-if="inboxList.length > 0">
           <div 
             v-for="mail in inboxList" 
@@ -77,9 +87,9 @@
             @click="handleMailClick(mail)"
           >
             <div class="mail-item-left">
-              <el-avatar :size="36" :style="{ backgroundColor: getAvatarColor(mail.fromName || mail.fromAddress) }">
+              <a-avatar :size="36" :style="{ backgroundColor: getAvatarColor(mail.fromName || mail.fromAddress) }">
                 {{ (mail.fromName || mail.fromAddress || '?').charAt(0).toUpperCase() }}
-              </el-avatar>
+              </a-avatar>
             </div>
             <div class="mail-item-right">
               <div class="mail-item-header">
@@ -88,10 +98,10 @@
               </div>
               <div class="mail-item-subject text-truncate">
                 <span v-if="mail.isRead === '0'" class="unread-dot"></span>
-                <el-icon v-if="mail.isStarred === '1'" class="icon-star"><StarFilled /></el-icon>
-                <el-tag v-if="mail.hasAttachment === '1'" size="small" type="info" effect="plain" class="attachment-tag">
-                  <el-icon><Paperclip /></el-icon>
-                </el-tag>
+                <StarFilled v-if="mail.isStarred === '1'" class="icon-star" />
+                <a-tag v-if="mail.hasAttachment === '1'" size="small" color="default" class="attachment-tag">
+                  <PaperClipOutlined />
+                </a-tag>
                 {{ mail.subject }}
               </div>
               <div class="mail-item-snippet text-truncate">
@@ -100,8 +110,8 @@
             </div>
           </div>
         </div>
-        <el-empty v-else description="暂无邮件" :image-size="80" />
-      </el-scrollbar>
+        <a-empty v-else description="暂无邮件" />
+      </div>
     </div>
 
     <!-- 右侧阅读窗格 -->
@@ -110,35 +120,41 @@
         <!-- 工具栏 -->
         <div class="detail-toolbar">
           <div class="toolbar-left">
-            <el-tooltip content="回复" placement="bottom">
-              <el-button plain circle icon="Position" @click="handleReply"></el-button>
-            </el-tooltip>
-            <el-tooltip content="删除" placement="bottom">
-              <el-button plain circle type="danger" icon="Delete" @click="handleDelete"></el-button>
-            </el-tooltip>
-            <el-divider direction="vertical" />
-            <el-tooltip :content="currentMail.isStarred === '1' ? '取消星标' : '星标'" placement="bottom">
-              <el-button plain circle :type="currentMail.isStarred === '1' ? 'warning' : 'default'" @click="handleToggleStar">
-                <el-icon><component :is="currentMail.isStarred === '1' ? 'StarFilled' : 'Star'" /></el-icon>
-              </el-button>
-            </el-tooltip>
-            <el-tooltip content="标为已读" placement="bottom" v-if="currentMail.isRead === '0'">
-              <el-button plain circle icon="Message" @click="handleMarkRead"></el-button>
-            </el-tooltip>
+            <a-tooltip title="回复">
+              <a-button shape="circle" @click="handleReply">
+                <template #icon><SendOutlined /></template>
+              </a-button>
+            </a-tooltip>
+            <a-tooltip title="删除">
+              <a-button shape="circle" danger @click="handleDelete">
+                <template #icon><DeleteOutlined /></template>
+              </a-button>
+            </a-tooltip>
+            <a-divider type="vertical" />
+            <a-tooltip :title="currentMail.isStarred === '1' ? '取消星标' : '星标'">
+              <a-button shape="circle" :type="currentMail.isStarred === '1' ? 'primary' : 'default'" @click="handleToggleStar">
+                <template #icon><StarFilled v-if="currentMail.isStarred === '1'" /><StarOutlined v-else /></template>
+              </a-button>
+            </a-tooltip>
+            <a-tooltip title="标为已读" v-if="currentMail.isRead === '0'">
+              <a-button shape="circle" @click="handleMarkRead">
+                <template #icon><MailOutlined /></template>
+              </a-button>
+            </a-tooltip>
           </div>
           <div class="toolbar-right">
           </div>
         </div>
 
         <!-- 邮件头 -->
-        <el-scrollbar class="detail-content-scroll">
+        <div class="detail-content-scroll" style="flex: 1; overflow: auto;">
           <div class="mail-header">
             <h1 class="mail-subject">{{ currentMail.subject }}</h1>
             <div class="mail-meta">
               <div class="sender-avatar">
-                <el-avatar :size="48" :style="{ backgroundColor: getAvatarColor(currentMail.fromName || currentMail.fromAddress) }">
+                <a-avatar :size="48" :style="{ backgroundColor: getAvatarColor(currentMail.fromName || currentMail.fromAddress) }">
                   {{ (currentMail.fromName || currentMail.fromAddress || '?').charAt(0).toUpperCase() }}
-                </el-avatar>
+                </a-avatar>
               </div>
               <div class="meta-info">
                 <div class="sender-row">
@@ -147,17 +163,17 @@
                 </div>
                 <div class="recipient-row">
                   <span>发给:{{ currentMail.toAddress }}</span>
-                  <el-popover placement="bottom" title="详细信息" :width="400" trigger="click">
-                    <template #reference>
-                      <el-button link size="small">详情 <el-icon><CaretBottom /></el-icon></el-button>
+                  <a-popover placement="bottom" title="详细信息" trigger="click">
+                    <template #content>
+                      <a-descriptions :column="1" size="small" bordered>
+                        <a-descriptions-item label="发件人">{{ currentMail.fromName }} &lt;{{ currentMail.fromAddress }}&gt;</a-descriptions-item>
+                        <a-descriptions-item label="收件人">{{ currentMail.toAddress }}</a-descriptions-item>
+                        <a-descriptions-item label="抄送" v-if="currentMail.ccAddress">{{ currentMail.ccAddress }}</a-descriptions-item>
+                        <a-descriptions-item label="时间">{{ currentMail.receiveTime }}</a-descriptions-item>
+                      </a-descriptions>
                     </template>
-                    <el-descriptions :column="1" size="small" border>
-                      <el-descriptions-item label="发件人">{{ currentMail.fromName }} &lt;{{ currentMail.fromAddress }}&gt;</el-descriptions-item>
-                      <el-descriptions-item label="收件人">{{ currentMail.toAddress }}</el-descriptions-item>
-                      <el-descriptions-item label="抄送" v-if="currentMail.ccAddress">{{ currentMail.ccAddress }}</el-descriptions-item>
-                      <el-descriptions-item label="时间">{{ currentMail.receiveTime }}</el-descriptions-item>
-                    </el-descriptions>
-                  </el-popover>
+                    <a-button type="link" size="small">详情 <DownOutlined /></a-button>
+                  </a-popover>
                 </div>
               </div>
               <div class="mail-date">
@@ -167,12 +183,12 @@
             
             <div v-if="currentMail.hasAttachment === '1' && getAttachments(currentMail).length > 0" class="mail-attachments">
               <div class="attachments-header">
-                <el-icon><Paperclip /></el-icon>
+                <PaperClipOutlined />
                 <span>附件 ({{ getAttachments(currentMail).length }})</span>
               </div>
               <div class="attachments-list">
                 <div v-for="(att, idx) in getAttachments(currentMail)" :key="idx" class="attachment-item">
-                  <el-icon><Document /></el-icon>
+                  <FileOutlined />
                   <span class="att-name">{{ att.fileName }}</span>
                   <span class="att-size">{{ formatFileSize(att.size) }}</span>
                   <span class="att-tip">请从原邮箱下载</span>
@@ -181,19 +197,19 @@
             </div>
           </div>
 
-          <el-divider />
+          <a-divider />
 
           <!-- 邮件内容 -->
           <div class="mail-body">
             <div v-if="isHtmlContent(currentMail.content)" v-html="currentMail.content" class="html-content"></div>
             <div v-else class="text-content">{{ currentMail.content }}</div>
           </div>
-        </el-scrollbar>
+        </div>
       </div>
       
       <div v-else class="empty-selection">
         <div class="empty-content">
-          <el-icon class="empty-icon"><Message /></el-icon>
+          <MailOutlined class="empty-icon" />
           <h3>选择一封邮件开始阅读</h3>
         </div>
       </div>
@@ -203,13 +219,12 @@
 
 <script setup name="MailInbox">
 import { 
-  Message, Star, StarFilled, Delete, Refresh, Search, 
-  Files, Bell, Edit, Paperclip, ArrowLeft, ArrowRight,
-  Position, CaretBottom, Setting, Document, Download
-} from '@element-plus/icons-vue'
+  MailOutlined, StarOutlined, StarFilled, DeleteOutlined, ReloadOutlined, SearchOutlined, 
+  FileOutlined, BellOutlined, EditOutlined, PaperClipOutlined, LeftOutlined, RightOutlined,
+  SendOutlined, DownOutlined, SettingOutlined, FolderOutlined
+} from '@ant-design/icons-vue'
 import { getEnabledConfigList, listMailInbox, getMailInbox, toggleStar, delMailInbox, syncMails, getUnreadCount, markAsRead, batchMarkAsRead, markAllAsReadByConfig } from "@/api/system/mail"
 import { useRouter } from 'vue-router'
-import request from '@/utils/request'
 
 const router = useRouter()
 const { proxy } = getCurrentInstance()
@@ -234,7 +249,7 @@ const queryParams = reactive({
 })
 
 // 头像颜色
-const avatarColors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#36cfc9', '#9254de', '#ff7875']
+const avatarColors = ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#8c8c8c', '#13c2c2', '#722ed1', '#ff7875']
 
 /** 获取头像颜色 */
 function getAvatarColor(name) {
@@ -249,17 +264,16 @@ function getAvatarColor(name) {
 /** 提取纯文本摘要 */
 function getSnippet(content) {
   if (!content) return ''
-  // 使用正则去除HTML标签,避免触发图片加载
   let text = content
-    .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '') // 移除style标签
-    .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '') // 移除script标签
-    .replace(/<[^>]+>/g, ' ') // 移除所有HTML标签
-    .replace(/&nbsp;/gi, ' ') // 替换&nbsp;
+    .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
+    .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
+    .replace(/<[^>]+>/g, ' ')
+    .replace(/&nbsp;/gi, ' ')
     .replace(/&lt;/gi, '<')
     .replace(/&gt;/gi, '>')
     .replace(/&amp;/gi, '&')
     .replace(/&quot;/gi, '"')
-    .replace(/\s+/g, ' ') // 压缩空格
+    .replace(/\s+/g, ' ')
     .trim()
   return text.length > 50 ? text.substring(0, 50) + '...' : text
 }
@@ -301,7 +315,6 @@ function formatFileSize(size) {
 /** 判断内容是否为HTML */
 function isHtmlContent(content) {
   if (!content) return false
-  // 检测是否包含常见HTML标签
   return /<[a-z][\s\S]*>/i.test(content)
 }
 
@@ -317,7 +330,6 @@ function handleBrokenImages() {
   })
 }
 
-// 监听邮件变化,处理图片
 watch(currentMail, (val) => {
   if (val) {
     handleBrokenImages()
@@ -329,13 +341,10 @@ function loadConfigList() {
   getEnabledConfigList().then(res => {
     configList.value = (res.data || []).map(c => ({...c, unreadCount: 0}))
     if (configList.value.length > 0) {
-      // 默认选中第一个
       if (!activeAccount.value) {
         toggleAccount(configList.value[0].configId)
-        // 首次进入页面自动同步邮件
         setTimeout(() => handleSync(), 300)
       }
-      // 加载所有账户的未读数
       configList.value.forEach(c => loadUnreadCount(c))
     }
   })
@@ -353,7 +362,7 @@ function toggleAccount(configId) {
   if (activeAccount.value === configId) return
   activeAccount.value = configId
   queryParams.configId = configId
-  currentFolder.value = 'all' // 重置为全部
+  currentFolder.value = 'all'
   resetFilters()
   getList()
 }
@@ -391,7 +400,6 @@ function getList() {
     inboxList.value = res.rows
     total.value = res.total
     loading.value = false
-    // 如果当前选中的邮件不在新列表中,清空选中
     if (currentMail.value && !inboxList.value.find(m => m.inboxId === currentMail.value.inboxId)) {
       currentMail.value = null
     }
@@ -418,29 +426,23 @@ function nextPage() {
 
 /** 同步邮件 */
 function handleSync() {
-  // 如果正在同步中,阻止再次点击
   if (syncing.value) {
     proxy.$modal.msgWarning('正在同步中,请稍后...')
     return
   }
   
   if (!activeAccount.value) {
-    console.log('同步失败: activeAccount 未设置')
     proxy.$modal.msgWarning('请先选择邮箱账户')
     return
   }
-  console.log('开始同步邮件, configId:', activeAccount.value)
   syncing.value = true
   syncMails(activeAccount.value).then(res => {
-    console.log('同步结果:', res)
     const count = res.data || 0
     proxy.$modal.msgSuccess(`同步完成,新增 ${count} 封邮件`)
     getList()
-    // 更新未读数
     const config = configList.value.find(c => c.configId === activeAccount.value)
     if (config) loadUnreadCount(config)
   }).catch(err => {
-    console.error('同步失败:', err)
     proxy.$modal.msgError('同步失败: ' + (err.message || '未知错误'))
   }).finally(() => {
     syncing.value = false
@@ -451,16 +453,12 @@ function handleSync() {
 function handleMailClick(mail) {
   if (currentMail.value && currentMail.value.inboxId === mail.inboxId) return
   
-  // 先从列表数据快速展示
   currentMail.value = mail
   
-  // 获取详情(更新已读状态)
   getMailInbox(mail.inboxId).then(res => {
     currentMail.value = res.data
-    // 本地更新列表状态
     if (mail.isRead === '0') {
       mail.isRead = '1'
-      // 更新左侧未读数
       const config = configList.value.find(c => c.configId === activeAccount.value)
       if (config && config.unreadCount > 0) config.unreadCount--
     }
@@ -487,7 +485,6 @@ function handleMarkRead() {
     const item = inboxList.value.find(m => m.inboxId === currentMail.value.inboxId)
     if (item) {
       item.isRead = '1'
-      // 更新未读数
       const config = configList.value.find(c => c.configId === activeAccount.value)
       if (config && config.unreadCount > 0) config.unreadCount--
     }
@@ -515,20 +512,15 @@ function markAllAsRead() {
   }
   
   proxy.$modal.confirm(`确定要将 ${config.unreadCount} 封未读邮件标为已读吗?`).then(() => {
-    // 调用后端接口直接标记当前账户的所有未读邮件
     return markAllAsReadByConfig(activeAccount.value)
   }).then(() => {
     proxy.$modal.msgSuccess('已全部标为已读')
-    // 更新列表
     inboxList.value.forEach(m => {
       if (m.isRead === '0') m.isRead = '1'
     })
-    // 更新未读数
     config.unreadCount = 0
     currentMail.value = null
-  }).catch(() => {
-    // 用户取消
-  })
+  }).catch(() => {})
 }
 
 /** 写邮件 */
@@ -539,7 +531,6 @@ function handleCompose() {
 /** 回复邮件 */
 function handleReply() {
   if (!currentMail.value) return
-  // 构造引用原文
   const mail = currentMail.value
   const quoteContent = `<br/><br/>---原始邮件---<br/>发件人: ${mail.fromName || mail.fromAddress}<br/>时间: ${mail.receiveTime}<br/>主题: ${mail.subject}<br/><br/>${mail.content || ''}`
   
@@ -553,21 +544,19 @@ function handleReply() {
   })
 }
 
-// 初始化
 loadConfigList()
 </script>
 
 <style scoped>
 .mail-container {
   display: flex;
-  height: calc(100vh - 84px); /* 减去顶部导航高度 */
+  height: calc(100vh - 84px);
   background-color: #f5f7fa;
   border: 1px solid #e6e6e6;
   margin: 0;
   overflow: hidden;
 }
 
-/* 左侧边栏 */
 .mail-sidebar {
   width: 240px;
   background-color: #fff;
@@ -585,10 +574,6 @@ loadConfigList()
   width: 100%;
 }
 
-.account-list {
-  flex: 1;
-}
-
 .account-group {
   margin-bottom: 8px;
 }
@@ -604,6 +589,7 @@ loadConfigList()
   gap: 8px;
   background-color: #fafafa;
 }
+
 .account-title:hover {
   background-color: #f0f0f0;
 }
@@ -621,14 +607,17 @@ loadConfigList()
   color: #606266;
   font-size: 14px;
 }
+
 .folder-item:hover {
   background-color: #f5f7fa;
 }
+
 .folder-item.active {
-  background-color: #ecf5ff;
-  color: #409eff;
+  background-color: #e6f7ff;
+  color: #1890ff;
   font-weight: 500;
 }
+
 .folder-name {
   display: flex;
   align-items: center;
@@ -641,7 +630,6 @@ loadConfigList()
   text-align: center;
 }
 
-/* 中间邮件列表 */
 .mail-list-panel {
   width: 320px;
   background-color: #fff;
@@ -656,6 +644,7 @@ loadConfigList()
   display: flex;
   gap: 8px;
 }
+
 .search-box {
   flex: 1;
 }
@@ -666,29 +655,17 @@ loadConfigList()
   align-items: center;
 }
 
-.is-syncing {
-  animation: rotate 1s linear infinite;
-}
-@keyframes rotate {
-  from { transform: rotate(0deg); }
-  to { transform: rotate(360deg); }
-}
-
 .list-filter {
   padding: 8px 12px;
   background-color: #f9fafe;
   border-bottom: 1px solid #f0f0f0;
   font-size: 12px;
-  color: #909399;
+  color: #999;
   display: flex;
   justify-content: space-between;
   align-items: center;
 }
 
-.mail-list-scroll {
-  flex: 1;
-}
-
 .mail-item {
   padding: 12px;
   border-bottom: 1px solid #f0f0f0;
@@ -697,28 +674,30 @@ loadConfigList()
   gap: 12px;
   transition: background-color 0.2s;
 }
+
 .mail-item:hover {
   background-color: #f5f7fa;
 }
+
 .mail-item.active {
-  background-color: #ecf5ff;
-  border-left: 3px solid #409eff;
+  background-color: #e6f7ff;
+  border-left: 3px solid #1890ff;
 }
+
 .mail-item.unread {
   background-color: #f0f9ff;
-  border-left: 3px solid #67c23a;
+  border-left: 3px solid #52c41a;
 }
+
 .mail-item.unread .mail-item-subject {
   font-weight: 700;
   color: #303133;
 }
+
 .mail-item.unread .sender-name {
   font-weight: 700;
   color: #303133;
 }
-.mail-item.unread .mail-item-header {
-  font-weight: 600;
-}
 
 .mail-item-right {
   flex: 1;
@@ -734,12 +713,14 @@ loadConfigList()
   font-size: 13px;
   align-items: center;
 }
+
 .sender-name {
   color: #606266;
   max-width: 140px;
 }
+
 .mail-time {
-  color: #909399;
+  color: #999;
   font-size: 12px;
 }
 
@@ -750,8 +731,9 @@ loadConfigList()
   align-items: center;
   gap: 4px;
 }
+
 .icon-star {
-  color: #e6a23c;
+  color: #faad14;
   font-size: 14px;
 }
 
@@ -760,14 +742,14 @@ loadConfigList()
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  background-color: #409eff;
+  background-color: #1890ff;
   margin-right: 4px;
   flex-shrink: 0;
 }
 
 .mail-item-snippet {
   font-size: 12px;
-  color: #909399;
+  color: #999;
 }
 
 .text-truncate {
@@ -776,7 +758,6 @@ loadConfigList()
   white-space: nowrap;
 }
 
-/* 右侧详情 */
 .mail-detail-panel {
   flex: 1;
   background-color: #fff;
@@ -797,17 +778,13 @@ loadConfigList()
   justify-content: space-between;
   background-color: #fff;
 }
+
 .toolbar-left {
   display: flex;
   gap: 12px;
   align-items: center;
 }
 
-.detail-content-scroll {
-  flex: 1;
-  padding: 0;
-}
-
 .mail-header {
   padding: 24px 32px 16px;
 }
@@ -837,24 +814,26 @@ loadConfigList()
   font-size: 14px;
   color: #303133;
 }
+
 .sender-name {
   font-weight: 600;
   margin-right: 8px;
 }
+
 .sender-email {
-  color: #909399;
+  color: #999;
 }
 
 .recipient-row {
   font-size: 12px;
-  color: #909399;
+  color: #999;
   display: flex;
   align-items: center;
 }
 
 .mail-date {
   font-size: 12px;
-  color: #909399;
+  color: #999;
 }
 
 .mail-attachments {
@@ -864,6 +843,7 @@ loadConfigList()
   border-radius: 8px;
   border: 1px solid #e6e6e6;
 }
+
 .attachments-header {
   display: flex;
   align-items: center;
@@ -872,11 +852,13 @@ loadConfigList()
   color: #606266;
   margin-bottom: 10px;
 }
+
 .attachments-list {
   display: flex;
   flex-direction: column;
   gap: 8px;
 }
+
 .attachment-item {
   display: flex;
   align-items: center;
@@ -886,6 +868,7 @@ loadConfigList()
   border-radius: 4px;
   border: 1px solid #e6e6e6;
 }
+
 .attachment-item .att-name {
   flex: 1;
   font-size: 13px;
@@ -894,9 +877,10 @@ loadConfigList()
   text-overflow: ellipsis;
   white-space: nowrap;
 }
+
 .attachment-item .att-size {
   font-size: 12px;
-  color: #909399;
+  color: #999;
 }
 
 .mail-body {
@@ -909,11 +893,12 @@ loadConfigList()
 .html-content :deep(img) {
   max-width: 100%;
 }
-/* 隐藏加载失败的图片 */
+
 .html-content :deep(img[src=""]),
 .html-content :deep(img:not([src])) {
   display: none;
 }
+
 .html-content :deep(blockquote) {
   border-left: 2px solid #ccc;
   margin-left: 0;
@@ -932,13 +917,15 @@ loadConfigList()
   align-items: center;
   justify-content: center;
 }
+
 .empty-content {
   text-align: center;
-  color: #909399;
+  color: #999;
 }
+
 .empty-icon {
   font-size: 64px;
   margin-bottom: 16px;
-  color: #dcdfe6;
+  color: #d9d9d9;
 }
 </style>

+ 121 - 88
yushu-uivue3/src/views/system/mail/log/index.vue

@@ -1,100 +1,120 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="收件人" prop="receiver">
-        <el-input v-model="queryParams.receiver" placeholder="请输入收件人" clearable style="width: 200px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="邮件主题" prop="subject">
-        <el-input v-model="queryParams.subject" placeholder="请输入邮件主题" clearable style="width: 200px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="发送状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
-          <el-option label="成功" value="0" />
-          <el-option label="失败" value="1" />
-          <el-option label="发送中" value="2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="发送时间" style="width: 308px">
-        <el-date-picker
-          v-model="dateRange"
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch" class="mb8">
+      <a-form-item label="收件人" name="receiver">
+        <a-input v-model:value="queryParams.receiver" placeholder="请输入收件人" allowClear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="邮件主题" name="subject">
+        <a-input v-model:value="queryParams.subject" placeholder="请输入邮件主题" allowClear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="发送状态" name="status">
+        <a-select v-model:value="queryParams.status" placeholder="请选择状态" allowClear style="width: 200px">
+          <a-select-option value="0">成功</a-select-option>
+          <a-select-option value="1">失败</a-select-option>
+          <a-select-option value="2">发送中</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="发送时间" style="width: 308px">
+        <a-range-picker
+          v-model:value="dateRange"
           value-format="YYYY-MM-DD"
-          type="daterange"
-          range-separator="-"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-        ></el-date-picker>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:log:remove']">删除</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" @click="handleClean" v-hasPermi="['system:mail:log:remove']">清空</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:mail:log:export']">导出</el-button>
-      </el-col>
+          format="YYYY-MM-DD"
+        />
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
+
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:log:remove']">
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger @click="handleClean" v-hasPermi="['system:mail:log:remove']">
+          <template #icon><DeleteOutlined /></template>
+          清空
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button @click="handleExport" v-hasPermi="['system:mail:log:export']">
+          <template #icon><DownloadOutlined /></template>
+          导出
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="logList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="日志ID" align="center" prop="logId" width="80" />
-      <el-table-column label="配置名称" align="center" prop="configName" width="120" :show-overflow-tooltip="true" />
-      <el-table-column label="发件人" align="center" prop="sender" :show-overflow-tooltip="true" />
-      <el-table-column label="收件人" align="center" prop="receiver" :show-overflow-tooltip="true" />
-      <el-table-column label="邮件主题" align="center" prop="subject" :show-overflow-tooltip="true" />
-      <el-table-column label="发送状态" align="center" prop="status" width="100">
-        <template #default="scope">
-          <el-tag :type="getStatusType(scope.row.status)">{{ getStatusName(scope.row.status) }}</el-tag>
+    </a-row>
+
+    <a-table 
+      :loading="loading" 
+      :dataSource="logList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="logId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'status'">
+          <a-tag :color="getStatusColor(record.status)">{{ getStatusName(record.status) }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="发送时间" align="center" prop="sendTime" width="160" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
-        <template #default="scope">
-          <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['system:mail:log:query']">详情</el-button>
-          <el-button link type="primary" icon="RefreshRight" @click="handleResend(scope.row)" v-if="scope.row.status === '1'" v-hasPermi="['system:mail:log:resend']">重发</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:mail:log:remove']">删除</el-button>
+        <template v-else-if="column.key === 'action'">
+          <a-button type="link" size="small" @click="handleView(record)" v-hasPermi="['system:mail:log:query']">
+            <template #icon><EyeOutlined /></template>
+            详情
+          </a-button>
+          <a-button type="link" size="small" @click="handleResend(record)" v-if="record.status === '1'" v-hasPermi="['system:mail:log:resend']">
+            <template #icon><ReloadOutlined /></template>
+            重发
+          </a-button>
+          <a-button type="link" danger size="small" @click="handleDelete(record)" v-hasPermi="['system:mail:log:remove']">
+            <template #icon><DeleteOutlined /></template>
+            删除
+          </a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
 
     <!-- 邮件详情对话框 -->
-    <el-dialog title="邮件详情" v-model="detailOpen" width="800px" append-to-body>
-      <el-descriptions :column="2" border>
-        <el-descriptions-item label="日志ID">{{ detail.logId }}</el-descriptions-item>
-        <el-descriptions-item label="配置名称">{{ detail.configName }}</el-descriptions-item>
-        <el-descriptions-item label="发件人">{{ detail.sender }}</el-descriptions-item>
-        <el-descriptions-item label="收件人">{{ detail.receiver }}</el-descriptions-item>
-        <el-descriptions-item label="抄送" v-if="detail.cc">{{ detail.cc }}</el-descriptions-item>
-        <el-descriptions-item label="密送" v-if="detail.bcc">{{ detail.bcc }}</el-descriptions-item>
-        <el-descriptions-item label="邮件主题" :span="2">{{ detail.subject }}</el-descriptions-item>
-        <el-descriptions-item label="内容类型">{{ detail.contentType === 'html' ? 'HTML' : '纯文本' }}</el-descriptions-item>
-        <el-descriptions-item label="发送状态">
-          <el-tag :type="getStatusType(detail.status)">{{ getStatusName(detail.status) }}</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="发送时间">{{ detail.sendTime }}</el-descriptions-item>
-        <el-descriptions-item label="创建时间">{{ detail.createTime }}</el-descriptions-item>
-        <el-descriptions-item label="错误信息" :span="2" v-if="detail.errorMsg">
-          <span style="color: #f56c6c">{{ detail.errorMsg }}</span>
-        </el-descriptions-item>
-      </el-descriptions>
-      <el-divider content-position="left">邮件内容</el-divider>
+    <a-modal title="邮件详情" v-model:open="detailOpen" width="800px" :footer="null">
+      <a-descriptions :column="2" bordered>
+        <a-descriptions-item label="日志ID">{{ detail.logId }}</a-descriptions-item>
+        <a-descriptions-item label="配置名称">{{ detail.configName }}</a-descriptions-item>
+        <a-descriptions-item label="发件人">{{ detail.sender }}</a-descriptions-item>
+        <a-descriptions-item label="收件人">{{ detail.receiver }}</a-descriptions-item>
+        <a-descriptions-item label="抄送" v-if="detail.cc">{{ detail.cc }}</a-descriptions-item>
+        <a-descriptions-item label="密送" v-if="detail.bcc">{{ detail.bcc }}</a-descriptions-item>
+        <a-descriptions-item label="邮件主题" :span="2">{{ detail.subject }}</a-descriptions-item>
+        <a-descriptions-item label="内容类型">{{ detail.contentType === 'html' ? 'HTML' : '纯文本' }}</a-descriptions-item>
+        <a-descriptions-item label="发送状态">
+          <a-tag :color="getStatusColor(detail.status)">{{ getStatusName(detail.status) }}</a-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="发送时间">{{ detail.sendTime }}</a-descriptions-item>
+        <a-descriptions-item label="创建时间">{{ detail.createTime }}</a-descriptions-item>
+        <a-descriptions-item label="错误信息" :span="2" v-if="detail.errorMsg">
+          <span style="color: #f5222d">{{ detail.errorMsg }}</span>
+        </a-descriptions-item>
+      </a-descriptions>
+      <a-divider>邮件内容</a-divider>
       <div class="content-container" v-if="detail.contentType === 'html'" v-html="detail.content"></div>
       <div class="content-container" v-else>{{ detail.content }}</div>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="MailLog">
+import { SearchOutlined, ReloadOutlined, DeleteOutlined, DownloadOutlined, EyeOutlined } from '@ant-design/icons-vue'
 import { listMailLog, getMailLog, delMailLog, cleanMailLog, resendMail } from "@/api/system/mail"
 
 const { proxy } = getCurrentInstance()
@@ -103,12 +123,24 @@ const logList = ref([])
 const detailOpen = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
+const selectedRowKeys = ref([])
 const ids = ref([])
 const multiple = ref(true)
 const total = ref(0)
 const dateRange = ref([])
 const detail = ref({})
 
+const columns = [
+  { title: '日志ID', dataIndex: 'logId', key: 'logId', width: 80, align: 'center' },
+  { title: '配置名称', dataIndex: 'configName', key: 'configName', width: 120, ellipsis: true },
+  { title: '发件人', dataIndex: 'sender', key: 'sender', ellipsis: true },
+  { title: '收件人', dataIndex: 'receiver', key: 'receiver', ellipsis: true },
+  { title: '邮件主题', dataIndex: 'subject', key: 'subject', ellipsis: true },
+  { title: '发送状态', key: 'status', width: 100, align: 'center' },
+  { title: '发送时间', dataIndex: 'sendTime', key: 'sendTime', width: 160, align: 'center' },
+  { title: '操作', key: 'action', width: 200, align: 'center' }
+]
+
 const data = reactive({
   queryParams: {
     pageNum: 1,
@@ -137,10 +169,10 @@ function getStatusName(status) {
   return map[status] || status
 }
 
-/** 获取状态标签类型 */
-function getStatusType(status) {
-  const map = { '0': 'success', '1': 'danger', '2': 'warning' }
-  return map[status] || 'info'
+/** 获取状态颜色 */
+function getStatusColor(status) {
+  const map = { '0': 'green', '1': 'red', '2': 'orange' }
+  return map[status] || 'default'
 }
 
 /** 搜索按钮操作 */
@@ -157,9 +189,10 @@ function resetQuery() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.logId)
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  multiple.value = !keys.length
 }
 
 /** 查看详情 */

+ 82 - 81
yushu-uivue3/src/views/system/mail/send/index.vue

@@ -1,93 +1,99 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="20">
+    <a-row :gutter="20">
       <!-- 左侧表单 -->
-      <el-col :span="12">
-        <el-card class="mail-compose-card">
-          <template #header>
-            <div class="card-header">
-              <span>发送邮件</span>
-            </div>
-          </template>
+      <a-col :span="12">
+        <a-card class="mail-compose-card">
+          <template #title>发送邮件</template>
           
-          <el-form ref="mailRef" :model="mailForm" :rules="rules" label-width="100px">
-            <el-form-item label="发件配置" prop="configId">
-              <el-select v-model="mailForm.configId" placeholder="请选择发件配置" style="width: 100%">
-                <el-option v-for="item in configList" :key="item.configId" :label="item.configName + ' (' + item.senderEmail + ')'" :value="item.configId" />
-              </el-select>
-            </el-form-item>
+          <a-form ref="mailRef" :model="mailForm" :rules="rules" :label-col="{ span: 5 }">
+            <a-form-item label="发件配置" name="configId">
+              <a-select v-model:value="mailForm.configId" placeholder="请选择发件配置" style="width: 100%">
+                <a-select-option v-for="item in configList" :key="item.configId" :value="item.configId">
+                  {{ item.configName + ' (' + item.senderEmail + ')' }}
+                </a-select-option>
+              </a-select>
+            </a-form-item>
             
-            <el-form-item label="收件人" prop="to">
-              <el-input v-model="mailForm.to" placeholder="多个收件人用逗号分隔" />
-            </el-form-item>
+            <a-form-item label="收件人" name="to">
+              <a-input v-model:value="mailForm.to" placeholder="多个收件人用逗号分隔" />
+            </a-form-item>
             
-            <el-form-item label="抄送">
-              <el-input v-model="mailForm.cc" placeholder="多个抄送人用逗号分隔(可选)" />
-            </el-form-item>
+            <a-form-item label="抄送">
+              <a-input v-model:value="mailForm.cc" placeholder="多个抄送人用逗号分隔(可选)" />
+            </a-form-item>
             
-            <el-form-item label="密送">
-              <el-input v-model="mailForm.bcc" placeholder="多个密送人用逗号分隔(可选)" />
-            </el-form-item>
+            <a-form-item label="密送">
+              <a-input v-model:value="mailForm.bcc" placeholder="多个密送人用逗号分隔(可选)" />
+            </a-form-item>
 
-            <el-divider content-position="left">邮件内容</el-divider>
+            <a-divider>邮件内容</a-divider>
 
-            <el-form-item label="发送方式">
-              <el-radio-group v-model="sendMode" @change="handleSendModeChange">
-                <el-radio value="template">使用模板</el-radio>
-                <el-radio value="custom">自定义内容</el-radio>
-              </el-radio-group>
-            </el-form-item>
+            <a-form-item label="发送方式">
+              <a-radio-group v-model:value="sendMode" @change="handleSendModeChange">
+                <a-radio value="template">使用模板</a-radio>
+                <a-radio value="custom">自定义内容</a-radio>
+              </a-radio-group>
+            </a-form-item>
 
             <!-- 模板模式 -->
             <template v-if="sendMode === 'template'">
-              <el-form-item label="选择模板" prop="templateId">
-                <el-select v-model="selectedTemplateId" placeholder="请选择邮件模板" style="width: 100%" @change="handleTemplateChange">
-                  <el-option v-for="item in templateList" :key="item.templateId" :label="item.templateName" :value="item.templateId" />
-                </el-select>
-              </el-form-item>
+              <a-form-item label="选择模板" name="templateId">
+                <a-select v-model:value="selectedTemplateId" placeholder="请选择邮件模板" style="width: 100%" @change="handleTemplateChange">
+                  <a-select-option v-for="item in templateList" :key="item.templateId" :value="item.templateId">
+                    {{ item.templateName }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
 
               <!-- 模板变量填写 -->
               <template v-if="templateVariables.length > 0">
-                <el-divider content-position="left">模板参数</el-divider>
-                <el-form-item v-for="varName in templateVariables" :key="varName" :label="varName">
-                  <el-input v-model="variableValues[varName]" :placeholder="'请输入 ' + varName" @input="updatePreview" />
-                </el-form-item>
+                <a-divider>模板参数</a-divider>
+                <a-form-item v-for="varName in templateVariables" :key="varName" :label="varName">
+                  <a-input v-model:value="variableValues[varName]" :placeholder="'请输入 ' + varName" @input="updatePreview" />
+                </a-form-item>
               </template>
             </template>
 
             <!-- 自定义内容模式 -->
             <template v-else>
-              <el-form-item label="邮件主题" prop="subject">
-                <el-input v-model="mailForm.subject" placeholder="请输入邮件主题" />
-              </el-form-item>
-
-              <el-form-item label="内容格式">
-                <el-radio-group v-model="mailForm.isHtml">
-                  <el-radio :value="true">HTML</el-radio>
-                  <el-radio :value="false">纯文本</el-radio>
-                </el-radio-group>
-              </el-form-item>
+              <a-form-item label="邮件主题" name="subject">
+                <a-input v-model:value="mailForm.subject" placeholder="请输入邮件主题" />
+              </a-form-item>
+
+              <a-form-item label="内容格式">
+                <a-radio-group v-model:value="mailForm.isHtml">
+                  <a-radio :value="true">HTML</a-radio>
+                  <a-radio :value="false">纯文本</a-radio>
+                </a-radio-group>
+              </a-form-item>
               
-              <el-form-item label="邮件内容" prop="content">
-                <el-input v-model="mailForm.content" type="textarea" :rows="10" placeholder="请输入邮件内容" />
-              </el-form-item>
+              <a-form-item label="邮件内容" name="content">
+                <a-textarea v-model:value="mailForm.content" :rows="10" placeholder="请输入邮件内容" />
+              </a-form-item>
             </template>
             
-            <el-form-item>
-              <el-button type="primary" icon="Position" :loading="sending" @click="handleSend">发送邮件</el-button>
-              <el-button icon="Refresh" @click="resetForm">重置</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-      </el-col>
+            <a-form-item>
+              <a-button type="primary" :loading="sending" @click="handleSend">
+                <template #icon><SendOutlined /></template>
+                发送邮件
+              </a-button>
+              <a-button style="margin-left: 8px" @click="resetForm">
+                <template #icon><ReloadOutlined /></template>
+                重置
+              </a-button>
+            </a-form-item>
+          </a-form>
+        </a-card>
+      </a-col>
 
       <!-- 右侧预览 -->
-      <el-col :span="12">
-        <el-card class="preview-card">
-          <template #header>
+      <a-col :span="12">
+        <a-card class="preview-card">
+          <template #title>
             <div class="card-header">
               <span>邮件预览</span>
-              <el-button v-if="sendMode === 'template' && selectedTemplateId" type="primary" link @click="refreshPreview">刷新预览</el-button>
+              <a-button v-if="sendMode === 'template' && selectedTemplateId" type="link" @click="refreshPreview">刷新预览</a-button>
             </div>
           </template>
           
@@ -95,18 +101,19 @@
             <div class="preview-subject">
               <strong>主题:</strong>{{ previewSubject }}
             </div>
-            <el-divider />
+            <a-divider />
             <div class="preview-body" v-if="mailForm.isHtml || (currentTemplate && currentTemplate.contentType === 'html')" v-html="previewContent"></div>
             <div class="preview-body text-content" v-else>{{ previewContent }}</div>
           </div>
-          <el-empty v-else description="请填写内容或选择模板后查看预览" />
-        </el-card>
-      </el-col>
-    </el-row>
+          <a-empty v-else description="请填写内容或选择模板后查看预览" />
+        </a-card>
+      </a-col>
+    </a-row>
   </div>
 </template>
 
 <script setup name="MailSend">
+import { SendOutlined, ReloadOutlined } from '@ant-design/icons-vue'
 import { getEnabledConfigList, getTemplateSelectList, getMailTemplate, sendMail, sendMailByTemplate } from "@/api/system/mail"
 import { useRoute } from 'vue-router'
 
@@ -120,9 +127,8 @@ const currentTemplate = ref(null)
 const templateVariables = ref([])
 const variableValues = ref({})
 const sending = ref(false)
-const sendMode = ref('template') // template 或 custom
+const sendMode = ref('template')
 
-// 预览内容
 const previewSubject = ref('')
 const previewContent = ref('')
 
@@ -188,11 +194,9 @@ function handleTemplateChange(templateId) {
   }
   getMailTemplate(templateId).then(res => {
     currentTemplate.value = res.data
-    // 解析模板变量
     if (res.data.variables) {
       try {
         templateVariables.value = JSON.parse(res.data.variables)
-        // 初始化变量值
         variableValues.value = {}
         templateVariables.value.forEach(v => {
           variableValues.value[v] = ''
@@ -218,7 +222,6 @@ function updatePreview() {
   let subject = currentTemplate.value.subject
   let content = currentTemplate.value.content
   
-  // 替换变量
   for (const [key, value] of Object.entries(variableValues.value)) {
     const regex = new RegExp('\\$\\{' + key + '\\}', 'g')
     const replacement = value || '<span style="color:#999">[' + key + ']</span>'
@@ -235,7 +238,6 @@ function refreshPreview() {
   updatePreview()
 }
 
-/** 监听自定义内容变化 */
 watch(() => mailForm.value.subject, () => {
   if (sendMode.value === 'custom') {
     previewSubject.value = mailForm.value.subject
@@ -250,7 +252,6 @@ watch(() => mailForm.value.content, () => {
 
 /** 发送邮件 */
 function handleSend() {
-  // 基础验证
   if (!mailForm.value.configId) {
     proxy.$modal.msgWarning('请选择发件配置')
     return
@@ -261,12 +262,10 @@ function handleSend() {
   }
 
   if (sendMode.value === 'template') {
-    // 模板发送
     if (!selectedTemplateId.value) {
       proxy.$modal.msgWarning('请选择邮件模板')
       return
     }
-    // 检查必填变量
     const emptyVars = templateVariables.value.filter(v => !variableValues.value[v])
     if (emptyVars.length > 0) {
       proxy.$modal.msgWarning('请填写模板参数:' + emptyVars.join(', '))
@@ -286,7 +285,6 @@ function handleSend() {
       sending.value = false
     })
   } else {
-    // 自定义内容发送
     if (!mailForm.value.subject) {
       proxy.$modal.msgWarning('请输入邮件主题')
       return
@@ -325,13 +323,11 @@ function resetForm() {
   previewContent.value = ''
 }
 
-// 初始化
 loadConfigList()
 loadTemplateList()
 
-// 如果有URL参数(如回复邮件),填充表单
 if (route.query.to || route.query.subject || route.query.content) {
-  sendMode.value = 'custom' // 切换到自定义模式
+  sendMode.value = 'custom'
   if (route.query.to) {
     mailForm.value.to = route.query.to
   }
@@ -349,6 +345,7 @@ if (route.query.to || route.query.subject || route.query.content) {
   height: calc(100vh - 140px);
   overflow: auto;
 }
+
 .card-header {
   display: flex;
   justify-content: space-between;
@@ -356,17 +353,21 @@ if (route.query.to || route.query.subject || route.query.content) {
   font-size: 16px;
   font-weight: bold;
 }
+
 .preview-content {
   padding: 10px;
 }
+
 .preview-subject {
   font-size: 16px;
   padding: 10px 0;
 }
+
 .preview-body {
   padding: 10px 0;
   line-height: 1.8;
 }
+
 .text-content {
   white-space: pre-wrap;
   font-family: inherit;

+ 167 - 137
yushu-uivue3/src/views/system/mail/template/index.vue

@@ -1,141 +1,161 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="模板名称" prop="templateName">
-        <el-input v-model="queryParams.templateName" placeholder="请输入模板名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="模板代码" prop="templateCode">
-        <el-input v-model="queryParams.templateCode" placeholder="请输入模板代码" clearable style="width: 200px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
-          <el-option label="正常" value="0" />
-          <el-option label="停用" value="1" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch" class="mb8">
+      <a-form-item label="模板名称" name="templateName">
+        <a-input v-model:value="queryParams.templateName" placeholder="请输入模板名称" allowClear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="模板代码" name="templateCode">
+        <a-input v-model:value="queryParams.templateCode" placeholder="请输入模板代码" allowClear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="状态" name="status">
+        <a-select v-model:value="queryParams.status" placeholder="请选择状态" allowClear style="width: 200px">
+          <a-select-option value="0">正常</a-select-option>
+          <a-select-option value="1">停用</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
 
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:mail:template:add']">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:mail:template:edit']">修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:template:remove']">删除</el-button>
-      </el-col>
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button type="primary" @click="handleAdd" v-hasPermi="['system:mail:template:add']">
+          <template #icon><PlusOutlined /></template>
+          新增
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:mail:template:edit']">
+          <template #icon><EditOutlined /></template>
+          修改
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:mail:template:remove']">
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+    </a-row>
 
-    <el-table v-loading="loading" :data="templateList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="模板ID" align="center" prop="templateId" width="80" />
-      <el-table-column label="模板名称" align="center" prop="templateName" :show-overflow-tooltip="true" />
-      <el-table-column label="模板代码" align="center" prop="templateCode" :show-overflow-tooltip="true" />
-      <el-table-column label="邮件主题" align="center" prop="subject" :show-overflow-tooltip="true" />
-      <el-table-column label="内容类型" align="center" prop="contentType" width="100">
-        <template #default="scope">
-          <el-tag :type="scope.row.contentType === 'html' ? 'primary' : 'info'">{{ scope.row.contentType === 'html' ? 'HTML' : '纯文本' }}</el-tag>
+    <a-table 
+      :loading="loading" 
+      :dataSource="templateList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="templateId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'contentType'">
+          <a-tag :color="record.contentType === 'html' ? 'blue' : 'default'">{{ record.contentType === 'html' ? 'HTML' : '纯文本' }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="状态" align="center" prop="status" width="80">
-        <template #default="scope">
-          <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag>
+        <template v-else-if="column.key === 'status'">
+          <a-tag :color="record.status === '0' ? 'green' : 'red'">{{ record.status === '0' ? '正常' : '停用' }}</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
-        <template #default="scope">
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:mail:template:edit']">修改</el-button>
-          <el-button link type="primary" icon="View" @click="handlePreview(scope.row)" v-hasPermi="['system:mail:template:preview']">预览</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:mail:template:remove']">删除</el-button>
+        <template v-else-if="column.key === 'action'">
+          <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:mail:template:edit']">
+            <template #icon><EditOutlined /></template>
+            修改
+          </a-button>
+          <a-button type="link" size="small" @click="handlePreview(record)" v-hasPermi="['system:mail:template:preview']">
+            <template #icon><EyeOutlined /></template>
+            预览
+          </a-button>
+          <a-button type="link" danger size="small" @click="handleDelete(record)" v-hasPermi="['system:mail:template:remove']">
+            <template #icon><DeleteOutlined /></template>
+            删除
+          </a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
 
     <!-- 添加或修改邮件模板对话框 -->
-    <el-dialog :title="title" v-model="open" width="900px" append-to-body>
-      <el-form ref="templateRef" :model="form" :rules="rules" label-width="100px">
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="模板名称" prop="templateName">
-              <el-input v-model="form.templateName" placeholder="请输入模板名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="模板代码" prop="templateCode">
-              <el-input v-model="form.templateCode" placeholder="请输入模板代码" :disabled="form.templateId != null" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="邮件主题" prop="subject">
-              <el-input v-model="form.subject" placeholder="请输入邮件主题,支持变量${xxx}" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="内容类型" prop="contentType">
-              <el-radio-group v-model="form.contentType">
-                <el-radio value="html">HTML</el-radio>
-                <el-radio value="text">纯文本</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="变量列表" prop="variables">
-              <el-input v-model="form.variables" placeholder='JSON数组,如:["name","code"]' />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="状态" prop="status">
-              <el-radio-group v-model="form.status">
-                <el-radio value="0">正常</el-radio>
-                <el-radio value="1">停用</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="模板内容" prop="content">
-              <el-input v-model="form.content" type="textarea" :rows="12" placeholder="请输入模板内容,支持变量${xxx}" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+    <a-modal :title="title" v-model:open="open" width="900px" :maskClosable="false">
+      <a-form ref="templateRef" :model="form" :rules="rules" :label-col="{ span: 4 }">
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="模板名称" name="templateName">
+              <a-input v-model:value="form.templateName" placeholder="请输入模板名称" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="模板代码" name="templateCode">
+              <a-input v-model:value="form.templateCode" placeholder="请输入模板代码" :disabled="form.templateId != null" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="邮件主题" name="subject">
+              <a-input v-model:value="form.subject" placeholder="请输入邮件主题,支持变量${xxx}" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="内容类型" name="contentType">
+              <a-radio-group v-model:value="form.contentType">
+                <a-radio value="html">HTML</a-radio>
+                <a-radio value="text">纯文本</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="变量列表" name="variables">
+              <a-input v-model:value="form.variables" placeholder='JSON数组,如:["name","code"]' />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="状态" name="status">
+              <a-radio-group v-model:value="form.status">
+                <a-radio value="0">正常</a-radio>
+                <a-radio value="1">停用</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="模板内容" name="content">
+              <a-textarea v-model:value="form.content" :rows="12" placeholder="请输入模板内容,支持变量${xxx}" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="备注" name="remark" :label-col="{ span: 2 }">
+              <a-textarea v-model:value="form.remark" placeholder="请输入备注" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
       <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
+        <a-button @click="cancel">取 消</a-button>
+        <a-button type="primary" @click="submitForm">确 定</a-button>
       </template>
-    </el-dialog>
+    </a-modal>
 
     <!-- 预览模板对话框 -->
-    <el-dialog title="模板预览" v-model="previewOpen" width="800px" append-to-body>
+    <a-modal title="模板预览" v-model:open="previewOpen" width="800px" :footer="null">
       <div class="preview-container" v-html="previewContent"></div>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="MailTemplate">
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'
 import { listMailTemplate, getMailTemplate, addMailTemplate, updateMailTemplate, delMailTemplate, previewMailTemplate } from "@/api/system/mail"
 
 const { proxy } = getCurrentInstance()
@@ -145,6 +165,7 @@ const open = ref(false)
 const previewOpen = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
+const selectedRowKeys = ref([])
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
@@ -152,6 +173,17 @@ const total = ref(0)
 const title = ref("")
 const previewContent = ref("")
 
+const columns = [
+  { title: '模板ID', dataIndex: 'templateId', key: 'templateId', width: 80, align: 'center' },
+  { title: '模板名称', dataIndex: 'templateName', key: 'templateName', ellipsis: true },
+  { title: '模板代码', dataIndex: 'templateCode', key: 'templateCode', ellipsis: true },
+  { title: '邮件主题', dataIndex: 'subject', key: 'subject', ellipsis: true },
+  { title: '内容类型', key: 'contentType', width: 100, align: 'center' },
+  { title: '状态', key: 'status', width: 80, align: 'center' },
+  { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160, align: 'center' },
+  { title: '操作', key: 'action', width: 200, align: 'center' }
+]
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -216,10 +248,11 @@ function resetQuery() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.templateId)
-  single.value = selection.length !== 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = !keys.length
 }
 
 /** 新增按钮操作 */
@@ -242,21 +275,19 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["templateRef"].validate(valid => {
-    if (valid) {
-      if (form.value.templateId != null) {
-        updateMailTemplate(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addMailTemplate(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["templateRef"].validate().then(() => {
+    if (form.value.templateId != null) {
+      updateMailTemplate(form.value).then(response => {
+        proxy.$modal.msgSuccess("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addMailTemplate(form.value).then(response => {
+        proxy.$modal.msgSuccess("新增成功")
+        open.value = false
+        getList()
+      })
     }
   })
 }
@@ -274,7 +305,6 @@ function handleDelete(row) {
 
 /** 预览按钮操作 */
 function handlePreview(row) {
-  // 解析变量并生成预览数据
   let variables = {}
   if (row.variables) {
     try {

+ 270 - 244
yushu-uivue3/src/views/system/menu/index.vue

@@ -1,290 +1,264 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="菜单名称" prop="menuName">
-            <el-input
-               v-model="queryParams.menuName"
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="菜单名称" name="menuName">
+            <a-input
+               v-model:value="queryParams.menuName"
                placeholder="请输入菜单名称"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select v-model="queryParams.status" placeholder="菜单状态" clearable style="width: 200px">
-               <el-option
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select v-model:value="queryParams.status" placeholder="菜单状态" allow-clear style="width: 200px">
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:menu:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button 
-               type="info"
-               plain
-               icon="Sort"
-               @click="toggleExpandAll"
-            >展开/折叠</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd()" v-hasPermi="['system:menu:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button @click="toggleExpandAll"><SortAscendingOutlined />展开/折叠</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table
+      <a-table
          v-if="refreshTable"
-         v-loading="loading"
-         :data="menuList"
-         row-key="menuId"
-         :default-expand-all="isExpandAll"
-         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+         :loading="loading"
+         :data-source="menuList"
+         :columns="tableColumns"
+         :row-key="record => record.menuId"
+         :pagination="false"
+         :default-expand-all-rows="isExpandAll"
+         :expanded-row-keys="expandedRowKeys"
+         @expand="handleExpand"
       >
-         <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
-         <el-table-column prop="icon" label="图标" align="center" width="100">
-            <template #default="scope">
-               <svg-icon :icon-class="scope.row.icon" />
-            </template>
-         </el-table-column>
-         <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
-         <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
-         <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
-         <el-table-column prop="status" label="状态" width="80">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="创建时间" align="center" width="160" prop="createTime">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" width="210" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']">修改</el-button>
-               <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']">新增</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'icon'">
+            <svg-icon :icon-class="record.icon" />
+          </template>
+          <template v-else-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:menu:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" @click="handleAdd(record)" v-hasPermi="['system:menu:add']"><PlusOutlined />新增</a-button>
+            <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:menu:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <!-- 添加或修改菜单对话框 -->
-      <el-dialog :title="title" v-model="open" width="680px" append-to-body>
-         <el-form ref="menuRef" :model="form" :rules="rules" label-width="100px">
-            <el-row>
-               <el-col :span="24">
-                  <el-form-item label="上级菜单">
-                     <el-tree-select
-                        v-model="form.parentId"
-                        :data="menuOptions"
-                        :props="{ value: 'menuId', label: 'menuName', children: 'children' }"
-                        value-key="menuId"
+      <a-modal :title="title" v-model:open="open" width="680px" :footer="null">
+         <a-form ref="menuRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-row>
+               <a-col :span="24">
+                  <a-form-item label="上级菜单">
+                     <a-tree-select
+                        v-model:value="form.parentId"
+                        :tree-data="menuOptions"
+                        :field-names="{ value: 'menuId', label: 'menuName', children: 'children' }"
                         placeholder="选择上级菜单"
-                        check-strictly
+                        tree-default-expand-all
+                        allow-clear
                      />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="24">
-                  <el-form-item label="菜单类型" prop="menuType">
-                     <el-radio-group v-model="form.menuType">
-                        <el-radio value="M">目录</el-radio>
-                        <el-radio value="C">菜单</el-radio>
-                        <el-radio value="F">按钮</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType != 'F'">
-                  <el-form-item label="菜单图标" prop="icon">
-                     <el-popover
-                        placement="bottom-start"
-                        :width="540"
-                        trigger="click"
-                     >
-                        <template #reference>
-                           <el-input v-model="form.icon" placeholder="点击选择图标" @blur="showSelectIcon" readonly>
-                              <template #prefix>
-                                 <svg-icon
-                                    v-if="form.icon"
-                                    :icon-class="form.icon"
-                                    class="el-input__icon"
-                                    style="height: 32px;width: 16px;"
-                                 />
-                                 <el-icon v-else style="height: 32px;width: 16px;"><search /></el-icon>
-                              </template>
-                           </el-input>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="24">
+                  <a-form-item label="菜单类型" name="menuType">
+                     <a-radio-group v-model:value="form.menuType">
+                        <a-radio value="M">目录</a-radio>
+                        <a-radio value="C">菜单</a-radio>
+                        <a-radio value="F">按钮</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType != 'F'">
+                  <a-form-item label="菜单图标" name="icon">
+                     <a-popover placement="bottomLeft" trigger="click" :overlayStyle="{ width: '540px' }">
+                        <template #content>
+                           <icon-select ref="iconSelectRef" @selected="selected" :active-icon="form.icon" />
                         </template>
-                        <icon-select ref="iconSelectRef" @selected="selected" :active-icon="form.icon" />
-                     </el-popover>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="显示排序" prop="orderNum">
-                     <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="菜单名称" prop="menuName">
-                     <el-input v-model="form.menuName" placeholder="请输入菜单名称" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType == 'C'">
-                  <el-form-item prop="routeName">
+                        <a-input v-model:value="form.icon" placeholder="点击选择图标" readonly @blur="showSelectIcon">
+                           <template #prefix>
+                              <svg-icon
+                                 v-if="form.icon"
+                                 :icon-class="form.icon"
+                                 style="height: 16px; width: 16px;"
+                              />
+                              <SearchOutlined v-else />
+                           </template>
+                        </a-input>
+                     </a-popover>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="显示排序" name="orderNum">
+                     <a-input-number v-model:value="form.orderNum" :min="0" style="width: 100%" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="菜单名称" name="menuName">
+                     <a-input v-model:value="form.menuName" placeholder="请输入菜单名称" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType == 'C'">
+                  <a-form-item name="routeName">
                      <template #label>
                         <span>
-                           <el-tooltip content="默认不填则和路由地址相同:如地址为:`user`,则名称为`User`(注意:因为router会删除名称相同路由,为避免名字的冲突,特殊情况下请自定义,保证唯一性)" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="默认不填则和路由地址相同:如地址为:`user`,则名称为`User`(注意:因为router会删除名称相同路由,为避免名字的冲突,特殊情况下请自定义,保证唯一性)">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            路由名称
                         </span>
                      </template>
-                     <el-input v-model="form.routeName" placeholder="请输入路由名称" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType != 'F'">
-                  <el-form-item>
+                     <a-input v-model:value="form.routeName" placeholder="请输入路由名称" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType != 'F'">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>是否外链
+                           <a-tooltip title="选择是外链则路由地址需要以`http(s)://`开头">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>是否外链
                         </span>
                      </template>
-                     <el-radio-group v-model="form.isFrame">
-                        <el-radio value="0">是</el-radio>
-                        <el-radio value="1">否</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType != 'F'">
-                  <el-form-item prop="path">
+                     <a-radio-group v-model:value="form.isFrame">
+                        <a-radio value="0">是</a-radio>
+                        <a-radio value="1">否</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType != 'F'">
+                  <a-form-item name="path">
                      <template #label>
                         <span>
-                           <el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            路由地址
                         </span>
                      </template>
-                     <el-input v-model="form.path" placeholder="请输入路由地址" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType == 'C'">
-                  <el-form-item prop="component">
+                     <a-input v-model:value="form.path" placeholder="请输入路由地址" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType == 'C'">
+                  <a-form-item name="component">
                      <template #label>
                         <span>
-                           <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="访问的组件路径,如:`system/user/index`,默认在`views`目录下">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            组件路径
                         </span>
                      </template>
-                     <el-input v-model="form.component" placeholder="请输入组件路径" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType != 'M'">
-                  <el-form-item>
-                     <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
+                     <a-input v-model:value="form.component" placeholder="请输入组件路径" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType != 'M'">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            权限字符
                         </span>
                      </template>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType == 'C'">
-                  <el-form-item>
-                     <el-input v-model="form.query" placeholder="请输入路由参数" maxlength="255" />
+                     <a-input v-model:value="form.perms" placeholder="请输入权限标识" :maxlength="100" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType == 'C'">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`'>
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            路由参数
                         </span>
                      </template>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType == 'C'">
-                  <el-form-item>
+                     <a-input v-model:value="form.query" placeholder="请输入路由参数" :maxlength="255" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType == 'C'">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            是否缓存
                         </span>
                      </template>
-                     <el-radio-group v-model="form.isCache">
-                        <el-radio value="0">缓存</el-radio>
-                        <el-radio value="1">不缓存</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12" v-if="form.menuType != 'F'">
-                  <el-form-item>
+                     <a-radio-group v-model:value="form.isCache">
+                        <a-radio value="0">缓存</a-radio>
+                        <a-radio value="1">不缓存</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12" v-if="form.menuType != 'F'">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            显示状态
                         </span>
                      </template>
-                     <el-radio-group v-model="form.visible">
-                        <el-radio
+                     <a-radio-group v-model:value="form.visible">
+                        <a-radio
                            v-for="dict in sys_show_hide"
                            :key="dict.value"
                            :value="dict.value"
-                        >{{ dict.label }}</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item>
+                        >{{ dict.label }}</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item>
                      <template #label>
                         <span>
-                           <el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
-                              <el-icon><question-filled /></el-icon>
-                           </el-tooltip>
+                           <a-tooltip title="选择停用则路由将不会出现在侧边栏,也不能被访问">
+                              <QuestionCircleOutlined />
+                           </a-tooltip>
                            菜单状态
                         </span>
                      </template>
-                     <el-radio-group v-model="form.status">
-                        <el-radio
+                     <a-radio-group v-model:value="form.status">
+                        <a-radio
                            v-for="dict in sys_normal_disable"
                            :key="dict.value"
                            :value="dict.value"
-                        >{{ dict.label }}</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                        >{{ dict.label }}</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+            </a-row>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
@@ -292,6 +266,11 @@
 import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu"
 import SvgIcon from "@/components/SvgIcon"
 import IconSelect from "@/components/IconSelect"
+import { Modal, message } from 'ant-design-vue'
+import {
+  SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
+  SortAscendingOutlined, QuestionCircleOutlined
+} from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable")
@@ -305,6 +284,18 @@ const menuOptions = ref([])
 const isExpandAll = ref(false)
 const refreshTable = ref(true)
 const iconSelectRef = ref(null)
+const expandedRowKeys = ref([])
+
+const tableColumns = [
+  { title: '菜单名称', dataIndex: 'menuName', key: 'menuName', ellipsis: true, width: 160 },
+  { title: '图标', key: 'icon', align: 'center', width: 100 },
+  { title: '排序', dataIndex: 'orderNum', key: 'orderNum', width: 60 },
+  { title: '权限标识', dataIndex: 'perms', key: 'perms', ellipsis: true },
+  { title: '组件路径', dataIndex: 'component', key: 'component', ellipsis: true },
+  { title: '状态', key: 'status', width: 80 },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 160 },
+  { title: '操作', key: 'action', align: 'center', width: 210 }
+]
 
 const data = reactive({
   form: {},
@@ -321,6 +312,30 @@ const data = reactive({
 
 const { queryParams, form, rules } = toRefs(data)
 
+/** 获取所有菜单ID(用于展开) */
+function getAllMenuIds(menus) {
+  let ids = []
+  menus.forEach(menu => {
+    ids.push(menu.menuId)
+    if (menu.children && menu.children.length > 0) {
+      ids = ids.concat(getAllMenuIds(menu.children))
+    }
+  })
+  return ids
+}
+
+/** 处理展开 */
+function handleExpand(expanded, record) {
+  if (expanded) {
+    expandedRowKeys.value.push(record.menuId)
+  } else {
+    const index = expandedRowKeys.value.indexOf(record.menuId)
+    if (index > -1) {
+      expandedRowKeys.value.splice(index, 1)
+    }
+  }
+}
+
 /** 查询菜单列表 */
 function getList() {
   loading.value = true
@@ -360,12 +375,13 @@ function reset() {
     visible: "0",
     status: "0"
   }
-  proxy.resetForm("menuRef")
 }
 
 /** 展示下拉图标 */
 function showSelectIcon() {
-  iconSelectRef.value.reset()
+  if (iconSelectRef.value) {
+    iconSelectRef.value.reset()
+  }
 }
 
 /** 选择图标 */
@@ -380,7 +396,10 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    menuName: undefined,
+    visible: undefined
+  }
   handleQuery()
 }
 
@@ -401,6 +420,11 @@ function handleAdd(row) {
 function toggleExpandAll() {
   refreshTable.value = false
   isExpandAll.value = !isExpandAll.value
+  if (isExpandAll.value) {
+    expandedRowKeys.value = getAllMenuIds(menuList.value)
+  } else {
+    expandedRowKeys.value = []
+  }
   nextTick(() => {
     refreshTable.value = true
   })
@@ -419,33 +443,35 @@ async function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["menuRef"].validate(valid => {
-    if (valid) {
-      if (form.value.menuId != undefined) {
-        updateMenu(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addMenu(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["menuRef"].validate().then(() => {
+    if (form.value.menuId != undefined) {
+      updateMenu(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addMenu(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
-  proxy.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?').then(function() {
-    return delMenu(row.menuId)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除名称为"' + row.menuName + '"的数据项?',
+    onOk() {
+      return delMenu(row.menuId).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 getList()

+ 45 - 49
yushu-uivue3/src/views/system/message/components/SelectUserDialog.vue

@@ -1,57 +1,56 @@
 <template>
-  <el-dialog title="选择接收者" v-model="visible" width="700px" append-to-body @close="handleClose">
-    <el-tabs v-model="activeTab">
-      <el-tab-pane label="按部门" name="dept">
-        <el-tree
+  <a-modal title="选择接收者" v-model:open="visible" width="700px" :maskClosable="false" @cancel="handleClose">
+    <a-tabs v-model:activeKey="activeTab">
+      <a-tab-pane key="dept" tab="按部门">
+        <a-tree
           ref="deptTreeRef"
-          :data="deptTree"
-          :props="deptTreeProps"
-          show-checkbox
-          node-key="id"
-          :default-checked-keys="selectedUserIds"
+          :tree-data="deptTree"
+          checkable
+          :field-names="{ title: 'label', key: 'id', children: 'children' }"
+          v-model:checkedKeys="checkedKeys"
           @check="handleDeptCheck"
         >
-          <template #default="{ node, data }">
+          <template #title="{ label, type, userName }">
             <span class="custom-tree-node">
               <span>
-                <el-icon v-if="data.type === 'dept'"><OfficeBuilding /></el-icon>
-                <el-icon v-else><User /></el-icon>
-                {{ node.label }}
+                <BankOutlined v-if="type === 'dept'" style="margin-right: 6px; color: #999;" />
+                <UserOutlined v-else style="margin-right: 6px; color: #999;" />
+                {{ label }}
               </span>
-              <span v-if="data.type === 'user'" class="user-account">{{ data.userName }}</span>
+              <span v-if="type === 'user'" class="user-account">{{ userName }}</span>
             </span>
           </template>
-        </el-tree>
-      </el-tab-pane>
+        </a-tree>
+      </a-tab-pane>
       
-      <el-tab-pane label="按角色" name="role">
+      <a-tab-pane key="role" tab="按角色">
         <div class="role-list">
-          <el-checkbox-group v-model="selectedRoles" @change="handleRoleChange">
+          <a-checkbox-group v-model:value="selectedRoles" @change="handleRoleChange">
             <div v-for="role in roleList" :key="role.roleId" class="role-item">
-              <el-checkbox :value="role.roleId">
+              <a-checkbox :value="role.roleId">
                 <span class="role-name">{{ role.roleName }}</span>
-                <el-tag size="small" type="info">{{ getRoleUserCount(role.roleId) }}人</el-tag>
-              </el-checkbox>
+                <a-tag size="small" color="default">{{ getRoleUserCount(role.roleId) }}人</a-tag>
+              </a-checkbox>
             </div>
-          </el-checkbox-group>
+          </a-checkbox-group>
         </div>
-      </el-tab-pane>
-    </el-tabs>
+      </a-tab-pane>
+    </a-tabs>
     
     <div class="selected-info">
-      <el-tag type="success">已选择 {{ selectedUsers.length }} 人</el-tag>
-      <el-button type="primary" link @click="clearSelection">清空</el-button>
+      <a-tag color="green">已选择 {{ selectedUsers.length }} 人</a-tag>
+      <a-button type="link" @click="clearSelection">清空</a-button>
     </div>
     
     <template #footer>
-      <el-button @click="visible = false">取消</el-button>
-      <el-button type="primary" @click="confirm">确定</el-button>
+      <a-button @click="visible = false">取消</a-button>
+      <a-button type="primary" @click="confirm">确定</a-button>
     </template>
-  </el-dialog>
+  </a-modal>
 </template>
 
 <script setup name="SelectUserDialog">
-import { OfficeBuilding, User } from '@element-plus/icons-vue'
+import { BankOutlined, UserOutlined } from '@ant-design/icons-vue'
 import { listDept } from "@/api/system/dept"
 import { listRole } from "@/api/system/role"
 import { listUser } from "@/api/system/user"
@@ -65,10 +64,7 @@ const roleList = ref([])
 const userList = ref([])
 const deptTree = ref([])
 const deptTreeRef = ref(null)
-const deptTreeProps = {
-  children: 'children',
-  label: 'label'
-}
+const checkedKeys = ref([])
 const selectedUserIds = ref([])
 const selectedRoles = ref([])
 
@@ -143,13 +139,19 @@ function buildDeptTree() {
   })
   
   deptTree.value = tree
+  
+  // 更新已选中的keys
+  updateCheckedKeys()
+}
+
+function updateCheckedKeys() {
+  checkedKeys.value = selectedUserIds.value.map(id => `user_${id}`)
 }
 
-function handleDeptCheck() {
-  const checkedNodes = deptTreeRef.value.getCheckedNodes()
-  selectedUserIds.value = checkedNodes
-    .filter(node => node.type === 'user')
-    .map(node => node.userId)
+function handleDeptCheck(keys, e) {
+  // 从选中的keys中提取用户ID
+  const userIds = keys.filter(key => key.startsWith('user_')).map(key => parseInt(key.replace('user_', '')))
+  selectedUserIds.value = userIds
 }
 
 function handleRoleChange() {
@@ -163,6 +165,7 @@ function handleRoleChange() {
   })
   
   selectedUserIds.value = Array.from(userIds)
+  updateCheckedKeys()
 }
 
 function getRoleUserCount(roleId) {
@@ -174,9 +177,7 @@ function getRoleUserCount(roleId) {
 function clearSelection() {
   selectedUserIds.value = []
   selectedRoles.value = []
-  if (deptTreeRef.value) {
-    deptTreeRef.value.setCheckedKeys([])
-  }
+  checkedKeys.value = []
 }
 
 function confirm() {
@@ -200,14 +201,9 @@ defineExpose({ open })
   font-size: 14px;
   padding-right: 8px;
   
-  .el-icon {
-    margin-right: 6px;
-    color: #909399;
-  }
-  
   .user-account {
     font-size: 12px;
-    color: #909399;
+    color: #999;
   }
 }
 
@@ -232,7 +228,7 @@ defineExpose({ open })
 .selected-info {
   margin-top: 16px;
   padding: 12px;
-  background: #f5f7fa;
+  background: #fafafa;
   border-radius: 4px;
   display: flex;
   align-items: center;

+ 65 - 68
yushu-uivue3/src/views/system/message/components/SendMessageDialog.vue

@@ -1,45 +1,46 @@
 <template>
-  <el-dialog title="发布系统通知" v-model="visible" width="800px" append-to-body @close="handleClose">
-    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
-      <el-form-item label="通知标题" prop="title">
-        <el-select
-          v-model="form.title"
-          filterable
-          allow-create
-          default-first-option
+  <a-modal title="发布系统通知" v-model:open="visible" width="800px" :maskClosable="false" @cancel="handleClose">
+    <a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 4 }">
+      <a-form-item label="通知标题" name="title">
+        <a-select
+          v-model:value="form.title"
+          mode="combobox"
           placeholder="选择或输入通知标题"
           style="width: 100%"
         >
-          <el-option label="系统维护通知" value="系统维护通知" />
-          <el-option label="版本更新通知" value="版本更新通知" />
-          <el-option label="重要公告" value="重要公告" />
-          <el-option label="安全提醒" value="安全提醒" />
-          <el-option label="活动通知" value="活动通知" />
-          <el-option label="节假日通知" value="节假日通知" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="优先级" prop="priority">
-        <el-radio-group v-model="form.priority">
-          <el-radio value="0">普通</el-radio>
-          <el-radio value="1">重要</el-radio>
-          <el-radio value="2">紧急</el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="接收者" prop="receiverIds">
+          <a-select-option value="系统维护通知">系统维护通知</a-select-option>
+          <a-select-option value="版本更新通知">版本更新通知</a-select-option>
+          <a-select-option value="重要公告">重要公告</a-select-option>
+          <a-select-option value="安全提醒">安全提醒</a-select-option>
+          <a-select-option value="活动通知">活动通知</a-select-option>
+          <a-select-option value="节假日通知">节假日通知</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="优先级" name="priority">
+        <a-radio-group v-model:value="form.priority">
+          <a-radio value="0">普通</a-radio>
+          <a-radio value="1">重要</a-radio>
+          <a-radio value="2">紧急</a-radio>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item label="接收者" name="receiverIds">
         <div class="receiver-selector">
           <div class="selector-actions">
-            <el-button icon="User" @click="openSelectUserDialog">
+            <a-button @click="openSelectUserDialog">
+              <template #icon><UserOutlined /></template>
               选择接收者
-            </el-button>
-            <el-button icon="CircleCheck" @click="selectAllUsers">
+            </a-button>
+            <a-button @click="selectAllUsers">
+              <template #icon><CheckCircleOutlined /></template>
               全选
-            </el-button>
-            <el-button v-if="form.receiverIds && form.receiverIds.length > 0" icon="Delete" @click="clearReceivers">
+            </a-button>
+            <a-button v-if="form.receiverIds && form.receiverIds.length > 0" @click="clearReceivers">
+              <template #icon><DeleteOutlined /></template>
               清空
-            </el-button>
+            </a-button>
           </div>
           <div class="selected-users">
-            <el-tag
+            <a-tag
               v-for="userId in form.receiverIds"
               :key="userId"
               closable
@@ -47,40 +48,38 @@
               style="margin: 4px;"
             >
               {{ getUserName(userId) }}
-            </el-tag>
+            </a-tag>
             <div v-if="!form.receiverIds || form.receiverIds.length === 0" class="empty-tip">
               未选择接收者
             </div>
           </div>
           <div class="receiver-count">
-            <el-tag size="small" type="success">已选择 {{ form.receiverIds ? form.receiverIds.length : 0 }} 人</el-tag>
+            <a-tag color="green">已选择 {{ form.receiverIds ? form.receiverIds.length : 0 }} 人</a-tag>
           </div>
         </div>
-      </el-form-item>
-      <el-form-item label="消息内容" prop="content">
-        <el-input
-          v-model="form.content"
-          type="textarea"
+      </a-form-item>
+      <a-form-item label="消息内容" name="content">
+        <a-textarea
+          v-model:value="form.content"
           :rows="8"
           placeholder="请输入消息内容"
-          maxlength="2000"
-          show-word-limit
+          :maxlength="2000"
+          showCount
         />
-      </el-form-item>
-    </el-form>
+      </a-form-item>
+    </a-form>
     <template #footer>
-      <div class="dialog-footer">
-        <el-button @click="cancel">取 消</el-button>
-        <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
-      </div>
+      <a-button @click="cancel">取 消</a-button>
+      <a-button type="primary" @click="submitForm" :loading="submitLoading">确 定</a-button>
     </template>
     
     <!-- 选择用户对话框 -->
     <select-user-dialog ref="selectUserDialogRef" @confirm="handleUserSelected" />
-  </el-dialog>
+  </a-modal>
 </template>
 
 <script setup name="SendMessageDialog">
+import { UserOutlined, CheckCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'
 import { addNotification } from "@/api/system/message"
 import { listUser } from "@/api/system/user"
 import SelectUserDialog from "./SelectUserDialog.vue"
@@ -170,25 +169,23 @@ function getUserName(userId) {
 
 /** 提交按钮 */
 function submitForm() {
-  formRef.value.validate(valid => {
-    if (valid) {
-      submitLoading.value = true
-      // 构建发送数据,将 receiverIds 转换为后端需要的格式
-      const submitData = {
-        title: form.value.title,
-        content: form.value.content,
-        priority: form.value.priority,
-        targetType: '2', // 2=指定用户
-        targetUsers: JSON.stringify(form.value.receiverIds)
-      }
-      addNotification(submitData).then(response => {
-        proxy.$modal.msgSuccess("发送成功")
-        visible.value = false
-        emit('success')
-      }).finally(() => {
-        submitLoading.value = false
-      })
+  formRef.value.validate().then(() => {
+    submitLoading.value = true
+    // 构建发送数据,将 receiverIds 转换为后端需要的格式
+    const submitData = {
+      title: form.value.title,
+      content: form.value.content,
+      priority: form.value.priority,
+      targetType: '2', // 2=指定用户
+      targetUsers: JSON.stringify(form.value.receiverIds)
     }
+    addNotification(submitData).then(response => {
+      proxy.$modal.msgSuccess("发送成功")
+      visible.value = false
+      emit('success')
+    }).finally(() => {
+      submitLoading.value = false
+    })
   })
 }
 
@@ -231,12 +228,12 @@ defineExpose({ open })
     max-height: 200px;
     overflow-y: auto;
     padding: 12px;
-    border: 1px solid #dcdfe6;
+    border: 1px solid #d9d9d9;
     border-radius: 4px;
-    background: #f5f7fa;
+    background: #fafafa;
     
     .empty-tip {
-      color: #909399;
+      color: #999;
       font-size: 13px;
       text-align: center;
       padding: 20px 0;

+ 158 - 148
yushu-uivue3/src/views/system/message/index.vue

@@ -1,119 +1,129 @@
 <template>
   <div class="app-container">
     <!-- 页面说明 -->
-    <el-alert
-      title="消息管理说明"
+    <a-alert
+      message="消息管理说明"
       type="info"
       :closable="false"
       style="margin-bottom: 16px"
     >
-      <div style="line-height: 1.8; font-size: 13px;">
-        此页面仅用于<strong>管理员发布系统通知</strong>。<br/>
-        • 用户之间的<strong>对话功能</strong>请使用右上角<el-tag size="small" type="primary" style="margin: 0 4px">💬 消息中心</el-tag><br/>
-        • 发布通知后,所有选中用户将<strong>实时收到WebSocket推送</strong>
-      </div>
-    </el-alert>
+      <template #description>
+        <div style="line-height: 1.8; font-size: 13px;">
+          此页面仅用于<strong>管理员发布系统通知</strong>。<br/>
+          • 用户之间的<strong>对话功能</strong>请使用右上角<a-tag color="blue" style="margin: 0 4px">💬 消息中心</a-tag><br/>
+          • 发布通知后,所有选中用户将<strong>实时收到WebSocket推送</strong>
+        </div>
+      </template>
+    </a-alert>
 
     <!-- 搜索栏 -->
-    <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
-      <el-form-item label="消息标题" prop="title">
-        <el-input
-          v-model="queryParams.title"
+    <a-form :model="queryParams" ref="queryFormRef" layout="inline" v-show="showSearch" class="mb8">
+      <a-form-item label="消息标题" name="title">
+        <a-input
+          v-model:value="queryParams.title"
           placeholder="请输入消息标题"
-          clearable
+          allowClear
           style="width: 200px"
-          @keyup.enter="handleQuery"
+          @pressEnter="handleQuery"
         />
-      </el-form-item>
-      <el-form-item label="优先级" prop="priority">
-        <el-select v-model="queryParams.priority" placeholder="请选择优先级" clearable style="width: 120px">
-          <el-option label="普通" value="0" />
-          <el-option label="重要" value="1" />
-          <el-option label="紧急" value="2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+      </a-form-item>
+      <a-form-item label="优先级" name="priority">
+        <a-select v-model:value="queryParams.priority" placeholder="请选择优先级" allowClear style="width: 120px">
+          <a-select-option value="0">普通</a-select-option>
+          <a-select-option value="1">重要</a-select-option>
+          <a-select-option value="2">紧急</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
 
     <!-- 操作按钮 -->
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button
           type="primary"
-          icon="Promotion"
           @click="handleSend"
           v-hasPermi="['system:message:send']"
-        >发布通知</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="danger"
-          plain
-          icon="Delete"
+        >
+          <template #icon><SendOutlined /></template>
+          发布通知
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button
+          danger
           :disabled="multiple"
           @click="handleDelete"
           v-hasPermi="['system:message:remove']"
-        >删除</el-button>
-      </el-col>
+        >
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+    </a-row>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="messageList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="消息ID" align="center" prop="messageId" width="80" />
-      <el-table-column label="优先级" align="center" width="80">
-        <template #default="scope">
-          <el-tag v-if="scope.row.priority === '0'" type="info" size="small">普通</el-tag>
-          <el-tag v-else-if="scope.row.priority === '1'" type="warning" size="small">重要</el-tag>
-          <el-tag v-else-if="scope.row.priority === '2'" type="danger" size="small">紧急</el-tag>
+    <a-table 
+      :loading="loading" 
+      :dataSource="messageList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="messageId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'priority'">
+          <a-tag v-if="record.priority === '0'" color="default">普通</a-tag>
+          <a-tag v-else-if="record.priority === '1'" color="orange">重要</a-tag>
+          <a-tag v-else-if="record.priority === '2'" color="red">紧急</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="消息标题" align="left" prop="title" :show-overflow-tooltip="true" min-width="200" />
-      <el-table-column label="发送者" align="center" prop="senderName" width="120">
-        <template #default="scope">
-          {{ scope.row.senderName || '系统' }}
+        <template v-else-if="column.key === 'senderName'">
+          {{ record.senderName || '系统' }}
         </template>
-      </el-table-column>
-      <el-table-column label="接收人数" align="center" width="100">
-        <template #default="scope">
-          <el-tag type="info" size="small">{{ scope.row.receiverCount || 0 }}人</el-tag>
+        <template v-else-if="column.key === 'receiverCount'">
+          <a-tag color="default">{{ record.receiverCount || 0 }}人</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="已读/未读" align="center" width="100">
-        <template #default="scope">
-          <span style="color: #67C23A;">{{ scope.row.readCount || 0 }}</span>
+        <template v-else-if="column.key === 'readStatus'">
+          <span style="color: #52c41a;">{{ record.readCount || 0 }}</span>
           /
-          <span style="color: #F56C6C;">{{ scope.row.unreadCount || 0 }}</span>
+          <span style="color: #f5222d;">{{ record.unreadCount || 0 }}</span>
         </template>
-      </el-table-column>
-      <el-table-column label="发送时间" align="center" prop="createTime" width="160">
-        <template #default="scope">
-          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
+        <template v-else-if="column.key === 'createTime'">
+          <span>{{ parseTime(record.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
         </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150" fixed="right">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            icon="View"
-            @click="handleView(scope.row)"
+        <template v-else-if="column.key === 'action'">
+          <a-button
+            type="link"
+            size="small"
+            @click="handleView(record)"
             v-hasPermi="['system:message:query']"
-          >查看</el-button>
-          <el-button
-            link
-            type="primary"
-            icon="Delete"
-            @click="handleDelete(scope.row)"
+          >
+            <template #icon><EyeOutlined /></template>
+            查看
+          </a-button>
+          <a-button
+            type="link"
+            danger
+            size="small"
+            @click="handleDelete(record)"
             v-hasPermi="['system:message:remove']"
-          >删除</el-button>
+          >
+            <template #icon><DeleteOutlined /></template>
+            删除
+          </a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
     
     <pagination
       v-show="total > 0"
@@ -124,66 +134,61 @@
     />
 
     <!-- 查看消息详情对话框 -->
-    <el-dialog title="消息详情" v-model="viewOpen" width="800px" append-to-body class="message-detail-dialog">
-      <el-descriptions :column="2" border>
-        <el-descriptions-item label="消息标题" :span="2">
+    <a-modal title="消息详情" v-model:open="viewOpen" width="800px" :footer="null">
+      <a-descriptions :column="2" bordered>
+        <a-descriptions-item label="消息标题" :span="2">
           <span style="font-weight: bold; font-size: 16px;">{{ form.title }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="消息类型">
-          <el-tag v-if="form.messageType === '1'" type="primary">对话消息</el-tag>
-          <el-tag v-else-if="form.messageType === '2'" type="success">系统通知</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="优先级">
-          <el-tag v-if="form.priority === '0'" type="info">普通</el-tag>
-          <el-tag v-else-if="form.priority === '1'" type="warning">重要</el-tag>
-          <el-tag v-else-if="form.priority === '2'" type="danger">紧急</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="发送者">
+        </a-descriptions-item>
+        <a-descriptions-item label="消息类型">
+          <a-tag v-if="form.messageType === '1'" color="blue">对话消息</a-tag>
+          <a-tag v-else-if="form.messageType === '2'" color="green">系统通知</a-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="优先级">
+          <a-tag v-if="form.priority === '0'" color="default">普通</a-tag>
+          <a-tag v-else-if="form.priority === '1'" color="orange">重要</a-tag>
+          <a-tag v-else-if="form.priority === '2'" color="red">紧急</a-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="发送者">
           {{ form.senderName || '系统' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="发送时间">
+        </a-descriptions-item>
+        <a-descriptions-item label="发送时间">
           {{ parseTime(form.createTime) }}
-        </el-descriptions-item>
-        <el-descriptions-item label="接收人数">
+        </a-descriptions-item>
+        <a-descriptions-item label="接收人数">
           {{ form.receiverCount || 0 }}人
-        </el-descriptions-item>
-        <el-descriptions-item label="已读/未读">
-          <span style="color: #67C23A;">{{ form.readCount || 0 }}</span>
+        </a-descriptions-item>
+        <a-descriptions-item label="已读/未读">
+          <span style="color: #52c41a;">{{ form.readCount || 0 }}</span>
           /
-          <span style="color: #F56C6C;">{{ form.unreadCount || 0 }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="消息内容" :span="2">
+          <span style="color: #f5222d;">{{ form.unreadCount || 0 }}</span>
+        </a-descriptions-item>
+        <a-descriptions-item label="消息内容" :span="2">
           <div class="message-content">{{ form.content }}</div>
-        </el-descriptions-item>
-        <el-descriptions-item label="接收人列表" :span="2" v-if="receiverList.length > 0">
-          <el-table :data="receiverList" size="small" max-height="300">
-            <el-table-column label="用户名" prop="userName" width="120" />
-            <el-table-column label="阅读状态" align="center" width="100">
-              <template #default="scope">
-                <el-tag v-if="scope.row.isRead === '0'" type="danger" size="small">未读</el-tag>
-                <el-tag v-else type="success" size="small">已读</el-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="接收人列表" :span="2" v-if="receiverList.length > 0">
+          <a-table :dataSource="receiverList" size="small" :pagination="false" :scroll="{ y: 300 }" rowKey="userId">
+            <a-table-column title="用户名" dataIndex="userName" width="120" />
+            <a-table-column title="阅读状态" align="center" width="100">
+              <template #default="{ record }">
+                <a-tag v-if="record.isRead === '0'" color="red">未读</a-tag>
+                <a-tag v-else color="green">已读</a-tag>
               </template>
-            </el-table-column>
-            <el-table-column label="阅读时间" prop="readTime" width="160">
-              <template #default="scope">
-                <span v-if="scope.row.readTime">{{ parseTime(scope.row.readTime, '{y}-{m}-{d} {h}:{i}') }}</span>
-                <span v-else style="color: #909399;">未读</span>
+            </a-table-column>
+            <a-table-column title="阅读时间" width="160">
+              <template #default="{ record }">
+                <span v-if="record.readTime">{{ parseTime(record.readTime, '{y}-{m}-{d} {h}:{i}') }}</span>
+                <span v-else style="color: #999;">未读</span>
               </template>
-            </el-table-column>
-            <el-table-column label="接收时间" prop="createTime" width="160">
-              <template #default="scope">
-                {{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}
+            </a-table-column>
+            <a-table-column title="接收时间" width="160">
+              <template #default="{ record }">
+                {{ parseTime(record.createTime, '{y}-{m}-{d} {h}:{i}') }}
               </template>
-            </el-table-column>
-          </el-table>
-        </el-descriptions-item>
-      </el-descriptions>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="viewOpen = false">关 闭</el-button>
-        </div>
-      </template>
-    </el-dialog>
+            </a-table-column>
+          </a-table>
+        </a-descriptions-item>
+      </a-descriptions>
+    </a-modal>
 
     <!-- 发送消息对话框 -->
     <send-message-dialog ref="sendDialogRef" @success="handleSendSuccess" />
@@ -191,12 +196,14 @@
 </template>
 
 <script setup name="MessageManagement">
+import { SearchOutlined, ReloadOutlined, SendOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'
 import { listNotification, getNotification, delNotification } from "@/api/system/message"
 import SendMessageDialog from './components/SendMessageDialog.vue'
 
 const { proxy } = getCurrentInstance()
 
 const loading = ref(true)
+const selectedRowKeys = ref([])
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
@@ -217,6 +224,17 @@ const viewOpen = ref(false)
 const sendDialogRef = ref(null)
 const queryFormRef = ref(null)
 
+const columns = [
+  { title: '消息ID', dataIndex: 'messageId', key: 'messageId', width: 80, align: 'center' },
+  { title: '优先级', key: 'priority', width: 80, align: 'center' },
+  { title: '消息标题', dataIndex: 'title', key: 'title', ellipsis: true, minWidth: 200 },
+  { title: '发送者', key: 'senderName', width: 120, align: 'center' },
+  { title: '接收人数', key: 'receiverCount', width: 100, align: 'center' },
+  { title: '已读/未读', key: 'readStatus', width: 100, align: 'center' },
+  { title: '发送时间', key: 'createTime', width: 160, align: 'center' },
+  { title: '操作', key: 'action', width: 150, align: 'center', fixed: 'right' }
+]
+
 /** 查询系统消息列表 */
 function getList() {
   loading.value = true
@@ -240,10 +258,11 @@ function resetQuery() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.messageId)
-  single.value = selection.length !== 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = !keys.length
 }
 
 /** 发送消息按钮操作 */
@@ -292,13 +311,4 @@ getList()
   white-space: pre-wrap;
   word-break: break-word;
 }
-
-:deep(.message-detail-dialog) {
-  .el-descriptions {
-    .el-descriptions__label {
-      background: #fafafa;
-      font-weight: 500;
-    }
-  }
-}
 </style>

+ 143 - 154
yushu-uivue3/src/views/system/notice/index.vue

@@ -1,105 +1,76 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="公告标题" prop="noticeTitle">
-            <el-input
-               v-model="queryParams.noticeTitle"
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="公告标题" name="noticeTitle">
+            <a-input
+               v-model:value="queryParams.noticeTitle"
                placeholder="请输入公告标题"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="操作人员" prop="createBy">
-            <el-input
-               v-model="queryParams.createBy"
+         </a-form-item>
+         <a-form-item label="操作人员" name="createBy">
+            <a-input
+               v-model:value="queryParams.createBy"
                placeholder="请输入操作人员"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="类型" prop="noticeType">
-            <el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable style="width: 200px">
-               <el-option
+         </a-form-item>
+         <a-form-item label="类型" name="noticeType">
+            <a-select v-model:value="queryParams.noticeType" placeholder="公告类型" allow-clear style="width: 200px">
+               <a-select-option
                   v-for="dict in sys_notice_type"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:notice:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:notice:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:notice:remove']"
-            >删除</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd" v-hasPermi="['system:notice:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:notice:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:notice:remove']"><DeleteOutlined />删除</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="序号" align="center" prop="noticeId" width="100" />
-         <el-table-column
-            label="公告标题"
-            align="center"
-            prop="noticeTitle"
-            :show-overflow-tooltip="true"
-         />
-         <el-table-column label="公告类型" align="center" prop="noticeType" width="100">
-            <template #default="scope">
-               <dict-tag :options="sys_notice_type" :value="scope.row.noticeType" />
-            </template>
-         </el-table-column>
-         <el-table-column label="状态" align="center" prop="status" width="100">
-            <template #default="scope">
-               <dict-tag :options="sys_notice_status" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="创建者" align="center" prop="createBy" width="100" />
-         <el-table-column label="创建时间" align="center" prop="createTime" width="100">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:edit']">修改</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:notice:remove']" >删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="noticeList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.noticeId"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'noticeType'">
+            <dict-tag :options="sys_notice_type" :value="record.noticeType" />
+          </template>
+          <template v-else-if="column.key === 'status'">
+            <dict-tag :options="sys_notice_status" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime, '{y}-{m}-{d}') }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:notice:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:notice:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -110,56 +81,55 @@
       />
 
       <!-- 添加或修改公告对话框 -->
-      <el-dialog :title="title" v-model="open" width="780px" append-to-body>
-         <el-form ref="noticeRef" :model="form" :rules="rules" label-width="80px">
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item label="公告标题" prop="noticeTitle">
-                     <el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="公告类型" prop="noticeType">
-                     <el-select v-model="form.noticeType" placeholder="请选择">
-                        <el-option
+      <a-modal :title="title" v-model:open="open" width="780px" :footer="null">
+         <a-form ref="noticeRef" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
+            <a-row>
+               <a-col :span="12">
+                  <a-form-item label="公告标题" name="noticeTitle">
+                     <a-input v-model:value="form.noticeTitle" placeholder="请输入公告标题" />
+                  </a-form-item>
+               </a-col>
+               <a-col :span="12">
+                  <a-form-item label="公告类型" name="noticeType">
+                     <a-select v-model:value="form.noticeType" placeholder="请选择">
+                        <a-select-option
                            v-for="dict in sys_notice_type"
                            :key="dict.value"
-                           :label="dict.label"
                            :value="dict.value"
-                        ></el-option>
-                     </el-select>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="24">
-                  <el-form-item label="状态">
-                     <el-radio-group v-model="form.status">
-                        <el-radio
+                        >{{ dict.label }}</a-select-option>
+                     </a-select>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="24">
+                  <a-form-item label="状态" :label-col="{ span: 2 }" :wrapper-col="{ span: 20 }">
+                     <a-radio-group v-model:value="form.status">
+                        <a-radio
                            v-for="dict in sys_notice_status"
                            :key="dict.value"
                            :value="dict.value"
-                        >{{ dict.label }}</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="24">
-                  <el-form-item label="内容">
+                        >{{ dict.label }}</a-radio>
+                     </a-radio-group>
+                  </a-form-item>
+               </a-col>
+               <a-col :span="24">
+                  <a-form-item label="内容" :label-col="{ span: 2 }" :wrapper-col="{ span: 20 }">
                     <editor v-model="form.noticeContent" :min-height="192"/>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  </a-form-item>
+               </a-col>
+            </a-row>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
 <script setup name="Notice">
 import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_notice_status, sys_notice_type } = proxy.useDict("sys_notice_status", "sys_notice_type")
@@ -169,11 +139,22 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 
+const tableColumns = [
+  { title: '序号', dataIndex: 'noticeId', key: 'noticeId', align: 'center', width: 100 },
+  { title: '公告标题', dataIndex: 'noticeTitle', key: 'noticeTitle', align: 'center', ellipsis: true },
+  { title: '公告类型', key: 'noticeType', align: 'center', width: 100 },
+  { title: '状态', key: 'status', align: 'center', width: 100 },
+  { title: '创建者', dataIndex: 'createBy', key: 'createBy', align: 'center', width: 100 },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 100 },
+  { title: '操作', key: 'action', align: 'center' }
+]
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -216,7 +197,6 @@ function reset() {
     noticeContent: undefined,
     status: "0"
   }
-  proxy.resetForm("noticeRef")
 }
 
 /** 搜索按钮操作 */
@@ -227,15 +207,22 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    noticeTitle: undefined,
+    createBy: undefined,
+    status: undefined
+  }
   handleQuery()
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.noticeId)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 新增按钮操作 */
@@ -258,34 +245,36 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["noticeRef"].validate(valid => {
-    if (valid) {
-      if (form.value.noticeId != undefined) {
-        updateNotice(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addNotice(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["noticeRef"].validate().then(() => {
+    if (form.value.noticeId != undefined) {
+      updateNotice(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addNotice(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const noticeIds = row.noticeId || ids.value
-  proxy.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?').then(function() {
-    return delNotice(noticeIds)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除公告编号为"' + noticeIds + '"的数据项?',
+    onOk() {
+      return delNotice(noticeIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 getList()

+ 224 - 202
yushu-uivue3/src/views/system/notification/index.vue

@@ -1,130 +1,145 @@
 <template>
   <div class="app-container">
     <!-- 页面说明 -->
-    <el-alert
-      title="系统通知管理"
+    <a-alert
+      message="系统通知管理"
       type="info"
       :closable="false"
       style="margin-bottom: 16px"
     >
-      <div style="line-height: 1.8; font-size: 13px;">
-        此页面用于<strong>管理员发布系统通知</strong>。<br/>
-        • 支持发送给全部用户、指定用户或指定角色<br/>
-        • 发布后用户将实时收到WebSocket推送通知
-      </div>
-    </el-alert>
+      <template #description>
+        <div style="line-height: 1.8; font-size: 13px;">
+          此页面用于<strong>管理员发布系统通知</strong>。<br/>
+          • 支持发送给全部用户、指定用户或指定角色<br/>
+          • 发布后用户将实时收到WebSocket推送通知
+        </div>
+      </template>
+    </a-alert>
 
     <!-- 搜索栏 -->
-    <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
-      <el-form-item label="通知标题" prop="title">
-        <el-input
-          v-model="queryParams.title"
+    <a-form :model="queryParams" ref="queryFormRef" layout="inline" v-show="showSearch" class="mb8">
+      <a-form-item label="通知标题" name="title">
+        <a-input
+          v-model:value="queryParams.title"
           placeholder="请输入通知标题"
-          clearable
+          allowClear
           style="width: 200px"
-          @keyup.enter="handleQuery"
+          @pressEnter="handleQuery"
         />
-      </el-form-item>
-      <el-form-item label="通知类型" prop="notificationType">
-        <el-select v-model="queryParams.notificationType" placeholder="请选择通知类型" clearable style="width: 120px">
-          <el-option label="系统通知" value="system" />
-          <el-option label="维护通知" value="maintenance" />
-          <el-option label="公告" value="announcement" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="优先级" prop="priority">
-        <el-select v-model="queryParams.priority" placeholder="请选择优先级" clearable style="width: 120px">
-          <el-option label="普通" value="0" />
-          <el-option label="重要" value="1" />
-          <el-option label="紧急" value="2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+      </a-form-item>
+      <a-form-item label="通知类型" name="notificationType">
+        <a-select v-model:value="queryParams.notificationType" placeholder="请选择通知类型" allowClear style="width: 120px">
+          <a-select-option value="system">系统通知</a-select-option>
+          <a-select-option value="maintenance">维护通知</a-select-option>
+          <a-select-option value="announcement">公告</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="优先级" name="priority">
+        <a-select v-model:value="queryParams.priority" placeholder="请选择优先级" allowClear style="width: 120px">
+          <a-select-option value="0">普通</a-select-option>
+          <a-select-option value="1">重要</a-select-option>
+          <a-select-option value="2">紧急</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery">
+          <template #icon><SearchOutlined /></template>
+          搜索
+        </a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery">
+          <template #icon><ReloadOutlined /></template>
+          重置
+        </a-button>
+      </a-form-item>
+    </a-form>
 
     <!-- 操作按钮 -->
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button
           type="primary"
-          icon="Plus"
           @click="handleAdd"
           v-hasPermi="['system:notification:add']"
-        >新增通知</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="danger"
-          icon="Delete"
+        >
+          <template #icon><PlusOutlined /></template>
+          新增通知
+        </a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button
+          danger
           :disabled="multiple"
           @click="handleDelete"
           v-hasPermi="['system:notification:remove']"
-        >删除</el-button>
-      </el-col>
+        >
+          <template #icon><DeleteOutlined /></template>
+          删除
+        </a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+    </a-row>
 
     <!-- 通知列表 -->
-    <el-table v-loading="loading" :data="notificationList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="通知标题" align="center" prop="title" :show-overflow-tooltip="true" />
-      <el-table-column label="通知类型" align="center" prop="notificationType" width="100">
-        <template #default="scope">
-          <el-tag v-if="scope.row.notificationType === 'system'" type="primary" size="small">系统通知</el-tag>
-          <el-tag v-else-if="scope.row.notificationType === 'maintenance'" type="warning" size="small">维护通知</el-tag>
-          <el-tag v-else-if="scope.row.notificationType === 'announcement'" type="success" size="small">公告</el-tag>
+    <a-table 
+      :loading="loading" 
+      :dataSource="notificationList" 
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :pagination="false"
+      rowKey="notificationId"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'notificationType'">
+          <a-tag v-if="record.notificationType === 'system'" color="blue">系统通知</a-tag>
+          <a-tag v-else-if="record.notificationType === 'maintenance'" color="orange">维护通知</a-tag>
+          <a-tag v-else-if="record.notificationType === 'announcement'" color="green">公告</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="优先级" align="center" prop="priority" width="80">
-        <template #default="scope">
-          <el-tag v-if="scope.row.priority === '0'" type="info" size="small">普通</el-tag>
-          <el-tag v-else-if="scope.row.priority === '1'" type="warning" size="small">重要</el-tag>
-          <el-tag v-else-if="scope.row.priority === '2'" type="danger" size="small">紧急</el-tag>
+        <template v-else-if="column.key === 'priority'">
+          <a-tag v-if="record.priority === '0'" color="default">普通</a-tag>
+          <a-tag v-else-if="record.priority === '1'" color="orange">重要</a-tag>
+          <a-tag v-else-if="record.priority === '2'" color="red">紧急</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="发送者" align="center" prop="senderName" width="100" />
-      <el-table-column label="状态" align="center" prop="status" width="80">
-        <template #default="scope">
-          <el-tag v-if="scope.row.status === '0'" type="info" size="small">草稿</el-tag>
-          <el-tag v-else-if="scope.row.status === '1'" type="success" size="small">已发布</el-tag>
-          <el-tag v-else-if="scope.row.status === '2'" type="danger" size="small">已撤回</el-tag>
+        <template v-else-if="column.key === 'status'">
+          <a-tag v-if="record.status === '0'" color="default">草稿</a-tag>
+          <a-tag v-else-if="record.status === '1'" color="green">已发布</a-tag>
+          <a-tag v-else-if="record.status === '2'" color="red">已撤回</a-tag>
         </template>
-      </el-table-column>
-      <el-table-column label="发布时间" align="center" prop="publishTime" width="180">
-        <template #default="scope">
-          <span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        <template v-else-if="column.key === 'publishTime'">
+          <span>{{ parseTime(record.publishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
         </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            icon="Edit"
-            @click="handleUpdate(scope.row)"
+        <template v-else-if="column.key === 'action'">
+          <a-button
+            type="link"
+            size="small"
+            @click="handleUpdate(record)"
             v-hasPermi="['system:notification:edit']"
-          >修改</el-button>
-          <el-button
-            v-if="scope.row.status === '0'"
-            link
-            type="primary"
-            icon="Promotion"
-            @click="handlePublish(scope.row)"
+          >
+            <template #icon><EditOutlined /></template>
+            修改
+          </a-button>
+          <a-button
+            v-if="record.status === '0'"
+            type="link"
+            size="small"
+            @click="handlePublish(record)"
             v-hasPermi="['system:notification:publish']"
-          >发布</el-button>
-          <el-button
-            link
-            type="primary"
-            icon="Delete"
-            @click="handleDelete(scope.row)"
+          >
+            <template #icon><SendOutlined /></template>
+            发布
+          </a-button>
+          <a-button
+            type="link"
+            danger
+            size="small"
+            @click="handleDelete(record)"
             v-hasPermi="['system:notification:remove']"
-          >删除</el-button>
+          >
+            <template #icon><DeleteOutlined /></template>
+            删除
+          </a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <!-- 分页 -->
     <pagination
@@ -136,106 +151,103 @@
     />
 
     <!-- 添加或修改通知对话框 -->
-    <el-dialog :title="title" v-model="open" width="800px" append-to-body>
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="通知标题" prop="title">
-              <el-input v-model="form.title" placeholder="请输入通知标题" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="通知类型" prop="notificationType">
-              <el-select v-model="form.notificationType" placeholder="请选择通知类型" style="width: 100%">
-                <el-option label="系统通知" value="system" />
-                <el-option label="维护通知" value="maintenance" />
-                <el-option label="公告" value="announcement" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="优先级" prop="priority">
-              <el-select v-model="form.priority" placeholder="请选择优先级" style="width: 100%">
-                <el-option label="普通" value="0" />
-                <el-option label="重要" value="1" />
-                <el-option label="紧急" value="2" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="目标类型" prop="targetType">
-              <el-radio-group v-model="form.targetType">
-                <el-radio value="1">全部用户</el-radio>
-                <el-radio value="2">指定用户</el-radio>
-                <el-radio value="3">指定角色</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row v-if="form.targetType === '2'">
-          <el-col :span="24">
-            <el-form-item label="目标用户" prop="targetUsers">
-              <el-select
-                v-model="selectedUsers"
-                multiple
-                filterable
-                remote
-                reserve-keyword
+    <a-modal :title="title" v-model:open="open" width="800px" :maskClosable="false">
+      <a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 4 }">
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="通知标题" name="title">
+              <a-input v-model:value="form.title" placeholder="请输入通知标题" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="通知类型" name="notificationType">
+              <a-select v-model:value="form.notificationType" placeholder="请选择通知类型" style="width: 100%">
+                <a-select-option value="system">系统通知</a-select-option>
+                <a-select-option value="maintenance">维护通知</a-select-option>
+                <a-select-option value="announcement">公告</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="优先级" name="priority">
+              <a-select v-model:value="form.priority" placeholder="请选择优先级" style="width: 100%">
+                <a-select-option value="0">普通</a-select-option>
+                <a-select-option value="1">重要</a-select-option>
+                <a-select-option value="2">紧急</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="目标类型" name="targetType">
+              <a-radio-group v-model:value="form.targetType">
+                <a-radio value="1">全部用户</a-radio>
+                <a-radio value="2">指定用户</a-radio>
+                <a-radio value="3">指定角色</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row v-if="form.targetType === '2'">
+          <a-col :span="24">
+            <a-form-item label="目标用户" name="targetUsers">
+              <a-select
+                v-model:value="selectedUsers"
+                mode="multiple"
+                :filter-option="false"
                 placeholder="请选择用户"
-                :remote-method="searchUsers"
+                @search="searchUsers"
                 :loading="userLoading"
                 style="width: 100%"
               >
-                <el-option
+                <a-select-option
                   v-for="user in userOptions"
                   :key="user.userId"
-                  :label="user.nickName || user.userName"
                   :value="user.userId"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="通知内容" prop="content">
-              <el-input
-                v-model="form.content"
-                type="textarea"
+                >
+                  {{ user.nickName || user.userName }}
+                </a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="通知内容" name="content">
+              <a-textarea
+                v-model:value="form.content"
                 :rows="6"
                 placeholder="请输入通知内容"
               />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="过期时间">
-              <el-date-picker
-                v-model="form.expireTime"
-                type="datetime"
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="过期时间">
+              <a-date-picker
+                v-model:value="form.expireTime"
+                show-time
                 placeholder="选择过期时间"
                 style="width: 100%"
               />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
       <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
+        <a-button @click="cancel">取 消</a-button>
+        <a-button type="primary" @click="submitForm">确 定</a-button>
       </template>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="Notification">
+import { SearchOutlined, ReloadOutlined, PlusOutlined, DeleteOutlined, EditOutlined, SendOutlined } from '@ant-design/icons-vue'
 import { 
   listNotification, 
   getNotification, 
@@ -249,6 +261,7 @@ import { listUser } from "@/api/system/user"
 const { proxy } = getCurrentInstance()
 
 const loading = ref(true)
+const selectedRowKeys = ref([])
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
@@ -260,6 +273,16 @@ const open = ref(false)
 const queryFormRef = ref(null)
 const formRef = ref(null)
 
+const columns = [
+  { title: '通知标题', dataIndex: 'title', key: 'title', ellipsis: true },
+  { title: '通知类型', dataIndex: 'notificationType', key: 'notificationType', width: 100, align: 'center' },
+  { title: '优先级', dataIndex: 'priority', key: 'priority', width: 80, align: 'center' },
+  { title: '发送者', dataIndex: 'senderName', key: 'senderName', width: 100, align: 'center' },
+  { title: '状态', dataIndex: 'status', key: 'status', width: 80, align: 'center' },
+  { title: '发布时间', dataIndex: 'publishTime', key: 'publishTime', width: 180, align: 'center' },
+  { title: '操作', key: 'action', width: 200, align: 'center', fixed: 'right' }
+]
+
 const queryParams = ref({
   pageNum: 1,
   pageSize: 10,
@@ -339,10 +362,11 @@ function resetQuery() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.notificationId)
-  single.value = selection.length !== 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = !keys.length
 }
 
 /** 新增按钮操作 */
@@ -368,27 +392,25 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  formRef.value.validate(valid => {
-    if (valid) {
-      if (form.value.targetType === '2' && selectedUsers.value.length > 0) {
-        form.value.targetUsers = JSON.stringify(selectedUsers.value)
-      } else {
-        form.value.targetUsers = null
-      }
-      
-      if (form.value.notificationId != null) {
-        updateNotification(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addNotification(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  formRef.value.validate().then(() => {
+    if (form.value.targetType === '2' && selectedUsers.value.length > 0) {
+      form.value.targetUsers = JSON.stringify(selectedUsers.value)
+    } else {
+      form.value.targetUsers = null
+    }
+    
+    if (form.value.notificationId != null) {
+      updateNotification(form.value).then(response => {
+        proxy.$modal.msgSuccess("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addNotification(form.value).then(response => {
+        proxy.$modal.msgSuccess("新增成功")
+        open.value = false
+        getList()
+      })
     }
   })
 }

+ 181 - 159
yushu-uivue3/src/views/system/param/index.vue

@@ -1,119 +1,128 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="参数名称" prop="configName">
-        <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="参数键名" prop="configKey">
-        <el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable style="width: 240px" @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="系统内置" prop="configType">
-        <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
-          <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="创建时间" style="width: 308px">
-        <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:config:add']">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:config:edit']">修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:config:remove']">删除</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:config:export']">导出</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:config:remove']">刷新缓存</el-button>
-      </el-col>
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+      <a-form-item label="参数名称" name="configName">
+        <a-input v-model:value="queryParams.configName" placeholder="请输入参数名称" allow-clear style="width: 240px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="参数键名" name="configKey">
+        <a-input v-model:value="queryParams.configKey" placeholder="请输入参数键名" allow-clear style="width: 240px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="系统内置" name="configType">
+        <a-select v-model:value="queryParams.configType" placeholder="系统内置" allow-clear>
+          <a-select-option v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="创建时间" style="width: 308px">
+        <a-range-picker v-model:value="dateRange" value-format="YYYY-MM-DD" />
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+      </a-form-item>
+    </a-form>
+
+    <a-row :gutter="10" class="mb8">
+      <a-col :span="1.5">
+        <a-button type="primary" @click="handleAdd" v-hasPermi="['system:config:add']"><PlusOutlined />新增</a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:config:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:config:remove']"><DeleteOutlined />删除</a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:config:export']"><DownloadOutlined />导出</a-button>
+      </a-col>
+      <a-col :span="1.5">
+        <a-button danger @click="handleRefreshCache" v-hasPermi="['system:config:remove']"><ReloadOutlined />刷新缓存</a-button>
+      </a-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="参数主键" align="center" prop="configId" />
-      <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
-      <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
-      <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
-      <el-table-column label="系统内置" align="center" prop="configType">
-        <template #default="scope">
-          <dict-tag :options="sys_yes_no" :value="scope.row.configType" />
+    </a-row>
+
+    <a-table
+      :loading="loading"
+      :data-source="configList"
+      :columns="tableColumns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :row-key="record => record.configId"
+      :pagination="false"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'configType'">
+          <dict-tag :options="sys_yes_no" :value="record.configType" />
         </template>
-      </el-table-column>
-      <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-        <template #default="scope">
-          <span>{{ parseTime(scope.row.createTime) }}</span>
+        <template v-else-if="column.key === 'createTime'">
+          <span>{{ parseTime(record.createTime) }}</span>
         </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']">修改</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']">删除</el-button>
+        <template v-else-if="column.key === 'action'">
+          <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:config:edit']"><EditOutlined />修改</a-button>
+          <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:config:remove']"><DeleteOutlined />删除</a-button>
         </template>
-      </el-table-column>
-    </el-table>
+      </template>
+    </a-table>
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
 
     <!-- 添加或修改参数配置对话框 -->
-    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-      <el-form ref="configRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="参数名称" prop="configName">
-          <el-input v-model="form.configName" placeholder="请输入参数名称" />
-        </el-form-item>
-        <el-form-item label="参数键名" prop="configKey">
-          <el-input v-model="form.configKey" placeholder="请输入参数键名" />
-        </el-form-item>
-        <el-form-item label="参数键值" prop="configValue">
-          <el-input v-model="form.configValue" placeholder="请输入参数键值" />
-        </el-form-item>
-        <el-form-item label="系统内置" prop="configType">
-          <el-radio-group v-model="form.configType">
-            <el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
+    <a-modal :title="title" v-model:open="open" width="500px" :footer="null">
+      <a-form ref="configRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+        <a-form-item label="参数名称" name="configName">
+          <a-input v-model:value="form.configName" placeholder="请输入参数名称" />
+        </a-form-item>
+        <a-form-item label="参数键名" name="configKey">
+          <a-input v-model:value="form.configKey" placeholder="请输入参数键名" />
+        </a-form-item>
+        <a-form-item label="参数键值" name="configValue">
+          <a-input v-model:value="form.configValue" placeholder="请输入参数键值" />
+        </a-form-item>
+        <a-form-item label="系统内置" name="configType">
+          <a-radio-group v-model:value="form.configType">
+            <a-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item label="备注" name="remark">
+          <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+        </a-form-item>
+      </a-form>
+      <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+        <a-button type="primary" @click="submitForm">确 定</a-button>
+        <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+      </div>
+    </a-modal>
   </div>
 </template>
 
 <script setup name="Param">
-import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config";
-
-const { proxy } = getCurrentInstance();
-const { sys_yes_no } = proxy.useDict("sys_yes_no");
-
-const configList = ref([]);
-const open = ref(false);
-const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref([]);
-const single = ref(true);
-const multiple = ref(true);
-const total = ref(0);
-const title = ref("");
-const dateRange = ref([]);
+import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined } from '@ant-design/icons-vue'
+
+const { proxy } = getCurrentInstance()
+const { sys_yes_no } = proxy.useDict("sys_yes_no")
+
+const configList = ref([])
+const open = ref(false)
+const loading = ref(true)
+const showSearch = ref(true)
+const ids = ref([])
+const selectedRowKeys = ref([])
+const single = ref(true)
+const multiple = ref(true)
+const total = ref(0)
+const title = ref("")
+const dateRange = ref([])
+
+const tableColumns = [
+  { title: '参数主键', dataIndex: 'configId', key: 'configId', align: 'center' },
+  { title: '参数名称', dataIndex: 'configName', key: 'configName', align: 'center', ellipsis: true },
+  { title: '参数键名', dataIndex: 'configKey', key: 'configKey', align: 'center', ellipsis: true },
+  { title: '参数键值', dataIndex: 'configValue', key: 'configValue', align: 'center', ellipsis: true },
+  { title: '系统内置', key: 'configType', align: 'center' },
+  { title: '备注', dataIndex: 'remark', key: 'remark', align: 'center', ellipsis: true },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 },
+  { title: '操作', key: 'action', align: 'center', width: 150 }
+]
 
 const data = reactive({
   form: {},
@@ -129,22 +138,27 @@ const data = reactive({
     configKey: [{ required: true, message: "参数键名不能为空", trigger: "blur" }],
     configValue: [{ required: true, message: "参数键值不能为空", trigger: "blur" }]
   }
-});
+})
 
-const { queryParams, form, rules } = toRefs(data);
+const { queryParams, form, rules } = toRefs(data)
 
 function getList() {
-  loading.value = true;
-  listConfig(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
-    configList.value = response.rows;
-    total.value = response.total;
-    loading.value = false;
-  });
+  loading.value = true
+  const params = { ...queryParams.value }
+  if (dateRange.value && dateRange.value.length === 2) {
+    params.beginTime = dateRange.value[0]
+    params.endTime = dateRange.value[1]
+  }
+  listConfig(params).then(response => {
+    configList.value = response.rows
+    total.value = response.total
+    loading.value = false
+  })
 }
 
 function cancel() {
-  open.value = false;
-  reset();
+  open.value = false
+  reset()
 }
 
 function reset() {
@@ -155,82 +169,90 @@ function reset() {
     configValue: undefined,
     configType: "Y",
     remark: undefined
-  };
-  proxy.resetForm("configRef");
+  }
 }
 
 function handleQuery() {
-  queryParams.value.pageNum = 1;
-  getList();
+  queryParams.value.pageNum = 1
+  getList()
 }
 
 function resetQuery() {
-  dateRange.value = [];
-  proxy.resetForm("queryRef");
-  handleQuery();
+  dateRange.value = []
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    configName: undefined,
+    configKey: undefined,
+    configType: undefined
+  }
+  handleQuery()
 }
 
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.configId);
-  single.value = selection.length != 1;
-  multiple.value = !selection.length;
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 function handleAdd() {
-  reset();
-  open.value = true;
-  title.value = "添加参数";
+  reset()
+  open.value = true
+  title.value = "添加参数"
 }
 
 function handleUpdate(row) {
-  reset();
-  const configId = row.configId || ids.value;
+  reset()
+  const configId = row.configId || ids.value
   getConfig(configId).then(response => {
-    form.value = response.data;
-    open.value = true;
-    title.value = "修改参数";
-  });
+    form.value = response.data
+    open.value = true
+    title.value = "修改参数"
+  })
 }
 
 function submitForm() {
-  proxy.$refs["configRef"].validate(valid => {
-    if (valid) {
-      if (form.value.configId != undefined) {
-        updateConfig(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功");
-          open.value = false;
-          getList();
-        });
-      } else {
-        addConfig(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功");
-          open.value = false;
-          getList();
-        });
-      }
+  proxy.$refs["configRef"].validate().then(() => {
+    if (form.value.configId != undefined) {
+      updateConfig(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addConfig(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  });
+  }).catch(() => {})
 }
 
 function handleDelete(row) {
-  const configIds = row.configId || ids.value;
-  proxy.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?').then(function () {
-    return delConfig(configIds);
-  }).then(() => {
-    getList();
-    proxy.$modal.msgSuccess("删除成功");
-  }).catch(() => {});
+  const configIds = row.configId || ids.value
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除参数编号为"' + configIds + '"的数据项?',
+    onOk() {
+      return delConfig(configIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 function handleExport() {
-  proxy.download("system/config/export", { ...queryParams.value }, `config_${new Date().getTime()}.xlsx`);
+  proxy.download("system/config/export", { ...queryParams.value }, `config_${new Date().getTime()}.xlsx`)
 }
 
 function handleRefreshCache() {
   refreshCache().then(() => {
-    proxy.$modal.msgSuccess("刷新缓存成功");
-  });
+    message.success("刷新缓存成功")
+  })
 }
 
-getList();
+getList()
 </script>

+ 134 - 144
yushu-uivue3/src/views/system/post/index.vue

@@ -1,105 +1,76 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-         <el-form-item label="岗位编码" prop="postCode">
-            <el-input
-               v-model="queryParams.postCode"
+      <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+         <a-form-item label="岗位编码" name="postCode">
+            <a-input
+               v-model:value="queryParams.postCode"
                placeholder="请输入岗位编码"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="岗位名称" prop="postName">
-            <el-input
-               v-model="queryParams.postName"
+         </a-form-item>
+         <a-form-item label="岗位名称" name="postName">
+            <a-input
+               v-model:value="queryParams.postName"
                placeholder="请输入岗位名称"
-               clearable
+               allow-clear
                style="width: 200px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select v-model="queryParams.status" placeholder="岗位状态" clearable style="width: 200px">
-               <el-option
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select v-model:value="queryParams.status" placeholder="岗位状态" allow-clear style="width: 200px">
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
 
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:post:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:post:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:post:remove']"
-            >删除</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Download"
-               @click="handleExport"
-               v-hasPermi="['system:post:export']"
-            >导出</el-button>
-         </el-col>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd" v-hasPermi="['system:post:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:post:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:post:remove']"><DeleteOutlined />删除</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:post:export']"><DownloadOutlined />导出</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
-      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="岗位编号" align="center" prop="postId" />
-         <el-table-column label="岗位编码" align="center" prop="postCode" />
-         <el-table-column label="岗位名称" align="center" prop="postName" />
-         <el-table-column label="岗位排序" align="center" prop="postSort" />
-         <el-table-column label="状态" align="center" prop="status">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']">修改</el-button>
-               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']">删除</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="postList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.postId"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:post:edit']"><EditOutlined />修改</a-button>
+            <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:post:remove']"><DeleteOutlined />删除</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -110,42 +81,42 @@
       />
 
       <!-- 添加或修改岗位对话框 -->
-      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-         <el-form ref="postRef" :model="form" :rules="rules" label-width="80px">
-            <el-form-item label="岗位名称" prop="postName">
-               <el-input v-model="form.postName" placeholder="请输入岗位名称" />
-            </el-form-item>
-            <el-form-item label="岗位编码" prop="postCode">
-               <el-input v-model="form.postCode" placeholder="请输入编码名称" />
-            </el-form-item>
-            <el-form-item label="岗位顺序" prop="postSort">
-               <el-input-number v-model="form.postSort" controls-position="right" :min="0" />
-            </el-form-item>
-            <el-form-item label="岗位状态" prop="status">
-               <el-radio-group v-model="form.status">
-                  <el-radio
+      <a-modal :title="title" v-model:open="open" width="500px" :footer="null">
+         <a-form ref="postRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-form-item label="岗位名称" name="postName">
+               <a-input v-model:value="form.postName" placeholder="请输入岗位名称" />
+            </a-form-item>
+            <a-form-item label="岗位编码" name="postCode">
+               <a-input v-model:value="form.postCode" placeholder="请输入编码名称" />
+            </a-form-item>
+            <a-form-item label="岗位顺序" name="postSort">
+               <a-input-number v-model:value="form.postSort" :min="0" style="width: 100%" />
+            </a-form-item>
+            <a-form-item label="岗位状态" name="status">
+               <a-radio-group v-model:value="form.status">
+                  <a-radio
                      v-for="dict in sys_normal_disable"
                      :key="dict.value"
                      :value="dict.value"
-                  >{{ dict.label }}</el-radio>
-               </el-radio-group>
-            </el-form-item>
-            <el-form-item label="备注" prop="remark">
-               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  >{{ dict.label }}</a-radio>
+               </a-radio-group>
+            </a-form-item>
+            <a-form-item label="备注" name="remark">
+               <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+            </a-form-item>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
 <script setup name="Post">
 import { listPost, addPost, delPost, getPost, updatePost } from "@/api/system/post"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined } from '@ant-design/icons-vue'
 
 const { proxy } = getCurrentInstance()
 const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
@@ -155,11 +126,22 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 
+const tableColumns = [
+  { title: '岗位编号', dataIndex: 'postId', key: 'postId', align: 'center' },
+  { title: '岗位编码', dataIndex: 'postCode', key: 'postCode', align: 'center' },
+  { title: '岗位名称', dataIndex: 'postName', key: 'postName', align: 'center' },
+  { title: '岗位排序', dataIndex: 'postSort', key: 'postSort', align: 'center' },
+  { title: '状态', key: 'status', align: 'center' },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 },
+  { title: '操作', key: 'action', align: 'center', width: 180 }
+]
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -204,7 +186,6 @@ function reset() {
     status: "0",
     remark: undefined
   }
-  proxy.resetForm("postRef")
 }
 
 /** 搜索按钮操作 */
@@ -215,15 +196,22 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    postCode: undefined,
+    postName: undefined,
+    status: undefined
+  }
   handleQuery()
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.postId)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 新增按钮操作 */
@@ -246,34 +234,36 @@ function handleUpdate(row) {
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["postRef"].validate(valid => {
-    if (valid) {
-      if (form.value.postId != undefined) {
-        updatePost(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addPost(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["postRef"].validate().then(() => {
+    if (form.value.postId != undefined) {
+      updatePost(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addPost(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const postIds = row.postId || ids.value
-  proxy.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function() {
-    return delPost(postIds)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除岗位编号为"' + postIds + '"的数据项?',
+    onOk() {
+      return delPost(postIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 /** 导出按钮操作 */

+ 91 - 89
yushu-uivue3/src/views/system/role/authUser.vue

@@ -1,84 +1,63 @@
-
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
-         <el-form-item label="用户名称" prop="userName">
-            <el-input
-               v-model="queryParams.userName"
+      <a-form :model="queryParams" ref="queryRef" v-show="showSearch" layout="inline">
+         <a-form-item label="用户名称" name="userName">
+            <a-input
+               v-model:value="queryParams.userName"
                placeholder="请输入用户名称"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="手机号码" prop="phonenumber">
-            <el-input
-               v-model="queryParams.phonenumber"
+         </a-form-item>
+         <a-form-item label="手机号码" name="phonenumber">
+            <a-input
+               v-model:value="queryParams.phonenumber"
                placeholder="请输入手机号码"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
-
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="openSelectUser"
-               v-hasPermi="['system:role:add']"
-            >添加用户</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="CircleClose"
-               :disabled="multiple"
-               @click="cancelAuthUserAll"
-               v-hasPermi="['system:role:remove']"
-            >批量取消授权</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button 
-               type="warning" 
-               plain 
-               icon="Close"
-               @click="handleClose"
-            >关闭</el-button>
-         </el-col>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
+
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="openSelectUser" v-hasPermi="['system:role:add']"><PlusOutlined />添加用户</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="cancelAuthUserAll" v-hasPermi="['system:role:remove']"><CloseCircleOutlined />批量取消授权</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleClose"><CloseOutlined />关闭</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
-
-      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
-         <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
-         <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
-         <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
-         <el-table-column label="状态" align="center" prop="status">
-            <template #default="scope">
-               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-            </template>
-         </el-table-column>
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-               <el-button link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)" v-hasPermi="['system:role:remove']">取消授权</el-button>
-            </template>
-         </el-table-column>
-      </el-table>
+      </a-row>
+
+      <a-table
+        :loading="loading"
+        :data-source="userList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.userId"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-button type="link" size="small" danger @click="cancelAuthUser(record)" v-hasPermi="['system:role:remove']"><CloseCircleOutlined />取消授权</a-button>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -94,6 +73,8 @@
 <script setup name="AuthUser">
 import selectUser from "./selectUser"
 import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role"
+import { Modal, message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined, PlusOutlined, CloseCircleOutlined, CloseOutlined } from '@ant-design/icons-vue'
 
 const route = useRoute()
 const { proxy } = getCurrentInstance()
@@ -105,6 +86,17 @@ const showSearch = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const userIds = ref([])
+const selectedRowKeys = ref([])
+
+const tableColumns = [
+  { title: '用户名称', dataIndex: 'userName', key: 'userName', ellipsis: true },
+  { title: '用户昵称', dataIndex: 'nickName', key: 'nickName', ellipsis: true },
+  { title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
+  { title: '手机', dataIndex: 'phonenumber', key: 'phonenumber', ellipsis: true },
+  { title: '状态', key: 'status', align: 'center' },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 },
+  { title: '操作', key: 'action', align: 'center' }
+]
 
 const queryParams = reactive({
   pageNum: 1,
@@ -138,14 +130,16 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.userName = undefined
+  queryParams.phonenumber = undefined
   handleQuery()
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  userIds.value = selection.map(item => item.userId)
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  userIds.value = keys
+  multiple.value = keys.length === 0
 }
 
 /** 打开授权用户表弹窗 */
@@ -155,24 +149,32 @@ function openSelectUser() {
 
 /** 取消授权按钮操作 */
 function cancelAuthUser(row) {
-  proxy.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function () {
-    return authUserCancel({ userId: row.userId, roleId: queryParams.roleId })
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("取消授权成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '确认要取消该用户"' + row.userName + '"角色吗?',
+    onOk() {
+      return authUserCancel({ userId: row.userId, roleId: queryParams.roleId }).then(() => {
+        getList()
+        message.success("取消授权成功")
+      })
+    }
+  })
 }
 
 /** 批量取消授权按钮操作 */
-function cancelAuthUserAll(row) {
+function cancelAuthUserAll() {
   const roleId = queryParams.roleId
   const uIds = userIds.value.join(",")
-  proxy.$modal.confirm("是否取消选中用户授权数据项?").then(function () {
-    return authUserCancelAll({ roleId: roleId, userIds: uIds })
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("取消授权成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否取消选中用户授权数据项?',
+    onOk() {
+      return authUserCancelAll({ roleId: roleId, userIds: uIds }).then(() => {
+        getList()
+        message.success("取消授权成功")
+      })
+    }
+  })
 }
 
 getList()

+ 299 - 306
yushu-uivue3/src/views/system/role/index.vue

@@ -1,135 +1,100 @@
 <template>
    <div class="app-container">
-      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px">
-         <el-form-item label="角色名称" prop="roleName">
-            <el-input
-               v-model="queryParams.roleName"
+      <a-form :model="queryParams" ref="queryRef" v-show="showSearch" layout="inline">
+         <a-form-item label="角色名称" name="roleName">
+            <a-input
+               v-model:value="queryParams.roleName"
                placeholder="请输入角色名称"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="权限字符" prop="roleKey">
-            <el-input
-               v-model="queryParams.roleKey"
+         </a-form-item>
+         <a-form-item label="权限字符" name="roleKey">
+            <a-input
+               v-model:value="queryParams.roleKey"
                placeholder="请输入权限字符"
-               clearable
+               allow-clear
                style="width: 240px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="状态" prop="status">
-            <el-select
-               v-model="queryParams.status"
+         </a-form-item>
+         <a-form-item label="状态" name="status">
+            <a-select
+               v-model:value="queryParams.status"
                placeholder="角色状态"
-               clearable
+               allow-clear
                style="width: 240px"
             >
-               <el-option
+               <a-select-option
                   v-for="dict in sys_normal_disable"
                   :key="dict.value"
-                  :label="dict.label"
                   :value="dict.value"
-               />
-            </el-select>
-         </el-form-item>
-         <el-form-item label="创建时间" style="width: 308px">
-            <el-date-picker
-               v-model="dateRange"
+               >{{ dict.label }}</a-select-option>
+            </a-select>
+         </a-form-item>
+         <a-form-item label="创建时间" style="width: 308px">
+            <a-range-picker
+               v-model:value="dateRange"
                value-format="YYYY-MM-DD"
-               type="daterange"
-               range-separator="-"
-               start-placeholder="开始日期"
-               end-placeholder="结束日期"
-            ></el-date-picker>
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
-      <el-row :gutter="10" class="mb8">
-         <el-col :span="1.5">
-            <el-button
-               type="primary"
-               plain
-               icon="Plus"
-               @click="handleAdd"
-               v-hasPermi="['system:role:add']"
-            >新增</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="success"
-               plain
-               icon="Edit"
-               :disabled="single"
-               @click="handleUpdate"
-               v-hasPermi="['system:role:edit']"
-            >修改</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="danger"
-               plain
-               icon="Delete"
-               :disabled="multiple"
-               @click="handleDelete"
-               v-hasPermi="['system:role:remove']"
-            >删除</el-button>
-         </el-col>
-         <el-col :span="1.5">
-            <el-button
-               type="warning"
-               plain
-               icon="Download"
-               @click="handleExport"
-               v-hasPermi="['system:role:export']"
-            >导出</el-button>
-         </el-col>
+            />
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
+      <a-row :gutter="10" class="mb8">
+         <a-col :span="1.5">
+            <a-button type="primary" @click="handleAdd" v-hasPermi="['system:role:add']"><PlusOutlined />新增</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:role:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:role:remove']"><DeleteOutlined />删除</a-button>
+         </a-col>
+         <a-col :span="1.5">
+            <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:role:export']"><DownloadOutlined />导出</a-button>
+         </a-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-      </el-row>
+      </a-row>
 
       <!-- 表格数据 -->
-      <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
-         <el-table-column type="selection" width="55" align="center" />
-         <el-table-column label="角色编号" prop="roleId" width="120" />
-         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
-         <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
-         <el-table-column label="显示顺序" prop="roleSort" width="100" />
-         <el-table-column label="状态" align="center" width="100">
-            <template #default="scope">
-               <el-switch
-                  v-model="scope.row.status"
-                  active-value="0"
-                  inactive-value="1"
-                  @change="handleStatusChange(scope.row)"
-               ></el-switch>
-            </template>
-         </el-table-column>
-         <el-table-column label="创建时间" align="center" prop="createTime">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-              <el-tooltip content="修改" placement="top" v-if="scope.row.roleId !== 1">
-                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
-              </el-tooltip>
-              <el-tooltip content="删除" placement="top" v-if="scope.row.roleId !== 1">
-                <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
-              </el-tooltip>
-              <el-tooltip content="数据权限" placement="top" v-if="scope.row.roleId !== 1">
-                <el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
-              </el-tooltip>
-              <el-tooltip content="分配用户" placement="top" v-if="scope.row.roleId !== 1">
-                <el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
-              </el-tooltip>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="roleList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.roleId"
+        :pagination="false"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <a-switch
+              :checked="record.status === '0'"
+              @change="(checked) => handleStatusChange(record, checked)"
+            />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+          <template v-else-if="column.key === 'action'">
+            <a-tooltip title="修改" v-if="record.roleId !== 1">
+              <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:role:edit']"><EditOutlined /></a-button>
+            </a-tooltip>
+            <a-tooltip title="删除" v-if="record.roleId !== 1">
+              <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:role:remove']"><DeleteOutlined /></a-button>
+            </a-tooltip>
+            <a-tooltip title="数据权限" v-if="record.roleId !== 1">
+              <a-button type="link" size="small" @click="handleDataScope(record)" v-hasPermi="['system:role:edit']"><CheckCircleOutlined /></a-button>
+            </a-tooltip>
+            <a-tooltip title="分配用户" v-if="record.roleId !== 1">
+              <a-button type="link" size="small" @click="handleAuthUser(record)" v-hasPermi="['system:role:edit']"><UserOutlined /></a-button>
+            </a-tooltip>
+          </template>
+        </template>
+      </a-table>
 
       <pagination
          v-show="total > 0"
@@ -140,110 +105,108 @@
       />
 
       <!-- 添加或修改角色配置对话框 -->
-      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-         <el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
-            <el-form-item label="角色名称" prop="roleName">
-               <el-input v-model="form.roleName" placeholder="请输入角色名称" />
-            </el-form-item>
-            <el-form-item prop="roleKey">
+      <a-modal :title="title" v-model:open="open" width="500px" :footer="null">
+         <a-form ref="roleRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-form-item label="角色名称" name="roleName">
+               <a-input v-model:value="form.roleName" placeholder="请输入角色名称" />
+            </a-form-item>
+            <a-form-item name="roleKey">
                <template #label>
                   <span>
-                     <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
-                        <el-icon><question-filled /></el-icon>
-                     </el-tooltip>
+                     <a-tooltip title="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)">
+                        <QuestionCircleOutlined />
+                     </a-tooltip>
                      权限字符
                   </span>
                </template>
-               <el-input v-model="form.roleKey" placeholder="请输入权限字符" />
-            </el-form-item>
-            <el-form-item label="角色顺序" prop="roleSort">
-               <el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
-            </el-form-item>
-            <el-form-item label="状态">
-               <el-radio-group v-model="form.status">
-                  <el-radio
+               <a-input v-model:value="form.roleKey" placeholder="请输入权限字符" />
+            </a-form-item>
+            <a-form-item label="角色顺序" name="roleSort">
+               <a-input-number v-model:value="form.roleSort" :min="0" style="width: 100%" />
+            </a-form-item>
+            <a-form-item label="状态">
+               <a-radio-group v-model:value="form.status">
+                  <a-radio
                      v-for="dict in sys_normal_disable"
                      :key="dict.value"
                      :value="dict.value"
-                  >{{ dict.label }}</el-radio>
-               </el-radio-group>
-            </el-form-item>
-            <el-form-item label="菜单权限">
-               <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
-               <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
-               <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
-               <el-tree
+                  >{{ dict.label }}</a-radio>
+               </a-radio-group>
+            </a-form-item>
+            <a-form-item label="菜单权限">
+               <a-checkbox v-model:checked="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</a-checkbox>
+               <a-checkbox v-model:checked="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</a-checkbox>
+               <a-checkbox v-model:checked="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</a-checkbox>
+               <a-tree
                   class="tree-border"
-                  :data="menuOptions"
-                  show-checkbox
-                  ref="menuRef"
-                  node-key="id"
-                  :check-strictly="!form.menuCheckStrictly"
-                  empty-text="加载中,请稍候"
-                  :props="{ label: 'label', children: 'children' }"
-               ></el-tree>
-            </el-form-item>
-            <el-form-item label="备注">
-               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  :tree-data="menuOptions"
+                  checkable
+                  v-model:checkedKeys="menuCheckedKeys"
+                  v-model:expandedKeys="menuExpandedKeys"
+                  :field-names="{ title: 'label', key: 'id', children: 'children' }"
+                  :checkStrictly="!form.menuCheckStrictly"
+               />
+            </a-form-item>
+            <a-form-item label="备注">
+               <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+            </a-form-item>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitForm">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancel">取 消</a-button>
+         </div>
+      </a-modal>
 
       <!-- 分配角色数据权限对话框 -->
-      <el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
-         <el-form :model="form" label-width="80px">
-            <el-form-item label="角色名称">
-               <el-input v-model="form.roleName" :disabled="true" />
-            </el-form-item>
-            <el-form-item label="权限字符">
-               <el-input v-model="form.roleKey" :disabled="true" />
-            </el-form-item>
-            <el-form-item label="权限范围">
-               <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
-                  <el-option
+      <a-modal :title="title" v-model:open="openDataScope" width="500px" :footer="null">
+         <a-form :model="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+            <a-form-item label="角色名称">
+               <a-input v-model:value="form.roleName" disabled />
+            </a-form-item>
+            <a-form-item label="权限字符">
+               <a-input v-model:value="form.roleKey" disabled />
+            </a-form-item>
+            <a-form-item label="权限范围">
+               <a-select v-model:value="form.dataScope" @change="dataScopeSelectChange">
+                  <a-select-option
                      v-for="item in dataScopeOptions"
                      :key="item.value"
-                     :label="item.label"
                      :value="item.value"
-                  ></el-option>
-               </el-select>
-            </el-form-item>
-            <el-form-item label="数据权限" v-show="form.dataScope == 2">
-               <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
-               <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
-               <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
-               <el-tree
+                  >{{ item.label }}</a-select-option>
+               </a-select>
+            </a-form-item>
+            <a-form-item label="数据权限" v-show="form.dataScope == '2'">
+               <a-checkbox v-model:checked="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</a-checkbox>
+               <a-checkbox v-model:checked="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</a-checkbox>
+               <a-checkbox v-model:checked="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</a-checkbox>
+               <a-tree
                   class="tree-border"
-                  :data="deptOptions"
-                  show-checkbox
+                  :tree-data="deptOptions"
+                  checkable
                   default-expand-all
-                  ref="deptRef"
-                  node-key="id"
-                  :check-strictly="!form.deptCheckStrictly"
-                  empty-text="加载中,请稍候"
-                  :props="{ label: 'label', children: 'children' }"
-               ></el-tree>
-            </el-form-item>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitDataScope">确 定</el-button>
-               <el-button @click="cancelDataScope">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
+                  v-model:checkedKeys="deptCheckedKeys"
+                  v-model:expandedKeys="deptExpandedKeys"
+                  :field-names="{ title: 'label', key: 'id', children: 'children' }"
+                  :checkStrictly="!form.deptCheckStrictly"
+               />
+            </a-form-item>
+         </a-form>
+         <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+            <a-button type="primary" @click="submitDataScope">确 定</a-button>
+            <a-button style="margin-left: 8px" @click="cancelDataScope">取 消</a-button>
+         </div>
+      </a-modal>
    </div>
 </template>
 
 <script setup name="Role">
 import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role"
 import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"
+import { Modal, message } from 'ant-design-vue'
+import {
+  SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
+  DownloadOutlined, CheckCircleOutlined, UserOutlined, QuestionCircleOutlined
+} from '@ant-design/icons-vue'
 
 const router = useRouter()
 const { proxy } = getCurrentInstance()
@@ -254,6 +217,7 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
@@ -262,12 +226,24 @@ const dateRange = ref([])
 const menuOptions = ref([])
 const menuExpand = ref(false)
 const menuNodeAll = ref(false)
+const menuCheckedKeys = ref([])
+const menuExpandedKeys = ref([])
 const deptExpand = ref(true)
 const deptNodeAll = ref(false)
 const deptOptions = ref([])
+const deptCheckedKeys = ref([])
+const deptExpandedKeys = ref([])
 const openDataScope = ref(false)
-const menuRef = ref(null)
-const deptRef = ref(null)
+
+const tableColumns = [
+  { title: '角色编号', dataIndex: 'roleId', key: 'roleId', width: 120 },
+  { title: '角色名称', dataIndex: 'roleName', key: 'roleName', ellipsis: true, width: 150 },
+  { title: '权限字符', dataIndex: 'roleKey', key: 'roleKey', ellipsis: true, width: 150 },
+  { title: '显示顺序', dataIndex: 'roleSort', key: 'roleSort', width: 100 },
+  { title: '状态', key: 'status', align: 'center', width: 100 },
+  { title: '创建时间', key: 'createTime', align: 'center' },
+  { title: '操作', key: 'action', align: 'center' }
+]
 
 /** 数据范围选项*/
 const dataScopeOptions = ref([
@@ -299,7 +275,12 @@ const { queryParams, form, rules } = toRefs(data)
 /** 查询角色列表 */
 function getList() {
   loading.value = true
-  listRole(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+  const params = { ...queryParams.value }
+  if (dateRange.value && dateRange.value.length === 2) {
+    params.beginTime = dateRange.value[0]
+    params.endTime = dateRange.value[1]
+  }
+  listRole(params).then(response => {
     roleList.value = response.rows
     total.value = response.total
     loading.value = false
@@ -315,19 +296,29 @@ function handleQuery() {
 /** 重置按钮操作 */
 function resetQuery() {
   dateRange.value = []
-  proxy.resetForm("queryRef")
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    roleName: undefined,
+    roleKey: undefined,
+    status: undefined
+  }
   handleQuery()
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const roleIds = row.roleId || ids.value
-  proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
-    return delRole(roleIds)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除角色编号为"' + roleIds + '"的数据项?',
+    onOk() {
+      return delRole(roleIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 /** 导出按钮操作 */
@@ -338,38 +329,29 @@ function handleExport() {
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.roleId)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 角色状态修改 */
-function handleStatusChange(row) {
-  let text = row.status === "0" ? "启用" : "停用"
-  proxy.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function () {
-    return changeRoleStatus(row.roleId, row.status)
-  }).then(() => {
-    proxy.$modal.msgSuccess(text + "成功")
-  }).catch(function () {
-    row.status = row.status === "0" ? "1" : "0"
+function handleStatusChange(row, checked) {
+  const newStatus = checked ? "0" : "1"
+  let text = newStatus === "0" ? "启用" : "停用"
+  Modal.confirm({
+    title: '提示',
+    content: '确认要"' + text + '""' + row.roleName + '"角色吗?',
+    onOk() {
+      return changeRoleStatus(row.roleId, newStatus).then(() => {
+        row.status = newStatus
+        message.success(text + "成功")
+      })
+    }
   })
 }
 
-/** 更多操作 */
-function handleCommand(command, row) {
-  switch (command) {
-    case "handleDataScope":
-      handleDataScope(row)
-      break
-    case "handleAuthUser":
-      handleAuthUser(row)
-      break
-    default:
-      break
-  }
-}
-
 /** 分配用户 */
 function handleAuthUser(row) {
   router.push("/system/role-auth/user/" + row.roleId)
@@ -382,25 +364,40 @@ function getMenuTreeselect() {
   })
 }
 
-/** 所有部门节点数据 */
-function getDeptAllCheckedKeys() {
-  // 目前被选中的部门节点
-  let checkedKeys = deptRef.value.getCheckedKeys()
-  // 半选中的部门节点
-  let halfCheckedKeys = deptRef.value.getHalfCheckedKeys()
-  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
-  return checkedKeys
+/** 获取所有菜单ID(用于全选) */
+function getAllMenuIds(menus) {
+  let ids = []
+  menus.forEach(menu => {
+    ids.push(menu.id)
+    if (menu.children && menu.children.length > 0) {
+      ids = ids.concat(getAllMenuIds(menu.children))
+    }
+  })
+  return ids
+}
+
+/** 获取所有部门ID(用于全选) */
+function getAllDeptIds(depts) {
+  let ids = []
+  depts.forEach(dept => {
+    ids.push(dept.id)
+    if (dept.children && dept.children.length > 0) {
+      ids = ids.concat(getAllDeptIds(dept.children))
+    }
+  })
+  return ids
 }
 
 /** 重置新增的表单以及其他数据  */
 function reset() {
-  if (menuRef.value != undefined) {
-    menuRef.value.setCheckedKeys([])
-  }
   menuExpand.value = false
   menuNodeAll.value = false
+  menuCheckedKeys.value = []
+  menuExpandedKeys.value = []
   deptExpand.value = true
   deptNodeAll.value = false
+  deptCheckedKeys.value = []
+  deptExpandedKeys.value = []
   form.value = {
     roleId: undefined,
     roleName: undefined,
@@ -413,7 +410,6 @@ function reset() {
     deptCheckStrictly: true,
     remark: undefined
   }
-  proxy.resetForm("roleRef")
 }
 
 /** 添加角色 */
@@ -433,15 +429,8 @@ function handleUpdate(row) {
     form.value = response.data
     form.value.roleSort = Number(form.value.roleSort)
     open.value = true
-    nextTick(() => {
-      roleMenu.then((res) => {
-        let checkedKeys = res.checkedKeys
-        checkedKeys.forEach((v) => {
-          nextTick(() => {
-            menuRef.value.setChecked(v, true, false)
-          })
-        })
-      })
+    roleMenu.then((res) => {
+      menuCheckedKeys.value = res.checkedKeys || []
     })
   })
   title.value = "修改角色"
@@ -464,69 +453,69 @@ function getDeptTree(roleId) {
 }
 
 /** 树权限(展开/折叠)*/
-function handleCheckedTreeExpand(value, type) {
+function handleCheckedTreeExpand(e, type) {
+  const value = e.target ? e.target.checked : e
   if (type == "menu") {
-    let treeList = menuOptions.value
-    for (let i = 0; i < treeList.length; i++) {
-      menuRef.value.store.nodesMap[treeList[i].id].expanded = value
+    if (value) {
+      menuExpandedKeys.value = getAllMenuIds(menuOptions.value)
+    } else {
+      menuExpandedKeys.value = []
     }
   } else if (type == "dept") {
-    let treeList = deptOptions.value
-    for (let i = 0; i < treeList.length; i++) {
-      deptRef.value.store.nodesMap[treeList[i].id].expanded = value
+    if (value) {
+      deptExpandedKeys.value = getAllDeptIds(deptOptions.value)
+    } else {
+      deptExpandedKeys.value = []
     }
   }
 }
 
 /** 树权限(全选/全不选) */
-function handleCheckedTreeNodeAll(value, type) {
+function handleCheckedTreeNodeAll(e, type) {
+  const value = e.target ? e.target.checked : e
   if (type == "menu") {
-    menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
+    if (value) {
+      menuCheckedKeys.value = getAllMenuIds(menuOptions.value)
+    } else {
+      menuCheckedKeys.value = []
+    }
   } else if (type == "dept") {
-    deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
+    if (value) {
+      deptCheckedKeys.value = getAllDeptIds(deptOptions.value)
+    } else {
+      deptCheckedKeys.value = []
+    }
   }
 }
 
 /** 树权限(父子联动) */
-function handleCheckedTreeConnect(value, type) {
+function handleCheckedTreeConnect(e, type) {
+  const value = e.target ? e.target.checked : e
   if (type == "menu") {
-    form.value.menuCheckStrictly = value ? true : false
+    form.value.menuCheckStrictly = value
   } else if (type == "dept") {
-    form.value.deptCheckStrictly = value ? true : false
+    form.value.deptCheckStrictly = value
   }
 }
 
-/** 所有菜单节点数据 */
-function getMenuAllCheckedKeys() {
-  // 目前被选中的菜单节点
-  let checkedKeys = menuRef.value.getCheckedKeys()
-  // 半选中的菜单节点
-  let halfCheckedKeys = menuRef.value.getHalfCheckedKeys()
-  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
-  return checkedKeys
-}
-
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["roleRef"].validate(valid => {
-    if (valid) {
-      if (form.value.roleId != undefined) {
-        form.value.menuIds = getMenuAllCheckedKeys()
-        updateRole(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        form.value.menuIds = getMenuAllCheckedKeys()
-        addRole(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["roleRef"].validate().then(() => {
+    form.value.menuIds = Array.isArray(menuCheckedKeys.value) ? menuCheckedKeys.value : (menuCheckedKeys.value.checked || [])
+    if (form.value.roleId != undefined) {
+      updateRole(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addRole(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 /** 取消按钮 */
@@ -538,25 +527,19 @@ function cancel() {
 /** 选择角色权限范围触发 */
 function dataScopeSelectChange(value) {
   if (value !== "2") {
-    deptRef.value.setCheckedKeys([])
+    deptCheckedKeys.value = []
   }
 }
 
 /** 分配数据权限操作 */
 function handleDataScope(row) {
   reset()
-  const deptTreeSelect = getDeptTree(row.roleId)
+  const deptTreeSelectPromise = getDeptTree(row.roleId)
   getRole(row.roleId).then(response => {
     form.value = response.data
     openDataScope.value = true
-    nextTick(() => {
-      deptTreeSelect.then(res => {
-        nextTick(() => {
-          if (deptRef.value) {
-            deptRef.value.setCheckedKeys(res.checkedKeys)
-          }
-        })
-      })
+    deptTreeSelectPromise.then(res => {
+      deptCheckedKeys.value = res.checkedKeys || []
     })
   })
   title.value = "分配数据权限"
@@ -565,9 +548,9 @@ function handleDataScope(row) {
 /** 提交按钮(数据权限) */
 function submitDataScope() {
   if (form.value.roleId != undefined) {
-    form.value.deptIds = getDeptAllCheckedKeys()
+    form.value.deptIds = Array.isArray(deptCheckedKeys.value) ? deptCheckedKeys.value : (deptCheckedKeys.value.checked || [])
     dataScope(form.value).then(response => {
-      proxy.$modal.msgSuccess("修改成功")
+      message.success("修改成功")
       openDataScope.value = false
       getList()
     })
@@ -582,3 +565,13 @@ function cancelDataScope() {
 
 getList()
 </script>
+
+<style scoped>
+.tree-border {
+  margin-top: 5px;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  max-height: 200px;
+  overflow: auto;
+}
+</style>

+ 79 - 59
yushu-uivue3/src/views/system/role/selectUser.vue

@@ -1,67 +1,67 @@
 <template>
    <!-- 授权用户 -->
-   <el-dialog title="选择用户" v-model="visible" width="800px" top="5vh" append-to-body>
-      <el-form :model="queryParams" ref="queryRef" :inline="true">
-         <el-form-item label="用户名称" prop="userName">
-            <el-input
-               v-model="queryParams.userName"
+   <a-modal title="选择用户" v-model:open="visible" width="800px" :footer="null">
+      <a-form :model="queryParams" ref="queryRef" layout="inline">
+         <a-form-item label="用户名称" name="userName">
+            <a-input
+               v-model:value="queryParams.userName"
                placeholder="请输入用户名称"
-               clearable
+               allow-clear
                style="width: 180px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item label="手机号码" prop="phonenumber">
-            <el-input
-               v-model="queryParams.phonenumber"
+         </a-form-item>
+         <a-form-item label="手机号码" name="phonenumber">
+            <a-input
+               v-model:value="queryParams.phonenumber"
                placeholder="请输入手机号码"
-               clearable
+               allow-clear
                style="width: 180px"
-               @keyup.enter="handleQuery"
+               @pressEnter="handleQuery"
             />
-         </el-form-item>
-         <el-form-item>
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-         </el-form-item>
-      </el-form>
-      <el-row>
-         <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
-            <el-table-column type="selection" width="55"></el-table-column>
-            <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
-            <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
-            <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
-            <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
-            <el-table-column label="状态" align="center" prop="status">
-               <template #default="scope">
-                  <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-               </template>
-            </el-table-column>
-            <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-               <template #default="scope">
-                  <span>{{ parseTime(scope.row.createTime) }}</span>
-               </template>
-            </el-table-column>
-         </el-table>
-         <pagination
-            v-show="total > 0"
-            :total="total"
-            v-model:page="queryParams.pageNum"
-            v-model:limit="queryParams.pageSize"
-            @pagination="getList"
-         />
-      </el-row>
-      <template #footer>
-         <div class="dialog-footer">
-            <el-button type="primary" @click="handleSelectUser">确 定</el-button>
-            <el-button @click="visible = false">取 消</el-button>
-         </div>
-      </template>
-   </el-dialog>
+         </a-form-item>
+         <a-form-item>
+            <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+            <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+         </a-form-item>
+      </a-form>
+      <a-table
+        :data-source="userList"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+        :row-key="record => record.userId"
+        :pagination="false"
+        :scroll="{ y: 260 }"
+        style="margin-top: 16px"
+        @row-click="clickRow"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <dict-tag :options="sys_normal_disable" :value="record.status" />
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+        </template>
+      </a-table>
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+      <div class="dialog-footer" style="text-align: right; margin-top: 16px;">
+         <a-button type="primary" @click="handleSelectUser">确 定</a-button>
+         <a-button style="margin-left: 8px" @click="visible = false">取 消</a-button>
+      </div>
+   </a-modal>
 </template>
 
 <script setup name="SelectUser">
 import { authUserSelectAll, unallocatedUserList } from "@/api/system/role"
+import { message } from 'ant-design-vue'
+import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue'
 
 const props = defineProps({
   roleId: {
@@ -76,6 +76,16 @@ const userList = ref([])
 const visible = ref(false)
 const total = ref(0)
 const userIds = ref([])
+const selectedRowKeys = ref([])
+
+const tableColumns = [
+  { title: '用户名称', dataIndex: 'userName', key: 'userName', ellipsis: true },
+  { title: '用户昵称', dataIndex: 'nickName', key: 'nickName', ellipsis: true },
+  { title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
+  { title: '手机', dataIndex: 'phonenumber', key: 'phonenumber', ellipsis: true },
+  { title: '状态', key: 'status', align: 'center' },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 }
+]
 
 const queryParams = reactive({
   pageNum: 1,
@@ -88,18 +98,27 @@ const queryParams = reactive({
 // 显示弹框
 function show() {
   queryParams.roleId = props.roleId
+  selectedRowKeys.value = []
+  userIds.value = []
   getList()
   visible.value = true
 }
 
 /**选择行 */
-function clickRow(row) {
-  proxy.$refs["refTable"].toggleRowSelection(row)
+function clickRow(record) {
+  const index = selectedRowKeys.value.indexOf(record.userId)
+  if (index > -1) {
+    selectedRowKeys.value.splice(index, 1)
+  } else {
+    selectedRowKeys.value.push(record.userId)
+  }
+  userIds.value = [...selectedRowKeys.value]
 }
 
 // 多选框选中数据
-function handleSelectionChange(selection) {
-  userIds.value = selection.map(item => item.userId)
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  userIds.value = keys
 }
 
 // 查询表数据
@@ -118,7 +137,8 @@ function handleQuery() {
 
 /** 重置按钮操作 */
 function resetQuery() {
-  proxy.resetForm("queryRef")
+  queryParams.userName = undefined
+  queryParams.phonenumber = undefined
   handleQuery()
 }
 
@@ -128,11 +148,11 @@ function handleSelectUser() {
   const roleId = queryParams.roleId
   const uIds = userIds.value.join(",")
   if (uIds == "") {
-    proxy.$modal.msgError("请选择要分配的用户")
+    message.error("请选择要分配的用户")
     return
   }
   authUserSelectAll({ roleId: roleId, userIds: uIds }).then(res => {
-    proxy.$modal.msgSuccess(res.msg)
+    message.success(res.msg)
     visible.value = false
     emit("ok")
   })

+ 73 - 52
yushu-uivue3/src/views/system/user/authRole.vue

@@ -1,52 +1,53 @@
 <template>
    <div class="app-container">
       <h4 class="form-header h4">基本信息</h4>
-      <el-form :model="form" label-width="80px">
-         <el-row>
-            <el-col :span="8" :offset="2">
-               <el-form-item label="用户昵称" prop="nickName">
-                  <el-input v-model="form.nickName" disabled />
-               </el-form-item>
-            </el-col>
-            <el-col :span="8" :offset="2">
-               <el-form-item label="登录账号" prop="userName">
-                  <el-input v-model="form.userName" disabled />
-               </el-form-item>
-            </el-col>
-         </el-row>
-      </el-form>
+      <a-form :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
+         <a-row>
+            <a-col :span="8" :offset="2">
+               <a-form-item label="用户昵称" name="nickName">
+                  <a-input v-model:value="form.nickName" disabled />
+               </a-form-item>
+            </a-col>
+            <a-col :span="8" :offset="2">
+               <a-form-item label="登录账号" name="userName">
+                  <a-input v-model:value="form.userName" disabled />
+               </a-form-item>
+            </a-col>
+         </a-row>
+      </a-form>
 
       <h4 class="form-header h4">角色信息</h4>
-      <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
-         <el-table-column label="序号" width="55" type="index" align="center">
-            <template #default="scope">
-               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
-            </template>
-         </el-table-column>
-         <el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
-         <el-table-column label="角色编号" align="center" prop="roleId" />
-         <el-table-column label="角色名称" align="center" prop="roleName" />
-         <el-table-column label="权限字符" align="center" prop="roleKey" />
-         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-            <template #default="scope">
-               <span>{{ parseTime(scope.row.createTime) }}</span>
-            </template>
-         </el-table-column>
-      </el-table>
+      <a-table
+        :loading="loading"
+        :data-source="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
+        :columns="tableColumns"
+        :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange, getCheckboxProps: getCheckboxProps }"
+        :row-key="record => record.roleId"
+        :pagination="false"
+        @row-click="clickRow"
+      >
+        <template #bodyCell="{ column, record, index }">
+          <template v-if="column.key === 'index'">
+            {{ (pageNum - 1) * pageSize + index + 1 }}
+          </template>
+          <template v-else-if="column.key === 'createTime'">
+            <span>{{ parseTime(record.createTime) }}</span>
+          </template>
+        </template>
+      </a-table>
 
       <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
 
-      <el-form label-width="100px">
-         <div style="text-align: center;margin-left:-120px;margin-top:30px;">
-            <el-button type="primary" @click="submitForm()">提交</el-button>
-            <el-button @click="close()">返回</el-button>
-         </div>
-      </el-form>
+      <div style="text-align: center; margin-top: 30px;">
+        <a-button type="primary" @click="submitForm()">提交</a-button>
+        <a-button style="margin-left: 8px" @click="close()">返回</a-button>
+      </div>
    </div>
 </template>
 
 <script setup name="AuthRole">
 import { getAuthRole, updateAuthRole } from "@/api/system/user"
+import { message } from 'ant-design-vue'
 
 const route = useRoute()
 const { proxy } = getCurrentInstance()
@@ -56,6 +57,7 @@ const total = ref(0)
 const pageNum = ref(1)
 const pageSize = ref(10)
 const roleIds = ref([])
+const selectedRowKeys = ref([])
 const roles = ref([])
 const form = ref({
   nickName: undefined,
@@ -63,26 +65,43 @@ const form = ref({
   userId: undefined
 })
 
+const tableColumns = [
+  { title: '序号', key: 'index', width: 55, align: 'center' },
+  { title: '角色编号', dataIndex: 'roleId', key: 'roleId', align: 'center' },
+  { title: '角色名称', dataIndex: 'roleName', key: 'roleName', align: 'center' },
+  { title: '权限字符', dataIndex: 'roleKey', key: 'roleKey', align: 'center' },
+  { title: '创建时间', key: 'createTime', align: 'center', width: 180 }
+]
+
 /** 单击选中行数据 */
-function clickRow(row) {
-  if (checkSelectable(row)) {
-    proxy.$refs["roleRef"].toggleRowSelection(row)
+function clickRow(record) {
+  if (checkSelectable(record)) {
+    const index = selectedRowKeys.value.indexOf(record.roleId)
+    if (index > -1) {
+      selectedRowKeys.value.splice(index, 1)
+    } else {
+      selectedRowKeys.value.push(record.roleId)
+    }
+    roleIds.value = [...selectedRowKeys.value]
   }
 }
 
 /** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  roleIds.value = selection.map(item => item.roleId)
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  roleIds.value = keys
 }
 
-/** 保存选中的数据编号 */
-function getRowKey(row) {
-  return row.roleId
+/** 获取复选框属性 */
+function getCheckboxProps(record) {
+  return {
+    disabled: !checkSelectable(record)
+  }
 }
 
 // 检查角色状态
 function checkSelectable(row) {
-  return row.status === "0" ? true : false
+  return row.status === "0"
 }
 
 /** 关闭按钮 */
@@ -96,7 +115,7 @@ function submitForm() {
   const userId = form.value.userId
   const rIds = roleIds.value.join(",")
   updateAuthRole({ userId: userId, roleIds: rIds }).then(response => {
-    proxy.$modal.msgSuccess("授权成功")
+    message.success("授权成功")
     close()
   })
 }
@@ -109,13 +128,15 @@ function submitForm() {
       form.value = response.user
       roles.value = response.roles
       total.value = roles.value.length
-      nextTick(() => {
-        roles.value.forEach(row => {
-          if (row.flag) {
-            proxy.$refs["roleRef"].toggleRowSelection(row)
-          }
-        })
+      // 初始化选中状态
+      const initialSelectedKeys = []
+      roles.value.forEach(row => {
+        if (row.flag) {
+          initialSelectedKeys.push(row.roleId)
+        }
       })
+      selectedRowKeys.value = initialSelectedKeys
+      roleIds.value = initialSelectedKeys
       loading.value = false
     })
   }

+ 478 - 288
yushu-uivue3/src/views/system/user/index.vue

@@ -1,215 +1,236 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="20">
+    <a-row :gutter="20">
       <splitpanes :horizontal="appStore.device === 'mobile'" class="default-theme">
         <!--部门数据-->
         <pane size="16">
-          <el-col>
+          <a-col>
             <div class="head-container">
-              <el-input v-model="deptName" placeholder="请输入部门名称" clearable prefix-icon="Search" style="margin-bottom: 20px" />
+              <a-input v-model:value="deptName" placeholder="请输入部门名称" allow-clear style="margin-bottom: 20px">
+                <template #prefix><SearchOutlined /></template>
+              </a-input>
             </div>
             <div class="head-container">
-              <el-tree :data="deptOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" ref="deptTreeRef" node-key="id" highlight-current default-expand-all @node-click="handleNodeClick" />
+              <a-tree
+                :tree-data="deptOptions"
+                :field-names="{ title: 'label', key: 'id', children: 'children' }"
+                :filter-tree-node="filterNode"
+                :search-value="deptName"
+                ref="deptTreeRef"
+                default-expand-all
+                @select="handleNodeClick"
+              />
             </div>
-          </el-col>
+          </a-col>
         </pane>
         <!--用户数据-->
         <pane size="84">
-          <el-col>
-            <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-              <el-form-item label="用户名称" prop="userName">
-                <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
-              </el-form-item>
-              <el-form-item label="手机号码" prop="phonenumber">
-                <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter="handleQuery" />
-              </el-form-item>
-              <el-form-item label="状态" prop="status">
-                <el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
-                  <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
-                </el-select>
-              </el-form-item>
-              <el-form-item label="创建时间" style="width: 308px">
-                <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
-              </el-form-item>
-              <el-form-item>
-                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              </el-form-item>
-            </el-form>
-
-            <el-row :gutter="10" class="mb8">
-              <el-col :span="1.5">
-                <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:user:add']">新增</el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']">修改</el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']">删除</el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:user:import']">导入</el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:user:export']">导出</el-button>
-              </el-col>
+          <a-col>
+            <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch">
+              <a-form-item label="用户名称" name="userName">
+                <a-input v-model:value="queryParams.userName" placeholder="请输入用户名称" allow-clear style="width: 240px" @pressEnter="handleQuery" />
+              </a-form-item>
+              <a-form-item label="手机号码" name="phonenumber">
+                <a-input v-model:value="queryParams.phonenumber" placeholder="请输入手机号码" allow-clear style="width: 240px" @pressEnter="handleQuery" />
+              </a-form-item>
+              <a-form-item label="状态" name="status">
+                <a-select v-model:value="queryParams.status" placeholder="用户状态" allow-clear style="width: 240px">
+                  <a-select-option v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</a-select-option>
+                </a-select>
+              </a-form-item>
+              <a-form-item label="创建时间">
+                <a-range-picker v-model:value="dateRange" value-format="YYYY-MM-DD" style="width: 308px" />
+              </a-form-item>
+              <a-form-item>
+                <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+                <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+              </a-form-item>
+            </a-form>
+
+            <a-row :gutter="10" class="mb8">
+              <a-col :span="1.5">
+                <a-button type="primary" @click="handleAdd" v-hasPermi="['system:user:add']"><PlusOutlined />新增</a-button>
+              </a-col>
+              <a-col :span="1.5">
+                <a-button type="primary" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+              </a-col>
+              <a-col :span="1.5">
+                <a-button danger :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']"><DeleteOutlined />删除</a-button>
+              </a-col>
+              <a-col :span="1.5">
+                <a-button @click="handleImport" v-hasPermi="['system:user:import']"><UploadOutlined />导入</a-button>
+              </a-col>
+              <a-col :span="1.5">
+                <a-button type="primary" style="background-color: #faad14; border-color: #faad14" @click="handleExport" v-hasPermi="['system:user:export']"><DownloadOutlined />导出</a-button>
+              </a-col>
               <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
-            </el-row>
-
-            <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
-              <el-table-column type="selection" width="50" align="center" />
-              <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns.userId.visible" />
-              <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns.userName.visible" :show-overflow-tooltip="true" />
-              <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns.nickName.visible" :show-overflow-tooltip="true" />
-              <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns.deptName.visible" :show-overflow-tooltip="true" />
-              <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns.phonenumber.visible" width="120" />
-              <el-table-column label="状态" align="center" key="status" v-if="columns.status.visible">
-                <template #default="scope">
-                  <el-switch
-                    v-model="scope.row.status"
-                    active-value="0"
-                    inactive-value="1"
-                    @change="handleStatusChange(scope.row)"
-                  ></el-switch>
+            </a-row>
+
+            <a-table
+              :loading="loading"
+              :data-source="userList"
+              :columns="tableColumns"
+              :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+              :row-key="record => record.userId"
+              :pagination="false"
+            >
+              <template #bodyCell="{ column, record }">
+                <template v-if="column.key === 'status'">
+                  <a-switch
+                    :checked="record.status === '0'"
+                    @change="(checked) => handleStatusChange(record, checked)"
+                  />
                 </template>
-              </el-table-column>
-              <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns.createTime.visible" width="160">
-                <template #default="scope">
-                  <span>{{ parseTime(scope.row.createTime) }}</span>
+                <template v-else-if="column.key === 'createTime'">
+                  <span>{{ parseTime(record.createTime) }}</span>
                 </template>
-              </el-table-column>
-              <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
-                <template #default="scope">
-                  <el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
-                    <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
-                  </el-tooltip>
-                  <el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
-                    <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
-                  </el-tooltip>
-                  <el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
-                    <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
-                  </el-tooltip>
-                  <el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
-                    <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
-                  </el-tooltip>
+                <template v-else-if="column.key === 'action'">
+                  <a-tooltip title="修改" v-if="record.userId !== 1">
+                    <a-button type="link" size="small" @click="handleUpdate(record)" v-hasPermi="['system:user:edit']"><EditOutlined /></a-button>
+                  </a-tooltip>
+                  <a-tooltip title="删除" v-if="record.userId !== 1">
+                    <a-button type="link" size="small" danger @click="handleDelete(record)" v-hasPermi="['system:user:remove']"><DeleteOutlined /></a-button>
+                  </a-tooltip>
+                  <a-tooltip title="重置密码" v-if="record.userId !== 1">
+                    <a-button type="link" size="small" @click="handleResetPwd(record)" v-hasPermi="['system:user:resetPwd']"><KeyOutlined /></a-button>
+                  </a-tooltip>
+                  <a-tooltip title="分配角色" v-if="record.userId !== 1">
+                    <a-button type="link" size="small" @click="handleAuthRole(record)" v-hasPermi="['system:user:edit']"><CheckCircleOutlined /></a-button>
+                  </a-tooltip>
                 </template>
-              </el-table-column>
-            </el-table>
+              </template>
+            </a-table>
             <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
-          </el-col>
+          </a-col>
         </pane>
       </splitpanes>
-    </el-row>
+    </a-row>
 
     <!-- 添加或修改用户配置对话框 -->
-    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
-      <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="用户昵称" prop="nickName">
-              <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="归属部门" prop="deptId">
-              <el-tree-select v-model="form.deptId" :data="enabledDeptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择归属部门" clearable check-strictly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="手机号码" prop="phonenumber">
-              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="邮箱" prop="email">
-              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
-              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="用户性别">
-              <el-select v-model="form.sex" placeholder="请选择">
-                <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="状态">
-              <el-radio-group v-model="form.status">
-                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="岗位">
-              <el-select v-model="form.postIds" multiple placeholder="请选择">
-                <el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" :disabled="item.status == 1"></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="角色">
-              <el-select v-model="form.roleIds" multiple placeholder="请选择">
-                <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId" :disabled="item.status == 1"></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="备注">
-              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+    <a-modal :title="title" v-model:open="open" width="600px" :footer="null">
+      <a-form :model="form" :rules="rules" ref="userRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="用户昵称" name="nickName">
+              <a-input v-model:value="form.nickName" placeholder="请输入用户昵称" :maxlength="30" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="归属部门" name="deptId">
+              <a-tree-select
+                v-model:value="form.deptId"
+                :tree-data="enabledDeptOptions"
+                :field-names="{ value: 'id', label: 'label', children: 'children' }"
+                placeholder="请选择归属部门"
+                allow-clear
+                tree-default-expand-all
+              />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="手机号码" name="phonenumber">
+              <a-input v-model:value="form.phonenumber" placeholder="请输入手机号码" :maxlength="11" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="邮箱" name="email">
+              <a-input v-model:value="form.email" placeholder="请输入邮箱" :maxlength="50" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item v-if="form.userId == undefined" label="用户名称" name="userName">
+              <a-input v-model:value="form.userName" placeholder="请输入用户名称" :maxlength="30" />
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item v-if="form.userId == undefined" label="用户密码" name="password">
+              <a-input-password v-model:value="form.password" placeholder="请输入用户密码" :maxlength="20" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="用户性别">
+              <a-select v-model:value="form.sex" placeholder="请选择">
+                <a-select-option v-for="dict in sys_user_sex" :key="dict.value" :value="dict.value">{{ dict.label }}</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="状态">
+              <a-radio-group v-model:value="form.status">
+                <a-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</a-radio>
+              </a-radio-group>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="12">
+            <a-form-item label="岗位">
+              <a-select v-model:value="form.postIds" mode="multiple" placeholder="请选择">
+                <a-select-option v-for="item in postOptions" :key="item.postId" :value="item.postId" :disabled="item.status == 1">{{ item.postName }}</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="角色">
+              <a-select v-model:value="form.roleIds" mode="multiple" placeholder="请选择">
+                <a-select-option v-for="item in roleOptions" :key="item.roleId" :value="item.roleId" :disabled="item.status == 1">{{ item.roleName }}</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-form-item label="备注" :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }">
+              <a-textarea v-model:value="form.remark" placeholder="请输入内容" />
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
+          <a-button type="primary" @click="submitForm">确 定</a-button>
+          <a-button @click="cancel">取 消</a-button>
         </div>
       </template>
-    </el-dialog>
+    </a-modal>
 
     <!-- 用户导入对话框 -->
-    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
-      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :on-change="handleFileChange" :on-remove="handleFileRemove" :auto-upload="false" drag>
-        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
-        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-        <template #tip>
-          <div class="el-upload__tip text-center">
-            <div class="el-upload__tip">
-              <el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
-            </div>
-            <span>仅允许导入xls、xlsx格式文件。</span>
-            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
-          </div>
-        </template>
-      </el-upload>
+    <a-modal :title="upload.title" v-model:open="upload.open" width="400px">
+      <a-upload-dragger
+        ref="uploadRef"
+        :max-count="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="upload.url + '?updateSupport=' + upload.updateSupport"
+        :disabled="upload.isUploading"
+        :before-upload="handleBeforeUpload"
+        @change="handleUploadChange"
+      >
+        <p class="ant-upload-drag-icon">
+          <InboxOutlined />
+        </p>
+        <p class="ant-upload-text">将文件拖到此处,或<em>点击上传</em></p>
+        <p class="ant-upload-hint">
+          <a-checkbox v-model:checked="upload.updateSupport" :true-value="1" :false-value="0" />是否更新已经存在的用户数据
+        </p>
+        <p class="ant-upload-hint">
+          仅允许导入xls、xlsx格式文件。
+          <a @click.stop="importTemplate" style="color: #1890ff">下载模板</a>
+        </p>
+      </a-upload-dragger>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitFileForm">确 定</el-button>
-          <el-button @click="upload.open = false">取 消</el-button>
+          <a-button type="primary" @click="submitFileForm">确 定</a-button>
+          <a-button @click="upload.open = false">取 消</a-button>
         </div>
       </template>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
@@ -219,6 +240,11 @@ import useAppStore from '@/store/modules/app'
 import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user"
 import { Splitpanes, Pane } from "splitpanes"
 import "splitpanes/dist/splitpanes.css"
+import { Modal, message } from 'ant-design-vue'
+import {
+  SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
+  UploadOutlined, DownloadOutlined, KeyOutlined, CheckCircleOutlined, InboxOutlined
+} from '@ant-design/icons-vue'
 
 const router = useRouter()
 const appStore = useAppStore()
@@ -230,14 +256,15 @@ const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
 const ids = ref([])
+const selectedRowKeys = ref([])
 const single = ref(true)
 const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 const dateRange = ref([])
 const deptName = ref("")
-const deptOptions = ref(undefined)
-const enabledDeptOptions = ref(undefined)
+const deptOptions = ref([])
+const enabledDeptOptions = ref([])
 const initPassword = ref(undefined)
 const postOptions = ref([])
 const roleOptions = ref([])
@@ -254,7 +281,9 @@ const upload = reactive({
   // 设置上传的请求头部
   headers: { Authorization: "Bearer " + getToken() },
   // 上传的地址
-  url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
+  url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData",
+  // 选中的文件
+  fileList: []
 })
 // 列显隐信息
 const columns = ref({
@@ -267,6 +296,34 @@ const columns = ref({
   createTime: { label: '创建时间', visible: true }
 })
 
+// 表格列定义
+const tableColumns = computed(() => {
+  const cols = []
+  if (columns.value.userId.visible) {
+    cols.push({ title: '用户编号', dataIndex: 'userId', key: 'userId', align: 'center' })
+  }
+  if (columns.value.userName.visible) {
+    cols.push({ title: '用户名称', dataIndex: 'userName', key: 'userName', align: 'center', ellipsis: true })
+  }
+  if (columns.value.nickName.visible) {
+    cols.push({ title: '用户昵称', dataIndex: 'nickName', key: 'nickName', align: 'center', ellipsis: true })
+  }
+  if (columns.value.deptName.visible) {
+    cols.push({ title: '部门', dataIndex: ['dept', 'deptName'], key: 'deptName', align: 'center', ellipsis: true })
+  }
+  if (columns.value.phonenumber.visible) {
+    cols.push({ title: '手机号码', dataIndex: 'phonenumber', key: 'phonenumber', align: 'center', width: 120 })
+  }
+  if (columns.value.status.visible) {
+    cols.push({ title: '状态', key: 'status', align: 'center' })
+  }
+  if (columns.value.createTime.visible) {
+    cols.push({ title: '创建时间', key: 'createTime', align: 'center', width: 160 })
+  }
+  cols.push({ title: '操作', key: 'action', align: 'center', width: 150 })
+  return cols
+})
+
 const data = reactive({
   form: {},
   queryParams: {
@@ -280,7 +337,7 @@ const data = reactive({
   rules: {
     userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }],
     nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
-    password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
+    password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\ |", trigger: "blur" }],
     email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
     phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
   }
@@ -289,20 +346,20 @@ const data = reactive({
 const { queryParams, form, rules } = toRefs(data)
 
 /** 通过条件过滤节点  */
-const filterNode = (value, data) => {
-  if (!value) return true
-  return data.label.indexOf(value) !== -1
+const filterNode = (node) => {
+  if (!deptName.value) return true
+  return node.label.indexOf(deptName.value) !== -1
 }
 
-/** 根据名称筛选部门树 */
-watch(deptName, val => {
-  proxy.$refs["deptTreeRef"].filter(val)
-})
-
 /** 查询用户列表 */
 function getList() {
   loading.value = true
-  listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
+  const params = { ...queryParams.value }
+  if (dateRange.value && dateRange.value.length === 2) {
+    params.beginTime = dateRange.value[0]
+    params.endTime = dateRange.value[1]
+  }
+  listUser(params).then(res => {
     loading.value = false
     userList.value = res.rows
     total.value = res.total
@@ -331,8 +388,12 @@ function filterDisabledDept(deptList) {
 }
 
 /** 节点单击事件 */
-function handleNodeClick(data) {
-  queryParams.value.deptId = data.id
+function handleNodeClick(selectedKeys, info) {
+  if (selectedKeys.length > 0) {
+    queryParams.value.deptId = selectedKeys[0]
+  } else {
+    queryParams.value.deptId = undefined
+  }
   handleQuery()
 }
 
@@ -345,21 +406,30 @@ function handleQuery() {
 /** 重置按钮操作 */
 function resetQuery() {
   dateRange.value = []
-  proxy.resetForm("queryRef")
-  queryParams.value.deptId = undefined
-  proxy.$refs.deptTreeRef.setCurrentKey(null)
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    userName: undefined,
+    phonenumber: undefined,
+    status: undefined,
+    deptId: undefined
+  }
   handleQuery()
 }
 
 /** 删除按钮操作 */
 function handleDelete(row) {
   const userIds = row.userId || ids.value
-  proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () {
-    return delUser(userIds)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
+  Modal.confirm({
+    title: '提示',
+    content: '是否确认删除用户编号为"' + userIds + '"的数据项?',
+    onOk() {
+      return delUser(userIds).then(() => {
+        getList()
+        message.success("删除成功")
+      })
+    }
+  })
 }
 
 /** 导出按钮操作 */
@@ -370,31 +440,24 @@ function handleExport() {
 }
 
 /** 用户状态修改  */
-function handleStatusChange(row) {
-  let text = row.status === "0" ? "启用" : "停用"
-  proxy.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?').then(function () {
-    return changeUserStatus(row.userId, row.status)
-  }).then(() => {
-    proxy.$modal.msgSuccess(text + "成功")
-  }).catch(function () {
-    row.status = row.status === "0" ? "1" : "0"
+function handleStatusChange(row, checked) {
+  const newStatus = checked ? "0" : "1"
+  let text = newStatus === "0" ? "启用" : "停用"
+  Modal.confirm({
+    title: '提示',
+    content: '确认要"' + text + '""' + row.userName + '"用户吗?',
+    onOk() {
+      return changeUserStatus(row.userId, newStatus).then(() => {
+        row.status = newStatus
+        message.success(text + "成功")
+      })
+    },
+    onCancel() {
+      // 取消时不需要做任何事,因为 switch 状态由 :checked 控制
+    }
   })
 }
 
-/** 更多操作 */
-function handleCommand(command, row) {
-  switch (command) {
-    case "handleResetPwd":
-      handleResetPwd(row)
-      break
-    case "handleAuthRole":
-      handleAuthRole(row)
-      break
-    default:
-      break
-  }
-}
-
 /** 跳转角色分配 */
 function handleAuthRole(row) {
   const userId = row.userId
@@ -403,36 +466,48 @@ function handleAuthRole(row) {
 
 /** 重置密码按钮操作 */
 function handleResetPwd(row) {
-  proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
-    confirmButtonText: "确定",
-    cancelButtonText: "取消",
-    closeOnClickModal: false,
-    inputPattern: /^.{5,20}$/,
-    inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
-    inputValidator: (value) => {
+  const inputValue = ref('')
+  Modal.confirm({
+    title: '提示',
+    content: () => h('div', [
+      h('p', '请输入"' + row.userName + '"的新密码'),
+      h('input', {
+        type: 'password',
+        style: 'width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px;',
+        value: inputValue.value,
+        onInput: (e) => { inputValue.value = e.target.value }
+      })
+    ]),
+    onOk() {
+      const value = inputValue.value
+      if (!value || value.length < 5 || value.length > 20) {
+        message.error("用户密码长度必须介于 5 和 20 之间")
+        return Promise.reject()
+      }
       if (/<|>|"|'|\||\\/.test(value)) {
-        return "不能包含非法字符:< > \" ' \\\ |"
+        message.error("不能包含非法字符:< > \" ' \\ |")
+        return Promise.reject()
       }
-    },
-  }).then(({ value }) => {
-    resetUserPwd(row.userId, value).then(response => {
-      proxy.$modal.msgSuccess("修改成功,新密码是:" + value)
-    })
-  }).catch(() => {})
+      return resetUserPwd(row.userId, value).then(() => {
+        message.success("修改成功,新密码是:" + value)
+      })
+    }
+  })
 }
 
 /** 选择条数  */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.userId)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
+function handleSelectionChange(keys, rows) {
+  selectedRowKeys.value = keys
+  ids.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
 }
 
 /** 导入按钮操作 */
 function handleImport() {
   upload.title = "用户导入"
   upload.open = true
-  upload.selectedFile = null
+  upload.fileList = []
 }
 
 /** 下载模板操作 */
@@ -441,38 +516,67 @@ function importTemplate() {
   }, `user_template_${new Date().getTime()}.xlsx`)
 }
 
-/**文件上传中处理 */
-const handleFileUploadProgress = (event, file, fileList) => {
-  upload.isUploading = true
-}
-
-/** 文件选择处理 */
-const handleFileChange = (file, fileList) => {
-  upload.selectedFile = file
-}
-
-/** 文件删除处理 */
-const handleFileRemove = (file, fileList) => {
-  upload.selectedFile = null
+/** 文件上传前处理 */
+const handleBeforeUpload = (file) => {
+  upload.fileList = [file]
+  return false // 阻止自动上传
 }
 
-/** 文件上传成功处理 */
-const handleFileSuccess = (response, file, fileList) => {
-  upload.open = false
-  upload.isUploading = false
-  proxy.$refs["uploadRef"].handleRemove(file)
-  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
-  getList()
+/** 文件上传变化处理 */
+const handleUploadChange = (info) => {
+  if (info.file.status === 'done') {
+    upload.open = false
+    upload.isUploading = false
+    const response = info.file.response
+    Modal.info({
+      title: '导入结果',
+      content: h('div', { innerHTML: response.msg, style: 'overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;' })
+    })
+    getList()
+  } else if (info.file.status === 'uploading') {
+    upload.isUploading = true
+  } else if (info.file.status === 'error') {
+    upload.isUploading = false
+    message.error('上传失败')
+  }
 }
 
 /** 提交上传文件 */
 function submitFileForm() {
-  const file = upload.selectedFile
-  if (!file || file.length === 0 || !file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx')) {
-    proxy.$modal.msgError("请选择后缀为 “xls”或“xlsx”的文件。")
+  if (upload.fileList.length === 0) {
+    message.error('请选择后缀为 "xls"或"xlsx"的文件。')
     return
   }
-  proxy.$refs["uploadRef"].submit()
+  const file = upload.fileList[0]
+  if (!file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx')) {
+    message.error('请选择后缀为 "xls"或"xlsx"的文件。')
+    return
+  }
+  
+  // 手动上传文件
+  const formData = new FormData()
+  formData.append('file', file)
+  
+  upload.isUploading = true
+  fetch(upload.url + '?updateSupport=' + upload.updateSupport, {
+    method: 'POST',
+    headers: upload.headers,
+    body: formData
+  })
+  .then(response => response.json())
+  .then(response => {
+    upload.open = false
+    upload.isUploading = false
+    Modal.info({
+      title: '导入结果',
+      content: h('div', { innerHTML: response.msg, style: 'overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0;' })
+    })
+    getList()
+  })
+  .catch(() => {
+    upload.isUploading = false
+    message.error('上传失败')
+  })
 }
 
 /** 重置操作表单 */
@@ -491,7 +595,6 @@ function reset() {
     postIds: [],
     roleIds: []
   }
-  proxy.resetForm("userRef")
 }
 
 /** 取消按钮 */
@@ -524,29 +627,27 @@ function handleUpdate(row) {
     form.value.roleIds = response.roleIds
     open.value = true
     title.value = "修改用户"
-    form.password = ""
+    form.value.password = ""
   })
 }
 
 /** 提交按钮 */
 function submitForm() {
-  proxy.$refs["userRef"].validate(valid => {
-    if (valid) {
-      if (form.value.userId != undefined) {
-        updateUser(form.value).then(response => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addUser(form.value).then(response => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+  proxy.$refs["userRef"].validate().then(() => {
+    if (form.value.userId != undefined) {
+      updateUser(form.value).then(response => {
+        message.success("修改成功")
+        open.value = false
+        getList()
+      })
+    } else {
+      addUser(form.value).then(response => {
+        message.success("新增成功")
+        open.value = false
+        getList()
+      })
     }
-  })
+  }).catch(() => {})
 }
 
 onMounted(() => {
@@ -557,3 +658,92 @@ onMounted(() => {
   })
 })
 </script>
+
+<style lang="scss" scoped>
+// 搜索表单布局
+.ant-form-inline {
+  background: #fff;
+  padding: 16px;
+  border-radius: 4px;
+  margin-bottom: 16px;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: flex-start;
+  
+  .ant-form-item {
+    margin-right: 16px;
+    margin-bottom: 16px;
+    display: flex;
+    align-items: center;
+    
+    .ant-form-item-label {
+      padding-right: 8px;
+      flex-shrink: 0;
+      
+      > label {
+        white-space: nowrap;
+        height: 32px;
+        line-height: 32px;
+      }
+    }
+    
+    .ant-form-item-control {
+      flex: 1;
+    }
+    
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+}
+
+// 按钮组间距
+.mb8 {
+  margin-bottom: 16px;
+  
+  .ant-col {
+    .ant-btn {
+      margin-right: 8px;
+      
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+// 表格容器
+.ant-table-wrapper {
+  background: #fff;
+  padding: 16px;
+  border-radius: 4px;
+}
+
+// 表格样式
+.ant-table {
+  .ant-table-thead > tr > th {
+    background-color: #fafafa;
+    font-weight: 600;
+    color: #262626;
+  }
+  
+  .ant-table-tbody > tr > td {
+    padding: 12px 16px;
+  }
+}
+
+// 按钮图标间距
+.ant-btn {
+  .anticon + span,
+  span + .anticon {
+    margin-left: 4px;
+  }
+}
+
+// 确保日期选择器宽度正确
+.ant-form-item {
+  .ant-picker {
+    width: 308px;
+  }
+}
+</style>

+ 30 - 24
yushu-uivue3/src/views/system/user/profile/index.vue

@@ -1,9 +1,9 @@
 <template>
    <div class="app-container">
-      <el-row :gutter="20">
-         <el-col :span="6" :xs="24">
-            <el-card class="box-card" shadow="never">
-               <template v-slot:header>
+      <a-row :gutter="20">
+         <a-col :span="6" :xs="24">
+            <a-card class="box-card" :bordered="false">
+               <template #title>
                  <div class="card-header">
                    <span>个人信息</span>
                  </div>
@@ -42,26 +42,26 @@
                      </li>
                   </ul>
                </div>
-            </el-card>
-         </el-col>
-         <el-col :span="18" :xs="24">
-            <el-card class="box-card" shadow="never">
-               <template v-slot:header>
+            </a-card>
+         </a-col>
+         <a-col :span="18" :xs="24">
+            <a-card class="box-card" :bordered="false">
+               <template #title>
                  <div class="card-header">
                    <span>基本资料</span>
                  </div>
                </template>
-               <el-tabs v-model="selectedTab" class="custom-tabs">
-                  <el-tab-pane label="基本资料" name="userinfo">
+               <a-tabs v-model:activeKey="selectedTab" class="custom-tabs">
+                  <a-tab-pane key="userinfo" tab="基本资料">
                      <userInfo :user="state.user" />
-                  </el-tab-pane>
-                  <el-tab-pane label="修改密码" name="resetPwd">
+                  </a-tab-pane>
+                  <a-tab-pane key="resetPwd" tab="修改密码">
                      <resetPwd />
-                  </el-tab-pane>
-               </el-tabs>
-            </el-card>
-         </el-col>
-      </el-row>
+                  </a-tab-pane>
+               </a-tabs>
+            </a-card>
+         </a-col>
+      </a-row>
    </div>
 </template>
 
@@ -167,18 +167,24 @@ onMounted(() => {
 }
 
 .custom-tabs {
-  :deep(.el-tabs__nav-wrap::after) {
-    background-color: transparent;
+  :deep(.ant-tabs-nav::before) {
+    border-bottom: none;
   }
   
-  :deep(.el-tabs__item) {
+  :deep(.ant-tabs-tab) {
     font-size: 15px;
     color: #606266;
     
-    &.is-active {
-      color: var(--el-color-primary);
-      font-weight: 600;
+    &.ant-tabs-tab-active {
+      .ant-tabs-tab-btn {
+        color: var(--ant-primary-color);
+        font-weight: 600;
+      }
     }
   }
 }
+
+.text-center {
+  text-align: center;
+}
 </style>

+ 22 - 23
yushu-uivue3/src/views/system/user/profile/resetPwd.vue

@@ -1,23 +1,24 @@
 <template>
-   <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
-      <el-form-item label="旧密码" prop="oldPassword">
-         <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
-      </el-form-item>
-      <el-form-item label="新密码" prop="newPassword">
-         <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
-      </el-form-item>
-      <el-form-item label="确认密码" prop="confirmPassword">
-         <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
-      </el-form-item>
-      <el-form-item>
-      <el-button type="primary" @click="submit">保存</el-button>
-      <el-button type="danger" @click="close">关闭</el-button>
-      </el-form-item>
-   </el-form>
+   <a-form ref="pwdRef" :model="user" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
+      <a-form-item label="旧密码" name="oldPassword">
+         <a-input-password v-model:value="user.oldPassword" placeholder="请输入旧密码" />
+      </a-form-item>
+      <a-form-item label="新密码" name="newPassword">
+         <a-input-password v-model:value="user.newPassword" placeholder="请输入新密码" />
+      </a-form-item>
+      <a-form-item label="确认密码" name="confirmPassword">
+         <a-input-password v-model:value="user.confirmPassword" placeholder="请确认新密码" />
+      </a-form-item>
+      <a-form-item :wrapper-col="{ offset: 4 }">
+        <a-button type="primary" @click="submit">保存</a-button>
+        <a-button danger style="margin-left: 8px" @click="close">关闭</a-button>
+      </a-form-item>
+   </a-form>
 </template>
 
 <script setup>
 import { updateUserPwd } from "@/api/system/user"
+import { message } from 'ant-design-vue'
 
 const { proxy } = getCurrentInstance()
 
@@ -37,19 +38,17 @@ const equalToPassword = (rule, value, callback) => {
 
 const rules = ref({
   oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
-  newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
+  newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\ |", trigger: "blur" }],
   confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
 })
 
 /** 提交按钮 */
 function submit() {
-  proxy.$refs.pwdRef.validate(valid => {
-    if (valid) {
-      updateUserPwd(user.oldPassword, user.newPassword).then(response => {
-        proxy.$modal.msgSuccess("修改成功")
-      })
-    }
-  })
+  proxy.$refs.pwdRef.validate().then(() => {
+    updateUserPwd(user.oldPassword, user.newPassword).then(response => {
+      message.success("修改成功")
+    })
+  }).catch(() => {})
 }
 
 /** 关闭按钮 */

+ 44 - 46
yushu-uivue3/src/views/system/user/profile/userAvatar.vue

@@ -2,11 +2,11 @@
   <div class="user-info-head" @click="editCropper()">
     <img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
     <div class="mask">
-      <el-icon><Camera /></el-icon>
+      <CameraOutlined />
     </div>
-    <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog" class="avatar-dialog">
-      <el-row :gutter="20">
-        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+    <a-modal :title="title" v-model:open="open" width="800px" @afterClose="closeDialog" class="avatar-dialog" :footer="null">
+      <a-row :gutter="20">
+        <a-col :xs="24" :md="12" :style="{ height: '350px' }">
           <vue-cropper
             ref="cropper"
             :img="options.img"
@@ -19,48 +19,47 @@
             @realTime="realTime"
             v-if="visible"
           />
-        </el-col>
-        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+        </a-col>
+        <a-col :xs="24" :md="12" :style="{ height: '350px' }">
           <div class="avatar-preview-wrapper">
             <div class="avatar-upload-preview">
               <img :src="options.previews.url" :style="options.previews.img" />
             </div>
             <div class="preview-text">预览</div>
           </div>
-        </el-col>
-      </el-row>
+        </a-col>
+      </a-row>
       <br />
-      <el-row>
-        <el-col :lg="2" :md="2">
-          <el-upload
-            action="#"
-            :http-request="requestUpload"
-            :show-file-list="false"
-            :before-upload="beforeUpload"
+      <a-row>
+        <a-col :lg="2" :md="2">
+          <a-upload
+            :customRequest="requestUpload"
+            :showUploadList="false"
+            :beforeUpload="beforeUpload"
           >
-            <el-button>
+            <a-button>
               选择
-              <el-icon class="el-icon--right"><Upload /></el-icon>
-            </el-button>
-          </el-upload>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
-          <el-button icon="Plus" @click="changeScale(1)"></el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button icon="Minus" @click="changeScale(-1)"></el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
-        </el-col>
-        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
-          <el-button type="primary" @click="uploadImg()">提 交</el-button>
-        </el-col>
-      </el-row>
-    </el-dialog>
+              <UploadOutlined />
+            </a-button>
+          </a-upload>
+        </a-col>
+        <a-col :lg="{ span: 1, offset: 2 }" :md="2">
+          <a-button @click="changeScale(1)"><PlusOutlined /></a-button>
+        </a-col>
+        <a-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <a-button @click="changeScale(-1)"><MinusOutlined /></a-button>
+        </a-col>
+        <a-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <a-button @click="rotateLeft()"><UndoOutlined /></a-button>
+        </a-col>
+        <a-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <a-button @click="rotateRight()"><RedoOutlined /></a-button>
+        </a-col>
+        <a-col :lg="{ span: 2, offset: 6 }" :md="2">
+          <a-button type="primary" @click="uploadImg()">提 交</a-button>
+        </a-col>
+      </a-row>
+    </a-modal>
   </div>
 </template>
 
@@ -69,7 +68,8 @@ import "vue-cropper/dist/index.css"
 import { VueCropper } from "vue-cropper"
 import { uploadAvatar } from "@/api/system/user"
 import useUserStore from "@/store/modules/user"
-import { Camera, Upload, Plus, Minus, RefreshLeft, RefreshRight } from '@element-plus/icons-vue'
+import { CameraOutlined, UploadOutlined, PlusOutlined, MinusOutlined, UndoOutlined, RedoOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
 
 const userStore = useUserStore()
 const { proxy } = getCurrentInstance()
@@ -93,10 +93,6 @@ const options = reactive({
 /** 编辑头像 */
 function editCropper() {
   open.value = true
-}
-
-/** 打开弹出层结束时的回调 */
-function modalOpened() {
   visible.value = true
 }
 
@@ -122,7 +118,8 @@ function changeScale(num) {
 /** 上传预处理 */
 function beforeUpload(file) {
   if (file.type.indexOf("image/") == -1) {
-    proxy.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
+    message.error("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
+    return false
   } else {
     const reader = new FileReader()
     reader.readAsDataURL(file)
@@ -130,6 +127,7 @@ function beforeUpload(file) {
       options.img = reader.result
       options.filename = file.name
     }
+    return false
   }
 }
 
@@ -142,7 +140,7 @@ function uploadImg() {
       open.value = false
       options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
       userStore.avatar = options.img
-      proxy.$modal.msgSuccess("修改成功")
+      message.success("修改成功")
       visible.value = false
     })
   })
@@ -156,7 +154,7 @@ function realTime(data) {
 /** 关闭窗口 */
 function closeDialog() {
   options.img = userStore.avatar
-  options.visible = false
+  visible.value = false
 }
 </script>
 
@@ -222,4 +220,4 @@ function closeDialog() {
   font-size: 14px;
   margin-top: 15px;
 }
-</style>
+</style>

+ 29 - 43
yushu-uivue3/src/views/system/user/profile/userInfo.vue

@@ -1,29 +1,30 @@
 <template>
-   <el-form ref="userRef" :model="form" :rules="rules" label-width="80px" class="user-info-form">
-      <el-form-item label="用户昵称" prop="nickName">
-         <el-input v-model="form.nickName" maxlength="30" />
-      </el-form-item>
-      <el-form-item label="手机号码" prop="phonenumber">
-         <el-input v-model="form.phonenumber" maxlength="11" />
-      </el-form-item>
-      <el-form-item label="邮箱" prop="email">
-         <el-input v-model="form.email" maxlength="50" />
-      </el-form-item>
-      <el-form-item label="性别">
-         <el-radio-group v-model="form.sex">
-            <el-radio value="0">男</el-radio>
-            <el-radio value="1">女</el-radio>
-         </el-radio-group>
-      </el-form-item>
-      <el-form-item class="form-actions">
-         <el-button type="primary" @click="submit">保存</el-button>
-         <el-button type="danger" plain @click="close">关闭</el-button>
-      </el-form-item>
-   </el-form>
+   <a-form ref="userRef" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }" class="user-info-form">
+      <a-form-item label="用户昵称" name="nickName">
+         <a-input v-model:value="form.nickName" :maxlength="30" />
+      </a-form-item>
+      <a-form-item label="手机号码" name="phonenumber">
+         <a-input v-model:value="form.phonenumber" :maxlength="11" />
+      </a-form-item>
+      <a-form-item label="邮箱" name="email">
+         <a-input v-model:value="form.email" :maxlength="50" />
+      </a-form-item>
+      <a-form-item label="性别">
+         <a-radio-group v-model:value="form.sex">
+            <a-radio value="0">男</a-radio>
+            <a-radio value="1">女</a-radio>
+         </a-radio-group>
+      </a-form-item>
+      <a-form-item class="form-actions" :wrapper-col="{ offset: 4 }">
+         <a-button type="primary" @click="submit">保存</a-button>
+         <a-button danger style="margin-left: 8px" @click="close">关闭</a-button>
+      </a-form-item>
+   </a-form>
 </template>
 
 <script setup>
 import { updateUserProfile } from "@/api/system/user"
+import { message } from 'ant-design-vue'
 
 const props = defineProps({
   user: {
@@ -42,15 +43,13 @@ const rules = ref({
 
 /** 提交按钮 */
 function submit() {
-  proxy.$refs.userRef.validate(valid => {
-    if (valid) {
-      updateUserProfile(form.value).then(response => {
-        proxy.$modal.msgSuccess("修改成功")
-        props.user.phonenumber = form.value.phonenumber
-        props.user.email = form.value.email
-      })
-    }
-  })
+  proxy.$refs.userRef.validate().then(() => {
+    updateUserProfile(form.value).then(response => {
+      message.success("修改成功")
+      props.user.phonenumber = form.value.phonenumber
+      props.user.email = form.value.email
+    })
+  }).catch(() => {})
 }
 
 /** 关闭按钮 */
@@ -71,19 +70,6 @@ watch(() => props.user, user => {
   max-width: 600px;
   padding-top: 10px;
   
-  :deep(.el-input__wrapper) {
-    box-shadow: 0 0 0 1px var(--el-border-color) inset;
-    transition: all 0.3s;
-    
-    &:hover {
-      box-shadow: 0 0 0 1px var(--el-border-color-hover) inset;
-    }
-    
-    &.is-focus {
-      box-shadow: 0 0 0 1px var(--el-color-primary) inset;
-    }
-  }
-  
   .form-actions {
     margin-top: 30px;
   }

+ 14 - 27
yushu-uivue3/src/views/tool/build/IconsDialog.vue

@@ -1,27 +1,26 @@
 <template>
   <div class="icon-dialog">
-    <el-dialog v-model="value" width="980px" :close-on-click-modal="false" :modal-append-to-body="false" @open="onOpen"
-      @close="onClose">
-      <template #header="{ close, titleId, titleClass }">
-        选择图标
-        <el-input v-model="key" size="small" :style="{ width: '260px' }" placeholder="请输入图标名称" prefix-icon="Search"
-          clearable />
+    <a-modal v-model:open="value" width="980px" :maskClosable="false" @open="onOpen" @cancel="onClose" title="选择图标">
+      <template #title>
+        <span>选择图标</span>
+        <a-input v-model:value="key" size="small" :style="{ width: '260px', marginLeft: '16px' }" placeholder="请输入图标名称" allow-clear>
+          <template #prefix><SearchOutlined /></template>
+        </a-input>
       </template>
       <ul class="icon-ul">
         <li v-for="icon in iconList" :key="icon" :class="active === icon ? 'active-item' : ''" @click="onSelect(icon)">
           <div>
-            <el-icon :size="30">
-              <component :is="icon" />
-            </el-icon>
+            <component :is="icon" :style="{ fontSize: '30px' }" />
             <div>{{ icon }}</div>
           </div>
         </li>
       </ul>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 <script setup>
-import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import * as AntDesignIconsVue from '@ant-design/icons-vue'
+import { SearchOutlined } from '@ant-design/icons-vue'
 import { watch } from 'vue'
 
 const iconList = ref([])
@@ -30,7 +29,7 @@ const key = ref('')
 const active = ref('')
 const emit = defineEmits(['select'])
 const value = defineModel()
-for (const [key] of Object.entries(ElementPlusIconsVue)) {
+for (const [key] of Object.entries(AntDesignIconsVue)) {
   iconList.value.push(key)
   originList.push(key)
 }
@@ -90,24 +89,12 @@ watch(key, (val) => {
 
 .icon-dialog {
   :deep() {
-    .el-dialog {
-      border-radius: 8px;
-      margin-bottom: 0;
-      margin-top: 4vh !important;
-      display: flex;
-      flex-direction: column;
-      max-height: 92vh;
-      overflow: hidden;
-      box-sizing: border-box;
-
-      .el-dialog__header {
-        padding-top: 14px;
-      }
-
-      .el-dialog__body {
+    .ant-modal {
+      .ant-modal-body {
         margin: 0 20px 20px 20px;
         padding: 0;
         overflow: auto;
+        max-height: 70vh;
       }
     }
   }

+ 2 - 2
yushu-uivue3/src/views/tool/build/index.vue

@@ -107,7 +107,7 @@ import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/
 import { makeUpJs } from '@/utils/generator/js'
 import { makeUpCss } from '@/utils/generator/css'
 import Download from '@/plugins/download'
-import { ElNotification } from 'element-plus'
+import { notification } from 'ant-design-vue'
 import DraggableItem from './DraggableItem'
 import RightPanel from './RightPanel'
 import CodeTypeDialog from './CodeTypeDialog'
@@ -300,7 +300,7 @@ onMounted(() => {
   clipboard = new ClipboardJS('#copyNode', {
     text: trigger => {
       const codeStr = generateCode()
-      ElNotification({ title: '成功', message: '代码已复制到剪切板,可粘贴。', type: 'success' })
+      notification.success({ message: '成功', description: '代码已复制到剪切板,可粘贴。' })
       return codeStr
     }
   })