本节在navbar添加菜单收缩按钮,收缩状态接入vuex 并对store做个session storage持久化, 保持之前收缩状态
本节效果
展开时
session storage
2-1 创建菜单收缩按钮组件
组件没什么内容 主要是svg图片和样式 一个props 激活状态 一个切换状态函数
src/components/Hambuger/index.vue
<template>
<div
class="hamburger-container"
style="padding: 0 15px"
@click="toggleClick"
>
<svg
:class="{'is-active': isActive}"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Hambuger',
props: {
isActive: {
type: Boolean,
default: false
}
},
// vue3新增 emits选项
// 1.起到声明的作用 让你知道当前组件 会emit出去哪些事件
// 2.可以对emit的参数进行效验 通过就可以emit出去 否则不能emit
// 具体使用说明看文档 https://v3.cn.vuejs.org/api/options-data.html#emits
emits: ['toggleClick'], // vue3 emits声明列表
setup (props, { emit }) {
const toggleClick = () => {
emit('toggleClick')
}
return {
toggleClick
}
}
})
</script>
<style lang="scss" scoped>
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025);
}
}
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>
navbar里导入组件
<template>
<div class="navbar">
<hambuger @toggleClick="toggleSidebar" :is-active="true"/>
<breadcrumb />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Breadcrumb from '@/components/Breadcrumb/index.vue'
import Hambuger from '@/components/Hambuger/index.vue'
export default defineComponent({
name: 'Navbar',
components: {
Breadcrumb,
Hambuger
},
setup() {
const toggleSidebar = () => {
console.log('click')
}
return {
toggleSidebar
}
}
})
</script>
2-2 接入vuex
这块儿会有 关于store actions mutations getters 类型声明定义的方式,都是根据文档定义的 类型定义不需要想太多为什么,知道怎么配就行
创建module
vuex 里面module src/store/modules存放module文件
创建src/store/modules/app.ts app module 针对一些后台设置状态存储 比如收缩状态 或配置状态
src/store/modules/app.ts
import { ActionTree, Module, MutationTree } from 'vuex'
import { IRootState } from '../index' // 全局状态 root state 从src/store/index.ts里定义导出
// 定义app里state类型
export interface IAppState {
sidebar: { // 定义sidebar相关状态
opened: boolean // 菜单导航展开时true 收缩时false
}
}
// 定义mutations
const mutations: MutationTree<IAppState> = {
TOGGLE_SIDEBAR(state) {
// 这块儿就会有类型提示 写state.sidebar 都会提示
state.sidebar.opened = !state.sidebar.opened
}
}
// 定义actions
const actions: ActionTree<IAppState, IRootState> = {
toggleSidebar({ commit }) { // 切换sidebar 收缩状态
commit('TOGGLE_SIDEBAR')
}
// test_action({ commit }, payload: string) { // action如果有payload自己定义类型就行
// }
}
// 定义module
const app: Module<IAppState, IRootState> = {
// 用了命名空间 store.dispatch('模块名/action函数名')
// 获取state就要 store.state.app.sidebar (store.state.模块名.状态)
namespaced: true,
state: {
sidebar: { // 定义sidebar相关状态
opened: true // 菜单导航展开时true 收缩时false
}
},
mutations,
actions
}
export default app
之后的vuex module定义 按这个写法就可以
2-3 定义全局getters
用到的一些模块状态 通过getters做给筛选获取 store.getters.sidebar
src/store/getters.ts
import { GetterTree } from 'vuex'
import { IRootState } from './index'
// 定义全局getters
const getters: GetterTree<IRootState, IRootState> = {
sidebar: (state) => state.app.sidebar
}
export default getters
2-4 store.ts里声明module
store里 为了我们在组件里使用useStore(store.state)时 有类型提示需要做些配置 都是官方文档教程
vuex useStore封装文档说明
src/store/index.ts
import { InjectionKey } from 'vue'
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import app, { IAppState } from '@/store/modules/app' // 导入模块
import getters from './getters' // 导入getters
// 声明全局状态类型,主要就是我们定义的模块 这样store.state.app才会有类型提示
export interface IRootState {
app: IAppState;
}
// 通过下面方式使用 TypeScript 定义 store 能在使用时正确地为 store 提供类型声明。
// https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage
// eslint-disable-next-line symbol-description
export const key: InjectionKey<Store<IRootState>> = Symbol()
// 这个key算是个密钥 入口main.ts需要用到 vue.use(store, key) 才能正常使用
// 对于getters在组件使用时没有类型提示
// 有人提交了pr #1896 为getters创建泛型 应该还未发布
// https://github.com/vuejs/vuex/pull/1896
// 代码pr内容详情
// https://github.com/vuejs/vuex/pull/1896/files#diff-093ad82a25aee498b11febf1cdcb6546e4d223ffcb49ed69cc275ac27ce0ccce
export default createStore<IRootState>({
getters,
modules: { // 注册模块
app
}
})
// 定义自己的 `useStore` 组合式函数
// https://next.vuex.vuejs.org/zh/guide/typescript-support.html#%E7%AE%80%E5%8C%96-usestore-%E7%94%A8%E6%B3%95
export function useStore () {
return baseUseStore(key)
}
对于getters 没有类型提示 有人提了pr已通过 好像还没发布https://github.com/vuejs/vuex/pull/1896
2-5 入口main.ts store里注入key
2-6 使用store 里sidebar状态
navbar组件里接入store.getters.sidebar
src/layout/components/Navbar.vue
useStore 使用的是我们自定义的配置好的 import { useStore } from ‘@/store’
类型提示:
<template>
<div class="navbar">
<hambuger @toggleClick="toggleSidebar" :is-active="sidebar.opened"/>
<breadcrumb />
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import Breadcrumb from '@/components/Breadcrumb/index.vue'
import Hambuger from '@/components/Hambuger/index.vue'
import { useStore } from '@/store/index'
export default defineComponent({
name: 'Navbar',
components: {
Breadcrumb,
Hambuger
},
setup() {
// 使用我们自定义的useStore 具备类型提示
// store.state.app.sidebar 对于getters里的属性没有类型提示
const store = useStore()
const toggleSidebar = () => {
store.dispatch('app/toggleSidebar')
}
// 从getters中获取sidebar
const sidebar = computed(() => store.getters.sidebar)
return {
toggleSidebar,
sidebar
}
}
})
</script>
sidebar组件里接入store.getters.sidebar
sidebar之前用的收缩状态是组件里自定义的 改动不大 导入的ref API 现在不用了 可以删了
src/layout/components/Sidebar/index.vue
<template>
<div>
<el-menu
class="sidebar-container-menu"
mode="vertical"
:default-active="activeMenu"
:background-color="scssVariables.menuBg"
:text-color="scssVariables.menuText"
:active-text-color="scssVariables.menuActiveText"
:collapse="isCollapse"
:collapse-transition="true"
>
<sidebar-item
v-for="route in menuRoutes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
import { routes } from '@/router'
import SidebarItem from './SidebarItem.vue'
import { useStore } from '@/store'
export default defineComponent({
name: 'Sidebar',
components: {
SidebarItem
},
setup() {
const route = useRoute()
const store = useStore()
// 根据路由路径 对应 当前激活的菜单
const activeMenu = computed(() => {
const { path } = route
return path
})
// scss变量
const scssVariables = computed(() => variables)
// 展开收起状态 稍后放store 当前是展开就让它收起
const isCollapse = computed(() => !store.getters.sidebar.opened)
// 渲染路由
const menuRoutes = computed(() => routes)
return {
// ...toRefs(variables), // 不有toRefs原因 缺点variables里面变量属性来源不明确
scssVariables,
isCollapse,
activeMenu,
menuRoutes
}
}
})
</script>
2-7 测试
刷新又恢复了 如果我想保留刷新前收缩状态 接下来就需要对vuex做持久化
本节源码参考
https://gitee.com/brolly/vue3-element-admin/commit/45b822c9fff8cd764a53c23a037390d9d26a61d8