Quellcode durchsuchen

提交:优化 Fresh Mint 导航与标签页视觉一致性

统一侧边栏与顶部区域的 Fresh Mint 风格,并修复工作台标签页间距及激活底线显示问题,确保与设计稿交互表现一致。

Made-with: Cursor
zlwu vor 6 Monaten
Ursprung
Commit
5060f7432f

+ 0 - 2
web-admin/src/assets/styles/core/app.scss

@@ -524,7 +524,6 @@ body {
 
 #app-header > div {
   background: var(--mint-surface) !important;
-  border-bottom: 1px solid var(--mint-border);
 }
 
 #app-header .relative.box-border.flex-b {
@@ -540,7 +539,6 @@ body {
 #app-header .flex-b.w-full {
   margin-bottom: 0 !important;
   padding: 0 24px 10px !important;
-  border-bottom: 1px solid var(--mint-border);
 }
 
 #app-sidebar .layout-sidebar {

+ 1 - 62
web-admin/src/components/core/layouts/art-header-bar/index.vue

@@ -61,23 +61,6 @@
       </div>
 
       <div class="flex-c gap-2.5 leading-none">
-        <!-- 搜索 -->
-        <div
-          v-if="shouldShowGlobalSearch"
-          class="flex-cb w-40 h-9 px-2.5 c-p border border-g-400 rounded-custom-sm max-md:!hidden"
-          @click="openSearchDialog"
-        >
-          <div class="flex-c">
-            <ArtSvgIcon icon="ri:search-line" class="text-sm text-g-500" />
-            <span class="ml-1 text-xs font-normal text-g-500">{{ $t('topBar.search.title') }}</span>
-          </div>
-          <div class="flex-c h-5 px-1.5 text-g-500/80 border border-g-400 rounded">
-            <ArtSvgIcon v-if="isWindows" icon="vaadin:ctrl-a" class="text-sm" />
-            <ArtSvgIcon v-else icon="ri:command-fill" class="text-xs" />
-            <span class="ml-0.5 text-xs">k</span>
-          </div>
-        </div>
-
         <!-- 全屏按钮 -->
         <ArtIconButton
           v-if="shouldShowFullscreen"
@@ -129,25 +112,6 @@
           <div class="breathing-dot absolute top-2 right-2 size-1.5 !bg-success rounded-full"></div>
         </ArtIconButton> -->
 
-        <!-- 设置按钮 -->
-        <div v-if="shouldShowSettings">
-          <ElPopover :visible="showSettingGuide" placement="bottom-start" :width="190" :offset="0">
-            <template #reference>
-              <div class="flex-cc">
-                <ArtIconButton icon="ri:settings-line" class="setting-btn" @click="openSetting" />
-              </div>
-            </template>
-            <template #default>
-              <p
-                >{{ $t('topBar.guide.title')
-                }}<span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.theme') }} </span
-                >、 <span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.menu') }} </span
-                >{{ $t('topBar.guide.description') }}
-              </p>
-            </template>
-          </ElPopover>
-        </div>
-
         <!-- 主题切换按钮 - 已隐藏 -->
         <!-- <ArtIconButton
           v-if="shouldShowThemeToggle"
@@ -187,9 +151,6 @@
 
   defineOptions({ name: 'ArtHeaderBar' })
 
-  // 检测操作系统类型
-  const isWindows = navigator.userAgent.includes('Windows')
-
   const router = useRouter()
   const { locale } = useI18n()
   const { width } = useWindowSize()
@@ -204,18 +165,15 @@
     shouldShowRefreshButton,
     shouldShowFastEnter,
     shouldShowBreadcrumb,
-    shouldShowGlobalSearch,
     shouldShowFullscreen,
     shouldShowNotification,
     shouldShowChat,
     shouldShowLanguage,
-    shouldShowSettings,
     shouldShowThemeToggle,
     fastEnterMinWidth: headerBarFastEnterMinWidth
   } = useHeaderBar()
 
-  const { menuOpen, systemThemeColor, showSettingGuide, menuType, isDark, tabStyle } =
-    storeToRefs(settingStore)
+  const { menuOpen, menuType, isDark, tabStyle } = storeToRefs(settingStore)
 
   const { language } = storeToRefs(userStore)
   const { menuList } = storeToRefs(menuStore)
@@ -292,25 +250,6 @@
     reload(50)
   }
 
