Просмотр исходного кода

improve: 菜单及样式优化

From-wh 2 лет назад
Родитель
Сommit
efddcd0d0c

+ 4 - 0
template/admin/src/components/main/components/header-search/index.vue

@@ -22,6 +22,7 @@
 <style>
 .search .ivu-select-selection {
   margin-right: 20px;
+  border-radius: 30px;
 }
 .search .ivu-select-visible .ivu-select-selection {
   box-shadow: unset !important;
@@ -33,6 +34,9 @@
 .search .select .ivu-select-item {
   font-size: 14px !important;
 }
+.ivu-select-input{
+  background-color: rgba(255,255,255,0.3);
+}
 </style>
 <script>
 import { menusListApi } from '@/api/account';

+ 3 - 1
template/admin/src/components/main/components/side-menu/collapsed-menu.vue

@@ -66,7 +66,6 @@ export default {
   },
   methods: {
     handleClick(name) {
-      console.log(name, this.activeMenuPath);
       this.$emit('on-click', name, this.activeMenuPath);
     },
     handleMousemove(event, children) {
@@ -94,4 +93,7 @@ export default {
   justify-content space-between
   width 100%
 }
+.drop-menu-a /deep/ .ivu-dropdown-rel {
+  min-width: 75px !important;
+}
 </style>

+ 40 - 136
template/admin/src/components/main/components/side-menu/side-menu.vue

@@ -6,7 +6,7 @@
       <div class="parent-menu">
         <Menu
           ref="menu"
-          :active-name="activeMenuPath"
+          :active-name="headerName"
           :open-names="openedNames"
           :accordion="accordion"
           :theme="theme"
@@ -14,7 +14,7 @@
           @on-open-change="openNameData"
           @on-select="handleSelect"
         >
-          <template v-for="item in menuList">
+          <template v-for="item in header">
             <template>
               <menu-item :name="item.path" :key="`menu${item.path}`"
                 ><common-icon :type="item.icon || ''" /><span class="title">{{ item.title }}</span></menu-item
@@ -23,20 +23,19 @@
           </template>
         </Menu>
       </div>
-
-      <div class="child-menu" v-if="childList.length">
-        <div class="cat-name">{{ catName }}</div>
+      <div class="child-menu" v-show="sider.length">
+        <div class="cat-name">{{ oneMenuName }}</div>
         <Menu
           ref="childMenu"
           :active-name="activePath"
-          :open-names="openMenus"
+          :open-names="openNames"
           :accordion="accordion"
           :theme="theme"
           width="145px"
           @on-open-change="openChildNameData"
           @on-select="handleChildSelect"
         >
-          <template v-for="item in childList">
+          <template v-for="item in sider">
             <template v-if="item.auth === undefined">
               <template v-if="item.children && item.children.length >= 1">
                 <side-menu-item
@@ -72,7 +71,7 @@
           v-if="item.children && item.children.length > 0"
           @on-click="collHandleSelect"
           :hide-title="true"
-          :activeMenuPath="activeMenuPath"
+          :activeMenuPath="headerName"
           :root-icon-size="rootIconSize"
           :icon-size="iconSize"
           :theme="theme"
@@ -83,7 +82,7 @@
           <a
             @click="collHandleSelect(item)"
             class="drop-menu-a"
-            :class="{ on: item.path == activeMenuPath }"
+            :class="{ on: item.path == headerName }"
             :style="{ textAlign: 'center' }"
             ><common-icon :color="textColor" :type="item.icon || (item.children && item.children[0].icon)" />
             <span class="title">{{ item.title }}</span>
@@ -97,7 +96,7 @@
 import SideMenuItem from './side-menu-item.vue';
 import CollapsedMenu from './collapsed-menu.vue';
 import { getUnion } from '@/libs/tools';
-import { mapState } from 'vuex';
+import { mapState, mapGetters } from 'vuex';
 import mixin from './mixin';
 import itemMixin from './item-mixin';
 import { setCookies } from '@/libs/util';
@@ -134,10 +133,10 @@ export default {
       default: 16,
     },
     accordion: Boolean,
-    openNames: {
-      type: Array,
-      default: () => [],
-    },
+    // openNames: {
+    //   type: Array,
+    //   default: () => [],
+    // },
   },
   data() {
     return {
@@ -145,74 +144,49 @@ export default {
       childList: [],
       activeChildName: '',
       childOptions: [],
-      activePath: '',
+      // activePath: '',
       activeMenuPath: '',
       catName: '',
     };
   },
   computed: {
     ...mapState('menus', ['openMenus']),
+    ...mapState('menu', ['activePath', 'openNames', 'header', 'headerName', 'sider', 'oneMenuName']),
+    ...mapGetters('menu', ['filterSider']),
+
     textColor() {
       return this.theme === 'dark' ? '#fff' : '#495060';
     },
   },
   watch: {
-    activeName(name) {
-      if (this.accordion) this.openedNames = this.getOpenedNamesByActiveName();
-      else this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName());
-      // this.handleSelect(this.activeName);
-    },
-    openNames(newNames) {
-      this.openedNames = newNames;
-    },
     openedNames() {
       this.$nextTick(() => {
         this.$refs.menu.updateOpened();
+        this.$refs.childMenu.updateActiveName();
+      });
+    },
+    oneMenuName() {
+      this.$nextTick(() => {});
+    },
+    activePath() {
+      this.$nextTick(() => {
+        console.log();
+        this.$refs.childMenu.updateOpened();
+        this.$refs.childMenu.updateActiveName();
       });
     },
     collapsed(val) {
       if (!val) {
-        console.log(this.$route, this.findParentById(this.menuList, this.$route.path));
-        this.handleSelect(this.findParentById(this.menuList, this.$route.path));
         this.$nextTick(() => {
+          this.$refs.menu.updateOpened();
+          this.$refs.childMenu.updateActiveName();
           this.$refs.childMenu.updateOpened();
         });
       }
     },
-    $route(newRoute) {},
-    $route: {
-      handler(newRoute) {
-        console.log(newRoute, 'newRoutenewRoute');
-        this.activePath = newRoute.path;
-        // this.activeMenuPath = newRoute.matched[0].path;
-        if (this.collapsed) {
-          sessionStorage.setItem('menuActive', newRoute.matched[0].path);
-          this.activeMenuPath = newRoute.matched[0].path;
-        }
-        this.handleUpdateMenuState();
-      },
-      immediate: true,
-    },
-  },
-  mounted() {
-    console.log(this.menuList);
-    this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName());
-    if (sessionStorage.getItem('menuActive')) {
-      this.activeMenuPath = sessionStorage.getItem('menuActive');
-      this.catName = sessionStorage.getItem('menuActiveTitle');
-      this.getChildrenList(sessionStorage.getItem('menuActive'));
-    } else {
-      this.handleSelect(this.openedNames[0]);
-    }
   },
+  mounted() {},
   methods: {
-    getChildrenList(path) {
-      this.menuList.map((e) => {
-        if (e.path === path) {
-          this.childList = e.children || [];
-        }
-      });
-    },
     handleCollpasedChange(state) {
       console.log(state);
       // this.collapsed = state;
@@ -220,90 +194,35 @@ export default {
       // setCookies('collapsed', state);
     },
     handleSelect(name, type) {
-      this.childOptions = [];
+      console.log(name, 'name');
       this.menuList.map((e) => {
         if (e.path === name) {
           if (e.children && e.children.length) {
             this.jump(e.children);
             this.catName = e.title;
-            // this.activeMenuPath = e.path;
-            this.childList = e.children || [];
-            sessionStorage.setItem('menuActive', e.path);
-            sessionStorage.setItem('menuActiveTitle', e.title);
-            this.activeMenuPath = e.path;
-            // this.activeChildName = e.children[0].path;
-            // this.childOptions = [e.children[0].path];
-            this.$store.commit('menus/childMenuList', this.childList);
+          } else {
             // if (!type) {
-            //   this.$emit('on-select', e.children[0].path);
+            //   this.$emit('on-select', name);
             // }
-          } else {
-            if (!type) {
-              this.$emit('on-select', name);
-            }
-            this.activeMenuPath = e.path;
-
-            this.childList = [];
-            this.$store.commit('menus/childMenuList', []);
           }
         }
       });
     },
+    handleChildSelect(name) {
+      this.turnToPage(name);
+    },
     jump(data) {
       if (data[0].children && data[0].children.length) {
         this.jump(data[0].children);
       } else {
-        this.catName = data[0].title;
-        // this.activeMenuPath = data[0].path;
-        this.activeChildName = data[0].path;
-        this.childOptions = [data[0].path];
-        this.$store.commit('menus/childMenuList', this.childList);
-        this.$emit('on-select', data[0].path);
+        console.log(data[0].path, 'data[0].path');
+        this.turnToPage(data[0].path);
       }
     },
-    handleChildSelect(name) {
-      this.turnToPage(name);
-    },
+
     collHandleSelect(name) {
-      console.log(this.menuList)
-      this.activeMenuPath = this.findParentById(this.menuList, name);
       this.turnToPage(name);
     },
-    findParentById(arr, path) {
-      var parentId = '',
-        hasParentId = (function loop(arr) {
-          return arr.some((item) => {
-            if (item.path === path) {
-              return true;
-            } else if (Array.isArray(item.children)) {
-              parentId = item.path;
-              return loop(item.children);
-            } else {
-              return false;
-            }
-          });
-        })(arr);
-      return hasParentId ? parentId : '未找到对应父元素';
-    },
-
-    // findParentsById(arr, id) {
-    //   var parentIds = [],
-    //     index = 0,
-    //     hasParentId = (function loop(arr, index) {
-    //       return arr.some((item) => {
-    //         if (item.MENU_ID === id) {
-    //           parentIds = parentIds.slice(0, index);
-    //           return true;
-    //         } else if (Array.isArray(item.MENU_INFO)) {
-    //           parentIds[index] = item.MENU_ID;
-    //           return loop(item.MENU_INFO, index + 1);
-    //         } else {
-    //           return false;
-    //         }
-    //       });
-    //     })(arr, index);
-    //   return hasParentId ? parentIds : [];
-    // },
     turnToPage(route, all) {
       let { path, name, params, query } = {};
       if (typeof route === 'string' && !all) path = route;
@@ -321,13 +240,6 @@ export default {
         query,
       });
     },
-    getOpenedNamesByActiveName() {
-      return this.$route.matched.map((item) => item.path).filter((item) => item !== name);
-    },
-    updateOpenName(name) {
-      if (name === this.$config.homeName) this.openedNames = [];
-      else this.openedNames = this.getOpenedNamesByActiveName();
-    },
     openNameData(n) {
       // this.openedNames = n
       // this.$store.commit('menus/getopenMenus', n)
@@ -335,14 +247,6 @@ export default {
     openChildNameData(e) {
       console.log(e);
     },
-    handleUpdateMenuState() {
-      this.$nextTick(() => {
-        if (this.$refs.childMenu) {
-          this.$refs.childMenu.updateActiveName();
-          if (this.accordion) this.$refs.childMenu.updateOpened();
-        }
-      });
-    },
   },
 };
 </script>

+ 14 - 6
template/admin/src/components/main/main.vue

@@ -24,7 +24,7 @@
       <Sider
         hide-trigger
         collapsible
-        :width="childMenuList.length ? 220 : 90"
+        :width="sider.length ? 220 : 90"
         :collapsed-width="isMobile ? 0 : 90"
         v-model="collapsed"
         :style="{ overflow: 'hidden' }"
@@ -52,13 +52,13 @@
               <router-view v-if="reload" style="min-height: 600px" />
             </keep-alive> -->
             <keep-alive>
-              <router-view v-if="$route.meta.keepAlive && reload" style="min-height: 600px"></router-view>
+              <router-view v-if="$route.meta.keepAlive && reload" class="main-warper"></router-view>
             </keep-alive>
-            <router-view v-if="!$route.meta.keepAlive && reload" style="min-height: 600px"></router-view>
+            <router-view v-if="!$route.meta.keepAlive && reload" class="main-warper"></router-view>
             <!-- <router-view v-if="reload" style="min-height: 600px" /> -->
-            <!--<ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>-->
+            <ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
+            <i-copyright v-if="!headMenuNoShow" />
           </Content>
-          <i-copyright v-if="!headMenuNoShow" />
         </Layout>
       </Content>
     </Layout>
@@ -118,9 +118,14 @@ export default {
       headMenuNoShow: false,
     };
   },
+  watch: {
+    sider(val) {
+      console.log(val);
+    },
+  },
   computed: {
-    ...mapState('menus', ['childMenuList']),
     ...mapGetters(['errorCount']),
+    ...mapState('menu', ['sider']),
     ...mapState('media', ['isMobile']),
     tagNavList() {
       return this.$store.state.app.tagNavList;
@@ -313,4 +318,7 @@ export default {
     width: 800px;
   }
 }
+.main-warper{
+  min-height: calc(~'100vh - 166px');
+}
 </style>

+ 224 - 0
template/admin/src/libs/system/index.js

@@ -0,0 +1,224 @@
+/**
+ * 系统内置方法集,正常情况下您不应该修改或移除此文件
+ * */
+
+import { cloneDeep } from 'lodash';
+
+/**
+ * @description 根据当前路由,找打顶部菜单名称
+ * @param {String} currentPath 当前路径
+ * @param {Array} menuList 所有路径
+ * */
+function getHeaderName(to, menuList) {
+  const allMenus = [];
+  menuList.forEach((menu) => {
+    const headerName = menu.path || '';
+    const menus = transferMenu(menu, headerName);
+    allMenus.push({
+      path: menu.path,
+      header: headerName,
+    });
+    menus.forEach((item) => allMenus.push(item));
+  });
+  const currentMenu = allMenus.find((item) => {
+    if (item.path === to.path) {
+      return true;
+    } else {
+      return to.path === getPath(to, item.path);
+    }
+  });
+  return currentMenu ? currentMenu.header : null;
+}
+
+function getPath(to, path) {
+  let params = [];
+  let query = [];
+  Object.keys(to.params).forEach((item) => {
+    params.push(to.params[item]);
+  });
+  Object.keys(to.query).forEach((item) => {
+    query.push(item + '=' + to.query[item]);
+  });
+  return path + (params.length ? '/' + params.join('/') : '') + (query.length ? '?' + query.join('&') : '');
+}
+
+function transferMenu(menu, headerName) {
+  if (menu.children && menu.children.length) {
+    return menu.children.reduce((all, item) => {
+      all.push({
+        path: item.path,
+        header: headerName,
+      });
+      const foundChildren = transferMenu(item, headerName);
+      return all.concat(foundChildren);
+    }, []);
+  } else {
+    return [menu];
+  }
+}
+
+export { getHeaderName };
+
+/**
+ * @description 根据当前路由,找打顶部菜单名称
+ * @param {String} currentPath 当前路径
+ * @param {Array} menuList 所有路径
+ * */
+function getHeaderSider(menuList) {
+  return menuList.filter((item) => item.is_header === 1);
+}
+
+export { getHeaderSider };
+/**
+ * @description 根据当前路由,找以及菜单名称
+ * @param {String} currentPath 当前路径
+ * @param {Array} menuList 所有路径
+ * */
+function getOneHeaderName(menuList, path) {
+  return menuList.filter((item) => item.path === path);
+}
+
+export { getOneHeaderName };
+
+/**
+ * @description 根据当前顶栏菜单 name,找到对应的二级菜单
+ * @param {Array} menuList 所有的二级菜单
+ * @param {String} headerName 当前顶栏菜单的 name
+ * */
+function getMenuSider(menuList, headerName = '') {
+  if (headerName) {
+    return menuList.filter((item) => item.path === headerName);
+  } else {
+    return menuList;
+  }
+}
+
+export { getMenuSider };
+
+/**
+ * @description 根据当前路由,找到其所有父菜单 path,作为展开侧边栏 open-names 依据
+ * @param {String} currentPath 当前路径
+ * @param {Array} menuList 所有路径
+ * */
+// function getSiderSubmenu (currentPath, menuList) {
+//     const allMenus = [];
+//     menuList.forEach(menu => {
+//         const menus = transferSubMenu(menu, []);
+//         allMenus.push({
+//             path: menu.path,
+//             openNames: []
+//         });
+//         menus.forEach(item => allMenus.push(item));
+//     });
+//     const currentMenu = allMenus.find(item => item.path === currentPath);
+//     return currentMenu ? currentMenu.openNames : [];
+// }
+
+function getSiderSubmenu(to, menuList) {
+  const allMenus = [];
+  menuList.forEach((menu) => {
+    const menus = transferSubMenu(menu, []);
+    allMenus.push({
+      path: menu.path,
+      openNames: [],
+    });
+    menus.forEach((item) => allMenus.push(item));
+  });
+  const currentMenu = allMenus.find((item) => {
+    if (item.openNames.length) {
+      return item.path === to.path || to.path === getPath(to, item.path);
+    }
+  });
+  return currentMenu ? currentMenu.openNames : [];
+}
+
+function transferSubMenu(menu, openNames) {
+  if (menu.children && menu.children.length) {
+    const itemOpenNames = openNames.concat([menu.path]);
+    return menu.children.reduce((all, item) => {
+      all.push({
+        path: item.path,
+        openNames: itemOpenNames,
+      });
+      const foundChildren = transferSubMenu(item, itemOpenNames);
+      return all.concat(foundChildren);
+    }, []);
+  } else {
+    return [menu].map((item) => {
+      return {
+        path: item.path,
+        openNames: openNames,
+      };
+    });
+  }
+}
+
+export { getSiderSubmenu };
+
+/**
+ * @description 递归获取所有子菜单
+ * */
+function getAllSiderMenu(menuList) {
+  let allMenus = [];
+
+  menuList.forEach((menu) => {
+    if (menu.children && menu.children.length) {
+      const menus = getMenuChildren(menu);
+      menus.forEach((item) => allMenus.push(item));
+    } else {
+      allMenus.push(menu);
+    }
+  });
+
+  return allMenus;
+}
+
+function getMenuChildren(menu) {
+  if (menu.children && menu.children.length) {
+    return menu.children.reduce((all, item) => {
+      const foundChildren = getMenuChildren(item);
+      return all.concat(foundChildren);
+    }, []);
+  } else {
+    return [menu];
+  }
+}
+
+export { getAllSiderMenu };
+
+/**
+ * @description 将菜单转为平级
+ * */
+function flattenSiderMenu(menuList, newList) {
+  menuList.forEach((menu) => {
+    let newMenu = {};
+    for (let i in menu) {
+      if (i !== 'children') newMenu[i] = cloneDeep(menu[i]);
+    }
+    newList.push(newMenu);
+    menu.children && flattenSiderMenu(menu.children, newList);
+  });
+  return newList;
+}
+
+export { flattenSiderMenu };
+
+/**
+ * @description 判断列表1中是否包含了列表2中的某一项
+ * 因为用户权限 access 为数组,includes 方法无法直接得出结论
+ * */
+function includeArray(list1, list2) {
+  let status = false;
+  if (list1 === true) {
+    return true;
+  } else {
+    if (typeof list2 !== 'object') {
+      return false;
+    }
+    list2.forEach((item) => {
+      if (list1.includes(item)) status = true;
+    });
+    return status;
+  }
+}
+export { includeArray };

+ 39 - 3
template/admin/src/main.js

@@ -55,7 +55,7 @@ import Viewer from 'v-viewer';
 import VueDND from 'awe-dnd';
 import formCreate from '@form-create/iview';
 import modalForm from '@/utils/modalForm';
-import exportExcel from '@/utils/newToExcel.js'
+import exportExcel from '@/utils/newToExcel.js';
 import videoCloud from '@/utils/videoCloud';
 import { modalSure } from '@/utils/public';
 import { authLapse } from '@/utils/authLapse';
@@ -67,6 +67,8 @@ import timeOptions from '@/libs/timeOptions';
 import scroll from '@/libs/loading';
 import * as tools from '@/libs/tools';
 import VueTreeList from 'vue-tree-list';
+import { getHeaderName, getHeaderSider, getMenuSider, getSiderSubmenu } from '@/libs/system';
+
 // 复制到粘贴板插件
 import VueClipboard from 'vue-clipboard2';
 
@@ -88,9 +90,9 @@ const routerPush = Router.prototype.push;
 Router.prototype.push = function push(location) {
   return routerPush.call(this, location).catch((error) => error);
 };
-import settings  from '@/setting'
+import settings from '@/setting';
 
-Vue.prototype.$routeProStr = settings.routePre
+Vue.prototype.$routeProStr = settings.routePre;
 
 // 实际打包时应该不引入mock
 /* eslint-disable */
@@ -193,11 +195,45 @@ new Vue({
   watch: {
     // 监听路由 控制侧边栏显示 标记当前顶栏菜单(如需要)
     $route(to, from) {
+      const path = to.path;
+      let menus = this.$store.state.menus.menusName;
+      console.log(menus, 'menus');
+      const menuSider = menus;
+
+      this.$store.commit('menu/setActivePath', path);
+      const openNames = getSiderSubmenu(to, menuSider);
+      this.$store.commit('menu/setOpenNames', openNames);
+      // 设置顶栏菜单 后台添加一个接口,设置顶部菜单
+      const headerSider = getHeaderSider(menuSider);
+      console.log(headerSider, 'headerSider');
+      this.$store.commit('menu/setHeader', headerSider);
+      // 指定当前侧边栏隶属顶部菜单名称。如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可
+      const headerName = getHeaderName(to, menuSider);
+      this.$store.commit('menu/setHeaderName', headerName);
+      // 获取侧边栏菜单
+      const filterMenuSider = getMenuSider(menuSider, headerName);
+      console.log(filterMenuSider, 'filterMenuSider');
+      // 指定当前显示的侧边菜单
+      this.$store.commit('menu/setOpenMenuName', filterMenuSider[0].title);
+      this.$store.commit('menu/setSider', filterMenuSider[0]?.children || []);
       if (to.meta.kefu) {
         document.getElementsByTagName('body')[0].className = 'kf_mobile';
       } else {
         document.getElementsByTagName('body')[0].className = '';
       }
+      // var storage = window.localStorage;
+      // let menus = JSON.parse(storage.getItem('menuList'));
+      // this.getMenus().then(menus => {
+      // 处理手动清除db 跳转403问题
+      if (!menus.length) {
+        if (path !== '/admin/login') {
+          this.$router.replace('/admin/login');
+        }
+        return;
+      }
+      // 在 404 时,是没有 headerName 的
+      
+      // });
     },
   },
 });

+ 0 - 1
template/admin/src/pages/product/productAdd/index.vue

@@ -3251,7 +3251,6 @@ export default {
   font-size: 12px;
   font-weight: 400;
   color: #999999;
-  margin-top: 14px;
 }
 
 .videbox {

+ 3 - 0
template/admin/src/pages/setting/setSystem/index.vue

@@ -225,4 +225,7 @@ export default {
   min-height: 600px;
   margin-top 0px !important ;
 }
+.article-manager /deep/ .ivu-form-item{
+  margin-bottom: 20px !important;
+}
 </style>

+ 3 - 0
template/admin/src/store/index.js

@@ -15,6 +15,7 @@ import VuexPersistence from 'vuex-persist';
 import user from './module/user';
 import app from './module/app';
 import menus from './module/menus';
+import menu from './module/menu';
 import userInfo from './module/userInfo';
 import userLevel from './module/userLevel';
 import order from './module/order';
@@ -51,6 +52,7 @@ export default new Vuex.Store({
         user: state.user, //这个就是存入localStorage的值
         app: state.app,
         menus: state.menus,
+        menu: state.menu,
         userInfo: state.userInfo,
         userLevel: state.userLevel,
         order: state.order,
@@ -67,6 +69,7 @@ export default new Vuex.Store({
     user,
     app,
     menus,
+    menu,
     userInfo,
     userLevel,
     order,

+ 165 - 0
template/admin/src/store/module/menu.js

@@ -0,0 +1,165 @@
+// +----------------------------------------------------------------------
+// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
+// +----------------------------------------------------------------------
+// | Author: CRMEB Team <admin@crmeb.com>
+// +----------------------------------------------------------------------
+/**
+ * 菜单
+ * */
+import { cloneDeep } from 'lodash';
+import { includeArray } from '@/libs/system';
+
+// 根据 menu 配置的权限,过滤菜单
+function filterMenu(menuList, access, lastList) {
+  menuList.forEach((menu) => {
+    let menuAccess = menu.auth;
+
+    if (!menuAccess || includeArray(menuAccess, access)) {
+      let newMenu = {};
+      for (let i in menu) {
+        if (i !== 'children') newMenu[i] = cloneDeep(menu[i]);
+      }
+      if (menu.children && menu.children.length) newMenu.children = [];
+
+      lastList.push(newMenu);
+      menu.children && filterMenu(menu.children, access, newMenu.children);
+    }
+  });
+  return lastList;
+}
+// 递归处理顶部菜单问题
+function getChilden(data) {
+  if (data.children) {
+    return getChilden(data.children[0]);
+  }
+  return data.path;
+}
+
+export default {
+  namespaced: true,
+  state: {
+    // 顶部菜单
+    header: [],
+    // 一级菜单名称
+    oneMenuName: '',
+    // 侧栏菜单
+    sider: [],
+    // 当前顶栏菜单的 name
+    headerName: '',
+    // 当前所在菜单的 path
+    activePath: '',
+    // 展开的子菜单 name 集合
+    openNames: [],
+  },
+  getters: {
+    /**
+     * @description 根据 user 里登录用户权限,对侧边菜单进行鉴权过滤
+     * */
+    filterSider(state, getters, rootState) {
+      const userInfo = rootState.user.info;
+      // @权限
+      const access = userInfo.access;
+      if (access && access.length) {
+        return filterMenu(state.sider, access, []);
+      } else {
+        return filterMenu(state.sider, [], []);
+      }
+    },
+    // 处理顶部路由递归
+
+    /**
+     * @description 根据 user 里登录用户权限,对顶栏菜单进行鉴权过滤
+     * */
+    filterHeader(state, getters, rootState) {
+      //  调用递归函数
+      state.header.forEach((item) => {
+        item.path = getChilden(item);
+      });
+
+      // @权限
+      const userInfo = rootState.admin.user.info;
+      const access = userInfo.access;
+      if (access && access.length) {
+        return state.header.filter((item) => {
+          let state = true;
+          if (item.auth && !includeArray(item.auth, access)) state = false;
+          return state;
+        });
+      } else {
+        return state.header.filter((item) => {
+          let state = true;
+          if (item.auth && item.auth.length) state = false;
+          return state;
+        });
+      }
+    },
+    /**
+     * @description 当前 header 的全部信息
+     * */
+    currentHeader(state) {
+      return state.header.find((item) => item.name === state.headerName);
+    },
+    /**
+     * @description 在当前 header 下,是否隐藏 sider(及折叠按钮)
+     * */
+    hideSider(state, getters) {
+      let visible = false;
+      if (getters.currentHeader && 'hideSider' in getters.currentHeader) visible = getters.currentHeader.hideSider;
+      return visible;
+    },
+  },
+  mutations: {
+    /**
+     * @description 设置侧边栏菜单
+     * @param {Object} state vuex state
+     * @param {Array} menu menu
+     */
+    setSider(state, menu) {
+      state.sider = menu;
+    },
+    /**
+     * @description 设置侧边栏菜单
+     * @param {Object} state vuex state
+     * @param {Array} menu menu
+     */
+    setOpenMenuName(state, menu) {
+      state.oneMenuName = menu;
+    },
+    /**
+     * @description 设置顶栏菜单
+     * @param {Object} state vuex state
+     * @param {Array} menu menu
+     */
+    setHeader(state, menu) {
+      state.header = menu;
+    },
+    /**
+     * @description 设置当前顶栏菜单 name
+     * @param {Object} state vuex state
+     * @param {Array} name headerName
+     */
+    setHeaderName(state, name) {
+      state.headerName = name;
+    },
+    /**
+     * @description 设置当前所在菜单的 path,用于侧栏菜单高亮当前项
+     * @param {Object} state vuex state
+     * @param {Array} path fullPath
+     */
+    setActivePath(state, path) {
+      state.activePath = path;
+    },
+    /**
+     * @description 设置当前所在菜单的全部展开父菜单的 names 集合
+     * @param {Object} state vuex state
+     * @param {Array} names openNames
+     */
+    setOpenNames(state, names) {
+      state.openNames = names;
+    },
+  },
+};