<script lang="ts" setup name="Topbar"> import { compile } from 'path-to-regexp' import Tools from '../Tools/index.vue' import useSettingsStore from '@/store/modules/settings' import useMenuStore from '@/store/modules/menu' import Setting from '@/views/setting.vue' const route = useRoute() const router = useRouter() const menuStore = useMenuStore() const settingsStore = useSettingsStore() const { proxy } = getCurrentInstance() as any const enableSubMenuCollapseButton = computed(() => { return settingsStore.mode === 'mobile' || ( ['side', 'head', 'single'].includes(settingsStore.menu.menuMode) && settingsStore.menu.enableSubMenuCollapseButton ) }) // const breadcrumbList = computed(() => { // const breadcrumbList = [] // if (settingsStore.dashboard.enable) { // breadcrumbList.push({ // path: '/dashboard', // title: settingsStore.dashboard.title, // }) // } // if (route.meta.breadcrumbNeste) { // breadcrumbList.push(...route.meta.breadcrumbNeste.filter(item => item.hide === false)) // } // return breadcrumbList // }) const publicPath = window.location.href.split('#')[0] const breadcrumbList = ref<any[]>(menuStore.breadcrumbList) const routeInfo = ref(route) watch(() => menuStore.breadcrumbList, (newVal) => { if (newVal.length) { breadcrumbList.value = menuStore.breadcrumbList } else { breadcrumbList.value = [] } }, { deep: true, immediate: true, }) watch(() => route.path, (newVal) => { // console.log(route, '当前路由信息') routeInfo.value = route }, { deep: true, // immediate: true, }) const scrollTop = ref(0) onMounted(() => { window.addEventListener('scroll', onScroll) }) onUnmounted(() => { window.removeEventListener('scroll', onScroll) }) function onScroll() { scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop } function pathCompile(path: string) { const toPath = compile(path) return toPath(route.params) } // 面包屑跳转 const breadcrumbTo = (path: any) => { // console.log(path, 'path') router.push(path.fullPath) } // 关闭面包屑 const closePage = (path: any) => { menuStore.removeBreadcrumb(path) setTimeout(() => { if (routeInfo.value.path === path.path) { router.push(breadcrumbList.value.length ? breadcrumbList.value[0].fullPath : '/home/index') } }) } </script> <template> <div class="topbar-container" :class="{ fixed: settingsStore.topbar.fixed, shadow: scrollTop, }" data-fixed-calc-width> <div class="topbar-container-inner"> <div class="left-box"> <div v-if="enableSubMenuCollapseButton" class="sidebar-collapse" :class="{ 'is-collapse': settingsStore.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()"> <el-icon> <svg-icon name="toolbar-collapse" /> </el-icon> </div> <el-breadcrumb v-if="settingsStore.breadcrumb.enable && settingsStore.mode === 'pc' && settingsStore.app.routeBaseOn !== 'filesystem'"> <transition-group name="breadcrumb"> <!-- <el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.path" :to="index < breadcrumbList.length - 1 ? pathCompile(item.path) : ''"> {{ item.title }} </el-breadcrumb-item> --> <div class="breadcrumb-container"> <!-- 返回首页 --> <span v-if="proxy.hasPerm('/dashboard')" class="is-active img" @click="breadcrumbTo({ path: '/dashboard/index', fullPath: '/dashboard/index' })"> <img :src="`${publicPath}/image/home.png`" alt=""> </span> <!-- 面包屑 --> <span v-for="(item) in breadcrumbList" :key="item.path" class="breadcrumb-item" :class="routeInfo.path === item.path ? 'is-active' : ''"> <span @click="breadcrumbTo(item)">{{ item.meta.title }}</span> <span class="icon" @click="closePage(item)">×</span> </span> </div> </transition-group> </el-breadcrumb> </div> <!-- <tools /> --> </div> </div> </template> <style lang="scss" scoped> .breadcrumb-container { display: flex; .img { margin: 0 4px; box-shadow: 0 0 2px rgb(0 0 0 / 12%); // padding: 8px; box-sizing: content-box; display: flex; flex-direction: column; justify-content: center; padding: 0 6px; border-radius: 3px; height: 30px; &:hover { cursor: pointer; } img { width: 20px; height: 20px; } } .breadcrumb-item { height: 30px; padding: 8px; background-color: #ebeef5; border-radius: 3px; margin: 0 4px; box-shadow: 0 0 2px rgb(0 0 0 / 12%); position: relative; padding-left: 14px; padding-right: 14px; .icon { position: absolute; top: -2px; // right: -4px; margin-left: 2px; font-size: 16px; opacity: 0; } &:hover { cursor: pointer; background-color: #0d76d4 !important; color: #fff; .icon { // padding-right: 14px; opacity: 1; // color: #000 !important; } } } .is-active { background-color: #0d76d4 !important; color: #fff; } } .topbar-container { position: absolute; z-index: 999; top: 0; display: flex; align-items: center; justify-content: space-between; height: var(--g-topbar-height); transition: width 0.3s, top 0.3s, transform 0.3s, background-color 0.3s, var(--el-transition-box-shadow); // background-image: linear-gradient(90deg, #71b5ff 30%, #3d7eff); .topbar-container-inner { width: 100%; border-radius: 15px 10px 0 0; background-color: var(--g-toolbar-bg); // background-color: #f1f2f6; } &.fixed { position: fixed; &.shadow { box-shadow: 0 10px 10px -10px var(--g-box-shadow-color); } } .left-box { display: flex; align-items: center; padding-right: 50px; overflow: hidden; mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 50px), transparent); .sidebar-collapse { display: flex; align-items: center; padding: 0 20px; height: 50px; cursor: pointer; .el-icon { color: var(--el-text-color-primary); transition: var(--el-transition-color), var(--el-transition-md-fade); } &:hover .el-icon { color: var(--el-color-primary); } &.is-collapse .el-icon { transform: rotateZ(-180deg); } &+.el-breadcrumb { margin-left: 0; } } :deep(.el-breadcrumb) { margin-left: 20px; white-space: nowrap; .el-breadcrumb__item { display: inline-block; float: none; span { font-weight: normal; } &:last-child span { color: #918f90; } } } } } // 面包屑动画 .breadcrumb-enter-active { transition: all 0.25s; } .breadcrumb-enter-from, .breadcrumb-leave-active { opacity: 0; transform: translateX(30px) skewX(-50deg); } </style>