-  /**
-   * 打开设置面板
-   */
-  const openSetting = (): void => {
-    mittBus.emit('openSetting')
-
-    // 隐藏设置引导提示
-    if (showSettingGuide.value) {
-      settingStore.hideSettingGuide()
-    }
-  }
-
-  /**
-   * 打开全局搜索对话框
-   */
-  const openSearchDialog = (): void => {
-    mittBus.emit('openSearchDialog')
-  }
-
   /**
    * 点击页面其他区域关闭通知面板
    * @param {Event} e - 点击事件对象

+ 33 - 4
web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue

@@ -80,10 +80,16 @@
           :class="{ 'is-dual-menu-name': isDualMenu }"
           :style="{
             color: getMenuTheme.systemNameColor,
-            opacity: !menuOpen ? 0 : 1
+            opacity: 1
           }"
         >
-          {{ AppConfig.systemInfo.name }}
+          <template v-if="AppConfig.systemInfo.name === '叁强鲜配'">
+            <span class="system-name-main">叁强</span>
+            <span class="system-name-accent">鲜配</span>
+          </template>
+          <template v-else>
+            {{ AppConfig.systemInfo.name }}
+          </template>
         </p>
       </div>
       <ElScrollbar :style="scrollbarStyle">
@@ -92,7 +98,7 @@
           :collapse="!menuOpen"
           :default-active="routerPath"
           :text-color="getMenuTheme.textColor"
-          :unique-opened="uniqueOpened"
+          :unique-opened="false"
           :background-color="getMenuTheme.background"
           :default-openeds="defaultOpenedMenus"
           :popper-class="`menu-left-popper menu-left-${getMenuTheme.theme}-popper`"
@@ -150,7 +156,7 @@
   const router = useRouter()
   const settingStore = useSettingStore()
 
-  const { getMenuOpenWidth, menuType, uniqueOpened, dualMenuShowText, menuOpen, getMenuTheme } =
+  const { getMenuOpenWidth, menuType, dualMenuShowText, menuOpen, getMenuTheme } =
     storeToRefs(settingStore)
 
   // 组件内部状态
@@ -209,6 +215,21 @@
     return currentMenu?.children ?? []
   })
 
+  /**
+   * 递归收集所有可展开菜单 index,保证左侧菜单默认全展开(桌面端)
+   */
+  const collectOpenMenuIndexes = (items: any[]): string[] => {
+    const openIndexes: string[] = []
+    items.forEach((item) => {
+      if (item?.children && item.children.length > 0 && !item?.meta?.isHide) {
+        const idx = String(item.path || item.meta?.title || '')
+        if (idx) openIndexes.push(idx)
+        openIndexes.push(...collectOpenMenuIndexes(item.children))
+      }
+    })
+    return openIndexes
+  }
+
   // 双列菜单收起时的滚动条样式
   const scrollbarStyle = computed(() => {
     const isCollapsed = isDualMenu.value && !menuOpen.value
@@ -332,6 +353,14 @@
       }
     }
   })
+
+  watch(
+    menuList,
+    (list) => {
+      defaultOpenedMenus.value = collectOpenMenuIndexes(list || [])
+    },
+    { immediate: true, deep: true }
+  )
 </script>
 
 <style lang="scss" scoped>

+ 38 - 5
web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss

@@ -3,7 +3,8 @@
   height: 100vh;
   user-select: none;
   scrollbar-width: none;
