refactor: Sidebar Component
This commit is contained in:
parent
d77f7fafa9
commit
d926c6c2b5
@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, PropType } from "vue"
|
||||
import { RouteRecordRaw } from "vue-router"
|
||||
import type { RouteRecordRaw } from "vue-router"
|
||||
import SidebarItemLink from "./SidebarItemLink.vue"
|
||||
import { isExternal } from "@/utils/validate"
|
||||
import path from "path-browserify"
|
||||
import SidebarItemLink from "./SidebarItemLink.vue"
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
@ -12,7 +12,7 @@ const props = defineProps({
|
||||
},
|
||||
isCollapse: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
default: false
|
||||
},
|
||||
isFirstLevel: {
|
||||
type: Boolean,
|
||||
@ -20,13 +20,14 @@ const props = defineProps({
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
required: true
|
||||
default: ""
|
||||
}
|
||||
})
|
||||
|
||||
const alwaysShowRootMenu = computed(() => {
|
||||
return !!(props.item.meta && props.item.meta.alwaysShow)
|
||||
return props.item.meta && props.item.meta.alwaysShow
|
||||
})
|
||||
|
||||
const showingChildNumber = computed(() => {
|
||||
if (props.item.children) {
|
||||
const showingChildren = props.item.children.filter((item) => {
|
||||
@ -36,6 +37,7 @@ const showingChildNumber = computed(() => {
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
const theOnlyOneChild = computed(() => {
|
||||
if (showingChildNumber.value > 1) {
|
||||
return null
|
||||
@ -64,7 +66,7 @@ const resolvePath = (routePath: string) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!item.meta || !item.meta.hidden" :class="{ 'simple-mode': isCollapse, 'first-level': isFirstLevel }">
|
||||
<div v-if="!props.item.meta?.hidden" :class="{ 'simple-mode': props.isCollapse, 'first-level': props.isFirstLevel }">
|
||||
<template v-if="!alwaysShowRootMenu && theOnlyOneChild && !theOnlyOneChild.children">
|
||||
<SidebarItemLink v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
|
||||
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
|
||||
@ -75,17 +77,17 @@ const resolvePath = (routePath: string) => {
|
||||
</el-menu-item>
|
||||
</SidebarItemLink>
|
||||
</template>
|
||||
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
|
||||
<el-sub-menu v-else :index="resolvePath(props.item.path)" popper-append-to-body>
|
||||
<template #title>
|
||||
<svg-icon v-if="item.meta && item.meta.icon" :name="item.meta.icon" />
|
||||
<span v-if="item.meta && item.meta.title">{{ item.meta.title }}</span>
|
||||
<svg-icon v-if="props.item.meta && props.item.meta.icon" :name="props.item.meta.icon" />
|
||||
<span v-if="props.item.meta && props.item.meta.title">{{ props.item.meta.title }}</span>
|
||||
</template>
|
||||
<template v-if="item.children">
|
||||
<template v-if="props.item.children">
|
||||
<sidebar-item
|
||||
v-for="child in item.children"
|
||||
v-for="child in props.item.children"
|
||||
:key="child.path"
|
||||
:item="child"
|
||||
:is-collapse="isCollapse"
|
||||
:is-collapse="props.isCollapse"
|
||||
:is-first-level="false"
|
||||
:base-path="resolvePath(child.path)"
|
||||
/>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from "vue-router"
|
||||
import { isExternal } from "@/utils/validate"
|
||||
|
||||
const props = defineProps({
|
||||
@ -8,21 +7,13 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const push = () => {
|
||||
router.push(props.to).catch((err) => {
|
||||
console.warn(err)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
||||
<a v-if="isExternal(props.to)" :href="props.to" target="_blank" rel="noopener">
|
||||
<slot />
|
||||
</a>
|
||||
<div v-else @click="push">
|
||||
<router-link v-else :to="props.to">
|
||||
<slot />
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
@ -9,11 +9,11 @@ const props = defineProps({
|
||||
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link v-if="props.collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||
<transition name="sidebar-logo-fade">
|
||||
<router-link v-if="props.collapse" key="collapse" to="/">
|
||||
<img src="@/assets/layout/logo.png" class="sidebar-logo" />
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<router-link v-else key="expand" to="/">
|
||||
<img src="@/assets/layout/logo-text-1.png" class="sidebar-logo-text" />
|
||||
</router-link>
|
||||
</transition>
|
||||
@ -21,16 +21,6 @@ const props = defineProps({
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebarLogoFade-enter-active,
|
||||
.sidebarLogoFade-leave-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
|
||||
.sidebarLogoFade-enter-from,
|
||||
.sidebarLogoFade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@ -42,25 +32,21 @@ const props = defineProps({
|
||||
.sidebar-logo {
|
||||
display: none;
|
||||
}
|
||||
& .sidebar-logo-link {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
& .sidebar-logo-text {
|
||||
.sidebar-logo-text {
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
&.collapse {
|
||||
}
|
||||
|
||||
.collapse {
|
||||
.sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.sidebar-logo-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useAppStore } from "@/store/modules/app"
|
||||
import { usePermissionStore } from "@/store/modules/permission"
|
||||
import { useSettingsStore } from "@/store/modules/settings"
|
||||
@ -17,47 +18,40 @@ const appStore = useAppStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const sidebar = computed(() => {
|
||||
return appStore.sidebar
|
||||
})
|
||||
const routes = computed(() => {
|
||||
return permissionStore.routes
|
||||
})
|
||||
const showLogo = computed(() => {
|
||||
return settingsStore.showSidebarLogo
|
||||
})
|
||||
const { showSidebarLogo } = storeToRefs(settingsStore)
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route
|
||||
if (meta !== null || meta !== undefined) {
|
||||
if (meta.activeMenu) {
|
||||
if (meta?.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
}
|
||||
return path
|
||||
})
|
||||
|
||||
const isCollapse = computed(() => {
|
||||
return !sidebar.value.opened
|
||||
return !appStore.sidebar.opened
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'has-logo': showLogo }">
|
||||
<SidebarLogo v-if="showLogo" :collapse="isCollapse" />
|
||||
<div :class="{ 'has-logo': showSidebarLogo }">
|
||||
<SidebarLogo v-if="showSidebarLogo" :collapse="isCollapse" />
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
:collapse="isCollapse"
|
||||
:unique-opened="true"
|
||||
:default-active="activeMenu"
|
||||
:collapse="isCollapse"
|
||||
:background-color="v3SidebarMenuBgColor"
|
||||
:text-color="v3SidebarMenuTextColor"
|
||||
:active-text-color="v3SidebarMenuActiveTextColor"
|
||||
:unique-opened="true"
|
||||
:collapse-transition="false"
|
||||
mode="vertical"
|
||||
>
|
||||
<SidebarItem
|
||||
v-for="routeItem in routes"
|
||||
:key="routeItem.path"
|
||||
:item="routeItem"
|
||||
:base-path="routeItem.path"
|
||||
v-for="route in permissionStore.routes"
|
||||
:key="route.path"
|
||||
:item="route"
|
||||
:base-path="route.path"
|
||||
:is-collapse="isCollapse"
|
||||
/>
|
||||
</el-menu>
|
||||
@ -65,29 +59,6 @@ const isCollapse = computed(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.sidebar-container {
|
||||
// 重置当前页面的 Element-Plus CSS, ,注意,虽然没有加 scoped 标识,但是被该页面的 sidebar-container 类名包裹,所以不会影响其他页面
|
||||
.horizontal-collapse-transition {
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
.el-scrollbar__bar {
|
||||
&.is-vertical {
|
||||
right: 0;
|
||||
}
|
||||
&.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin tip-line {
|
||||
&::before {
|
||||
@ -101,16 +72,30 @@ const isCollapse = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.has-logo {
|
||||
.el-scrollbar {
|
||||
height: calc(100% - var(--v3-header-height));
|
||||
}
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
::v-deep(.scrollbar-wrapper) {
|
||||
// 限制水平宽度
|
||||
overflow-x: hidden !important;
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
// 滚动条
|
||||
::v-deep(.el-scrollbar__bar) {
|
||||
&.is-horizontal {
|
||||
// 隐藏水平滚动条
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: none;
|
||||
height: 100%;
|
||||
|
@ -1,27 +1,14 @@
|
||||
// See https://staging-cn.vuejs.org/guide/built-ins/transition.html for detail
|
||||
|
||||
// fade
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.28s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// fade-transform
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
@ -32,17 +19,24 @@
|
||||
.breadcrumb-leave-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-move {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// sidebar-logo-fade
|
||||
.sidebar-logo-fade-enter-active,
|
||||
.sidebar-logo-fade-leave-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
.sidebar-logo-fade-enter-from,
|
||||
.sidebar-logo-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user