【Vue3+Element Plus】从0-1搭建后台管理系统(03)-Header-左侧侧
这一章记录完成Header顶部左侧区域的静态布局实现过程。
顶部区域的部分包含了侧边栏折叠图标、全屏、面包屑、消息通知图标、用户头像、主题切换开关(白昼) 的渲染。
创建对应目录以及组件
- 在
layout目录下新建Header文件夹 - 在
Header文件夹下新建index.vue - 在
Header目录下新建components文件夹,用来存放用到的组件 - 在
Header目录下新建Nav.vue顶部导航栏组件、Tags.vue标签栏组件以及Breadcrumb.vue面包屑组件。其余的组件可以根据自己需要自行创建,例如:用户头像之类的。
顶部导航栏(Nav)
顶部导航栏我将其分为左侧和右侧两个区域,左侧用来展示侧边栏展开收起图标以及面包屑,右侧用来展示用户头像等。使用flex布局实现左右两侧布局,具体代码如下:
<style lang="scss" scoped>
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
height: 50px;
border-bottom: 1px solid $border-color;
}
</style>
左侧
图标
-
图标这块我选择的是iconify。
-
安装:
npm install --save-dev @iconify/vue -
封装:
在
src/components目录下创建Icon专门用来放图标组件,然后在此目录下创建IconifyIcon.vue:<template> <IconifyIcon :icon="iconName" :style="{ ...attr }" /> </template> <script setup lang="ts"> import { Icon as IconifyIcon } from '@iconify/vue' import { useAttrs } from 'vue' defineOptions({ name: 'IconifyIcon' }) /** 父组件绑定在子组件上但子组件没有使用props接收的属性 */ const attr = useAttrs() /** 用来接受父组件传的值 */ const props = withDefaults(defineProps<{ iconName: string }>(), { iconName: '' }) </script> <style lang="scss" scoped></style>这里需要注意一下,图标的容器高度会多出几个像素,上网查找了一下解决方法,给svg图标的容器添加
display: flex即可解决。在
Nav.vue中引入使用:<template> <div class="nav"> <!-- 左侧区域 --> <div class="nav-left"> <!-- 折叠图标 --> <iconify-icon icon-name="ant-design:menu-fold-outlined"></iconify-icon> <!-- 面包屑 --> </div> <!-- 右侧区域 --> <div class="nav-right"></div> </div> </template> <script setup lang="ts"> /** 引入iconify-icon */ import IconifyIcon from '@/components/Icon/IconifyIcon.vue' defineOptions({ name: 'DTNav' }) </script>
控制侧边栏的展开/收起
控制侧边栏的展开和收起需要一个开关去进行控制,这个开关不单单是控制侧边栏的展开和收起,并且控制着顶部左侧图标的一个切换。
因为这个开关值在两个没有任何关联的组件中都使用到了,所以我将这个开关定义在pinia中:
-
创建一个pinia仓库:
import { defineStore, type Store } from 'pinia' import { ref } from 'vue' import { store } from '../index' export const useLayoutStore = defineStore('layout', () => { /** 控制侧边栏是否收起 */ const isCollapse = ref(false) /** 切换isCollapse的状态 */ const changeIsCpllapse = (): void => { isCollapse.value = !isCollapse.value } return { isCollapse, changeIsCpllapse } }) /** * 用于setup语法糖以外的地方使用 * @returns {Store} */ export const useLayoutStoreHook = (): Store => { return useLayoutStore(store) } -
在
Nav.vue组件中控制图标的切换:<template> <!-- 折叠图标 --> <iconify-icon :icon-name="isCollapse ? 'ant-design:menu-unfold-outlined' : 'ant-design:menu-fold-outlined'" @click="changeIsCpllapse"> </iconify-icon> </template> <script setup lang="ts"> /** 引入 layout store */ import { useLayoutStore } from '@/store/modules/layout' /** 引入storeToRef()函数,从store中获取属性保持响应性 */ import { storeToRefs } from 'pinia' defineOptions({ name: 'DTNav' }) /** layoutstore */ const layoutStore = useLayoutStore() /** 获取layoutstore中的方法 */ const { changeIsCpllapse } = layoutStore /** 获取layoutstore中的属性 */ const { isCollapse } = storeToRefs(layoutStore) console.log('isCollapse', isCollapse) </script> -
在
Sidebar/index.vue组件中引入并控制el-menu的收起/展开:<template> <el-menu default-active="2" background-color="#001428" text-color="#BBB" active-text-color="#fff" :collapse="isCollapse" > </template> <script setup lang="ts"> /** 引入 layoutstore */ import { useLayoutStore } from '@/store/modules/layout' /** 引入storeToRef()函数,从store中解构属性保持响应性 */ import { storeToRefs } from 'pinia' defineOptions({ name: 'SideBar' }) /** 创建useLayoutStore */ const layoutStore = useLayoutStore() /** 获取layoutStore中的属性 */ const { isCollapse } = storeToRefs(layoutStore) </script> -
在
layout/index.vue组件中引入并控制el-aside的收起/展开:<template> <el-aside :width="isCollapse ? '65px' : '200px'"> <SideBar /> </el-aside> </template> <script setup lang="ts"> import SideBar from './SideBar/index.vue' /** 引入useLayoutStore */ import { useLayoutStore } from '@/store/modules/layout' /** storeToRefs(),从store中解构属性保持响应性 */ import { storeToRefs } from 'pinia' defineOptions({ name: 'Layout' }) /** 创建layoutStore */ const layoutStore = useLayoutStore() /** 从layoutStore中解构出isCollapse属性 */ const { isCollapse } = storeToRefs(layoutStore) </script> <style> .el-aside { ... transition: width 0.5s; } </style> -
侧边栏logo的变化
logo的变化指的是在侧边栏收起的时候,logo只展示图片不展示文字:
<template> <div class="logo"> <img class="logo-image" :src="getImageUrl('logo.png')" alt="logo.png" /> <Transition enter-active-class="animate__animated animate__fadeInDown" mode="out-in"> <h1 class="logo-text" v-if="!isCollapse">DaiTuAdmin</h1> </Transition> </div> </template> <script setup lang="ts"> /** 引入useLayoutStore */ import { useLayoutStore } from '@/store/modules/layout' /** 引入storeToRefs(),从store中解构属性保持响应性 */ import { storeToRefs } from 'pinia' /** 引入animate.css */ import 'animate.css' defineOptions({ name: 'Logo' }) /** 创建layoutStore */ const layoutStore = useLayoutStore() /** 从layoutStore中解构属性 */ const { isCollapse } = storeToRefs(layoutStore) </script>
最终的效果:
面包屑的渲染
面包屑也是同样在左侧展示,用到Element-plus的Breadcrumb组件:
首先还是将我们需要使用到的element-plus的组件引入进行全局注册。
然后在Breadcrumb.vue组件中进行使用:
Breadcrumb.vue:
<template>
<div class="breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item>
<el-breadcrumb-item>promotion management</el-breadcrumb-item>
<el-breadcrumb-item>promotion list</el-breadcrumb-item>
<el-breadcrumb-item>promotion detail</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'Breadcrumb'
})
</script>
<style lang="scss" scoped></style>
然后在Nav.vue组件中引入并使用:
<!-- 左侧区域 -->
<template>
<div class="nav-left">
<!-- 折叠图标 -->
<i class="nav-left__iscollapse">
<component :is="renderIconIfyIcon('ant-design:menu-fold-outlined')" />
</i>
<!-- 面包屑 -->
<Breadcrumb />
</div>
</template>
<script setup lang="ts">
/** 引入面包屑组件 */
import Breadcrumb from './Breadcrumb.vue'
</script>