From e38c0656952f70552170fc4ee72420b0c741919e Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 18 六月 2026 10:04:09 +0800
Subject: [PATCH] 样式修改

---
 src/components/TopNav/index.vue |  433 +++++++++++++++++++++++++++---------------------------
 1 files changed, 216 insertions(+), 217 deletions(-)

diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue
index 56e2803..27de555 100644
--- a/src/components/TopNav/index.vue
+++ b/src/components/TopNav/index.vue
@@ -1,217 +1,216 @@
-<template>
-  <el-menu
-    :default-active="activeMenu"
-    mode="horizontal"
-    @select="handleSelect"
-    :ellipsis="false"
-  >
-    <template v-for="(item, index) in topMenus">
-      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" 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>
-    </template>
-
-    <!-- 椤堕儴鑿滃崟瓒呭嚭鏁伴噺鎶樺彔 -->
-    <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
-      <template #title>鏇村鑿滃崟</template>
-      <template v-for="(item, index) in topMenus">
-        <el-menu-item
-          :index="item.path"
-          :key="index"
-          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>
-      </template>
-    </el-sub-menu>
-  </el-menu>
-</template>
-
-<script setup>
-import { constantRoutes } from "@/router"
-import { isHttp } from '@/utils/validate'
-import useAppStore from '@/store/modules/app'
-import useSettingsStore from '@/store/modules/settings'
-import usePermissionStore from '@/store/modules/permission'
-
-// 椤堕儴鏍忓垵濮嬫暟
-const visibleNumber = ref(null)
-// 褰撳墠婵�娲昏彍鍗曠殑 index
-const currentIndex = ref(null)
-// 闅愯棌渚ц竟鏍忚矾鐢�
-const hideList = ['/index', '/user/profile']
-
-const appStore = useAppStore()
-const settingsStore = useSettingsStore()
-const permissionStore = usePermissionStore()
-const route = useRoute()
-const router = useRouter()
-
-// 涓婚棰滆壊
-const theme = computed(() => settingsStore.theme)
-// 鎵�鏈夌殑璺敱淇℃伅
-const routers = computed(() => permissionStore.topbarRouters)
-
-// 椤堕儴鏄剧ず鑿滃崟
-const topMenus = computed(() => {
-  let topMenus = []
-  routers.value.map((menu) => {
-    if (menu.hidden !== true) {
-      // 鍏煎椤堕儴鏍忎竴绾ц彍鍗曞唴閮ㄨ烦杞�
-      if (menu.path === '/' && menu.children) {
-          topMenus.push(menu.children[0])
-      } else {
-          topMenus.push(menu)
-      }
-    }
-  })
-  return topMenus
-})
-
-// 璁剧疆瀛愯矾鐢�
-const childrenMenus = computed(() => {
-  let childrenMenus = []
-  routers.value.map((router) => {
-    for (let item in router.children) {
-      if (router.children[item].parentPath === undefined) {
-        if(router.path === "/") {
-          router.children[item].path = "/" + router.children[item].path
-        } else {
-          if(!isHttp(router.children[item].path)) {
-            router.children[item].path = router.path + "/" + router.children[item].path
-          }
-        }
-        router.children[item].parentPath = router.path
-      }
-      childrenMenus.push(router.children[item])
-    }
-  })
-  return constantRoutes.concat(childrenMenus)
-})
-
-// 榛樿婵�娲荤殑鑿滃崟
-const activeMenu = computed(() => {
-  const path = route.path
-  let activePath = path
-  if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
-    const tmpPath = path.substring(1, path.length)
-    if (!route.meta.link) {
-      activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"))
-      appStore.toggleSideBarHide(false)
-    }
-  } else if(!route.children) {
-    activePath = path
-    appStore.toggleSideBarHide(true)
-  }
-  activeRoutes(activePath)
-  return activePath
-})
-
-function setVisibleNumber() {
-  const width = document.body.getBoundingClientRect().width / 3
-  visibleNumber.value = parseInt(width / 85)
-}
-
-function handleSelect(key, keyPath) {
-  currentIndex.value = key
-  const route = routers.value.find(item => item.path === key)
-  if (isHttp(key)) {
-    // http(s):// 璺緞鏂扮獥鍙f墦寮�
-    window.open(key, "_blank")
-  } else if (!route || !route.children) {
-    // 娌℃湁瀛愯矾鐢辫矾寰勫唴閮ㄦ墦寮�
-    const routeMenu = childrenMenus.value.find(item => item.path === key)
-    if (routeMenu && routeMenu.query) {
-      let query = JSON.parse(routeMenu.query)
-      router.push({ path: key, query: query })
-    } else {
-      router.push({ path: key })
-    }
-    appStore.toggleSideBarHide(true)
-  } else {
-    // 鏄剧ず宸︿晶鑱斿姩鑿滃崟
-    activeRoutes(key)
-    appStore.toggleSideBarHide(false)
-  }
-}
-
-function activeRoutes(key) {
-  let routes = []
-  if (childrenMenus.value && childrenMenus.value.length > 0) {
-    childrenMenus.value.map((item) => {
-      if (key == item.parentPath || (key == "index" && "" == item.path)) {
-        routes.push(item)
-      }
-    })
-  }
-  if(routes.length > 0) {
-    permissionStore.setSidebarRouters(routes)
-  } else {
-    appStore.toggleSideBarHide(true)
-  }
-  return routes
-}
-
-onMounted(() => {
-  window.addEventListener('resize', setVisibleNumber)
-})
-
-onBeforeUnmount(() => {
-  window.removeEventListener('resize', setVisibleNumber)
-})
-
-onMounted(() => {
-  setVisibleNumber()
-})
-</script>
-
-<style lang="scss">
-.topmenu-container.el-menu--horizontal > .el-menu-item {
-  float: left;
-  height: 50px !important;
-  line-height: 50px !important;
-  color: #999093 !important;
-  padding: 0 5px !important;
-  margin: 0 10px !important;
-}
-
-.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
-  border-bottom: 2px solid #{'var(--theme)'} !important;
-  color: #303133;
-}
-
-/* sub-menu item */
-.topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
-  float: left;
-  height: 50px !important;
-  line-height: 50px !important;
-  color: #999093 !important;
-  padding: 0 5px !important;
-  margin: 0 10px !important;
-}
-
-/* 鑳屾櫙鑹查殣钘� */
-.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
-  background-color: #ffffff;
-}
-
-/* 鍥炬爣鍙抽棿璺� */
-.topmenu-container .svg-icon {
-  margin-right: 4px;
-}
-
-/* topmenu more arrow */
-.topmenu-container .el-sub-menu .el-sub-menu__icon-arrow {
-  position: static;
-  vertical-align: middle;
-  margin-left: 8px;
-  margin-top: 0px;
-}
-
-
-</style>
+<template>
+  <div class="top-nav">
+    <button v-show="showArrows"
+            class="nav-arrow nav-arrow--left"
+            type="button"
+            :disabled="!canScrollLeft"
+            @click="scrollLeft">
+      <el-icon :size="18">
+        <ArrowLeft />
+      </el-icon>
+    </button>
+    <div ref="scrollWrapRef"
+         class="top-nav__scroll"
+         @scroll.passive="updateScrollState">
+      <el-menu class="top-nav-menu"
+               :default-active="activeMenu"
+               mode="horizontal"
+               :unique-opened="true"
+               :ellipsis="false"
+               :active-text-color="theme">
+        <sidebar-item v-for="(routeItem, index) in topbarRouters"
+                      :key="routeItem.path + index"
+                      :item="routeItem"
+                  :base-path="routeItem.path" />
+      </el-menu>
+    </div>
+    <button v-show="showArrows"
+            class="nav-arrow nav-arrow--right"
+            type="button"
+            :disabled="!canScrollRight"
+            @click="scrollRight">
+      <el-icon :size="18">
+        <ArrowRight />
+      </el-icon>
+    </button>
+  </div>
+</template>
+
+<script setup>
+  import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
+  import useSettingsStore from "@/store/modules/settings";
+  import usePermissionStore from "@/store/modules/permission";
+  import SidebarItem from "@/layout/components/Sidebar/SidebarItem.vue";
+
+  const settingsStore = useSettingsStore();
+  const permissionStore = usePermissionStore();
+  const route = useRoute();
+
+  // 涓婚棰滆壊
+  const theme = computed(() => settingsStore.theme);
+  const topbarRouters = computed(() => permissionStore.topbarRouters);
+  const scrollWrapRef = ref(null);
+  const canScrollLeft = ref(false);
+  const canScrollRight = ref(false);
+  const showArrows = computed(() => canScrollLeft.value || canScrollRight.value);
+
+  // 榛樿婵�娲荤殑鑿滃崟
+  const activeMenu = computed(() => {
+    const { meta, path } = route;
+    if (meta?.activeMenu) return meta.activeMenu;
+    return path;
+  });
+
+  function updateScrollState() {
+    const el = scrollWrapRef.value;
+    if (!el) return;
+    const maxScrollLeft = el.scrollWidth - el.clientWidth;
+    canScrollLeft.value = el.scrollLeft > 0;
+    canScrollRight.value = el.scrollLeft < maxScrollLeft - 1;
+  }
+
+  function scrollByStep(direction) {
+    const el = scrollWrapRef.value;
+    if (!el) return;
+    const step = Math.max(240, Math.floor(el.clientWidth * 0.6));
+    el.scrollBy({ left: direction * step, behavior: "smooth" });
+    requestAnimationFrame(() => updateScrollState());
+  }
+
+  function scrollLeft() {
+    scrollByStep(-1);
+  }
+
+  function scrollRight() {
+    scrollByStep(1);
+  }
+
+  let resizeRaf = 0;
+  function handleResize() {
+    if (resizeRaf) cancelAnimationFrame(resizeRaf);
+    resizeRaf = requestAnimationFrame(() => {
+      updateScrollState();
+    });
+  }
+
+  onMounted(() => {
+    updateScrollState();
+    window.addEventListener("resize", handleResize, { passive: true });
+  });
+
+  onBeforeUnmount(() => {
+    window.removeEventListener("resize", handleResize);
+    if (resizeRaf) cancelAnimationFrame(resizeRaf);
+  });
+</script>
+
+<style lang="scss" scoped>
+  .top-nav {
+    position: relative;
+    height: var(--topbar-height);
+    display: flex;
+    align-items: center;
+    width: 100%;
+    min-width: 0;
+  }
+
+  .top-nav__scroll {
+    flex: 1;
+    min-width: 0;
+    height: 100%;
+    overflow-x: auto;
+    overflow-y: hidden;
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  .nav-arrow {
+    width: 34px;
+    height: 34px;
+    flex: 0 0 34px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 999px;
+    border: 1px solid var(--surface-border);
+    background: var(--surface-base);
+    color: var(--text-secondary);
+    cursor: pointer;
+    transition: 0.2s ease;
+    margin: 0 8px;
+
+    &:hover:not(:disabled) {
+      color: var(--el-color-primary);
+      border-color: rgba(var(--el-color-primary-rgb), 0.35);
+      background: rgba(var(--el-color-primary-rgb), 0.06);
+    }
+
+    &:disabled {
+      opacity: 0.45;
+      cursor: not-allowed;
+    }
+  }
+
+  .top-nav-menu {
+    width: max-content;
+    min-width: 100%;
+    height: 100%;
+    border-bottom: none;
+    background: transparent;
+  }
+
+  :deep(.top-nav-menu.el-menu--horizontal) {
+    display: flex;
+    align-items: stretch;
+    flex-wrap: nowrap;
+    height: 100%;
+    width: max-content;
+    min-width: 100%;
+  }
+
+  :deep(.top-nav-menu.el-menu--horizontal > .el-menu-item),
+  :deep(.top-nav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title) {
+    height: var(--topbar-height);
+    line-height: var(--topbar-height);
+    padding: 0 12px;
+    color: var(--text-secondary);
+    border-bottom: 2px solid transparent;
+    transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+  }
+
+  :deep(
+      .top-nav-menu.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover
+    ),
+  :deep(
+      .top-nav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title:hover
+    ) {
+    background: rgba(var(--el-color-primary-rgb), 0.06);
+    color: var(--el-color-primary);
+  }
+
+  :deep(.top-nav-menu.el-menu--horizontal > .el-menu-item.is-active),
+  :deep(
+      .top-nav-menu.el-menu--horizontal
+        > .el-sub-menu.is-active
+        > .el-sub-menu__title
+    ) {
+    background: rgba(var(--el-color-primary-rgb), 0.06);
+    color: var(--el-color-primary);
+    font-weight: 600;
+    border-bottom-color: var(--el-color-primary);
+  }
+
+  :deep(.top-nav-menu.el-menu--horizontal > .el-menu-item .svg-icon),
+  :deep(
+      .top-nav-menu.el-menu--horizontal
+        > .el-sub-menu
+        > .el-sub-menu__title
+        .svg-icon
+    ) {
+    margin-right: 8px;
+  }
+</style>

--
Gitblit v1.9.3