获取用户信息

我们把 loginget_user_info两件事分开处理,我们在守卫路由中获取路由信息。

router.beforeEach((to, from, next) => {
  if (!(store.getters.savestate === 0)) {
    Message({
      showClose: true,
      message: '正在编辑状态中,请先保存',
      type: 'error'
    })
    next(false)
    return
  }
  NProgress.start()
  // 如果已经获取了token
  if (getToken()) {
    // 如果将要跳转到的页面是/login
    if (to.path === '/login') {
      // 那么就直接调转到/
      next({ path: '/' })
      // 进度条结束
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
    } else {
      // 如果角色长度为零,即没有获取到角色
      // 判断当前用户是否已拉取完user_info信息
      if (store.getters.roles.length === 0) {
        // 调用action派遣事件发送请求获取用户信息(包括角色权限)。
        store.dispatch('GetInfo').then(res => { // 拉取用户信息
          // const roles = res.data.roles
          console.log('菜单树信息为:', res.data)
          var { admdivcode, admdivname, year, name, businessNb, officename } = res.data
          // 以后接口字段待调整。
          businessNb = '001财政局非税'
          var userInfo = { admdivcode, admdivname, year, name, businessNb, officename }
          store.dispatch('SetUserInfo', userInfo)
          store.dispatch('SetAllUserInfo', res.data.userinfo)
          store.dispatch('GenerateRoutes', res.data).then(() => { // 生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true })// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) => {
          // getInfo的api报错时,打印错误并返回根目录。
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    // 如果没有获取到token,但是要跳转的页面在白名单中。
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      // 如果没有获取到token,且要跳转的页面不在白名单中。
      // next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页,且记住之前的页面。
      next(`/login`) // 否则全部重定向到登录页,且记住之前的页面。
      NProgress.done()
    }
  }
})

后台数据库存储的资源(菜单)信息

element左侧导航做滑过显示子菜单_element左侧导航做滑过显示子菜单

上图中 ,资源编码(如 zcgl_wjgl)等是构成前端页面路由的基础。

我们会从后端/user/info 获取用户信息:返回的结构结构如下:

{
	"code": "200",
	"data": {
		"userShortcut": [],
		"year": "2019",
		"roles": ["admin"],
		"admdivcode": "420000",
		"menutree": [.....],
		"version": "2020",
		"oldYear": false,
		"admdivname": "省本级",
		"officename": "**局",
		"name": "**",
		"setid": "1234",
		"officecode": "80",
		"usercode": "8043",
		"userinfo": {
			"passwordquestion": null,
			"nid": "DCBFA88EF11C4D7AB2DD56AD06C9688B",
			"oid": "80",
			"cid": null,
			"guid": "DCBFA88EF11C4D7AB2DD56AD06C9688B",
			"year": "2019",
			"admdivcode": "420000",
			"pername": "**",
            ......
			"clientType": "1",
			"admdivname": "省本级",
			"officename": "**局",
			"officecode": "80",
			"ishall": null,
			"mack": "2020",
			"setid": "1234"
		},
		"username": "**"
	}
}

其中,形成菜单的节点为:

"menutree": [
		{},
		{
			"id": "402880b3348289190134835596f700ec",
			"text": "政策法规管理",
			"state": null,
			"checked": false,
			"attributes": {},
			"children": [{
				"id": "402880b33482891901348355d21c00ee",
				"text": "政策法规文件管理",
				"state": null,
				"checked": false,
				"attributes": {
					"butns": ["/zcfggl/zcfgwjgl/add", "/zcfggl/zcfgwjgl/delete", "/zcfggl/zcfgwjgl/update", "/zcfggl/zcfgwjgl/upload", "/zcfggl/zcfgwjgl/download", "/zcfggl/zcfgwjgl/print", "/zcfggl/zcfgwjgl/set", "/zcfggl/zcfgwjgl/export"]
				},
				"children": [],
				"parentId": "402880b3348289190134835596f700ec",
				"hasParent": true,
				"hasChildren": false,
				"icon": "i-zcfgwjgl",
				"btn": false,
				"menuId": "/zcfggl/zcfgwjgl",
				"levelnum": 0
			}, {
				"id": "402880b334828919013483561add00f0",
				"text": "政策法规文件查询",
				"state": null,
				"checked": false,
				"attributes": {
					"butns": ["/zcfggl/zcfgwjcx/print", "/zcfggl/zcfgwjcx/set", "/zcfggl/zcfgwjcx/export"]
				},
				"children": [],
				"parentId": "402880b3348289190134835596f700ec",
				"hasParent": true,
				"hasChildren": false,
				"icon": "i-zcfgwjcx",
				"btn": false,
				"menuId": "/zcfggl/zcfgwjcx",
				"levelnum": 0
			}, {
				"id": "402880b334828919013483565ff500f2",
				"text": "非税收入项目库查询",
				"state": null,
				"checked": false,
				"attributes": {
					"butns": ["/zcfggl/fssrxmkcx/print", "/zcfggl/fssrxmkcx/set", "/zcfggl/fssrxmkcx/export"]
				},
				"children": [],
				"parentId": "402880b3348289190134835596f700ec",
				"hasParent": true,
				"hasChildren": false,
				"icon": "i-fssrxmkcx",
				"btn": false,
				"menuId": "/zcfggl/fssrxmkcx",
				"levelnum": 0
			}, {
				"id": "402880b33482891901348357ddab00f6",
				"text": "单位非税收入项目查询",
				"state": null,
				"checked": false,
				"attributes": {
					"butns": ["/zcfggl/dwfssrxmcx/print", "/zcfggl/dwfssrxmcx/set", "/zcfggl/dwfssrxmcx/export"]
				},
				"children": [],
				"parentId": "402880b3348289190134835596f700ec",
				"hasParent": true,
				"hasChildren": false,
				"icon": "i-dwfssrxmcx",
				"btn": false,
				"menuId": "/zcfggl/dwfssrxmcx",
				"levelnum": 0
			}],
			"parentId": "-1",
			"hasParent": false,
			"hasChildren": true,
			"icon": "i-zcfggl",
			"btn": false,
			"menuId": "/zcfggl",
			"levelnum": 0
		},
		......
		{}
		],

前端根据后台泛返回的数据,构建前端菜单与路由(主要根据类似"menuId": "/zcfggl/zcfgwjgl"的节点)

router:

本段涉及:什么是路由,路由的使用步骤 动态路由  路由懒加载

路由是根据不同的 url 地址展示不同的内容或页面。

要使用router,必须先搞个路由地图。

静态路由:

1、定义组件

const Foo = {
        template:'<div>foo</div>'
        };
    const Bar = {
        template:'<div>bar</div>'
    }

2、定义路由地图

const routes = [
        {
            path:'/foo',
            component:Foo,
        },{
            path:'/bar',
            component:Bar
        }
    ];

3、根据路由地图创建路由实例:

const router = new VueRouter({
        routes:routes//可以简写 routes:routes
    })

4、路由实例注入到根实例:

const app = new Vue({
        router
    }).$mount('#app')

5、 整个应用都具有路由功能,项目可以根据自己创建的路由,导航到到不同的页面

<div id="app">
        <h1>hello app</h1>
        <p>
            <!-- 使用router-link组件来导航 -->
            <router-link to="/foo">Go to Foo</router-link>
            <router-link to="/bar">Go to Bar</router-link>
        </p>

        <!-- 
            路由出口路由匹配到的在这里
         -->
         <router-view></router-view>
    </div>

总结一下:route 是一条路由,routes 是一组路由,router是一个机制,相当于管理者,他来管理路由

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由.

官方文档通篇都常使用 router 实例。留意一下 this.$router 和 router 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。

路由的编程式导航:

静态方式下,使用router-link组件来导航,router-link根据“导航地图”,“渲染”相应router-view。

编程式导航,就是用router.push(...)代替rout-link组件,事实上,router-link其实调用的就是router.push(...)方法。

嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件.

从main.js中,可见,最外层是#app

import Vue from 'vue'
import App from './App'
......

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

app.vue如下:

<template>
  <div id="app">
    <!-- <div class="main-app">123</div> -->
    <router-view/>
  </div>
</template>
<script>
export default {
  name: 'App',
  data() {
    return {}
  }
}
</script>

从router中可见到,"/"即为Layout:

//路由
{
  path: '/',
  component: Layout,
  redirect: '/dashboard',
  name: 'Dashboard',
  hidden: true,
  children: [{
    path: 'dashboard',
    component: () => import('@/views/dashboard/index')
  }]
}

Layout.vue结构如下:

<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="disabled" class="shield" />
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <head-top/>
    <sidebar class="sidebar-container" />
    <div class="main-container">
      <navbar class="toggle-sidebar" />
      <app-main/>
    </div>
    <foot/>
  </div>
</template>

<script>
import { Navbar, Sidebar, AppMain } from './components'
import HeadTop from '@/components/headTop'
import Foot from '@/components/foot'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import Bus from '@/utils/Bus'
import { getCodeLabelDictionary } from '@/api/CommonApi'
import { getNowDate } from '@/api/login'

export default {
  name: 'Layout',
  components: {
    Navbar,
    Sidebar,
    AppMain,
    HeadTop,
    Foot
  },
  mixins: [ResizeMixin],
  computed: {
    ...
  },
  watch: {
    ...
  },
  created() {
    ...
  },

  methods: {
    ...
  }
}
</script>
<style lang="scss">
    ...
</style>

Layout.vue当中,可以看出,有左侧导航栏的区域了。

我们是动态路由和静态路由混用的:

import Vue from 'vue'
import Router from 'vue-router'


Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

//静态路由部分
export const constantRouterMap = [{
  path: '/login',
  component: () => import(`@/views/login/index`),
  hidden: true
},
{
  path: '/404',
  component: () => import('@/views/404'),
  hidden: true
},
{
  path: '/',
  component: Layout,
  redirect: '/dashboard',
  name: 'Dashboard',
  hidden: true,
  children: [{
    path: 'dashboard',
    component: () => import('@/views/dashboard/index')
  }]
}
]

export default new Router({
  // mode: 'history', //后端支持可开
  scrollBehavior: () => ({
    y: 0
  }),
  routes: constantRouterMap
})

// 异步挂载的路由
// 动态需要根据权限加载的路由表
export const asyncRouterMap = [
]

// component: () => import('@/components/permision'),

参考1:vue-router八个重要知识点应用图解

参考2:vue-router 基本使用

参考3:vue-router 快速入门

ps:嵌套路由与路由参数的比较,我前端同事给的解释如下:

嵌套路由的表现形式在url上是这样的 :   /path/pathtwo/paththree
路由参数在url上表现出是这样的: /path/pathtwo/paththree/154/887,这种形式需要在路由的path配置的时候写成 /path/pathtwo/paththree/:appid/:productid
或者以查询字符串形式带问号的那种:/path/pathtwo/paththree?appid=154&productid=887

如果路由参数以非查询字符串形式表示,确实可能和嵌套路由表现出的地址一样 。但是两者在路由router中的path是完全不一样的 。

如 /path/:paramsone/:paramstwo   ,结果paramsone的值为'pathtwo' ,paramstwo  的值为paththree' ,那么最终的路由地址是
 /path/pathtwo/paththree,
而如果此时你正好也写了一个子路由,path为  :/path/pathtwo/paththree

这两个url看起来一样 ,但是匹配的path和组件页面是完全不一样的 。前者匹配的是/path对应的页面 ,后面的paramsone,paramstwo  只不过是路由里面的参数 。
后者匹配的就是/path/pathtwo/paththree对应的页面 ,且无路由参数 。

参考:vue-router+vuex实现加载动态路由和菜单

1、路由 什么时候 什么地方从后端请求的

permission.js中有个守护路由router.beforeEach方法,是取得路由的核心代码。

ps:Promise vs  permission:

Promise:a declaration or assurance that one will do a particular thing or that a particular thing will happen.es6中,用这个词表示是异步函数。

permission:consent; authorization.。

改进点:

element左侧导航做滑过显示子菜单_Layout_02

后端返回的是该用户可访问的全部菜单 ,并不是全部页面的菜单。
已经与用户绑定了,所以这里的权限过滤并没有起效 。
而且,后端并没有根据用户不同配置不同的角色状态,只是写死了每个登陆的用户都是admin,所以这里的else里永远不会触发,也就是这里的权限过滤还是没有使用。

这种方式是不是值得商榷呢?

2、登录跳转

验证成功就直接跳转首页,跳转的过程,就会触发Navigation Guards,Navigation Guards中,除了验证用户是否登录外,如果是首次登录,还会从后端查询用户的功能菜单,组装成前端的router.

3、路由和左边文件夹导航

如何和左边导航栏关联

导航菜单

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由.

官方文档通篇都常使用 router 实例。留意一下 this.$router 和 router 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。

主页面布局:

布局层次示意图:

element左侧导航做滑过显示子菜单_vue.js_03

(单页面布局示意图) 

Layout的template:

element左侧导航做滑过显示子菜单_Layout_04

AppMain.vue  右边的视图窗口

appmain 中,有router-view class="view"

element-ui菜单menu:

它有四个

  1. el-menu
  2. el-submenu
  3. el-menu-item-group
  4. el-menu-item

这里el-menu定义了当前的导航菜单及属性,
el-submenu定义了子菜单栏
el-menu-item-group定义了菜单分组   我们项目暂时没用这个
el-menu-item  定义菜单项目

和router 结合,进行动态路由跳转有二种方式:

方式一:在<el-menu>里加上router属性的方式,官网描述如下:

element左侧导航做滑过显示子菜单_vue.js_05

这样就很方便,只要你<el-menu-item>里的index属性值正确就可以直接跳转了,不用再写js方法了

要理解这个element-ui  与router 形成的菜单,先看一个静态的例子:

<el-menu  router :default-active="$route.path" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" theme="dark">  
    <el-submenu index="1">  
        <template slot="title">  
            <i class="el-icon-location"></i>  
            <span>用户信息</span>  
        </template>  
        <el-menu-item-group>  
            <el-menu-item index="/user/account">账号信息</el-menu-item>  
            <el-menu-item index="/user/password">修改密码</el-menu-item>  
    </el-submenu>  
    <el-submenu index="2">  
            <template slot="title">  
            <i class="el-icon-location"></i>  
            <span>公司信息</span>  
        </template>  
        <el-menu-item-group>  
            <el-menu-item index="/company/userManager">用户管理</el-menu-item>  
            <el-menu-item index="/company/editUser">添加/编辑用户</el-menu-item>  
        </el-menu-item-group>  
        </el-submenu>  
</el-menu>

1.要实现路由跳转,先要在el-menu标签上添加router属性,然后只要在每个el-menu-item标签内的index属性设置一下url即可实现点击el-menu-item实现路由跳转。

2.导航当前项,在el-menu标签中绑定  :default-active="$route.path",注意是绑定属性,不要忘了加“:”,当$route.path等于el-menu-item标签中的index属性值时则该item为当前项。

方式二 利用link  或者 点击事件,进行路由跳转:

我们是采取这个思路解决问题。

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu :show-timeout="200" :default-active="$route.path" :collapse="isCollapse" :background-color="variables.menuBg" :text-color="variables.menuText" :active-text-color="variables.menuActiveText" unique-opened mode="vertical">
      <sidebar-item v-for="route in permission_routers" :key="route.path" :item="route" :base-path="route.path" />
    </el-menu>
  </el-scrollbar>
</template>

这里,我们看到el-menu没有router的属性了。sidebar-item 是我们自定义的一个组件。

<template>
  <div v-if="!item.hidden&&item.children" class="menu-wrapper">
<!--如果是末级菜单,可以直接链接-->
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon||item.meta.icon" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>
<!--如果是非末级菜单的情况-->
    <el-submenu v-else :index="resolvePath(item.path)">
        .......
    </el-submenu>
  </div>

</template>

<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'

export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  props: {
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      onlyOneChild: null
    }
  },
  methods: {
    hasOneShowingChild(children, parent) {
      .......
    },
    resolvePath(routePath) {
      if (this.isExternalLink(routePath)) {
        return routePath
      }
      return path.resolve(this.basePath, routePath)
    },
    isExternalLink(routePath) {
      return isExternal(routePath)
    }
  }
}
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
......
</style>

AppLink是我们自定义的一个带插槽的组件。插入的当然是menu_item.

element左侧导航做滑过显示子菜单_vue.js_06

ps:vue 利用component组件和is属性实现动态组件