1. 介绍
书接上一回,我们说完了header部分的内容,如果没有看过的可以在其他文章中跳转。本篇介绍sideBar中的内容。
功能:sideBar主要是展示菜单,其中包括:多级菜单、外链、单个子路由合并
注:涉及到路由方面都会涉及到动态路由,也就是路由权限管理,继续挖坑嘿嘿,等待下篇讲解。
2. 详解
2.1 介绍
- sideBar结构很简单,上面是后台管理的logo或名称,下面是一个菜单栏。
- 菜单栏中如果不止一个子路由就可以展开展示,如果只有一个子路由且是最终的子路由(子路由没有子路由)我进行了合并成为父路由,单个路由展开有些奇怪。
多级嵌套:我们是不限制嵌套多少级子路由的,所以这里得使用一个递归组件的思想。
vue3文档:一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用<FooBar/>
引用它自己。
2.2 实现
最外层使用的是el-munu,定义了一个子组件aside-item
用来进行自我递归,传入的是当前角色的动态路由+静态路由。样式取的是定义的主题中的变量。
// layout > components > sideBar > index.vue
<template>
<div class="aside-wrapper">
<section class="logo-wrapper" >
<p style="color:#fff; font-size: 50px; text-align: center">hr</p>
</section>
<el-menu
default-active="dashboard"
:collapse="isCollapse"
:unique-opened="true"
:collapse-transition="false"
>
<aside-item
:menu="menu"
:icon-show="true"
:is-collapse="isCollapse"
/>
</el-menu>
</div>
</template>
<script lang="ts" setup>
import {computed} from 'vue'
import AsideItem from './componts/asideItem.vue'
import {useUserStore} from '@/store/user'
const props = defineProps({
isCollapse: {
type: Boolean,
default: false
}
})
// 当前角色全部路由
const userStore = useUserStore()
const menu = userStore.routes
// 获取主题中定义的aside宽度
const asideWidth = computed(()=>props.isCollapse? 'var(--aside-min-width)': 'var(--aside-max-width)')
</script>
<!---->
<style lang="less" scoped>
.aside-wrapper {
width: v-bind(asideWidth);
height: calc(100% - @tag-height);
transition:.5s;
background-color: @theme-aside-bg-color;
.logo-wrapper {
height: calc(@header-height + @tag-height);
line-height: calc(@header-height + @tag-height);
}
:deep(.el-menu-item){
&.is-active {
background-color: @theme-aside-active-bg-color;
}
}
}
</style>
- 通过计算属性,去计算是否是多级或者是单级。多级用el-sub-menu,在将内容进行递归,单级使用el-menu-item。
- 每个item的key为路由的path,如果不是外链就直接路由跳转,如果是外链单独跳转。
- 只有一级路由会有icon,每级路由都有名称。
主要实现如下:
// sideBar > components > asideItem
<template>
<template v-for="route in menu">
<el-sub-menu v-if="!isOneItemShow(route)"
:class="{'aside-collapse':isCollapse}"
:index="route.path">
<template #title>
<svg-icon class="aside-svg"
v-if="iconShow"
:name="route?.meta?.svgIcon"
/>
<span class="aside-title">
{{ route?.meta?.title }}
</span>
</template>
<aside-item
:menu="route.children"
/>
</el-sub-menu>
<el-menu-item v-else
:class="{'aside-collapse':isCollapse}"
@click="clickMenuItem(route.children && route.children[0] || route)"
:index="route.path"
>
<svg-icon v-if="iconShow"
class="aside-svg"
:name="route?.meta?.svgIcon"
/>
<template #title>
<span class="aside-title">
{{ route?.meta?.title }}
</span>
</template>
</el-menu-item>
</template>
</template>
<script setup lang="ts">
import {useRouter} from "vue-router";
// TODO 组件遍历自身
const props = defineProps({
menu: {
type: Object,
require: true,
},
iconShow: {
type: Boolean,
default: false
},
isCollapse: {
type: Boolean,
require: true
}
})
const router = useRouter()
// 过滤隐藏
const menu = props.menu?.filter(route => !route.meta.sidebarHide)
// 判断单个路由 和 是否合并显示
const isOneItemShow = (router) => {
if(router.children){
if ( router.children.length > 1 || router.children[0].children) {
return false
}
}
return true
}
// 点击后跳转至对应路由
const clickMenuItem = (item) => {
if (item?.meta?.isExt) {
window.open(item.path, '_blank')
} else {
router.push(item.path)
}
}
</script>
<style lang="less" scoped>
.aside-svg {
box-sizing: content-box;
margin: 0 0.5em;
font-size: 1.2em;
}
.aside-collapse {
:deep(.el-tooltip__trigger) {
padding: 0;
}
.aside-svg {
width: 100%;
}
}
</style>
2.3 main
- 使用router-view的插槽方式去拓展动画和keep
alive等额外功能 - 我们在constants中定义了需要保活的列表,keep-alive 中支持include传入路由列表name值,其他方式没尝试成功,道友可以试一试。
// layout > index.vue
<template>
<section class="layout-wrapper">
<aside class="left-wrapper">
<my-side :is-collapse="isCollapsed"/>
</aside>
<div class="right-wrapper">
<header>
<my-header v-model:isCollapsed="isCollapsed">
</my-header>
</header>
<main class="scrollbar">
<router-view v-slot="{ Component, route }">
<transition name="fade">
<keep-alive :include="KEEP_ALIVE_COMPONENT">
<component :is="Component" :key="route.path"/>
</keep-alive>
</transition>
</router-view>
</main>
</div>
</section>
</template>
<script setup lang="ts">
import {onMounted, watch, ref, computed} from 'vue'
import MyHeader from './components/header/index.vue'
import MySide from './components/sideBar/index.vue'
import cache from '@/utils/cache'
import {KEEP_ALIVE_COMPONENT} from '@/constants/setting'
import {IS_COLLAPSED_KEY} from '@/constants/cacheKeys'
const isCollapsed = ref<boolean>(false)
onMounted(()=>{
isCollapsed.value = cache.getItem(IS_COLLAPSED_KEY)
})
watch(()=>isCollapsed.value,()=>{
cache.setItem(IS_COLLAPSED_KEY,isCollapsed.value)
})
</script>
<style lang="less" scoped>
body {
background-color: @theme-bg-color;
}
.theme-style{
color: @theme-font-color;
background-color: @theme-bg-color;
}
.layout-wrapper {
display: flex;
.left-wrapper {
z-index:999;
position: sticky;
top: 0;
left: 0;
flex-shrink: 0;
overflow-x: hidden;
overflow-y: auto;
height: 100vh;
}
.right-wrapper {
flex:1;
.theme-style();
header{
.theme-style();
background-color: @theme-color;
}
main{
overflow-y: scroll;
position: relative;
height: calc( 100% - 10px * 2 - @header-height - @tag-height );
margin: 10px;
background-color: @theme-color;
}
}
}
</style>
整体框架就是这样了,写的有些粗糙,有问题可以私聊,有什么建议可以提出来,大家想办法一起改进。
3. 本框架其他文章链接
GitHub开源链接:GitHub - grxynl/vue3-admin-template: vue3+TypeScript+pinia 后台管理系统模板
其他文章
从0开始搭建后台管理系统(首篇)
从0开始搭建后台管理系统-01(Login登录)
从0开始搭建后台管理系统-02(Layout布局)(上篇)
4. 结束语
本框架完全免费,框架尚有不足,供前端爱好者一起讨论,一起学习。
源码和文档都制作不易,如果觉得您还可以的话,求一个stars,这是对我最大的支持,也是本框架前进的最大动力。