From 002cf50440c8e3e5d87f934c6edfb09c52043746 Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Fri, 28 Aug 2020 14:25:54 +0800
Subject: [PATCH 01/12] update docs;

---
 docs/advance/async.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/docs/advance/async.md b/docs/advance/async.md
index 0e2ff8a..750f6d7 100644
--- a/docs/advance/async.md
+++ b/docs/advance/async.md
@@ -24,7 +24,7 @@ module.exports = {
 }
 ```
 ### 注册路由组件
-基础路由组件包含路由基本配置和对应的视图组件,我们统一在 `/router/router.map.js` 文件中注册它们。它和正常的路由配置基本无异,相当于把完整的路由拆分成单个的路由配置进行注册,为后面的路由动态配置打好基础。  
+基础路由组件包含路由基本配置和对应的视图组件,我们统一在 `/router/async/router.map.js` 文件中注册它们。它和正常的路由配置基本无异,相当于把完整的路由拆分成单个的路由配置进行注册,为后面的路由动态配置打好基础。  
 一个单独的路由组件注册示例如下:
 ```jsx
 registerName: {                               //路由组件注册名称,唯一标识
@@ -108,7 +108,7 @@ export default routerMap
 ```
 :::
 ### 配置基本路由
-如果没有任何路由,你的应用是无法访问的,所以我们需要在本地配置一些基本的路由,比如登录页、404、403 等。你可以在 `/router/config.async.js` 文件中配置一些本地必要的路由。如下:
+如果没有任何路由,你的应用是无法访问的,所以我们需要在本地配置一些基本的路由,比如登录页、404、403 等。你可以在 `/router/async/config.async.js` 文件中配置一些本地必要的路由。如下:
 ```js
 const routesConfig = [
   'login',                      //匹配 router.map.js 中注册的 registerName = login 的路由
@@ -163,25 +163,25 @@ export default options
 那么我们就需要先从后端服务获取异步路由配置,后端返回的异步路由配置 `routesConfig` 是一个异步路由配置数组, 应当如下格式:
 ```jsx
 [{
-  router: 'root',                           //匹配 /router/router.map.js 中注册名 registerName = root 的路由
+  router: 'root',                           //匹配 router.map.js 中注册名 registerName = root 的路由
   children: [                               //root 路由的子路由配置
     {
-      router: 'dashboard',                  //匹配 /router/router.map.js 中注册名 registerName = dashboard 的路由
+      router: 'dashboard',                  //匹配 router.map.js 中注册名 registerName = dashboard 的路由
       children: ['workplace', 'analysis'],  //dashboard 路由的子路由配置,依次匹配 registerName 为 workplace 和 analysis 的路由
     },
     {
-      router: 'form',                       //匹配 /router/router.map.js 中注册名 registerName = form 的路由
+      router: 'form',                       //匹配 router.map.js 中注册名 registerName = form 的路由
       children: [                           //form 路由的子路由配置
-        'basicForm',                        //匹配 /router/router.map.js 中注册名 registerName = basicForm 的路由
-        'stepForm',                         //匹配 /router/router.map.js 中注册名 registerName = stepForm 的路由
+        'basicForm',                        //匹配 router.map.js 中注册名 registerName = basicForm 的路由
+        'stepForm',                         //匹配 router.map.js 中注册名 registerName = stepForm 的路由
         {
-          router: 'advanceForm',            //匹配 /router/router.map.js 中注册名 registerName = advanceForm 的路由
+          router: 'advanceForm',            //匹配 router.map.js 中注册名 registerName = advanceForm 的路由
           path: 'advance'                   //重写 advanceForm 路由的 path 属性
         }
       ]   
     },
     {
-      router: 'basicForm',                  //匹配 /router/router.map.js 中注册名 registerName = basicForm 的路由
+      router: 'basicForm',                  //匹配 router.map.js 中注册名 registerName = basicForm 的路由
       name: '验权表单',                     //重写 basicForm 路由的 name 属性
       icon: 'file-excel',                   //重写 basicForm 路由的 icon 属性
       authority: 'form'                     //重写 basicForm 路由的 authority 属性
@@ -203,7 +203,7 @@ getRoutesConfig().then(result => {
 至此,异步路由的加载就完成了,你可以访问异步加载的路由了。
 :::tip
 上面获取异步路由的代码,在 /pages/login/Login.vue 文件中可以找到。   
-loadRoutes 方法会合并 /router/config.async.js 文件中配置的基本路由。
+loadRoutes 方法会合并 /router/async/config.async.js 文件中配置的基本路由。
 :::
 :::details 点击查看 loadRoutes 的详细代码
 ```js

From 094935b758c4de7a15de17d7f8925f6aa1d4b051 Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Mon, 31 Aug 2020 12:23:56 +0800
Subject: [PATCH 02/12] =?UTF-8?q?feat:=20add=20mixed=20navigation=20mode;?=
 =?UTF-8?q?=20:star:=20#102=20=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=B7=B7?=
 =?UTF-8?q?=E5=90=88=E5=AF=BC=E8=88=AA=E8=8F=9C=E5=8D=95=E6=A8=A1=E5=BC=8F?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/menu/menu.js         | 12 +++------
 src/components/setting/Setting.vue  |  1 +
 src/components/setting/i18n.js      |  2 ++
 src/layouts/AdminLayout.vue         | 41 +++++++++++++++++++++++++----
 src/layouts/header/AdminHeader.vue  | 17 ++++++++----
 src/layouts/header/HeaderSearch.vue |  2 ++
 src/layouts/header/index.less       |  6 +++++
 src/router/guards.js                | 25 +++++++++++++++++-
 src/store/modules/setting.js        | 26 ++++++++++++++++++
 src/utils/i18n.js                   |  3 ++-
 10 files changed, 115 insertions(+), 20 deletions(-)

diff --git a/src/components/menu/menu.js b/src/components/menu/menu.js
index 15d18eb..49ef98f 100644
--- a/src/components/menu/menu.js
+++ b/src/components/menu/menu.js
@@ -77,7 +77,7 @@ export default {
   },
   created () {
     this.updateMenu()
-    if (!this.options[0].fullPath) {
+    if (this.options.length > 0 && !this.options[0].fullPath) {
       this.formatOptions(this.options, '')
     }
     // 自定义国际化配置
@@ -90,7 +90,7 @@ export default {
   },
   watch: {
     options(val) {
-      if (!val[0].fullPath) {
+      if (val.length > 0 && !val[0].fullPath) {
         this.formatOptions(this.options, '')
       }
     },
@@ -195,18 +195,14 @@ export default {
     },
     updateMenu () {
       const menuRoutes = this.$route.matched.filter(item => item.path !== '')
-      const route = menuRoutes.pop()
-      this.selectedKeys = [this.getSelectedKey(route)]
+      this.selectedKeys = this.getSelectedKey(this.$route)
       let openKeys = menuRoutes.map(item => item.path)
       if (!fastEqual(openKeys, this.sOpenKeys)) {
         this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
       }
     },
     getSelectedKey (route) {
-      if (route.meta.invisible && route.parent) {
-        return this.getSelectedKey(route.parent)
-      }
-      return route.path
+      return route.matched.map(item => item.path)
     }
   },
   render (h) {
diff --git a/src/components/setting/Setting.vue b/src/components/setting/Setting.vue
index 93ec7f1..d4bdd4d 100644
--- a/src/components/setting/Setting.vue
+++ b/src/components/setting/Setting.vue
@@ -26,6 +26,7 @@
       >
         <img-checkbox :title="$t('navigate.side')" img="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" value="side"/>
         <img-checkbox :title="$t('navigate.head')" img="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" value="head"/>
+        <img-checkbox :title="$t('navigate.mix')" img="https://gw.alipayobjects.com/zos/antfincdn/x8Ob%26B8cy8/LCkqqYNmvBEbokSDscrm.svg" value="mix"/>
       </img-checkbox-group>
     </setting-item>
     <setting-item>
diff --git a/src/components/setting/i18n.js b/src/components/setting/i18n.js
index 03873a7..85fb98d 100644
--- a/src/components/setting/i18n.js
+++ b/src/components/setting/i18n.js
@@ -12,6 +12,7 @@ module.exports = {
         title: '导航设置',
         side: '侧边导航',
         head: '顶部导航',
+        mix: '混合导航',
         content: {
           title: '内容区域宽度',
           fluid: '流式',
@@ -82,6 +83,7 @@ module.exports = {
         title: 'Navigation Mode',
         side: 'Side Menu Layout',
         head: 'Top Menu Layout',
+        mix: 'Mix Menu Layout',
         content: {
           title: 'Content Width',
           fluid: 'Fluid',
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 92a9075..dd197fe 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -3,7 +3,7 @@
     <drawer v-if="isMobile" v-model="collapsed">
       <side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
     </drawer>
-    <side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side'" :menuData="menuData" :collapsed="collapsed" :collapsible="true" />
+    <side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side' || layout === 'mix'" :menuData="sideMenuData" :collapsed="collapsed" :collapsible="true" />
     <div v-if="fixedSideBar && !isMobile" :style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`" class="virtual-side"></div>
     <drawer v-if="!hideSetting" v-model="showSetting" placement="right">
       <div class="setting" slot="handler">
@@ -12,7 +12,7 @@
       <setting />
     </drawer>
     <a-layout class="admin-layout-main beauty-scroll">
-      <admin-header :style="headerStyle" :menuData="menuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
+      <admin-header :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
       <a-layout-header v-if="fixedHeader"></a-layout-header>
       <a-layout-content class="admin-layout-content">
         <div :style="`min-height: ${minHeight}px; position: relative`">
@@ -32,7 +32,7 @@ import PageFooter from './footer/PageFooter'
 import Drawer from '../components/tool/Drawer'
 import SideMenu from '../components/menu/SideMenu'
 import Setting from '../components/setting/Setting'
-import {mapState, mapMutations} from 'vuex'
+import {mapState, mapMutations, mapGetters} from 'vuex'
 
 const minHeight = window.innerHeight - 64 - 24 - 122
 
@@ -46,30 +46,61 @@ export default {
       showSetting: false
     }
   },
+  watch: {
+    $route(val) {
+      this.setActivated(val)
+    },
+    layout() {
+      this.setActivated(this.$route)
+    }
+  },
   computed: {
     ...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
       'hideSetting', 'menuData']),
+    ...mapGetters('setting', ['firstMenu', 'subMenu']),
     sideMenuWidth() {
       return this.collapsed ? '80px' : '256px'
     },
     headerStyle() {
-      let width = (this.fixedHeader && this.layout == 'side' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
+      let width = (this.fixedHeader && this.layout !== 'head' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
       let position = this.fixedHeader ? 'fixed' : 'static'
       let transition = this.fixedHeader ? 'transition: width 0.2s' : ''
       return `width: ${width}; position: ${position}; ${transition}`
+    },
+    headMenuData() {
+      const {layout, menuData, firstMenu} = this
+      return layout === 'mix' ? firstMenu : menuData
+    },
+    sideMenuData() {
+      const {layout, menuData, subMenu} = this
+      return layout === 'mix' ? subMenu : menuData
     }
   },
   methods: {
-    ...mapMutations('setting', ['correctPageMinHeight']),
+    ...mapMutations('setting', ['correctPageMinHeight', 'setActivatedFirst']),
     toggleCollapse () {
       this.collapsed = !this.collapsed
     },
     onMenuSelect () {
       this.toggleCollapse()
     },
+    setActivated(route) {
+      if (this.layout === 'mix') {
+        let matched = route.matched
+        matched = matched.slice(0, matched.length - 1)
+        const {firstMenu} = this
+        for (let menu of firstMenu) {
+          if (matched.findIndex(item => item.path === menu.fullPath) !== -1) {
+            this.setActivatedFirst(menu.fullPath)
+            break
+          }
+        }
+      }
+    }
   },
   created() {
     this.correctPageMinHeight(minHeight - 1)
+    this.setActivated(this.$route)
   },
   beforeDestroy() {
     this.correctPageMinHeight(-minHeight + 1)
diff --git a/src/layouts/header/AdminHeader.vue b/src/layouts/header/AdminHeader.vue
index b320432..a65b0ad 100644
--- a/src/layouts/header/AdminHeader.vue
+++ b/src/layouts/header/AdminHeader.vue
@@ -6,12 +6,12 @@
         <h1 v-if="!isMobile">{{systemName}}</h1>
       </router-link>
       <a-divider v-if="isMobile" type="vertical" />
-      <a-icon v-if="layout === 'side'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
-      <div v-if="layout == 'head' && !isMobile" class="admin-header-menu">
-        <i-menu class="head-menu" style="height: 64px; line-height: 64px;box-shadow: none" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect"/>
+      <a-icon v-if="layout !== 'head'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
+      <div v-if="layout !== 'side' && !isMobile" class="admin-header-menu" :style="`width: ${menuWidth};`">
+        <i-menu class="head-menu" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect"/>
       </div>
       <div :class="['admin-header-right', headerTheme]">
-          <header-search class="header-item" />
+          <header-search class="header-item" @active="val => searchActive = val" />
           <a-tooltip class="header-item" title="帮助文档" placement="bottom" >
             <a href="https://iczer.github.io/vue-antd-admin/" target="_blank">
               <a-icon type="question-circle-o" />
@@ -49,7 +49,8 @@ export default {
         {key: 'CN', name: '简体中文', alias: '简体'},
         {key: 'HK', name: '繁體中文', alias: '繁體'},
         {key: 'US', name: 'English', alias: 'English'}
-      ]
+      ],
+      searchActive: false
     }
   },
   computed: {
@@ -63,6 +64,12 @@ export default {
     langAlias() {
       let lang = this.langList.find(item => item.key == this.lang)
       return lang.alias
+    },
+    menuWidth() {
+      const {layout, searchActive} = this
+      const headWidth = layout === 'head' ? '1236px' : '100%'
+      const extraWidth = searchActive ? '564px' : '364px'
+      return `calc(${headWidth} - ${extraWidth})`
     }
   },
   methods: {
diff --git a/src/layouts/header/HeaderSearch.vue b/src/layouts/header/HeaderSearch.vue
index bd408cc..ec4f116 100644
--- a/src/layouts/header/HeaderSearch.vue
+++ b/src/layouts/header/HeaderSearch.vue
@@ -24,10 +24,12 @@ export default {
   methods: {
     enterSearchMode () {
       this.searchMode = true
+      this.$emit('active', true)
       setTimeout(() => this.$refs.input.focus(), 300)
     },
     leaveSearchMode () {
       this.searchMode = false
+      setTimeout(() => this.$emit('active', false), 300)
     }
   }
 }
diff --git a/src/layouts/header/index.less b/src/layouts/header/index.less
index 443f030..ba90761 100644
--- a/src/layouts/header/index.less
+++ b/src/layouts/header/index.less
@@ -4,6 +4,12 @@
   box-shadow: @shadow-down;
   position: relative;
   background: @base-bg-color;
+  .head-menu{
+    height: 64px;
+    line-height: 64px;
+    vertical-align: middle;
+    box-shadow: none;
+  }
   &.dark{
     background: @header-bg-color-dark;
     color: white;
diff --git a/src/router/guards.js b/src/router/guards.js
index b8719f9..67de64c 100644
--- a/src/router/guards.js
+++ b/src/router/guards.js
@@ -38,7 +38,30 @@ const authorityGuard = (to, from, next, options) => {
   }
 }
 
+/**
+ * 混合导航模式下一级菜单跳转重定向
+ * @param to
+ * @param from
+ * @param next
+ * @param options
+ * @returns {*}
+ */
+const redirectGuard = (to, from, next, options) => {
+  const {store} = options
+  if (store.state.setting.layout === 'mix') {
+    const firstMenu = store.getters['setting/firstMenu']
+    if (firstMenu.find(item => item.fullPath === to.fullPath)) {
+      store.commit('setting/setActivatedFirst', to.fullPath)
+      const subMenu = store.getters['setting/subMenu']
+      if (subMenu.length > 0) {
+        return next({path: subMenu[0].fullPath})
+      }
+    }
+  }
+  next()
+}
+
 export default {
-  beforeEach: [loginGuard, authorityGuard],
+  beforeEach: [loginGuard, authorityGuard, redirectGuard],
   afterEach: []
 }
diff --git a/src/store/modules/setting.js b/src/store/modules/setting.js
index 7f0c22b..904c173 100644
--- a/src/store/modules/setting.js
+++ b/src/store/modules/setting.js
@@ -1,5 +1,6 @@
 import config from '@/config'
 import {ADMIN} from '@/config/default'
+import {formatFullPath} from '@/utils/i18n'
 export default {
   namespaced: true,
   state: {
@@ -8,8 +9,30 @@ export default {
     palettes: ADMIN.palettes,
     pageMinHeight: 0,
     menuData: [],
+    activatedFirst: undefined,
     ...config,
   },
+  getters: {
+    firstMenu(state) {
+      const {menuData} = state
+      if (!menuData[0].fullPath) {
+        formatFullPath(menuData)
+      }
+      return menuData.map(item => {
+        const menuItem = {...item}
+        delete menuItem.children
+        return menuItem
+      })
+    },
+    subMenu(state) {
+      const {menuData, activatedFirst} = state
+      if (!menuData[0].fullPath) {
+        formatFullPath(menuData)
+      }
+      const current = menuData.find(menu => menu.fullPath === activatedFirst)
+      return current ? current.children : []
+    }
+  },
   mutations: {
     setDevice (state, isMobile) {
       state.isMobile = isMobile
@@ -49,6 +72,9 @@ export default {
     },
     setAsyncRoutes(state, asyncRoutes) {
       state.asyncRoutes = asyncRoutes
+    },
+    setActivatedFirst(state, activatedFirst) {
+      state.activatedFirst = activatedFirst
     }
   }
 }
diff --git a/src/utils/i18n.js b/src/utils/i18n.js
index 50e3c36..ec64b9f 100644
--- a/src/utils/i18n.js
+++ b/src/utils/i18n.js
@@ -73,5 +73,6 @@ function mergeI18nFromRoutes(i18n, routes) {
 
 export {
   initI18n,
-  mergeI18nFromRoutes
+  mergeI18nFromRoutes,
+  formatFullPath
 }

From cbda23e3dbd567165c619e2df677c7ad327a6e32 Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Mon, 31 Aug 2020 12:38:49 +0800
Subject: [PATCH 03/12] =?UTF-8?q?fix:=20style=20problem=20of=20tooltip=20i?=
 =?UTF-8?q?n=20setting=20pane;=20:bug:=20=E4=BF=AE=E5=A4=8D=EF=BC=9A?=
 =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=9D=A2=E6=9D=BF=E4=B8=AD=20tooltip=20?=
 =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E6=A0=B7=E5=BC=8F=E9=97=AE=E9=A2=98?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/checkbox/ImgCheckbox.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/checkbox/ImgCheckbox.vue b/src/components/checkbox/ImgCheckbox.vue
index b59dd22..ca06bdd 100644
--- a/src/components/checkbox/ImgCheckbox.vue
+++ b/src/components/checkbox/ImgCheckbox.vue
@@ -1,5 +1,5 @@
 <template>
-  <a-tooltip :title="title">
+  <a-tooltip :title="title" :overlayStyle="{zIndex: 2001}">
     <div class="img-check-box" @click="toggle">
       <img :src="img" />
       <div v-if="sChecked" class="check-item">

From 1fb75f491d37d37da775a5f6de5966ecef5ed3fb Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Mon, 31 Aug 2020 20:51:58 +0800
Subject: [PATCH 04/12] =?UTF-8?q?feat:=20add=20function=20of=20filtering?=
 =?UTF-8?q?=20menu=20data=20through=20authority;=20:star:=20=E6=96=B0?=
 =?UTF-8?q?=E5=A2=9E=EF=BC=9A=E5=A2=9E=E5=8A=A0=E9=80=9A=E8=BF=87=E6=9D=83?=
 =?UTF-8?q?=E9=99=90=E8=BF=87=E6=BB=A4=E8=8F=9C=E5=8D=95=E6=95=B0=E6=8D=AE?=
 =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/config/default/setting.config.js |  1 +
 src/layouts/AdminLayout.vue          |  4 ++--
 src/store/modules/setting.js         | 11 ++++++++++-
 src/utils/authority-utils.js         | 17 ++++++++++++++++-
 4 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/src/config/default/setting.config.js b/src/config/default/setting.config.js
index 6f2c5e8..bcbf470 100644
--- a/src/config/default/setting.config.js
+++ b/src/config/default/setting.config.js
@@ -18,6 +18,7 @@ module.exports = {
   copyright: '2018 ICZER 工作室出品',     //copyright
   asyncRoutes: false,                   //异步加载路由,true:开启,false:不开启
   showPageTitle: true,                  //是否显示页面标题(PageLayout 布局中的页面标题),true:显示,false:不显示
+  filterMenu: true,                    //根据权限过滤菜单,true:过滤,false:不过滤
   animate: {                            //动画设置
     disabled: false,                    //禁用动画,true:禁用,false:启用
     name: 'bounce',                     //动画效果,支持的动画效果可参考 ./animate.config.js
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index dd197fe..ad0dda8 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -56,8 +56,8 @@ export default {
   },
   computed: {
     ...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
-      'hideSetting', 'menuData']),
-    ...mapGetters('setting', ['firstMenu', 'subMenu']),
+      'hideSetting']),
+    ...mapGetters('setting', ['firstMenu', 'subMenu', 'menuData']),
     sideMenuWidth() {
       return this.collapsed ? '80px' : '256px'
     },
diff --git a/src/store/modules/setting.js b/src/store/modules/setting.js
index 904c173..f6d2aed 100644
--- a/src/store/modules/setting.js
+++ b/src/store/modules/setting.js
@@ -1,6 +1,8 @@
 import config from '@/config'
 import {ADMIN} from '@/config/default'
 import {formatFullPath} from '@/utils/i18n'
+import {filterMenu} from '@/utils/authority-utils'
+
 export default {
   namespaced: true,
   state: {
@@ -13,6 +15,13 @@ export default {
     ...config,
   },
   getters: {
+    menuData(state, getters, rootState) {
+      if (state.filterMenu) {
+        const {permissions, roles} = rootState.account
+        filterMenu(state.menuData, permissions, roles)
+      }
+      return state.menuData
+    },
     firstMenu(state) {
       const {menuData} = state
       if (!menuData[0].fullPath) {
@@ -30,7 +39,7 @@ export default {
         formatFullPath(menuData)
       }
       const current = menuData.find(menu => menu.fullPath === activatedFirst)
-      return current ? current.children : []
+      return current && current.children ? current.children : []
     }
   },
   mutations: {
diff --git a/src/utils/authority-utils.js b/src/utils/authority-utils.js
index 48dd2ea..71ab473 100644
--- a/src/utils/authority-utils.js
+++ b/src/utils/authority-utils.js
@@ -47,4 +47,19 @@ function hasAnyRole(required, roles) {
   }
 }
 
-export {hasPermission, hasRole}
+/**
+ * 根据权限配置过滤菜单数据
+ * @param menuData
+ * @param permissions
+ * @param roles
+ */
+function filterMenu(menuData, permissions, roles) {
+  menuData.forEach(menu => {
+    menu.meta.invisible = !hasPermission(menu, permissions) && !hasRole(menu, roles)
+    if (menu.children && menu.children.length > 0) {
+      filterMenu(menu.children, permissions, roles)
+    }
+  })
+}
+
+export {hasPermission, hasRole, filterMenu}

From 5ec6f73d6f8a55ff7e8355548c97253c55e8b7ba Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Tue, 1 Sep 2020 19:59:50 +0800
Subject: [PATCH 05/12] =?UTF-8?q?fix:=20problem=20tha=20the=20authority=20?=
 =?UTF-8?q?config=20of=20route=20be=20overwritten;=20:bug:=20=E4=BF=AE?=
 =?UTF-8?q?=E5=A4=8D=EF=BC=9A=E8=B7=AF=E7=94=B1=E6=9D=83=E9=99=90=E9=85=8D?=
 =?UTF-8?q?=E7=BD=AE=E8=A2=AB=E8=A6=86=E7=9B=96=E7=9A=84=E9=97=AE=E9=A2=98?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/utils/authority-utils.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/utils/authority-utils.js b/src/utils/authority-utils.js
index 71ab473..e421e12 100644
--- a/src/utils/authority-utils.js
+++ b/src/utils/authority-utils.js
@@ -55,9 +55,11 @@ function hasAnyRole(required, roles) {
  */
 function filterMenu(menuData, permissions, roles) {
   menuData.forEach(menu => {
-    menu.meta.invisible = !hasPermission(menu, permissions) && !hasRole(menu, roles)
-    if (menu.children && menu.children.length > 0) {
-      filterMenu(menu.children, permissions, roles)
+    if (menu.meta && menu.meta.invisible === undefined) {
+      menu.meta.invisible = !hasPermission(menu, permissions) && !hasRole(menu, roles)
+      if (menu.children && menu.children.length > 0) {
+        filterMenu(menu.children, permissions, roles)
+      }
     }
   })
 }

From 4db7c2deff11864dcc5a4a5e1752d98419b4cdb3 Mon Sep 17 00:00:00 2001
From: iczer <1126263215@qq.com>
Date: Wed, 2 Sep 2020 18:55:28 +0800
Subject: [PATCH 06/12] =?UTF-8?q?fix:=20problem=20that=20child=20route=20c?=
 =?UTF-8?q?an't=20inherit=20authority=20configuration=20of=20it's=20parent?=
 =?UTF-8?q?;=20:bug:=20=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=AD=90=E8=B7=AF?=
 =?UTF-8?q?=E7=94=B1=E6=97=A0=E6=B3=95=E7=BB=A7=E6=89=BF=E5=85=B6=E7=88=B6?=
 =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E6=9D=83=E9=99=90=E9=85=8D=E7=BD=AE?=
 =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/router/guards.js         |  4 ++--
 src/utils/authority-utils.js | 31 +++++++++++++++++++++++--------
 src/utils/routerUtil.js      | 18 +++++++++++-------
 3 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/src/router/guards.js b/src/router/guards.js
index 67de64c..4646765 100644
--- a/src/router/guards.js
+++ b/src/router/guards.js
@@ -1,4 +1,4 @@
-import {hasPermission, hasRole} from '@/utils/authority-utils'
+import {hasAuthority} from '@/utils/authority-utils'
 import {loginIgnore} from '@/router/index'
 import {checkAuthorization} from '@/utils/request'
 
@@ -30,7 +30,7 @@ const authorityGuard = (to, from, next, options) => {
   const {store, message} = options
   const permissions = store.getters['account/permissions']
   const roles = store.getters['account/roles']
-  if (!hasPermission(to, permissions) && !hasRole(to, roles)) {
+  if (!hasAuthority(to, permissions, roles)) {
     message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`)
     next({path: '/403'})
   } else {
diff --git a/src/utils/authority-utils.js b/src/utils/authority-utils.js
index e421e12..a08c474 100644
--- a/src/utils/authority-utils.js
+++ b/src/utils/authority-utils.js
@@ -1,11 +1,10 @@
 /**
  * 判断是否有路由的权限
- * @param route 路由
+ * @param authority 路由权限配置
  * @param permissions 用户权限集合
  * @returns {boolean|*}
  */
-function hasPermission(route, permissions) {
-  const authority = route.meta.authority || '*'
+function hasPermission(authority, permissions) {
   let required = '*'
   if (typeof authority === 'string') {
     required = authority
@@ -17,11 +16,10 @@ function hasPermission(route, permissions) {
 
 /**
  * 判断是否有路由需要的角色
- * @param route 路由
+ * @param authority 路由权限配置
  * @param roles 用户角色集合
  */
-function hasRole(route, roles) {
-  const authority = route.meta.authority || '*'
+function hasRole(authority, roles) {
   let required = undefined
   if (typeof authority === 'object') {
     required = authority.role
@@ -47,6 +45,23 @@ function hasAnyRole(required, roles) {
   }
 }
 
+/**
+ * 路由权限校验
+ * @param route 路由
+ * @param permissions 用户权限集合
+ * @param roles 用户角色集合
+ * @returns {boolean}
+ */
+function hasAuthority(route, permissions, roles) {
+  const authorities = [...route.meta.pAuthorities, route.meta.authority]
+  for (let authority of authorities) {
+    if (!hasPermission(authority, permissions) && !hasRole(authority, roles)) {
+      return false
+    }
+  }
+  return true
+}
+
 /**
  * 根据权限配置过滤菜单数据
  * @param menuData
@@ -56,7 +71,7 @@ function hasAnyRole(required, roles) {
 function filterMenu(menuData, permissions, roles) {
   menuData.forEach(menu => {
     if (menu.meta && menu.meta.invisible === undefined) {
-      menu.meta.invisible = !hasPermission(menu, permissions) && !hasRole(menu, roles)
+      menu.meta.invisible = !hasAuthority(menu, permissions, roles)
       if (menu.children && menu.children.length > 0) {
         filterMenu(menu.children, permissions, roles)
       }
@@ -64,4 +79,4 @@ function filterMenu(menuData, permissions, roles) {
   })
 }
 
-export {hasPermission, hasRole, filterMenu}
+export {filterMenu, hasAuthority}
diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js
index 7fc9160..68416ea 100644
--- a/src/utils/routerUtil.js
+++ b/src/utils/routerUtil.js
@@ -97,15 +97,16 @@ function mergeRoutes(target, source) {
 
 /**
  * 格式化路由的权限配置
- * @param routes
+ * @param routes 路由
+ * @param pAuthorities 父级路由权限配置集合
  */
-function formatAuthority(routes) {
+function formatAuthority(routes, pAuthorities = []) {
   routes.forEach(route => {
     const meta = route.meta
     if (meta) {
       let authority = {}
       if (!meta.authority) {
-        authority.permission = '*'
+        authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
       }else if (typeof meta.authority === 'string') {
         authority.permission = meta.authority
       } else if (typeof meta.authority === 'object') {
@@ -114,17 +115,20 @@ function formatAuthority(routes) {
         if (typeof role === 'string') {
           authority.role = [role]
         }
+        if (!authority.permission && !authority.role) {
+          authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
+        }
       } else {
         console.log(typeof meta.authority)
       }
       meta.authority = authority
     } else {
-      route.meta = {
-        authority: {permission: '*'}
-      }
+      const authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
+      route.meta = {authority}
     }
+    route.meta.pAuthorities = pAuthorities
     if (route.children) {
-      formatAuthority(route.children)
+      formatAuthority(route.children, [...pAuthorities, route.meta.authority])
     }
   })
 }

From 8102c8a9242ee176571a2c551ff8d1d5dd100781 Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Thu, 3 Sep 2020 18:24:28 +0800
Subject: [PATCH 07/12] =?UTF-8?q?feat:=20add=20deep=20merge=20routes=20fun?=
 =?UTF-8?q?ction=20for=20routerUtil.js;=20:star:=20=E6=96=B0=E5=A2=9E?=
 =?UTF-8?q?=EF=BC=9ArouterUtil.js=20=E5=B7=A5=E5=85=B7=E6=96=B0=E5=A2=9E?=
 =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E5=90=88=E5=B9=B6=E8=B7=AF=E7=94=B1=E6=96=B9?=
 =?UTF-8?q?=E6=B3=95=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/utils/routerUtil.js | 41 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 40 insertions(+), 1 deletion(-)

diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js
index 68416ea..dfab740 100644
--- a/src/utils/routerUtil.js
+++ b/src/utils/routerUtil.js
@@ -1,6 +1,7 @@
 import routerMap from '@/router/async/router.map'
 import {mergeI18nFromRoutes} from '@/utils/i18n'
 import Router from 'vue-router'
+import deepMerge from 'deepmerge'
 
 /**
  * 根据 路由配置 和 路由组件注册 解析路由
@@ -95,6 +96,44 @@ function mergeRoutes(target, source) {
   return Object.values(routesMap)
 }
 
+/**
+ * 深度合并路由
+ * @param target {Route[]}
+ * @param source {Route[]}
+ * @returns {Route[]}
+ */
+function deepMergeRoutes(target, source) {
+  // 映射路由数组
+  const mapRoutes = routes => {
+    const routesMap = {}
+    routes.forEach(item => {
+      routesMap[item.path] = {
+        ...item,
+        children: item.children ? mapRoutes(item.children) : undefined
+      }
+    })
+    return routesMap
+  }
+  const tarMap = mapRoutes(target)
+  const srcMap = mapRoutes(source)
+
+  // 合并路由
+  const merge = deepMerge(tarMap, srcMap)
+
+  // 转换为 routes 数组
+  const parseRoutesMap = routesMap => {
+    return Object.values(routesMap).map(item => {
+      if (item.children) {
+        item.children = parseRoutesMap(item.children)
+      } else {
+        delete item.children
+      }
+      return item
+    })
+  }
+  return parseRoutesMap(merge)
+}
+
 /**
  * 格式化路由的权限配置
  * @param routes 路由
@@ -164,4 +203,4 @@ function loadGuards(guards, options) {
   })
 }
 
-export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards}
+export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes}

From 4856f06f41563b8fa2d12aaa1247d8925eb43aa3 Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Thu, 3 Sep 2020 18:47:03 +0800
Subject: [PATCH 08/12] =?UTF-8?q?fix:=20configuration=20problem=20of=20fir?=
 =?UTF-8?q?st=20route's=20path;=20:bug:=20=E4=BF=AE=E5=A4=8D=EF=BC=9A?=
 =?UTF-8?q?=E4=B8=80=E7=BA=A7=E8=B7=AF=E7=94=B1=20path=20=E9=85=8D?=
 =?UTF-8?q?=E7=BD=AE=E9=97=AE=E9=A2=98=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/router/index.js     |  4 ++--
 src/utils/routerUtil.js | 18 ++++++++++++++++--
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/router/index.js b/src/router/index.js
index 3bdcc3c..c5aa144 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,6 +1,6 @@
 import Vue from 'vue'
 import Router from 'vue-router'
-import {formatAuthority} from '@/utils/routerUtil'
+import {formatRoutes} from '@/utils/routerUtil'
 
 Vue.use(Router)
 
@@ -25,7 +25,7 @@ const loginIgnore = {
  */
 function initRouter(isAsync) {
   const options = isAsync ? require('./async/config.async').default : require('./config').default
-  formatAuthority(options.routes)
+  formatRoutes(options.routes)
   return new Router(options)
 }
 export {loginIgnore, initRouter}
diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js
index dfab740..34a8941 100644
--- a/src/utils/routerUtil.js
+++ b/src/utils/routerUtil.js
@@ -66,7 +66,7 @@ function loadRoutes({router, store, i18n}, routesConfig) {
   if (asyncRoutes) {
     if (routesConfig && routesConfig.length > 0) {
       const routes = parseRoutes(routesConfig, routerMap)
-      formatAuthority(routes)
+      formatRoutes(routes)
       const finalRoutes = mergeRoutes(router.options.routes, routes)
       router.options = {...router.options, routes: finalRoutes}
       router.matcher = new Router({...router.options, routes:[]}).matcher
@@ -134,6 +134,20 @@ function deepMergeRoutes(target, source) {
   return parseRoutesMap(merge)
 }
 
+/**
+ * 格式化路由
+ * @param routes 路由配置
+ */
+function formatRoutes(routes) {
+  routes.forEach(route => {
+    const {path} = route
+    if (!path.startsWith('/') && path !== '*') {
+      route.path = '/' + path
+    }
+  })
+  formatAuthority(routes)
+}
+
 /**
  * 格式化路由的权限配置
  * @param routes 路由
@@ -203,4 +217,4 @@ function loadGuards(guards, options) {
   })
 }
 
-export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes}
+export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes, formatRoutes}

From 69b514ee10ff2118dc0089bad560db1910fcd960 Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Thu, 3 Sep 2020 19:01:16 +0800
Subject: [PATCH 09/12] chore: optimize the code of routerUtil.js

---
 src/utils/routerUtil.js | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js
index 34a8941..ce573c7 100644
--- a/src/utils/routerUtil.js
+++ b/src/utils/routerUtil.js
@@ -156,10 +156,11 @@ function formatRoutes(routes) {
 function formatAuthority(routes, pAuthorities = []) {
   routes.forEach(route => {
     const meta = route.meta
+    const defaultAuthority = pAuthorities[pAuthorities.length - 1] || {permission: '*'}
     if (meta) {
       let authority = {}
       if (!meta.authority) {
-        authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
+        authority = defaultAuthority
       }else if (typeof meta.authority === 'string') {
         authority.permission = meta.authority
       } else if (typeof meta.authority === 'object') {
@@ -169,14 +170,12 @@ function formatAuthority(routes, pAuthorities = []) {
           authority.role = [role]
         }
         if (!authority.permission && !authority.role) {
-          authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
+          authority = defaultAuthority
         }
-      } else {
-        console.log(typeof meta.authority)
       }
       meta.authority = authority
     } else {
-      const authority = pAuthorities.length > 0 ? pAuthorities[pAuthorities.length - 1] : {permission: '*'}
+      const authority = defaultAuthority
       route.meta = {authority}
     }
     route.meta.pAuthorities = pAuthorities

From 8a7b82ac500502b01a40b5997c02282c1151bc60 Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Sat, 5 Sep 2020 12:36:37 +0800
Subject: [PATCH 10/12] update docs

---
 docs/advance/login.md | 71 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 70 insertions(+), 1 deletion(-)

diff --git a/docs/advance/login.md b/docs/advance/login.md
index e0ae3e9..104caa1 100644
--- a/docs/advance/login.md
+++ b/docs/advance/login.md
@@ -3,5 +3,74 @@ title: 登录认证
 lang: zn-CN
 ---
 # 登录认证
+Vue Antd Admin 使用 js-cookie.js 管理用户的 token,结合 axios 配置,可以为每个请求头加上 token 信息。
 
-### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
+## token名称
+后端系统通常会从请求 header 中获取用户的 token,因此我们需要配置好 token 名称,好让后端能正确的识别到用户 token。
+Vue Antd Admin 默认token 名称为 `Authorization`,你可以在 /utils/request.js 中修改它。
+```js{5}
+import axios from 'axios'
+import Cookie from 'js-cookie'
+
+// 跨域认证信息 header 名
+const xsrfHeaderName = 'Authorization'
+...
+```
+## token 设置
+调用登录接口后拿到用户的 token 和 token 过期时间(如无过期时间,可忽略),并使用 /utils/request.js #setAuthorization 方法保存token。
+```js{5}
+import {setAuthorization} from '@/utils/request'
+
+login(name, password).then(res => {
+  const {token, expireAt} = res.data
+  setAuthorization({token, expireAt: new Date(expireAt)})
+})
+```
+## token 校验
+Vue Antd Admin 默认添加了登录导航守卫,如检查到本地cookie 中不包含 token 信息,则会拦截跳转至登录页。你可以在 /router/index.js 中配置
+不需要登录拦截的路由
+```js
+// 不需要登录拦截的路由配置
+const loginIgnore = {
+  names: ['404', '403'],      //根据路由名称匹配
+  paths: ['/login'],   //根据路由fullPath匹配
+  /**
+   * 判断路由是否包含在该配置中
+   * @param route vue-router 的 route 对象
+   * @returns {boolean}
+   */
+  includes(route) {
+    return this.names.includes(route.name) || this.paths.includes(route.path)
+  }
+}
+```
+或者在 /router/guards.js 中移出登录守卫
+```diff
+...
+export default {
+-  beforeEach: [loginGuard, authorityGuard, redirectGuard],
++  beforeEach: [authorityGuard, redirectGuard],
+  afterEach: []
+}
+```
+## Api
+### setAuthorization(auth, authType)
+来源:/utils/request.js  
+该方法用于保存用户 token,接收两个参数:  
+* **auth**   
+认证信息,包含 token、expireAt 等认证数据。  
+* **authType**  
+认证类型,默认为 `AUTH_TYPE.BEARER`(AUTH_TYPE.BEARER 默认会给token 加上 Bearer 识别前缀),可根据自己的认证类型自行扩展。  
+
+### checkAuthorization(authType)
+该方法用于校验用户 token 是否过期,接收一个参数:  
+* **authType**  
+认证类型,默认为 `AUTH_TYPE.BEARER`。 
+
+### removeAuthorization(authType)
+该方法用于移出用户本地存储的 token,接收一个参数:  
+* **authType**  
+认证类型,默认为 `AUTH_TYPE.BEARER`。
+:::tip
+以上 Api 均可在 /utils/request.js 文件中找到。
+:::
\ No newline at end of file

From 8b38e77922027e9e873c727c4b2b970066bc3fe3 Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Sat, 5 Sep 2020 21:07:00 +0800
Subject: [PATCH 11/12] update docs

---
 docs/.vuepress/config.js |   2 +-
 docs/advance/guard.md    | 108 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 106 insertions(+), 4 deletions(-)

diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 7a9d3f1..156ab7d 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -36,7 +36,7 @@ module.exports = {
         title: '进阶',
         collapsable: false,
         children: [
-          '/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors', '/advance/skill'
+          '/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors'
         ]
       },
       {
diff --git a/docs/advance/guard.md b/docs/advance/guard.md
index a43508c..274abfe 100644
--- a/docs/advance/guard.md
+++ b/docs/advance/guard.md
@@ -1,7 +1,109 @@
 ---
-title: 导航守卫
+title: 路由守卫
 lang: zn-CN
 ---
-# 导航守卫
+# 路由守卫
+Vue Antd Admin 使用 vue-router 实现路由导航功能,因此可以为路由配置一些守卫。  
+我们统一把导航守卫配置在 router/guards.js 文件中。
 
-### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
+## 前置守卫
+Vue Antd Admin 为每个前置导航守卫函数注入 to,from,next,options 四个参数:
+* `to: Route`: 即将要进入的目标[路由对象](https://router.vuejs.org/zh/api/#%E8%B7%AF%E7%94%B1%E5%AF%B9%E8%B1%A1)
+* `from: Route`: 当前导航正要离开的路由对象
+* `next: Function`: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。详情查看 [Vue Router #导航守卫](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。  
+如下,是登录拦截导航守卫的定义
+```js
+const loginGuard = (to, from, next, options) => {
+  const {message} = options
+  if (!loginIgnore.includes(to) && !checkAuthorization()) {
+    message.warning('登录已失效,请重新登录')
+    next({path: '/login'})
+  } else {
+    next()
+  }
+}
+```
+
+## 后置守卫
+你也可以定义后置导航守卫,Vue Antd Admin 为每个后置导航函数注入 to,from,options 三个参数:
+* `to: Route`: 即将要进入的目标[路由对象](https://router.vuejs.org/zh/api/#%E8%B7%AF%E7%94%B1%E5%AF%B9%E8%B1%A1)
+* `from: Route`: 当前导航正要离开的路由对象
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。  
+如下,是一个后置导航守卫的定义
+```js
+const afterGuard = (to, from, options) => {
+  const {store, message} = options
+  // 做些什么
+  message.info('do something')
+}
+```
+
+## 导出守卫配置
+定义好导航守卫后,只需按照类别在 guard.js 中导出即可。分为两类,`前置守卫`和`后置守卫`。如下:
+```js
+export default {
+  beforeEach: [loginGuard, authorityGuard],
+  afterEach: [afterGuard]
+}
+```
+
+:::details 点击查看完整的导航守卫配置
+```js
+import {loginIgnore} from '@/router/index'
+import {checkAuthorization} from '@/utils/request'
+
+/**
+ * 登录守卫
+ * @param to
+ * @param form
+ * @param next
+ * @param options
+ */
+const loginGuard = (to, from, next, options) => {
+  const {message} = options
+  if (!loginIgnore.includes(to) && !checkAuthorization()) {
+    message.warning('登录已失效,请重新登录')
+    next({path: '/login'})
+  } else {
+    next()
+  }
+}
+
+/**
+ * 权限守卫
+ * @param to
+ * @param form
+ * @param next
+ * @param options
+ */
+const authorityGuard = (to, from, next, options) => {
+  const {store, message} = options
+  const permissions = store.getters['account/permissions']
+  const roles = store.getters['account/roles']
+  if (!hasAuthority(to, permissions, roles)) {
+    message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`)
+    next({path: '/403'})
+  } else {
+    next()
+  }
+}
+
+/**
+ * 后置守卫
+ * @param to
+ * @param form
+ * @param options
+ */
+const afterGuard = (to, from, options) => {
+  const {store, message} = options
+  // 做些什么
+  message.info('do something')
+}
+
+export default {
+  beforeEach: [loginGuard, authorityGuard],
+  afterEach: [afterGuard]
+}
+```
+:::
\ No newline at end of file

From fd13c7d0427c76bfbbdde27fba38daf89bb31b6a Mon Sep 17 00:00:00 2001
From: chenghongxing <1126263215@qq.com>
Date: Sat, 5 Sep 2020 21:56:17 +0800
Subject: [PATCH 12/12] update docs

---
 docs/advance/interceptors.md | 126 ++++++++++++++++++++++++++++++++++-
 1 file changed, 125 insertions(+), 1 deletion(-)

diff --git a/docs/advance/interceptors.md b/docs/advance/interceptors.md
index 843bc6a..f34fc94 100644
--- a/docs/advance/interceptors.md
+++ b/docs/advance/interceptors.md
@@ -3,5 +3,129 @@ title: 拦截器配置
 lang: zn-CN
 ---
 # 拦截器配置
+Vue Antd Admin 基于 aixos 封装了 http 通信功能,我们可以为 http 请求响应配置一些拦截器。拦截器统一配置在 /utils/axios-interceptors.js 文件中。
+## 请求拦截器
+你可以为每个请求拦截器配置 `onFulfilled` 或 `onRejected` 两个钩子函数。
+### onFulfilled
+我们会为 onFulfilled 钩子函数注入 config 和 options 两个参数:
+* `config: AxiosRequestConfig`: axios 请求配置,详情参考 [axios 请求配置](http://www.axios-js.com/zh-cn/docs/#%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE)
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
 
-### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
+### onRejected
+我们会为 onFulfilled 钩子函数注入 error 和 options 两个参数:
+* `error: Error`: axios 请求错误对象
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。  
+  
+如下,为一个完整的请求拦截器配置:
+```js
+const tokenCheck = {
+  // 发送请求之前做些什么
+  onFulfilled(config, options) {
+    const {message} = options
+    const {url, xsrfCookieName} = config
+    if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
+      message.warning('认证 token 已过期,请重新登录')
+    }
+    return config
+  },
+  // 请求出错时做点什么
+  onRejected(error, options) {
+    const {message} = options
+    message.error(error.message)
+    return Promise.reject(error)
+  }
+}
+```
+## 响应拦截器
+响应拦截器也同样可以配置 `onFulfilled` 或 `onRejected` 两个钩子函数。
+### onFulfilled
+我们会为 onFulfilled 钩子函数注入 response 和 options 两个参数:
+* `response: AxiosResponse`: axios 响应对象,详情参考 [axios 响应对象](http://www.axios-js.com/zh-cn/docs/#%E5%93%8D%E5%BA%94%E7%BB%93%E6%9E%84)
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
+
+### onRejected
+我们会为 onFulfilled 钩子函数注入 error 和 options 两个参数:
+* `error: Error`: axios 请求错误对象
+* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。 
+
+如下,为一个完整的响应拦截器配置:
+```js
+const resp401 = {
+  // 响应数据之前做点什么
+  onFulfilled(response, options) {
+    const {message} = options
+    if (response.status === 401) {
+      message.error('无此接口权限')
+    }
+    return response
+  },
+  // 响应出错时做点什么
+  onRejected(error, options) {
+    const {message} = options
+    if (response.status === 401) {
+      message.error('无此接口权限')
+    }
+    return Promise.reject(error)
+  }
+}
+```
+## 导出拦截器
+定义好拦截器后,只需在 axios-interceptors.js 文件中导出即可。分为两类,`请求拦截器`和`响应拦截器`。如下:
+```js
+export default {
+  request: [tokenCheck], // 请求拦截
+  response: [resp401] // 响应拦截
+}
+```
+
+:::details 点击查看完整的拦截器配置示例
+```js
+import Cookie from 'js-cookie'
+// 401拦截
+const resp401 = {
+  onFulfilled(response, options) {
+    const {message} = options
+    if (response.status === 401) {
+      message.error('无此接口权限')
+    }
+    return response
+  },
+  onRejected(error, options) {
+    const {message} = options
+    message.error(error.message)
+    return Promise.reject(error)
+  }
+}
+
+const resp403 = {
+  onFulfilled(response, options) {
+    const {message} = options
+    if (response.status === 403) {
+      message.error(`请求被拒绝`)
+    }
+    return response
+  }
+}
+
+const reqCommon = {
+  onFulfilled(config, options) {
+    const {message} = options
+    const {url, xsrfCookieName} = config
+    if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
+      message.warning('认证 token 已过期,请重新登录')
+    }
+    return config
+  },
+  onRejected(error, options) {
+    const {message} = options
+    message.error(error.message)
+    return Promise.reject(error)
+  }
+}
+
+export default {
+  request: [reqCommon], // 请求拦截
+  response: [resp401, resp403] // 响应拦截
+}
+```
+:::
\ No newline at end of file