本节在navbar添加菜单收缩按钮,收缩状态接入vuex 并对store做个session storage持久化, 保持之前收缩状态

本节效果
展开时
image.png
session storage
image.png

收缩时
image.png
session storage
image.png

2-1 创建菜单收缩按钮组件

组件没什么内容 主要是svg图片和样式 一个props 激活状态 一个切换状态函数

src/components/Hambuger/index.vue

  1. <template>
  2. <div
  3. class="hamburger-container"
  4. style="padding: 0 15px"
  5. @click="toggleClick"
  6. >
  7. <svg
  8. :class="{'is-active': isActive}"
  9. class="hamburger"
  10. viewBox="0 0 1024 1024"
  11. xmlns="http://www.w3.org/2000/svg"
  12. width="64"
  13. height="64"
  14. >
  15. <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" />
  16. </svg>
  17. </div>
  18. </template>
  19. <script lang="ts">
  20. import { defineComponent } from 'vue'
  21. export default defineComponent({
  22. name: 'Hambuger',
  23. props: {
  24. isActive: {
  25. type: Boolean,
  26. default: false
  27. }
  28. },
  29. // vue3新增 emits选项
  30. // 1.起到声明的作用 让你知道当前组件 会emit出去哪些事件
  31. // 2.可以对emit的参数进行效验 通过就可以emit出去 否则不能emit
  32. // 具体使用说明看文档 https://v3.cn.vuejs.org/api/options-data.html#emits
  33. emits: ['toggleClick'], // vue3 emits声明列表
  34. setup (props, { emit }) {
  35. const toggleClick = () => {
  36. emit('toggleClick')
  37. }
  38. return {
  39. toggleClick
  40. }
  41. }
  42. })
  43. </script>
  44. <style lang="scss" scoped>
  45. .hamburger-container {
  46. line-height: 46px;
  47. height: 100%;
  48. float: left;
  49. cursor: pointer;
  50. transition: background .3s;
  51. -webkit-tap-highlight-color: transparent;
  52. &:hover {
  53. background: rgba(0, 0, 0, .025);
  54. }
  55. }
  56. .hamburger {
  57. display: inline-block;
  58. vertical-align: middle;
  59. width: 20px;
  60. height: 20px;
  61. }
  62. .hamburger.is-active {
  63. transform: rotate(180deg);
  64. }
  65. </style>

navbar里导入组件

image.png

  1. <template>
  2. <div class="navbar">
  3. <hambuger @toggleClick="toggleSidebar" :is-active="true"/>
  4. <breadcrumb />
  5. </div>
  6. </template>
  7. <script lang="ts">
  8. import { defineComponent } from 'vue'
  9. import Breadcrumb from '@/components/Breadcrumb/index.vue'
  10. import Hambuger from '@/components/Hambuger/index.vue'
  11. export default defineComponent({
  12. name: 'Navbar',
  13. components: {
  14. Breadcrumb,
  15. Hambuger
  16. },
  17. setup() {
  18. const toggleSidebar = () => {
  19. console.log('click')
  20. }
  21. return {
  22. toggleSidebar
  23. }
  24. }
  25. })
  26. </script>

2-2 接入vuex

这块儿会有 关于store actions mutations getters 类型声明定义的方式,都是根据文档定义的 类型定义不需要想太多为什么,知道怎么配就行

创建module

vuex 里面module src/store/modules存放module文件

创建src/store/modules/app.ts app module 针对一些后台设置状态存储 比如收缩状态 或配置状态
image.png
src/store/modules/app.ts

  1. import { ActionTree, Module, MutationTree } from 'vuex'
  2. import { IRootState } from '../index' // 全局状态 root state 从src/store/index.ts里定义导出
  3. // 定义app里state类型
  4. export interface IAppState {
  5. sidebar: { // 定义sidebar相关状态
  6. opened: boolean // 菜单导航展开时true 收缩时false
  7. }
  8. }
  9. // 定义mutations
  10. const mutations: MutationTree<IAppState> = {
  11. TOGGLE_SIDEBAR(state) {
  12. // 这块儿就会有类型提示 写state.sidebar 都会提示
  13. state.sidebar.opened = !state.sidebar.opened
  14. }
  15. }
  16. // 定义actions
  17. const actions: ActionTree<IAppState, IRootState> = {
  18. toggleSidebar({ commit }) { // 切换sidebar 收缩状态
  19. commit('TOGGLE_SIDEBAR')
  20. }
  21. // test_action({ commit }, payload: string) { // action如果有payload自己定义类型就行
  22. // }
  23. }
  24. // 定义module
  25. const app: Module<IAppState, IRootState> = {
  26. // 用了命名空间 store.dispatch('模块名/action函数名')
  27. // 获取state就要 store.state.app.sidebar (store.state.模块名.状态)
  28. namespaced: true,
  29. state: {
  30. sidebar: { // 定义sidebar相关状态
  31. opened: true // 菜单导航展开时true 收缩时false
  32. }
  33. },
  34. mutations,
  35. actions
  36. }
  37. export default app

之后的vuex module定义 按这个写法就可以

2-3 定义全局getters

用到的一些模块状态 通过getters做给筛选获取 store.getters.sidebar

src/store/getters.ts

  1. import { GetterTree } from 'vuex'
  2. import { IRootState } from './index'
  3. // 定义全局getters
  4. const getters: GetterTree<IRootState, IRootState> = {
  5. sidebar: (state) => state.app.sidebar
  6. }
  7. export default getters

2-4 store.ts里声明module

store里 为了我们在组件里使用useStore(store.state)时 有类型提示需要做些配置 都是官方文档教程

