DFS深度优先遍历-菜单的扁平化

901 阅读1分钟

前言

最近在写一个基于element-plus的后台管理系统,我需要在切换菜单时,动态的修改面包屑导航。但是对于如何处理嵌套数据有点疑问,好在最后解决了。

面包屑.gif

菜单结构

src/store/modules/app/index.ts

interface state {
  bread: string[]
  menu: menuData[]
}
const state: state = {
  //面包屑
  bread: [],
  //菜单数据
  menu: [
    {
      id: 1,
      pid: 0,
      icon: 'iconfont icon-yibiao',
      index: '/dashboard',
      title: '仪表盘',
      sub: []
    },
    {
      id: 2,
      pid: 0,
      icon: 'iconfont icon-moban',
      index: 'template',
      title: '模板',
      sub: [
        {
          id: 3,
          pid: 2,
          index: '/template/leven1',
          title: 'leven1',
          sub: []
        },
        {
          id: 4,
          pid: 2,
          index: '',
          title: '三级菜单',
          sub: [
            {
              id: 5,
              pid: 4,
              index: '/template/leven1/leven3',
              title: 'leven3',
              sub: []
            },
            {
              id: 6,
              pid: 4,
              index: '/template/leven1/leven4',
              title: 'leven6',
              sub: []
            }
          ]
        }
      ]
    }
  ]
}
const getters = {}
const actions = {}
const mutations = {
  coverBread(state: state, payload: string[]) {
    state.bread = payload
  }
}
export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

菜单组件

src/layouts/components/side-bar/index.vue
关注80-94行,处理方法是以path为key,对menu嵌套对象进行扁平化处理。

<template>
  <div class="aside">
    <div :class="['logo', isCollapse ? 'collapse-logo' : '']">
      <img src="@/assets/images/logo.png" />
    </div>
    <el-menu
      class="el-menu-vertical"
      :collapse="isCollapse"
      background-color="#303133"
      text-color="#fff"
      active-text-color="#ffd04b"
      :router="true"
      unique-opened
      :default-active="activePath"
    >
      <template v-for="(item, index) in menu" :key="index">
        <el-submenu v-if="item.sub.length" :index="item.title">
          <template #title>
            <i :class="item.icon"></i>
            <span class="menu-title">{{ item.title }}</span>
          </template>
          <template v-for="(item, index) in item.sub" :key="index">
            <el-submenu v-if="item.sub.length" :index="item.title">
              <template #title>
                <i :class="item.icon"></i>
                <span class="menu-title">{{ item.title }}</span>
              </template>
              <el-menu-item
                v-for="item in item.sub"
                :key="item.index"
                :index="item.index"
                class="menu-item-sub"
                >{{ item.title }}
              </el-menu-item>
            </el-submenu>
            <el-menu-item :index="item.index" v-else class="menu-item"
              >{{ item.title }}
            </el-menu-item>
          </template>
        </el-submenu>
        <el-menu-item v-else :index="item.index">
          <i :class="item.icon"></i>
          <template #title>
            <span class="menu-title">{{ item.title }}</span>
          </template>
        </el-menu-item>
      </template>
    </el-menu>
    <i
      :class="['check-menu-icon', isCollapse ? 'el-icon-s-fold' : 'el-icon-s-unfold']"
      @click="isCollapse = !isCollapse"
    ></i>
  </div>
</template>

<script lang="ts">
  /* eslint-disable no-undef */
  import { computed, defineComponent, ref, reactive } from 'vue'
  import { useRoute } from 'vue-router'
  import { useStore } from 'vuex'

  export default defineComponent({
    name: 'appSideBar',
    components: {},
    setup() {
      const store = useStore()
      const route = useRoute()
      let isCollapse = ref(false)
      let menu = computed(() => store.state.app.menu)
      let dfsBreadData = computed(() => dfsMenu(menu.value)) //扁平化后的菜单,用于面包屑
      const activePath = computed(() => {
        const { meta, path } = route
        console.log('path', path)
        dfsBreadData.value[path] && store.commit('app/coverBread', dfsBreadData.value[path])
        //切换菜单页面下级页面,菜单hover状态应该不变
        if (meta.activeMenu) {
          return meta.activeMenu
        }
        return path
      })
      function dfsMenu(menu: menuData[]): any {
        let result: any = {}
        menu.forEach((item: menuData) => dfs(item, []))
        return result
        // 深度优先遍历
        function dfs(menu: menuData, path: string[]) {
          if (!menu.sub.length) {
            // 用路径做key,方便查找 path是title组成的数组.
            result[menu.index] = [...path, menu.title]
          } else {
            path.push(menu.title) //把当前title push进数组
            menu.sub.forEach((menu: menuData) => dfs(menu, path)) //递归
          }
        }
      }
      return {
        menu,
        dfsBreadData,
        isCollapse,
        activePath
      }
    }
  })
</script>

结语

扁平化之后的数据,以路由path为key,可以很快捷的进行面包屑赋值。

{
    "/dashboard": [
        "仪表盘"
    ],
    "/template/leven1": [
        "模板",
        "leven1"
    ],
    "/template/leven1/leven3": [
        "模板",
        "三级菜单",
        "leven3"
    ],
    "/template/leven1/leven4": [
        "模板",
        "三级菜单",
        "leven6"
    ]
}

如果觉得这篇文章对您有帮助的话,欢迎点赞评论加转发。
代码实例
首发于语雀文档@is_tao