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

---
 src/components/TopNav/index.vue |  402 +++++++++++++++++++++++++++++++--------------------------
 1 files changed, 219 insertions(+), 183 deletions(-)

diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue
index 853e48f..2151eb5 100644
--- a/src/components/TopNav/index.vue
+++ b/src/components/TopNav/index.vue
@@ -1,183 +1,219 @@
-<template>
-  <el-menu
-    :default-active="activeMenu"
-    mode="horizontal"
-    @select="handleSelect"
-  >
-    <template v-for="(item, index) in topMenus">
-      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
-        ><svg-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 :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'
-
-// 椤堕儴鏍忓垵濮嬫暟
-const visibleNumber = ref(null);
-// 鏄惁涓洪娆″姞杞�
-const isFrist = ref(null);
-// 褰撳墠婵�娲昏彍鍗曠殑 index
-const currentIndex = ref(null);
-
-const store = useStore();
-const route = useRoute();
-const router = useRouter();
-
-// 涓婚棰滆壊
-const theme = computed(() => store.state.settings.theme);
-// 鎵�鏈夌殑璺敱淇℃伅
-const routers = computed(() => store.state.permission.topbarRouters);
-
-// 椤堕儴鏄剧ず鑿滃崟
-const topMenus = computed(() => {
-  let topMenus = [];
-  routers.value.map((menu) => {
-    if (menu.hidden !== true) {
-      // 鍏煎椤堕儴鏍忎竴绾ц彍鍗曞唴閮ㄨ烦杞�
-      if (menu.path === "/") {
-          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) {
-    const tmpPath = path.substring(1, path.length);
-    activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
-    store.dispatch('app/toggleSideBarHide', false);
-  } else if ("/index" == path || "" == path) {
-    if (!isFrist.value) {
-      isFrist.value = true;
-    } else {
-      activePath = "index";
-    }
-    store.dispatch('app/toggleSideBarHide', true);
-  } else if(!route.children) {
-    activePath = path;
-    store.dispatch('app/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) {
-    // 娌℃湁瀛愯矾鐢辫矾寰勫唴閮ㄦ墦寮�
-    router.push({ path: key });
-    store.dispatch('app/toggleSideBarHide', true);
-  } else {
-    // 鏄剧ず宸︿晶鑱斿姩鑿滃崟
-    activeRoutes(key);
-    store.dispatch('app/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) {
-    store.commit("SET_SIDEBAR_ROUTERS", routes);
-  }
-  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;
-}
-</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: 100%;
+    line-height: var(--topbar-height);
+    display: flex;
+    align-items: center;
+    padding: 0 12px;
+    color: var(--navbar-text);
+    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(255, 255, 255, 0.1);
+    color: #ffffff;
+  }
+
+  :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(255, 255, 255, 0.15);
+    color: #ffffff;
+    font-weight: 600;
+    border-bottom-color: #ffffff;
+  }
+
+  :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;
+    vertical-align: middle;
+  }
+</style>

--
Gitblit v1.9.3