获取用户信息
我们把 login
和get_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()
}
}
})
后台数据库存储的资源(菜单)信息
上图中 ,资源编码(如 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'),
参考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对应的页面 ,且无路由参数 。
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.。
改进点:
后端返回的是该用户可访问的全部菜单 ,并不是全部页面的菜单。
已经与用户绑定了,所以这里的权限过滤并没有起效 。
而且,后端并没有根据用户不同配置不同的角色状态,只是写死了每个登陆的用户都是admin,所以这里的else里永远不会触发,也就是这里的权限过滤还是没有使用。
这种方式是不是值得商榷呢?
2、登录跳转
验证成功就直接跳转首页,跳转的过程,就会触发Navigation Guards,Navigation Guards中,除了验证用户是否登录外,如果是首次登录,还会从后端查询用户的功能菜单,组装成前端的router.
3、路由和左边文件夹导航
如何和左边导航栏关联
导航菜单
通过注入路由器,我们可以在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由.
官方文档通篇都常使用 router
实例。留意一下 this.$router
和 router
使用起来完全一样。我们使用 this.$router
的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。
主页面布局:
布局层次示意图:
(单页面布局示意图)
Layout的template:
AppMain.vue 右边的视图窗口
appmain 中,有router-view class="view"
element-ui菜单menu:
它有四个
- el-menu
- el-submenu
- el-menu-item-group
- el-menu-item
这里el-menu定义了当前的导航菜单及属性,
el-submenu定义了子菜单栏
el-menu-item-group定义了菜单分组 我们项目暂时没用这个
el-menu-item 定义菜单项目
和router 结合,进行动态路由跳转有二种方式:
方式一:在<el-menu>里加上router属性的方式,官网描述如下:
这样就很方便,只要你<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.
ps:vue 利用component组件和is属性实现动态组件