vuex useStore封装文档说明
src/store/index.ts

  1. import { InjectionKey } from 'vue'
  2. import { createStore, Store, useStore as baseUseStore } from 'vuex'
  3. import app, { IAppState } from '@/store/modules/app' // 导入模块
  4. import getters from './getters' // 导入getters
  5. // 声明全局状态类型,主要就是我们定义的模块 这样store.state.app才会有类型提示
  6. export interface IRootState {
  7. app: IAppState;
  8. }
  9. // 通过下面方式使用 TypeScript 定义 store 能在使用时正确地为 store 提供类型声明。
  10. // https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage
  11. // eslint-disable-next-line symbol-description
  12. export const key: InjectionKey<Store<IRootState>> = Symbol()
  13. // 这个key算是个密钥 入口main.ts需要用到 vue.use(store, key) 才能正常使用
  14. // 对于getters在组件使用时没有类型提示
  15. // 有人提交了pr #1896 为getters创建泛型 应该还未发布
  16. // https://github.com/vuejs/vuex/pull/1896
  17. // 代码pr内容详情
  18. // https://github.com/vuejs/vuex/pull/1896/files#diff-093ad82a25aee498b11febf1cdcb6546e4d223ffcb49ed69cc275ac27ce0ccce
  19. export default createStore<IRootState>({
  20. getters,
  21. modules: { // 注册模块
  22. app
  23. }
  24. })
  25. // 定义自己的 `useStore` 组合式函数
  26. // https://next.vuex.vuejs.org/zh/guide/typescript-support.html#%E7%AE%80%E5%8C%96-usestore-%E7%94%A8%E6%B3%95
  27. export function useStore () {
  28. return baseUseStore(key)
  29. }

对于getters 没有类型提示 有人提了pr已通过 好像还没发布https://github.com/vuejs/vuex/pull/1896

2-5 入口main.ts store里注入key

key可以认为是store加密解密的密钥
image.png

2-6 使用store 里sidebar状态

navbar组件里接入store.getters.sidebar

image.png
src/layout/components/Navbar.vue

useStore 使用的是我们自定义的配置好的 import { useStore } from ‘@/store’

类型提示:
image.pngimage.png

  1. <template>
  2. <div class="navbar">
  3. <hambuger @toggleClick="toggleSidebar" :is-active="sidebar.opened"/>
  4. <breadcrumb />
  5. </div>
  6. </template>
  7. <script lang="ts">
  8. import { defineComponent, computed } from 'vue'
  9. import Breadcrumb from '@/components/Breadcrumb/index.vue'
  10. import Hambuger from '@/components/Hambuger/index.vue'
  11. import { useStore } from '@/store/index'
  12. export default defineComponent({
  13. name: 'Navbar',
  14. components: {
  15. Breadcrumb,
  16. Hambuger
  17. },
  18. setup() {
  19. // 使用我们自定义的useStore 具备类型提示
  20. // store.state.app.sidebar 对于getters里的属性没有类型提示
  21. const store = useStore()
  22. const toggleSidebar = () => {
  23. store.dispatch('app/toggleSidebar')
  24. }
  25. // 从getters中获取sidebar
  26. const sidebar = computed(() => store.getters.sidebar)
  27. return {
  28. toggleSidebar,
  29. sidebar
  30. }
  31. }
  32. })
  33. </script>

sidebar组件里接入store.getters.sidebar

sidebar之前用的收缩状态是组件里自定义的 改动不大 导入的ref API 现在不用了 可以删了

image.png
src/layout/components/Sidebar/index.vue

  1. <template>
  2. <div>
  3. <el-menu
  4. class="sidebar-container-menu"
  5. mode="vertical"
  6. :default-active="activeMenu"
  7. :background-color="scssVariables.menuBg"
  8. :text-color="scssVariables.menuText"
  9. :active-text-color="scssVariables.menuActiveText"
  10. :collapse="isCollapse"
  11. :collapse-transition="true"
  12. >
  13. <sidebar-item
  14. v-for="route in menuRoutes"
  15. :key="route.path"
  16. :item="route"
  17. :base-path="route.path"
  18. />
  19. </el-menu>
  20. </div>
  21. </template>
  22. <script lang="ts">
  23. import { defineComponent, computed, ref } from 'vue'
  24. import { useRoute } from 'vue-router'
  25. import variables from '@/styles/variables.scss'
  26. import { routes } from '@/router'
  27. import SidebarItem from './SidebarItem.vue'
  28. import { useStore } from '@/store'
  29. export default defineComponent({
  30. name: 'Sidebar',
  31. components: {
  32. SidebarItem
  33. },
  34. setup() {
  35. const route = useRoute()
  36. const store = useStore()
  37. // 根据路由路径 对应 当前激活的菜单
  38. const activeMenu = computed(() => {
  39. const { path } = route
  40. return path
  41. })
  42. // scss变量
  43. const scssVariables = computed(() => variables)
  44. // 展开收起状态 稍后放store 当前是展开就让它收起
  45. const isCollapse = computed(() => !store.getters.sidebar.opened)
  46. // 渲染路由
  47. const menuRoutes = computed(() => routes)
  48. return {
  49. // ...toRefs(variables), // 不有toRefs原因 缺点variables里面变量属性来源不明确
  50. scssVariables,
  51. isCollapse,
  52. activeMenu,
  53. menuRoutes
  54. }
  55. }
  56. })
  57. </script>

2-7 测试

image.png
刷新又恢复了 如果我想保留刷新前收缩状态 接下来就需要对vuex做持久化
image.png

本节源码参考

https://gitee.com/brolly/vue3-element-admin/commit/45b822c9fff8cd764a53c23a037390d9d26a61d8