本节在navbar添加菜单收缩按钮,收缩状态接入vuex 并对store做个session storage持久化, 保持之前收缩状态
本节效果
展开时
session storage
2-1 创建菜单收缩按钮组件
组件没什么内容 主要是svg图片和样式 一个props 激活状态 一个切换状态函数
src/components/Hambuger/index.vue
<template><divclass="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#emitsemits: ['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}}// 定义mutationsconst mutations: MutationTree<IAppState> = {TOGGLE_SIDEBAR(state) {// 这块儿就会有类型提示 写state.sidebar 都会提示state.sidebar.opened = !state.sidebar.opened}}// 定义actionsconst actions: ActionTree<IAppState, IRootState> = {toggleSidebar({ commit }) { // 切换sidebar 收缩状态commit('TOGGLE_SIDEBAR')}// test_action({ commit }, payload: string) { // action如果有payload自己定义类型就行// }}// 定义moduleconst 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'// 定义全局gettersconst 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-descriptionexport 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-093ad82a25aee498b11febf1cdcb6546e4d223ffcb49ed69cc275ac27ce0ccceexport 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%95export 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中获取sidebarconst 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-menuclass="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-itemv-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 } = routereturn 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

