根据后台返回路由表配置前端菜单
目录
根据后台返回路由表配置前端菜单
前言
本以为这个功能我2天就能做好,结果本大爷在公司都做了3天,再加上问我们领导和自己研究,花了四天时间。实际上这个不难,但是有很多细节要注意的,但是我们后台因为是其他项目的后台,他们之前前端用的不是vue-cli,所以返回的路由表数据并不是完整的路由表数据,但是跟我们前端还有一个key是匹配的,有这个key来连通后台数据和前端已有的路由表,就可以筛选出每个用户的权限。
理是这么个理,但是处理起来好多问题。
上代码
api/user.js—-获取权限的接口
/** *
* 获取菜单
*/
export function getAsyncRouter() {
return request({
url: '/auth/resource/menuPer?resPlate=3',
method: 'get'
})
}
router/index.js—–前端配置路由,404的页面一定要在最后
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
redirect: '/monitor'
},
{
path: '/monitor',
component: Layout,
redirect: '/monitor/traffic-monitor',
name: 'Monitor',
meta: { title: '实时监测', icon: 'shishijiance' },
children: [
{
path: 'traffic-monitor',
name: 'TrafficMonitor',
component: () => import('@/views/trafficmonitor/TrafficMonitor.vue'),
meta: { title: '事件监控', icon: 'monitor' }
},
{
path: 'running-monitor',
name: 'RunningMonitor',
component: () => import('@/views/trafficmonitor/RunningMonitor.vue'),
meta: { title: '运行监测', icon: 'yunxingjianche' }
},
{
path: 'video-monitor',
name: 'VideoMonitor',
component: () => import('@/views/videomonitor/VideoMonitor.vue'),
meta: { title: '视频监测', icon: 'shipinjianche' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: { title: '指挥调度', icon: 'command' },
children: [
{
path: 'menu2',
name: 'Menu2',
component: () => import('@/views/nested/menu2/EventWarning.vue'),
meta: { title: '事件研判', icon: 'shijianyujin' }
},
{
path: 'menu1',
name: 'Menu1',
component: () => import('@/views/nested/menu1/index.vue'),
meta: { title: '事件处理', icon: 'table' }
},
{
path: 'emergency-plan',
name: 'EmergencyPlan',
component: () => import('@/views/nested/Nested.vue'),
meta: { title: '应急预案', icon: 'yuan' },
children: [
{
path: 'plan',
name: 'Plan',
component: () => import('@/views/baseInfo/Plan'),
meta: { title: '预案管理', icon: 'table' }
},
{
path: 'process',
name: 'Process',
component: () => import('@/views/baseInfo/Process'),
meta: { title: '流程管理', icon: 'liuchen' }
}
]
},
{
path: 'emergency-resource',
name: 'EmergencyResource',
component: () => import('@/views/nested/Nested.vue'),
meta: { title: '应急资源', icon: 'resource' },
children: [
{
path: 'risk',
name: 'Risk',
component: () => import('@/views/baseInfo/Risk'),
meta: { title: '风险源管理', icon: 'table' }
},
{
path: 'team',
name: 'Team',
component: () => import('@/views/baseInfo/EmergencyTeam'),
meta: { title: '应急队伍', icon: 'table' }
},
{
path: 'vehicle',
name: 'Vehicle',
component: () => import('@/views/baseInfo/EmergencyVehicles'),
meta: { title: '应急车辆', icon: 'table' }
},
{
path: 'professor',
name: 'Professor',
component: () => import('@/views/baseInfo/EmergencyExperts'),
meta: { title: '应急专家', icon: 'table' }
},
{
path: 'supply',
name: 'Supply',
component: () => import('@/views/baseInfo/EmergencySupply'),
meta: { title: '应急物资', icon: 'table' }
}
]
}
]
},
{
path: '/equipment',
component: Layout,
redirect: '/equipment/weighing',
name: 'Equipment',
meta: { title: '设备管理', icon: 'devicemange' },
children: [
{
path: 'radio',
name: 'Radio',
component: () => import('@/views/equipment/Weighing.vue'),
meta: { title: '广播管理', icon: 'table' }
}
]
},
{
path: '/statistic',
component: Layout,
redirect: '/statistic/analyse',
name: 'Statistic',
meta: { title: '统计分析', icon: 'tongji' },
children: [
{
path: 'analyse',
name: 'Analyse',
component: () => import('@/views/statistic/Statistic.vue'),
meta: { title: '统计分析', icon: 'tongji' }
}
]
},
{
path: '/visual-model',
component: Layout,
redirect: '/visual-model/bim',
name: 'VisualModel',
meta: { title: '可视化模型', icon: 'bim' },
children: [
{
path: 'bim',
name: 'Bim',
component: () => import('@/views/bim/BimIndex.vue'),
meta: { title: '三维可视化', icon: 'bim' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
// filterRoute() // 根据用户权限筛选出路由表
// 生产环境不显示
if (process.env.NODE_ENV === 'production') {
constantRoutes.find((em, index) => {
const flag = em.meta && em.meta.hiddenInProd === true
if (flag) {
constantRoutes.splice(index, 1)
}
return flag
})
}
const createRouter = () =>
new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
permisson.js—-处理后台返回来的路由
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import { getAsyncRouter } from '@/api/user'
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
// await store.dispatch('user/getInfo')
next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
const route = router.options.routes
filterRoute(route)
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
// 筛选出用户权限
async function filterRoute(route) {
const resPermissions = {}
let asyncRouter = []
// 刷新时重新请求
await getAsyncRouter().then((res) => {
const { data } = res
asyncRouter = data
})
fetchName(asyncRouter)
setHidden(route)
// 取出用户权限
function fetchName(arr) {
arr.map((v, i) => {
resPermissions[v.url] = true
if (v.childNode) {
fetchName(v.childNode)
} else return
})
}
// 设置需要隐藏的菜单
function setHidden(arr) {
arr.map((item) => {
if (!item.name) { // 没有name,默认的不处理
return
} else {
item.hidden = !resPermissions[item.name] === true
if (item.hidden) { // 如果一级已经是hidden,则无需进行后面的循环判断
return
}
if (item.children) { // 如果有子集,则进行递归
setHidden(item.children)
} else return
}
})
}
}
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
login.vue—-因为我领导叫我将返回的路由数据存在vuex中,所以登陆时我触发了一个Action
export default {
name: 'Login',
data() {
// const validatePassword = (rule, value, callback) => {
// if (value.length < 6) {
// callback(new Error('The password can not be less than 6 digits'))
// } else {
// callback()
// }
// }
return {
loginForm: {
account: '',
password: '',
captcha: '12345',
loginSource: 1
},
loginRules: {
account: [{
required: true,
trigger: 'blur',
message: '请输入登录账号'
},
{
type: 'string',
min: 2,
max: 30,
message: '登录账号长度在 2 到 30 个字符之间',
trigger: 'blur'
}
],
password: [{
required: true,
trigger: 'blur',
message: '请输入密码'
}]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(async() => {
this.$router.push({
path: this.redirect || '/'
})
await this.$store.dispatch('user/getAsyncRouter') // 获取用户权限并存储在vuex中
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
// console.log('error submit!!')
return false
}
})
}
}
}
store/modules/user.js—-存储用户相关数据的vuex
import { login, getInfo, getAsyncRouter } from '@/api/user'
import { getToken, setToken, removeToken, getUserInfo, setUserInfo, removeUserInfo } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
userInfo: getUserInfo()
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_INFO: (state, userInfo) => {
state.userInfo = userInfo
},
SET_ROUTER: (state, asyncRouterMap) => {
state.asyncRouterMap = asyncRouterMap
}
}
const actions = {
// user login
login({ commit }, userInfo) {
// const { account, password } = userInfo
return new Promise((resolve, reject) => {
login(userInfo).then(response => {
const { data, token } = response
commit('SET_TOKEN', token)
commit('SET_INFO', data)
setToken(token)
setUserInfo(data)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('Verification failed, please Login again.')
}
const { name, avatar } = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// get user permission
getAsyncRouter({ commit, state }) {
return new Promise((resolve, reject) => {
getAsyncRouter().then(response => {
const { data } = response
commit('SET_ROUTER', data)// 存储在状态机
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
// logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
// }).catch(error => {
// reject(error)
// })
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
removeUserInfo()
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
注意点
permission.js中注意使用async和await,将异步请求拉平,不然拿不到数据,就是这个地方一开始没太懂async await的写法, 所以一直出不来数据。
对async await不太懂的,可以看看这篇文章
async function filterRoute(route) {
const resPermissions = {}
let asyncRouter = []
// 刷新时重新请求
await getAsyncRouter().then((res) => {
const { data } = res
asyncRouter = data
})
总结
每个公司每个项目每个后台返回的数据其实都跟网上的其实都不大一致,如果你非要要求你们后台给你返回网上要求的那种格式也无妨,但是怎么利用现有条件也能简单快速的完整需求也是自身的一种能力吧。
到现在我还是很多东西都是会写但是不知道为什么这么做的状态,但是项目要求时间紧,只能先做了再说,以后可能久了就会融会贯通了。
关于权限的处理,一定要自己去测试各种细节,然后自己总结吧,以后肯定一次会比一次好的。