左侧菜单伸缩功能实现
接着我们来实现一个左侧菜单伸缩,这个功能的核心点在于动画的处理;样式的改变总是由数据进行驱动,所以我们首先去创建对应的数据;
创建store/modules/app.js,写入代码:
app.js
export default {
namespaced: true,
state: () => ({
sidebarOpened: true
}),
mutations: {
triggerSidebarOpened(state) {
state.sidebarOpened = !state.sidebarOpened
}
},
actions: {}
}
在store/index.js中进行导入:
index.js
import app from './modules/app'
export default createStore({
getters,
modules: {
user,
app
}
})
在store/getters.js里创建一个快捷访问;
getters.js
const getters = {
...
sidebarOpened: (state) => state.app.sidebarOpened
}
export default getters
到这里我们数据源的问题就解决了;
现在来添加一个组件:
新建components/hamburger/index.js
index.js
<template>
<div class="hamburger-container" @click="toggleClick">
<svg-icon class="hamburger" :icon="icon"></svg-icon>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// 点击事件
const toggleClick = () => {
store.commit('app/triggerSidebarOpened')
}
const icon = computed(() => {
return store.getters.sidebarOpened ? 'hamburger-opened' : 'hamburger-closed'
})
</script>
<style lang="scss" scoped>
.hamburger-container {
height: 50px;
padding: 0 20px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
/**hover 动画 */
transition: backgrount 0.5s;
&:hover {
background: rgba(0, 0, 0, 0.1);
}
}
</style>
在layout/components/Navbar.vue中导入组件:
Navbar.vue
<template>
<div class="navbar">
<div class="left-menu">
<Hamburger></Hamburger>
</div>
</div>
</template>
在components/Sidebar/SidebarMenu.vue中写入:
SidebarMenu.vue
<template>
<el-menu
:collapse="!$store.getters.sidebarOpened">
</el-menu>
<template>
Sidebar占位头像实现
在layout/components/Sidebar/index.vue中修改;
index.vue
<template>
<div>
<div class="logo-container">
<el-avatar
size="44"
shape="square"
src="https://m.imooc.com/static/wap/static/common/img/logo-small@2x.png"
></el-avatar>
<div class="logo-title" v-if="$store.getters.sidebarOpened">mj-admin</div>
</div>
<el-scrollerbar>
<SidebarMenu />
</el-scrollerbar>
</div>
</template>
<script setup>
import SidebarMenu from './SidebarMenu.vue'
</script>
<style lang="scss" scoped>
@import '~@/styles/sidebar.scss';
.logo-container {
height: 44px;
padding: 10px 0 22px 0;
display: flex;
align-items: center;
justify-content: center;
.logo-title {
margin-left: 10px;
color: #fff;
font-weight: 600;
font-size: large;
}
/**头像灰色背景处理 */
.el-avatar {
--el-avatar-bg-color: none;
--el-avatar-background-color: none;
}
}
</style>
在styles/variable.scss中定义一个transition时间常量;sidebar的transition还没加;
给sidebar加上动画效果
在variable.scss中增加一个常量:
variable.scss
$sidebarDuration: 0.28s;
index.vue
.sidebar-container {
transition: width $sidebarDuration;
}
.main-container {
transition: margin-left $sidebarDuration;
}
.hideSidebar{
.fixed-header{
transition: width $sidebarDuration;
}
}
// menu菜单的border去掉
.el-menu {
border: none;
}
补充知识点
什么是:v-deep?
scoped属性是HTML5中的新属性,<style标签加上了scoped>属性时,实现样式私有化模块化;
scoped会给DOM节点加一个不重复data属性来表示他的唯一性;
:deep()改变css解析时私有属性的位置;常用于修改组件库样式;
transition
transition属性可以用来实现动画效果;它有四个属性:
- transition-property:指定CSS属性的name,transition效果;
- transition-duration:指定多少秒多少毫秒才能完成;
- transiontion-timing-function:指定transition效果的转速曲线;
- transition-delay:定义transition效果开始的时候;
其他问题
- 我在实现的时候发现样式总有问题,后来发现老师的 "element-plus": "^2.0.4",而我的是v1版本;
- 还有折叠的样式,菜单折叠的时候总是显示出title;老师封装了一个MenuItem,我没用这个,直接改成,样式问题就解决了:
js
<svg-icon :icon="route.meta.icon"></svg-icon>
<template #title>{{ route.meta.title }}</template>
组件状态驱动的动态css值
vue3.2更新中,还有一个很重要的更新,那就是组件状态驱动的动态css值 ,详情🔎
通过v-bind指令把响应式数据绑定在css上;
接下来我们就用v-bind为logo-container指定一下高度:
在Sidebar/index.vue中修改:
index.vue
<template>
<el-avatar
:size="logoHeight">
</el-avatar>
</template>
<script setup>
const logoHeight = 44
</script>
<style>
.logo-container {
height: v-bind(logoHeight) + 'px';
}
</style>
动态面包屑
分成3个步骤来实现:
1.创建、渲染基本的面包屑组件;
2.计算面包屑结构属性;
3.根据数据渲染动态面包屑内容;
动态面包屑的实现
在Breadcrumb/index.vue中完整代码:
index.vue
<template>
<el-breadcrumb class="breadcrumb" separator="/">
<el-breadcrumb-item
v-for="(item, index) in breadcrumbData"
:key="item.path"
>
<!--不可点击-->
<span class="no-redirect" v-if="index === breadcrumbData.length - 1">{{
item.meta.title
}}</span>
<!--可点击-->
<span class="redirect" @click="onLinkClick(item)" v-else>{{
item.meta.title
}}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { watch, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
// 生成数组数据
const breadcrumbData = ref([])
const getBreadcrumbData = () => {
console.log(route.matched)
breadcrumbData.value = route.matched.filter(
(item) => item.meta && item.meta.title
)
}
// 监听路由变化
const route = useRoute()
watch(
route,
() => {
getBreadcrumbData()
},
{
immediate: true
}
)
// 跳转点击事件
const router = useRouter()
const onLinkClick = (item) => {
router.push(item.path)
}
// 将来需要主题替换,所以hover的颜色我们设置为主色
const store = useStore()
const linkHoverColor = ref(store.getters.cssVar.menuBg)
</script>
<style lang="scss" scoped>
.breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
.redirect {
cursor: pointer;
color: #666;
font-size: 14px;
}
.redirect:hover {
color: v-bind(linkHoverColor);
}
}
</style>
为面包屑增加动画样式
1.创建styles/index.scss
styles/index.scss
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.5s;
}
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transition: translageX(20px);
}
.breadcrumb-leave-active {
position: absolute;
}
在Breadcrumb/index.vue多加一层transition;
index.vue
<template>
<el-breadcrumb class="breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item></el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>