-  border-right: 1px solid var(--art-card-border);
+  border-right: none;
+  background: var(--mint-surface, #fff);
 
   &.no-border {
     border-right: none !important;
@@ -90,6 +91,7 @@
     position: relative;
     box-sizing: border-box;
     height: 100vh;
+    background: var(--mint-surface, #fff);
 
     @media only screen and (width <= 640px) {
       height: 100dvh;
@@ -143,13 +145,16 @@
     display: flex;
     align-items: center;
     width: 100%;
-    height: 60px;
+    height: 64px;
     overflow: hidden;
-    line-height: 60px;
+    line-height: 64px;
     cursor: pointer;
+    position: relative;
+    background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+    border-bottom: none !important;
 
     .logo {
-      margin-left: 22px;
+      margin-left: 18px;
     }
 
     p {
@@ -159,13 +164,39 @@
       left: 58px;
       box-sizing: border-box;
       margin-left: 10px;
-      font-size: 18px;
+      font-size: 16px;
+      font-weight: 700;
+      color: #111827 !important;
 
       &.is-dual-menu-name {
         left: 25px;
         margin: auto;
       }
+
+      .system-name-main {
+        color: #111827;
+      }
+
+      .system-name-accent {
+        color: #059669;
+      }
     }
+
+    &::after {
+      position: absolute;
+      right: 16px;
+      bottom: 0;
+      left: 16px;
+      height: 1px;
+      content: '';
+      background: rgba(15, 23, 42, 0.18);
+    }
+  }
+
+  // 覆盖全局 header 规则,避免出现第二条分割线
+  .menu-left > .header {
+    border-bottom: none !important;
+    box-shadow: none !important;
   }
 
   .el-menu {
@@ -175,6 +206,8 @@
     border-right: 0;
     scrollbar-width: none;
     -ms-scroll-chaining: contain;
+    padding: 12px 8px;
+    background: transparent;
 
     &::-webkit-scrollbar {
       width: 0 !important;

+ 45 - 22
web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss

@@ -1,10 +1,10 @@
 @use '@styles/core/mixin.scss' as *;
 
 // 菜单样式变量
-$menu-height: 42px;
+$menu-height: 40px;
 $menu-icon-size: 20px;
-$menu-font-size: 14px;
-$hover-bg-color: var(--art-gray-200);
+$menu-font-size: 13px;
+$hover-bg-color: var(--mint-row-alt, #f8fafc);
 $popup-menu-height: 40px;
 $popup-menu-padding: 8px;
 $popup-menu-margin: 5px;
@@ -12,9 +12,9 @@ $popup-menu-radius: 6px;
 
 // 通用菜单项样式
 @mixin menu-item-base {
-  width: calc(100% - 16px);
-  margin-left: 8px;
-  border-radius: 6px;
+  width: calc(100% - 12px);
+  margin-left: 6px;
+  border-radius: 8px;
 
   .menu-icon {
     margin-left: -7px;
@@ -34,6 +34,7 @@ $popup-menu-radius: 6px;
   .el-menu-item.is-active {
     color: $color !important;
     background-color: $bg-color;
+    font-weight: 600;
 
     .menu-icon {
       .art-svg-icon {
@@ -121,22 +122,41 @@ $popup-menu-radius: 6px;
   .el-sub-menu__title,
   .el-menu-item {
     height: $menu-height !important;
-    margin-bottom: 4px;
+    margin-bottom: 2px;
     line-height: $menu-height !important;
 
     span {
       font-size: $menu-font-size !important;
+      font-weight: 500;
+      color: var(--mint-text, #1e293b);
+      text-rendering: geometricprecision;
+      -webkit-font-smoothing: antialiased;
+      -moz-osx-font-smoothing: grayscale;
 
       @include ellipsis();
     }
   }
 
+  .el-sub-menu > .el-sub-menu__title {
+    font-size: 12px !important;
+    font-weight: 700;
+    color: var(--mint-text-muted, #64748b) !important;
+    letter-spacing: 0.02em;
+  }
+
   // 右侧箭头
   .el-sub-menu__icon-arrow {
     width: 13px !important;
     font-size: 13px !important;
   }
 
+  // 子菜单区域左侧绿色竖线(对齐 fresh-mint 导航层级感)
+  .el-sub-menu .el-menu {
+    margin: 2px 0 6px 10px;
+    padding: 2px 0 2px 8px !important;
+    border-left: 2px solid var(--mint-border, #d9e8e2);
+  }
+
   // 菜单折叠
   .el-menu--collapse {
     .el-sub-menu.is-active {
@@ -154,11 +174,24 @@ $popup-menu-radius: 6px;
   // ---------------------- Design theme menu ----------------------
   .el-menu-design {
     @include theme-menu-base;
-    @include menu-active(var(--theme-color), var(--el-color-primary-light-9));
+    @include menu-active(
+      var(--mint-primary-dark, #059669),
+      var(--mint-primary-bg, #ecfdf5),
+      var(--mint-primary-dark, #059669)
+    );
     @include menu-hover($hover-bg-color);
 
+    .el-menu-item,
+    .el-sub-menu__title {
+      color: var(--mint-text, #1e293b) !important;
+    }
+
+    .el-menu-item.is-active {
+      color: var(--mint-primary-dark, #059669) !important;
+    }
+
     .el-sub-menu__icon-arrow {
-      color: var(--art-gray-600);
+      color: var(--mint-text-muted, #64748b);
     }
   }
 
@@ -183,27 +216,17 @@ $popup-menu-radius: 6px;
     }
 
     .el-menu-item.is-active {
-      background-color: var(--el-color-primary-light-9);
+      background-color: var(--mint-primary-bg, #ecfdf5);
 
       .art-svg-icon {
-        color: var(--theme-color) !important;
-      }
-
-      &::before {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 4px;
-        height: 100%;
-        content: '';
-        background: var(--theme-color);
+        color: var(--mint-primary-dark, #059669) !important;
       }
     }
 
     @include menu-hover($hover-bg-color);
 
     .el-sub-menu__icon-arrow {
-      color: var(--art-gray-600);
+      color: var(--mint-text-muted, #64748b);
     }
   }
 }

+ 65 - 99
web-admin/src/components/core/layouts/art-work-tab/index.vue

@@ -2,13 +2,9 @@
 <template>
   <div
     v-if="showWorkTab"
-    class="box-border flex-b w-full px-5 mb-3 select-none max-sm:px-[15px]"
-    :class="[
-      tabStyle === 'tab-card' ? 'py-1 border-b border-[var(--art-card-border)]' : '',
-      tabStyle === 'tab-google' ? 'pt-1 pb-0 border-b border-[var(--art-card-border)]' : ''
-    ]"
+    class="worktab-strip box-border flex-b w-full px-5 mb-3 select-none max-sm:px-[15px]"
   >
-    <div class="w-full overflow-hidden" ref="scrollRef">
+    <div class="worktab-scroll-wrap w-full" ref="scrollRef">
       <ul
         class="float-left whitespace-nowrap !bg-transparent flex"
         :class="[tabStyle === 'tab-google' ? 'pl-1' : '']"
@@ -19,24 +15,14 @@
         }"
       >
         <li
-          class="art-card-xs inline-flex flex-cc h-8 mr-1.5 text-xs c-p hover:text-theme group"
-          :class="[
-            item.path === activeTab ? 'activ-tab !text-theme' : '',
-            tabStyle === 'tab-google' ? 'google-tab relative !h-8 !leading-8 !border-none' : ''
-          ]"
-          :style="{
-            padding: item.fixedTab ? '0 10px' : '0 8px 0 12px',
-            borderRadius:
-              tabStyle === 'tab-google'
-                ? 'calc(var(--custom-radius) / 2.5 + 4px) !important'
-                : 'calc(var(--custom-radius) / 2.5 + 2px) !important'
-          }"
+          class="worktab-chip inline-flex flex-cc mr-1.5 text-xs c-p group"
+          :class="[item.path === activeTab ? 'is-active' : '', item.fixedTab ? 'is-fixed' : '']"
           v-for="(item, index) in list"
           :key="item.path"
           :ref="item.path"
           :id="`scroll-li-${index}`"
           @click="clickTab(item)"
-          @contextmenu.prevent="(e: MouseEvent) => showMenu(e, item.path)"
+          @contextmenu.prevent="(e) => showMenu(e, item.path)"
         >
           <ArtSvgIcon
             v-show="item.icon"
@@ -47,27 +33,19 @@
           {{ item.customTitle || formatMenuTitle(item.title) }}
           <span
             v-if="list.length > 1 && !item.fixedTab"
-            class="inline-flex flex-cc relative ml-0.5 p-1 rounded-full tad-200 hover:bg-g-200"
+            class="worktab-close inline-flex flex-cc relative ml-0.5 p-1 rounded-full tad-200"
             @click.stop="closeWorktab('current', item.path)"
           >
             <ArtSvgIcon icon="ri:close-large-fill" class="text-[10px] text-inherit" />
           </span>
-          <div
-            v-if="tabStyle === 'tab-google'"
-            class="line absolute top-0 bottom-0 left-0 w-px h-4 my-auto bg-g-400 transition-opacity duration-150"
-          />
         </li>
       </ul>
     </div>
 
     <div class="flex">
       <div
-        class="flex-cc art-card-xs relative top-0 size-8 leading-8 text-center c-p tad-200 hover:!bg-hover-color"
-        :style="{
-          borderRadius: 'calc(var(--custom-radius) / 2.5 + 0px)',
-          marginTop: tabStyle === 'tab-google' ? '-2px' : ''
-        }"
-        @click="(e: MouseEvent) => showMenu(e, activeTab)"
+        class="worktab-menu-btn flex-cc relative top-0 size-8 leading-8 text-center c-p tad-200"
+        @click="(e) => showMenu(e, activeTab)"
       >
         <ArtSvgIcon icon="iconamoon:arrow-down-2-thin" class="text-2xl text-g-700" />
       </div>
@@ -480,103 +458,91 @@
 </script>
 
 <style scoped>
-  .google-tab.activ-tab {
-    color: var(--theme-color) !important;
-    background-color: var(--el-color-primary-light-9) !important;
-    border-bottom: 0 !important;
-    border-bottom-right-radius: 0 !important;
-    border-bottom-left-radius: 0 !important;
-  }
-
-  .google-tab.activ-tab::before,
-  .google-tab.activ-tab::after {
-    position: absolute;
-    bottom: 0;
-    width: 20px;
-    height: 20px;
-    content: '';
-    border-radius: 50%;
-    box-shadow: 0 0 0 30px var(--el-color-primary-light-9);
+  .worktab-strip {
+    position: relative;
+    padding: 0 24px 12px !important;
+    background: var(--mint-surface);
+    overflow: visible !important;
   }
 
-  .google-tab.activ-tab::before {
-    left: -20px;
-    clip-path: inset(50% -10px 0 50%);
+  .worktab-scroll-wrap {
+    overflow-x: clip;
+    overflow-y: visible;
   }
 
-  .google-tab.activ-tab::after {
-    right: -20px;
-    clip-path: inset(50% 50% 0 -10px);
+  .worktab-chip {
+    position: relative;
+    z-index: 1;
+    max-width: 220px;
+    padding: 10px 16px !important;
+    margin-bottom: -11px;
+    overflow: hidden;
+    font-size: 13px;
+    font-weight: 600;
+    color: var(--mint-text-muted);
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    background: var(--mint-surface);
+    border: 1px solid var(--mint-border);
+    border-radius: 8px 8px 0 0 !important;
+    transform: translateY(1px);
   }
 
-  .dark .google-tab.activ-tab {
-    color: var(--art-gray-800) !important;
-    background-color: var(--art-hover-color) !important;
+  .worktab-chip.is-fixed {
+    padding-right: 12px !important;
+    padding-left: 12px !important;
   }
 
-  .dark .google-tab.activ-tab::before,
-  .dark .google-tab.activ-tab::after {
-    box-shadow: 0 0 0 30px var(--art-hover-color);
+  .worktab-chip:hover {
+    color: var(--mint-text);
+    background: var(--mint-row-alt);
   }
 
-  .google-tab:not(.activ-tab):hover {
-    box-sizing: border-box;
-    color: var(--art-gray-600) !important;
-    background-color: var(--art-gray-200) !important;
-    border-bottom: 1px solid var(--default-box-color) !important;
-    border-radius: calc(var(--custom-radius) / 2.5 + 4px) !important;
+  .worktab-chip.is-active {
+    z-index: 2;
+    color: var(--mint-primary-dark);
+    border-bottom-color: var(--mint-surface);
+    box-shadow: none;
   }
 
-  .dark .google-tab:not(.activ-tab):hover {
-    background-color: var(--art-hover-color) !important;
-  }
-
-  .google-tab:hover .line,
-  .google-tab.activ-tab .line,
-  .google-tab:first-child .line {
-    opacity: 0;
-  }
-
-  .google-tab:hover + .google-tab .line,
-  .google-tab.activ-tab + .google-tab .line {
-    opacity: 0;
-  }
-
-  .google-tab::before,
-  .google-tab::after {
-    position: absolute;
-    bottom: 0;
-    width: 20px;
-    height: 20px;
+  .worktab-chip.is-active::after {
     content: '';
-    border-radius: 50%;
-    box-shadow: 0 0 0 30px transparent;
+    position: absolute;
+    z-index: 3;
+    right: 0;
+    bottom: 1px;
+    left: 0;
+    height: 2px;
+    background: var(--mint-primary);
+    pointer-events: none;
   }
 
-  .google-tab::before {
-    left: -20px;
-    clip-path: inset(50% -10px 0 50%);
+  .worktab-close:hover {
+    background: var(--mint-row-alt);
   }
 
-  .google-tab::after {
-    right: -20px;
-    clip-path: inset(50% 50% 0 -10px);
+  .worktab-menu-btn {
+    margin-left: 6px;
+    color: var(--mint-text-muted);
+    border: 1px solid var(--mint-border);
+    border-radius: 8px;
+    background: var(--mint-surface);
   }
 
-  .google-tab i:hover {
-    color: var(--art-gray-700);
-    background: var(--art-gray-300);
+  .worktab-menu-btn:hover {
+    color: var(--mint-text);
+    background: var(--mint-row-alt);
   }
 
   @media only screen and (width <= 768px) {
-    .box-border.flex.justify-between {
+    .worktab-strip {
       padding-right: 0.625rem;
       padding-left: 0.625rem;
     }
   }
 
   @media only screen and (width <= 640px) {
-    .box-border.flex.justify-between {
+    .worktab-strip {
       padding-right: 0.9375rem;
       padding-left: 0.9375rem;
     }