diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index f93cdc0..224e2ff 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -17,7 +17,7 @@ :collapse-transition="false" mode="vertical" > - + @@ -38,11 +38,12 @@ export default { }, computed: { ...mapGetters([ + 'permission_routes', 'sidebar' ]), - routes() { - return this.$router.options.routes - }, + // routes() { + // return this.$router.options.routes + // }, activeMenu() { const route = this.$route const { meta, path } = route diff --git a/src/permission.js b/src/permission.js index 5240522..6725915 100644 --- a/src/permission.js +++ b/src/permission.js @@ -1,6 +1,6 @@ -import router from './router' +import router, { asyncRoutes } from './router' import store from './store' -import { Message } from 'element-ui' +// import { Message } from 'element-ui' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import getPageTitle from '@/utils/get-page-title' @@ -25,23 +25,30 @@ router.beforeEach(async(to, from, next) => { next({ path: '/' }) NProgress.done() } else { - const hasGetUserInfo = store.getters.name - if (hasGetUserInfo) { + if (to.name) { 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() - } + store.commit('permission/SET_ROUTES', asyncRoutes) + router.addRoutes(asyncRoutes) + next({ ...to, replace: true }) } + // 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() + // } + // } } } else { /* has no token*/ diff --git a/src/router/index.js b/src/router/index.js index 28045cc..b4db9a9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,7 +4,8 @@ import Router from 'vue-router' Vue.use(Router) /* Layout */ -import Layout from '@/layout' +import reagentRouter from './modules/reagent' +import userRouter from '@/router/modules/user' /** * Note: sub-menu only appear when route children.length >= 1 @@ -46,199 +47,16 @@ export const constantRoutes = [ path: '/', redirect: '/home' }, - { path: '/404', component: () => import('@/views/404'), hidden: true - }, - { - path: '/reagent', - component: Layout, - redirect: '/reagent/mainoverview/index' - }, - { - path: '/reagent/mainoverview', - component: Layout, - children: [ - { - path: 'index', - name: 'MainOverview', - meta: { title: '主预览', icon: '主概览' }, - component: () => import('@/views/reagent/mainoverview/index') - } - ] - }, + } +] - { - path: '/reagent/report', - component: Layout, - children: [ - { - path: 'index', - name: 'Report', - component: () => import('@/views/reagent/report/index'), - meta: { title: '报告统计', icon: '报表统计' } - }, - { - path: 'storeinfo', - name: 'StoreInfo', - component: () => import('@/views/reagent/report/storeinfo/index'), - hidden: true, - meta: { title: '库存信息总览', icon: '报表统计' } - }, - { - path: 'reagentinfo', - name: 'ReagentInfo', - component: () => import('@/views/reagent/report/reagentinfo/index'), - hidden: true, - meta: { title: '试剂信息详情', icon: '报表统计' } - }, - { - path: 'warehousinginfo', - name: 'WarehousingInfo', - component: () => import('@/views/reagent/report/warehousinginfo/index'), - hidden: true, - meta: { title: '入库信息查询', icon: '报表统计' } - }, - { - path: 'inventoryconsuminfo', - name: 'InventoryConsumInfo', - component: () => import('@/views/reagent/report/inventoryconsum/index'), - hidden: true, - meta: { title: '库存消耗', icon: '报表统计' } - }, - { - path: 'reagentconsuminfo', - name: 'ReagentConsumInfo', - component: () => import('@/views/reagent/report/reagentconsum/index'), - hidden: true, - meta: { title: '试剂消耗', icon: '报表统计' } - }, - { - path: 'userconsuminfo', - name: 'UserConsumInfo', - component: () => import('@/views/reagent/report/userconsum/index'), - hidden: true, - meta: { title: '人员用量消耗', icon: '报表统计' } - }, - { - path: 'usefrequencyinfo', - name: 'UseFrequencyInfo', - component: () => import('@/views/reagent/report/usefrequency/index'), - hidden: true, - meta: { title: '使用频率', icon: '报表统计' } - }, - { - path: 'recordinfo/:t', - name: 'RecordInfo', - component: () => import('@/views/reagent/report/record/index'), - hidden: true, - meta: { title: '记录', icon: '报表统计' } - } - ] - }, - { - path: '/reagent/warehousing', - component: Layout, - children: [ - { - path: 'index', - name: 'Warehousing', - component: () => import('@/views/reagent/warehousing/index'), - meta: { title: '试剂入库', icon: '试剂入库' } - } - ] - }, - { - path: '/reagent/receiving', - component: Layout, - children: [ - { - path: 'index', - name: 'Receiving', - component: () => import('@/views/reagent/receivingandreturn/index'), - meta: { title: '试剂领用', icon: '试剂领用' } - } - ] - }, - { - path: '/reagent/sendback', - component: Layout, - children: [ - { - path: 'index', - name: 'SendBack', - component: () => import('@/views/reagent/receivingandreturn/index'), - meta: { title: '试剂归还', icon: '试剂归还' } - } - ] - }, - { - path: '/reagent/weighing', - component: Layout, - children: [ - { - path: 'index', - name: 'weighing', - component: () => import('@/views/reagent/weighing/index'), - meta: { title: '试剂称重', icon: '试剂称重' } - } - ] - }, - { - path: '/reagent/inventory', - component: Layout, - children: [ - { - path: 'index', - name: 'Inventory', - component: () => import('@/views/reagent/inventory/index'), - meta: { title: '试剂盘点', icon: '库存盘点' } - } - ] - }, - { - path: '/reagent/database', - component: Layout, - children: [ - { - path: 'index', - name: 'Database', - component: () => import('@/views/reagent/database/index'), - meta: { title: '化学品数据库', icon: '化学品数据库' } - } - ] - }, - { - path: '/reagent/management', - component: Layout, - children: [ - { - path: 'index', - name: 'Management', - component: () => import('@/views/reagent/management/index'), - meta: { title: '试剂管理', icon: '试剂管理' } - } - ] - }, - { - path: '/reagent/buy', - component: Layout, - children: [ - { - path: 'index', - name: 'Buy', - component: () => import('@/views/reagent/buy/index'), - meta: { title: '请购', icon: '请购' } - } - ] - }, - { - path: '/gotohome', - redirect: '/home', - meta: { title: '返回主页', icon: 'el-icon-arrow-left' } - }, +export const asyncRoutes = [ + ...reagentRouter, + ...userRouter, // 404 page must be placed at the end !!! { path: '*', redirect: '/404', hidden: true } ] diff --git a/src/router/modules/reagent.js b/src/router/modules/reagent.js new file mode 100644 index 0000000..3b43d6b --- /dev/null +++ b/src/router/modules/reagent.js @@ -0,0 +1,202 @@ +import Layout from '@/layout' + +const reagentRouter = [ + { + path: '/reagent', + hidden: true, + redirect: '/reagent/mainoverview/index' + }, + { + path: '/reagent/mainoverview', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'MainOverview', + meta: { title: '主预览', icon: '主概览', class: 'reagent' }, + component: () => import('@/views/reagent/mainoverview/index') + } + ] + }, + { + path: '/reagent/report', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Report', + component: () => import('@/views/reagent/report/index'), + meta: { title: '报告统计', icon: '报表统计', class: 'reagent' } + }, + { + path: 'storeinfo', + name: 'StoreInfo', + component: () => import('@/views/reagent/report/storeinfo/index'), + hidden: true, + meta: { title: '库存信息总览', icon: '报表统计', class: 'reagent' } + }, + { + path: 'reagentinfo', + name: 'ReagentInfo', + component: () => import('@/views/reagent/report/reagentinfo/index'), + hidden: true, + meta: { title: '试剂信息详情', icon: '报表统计', class: 'reagent' } + }, + { + path: 'warehousinginfo', + name: 'WarehousingInfo', + component: () => import('@/views/reagent/report/warehousinginfo/index'), + hidden: true, + meta: { title: '入库信息查询', icon: '报表统计', class: 'reagent' } + }, + { + path: 'inventoryconsuminfo', + name: 'InventoryConsumInfo', + component: () => import('@/views/reagent/report/inventoryconsum/index'), + hidden: true, + meta: { title: '库存消耗', icon: '报表统计', class: 'reagent' } + }, + { + path: 'reagentconsuminfo', + name: 'ReagentConsumInfo', + component: () => import('@/views/reagent/report/reagentconsum/index'), + hidden: true, + meta: { title: '试剂消耗', icon: '报表统计', class: 'reagent' } + }, + { + path: 'userconsuminfo', + name: 'UserConsumInfo', + component: () => import('@/views/reagent/report/userconsum/index'), + hidden: true, + meta: { title: '人员用量消耗', icon: '报表统计', class: 'reagent' } + }, + { + path: 'usefrequencyinfo', + name: 'UseFrequencyInfo', + component: () => import('@/views/reagent/report/usefrequency/index'), + hidden: true, + meta: { title: '使用频率', icon: '报表统计', class: 'reagent' } + }, + { + path: 'recordinfo/:t', + name: 'RecordInfo', + component: () => import('@/views/reagent/report/record/index'), + hidden: true, + meta: { title: '记录', icon: '报表统计', class: 'reagent' } + } + ] + }, + { + path: '/reagent/warehousing', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Warehousing', + component: () => import('@/views/reagent/warehousing/index'), + meta: { title: '试剂入库', icon: '试剂入库', class: 'reagent' } + } + ] + }, + { + path: '/reagent/receiving', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Receiving', + component: () => import('@/views/reagent/receivingandreturn/index'), + meta: { title: '试剂领用', icon: '试剂领用', class: 'reagent' } + } + ] + }, + { + path: '/reagent/sendback', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'SendBack', + component: () => import('@/views/reagent/receivingandreturn/index'), + meta: { title: '试剂归还', icon: '试剂归还', class: 'reagent' } + } + ] + }, + { + path: '/reagent/weighing', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'weighing', + component: () => import('@/views/reagent/weighing/index'), + meta: { title: '试剂称重', icon: '试剂称重', class: 'reagent' } + } + ] + }, + { + path: '/reagent/inventory', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Inventory', + component: () => import('@/views/reagent/inventory/index'), + meta: { title: '试剂盘点', icon: '库存盘点', class: 'reagent' } + } + ] + }, + { + path: '/reagent/database', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Database', + component: () => import('@/views/reagent/database/index'), + meta: { title: '化学品数据库', icon: '化学品数据库', class: 'reagent' } + } + ] + }, + { + path: '/reagent/management', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Management', + component: () => import('@/views/reagent/management/index'), + meta: { title: '试剂管理', icon: '试剂管理', class: 'reagent' } + } + ] + }, + { + path: '/reagent/buy', + component: Layout, + meta: { class: 'reagent' }, + children: [ + { + path: 'index', + name: 'Buy', + component: () => import('@/views/reagent/buy/index'), + meta: { title: '请购', icon: '请购', class: 'reagent' } + } + ] + }, + { + path: '/gotohome', + redirect: '/home', + meta: { title: '返回主页', icon: 'el-icon-arrow-left', class: 'reagent' } + } +] + +export default reagentRouter diff --git a/src/router/modules/user.js b/src/router/modules/user.js new file mode 100644 index 0000000..0dddf3e --- /dev/null +++ b/src/router/modules/user.js @@ -0,0 +1,40 @@ +// import Layout from '@/layout' + +import Layout from '@/layout' + +const userRouter = [ + { + path: '/user', + redirect: '/user/info/index', + hidden: true, + meta: { class: 'user' } + }, + { + path: '/user/info', + component: Layout, + meta: { class: 'user' }, + children: [ + { + path: 'index', + component: () => import('@/views/user/index'), + name: 'UserInfo', + meta: { title: '用户信息', icon: 'lock', class: 'user' } + } + ] + }, + { + path: '/user/role', + component: Layout, + meta: { class: 'user' }, + children: [ + { + path: 'index', + component: () => import('@/views/user/role/index'), + name: 'Role', + meta: { title: '角色信息', icon: 'lock', class: 'user' } + } + ] + } +] + +export default userRouter diff --git a/src/store/getters.js b/src/store/getters.js index 5ab7b4c..d75c2ad 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -3,6 +3,7 @@ const getters = { device: state => state.app.device, token: state => state.user.token, avatar: state => state.user.avatar, - name: state => state.user.name + name: state => state.user.name, + permission_routes: state => state.permission.routes } export default getters diff --git a/src/store/index.js b/src/store/index.js index 03db22e..b79ee33 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,6 +4,7 @@ import getters from './getters' import app from './modules/app' import settings from './modules/settings' import user from './modules/user' +import permission from './modules/permission' import createPersistedState from 'vuex-persistedstate' Vue.use(Vuex) @@ -11,7 +12,8 @@ const store = new Vuex.Store({ modules: { app, settings, - user + user, + permission }, getters, plugins: [createPersistedState()] diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js new file mode 100644 index 0000000..aeb5ee5 --- /dev/null +++ b/src/store/modules/permission.js @@ -0,0 +1,69 @@ +import { asyncRoutes, constantRoutes } from '@/router' + +/** + * Use meta.role to determine if the current user has permission + * @param roles + * @param route + */ +function hasPermission(roles, route) { + if (route.meta && route.meta.roles) { + return roles.some(role => route.meta.roles.includes(role)) + } else { + return true + } +} + +/** + * Filter asynchronous routing tables by recursion + * @param routes asyncRoutes + * @param roles + */ +export function filterAsyncRoutes(routes, roles) { + const res = [] + + routes.forEach(route => { + const tmp = { ...route } + if (hasPermission(roles, tmp)) { + if (tmp.children) { + tmp.children = filterAsyncRoutes(tmp.children, roles) + } + res.push(tmp) + } + }) + + return res +} + +const state = { + routes: [], + addRoutes: [] +} + +const mutations = { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + } +} + +const actions = { + generateRoutes({ commit }, roles) { + return new Promise(resolve => { + let accessedRoutes + if (roles.includes('admin')) { + accessedRoutes = asyncRoutes || [] + } else { + accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) + } + commit('SET_ROUTES', accessedRoutes) + resolve(accessedRoutes) + }) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/utils/request.js b/src/utils/request.js index 2b3f515..a1c994c 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -18,7 +18,7 @@ service.interceptors.request.use( // let each request carry token // ['X-Token'] is a custom headers key // please modify it according to the actual situation - config.headers['Authorization'] = store.getters.token + config.headers['Authorization'] = 'Bearer ' + store.getters.token } return config }, @@ -53,11 +53,20 @@ service.interceptors.response.use( }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; - if (res.code === 50008 || res.code === 50012 || res.code === 50014) { + if (res.status === 401) { // to re-login - MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { - confirmButtonText: 'Re-Login', - cancelButtonText: 'Cancel', + // MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { + // confirmButtonText: 'Re-Login', + // cancelButtonText: 'Cancel', + // type: 'warning' + // }).then(() => { + // store.dispatch('user/resetToken').then(() => { + // location.reload() + // }) + // }) + MessageBox.confirm('登录超时请重新登录', '返回登录', { + confirmButtonText: '确认', + cancelButtonText: '取消', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 371de06..5a037b4 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -72,6 +72,7 @@ // import { validUsername } from '@/utils/validate' import stringify from '@/utils/stringify' import { login } from '@/api/user/user' +import { asyncRoutes } from '@/router' export default { name: 'Login', data() { @@ -131,8 +132,9 @@ export default { this.loading = true login(stringify(this.loginForm)).then(res => { this.$store.commit('user/SET_TOKEN', res.data.token) - console.log(this.$store.getters.token) - this.$router.push('/home') + this.$store.commit('permission/SET_ROUTES', asyncRoutes) + this.$router.addRoutes(asyncRoutes) + this.$router.replace('/home') }).finally( () => { this.loading = false diff --git a/src/views/user/index.vue b/src/views/user/index.vue new file mode 100644 index 0000000..d954d8a --- /dev/null +++ b/src/views/user/index.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/src/views/user/role/index.vue b/src/views/user/role/index.vue new file mode 100644 index 0000000..709727b --- /dev/null +++ b/src/views/user/role/index.vue @@ -0,0 +1,13 @@ + + + + +