ys пре 2 недеља
родитељ
комит
bc5b5e9533

+ 10 - 1
yushu-uivue3/src/App.vue

@@ -8,8 +8,17 @@ import { handleThemeStyle } from '@/utils/theme'
 
 onMounted(() => {
   nextTick(() => {
+    const settingsStore = useSettingsStore()
+    
     // 初始化主题样式
-    handleThemeStyle(useSettingsStore().theme)
+    handleThemeStyle(settingsStore.theme)
+    
+    // 初始化暗黑模式
+    if (settingsStore.isDark) {
+      document.documentElement.classList.add('dark')
+    } else {
+      document.documentElement.classList.remove('dark')
+    }
     
     // 移除 Loading 动画并显示 App
     const loader = document.querySelector('.app-loading')

+ 4 - 137
yushu-uivue3/src/assets/styles/ant-design-vue.scss

@@ -1,12 +1,13 @@
-// Ant Design Vue 样式覆盖
+// Ant Design Vue 样式 - 只保留必要的工具类样式,不覆盖组件默认样式
 
-// 上传组件样式
+// 上传组件样式 - 仅隐藏文件输入框
 .ant-upload {
   input[type="file"] {
     display: none !important;
   }
 }
 
+// 工具类样式
 .cell {
   .ant-tag {
     margin-right: 0px;
@@ -38,98 +39,6 @@
   }
 }
 
-// Modal 样式 - 玻璃质感
-.ant-modal {
-  .ant-modal-content {
-    border-radius: 16px !important;
-    background: rgba(255, 255, 255, 0.7) !important;
-    backdrop-filter: blur(20px) saturate(180%) !important;
-    -webkit-backdrop-filter: blur(20px) saturate(180%) !important;
-    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
-    border: 1px solid rgba(255, 255, 255, 0.5) !important;
-    overflow: hidden;
-  }
-
-  .ant-modal-header {
-    background: transparent !important;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
-    padding: 20px 24px !important;
-    
-    .ant-modal-title {
-      font-weight: 600;
-      color: #1e293b;
-    }
-  }
-
-  .ant-modal-close {
-    top: 24px;
-    right: 24px;
-    color: #94a3b8;
-    transition: all 0.3s;
-    
-    &:hover {
-      color: #ef4444;
-      transform: rotate(90deg);
-    }
-  }
-
-  .ant-modal-body {
-    background: transparent !important;
-    padding: 24px !important;
-    color: #334155;
-  }
-
-  .ant-modal-footer {
-    padding: 16px 24px 24px !important;
-    border-top: 1px solid rgba(0, 0, 0, 0.05);
-    background: transparent !important;
-  }
-}
-
-// Drawer 样式 - 玻璃质感
-.ant-drawer {
-  .ant-drawer-content {
-    background: rgba(255, 255, 255, 0.7) !important;
-    backdrop-filter: blur(20px) saturate(180%) !important;
-    -webkit-backdrop-filter: blur(20px) saturate(180%) !important;
-    box-shadow: -10px 0 30px rgba(0, 0, 0, 0.15) !important;
-    border-left: 1px solid rgba(255, 255, 255, 0.5) !important;
-  }
-  
-  .ant-drawer-header {
-    background: transparent !important;
-    margin-bottom: 0 !important;
-    padding: 20px 24px !important;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
-    
-    .ant-drawer-title {
-      font-weight: 600;
-      color: #1e293b;
-      font-size: 18px;
-    }
-    
-    .ant-drawer-close {
-      transition: all 0.3s;
-      &:hover {
-        color: #ef4444;
-        transform: rotate(90deg);
-      }
-    }
-  }
-  
-  .ant-drawer-body {
-    background: transparent !important;
-    padding: 24px !important;
-    color: #334155;
-  }
-
-  .ant-drawer-footer {
-    padding: 16px 24px 24px !important;
-    border-top: 1px solid rgba(0, 0, 0, 0.05);
-    background: transparent !important;
-  }
-}
-
 // 上传容器
 .upload-container {
   .ant-upload {
@@ -147,51 +56,9 @@
   display: inline-flex !important;
 }
 
-// 菜单样式
-.ant-menu {
-  border: none;
-  
-  &.ant-menu-inline-collapsed {
-    .ant-menu-submenu-title {
-      .ant-menu-submenu-arrow {
-        display: none;
-      }
-    }
-  }
-}
-
-// 下拉菜单样式
+// 下拉菜单样式 - 仅修复链接显示
 .ant-dropdown-menu {
   a {
     display: block;
   }
 }
-
-// Message 样式
-.ant-message {
-  .ant-message-notice {
-    .ant-message-notice-content {
-      border-radius: 12px !important;
-      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08) !important;
-      border: 1px solid rgba(255, 255, 255, 0.6) !important;
-      padding: 12px 20px !important;
-      min-width: 300px !important;
-      backdrop-filter: blur(12px) !important;
-      -webkit-backdrop-filter: blur(12px) !important;
-      background: rgba(255, 255, 255, 0.95) !important;
-    }
-  }
-}
-
-// Notification 样式
-.ant-notification {
-  .ant-notification-notice {
-    border-radius: 16px !important;
-    border: 1px solid rgba(255, 255, 255, 0.6) !important;
-    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1) !important;
-    padding: 16px 24px !important;
-    backdrop-filter: blur(16px) !important;
-    -webkit-backdrop-filter: blur(16px) !important;
-    background: rgba(255, 255, 255, 0.95) !important;
-  }
-}

+ 0 - 117
yushu-uivue3/src/assets/styles/index.scss

@@ -178,120 +178,3 @@ aside {
   }
 }
 
-// 现代化弹窗样式 - 毛玻璃效果
-.el-message {
-  border-radius: 12px !important;
-  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08) !important;
-  border: 1px solid rgba(255, 255, 255, 0.4) !important;
-  padding: 12px 20px !important;
-  min-width: 300px !important;
-  backdrop-filter: blur(12px) !important;
-  -webkit-backdrop-filter: blur(12px) !important;
-  background: rgba(255, 255, 255, 0.85) !important;
-  
-  .el-message__content {
-    font-weight: 500 !important;
-    font-size: 14px !important;
-    color: #303133 !important;
-  }
-  
-  .el-icon {
-    margin-right: 8px !important;
-    font-size: 18px !important;
-  }
-  
-  &.el-message--success {
-    background: rgba(240, 249, 235, 0.85) !important;
-    border-color: rgba(103, 194, 58, 0.2) !important;
-    .el-icon { color: #67c23a !important; }
-    .el-message__content { color: #67c23a !important; }
-  }
-  
-  &.el-message--warning {
-    background: rgba(253, 246, 236, 0.85) !important;
-    border-color: rgba(230, 162, 60, 0.2) !important;
-    .el-icon { color: #e6a23c !important; }
-    .el-message__content { color: #e6a23c !important; }
-  }
-  
-  &.el-message--error {
-    background: rgba(254, 240, 240, 0.85) !important;
-    border-color: rgba(245, 108, 108, 0.2) !important;
-    .el-icon { color: #f56c6c !important; }
-    .el-message__content { color: #f56c6c !important; }
-  }
-  
-  &.el-message--info {
-    background: rgba(244, 244, 245, 0.85) !important;
-    border-color: rgba(144, 147, 153, 0.2) !important;
-    .el-icon { color: #909399 !important; }
-    .el-message__content { color: #909399 !important; }
-  }
-}
-
-// 现代化 Notification 样式 - 毛玻璃效果
-.el-notification {
-  border-radius: 16px !important;
-  border: 1px solid rgba(255, 255, 255, 0.5) !important;
-  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12) !important;
-  padding: 16px 20px !important;
-  backdrop-filter: blur(16px) !important;
-  -webkit-backdrop-filter: blur(16px) !important;
-  background: rgba(255, 255, 255, 0.8) !important;
-  
-  .el-notification__title {
-    font-weight: 600 !important;
-    font-size: 16px !important;
-    color: #303133 !important;
-  }
-  
-  .el-notification__content {
-    color: #606266 !important;
-    font-size: 14px !important;
-    margin-top: 8px !important;
-  }
-}
-
-// 现代化 MessageBox 样式 - 毛玻璃效果
-.el-message-box {
-  border-radius: 16px !important;
-  border: 1px solid rgba(255, 255, 255, 0.6) !important;
-  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15) !important;
-  backdrop-filter: blur(20px) !important;
-  -webkit-backdrop-filter: blur(20px) !important;
-  background: rgba(255, 255, 255, 0.9) !important;
-  padding-bottom: 20px !important;
-  
-  .el-message-box__header {
-    padding: 20px 24px 10px !important;
-    .el-message-box__title {
-      font-weight: 600 !important;
-      font-size: 18px !important;
-    }
-  }
-  
-  .el-message-box__content {
-    padding: 10px 24px 20px !important;
-    font-size: 14px !important;
-    color: #606266 !important;
-  }
-  
-  .el-message-box__btns {
-    padding: 10px 24px 0 !important;
-    
-    .el-button {
-      border-radius: 6px !important;
-      padding: 9px 20px !important;
-      font-weight: 500 !important;
-      
-      &--primary {
-        background-color: #409eff !important;
-        border-color: #409eff !important;
-        &:hover {
-          background-color: #66b1ff !important;
-          border-color: #66b1ff !important;
-        }
-      }
-    }
-  }
-}

+ 16 - 417
yushu-uivue3/src/assets/styles/sidebar.scss

@@ -37,11 +37,6 @@
     &.theme-dark {
       --sidebar-bg: rgba(15, 23, 42, 0.85); // Slate 900
       --sidebar-text: #94a3b8; // Slate 400
-      --menu-hover: rgba(30, 41, 59, 0.6); // Slate 800
-      --menu-active-bg: rgba(56, 189, 248, 0.15); // Sky 400
-      --menu-active-text: #38bdf8; // Sky 400
-      --menu-active-border: rgba(56, 189, 248, 0.3);
-      --menu-item-hover: rgba(30, 41, 59, 0.6); // Slate 800
       
       // 星空粒子背景
       background-color: #0f172a !important;
@@ -57,25 +52,6 @@
       backdrop-filter: blur(10px);
       -webkit-backdrop-filter: blur(10px);
       border-right: 1px solid rgba(255, 255, 255, 0.05);
-      
-      .el-menu {
-        background: transparent !important;
-      }
-      
-      .el-menu-item, .el-sub-menu__title {
-        color: var(--sidebar-text) !important;
-        
-        &:hover {
-          background-color: var(--menu-item-hover) !important;
-          color: #fff !important;
-          box-shadow: inset 0 0 20px rgba(56, 189, 248, 0.15);
-          transform: translateX(4px);
-        }
-      }
-      
-      .is-active > .el-sub-menu__title {
-        color: #fff !important;
-      }
     }
 
     @keyframes star-move {
@@ -88,93 +64,18 @@
       --sidebar-bg: rgba(255, 255, 255, 0.85); // 半透明白
       --sidebar-bg-image: none;
       --sidebar-text: #475569; // Slate 600
-      --menu-hover: rgba(255, 255, 255, 0.6);
-      --menu-active-bg: rgba(255, 255, 255, 0.9); // 选中项高亮白
-      --menu-active-text: #2563eb; // Blue 600
-      --menu-active-border: transparent;
-      --menu-item-hover: rgba(241, 245, 249, 0.5); // Slate 100
       
       background: var(--sidebar-bg) !important;
       backdrop-filter: blur(12px); // 启用磨砂
       -webkit-backdrop-filter: blur(12px);
       border-right: 1px solid rgba(0, 0, 0, 0.05);
       animation: none;
-
-      .el-menu {
-        background: transparent !important;
-      }
-
-      .el-menu-item, .el-sub-menu__title {
-        color: var(--sidebar-text) !important;
-        margin: 4px 10px !important;
-        border-radius: 8px !important;
-        
-        &:hover {
-          background-color: var(--menu-item-hover) !important;
-          color: var(--menu-active-text) !important;
-          transform: translateY(-1px);
-          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); // 轻微阴影
-        }
-      }
-      
-      .is-active > .el-sub-menu__title {
-        color: var(--menu-active-text) !important;
-        font-weight: 600;
-      }
-
-      // 浅色模式下的选中状态 - 悬浮玻璃块
-      .el-menu-item.is-active {
-        background: linear-gradient(135deg, rgba(239, 246, 255, 0.8) 0%, rgba(219, 234, 254, 0.8) 100%) !important; // 柔和的淡蓝渐变
-        border: 1px solid rgba(255, 255, 255, 0.6); // 半透明白边框
-        border-right: none;
-        color: #2563eb !important; // Blue 600
-        font-weight: 600;
-        box-shadow: 0 4px 12px rgba(37, 99, 235, 0.08), inset 0 0 0 1px rgba(255, 255, 255, 0.5); // 柔和蓝影+内发光
-        backdrop-filter: blur(5px);
-        
-        &::after {
-          display: none;
-        }
-
-        .svg-icon {
-          color: #2563eb !important;
-          filter: drop-shadow(0 2px 4px rgba(37, 99, 235, 0.15));
-        }
-      }
-    }
-
-    // reset element-ui css
-    .horizontal-collapse-transition {
-      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
     }
 
     .scrollbar-wrapper {
       overflow-x: hidden !important;
     }
 
-    // 滚动条样式美化
-    .el-scrollbar__bar.is-vertical {
-      right: 0px;
-      width: 4px;
-      .el-scrollbar__thumb {
-        background-color: var(--el-text-color-secondary); // 自适应滚动条颜色
-        opacity: 0.3;
-        &:hover {
-          opacity: 0.5;
-        }
-      }
-    }
-
-    .el-scrollbar {
-      height: 100%;
-    }
-
-    &.has-logo {
-      .el-scrollbar {
-        height: calc(100% - 50px);
-      }
-    }
-
     .is-horizontal {
       display: none;
     }
@@ -193,183 +94,29 @@
       color: var(--sidebar-text); // 自适应图标颜色
     }
 
-    .el-menu {
-      border: none;
-      height: 100%;
-      width: 100% !important;
-      background-color: transparent !important;
-    }
-
-    .el-menu-item, .el-sub-menu__title {
-      overflow: hidden !important;
-      text-overflow: ellipsis !important;
-      white-space: nowrap !important;
-      height: 48px !important;
-      line-height: 48px !important;
-      margin: 4px 10px !important;
-      border-radius: 8px !important;
-      transition: all 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
-      color: var(--sidebar-text) !important;
-      display: flex !important; // 使用 flex 布局保证垂直居中
-      align-items: center !important;
-      padding-left: 12px !important; // 统一左侧内边距
-    }
-
-    // 修复折叠菜单样式
-    .el-menu--collapse {
-      .el-menu-item, .el-sub-menu__title {
-        margin: 4px 0 !important; // 折叠时取消左右margin
-        padding: 0 !important; // 强制取消所有padding
-        justify-content: center !important; // 图标居中
-        border-radius: 0 !important;
+    // 收缩状态下图标居中
+    .ant-menu-inline-collapsed {
+      width: 58px !important;
+      
+      .ant-menu-item,
+      .ant-menu-submenu-title {
+        padding: 0 !important;
         text-align: center;
         
-        .svg-icon {
-          margin-right: 0 !important; // 折叠时取消图标右间距
-          margin-left: 0 !important;  // 确保左边也没有间距
-          font-size: 18px;
+        .ant-menu-item-icon,
+        .anticon {
+          margin-right: 0 !important;
+          margin-left: 0 !important;
         }
-        
-        span {
-          display: none !important;
-          width: 0;
-          height: 0;
-          overflow: hidden;
-        }
-        
-        .el-sub-menu__icon-arrow {
-          display: none !important;
-        }
-      }
-      
-      // 修复折叠后弹出菜单的样式
-      .el-sub-menu {
-        &.is-active > .el-sub-menu__title {
-           color: var(--menu-active-text) !important;
-           background-color: var(--menu-item-hover) !important;
-        }
-      }
-    }
-
-    // 修复子菜单缩进问题 - 多级菜单层级区分
-    // 二级菜单项和二级目录标题(在一级目录下)
-    .nest-menu .el-menu-item,
-    .nest-menu .el-sub-menu__title {
-      padding-left: 48px !important;
-    }
-    
-    // 三级菜单项和三级目录标题(在二级目录下)
-    .nest-menu .nest-menu .el-menu-item,
-    .nest-menu .nest-menu .el-sub-menu__title {
-      padding-left: 64px !important;
-    }
-    
-    // 四级菜单项
-    .nest-menu .nest-menu .nest-menu .el-menu-item,
-    .nest-menu .nest-menu .nest-menu .el-sub-menu__title {
-      padding-left: 80px !important;
-    }
-
-    .el-menu-item .el-menu-tooltip__trigger {
-      display: inline-block !important;
-    }
-
-    // menu hover
-    .sub-menu-title-noDropdown,
-    .el-sub-menu__title {
-      color: var(--sidebar-text) !important;
-      position: relative; // 为伪元素定位
-      overflow: hidden;   // 防止光效溢出
-
-      &:hover {
-        background-color: var(--menu-item-hover) !important;
-        color: #fff !important;
-        box-shadow: inset 0 0 20px rgba(56, 189, 248, 0.15); // 稍微增强内发光
-        transform: translateX(4px); // 轻微右移,增加交互感
       }
-    }
-
-    & .theme-dark .is-active > .el-sub-menu__title {
-      color: var(--menu-active-text) !important;
-      font-weight: 600;
-    }
-
-    & .nest-menu .el-sub-menu>.el-sub-menu__title,
-    & .el-sub-menu .el-menu-item {
-      min-width: calc(vars.$base-sidebar-width - 20px) !important;
-      position: relative; // 定位
-      overflow: hidden;
-
-      &:hover {
-        background-color: var(--menu-item-hover) !important;
-        color: #fff !important;
-        box-shadow: inset 0 0 20px rgba(56, 189, 248, 0.15); // 稍微增强内发光
-        transform: translateX(4px); // 轻微右移
-      }
-    }
-
-    & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
-    & .theme-dark .el-sub-menu .el-menu-item {
-      background-color: transparent;
       
-      &:hover {
-        background-color: var(--menu-item-hover) !important;
+      .ant-menu-item-icon {
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
       }
     }
-
-    // 选中状态样式重写 - 磨砂玻璃质感 (Glassmorphism) - 自适应版
-    .el-menu-item.is-active {
-      background: rgba(56, 189, 248, 0.15) !important; // 稍微调高透明度
-      backdrop-filter: blur(12px);
-      -webkit-backdrop-filter: blur(12px);
-      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2), 
-                  inset 0 0 0 1px rgba(255, 255, 255, 0.1); // 内部微光边框
-      border: 1px solid rgba(56, 189, 248, 0.2); // 外部边框
-      color: #fff !important;
-      font-weight: 600;
-      position: relative;
-      overflow: hidden; // 确保光效不溢出
-      
-      &::before {
-        display: none;
-      }
-
-      // 中速流光掠过效果
-      &::after {
-        content: "";
-        position: absolute;
-        top: 0;
-        left: -100%;
-        width: 60%;
-        height: 100%;
-        background: linear-gradient(
-          to right,
-          rgba(255, 255, 255, 0) 0%,
-          rgba(255, 255, 255, 0.15) 50%,
-          rgba(255, 255, 255, 0) 100%
-        );
-        transform: skewX(-25deg);
-        animation: sidebar-shine 2s infinite; // 2s 中速循环
-      }
-
-      .svg-icon {
-        color: #fff !important;
-        filter: drop-shadow(0 0 5px rgba(56, 189, 248, 0.5));
-        position: relative;
-        z-index: 1; // 确保图标在光效之上
-      }
-      
-      span {
-        position: relative;
-        z-index: 1; // 确保文字在光效之上
-      }
-    }
-
-    @keyframes sidebar-shine {
-      0% { left: -100%; }
-      20% { left: 200%; } // 快速划过
-      100% { left: 200%; } // 停留一段时间
-    }
   }
 
   .hideSidebar {
@@ -380,57 +127,6 @@
     .main-container {
       margin-left: 54px;
     }
-
-    .sub-menu-title-noDropdown {
-      padding: 0 !important;
-      position: relative;
-
-      .el-tooltip {
-        padding: 0 !important;
-
-        .svg-icon {
-          margin-left: 20px;
-        }
-      }
-    }
-
-    .el-sub-menu {
-      overflow: hidden;
-
-      &>.el-sub-menu__title {
-        padding: 0 !important;
-
-        .svg-icon {
-          margin-left: 20px;
-        }
-
-      }
-    }
-
-    .el-menu--collapse {
-      .el-sub-menu {
-        &>.el-sub-menu__title {
-          &>span {
-            height: 0;
-            width: 0;
-            overflow: hidden;
-            visibility: hidden;
-            display: inline-block;
-          }
-          &>i {
-            height: 0;
-            width: 0;
-            overflow: hidden;
-            visibility: hidden;
-            display: inline-block;
-          }
-        }
-      }
-    }
-  }
-
-  .el-menu--collapse .el-menu .el-sub-menu {
-    min-width: vars.$base-sidebar-width !important;
   }
 
   // mobile responsive
@@ -461,100 +157,3 @@
     }
   }
 }
-
-// when menu collapsed
-.el-menu--vertical {
-  &>.el-menu {
-    .svg-icon {
-      margin-right: 16px;
-    }
-  }
-
-  .nest-menu .el-sub-menu>.el-sub-menu__title,
-  .el-menu-item {
-    &:hover {
-      // you can use $sub-menuHover
-      background-color: rgba(0, 0, 0, 0.06) !important;
-    }
-  }
-
-  // the scroll bar appears when the sub-menu is too long
-  >.el-menu--popup {
-    max-height: 100vh;
-    overflow-y: auto;
-
-    &::-webkit-scrollbar-track-piece {
-      background: #d3dce6;
-    }
-
-    &::-webkit-scrollbar {
-      width: 6px;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      background: #99a9bf;
-      border-radius: 20px;
-    }
-  }
-}
-
-// =============================================
-// 全局弹出菜单样式优化 (Popper Menu)
-// =============================================
-.el-menu--popup {
-  min-width: 180px;
-  border-radius: 12px !important; // 大圆角
-  padding: 6px !important; // 整体内边距
-  box-shadow: 0 10px 30px -10px rgba(0,0,0,0.15), 
-              0 4px 10px -4px rgba(0,0,0,0.1) !important; // 柔和深阴影
-  border: 1px solid var(--el-border-color-lighter) !important;
-
-  // 菜单项
-  .el-menu-item {
-    height: 40px;
-    line-height: 40px;
-    margin: 2px 0; // 上下间距
-    padding: 0 12px !important;
-    border-radius: 8px !important; // 子项圆角
-    color: var(--el-text-color-regular);
-    transition: all 0.2s;
-
-    &:hover {
-      background-color: var(--el-color-primary-light-9) !important;
-      color: var(--el-color-primary) !important;
-    }
-
-    &.is-active {
-      background-color: var(--el-color-primary) !important;
-      color: #fff !important;
-      font-weight: 500;
-      box-shadow: 0 2px 8px rgba(var(--el-color-primary-rgb), 0.25);
-    }
-
-    .svg-icon {
-      margin-right: 10px;
-      font-size: 16px;
-      vertical-align: -0.2em;
-    }
-  }
-}
-
-// 暗色模式下的弹出菜单适配
-html.dark .el-menu--popup {
-  background-color: #1e293b !important; // Slate 800
-  border: 1px solid #334155 !important; // Slate 700
-
-  .el-menu-item {
-    color: #cbd5e1; // Slate 300
-
-    &:hover {
-      background-color: #334155 !important; // Slate 700
-      color: #fff !important;
-    }
-
-    &.is-active {
-      background-color: var(--el-color-primary) !important;
-      color: #fff !important;
-    }
-  }
-}

+ 5 - 5
yushu-uivue3/src/components/Breadcrumb/index.vue

@@ -1,12 +1,12 @@
 <template>
-  <el-breadcrumb class="app-breadcrumb" separator="/">
+  <a-breadcrumb class="app-breadcrumb" separator="/">
     <transition-group name="breadcrumb">
-      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
+      <a-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
         <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
         <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
-      </el-breadcrumb-item>
+      </a-breadcrumb-item>
     </transition-group>
-  </el-breadcrumb>
+  </a-breadcrumb>
 </template>
 
 <script setup>
@@ -84,7 +84,7 @@ getBreadcrumb()
 </script>
 
 <style lang='scss' scoped>
-.app-breadcrumb.el-breadcrumb {
+.app-breadcrumb {
   display: inline-block;
   font-size: 14px;
   line-height: 50px;

+ 32 - 14
yushu-uivue3/src/components/HeaderSearch/index.vue

@@ -1,29 +1,31 @@
 <template>
   <div class="header-search">
     <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
-    <el-dialog
-      v-model="show"
-      width="600"
-      @close="close"
-      :show-close="false"
-      append-to-body
+    <a-modal
+      v-model:open="show"
+      :width="600"
+      :footer="null"
+      :mask-closable="false"
+      @cancel="close"
     >
-      <el-input
-        v-model="search"
+      <a-input
+        v-model:value="search"
         ref="headerSearchSelectRef"
         size="large"
         @input="querySearch"
-        prefix-icon="Search"
         placeholder="菜单搜索,支持标题、URL模糊查询"
-        clearable
+        allow-clear
         @keyup.enter="selectActiveResult"
         @keydown.up.prevent="navigateResult('up')"
         @keydown.down.prevent="navigateResult('down')"
       >
-      </el-input>
+        <template #prefix>
+          <SearchOutlined />
+        </template>
+      </a-input>
 
       <div class="result-wrap">
-        <el-scrollbar>
+        <div class="result-scroll">
           <div class="search-item" tabindex="1" v-for="(item, index) in options" :key="item.path" :style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1">
             <div class="left">
               <svg-icon class="menu-icon" :icon-class="item.icon" />
@@ -38,14 +40,15 @@
             </div>
             <svg-icon icon-class="enter" v-show="index === activeIndex"/>
           </div>
-        </el-scrollbar>
+        </div>
       </div>
-    </el-dialog>
+    </a-modal>
   </div>
 </template>
 
 <script setup>
 import Fuse from 'fuse.js'
+import { SearchOutlined } from '@ant-design/icons-vue'
 import { getNormalPath } from '@/utils/yushu'
 import { isHttp } from '@/utils/validate'
 import useSettingsStore from '@/store/modules/settings'
@@ -210,6 +213,21 @@ watch(searchPool, (list) => {
   height: 280px;
   margin: 6px 0;
 
+  .result-scroll {
+    height: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+    
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+    
+    &::-webkit-scrollbar-thumb {
+      background: #d3d3d3;
+      border-radius: 3px;
+    }
+  }
+
   .search-item {
     display: flex;
     height: 48px;

+ 74 - 86
yushu-uivue3/src/components/TopNav/index.vue

@@ -1,35 +1,33 @@
 <template>
-  <el-menu
-    :default-active="activeMenu"
+  <a-menu
+    :selected-keys="[activeMenu]"
     mode="horizontal"
-    @select="handleSelect"
-    :ellipsis="false"
+    @click="handleMenuClick"
   >
     <template v-for="(item, index) in topMenus">
-      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber">
+      <a-menu-item :key="item.path" v-if="index < visibleNumber">
         <svg-icon
         v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
         :icon-class="item.meta.icon"/>
         {{ item.meta.title }}
-      </el-menu-item>
+      </a-menu-item>
     </template>
 
     <!-- 顶部菜单超出数量折叠 -->
-    <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
+    <a-sub-menu key="more" v-if="topMenus.length > visibleNumber">
       <template #title>更多菜单</template>
       <template v-for="(item, index) in topMenus">
-        <el-menu-item
-          :index="item.path"
-          :key="index"
+        <a-menu-item
+          :key="item.path"
           v-if="index >= visibleNumber">
         <svg-icon
           v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
           :icon-class="item.meta.icon"/>
         {{ item.meta.title }}
-        </el-menu-item>
+        </a-menu-item>
       </template>
-    </el-sub-menu>
-  </el-menu>
+    </a-sub-menu>
+  </a-menu>
 </template>
 
 <script setup>
@@ -117,7 +115,11 @@ function setVisibleNumber() {
   visibleNumber.value = parseInt(width / 85)
 }
 
-function handleSelect(key, keyPath) {
+function handleMenuClick({ key }) {
+  handleSelect(key)
+}
+
+function handleSelect(key) {
   currentIndex.value = key
   const route = routers.value.find(item => item.path === key)
   if (isHttp(key)) {
@@ -171,56 +173,14 @@ onMounted(() => {
 </script>
 
 <style lang="scss">
-.topmenu-container.el-menu--horizontal {
-  border-bottom: none !important;
-  background: transparent !important;
-  
-  > .el-menu-item {
-    float: left;
-    height: 46px !important; // 稍微增加高度
-    line-height: 46px !important;
-    color: var(--el-text-color-regular) !important;
-    padding: 0 12px !important;
-    margin: 0 4px !important;
-    border-radius: 6px; // 悬停时的圆角
+.topmenu-container {
+  :deep(.ant-menu) {
     border-bottom: none !important;
-    transition: all 0.3s;
-    position: relative; // 为底部条定位
-
-    &:hover {
-      color: var(--el-text-color-primary) !important;
-      background-color: rgba(0,0,0,0.04) !important; // 极淡的悬停背景
-    }
-
-    &.is-active {
-      color: var(--el-color-primary) !important;
-      background-color: transparent !important; // 移除选中背景
-      font-weight: 600;
-      box-shadow: none; // 移除投影
-      
-      // 底部发光条
-      &::after {
-        content: "";
-        position: absolute;
-        bottom: 2px;
-        left: 12px; // 左右留空
-        right: 12px;
-        height: 3px;
-        background-color: var(--el-color-primary);
-        border-radius: 3px;
-        transform: scaleX(1);
-        transition: transform 0.3s;
-      }
-    }
-  }
-
-  /* sub-menu item */
-  > .el-sub-menu {
-    .el-sub-menu__title {
-      float: left;
+    background: transparent !important;
+    
+    > .ant-menu-item {
       height: 46px !important;
       line-height: 46px !important;
-      color: var(--el-text-color-regular) !important;
       padding: 0 12px !important;
       margin: 0 4px !important;
       border-radius: 6px;
@@ -229,27 +189,63 @@ onMounted(() => {
       position: relative;
 
       &:hover {
-        color: var(--el-text-color-primary) !important;
         background-color: rgba(0,0,0,0.04) !important;
       }
+
+      &.ant-menu-item-selected {
+        background-color: transparent !important;
+        font-weight: 600;
+        box-shadow: none;
+        
+        // 底部发光条
+        &::after {
+          content: "";
+          position: absolute;
+          bottom: 2px;
+          left: 12px;
+          right: 12px;
+          height: 3px;
+          background-color: #1890ff;
+          border-radius: 3px;
+          transform: scaleX(1);
+          transition: transform 0.3s;
+        }
+      }
     }
 
-    &.is-active .el-sub-menu__title {
-      color: var(--el-color-primary) !important;
-      background-color: transparent !important;
-      font-weight: 600;
-      border-bottom: none !important;
-      
-      // 底部发光条
-      &::after {
-        content: "";
-        position: absolute;
-        bottom: 2px;
-        left: 12px;
-        right: 12px;
-        height: 3px;
-        background-color: var(--el-color-primary);
-        border-radius: 3px;
+    /* sub-menu item */
+    > .ant-menu-submenu {
+      .ant-menu-submenu-title {
+        height: 46px !important;
+        line-height: 46px !important;
+        padding: 0 12px !important;
+        margin: 0 4px !important;
+        border-radius: 6px;
+        border-bottom: none !important;
+        transition: all 0.3s;
+        position: relative;
+
+        &:hover {
+          background-color: rgba(0,0,0,0.04) !important;
+        }
+      }
+
+      &.ant-menu-submenu-selected .ant-menu-submenu-title {
+        background-color: transparent !important;
+        font-weight: 600;
+        border-bottom: none !important;
+        
+        // 底部发光条
+        &::after {
+          content: "";
+          position: absolute;
+          bottom: 2px;
+          left: 12px;
+          right: 12px;
+          height: 3px;
+          background-color: #1890ff;
+          border-radius: 3px;
+        }
       }
     }
   }
@@ -260,12 +256,4 @@ onMounted(() => {
   margin-right: 6px;
   vertical-align: -0.15em;
 }
-
-/* topmenu more arrow */
-.topmenu-container .el-sub-menu .el-sub-menu__icon-arrow {
-  position: static;
-  vertical-align: middle;
-  margin-left: 4px;
-  margin-top: 0px;
-}
 </style>

+ 58 - 1
yushu-uivue3/src/layout/components/AppMain.vue

@@ -1,5 +1,25 @@
 <template>
-  <section class="app-main">
+  <a-watermark
+    :content="watermarkContent"
+    :gap="[120, 120]"
+    :z-index="9999"
+    :font="{ color: 'rgba(0, 0, 0, 0.15)', fontSize: 14 }"
+    :rotate="-22"
+    v-if="watermarkVisible"
+  >
+    <section class="app-main">
+      <router-view v-slot="{ Component, route }">
+        <transition name="fade-transform" mode="out-in">
+          <keep-alive :include="tagsViewStore.cachedViews">
+            <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
+          </keep-alive>
+        </transition>
+      </router-view>
+      <iframe-toggle />
+      <copyright />
+    </section>
+  </a-watermark>
+  <section v-else class="app-main">
     <router-view v-slot="{ Component, route }">
       <transition name="fade-transform" mode="out-in">
         <keep-alive :include="tagsViewStore.cachedViews">
@@ -16,9 +36,46 @@
 import copyright from "./Copyright/index"
 import iframeToggle from "./IframeToggle/index"
 import useTagsViewStore from '@/store/modules/tagsView'
+import useSettingsStore from '@/store/modules/settings'
+import useUserStore from '@/store/modules/user'
+import { parseTime } from '@/utils/yushu'
 
 const route = useRoute()
 const tagsViewStore = useTagsViewStore()
+const settingsStore = useSettingsStore()
+const userStore = useUserStore()
+
+const watermarkVisible = computed(() => settingsStore.watermarkVisible)
+
+// 生成水印内容:系统名称、时间、用户信息(昵称和账号)
+const currentTime = ref(parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'))
+const watermarkContent = computed(() => {
+  const systemName = import.meta.env.VITE_APP_TITLE || '予书管理系统'
+  const nickName = userStore.nickName || ''
+  const userName = userStore.name || ''
+  const userInfo = nickName && userName ? `${nickName}(${userName})` : (nickName || userName || '未知用户')
+  
+  return [
+    systemName,
+    currentTime.value,
+    userInfo
+  ]
+})
+
+// 定时更新水印时间
+let timeInterval = null
+onMounted(() => {
+  // 每秒更新一次时间
+  timeInterval = setInterval(() => {
+    currentTime.value = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
+  }, 1000)
+})
+
+onUnmounted(() => {
+  if (timeInterval) {
+    clearInterval(timeInterval)
+  }
+})
 
 onMounted(() => {
   addIframe()

+ 2 - 13
yushu-uivue3/src/layout/components/Navbar.vue

@@ -20,9 +20,9 @@
         </a-tooltip>
       </template>
 
-      <a-dropdown @click="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover">
+      <a-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
         <div class="avatar-wrapper">
-          <img :src="userStore.avatar" class="user-avatar" />
+          <a-avatar :src="userStore.avatar" :size="32" />
           <span class="user-nickname"> {{ userStore.nickName }} </span>
         </div>
         <template #overlay>
@@ -281,14 +281,6 @@ function setLayout() {
           background: rgba(0,0,0,0.04);
         }
 
-        .user-avatar {
-          cursor: pointer;
-          width: 32px;
-          height: 32px;
-          border-radius: 50%;
-          border: 1px solid #e2e8f0; // 头像描边
-        }
-
         .user-nickname{
           margin-left: 8px;
           font-size: 14px;
@@ -331,9 +323,6 @@ html.dark .navbar {
       color: #f1f5f9;
     }
 
-    .user-avatar {
-      border-color: #334155;
-    }
   }
 }
 </style>

+ 55 - 17
yushu-uivue3/src/layout/components/Settings/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <a-drawer v-model:open="showSettings" :headerStyle="{ display: 'none' }" :maskClosable="false" placement="right" :width="300">
+  <a-drawer v-model:open="showSettings" title="系统设置" :maskClosable="true" placement="right" :width="300">
     <div class="setting-drawer-title">
       <h3 class="drawer-title">主题风格设置</h3>
     </div>
@@ -25,7 +25,7 @@
         </div>
       </div>
     </div>
-    <div class="drawer-item">
+    <div class="drawer-item" style="margin-top: 16px;">
       <span>主题颜色</span>
       <span class="comp-style">
         <a-color-picker v-model:value="theme" :presets="predefineColors.map(c => ({ label: c, value: c }))" @change="themeChange"/>
@@ -107,16 +107,25 @@
       </span>
     </div>
 
+    <div class="drawer-item">
+      <span>显示水印</span>
+      <span class="comp-style">
+        <a-switch v-model:checked="settingsStore.watermarkVisible" class="drawer-switch" />
+      </span>
+    </div>
+
     <a-divider />
 
-    <a-button type="primary" ghost @click="saveSetting">
-      <template #icon><FileAddOutlined /></template>
-      保存配置
-    </a-button>
-    <a-button ghost @click="resetSetting">
-      <template #icon><ReloadOutlined /></template>
-      重置配置
-    </a-button>
+    <div class="drawer-footer">
+      <a-button type="primary" @click="saveSetting" style="margin-right: 8px;">
+        <template #icon><FileAddOutlined /></template>
+        保存配置
+      </a-button>
+      <a-button @click="resetSetting">
+        <template #icon><ReloadOutlined /></template>
+        重置配置
+      </a-button>
+    </div>
   </a-drawer>
 
 </template>
@@ -140,6 +149,15 @@ const isDark = ref(settingsStore.isDark)
 const storeSettings = computed(() => settingsStore)
 const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"])
 
+// 初始化时设置 dark 类名
+onMounted(() => {
+  if (isDark.value) {
+    document.documentElement.classList.add('dark')
+  } else {
+    document.documentElement.classList.remove('dark')
+  }
+})
+
 // 监听主题模式变化
 watch(isDark, (val) => {
   settingsStore.isDark = val
@@ -151,6 +169,7 @@ watch(isDark, (val) => {
   }
 })
 
+
 /** 切换布局大小 */
 function handleSizeChange(val) {
   proxy.$modal.loading("正在设置布局大小,请稍候...")
@@ -181,6 +200,7 @@ function handleTheme(val) {
   sideTheme.value = val
 }
 
+
 function saveSetting() {
   proxy.$modal.loading("正在保存到本地,请稍候...")
   let layoutSetting = {
@@ -192,16 +212,24 @@ function saveSetting() {
     "dynamicTitle": storeSettings.value.dynamicTitle,
     "footerVisible": storeSettings.value.footerVisible,
     "sideTheme": storeSettings.value.sideTheme,
-    "theme": storeSettings.value.theme
+    "theme": storeSettings.value.theme,
+    "isDark": storeSettings.value.isDark,
+    "watermarkVisible": storeSettings.value.watermarkVisible,
+    "watermarkText": storeSettings.value.watermarkText
   }
   localStorage.setItem("layout-setting", JSON.stringify(layoutSetting))
-  setTimeout(proxy.$modal.closeLoading(), 1000)
+  setTimeout(() => {
+    proxy.$modal.closeLoading()
+    proxy.$modal.msgSuccess("配置保存成功!")
+  }, 1000)
 }
 
 function resetSetting() {
   proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...")
   localStorage.removeItem("layout-setting")
-  setTimeout("window.location.reload()", 1000)
+  setTimeout(() => {
+    window.location.reload()
+  }, 1000)
 }
 
 function openSetting() {
@@ -216,12 +244,12 @@ defineExpose({
 <style lang='scss' scoped>
 .setting-drawer-title {
   margin-bottom: 12px;
-  color: var(--el-text-color-primary, rgba(0, 0, 0, 0.85));
   line-height: 22px;
   font-weight: bold;
 
   .drawer-title {
     font-size: 14px;
+    margin: 0;
   }
 }
 
@@ -259,13 +287,23 @@ defineExpose({
 }
 
 .drawer-item {
-  color: var(--el-text-color-regular, rgba(0, 0, 0, 0.65));
   padding: 12px 0;
   font-size: 14px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  span:first-child {
+    flex: 1;
+  }
 
   .comp-style {
-    float: right;
-    margin: -3px 8px 0px 0px;
+    margin-left: 16px;
   }
 }
+
+.drawer-footer {
+  margin-top: 16px;
+  text-align: right;
+}
 </style>

+ 12 - 8
yushu-uivue3/src/layout/components/Sidebar/SidebarItem.vue

@@ -2,17 +2,21 @@
   <div v-if="!item.hidden">
     <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
       <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
-        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
-          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
-          <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
-        </el-menu-item>
+        <a-menu-item :key="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
+          <template #icon>
+            <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
+          </template>
+          {{ onlyOneChild.meta.title }}
+        </a-menu-item>
       </app-link>
     </template>
 
-    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
-      <template v-if="item.meta" #title>
+    <a-sub-menu v-else ref="subMenu" :key="resolvePath(item.path)">
+      <template v-if="item.meta" #icon>
         <svg-icon :icon-class="item.meta && item.meta.icon" />
-        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
+      </template>
+      <template v-if="item.meta" #title>
+        <span>{{ item.meta.title }}</span>
       </template>
 
       <sidebar-item
@@ -23,7 +27,7 @@
         :base-path="resolvePath(child.path)"
         class="nest-menu"
       />
-    </el-sub-menu>
+    </a-sub-menu>
   </div>
 </template>
 

+ 312 - 29
yushu-uivue3/src/layout/components/Sidebar/index.vue

@@ -1,33 +1,33 @@
 <template>
   <div :class="{ 'has-logo': showLogo, [sideTheme]: true }" class="sidebar-container">
     <logo v-if="showLogo" :collapse="isCollapse" />
-    <el-scrollbar wrap-class="scrollbar-wrapper">
-      <el-menu
-        :default-active="activeMenu"
-        :collapse="isCollapse"
-        :unique-opened="true"
-        :active-text-color="theme"
-        :collapse-transition="false"
-        mode="vertical"
-      >
-        <sidebar-item
-          v-for="(route, index) in sidebarRouters"
-          :key="route.path + index"
-          :item="route"
-          :base-path="route.path"
-        />
-      </el-menu>
-    </el-scrollbar>
+    <div class="sidebar-menu-wrapper" :style="{ height: showLogo ? 'calc(100vh - 50px)' : '100vh', overflow: 'auto' }">
+      <a-menu
+        :openKeys="openKeys"
+        :selectedKeys="selectedKeys"
+        :mode="'inline'"
+        :theme="sideTheme === 'theme-dark' ? 'dark' : 'light'"
+        :inline-collapsed="isCollapse"
+        :items="menuItems"
+        @click="handleMenuClick"
+        @update:openKeys="handleOpenKeysChange"
+      />
+    </div>
   </div>
 </template>
 
 <script setup>
+import { h, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
 import Logo from './Logo'
-import SidebarItem from './SidebarItem'
+import SvgIcon from '@/components/SvgIcon'
 import useAppStore from '@/store/modules/app'
 import useSettingsStore from '@/store/modules/settings'
 import usePermissionStore from '@/store/modules/permission'
+import { isExternal } from '@/utils/validate'
+import { getNormalPath } from '@/utils/yushu'
 
+const router = useRouter()
 const route = useRoute()
 const appStore = useAppStore()
 const settingsStore = useSettingsStore()
@@ -46,22 +46,305 @@ const activeMenu = computed(() => {
   }
   return path
 })
-</script>
 
-<style lang="scss" scoped>
-.sidebar-container {
-  // background-color 由CSS变量控制
+const selectedKeys = ref([activeMenu.value])
+const openKeys = ref([])
+const preOpenKeys = ref([])
+
+watch(() => activeMenu.value, (val) => {
+  selectedKeys.value = [val]
+})
+
+function handleOpenKeysChange(keys) {
+  openKeys.value = keys
+  preOpenKeys.value = keys
+}
+
+watch(() => isCollapse.value, (val) => {
+  if (val) {
+    preOpenKeys.value = openKeys.value
+    openKeys.value = []
+  } else {
+    openKeys.value = preOpenKeys.value
+  }
+})
+
+// 初始化时设置 openKeys,展开当前路由所在的父级菜单
+onMounted(() => {
+  updateOpenKeys()
+})
+
+watch(() => route.path, () => {
+  updateOpenKeys()
+})
+
+function updateOpenKeys() {
+  const path = activeMenu.value
+  const keys = []
+  
+  // 查找当前路由的所有父级菜单
+  const findParentKeys = (items, targetPath, parentKey = null) => {
+    for (const item of items) {
+      if (item.key === targetPath) {
+        if (parentKey) {
+          keys.push(parentKey)
+        }
+        return true
+      }
+      if (item.children) {
+        if (findParentKeys(item.children, targetPath, item.key)) {
+          if (parentKey) {
+            keys.push(parentKey)
+          }
+          return true
+        }
+      }
+    }
+    return false
+  }
   
-  .scrollbar-wrapper {
-    // background-color 由CSS变量控制
+  findParentKeys(menuItems.value, path)
+  if (keys.length > 0) {
+    openKeys.value = keys
+    preOpenKeys.value = keys
   }
+}
 
-  .el-menu {
-    border: none;
-    height: 100%;
-    width: 100% !important;
+// 转换路由为菜单项
+function convertRoutesToItems(routes, basePath = '') {
+  const items = []
+  
+  routes.forEach((routeItem) => {
+    if (routeItem.hidden) return
+    
+    const hasOneChild = hasOneShowingChild(routeItem.children, routeItem)
+    const onlyOneChild = getOnlyOneChild(routeItem.children, routeItem)
     
-    // 移除旧的hover样式,已在sidebar.scss中统一管理
+    if (hasOneChild && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !routeItem.alwaysShow) {
+      // 单个子菜单,显示子菜单
+      const path = resolvePath(onlyOneChild.path, onlyOneChild.query, basePath)
+      const key = path
+      
+      items.push({
+        key,
+        icon: onlyOneChild.meta?.icon ? () => h(SvgIcon, { iconClass: onlyOneChild.meta.icon }) : undefined,
+        label: onlyOneChild.meta?.title || '',
+        title: onlyOneChild.meta?.title || '',
+        // 存储路由信息用于点击时跳转
+        _route: {
+          path,
+          query: onlyOneChild.query ? JSON.parse(onlyOneChild.query) : undefined
+        }
+      })
+    } else if (routeItem.children && routeItem.children.length > 0) {
+      // 有子菜单
+      const path = resolvePath(routeItem.path, routeItem.query, basePath)
+      const key = path
+      const children = convertRoutesToItems(routeItem.children, path)
+      
+      if (children.length > 0) {
+        // 查找第一个可跳转的子路由(叶子节点)
+        const firstChildRoute = findFirstRoute(children)
+        console.log(`Parent menu ${routeItem.meta?.title} (${path}) first child route:`, firstChildRoute)
+        
+        items.push({
+          key,
+          icon: routeItem.meta?.icon ? () => h(SvgIcon, { iconClass: routeItem.meta.icon }) : undefined,
+          label: routeItem.meta?.title || '',
+          title: routeItem.meta?.title || '',
+          children,
+          // 如果有第一个子路由,总是设置跳转路由(不管 redirect 是什么)
+          // 这样点击父级菜单时总是跳转到第一个子路由
+          _route: firstChildRoute || undefined
+        })
+      }
+    } else if (routeItem.meta) {
+      // 普通菜单项
+      const path = resolvePath(routeItem.path, routeItem.query, basePath)
+      const key = path
+      
+      items.push({
+        key,
+        icon: routeItem.meta.icon ? () => h(SvgIcon, { iconClass: routeItem.meta.icon }) : undefined,
+        label: routeItem.meta.title || '',
+        title: routeItem.meta.title || '',
+        // 存储路由信息用于点击时跳转
+        _route: {
+          path,
+          query: routeItem.query ? JSON.parse(routeItem.query) : undefined
+        }
+      })
+    }
+  })
+  
+  return items
+}
+
+// 查找第一个可跳转的路由(只查找叶子节点,即没有子菜单的项)
+function findFirstRoute(items) {
+  for (const item of items) {
+    // 如果是叶子节点(没有子菜单)且有路由,直接返回
+    if (!item.children && item._route) {
+      console.log('Found first route (leaf node):', item._route, 'for item:', item.label)
+      return item._route
+    }
+    // 如果有子菜单,递归查找第一个叶子节点
+    if (item.children && item.children.length > 0) {
+      const found = findFirstRoute(item.children)
+      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
+}
+
+function hasOneShowingChild(children = [], parent) {
+  if (!children) {
+    children = []
+  }
+  const showingChildren = children.filter(item => !item.hidden)
+  
+  if (showingChildren.length === 1) {
+    return true
+  }
+  
+  if (showingChildren.length === 0) {
+    return true
+  }
+  
+  return false
+}
+
+function getOnlyChild(children = [], parent) {
+  if (!children) {
+    children = []
+  }
+  const showingChildren = children.filter(item => !item.hidden)
+  
+  if (showingChildren.length === 1) {
+    return showingChildren[0]
+  }
+  
+  if (showingChildren.length === 0) {
+    return { ...parent, path: '', noShowingChildren: true }
+  }
+  
+  return null
+}
+
+function getOnlyOneChild(children = [], parent) {
+  return getOnlyChild(children, parent)
+}
+
+function resolvePath(routePath, routeQuery, basePath) {
+  if (isExternal(routePath)) {
+    return routePath
+  }
+  if (isExternal(basePath)) {
+    return basePath
+  }
+  if (!routePath) {
+    return basePath || '/'
+  }
+  // 如果 routePath 已经是绝对路径(以 / 开头),直接返回
+  if (routePath.startsWith('/')) {
+    return getNormalPath(routePath)
+  }
+  // 否则拼接 basePath
+  if (basePath) {
+    return getNormalPath(basePath + '/' + routePath)
+  }
+  return getNormalPath('/' + routePath)
+}
+
+const menuItems = computed(() => {
+  return convertRoutesToItems(sidebarRouters.value)
+})
+
+function handleMenuClick(info) {
+  const { key, keyPath } = info
+  console.log('Menu clicked:', { key, keyPath })
+  
+  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)
+        if (found) return found
+      }
+    }
+    return null
+  }
+  
+  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
+      }).then(() => {
+        // 路由跳转成功后,通过路由路径查找对应的 key 并更新选中状态
+        const findKeyByPath = (items, targetPath) => {
+          for (const item of items) {
+            if (item._route && item._route.path === targetPath) {
+              return item.key
+            }
+            if (item.children) {
+              const found = findKeyByPath(item.children, targetPath)
+              if (found) return found
+            }
+          }
+          return null
+        }
+        const targetKey = findKeyByPath(menuItems.value, routeInfo.path)
+        if (targetKey) {
+          selectedKeys.value = [targetKey]
+        } else {
+          // 如果找不到,使用当前 key(可能是父级菜单跳转到子路由的情况)
+          selectedKeys.value = [key]
+        }
+        console.log('Navigation successful, selected key:', selectedKeys.value[0])
+      }).catch((err) => {
+        // 路由跳转失败,不做处理
+        console.error('Navigation failed:', err)
+      })
+    }
+  } else {
+    console.log('No route info found, might be parent menu with noRedirect')
+    // 如果没有找到路由信息,可能是点击了父级菜单(redirect 是 noRedirect),只更新 openKeys
+    // openKeys 会由 Ant Design Vue 自动管理
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebar-container {
+  .sidebar-menu-wrapper {
+    width: 100%;
   }
 }
 </style>

+ 33 - 20
yushu-uivue3/src/layout/components/TagsView/ScrollPane.vue

@@ -1,34 +1,39 @@
 <template>
-  <el-scrollbar
+  <div
     ref="scrollContainer"
-    :vertical="false"
     class="scroll-container"
     @wheel.prevent="handleScroll"
   >
-    <slot />
-  </el-scrollbar>
+    <div ref="scrollWrapper" class="scroll-wrapper">
+      <slot />
+    </div>
+  </div>
 </template>
 
 <script setup>
 import useTagsViewStore from '@/store/modules/tagsView'
 
 const tagAndTagSpacing = ref(4)
-const { proxy } = getCurrentInstance()
-
-const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef)
+const scrollContainer = ref(null)
+const scrollWrapper = ref(null)
 
 onMounted(() => {
-  scrollWrapper.value.addEventListener('scroll', emitScroll, true)
+  if (scrollWrapper.value) {
+    scrollWrapper.value.addEventListener('scroll', emitScroll, true)
+  }
 })
 
 onBeforeUnmount(() => {
-  scrollWrapper.value.removeEventListener('scroll', emitScroll)
+  if (scrollWrapper.value) {
+    scrollWrapper.value.removeEventListener('scroll', emitScroll)
+  }
 })
 
 function handleScroll(e) {
   const eventDelta = e.wheelDelta || -e.deltaY * 40
-  const $scrollWrapper = scrollWrapper.value
-  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
+  if (scrollWrapper.value) {
+    scrollWrapper.value.scrollLeft = scrollWrapper.value.scrollLeft + eventDelta / 4
+  }
 }
 
 const emits = defineEmits()
@@ -40,7 +45,9 @@ const tagsViewStore = useTagsViewStore()
 const visitedViews = computed(() => tagsViewStore.visitedViews)
 
 function moveToTarget(currentTag) {
-  const $container = proxy.$refs.scrollContainer.$el
+  if (!scrollContainer.value || !scrollWrapper.value) return
+  
+  const $container = scrollContainer.value
   const $containerWidth = $container.offsetWidth
   const $scrollWrapper = scrollWrapper.value
 
@@ -64,20 +71,20 @@ function moveToTarget(currentTag) {
     let nextTag = null
     for (const k in tagListDom) {
       if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
-        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
+        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1]?.path) {
           prevTag = tagListDom[k]
         }
-        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
+        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1]?.path) {
           nextTag = tagListDom[k]
         }
       }
     }
 
     // the tag's offsetLeft after of nextTag
-    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
+    const afterNextTagOffsetLeft = nextTag ? nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value : 0
 
     // the tag's offsetLeft before of prevTag
-    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
+    const beforePrevTagOffsetLeft = prevTag ? prevTag.offsetLeft - tagAndTagSpacing.value : 0
     if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
       $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
     } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
@@ -97,11 +104,17 @@ defineExpose({
   position: relative;
   overflow: hidden;
   width: 100%;
-  :deep(.el-scrollbar__bar) {
-    bottom: 0px;
-  }
-  :deep(.el-scrollbar__wrap) {
+  
+  .scroll-wrapper {
     height: 39px;
+    overflow-x: auto;
+    overflow-y: hidden;
+    white-space: nowrap;
+    
+    &::-webkit-scrollbar {
+      height: 0;
+      display: none;
+    }
   }
 }
 </style>

+ 15 - 18
yushu-uivue3/src/layout/components/TagsView/index.vue

@@ -15,28 +15,28 @@
         <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
         {{ tag.title }}
         <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
-          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
+          <CloseOutlined style="width: 1em; height: 1em;vertical-align: middle;" />
         </span>
       </router-link>
     </scroll-pane>
     <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
       <li @click="refreshSelectedTag(selectedTag)">
-        <refresh-right style="width: 1em; height: 1em;" /> 刷新页面
+        <ReloadOutlined style="width: 1em; height: 1em;" /> 刷新页面
       </li>
       <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
-        <close style="width: 1em; height: 1em;" /> 关闭当前
+        <CloseOutlined style="width: 1em; height: 1em;" /> 关闭当前
       </li>
       <li @click="closeOthersTags">
-        <circle-close style="width: 1em; height: 1em;" /> 关闭其他
+        <CloseCircleOutlined style="width: 1em; height: 1em;" /> 关闭其他
       </li>
       <li v-if="!isFirstView()" @click="closeLeftTags">
-        <back style="width: 1em; height: 1em;" /> 关闭左侧
+        <LeftOutlined style="width: 1em; height: 1em;" /> 关闭左侧
       </li>
       <li v-if="!isLastView()" @click="closeRightTags">
-        <right style="width: 1em; height: 1em;" /> 关闭右侧
+        <RightOutlined style="width: 1em; height: 1em;" /> 关闭右侧
       </li>
       <li @click="closeAllTags(selectedTag)">
-        <circle-close style="width: 1em; height: 1em;" /> 全部关闭
+        <CloseCircleOutlined style="width: 1em; height: 1em;" /> 全部关闭
       </li>
     </ul>
   </div>
@@ -48,6 +48,7 @@ import { getNormalPath } from '@/utils/yushu'
 import useTagsViewStore from '@/store/modules/tagsView'
 import useSettingsStore from '@/store/modules/settings'
 import usePermissionStore from '@/store/modules/permission'
+import { CloseOutlined, ReloadOutlined, CloseCircleOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
 
 const visible = ref(false)
 const top = ref(0)
@@ -341,29 +342,25 @@ function handleScroll() {
 </style>
 
 <style lang="scss">
-//reset element css of el-icon-close
+// TagsView close icon styles
 .tags-view-wrapper {
   .tags-view-item {
-    .el-icon-close {
+    span {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
       width: 16px;
       height: 16px;
       vertical-align: 2px;
       border-radius: 50%;
       text-align: center;
       transition: all .3s cubic-bezier(.645, .045, .355, 1);
-      transform-origin: 100% 50%;
-
-      &:before {
-        transform: scale(.6);
-        display: inline-block;
-        vertical-align: -3px;
-      }
+      margin-left: 4px;
+      cursor: pointer;
 
       &:hover {
         background-color: var(--tags-close-hover, #b4bccc);
         color: #fff;
-        width: 12px !important;
-        height: 12px !important;
       }
     }
   }

+ 16 - 1
yushu-uivue3/src/settings.js

@@ -7,7 +7,7 @@ export default {
   /**
    * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
    */
-  sideTheme: 'theme-dark',
+  sideTheme: 'theme-light',
 
   /**
    * 是否系统布局配置
@@ -49,6 +49,21 @@ export default {
    */
   footerVisible: false,
 
+  /**
+   * 是否暗黑模式(false为亮色模式,true为暗黑模式)
+   */
+  isDark: false,
+
+  /**
+   * 是否显示水印
+   */
+  watermarkVisible: false,
+
+  /**
+   * 水印文本
+   */
+  watermarkText: '予书管理系统',
+
   /**
    * 底部版权文本内容
    */

+ 4 - 2
yushu-uivue3/src/store/modules/settings.js

@@ -5,7 +5,7 @@ import { useDynamicTitle } from '@/utils/dynamicTitle'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 
-const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
+const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent, isDark: defaultIsDark, watermarkVisible, watermarkText } = defaultSettings
 
 const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
 
@@ -25,7 +25,9 @@ const useSettingsStore = defineStore(
       dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
       footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible,
       footerContent: footerContent,
-      isDark: isDark.value
+      isDark: storageSetting.isDark !== undefined ? storageSetting.isDark : defaultIsDark,
+      watermarkVisible: storageSetting.watermarkVisible !== undefined ? storageSetting.watermarkVisible : watermarkVisible,
+      watermarkText: storageSetting.watermarkText || watermarkText
     }),
     actions: {
       // 修改布局设置

+ 63 - 56
yushu-uivue3/src/views/index.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="dashboard-container">
     <!-- 顶部统计卡片 -->
-    <el-row :gutter="20">
-      <el-col :xs="24" :sm="12" :md="6" v-for="(item, index) in statCards" :key="index">
-        <el-card shadow="hover" class="stat-card-item">
+    <a-row :gutter="20">
+      <a-col :xs="24" :sm="12" :md="6" v-for="(item, index) in statCards" :key="index">
+        <a-card hoverable class="stat-card-item">
           <div class="stat-card-content">
             <div class="stat-icon-wrapper" :style="{ background: item.color }">
-              <el-icon :size="24" color="#fff"><component :is="item.icon" /></el-icon>
+              <component :is="item.icon" :style="{ fontSize: '24px', color: '#fff' }" />
             </div>
             <div class="stat-info">
               <div class="stat-label">{{ item.label }}</div>
@@ -19,34 +19,34 @@
             <span>较昨日</span>
             <span :class="item.trend >= 0 ? 'trend-up' : 'trend-down'">
               {{ Math.abs(item.trend) }}%
-              <el-icon><component :is="item.trend >= 0 ? 'CaretTop' : 'CaretBottom'" /></el-icon>
+              <component :is="item.trend >= 0 ? CaretUpOutlined : CaretDownOutlined" />
             </span>
           </div>
-        </el-card>
-      </el-col>
-    </el-row>
+        </a-card>
+      </a-col>
+    </a-row>
 
     <!-- 中部图表和快捷入口 -->
-    <el-row :gutter="20" class="mt-20">
-      <el-col :xs="24" :lg="16">
-        <el-card shadow="hover" class="chart-card">
-          <template #header>
+    <a-row :gutter="20" class="mt-20">
+      <a-col :xs="24" :lg="16">
+        <a-card hoverable class="chart-card">
+          <template #title>
             <div class="card-header">
               <span>访问趋势</span>
-              <el-radio-group v-model="chartPeriod" size="small">
-                <el-radio-button label="week">本周</el-radio-button>
-                <el-radio-button label="month">本月</el-radio-button>
-              </el-radio-group>
+              <a-radio-group v-model:value="chartPeriod" size="small">
+                <a-radio-button value="week">本周</a-radio-button>
+                <a-radio-button value="month">本月</a-radio-button>
+              </a-radio-group>
             </div>
           </template>
           <div class="chart-wrapper">
              <line-chart :chart-data="lineChartData" />
           </div>
-        </el-card>
-      </el-col>
-      <el-col :xs="24" :lg="8">
-        <el-card shadow="hover" class="quick-entry-card">
-          <template #header>
+        </a-card>
+      </a-col>
+      <a-col :xs="24" :lg="8">
+        <a-card hoverable class="quick-entry-card">
+          <template #title>
             <div class="card-header">
               <span>快捷导航</span>
             </div>
@@ -59,18 +59,18 @@
               @click="goRoute(item.path)"
             >
               <div class="nav-icon" :style="{ background: item.bg }">
-                <el-icon :size="20" :color="item.color"><component :is="item.icon" /></el-icon>
+                <component :is="item.icon" :style="{ fontSize: '20px', color: item.color }" />
               </div>
               <span class="nav-label">{{ item.name }}</span>
             </div>
           </div>
-        </el-card>
+        </a-card>
         
-        <el-card shadow="hover" class="mt-20 system-info-card">
-          <template #header>
+        <a-card hoverable class="mt-20 system-info-card">
+          <template #title>
             <div class="card-header">
               <span>系统概览</span>
-              <el-tag size="small" effect="plain">v3.9.0</el-tag>
+              <a-tag size="small">v3.9.0</a-tag>
             </div>
           </template>
           <div class="info-list">
@@ -79,8 +79,8 @@
               <span class="value">3.x</span>
             </div>
             <div class="info-item">
-              <span class="label">Element Plus</span>
-              <span class="value">2.x</span>
+              <span class="label">Ant Design Vue</span>
+              <span class="value">4.x</span>
             </div>
             <div class="info-item">
               <span class="label">Vite</span>
@@ -91,30 +91,36 @@
               <span class="value">{{ env }}</span>
             </div>
           </div>
-        </el-card>
-      </el-col>
-    </el-row>
+        </a-card>
+      </a-col>
+    </a-row>
 
     <!-- 底部项目介绍 -->
-    <el-row :gutter="20" class="mt-20">
-      <el-col :span="24">
-        <el-card shadow="hover" class="project-card">
+    <a-row :gutter="20" class="mt-20">
+      <a-col :span="24">
+        <a-card hoverable class="project-card">
           <div class="project-intro">
             <div class="project-logo">
               <img src="@/assets/logo/logo.png" alt="logo" />
             </div>
             <div class="project-desc">
               <h3>予书后台管理系统</h3>
-              <p>一款基于 Vue3 + Vite + Element Plus 的现代化后台管理系统。提供丰富的组件和功能,开箱即用,助力开发者快速构建高质量的 Web 应用。</p>
+              <p>一款基于 Vue3 + Vite + Ant Design Vue 的现代化后台管理系统。提供丰富的组件和功能,开箱即用,助力开发者快速构建高质量的 Web 应用。</p>
               <div class="project-actions">
-                <el-button type="primary" icon="Promotion" @click="goTarget('http://yushu.vip')">访问官网</el-button>
-                <el-button icon="Star" @click="goTarget('https://gitee.com/y_project/yushu-Vue')">Gitee Star</el-button>
+                <a-button type="primary" @click="goTarget('http://yushu.vip')">
+                  <template #icon><ShareAltOutlined /></template>
+                  访问官网
+                </a-button>
+                <a-button @click="goTarget('https://gitee.com/y_project/yushu-Vue')">
+                  <template #icon><StarOutlined /></template>
+                  Gitee Star
+                </a-button>
               </div>
             </div>
           </div>
-        </el-card>
-      </el-col>
-    </el-row>
+        </a-card>
+      </a-col>
+    </a-row>
   </div>
 </template>
 
@@ -122,9 +128,10 @@
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { 
-  User, ChatDotRound, Folder, Monitor, Setting, Document, Bell, ChatLineRound,
-  CaretTop, CaretBottom, Promotion, Star, DataLine, Goods, Reading
-} from '@element-plus/icons-vue'
+  UserOutlined, MessageOutlined, FolderOutlined, MonitorOutlined, SettingOutlined, 
+  FileTextOutlined, BellOutlined, CommentOutlined, CaretUpOutlined, CaretDownOutlined, 
+  ShareAltOutlined, StarOutlined, LineChartOutlined, ShoppingOutlined, ReadOutlined
+} from '@ant-design/icons-vue'
 import LineChart from './dashboard/LineChart.vue'
 import { getDashboardStats, getVisitsTrend } from '@/api/system/statistics'
 
@@ -134,10 +141,10 @@ const loading = ref(false)
 
 // 统计数据
 const statCards = ref([
-  { label: '用户总数', value: 0, icon: 'User', color: '#409eff', trend: 0 },
-  { label: '消息数量', value: 0, icon: 'ChatDotRound', color: '#67c23a', trend: 0 },
-  { label: '文件数量', value: 0, icon: 'Folder', color: '#e6a23c', trend: 0 },
-  { label: '今日访问', value: 0, icon: 'Monitor', color: '#f56c6c', trend: 0 }
+  { label: '用户总数', value: 0, icon: UserOutlined, color: '#409eff', trend: 0 },
+  { label: '消息数量', value: 0, icon: MessageOutlined, color: '#67c23a', trend: 0 },
+  { label: '文件数量', value: 0, icon: FolderOutlined, color: '#e6a23c', trend: 0 },
+  { label: '今日访问', value: 0, icon: MonitorOutlined, color: '#f56c6c', trend: 0 }
 ])
 
 // 图表数据
@@ -149,14 +156,14 @@ const lineChartData = ref({
 
 // 快捷入口
 const quickLinks = ref([
-  { name: '用户管理', path: '/system/user', icon: 'User', color: '#409eff', bg: '#ecf5ff' },
-  { name: '角色管理', path: '/system/role', icon: 'UserFilled', color: '#67c23a', bg: '#f0f9eb' },
-  { name: '菜单管理', path: '/system/menu', icon: 'Menu', color: '#e6a23c', bg: '#fdf6ec' },
-  { name: '文件管理', path: '/file', icon: 'Folder', color: '#f56c6c', bg: '#fef0f0' },
-  { name: '消息中心', path: '/message', icon: 'ChatDotRound', color: '#409eff', bg: '#ecf5ff' },
-  { name: '通知公告', path: '/system/notice', icon: 'Bell', color: '#e6a23c', bg: '#fdf6ec' },
-  { name: '操作日志', path: '/system/log/operlog', icon: 'Document', color: '#909399', bg: '#f4f4f5' },
-  { name: 'AI 对话', path: '/ai/chat', icon: 'ChatLineRound', color: '#764ba2', bg: '#f8f5fa' }
+  { name: '用户管理', path: '/system/user', icon: UserOutlined, color: '#409eff', bg: '#ecf5ff' },
+  { name: '角色管理', path: '/system/role', icon: UserOutlined, color: '#67c23a', bg: '#f0f9eb' },
+  { name: '菜单管理', path: '/system/menu', icon: SettingOutlined, color: '#e6a23c', bg: '#fdf6ec' },
+  { name: '文件管理', path: '/file', icon: FolderOutlined, color: '#f56c6c', bg: '#fef0f0' },
+  { name: '消息中心', path: '/message', icon: MessageOutlined, color: '#409eff', bg: '#ecf5ff' },
+  { name: '通知公告', path: '/system/notice', icon: BellOutlined, color: '#e6a23c', bg: '#fdf6ec' },
+  { name: '操作日志', path: '/system/log/operlog', icon: FileTextOutlined, color: '#909399', bg: '#f4f4f5' },
+  { name: 'AI 对话', path: '/ai/chat', icon: CommentOutlined, color: '#764ba2', bg: '#f8f5fa' }
 ])
 
 /** 加载统计数据 */
@@ -236,7 +243,7 @@ onMounted(() => {
   margin-bottom: 20px;
   border: none;
   
-  :deep(.el-card__body) {
+  :deep(.ant-card-body) {
     padding: 20px;
   }