refactor: Sidebar Component
This commit is contained in:
parent
d77f7fafa9
commit
d926c6c2b5
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, PropType } from "vue"
|
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 { isExternal } from "@/utils/validate"
|
||||||
import path from "path-browserify"
|
import path from "path-browserify"
|
||||||
import SidebarItemLink from "./SidebarItemLink.vue"
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
@ -12,7 +12,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
isCollapse: {
|
isCollapse: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false
|
default: false
|
||||||
},
|
},
|
||||||
isFirstLevel: {
|
isFirstLevel: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -20,13 +20,14 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
basePath: {
|
basePath: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
default: ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const alwaysShowRootMenu = computed(() => {
|
const alwaysShowRootMenu = computed(() => {
|
||||||
return !!(props.item.meta && props.item.meta.alwaysShow)
|
return props.item.meta && props.item.meta.alwaysShow
|
||||||
})
|
})
|
||||||
|
|
||||||
const showingChildNumber = computed(() => {
|
const showingChildNumber = computed(() => {
|
||||||
if (props.item.children) {
|
if (props.item.children) {
|
||||||
const showingChildren = props.item.children.filter((item) => {
|
const showingChildren = props.item.children.filter((item) => {
|
||||||
@ -36,6 +37,7 @@ const showingChildNumber = computed(() => {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const theOnlyOneChild = computed(() => {
|
const theOnlyOneChild = computed(() => {
|
||||||
if (showingChildNumber.value > 1) {
|
if (showingChildNumber.value > 1) {
|
||||||
return null
|
return null
|
||||||
@ -64,7 +66,7 @@ const resolvePath = (routePath: string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<template v-if="!alwaysShowRootMenu && theOnlyOneChild && !theOnlyOneChild.children">
|
||||||
<SidebarItemLink v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
|
<SidebarItemLink v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
|
||||||
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
|
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
|
||||||
@ -75,17 +77,17 @@ const resolvePath = (routePath: string) => {
|
|||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</SidebarItemLink>
|
</SidebarItemLink>
|
||||||
</template>
|
</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>
|
<template #title>
|
||||||
<svg-icon v-if="item.meta && item.meta.icon" :name="item.meta.icon" />
|
<svg-icon v-if="props.item.meta && props.item.meta.icon" :name="props.item.meta.icon" />
|
||||||
<span v-if="item.meta && item.meta.title">{{ item.meta.title }}</span>
|
<span v-if="props.item.meta && props.item.meta.title">{{ props.item.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="item.children">
|
<template v-if="props.item.children">
|
||||||
<sidebar-item
|
<sidebar-item
|
||||||
v-for="child in item.children"
|
v-for="child in props.item.children"
|
||||||
:key="child.path"
|
:key="child.path"
|
||||||
:item="child"
|
:item="child"
|
||||||
:is-collapse="isCollapse"
|
:is-collapse="props.isCollapse"
|
||||||
:is-first-level="false"
|
:is-first-level="false"
|
||||||
:base-path="resolvePath(child.path)"
|
:base-path="resolvePath(child.path)"
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from "vue-router"
|
|
||||||
import { isExternal } from "@/utils/validate"
|
import { isExternal } from "@/utils/validate"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -8,21 +7,13 @@ const props = defineProps({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const push = () => {
|
|
||||||
router.push(props.to).catch((err) => {
|
|
||||||
console.warn(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 />
|
<slot />
|
||||||
</a>
|
</a>
|
||||||
<div v-else @click="push">
|
<router-link v-else :to="props.to">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,11 +9,11 @@ const props = defineProps({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
|
<div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
|
||||||
<transition name="sidebarLogoFade">
|
<transition name="sidebar-logo-fade">
|
||||||
<router-link v-if="props.collapse" key="collapse" class="sidebar-logo-link" to="/">
|
<router-link v-if="props.collapse" key="collapse" to="/">
|
||||||
<img src="@/assets/layout/logo.png" class="sidebar-logo" />
|
<img src="@/assets/layout/logo.png" class="sidebar-logo" />
|
||||||
</router-link>
|
</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" />
|
<img src="@/assets/layout/logo-text-1.png" class="sidebar-logo-text" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</transition>
|
</transition>
|
||||||
@ -21,16 +21,6 @@ const props = defineProps({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<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 {
|
.sidebar-logo-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -42,25 +32,21 @@ const props = defineProps({
|
|||||||
.sidebar-logo {
|
.sidebar-logo {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
& .sidebar-logo-link {
|
.sidebar-logo-text {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
vertical-align: middle;
|
||||||
& .sidebar-logo-text {
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&.collapse {
|
}
|
||||||
.sidebar-logo {
|
|
||||||
width: 32px;
|
.collapse {
|
||||||
height: 32px;
|
.sidebar-logo {
|
||||||
vertical-align: middle;
|
width: 32px;
|
||||||
margin-right: 0;
|
height: 32px;
|
||||||
display: inline-block;
|
vertical-align: middle;
|
||||||
}
|
display: inline-block;
|
||||||
.sidebar-logo-text {
|
}
|
||||||
display: none;
|
.sidebar-logo-text {
|
||||||
}
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { usePermissionStore } from "@/store/modules/permission"
|
import { usePermissionStore } from "@/store/modules/permission"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
@ -17,47 +18,40 @@ const appStore = useAppStore()
|
|||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
const sidebar = computed(() => {
|
const { showSidebarLogo } = storeToRefs(settingsStore)
|
||||||
return appStore.sidebar
|
|
||||||
})
|
|
||||||
const routes = computed(() => {
|
|
||||||
return permissionStore.routes
|
|
||||||
})
|
|
||||||
const showLogo = computed(() => {
|
|
||||||
return settingsStore.showSidebarLogo
|
|
||||||
})
|
|
||||||
const activeMenu = computed(() => {
|
const activeMenu = computed(() => {
|
||||||
const { meta, path } = route
|
const { meta, path } = route
|
||||||
if (meta !== null || meta !== undefined) {
|
if (meta?.activeMenu) {
|
||||||
if (meta.activeMenu) {
|
return meta.activeMenu
|
||||||
return meta.activeMenu
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
})
|
})
|
||||||
|
|
||||||
const isCollapse = computed(() => {
|
const isCollapse = computed(() => {
|
||||||
return !sidebar.value.opened
|
return !appStore.sidebar.opened
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': showLogo }">
|
<div :class="{ 'has-logo': showSidebarLogo }">
|
||||||
<SidebarLogo v-if="showLogo" :collapse="isCollapse" />
|
<SidebarLogo v-if="showSidebarLogo" :collapse="isCollapse" />
|
||||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||||
<el-menu
|
<el-menu
|
||||||
:collapse="isCollapse"
|
|
||||||
:unique-opened="true"
|
|
||||||
:default-active="activeMenu"
|
:default-active="activeMenu"
|
||||||
|
:collapse="isCollapse"
|
||||||
:background-color="v3SidebarMenuBgColor"
|
:background-color="v3SidebarMenuBgColor"
|
||||||
:text-color="v3SidebarMenuTextColor"
|
:text-color="v3SidebarMenuTextColor"
|
||||||
:active-text-color="v3SidebarMenuActiveTextColor"
|
:active-text-color="v3SidebarMenuActiveTextColor"
|
||||||
|
:unique-opened="true"
|
||||||
|
:collapse-transition="false"
|
||||||
mode="vertical"
|
mode="vertical"
|
||||||
>
|
>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
v-for="routeItem in routes"
|
v-for="route in permissionStore.routes"
|
||||||
:key="routeItem.path"
|
:key="route.path"
|
||||||
:item="routeItem"
|
:item="route"
|
||||||
:base-path="routeItem.path"
|
:base-path="route.path"
|
||||||
:is-collapse="isCollapse"
|
:is-collapse="isCollapse"
|
||||||
/>
|
/>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
@ -65,29 +59,6 @@ const isCollapse = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<style lang="scss" scoped>
|
||||||
@mixin tip-line {
|
@mixin tip-line {
|
||||||
&::before {
|
&::before {
|
||||||
@ -101,16 +72,30 @@ const isCollapse = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-scrollbar {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-logo {
|
.has-logo {
|
||||||
.el-scrollbar {
|
.el-scrollbar {
|
||||||
height: calc(100% - var(--v3-header-height));
|
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 {
|
.el-menu {
|
||||||
border: none;
|
border: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,27 +1,14 @@
|
|||||||
// See https://staging-cn.vuejs.org/guide/built-ins/transition.html for detail
|
// 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
|
||||||
.fade-transform-leave-active,
|
.fade-transform-leave-active,
|
||||||
.fade-transform-enter-active {
|
.fade-transform-enter-active {
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-transform-enter {
|
.fade-transform-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(-30px);
|
transform: translateX(-30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-transform-leave-to {
|
.fade-transform-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(30px);
|
transform: translateX(30px);
|
||||||
@ -32,17 +19,24 @@
|
|||||||
.breadcrumb-leave-active {
|
.breadcrumb-leave-active {
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-enter,
|
.breadcrumb-enter,
|
||||||
.breadcrumb-leave-active {
|
.breadcrumb-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(20px);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-move {
|
.breadcrumb-move {
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
.breadcrumb-leave-active {
|
||||||
position: absolute;
|
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