<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>
|