From c9e4451974b4d85c9e4a14e500dc4c815c0ffe1d Mon Sep 17 00:00:00 2001 From: iczer <1126263215@qq.com> Date: Tue, 28 Jul 2020 20:48:02 +0800 Subject: [PATCH 1/4] chore: optimize the code of async router; :star2: --- .env | 4 + .env.development | 4 + src/bootstrap.js | 19 ++-- src/components/exception/ExceptionPage.vue | 12 ++- src/layouts/tabs/index.js | 2 + src/mock/user/login.js | 2 + src/mock/user/routes.js | 14 ++- src/pages/exception/403.vue | 7 +- src/pages/exception/404.vue | 2 +- src/pages/exception/500.vue | 7 +- src/pages/login/Login.vue | 13 ++- src/pages/login/index.js | 2 + src/router/config.async.js | 38 ++++++++ src/router/config.js | 100 ++++++++++++++++++--- src/router/index.js | 17 +--- src/router/router.map.js | 5 +- src/services/user.js | 4 +- src/store/modules/account.js | 50 +++++++++++ src/utils/routerUtil.js | 97 +++++++++++++++++--- 19 files changed, 331 insertions(+), 68 deletions(-) create mode 100644 .env create mode 100644 .env.development create mode 100644 src/layouts/tabs/index.js create mode 100644 src/pages/login/index.js create mode 100644 src/router/config.async.js diff --git a/.env b/.env new file mode 100644 index 0000000..58cfd7b --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +VUE_APP_NAME=admin +VUE_APP_ROUTES_KEY=admin.routes +VUE_APP_PERMISSIONS_KEY=admin.permissions +VUE_APP_ROLES_KEY=admin.roles diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..58cfd7b --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +VUE_APP_NAME=admin +VUE_APP_ROUTES_KEY=admin.routes +VUE_APP_PERMISSIONS_KEY=admin.permissions +VUE_APP_ROLES_KEY=admin.roles diff --git a/src/bootstrap.js b/src/bootstrap.js index 7b1ae4b..b3f0226 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -1,23 +1,18 @@ -// 应用启动时需要执行的操作放在这里 -import {loadRoutes} from '@/utils/routerUtil' +import {loadRoutes, loginGuard, authorityGuard} from '@/utils/routerUtil' /** * 启动引导方法 + * 应用启动时需要执行的操作放在这里 * @param router 应用的路由实例 * @param store 应用的 vuex.store 实例 * @param i18n 应用的 vue-i18n 实例 */ function bootstrap({router, store, i18n}) { - // 加载本地存储的异步路由 - const localRoutes = localStorage.getItem('routes') - if (localRoutes) { - try { - const routesConfig = JSON.parse(localRoutes) - loadRoutes(routesConfig, router, store, i18n) - } catch (e) { - console.error(e.message) - } - } + // 加载路由 + loadRoutes({router, store, i18n}) + // 添加路由守卫 + loginGuard(router) + authorityGuard(router, store) } export default bootstrap diff --git a/src/components/exception/ExceptionPage.vue b/src/components/exception/ExceptionPage.vue index 5c5316b..f9fb84a 100644 --- a/src/components/exception/ExceptionPage.vue +++ b/src/components/exception/ExceptionPage.vue @@ -7,7 +7,7 @@

{{config[type].title}}

{{config[type].desc}}
- 返回首页 + 返回首页
@@ -18,11 +18,19 @@ import Config from './typeConfig' export default { name: 'ExceptionPage', - props: ['type'], + props: ['type', 'homeRoute'], data () { return { config: Config } + }, + methods: { + backHome() { + if (this.homeRoute) { + this.$router.push(this.homeRoute) + } + this.$emit('backHome', this.type) + } } } diff --git a/src/layouts/tabs/index.js b/src/layouts/tabs/index.js new file mode 100644 index 0000000..60a363f --- /dev/null +++ b/src/layouts/tabs/index.js @@ -0,0 +1,2 @@ +import TabsView from './TabsView' +export default TabsView diff --git a/src/mock/user/login.js b/src/mock/user/login.js index 4bf77f5..abeb0cc 100644 --- a/src/mock/user/login.js +++ b/src/mock/user/login.js @@ -22,6 +22,8 @@ Mock.mock('/login', 'post', ({body}) => { result.data.user = user result.data.token = 'Authorization:' + Math.random() result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000) + result.data.permissions = [{id: 'demo', extra: ['add', 'edit', 'delete']}] + result.data.roles = [{id: 'admin', extra: ['add', 'edit', 'delete']}] } return result }) diff --git a/src/mock/user/routes.js b/src/mock/user/routes.js index 78f4316..8b4b96b 100644 --- a/src/mock/user/routes.js +++ b/src/mock/user/routes.js @@ -8,11 +8,21 @@ Mock.mock('/routes', 'get', () => { children: ['demo', { router: 'parent1', - children: ['demo'], + children: [{ + router: 'demo', + name: 'demo1', + authority: { + permission: 'demo', + role: 'admin' + } + }], }, { router: 'parent2', - children: ['demo'], + children: [{ + router: 'demo', + name: 'demo2' + }], }, { router: 'exception', diff --git a/src/pages/exception/403.vue b/src/pages/exception/403.vue index ca933ea..37f1041 100644 --- a/src/pages/exception/403.vue +++ b/src/pages/exception/403.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/exception/404.vue b/src/pages/exception/404.vue index fa4edfa..7bbca9b 100644 --- a/src/pages/exception/404.vue +++ b/src/pages/exception/404.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/login/Login.vue b/src/pages/login/Login.vue index 460fb79..3ed0a22 100644 --- a/src/pages/login/Login.vue +++ b/src/pages/login/Login.vue @@ -78,6 +78,7 @@ import CommonLayout from '@/layouts/CommonLayout' import {login, getRoutesConfig} from '@/services' import {setAuthorization} from '@/utils/request' import {loadRoutes} from '@/utils/routerUtil' +import {mapMutations} from 'vuex' export default { name: 'Login', @@ -95,6 +96,7 @@ export default { } }, methods: { + ...mapMutations('account', ['setUser', 'setPermissions', 'setRoles']), onSubmit (e) { e.preventDefault() this.form.validateFields((err) => { @@ -111,14 +113,17 @@ export default { const loginRes = res.data if (loginRes.code >= 0) { const user = loginRes.data.user + const permissions = loginRes.data.permissions + const roles = loginRes.data.roles + this.setUser(user) + this.setPermissions(permissions) + this.setRoles(roles) setAuthorization({token: loginRes.data.token, expireAt: new Date(loginRes.data.expireAt)}) // 获取路由配置 getRoutesConfig().then(result => { const routesConfig = result.data.data - localStorage.setItem('routes', JSON.stringify(routesConfig)) - loadRoutes(routesConfig, this.$router, this.$store, this.$i18n) - this.$router.push('/parent1/demo') - this.$store.commit('account/setUser', user) + loadRoutes({router: this.$router, store: this.$store, i18n: this.$i18n}, routesConfig) + this.$router.push('/demo') this.$message.success(loginRes.message, 3) }) } else { diff --git a/src/pages/login/index.js b/src/pages/login/index.js new file mode 100644 index 0000000..8b44975 --- /dev/null +++ b/src/pages/login/index.js @@ -0,0 +1,2 @@ +import Login from './Login' +export default Login diff --git a/src/router/config.async.js b/src/router/config.async.js new file mode 100644 index 0000000..4fc21b0 --- /dev/null +++ b/src/router/config.async.js @@ -0,0 +1,38 @@ +import routerMap from './router.map' +import {parseRoutes} from '@/utils/routerUtil' + +// 异步路由配置 +const routesConfig = [ + 'login', + 'root', + { + router: 'exp404', + path: '*', + name: '404' + }, + { + router: 'exp403', + path: '/403', + name: '403' + } +] + +const options = { + routes: parseRoutes(routesConfig, routerMap) +} + +// 不需要登录拦截的路由配置 +const loginIgnore = { + names: ['404'], //根据路由名称匹配 + paths: ['/login'], //根据路由fullPath匹配 + /** + * 判断路由是否包含在该配置中 + * @param route vue-router 的 route 对象 + * @returns {boolean} + */ + includes(route) { + return this.names.includes(route.name) || this.paths.includes(route.path) + } +} + +export {options, loginIgnore} diff --git a/src/router/config.js b/src/router/config.js index 61fe1f3..d882e52 100644 --- a/src/router/config.js +++ b/src/router/config.js @@ -1,19 +1,93 @@ -import routerMap from './router.map' -import {parseRoutes} from '@/utils/routerUtil' +import Login from '@/pages/login/Login' +import TabsView from '@/layouts/tabs/TabsView' +import BlankView from '@/layouts/BlankView' +import PageView from '@/layouts/PageView' // 路由配置 -const routesConfig = [ - 'login', - 'root', - { - router: 'exp404', - path: '*', - name: '404' - } -] - const options = { - routes: parseRoutes(routesConfig, routerMap) + routes: [ + { + path: '/login', + name: '登录页', + component: Login + }, + { + path: '*', + name: '404', + component: () => import('@/pages/exception/404'), + }, + { + path: '/', + name: '首页', + component: TabsView, + redirect: '/login', + children: [ + { + path: 'demo', + name: '演示页0', + meta: { + icon: 'file-ppt' + }, + component: () => import('@/pages/demo') + }, + { + path: 'parent1', + name: '父级路由1', + meta: { + icon: 'dashboard' + }, + component: BlankView, + children: [ + { + path: 'demo1', + name: '演示页面1', + component: () => import('@/pages/demo'), + } + ] + }, + { + path: 'parent2', + name: '父级路由2', + meta: { + icon: 'form' + }, + component: PageView, + children: [ + { + path: 'demo2', + name: '演示页面2', + component: () => import('@/pages/demo'), + } + ] + }, + { + path: 'exception', + name: '异常页', + meta: { + icon: 'warning', + }, + component: BlankView, + children: [ + { + path: '404', + name: 'Exp404', + component: () => import('@/pages/exception/404') + }, + { + path: '403', + name: 'Exp403', + component: () => import('@/pages/exception/403') + }, + { + path: '500', + name: 'Exp500', + component: () => import('@/pages/exception/500') + } + ] + }, + ] + } + ] } // 不需要登录拦截的路由配置 diff --git a/src/router/index.js b/src/router/index.js index fa07945..5a4a2a5 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,19 +1,10 @@ import Vue from 'vue' import Router from 'vue-router' -import {checkAuthorization} from '@/utils/request' -import {options, loginIgnore} from './config' +// import {options, loginIgnore} from './config' //本地路由配置 +import {options, loginIgnore} from './config.async' //异步路由配置 Vue.use(Router) +const router = new Router({...options}) -const router = new Router(options) - -// 登录拦截 -router.beforeEach((to, from, next) => { - if (!loginIgnore.includes(to) && !checkAuthorization()) { - next({path: '/login'}) - } else { - next() - } -}) - +export {loginIgnore} export default router diff --git a/src/router/router.map.js b/src/router/router.map.js index b486dfd..1132ff2 100644 --- a/src/router/router.map.js +++ b/src/router/router.map.js @@ -13,25 +13,22 @@ const routerMap = { component: () => import('@/pages/login') }, demo: { - authority: 'admin', name: '演示页', renderMenu: false, component: () => import('@/pages/demo') }, exp403: { - authority: 'admin', + authority: '*', name: 'exp403', path: '403', component: () => import('@/pages/exception/403') }, exp404: { - authority: '*', name: 'exp404', path: '404', component: () => import('@/pages/exception/404') }, exp500: { - authority: 'admin', name: 'exp500', path: '500', component: () => import('@/pages/exception/500') diff --git a/src/services/user.js b/src/services/user.js index 7046124..04c91c8 100644 --- a/src/services/user.js +++ b/src/services/user.js @@ -22,7 +22,9 @@ async function getRoutesConfig() { * 退出登录 */ function logout() { - localStorage.removeItem('routes') + localStorage.removeItem(process.env.VUE_APP_ROUTES_KEY) + localStorage.removeItem(process.env.VUE_APP_PERMISSIONS_KEY) + localStorage.removeItem(process.env.VUE_APP_ROLES_KEY) removeAuthorization() } diff --git a/src/store/modules/account.js b/src/store/modules/account.js index c02aea1..2e27883 100644 --- a/src/store/modules/account.js +++ b/src/store/modules/account.js @@ -10,6 +10,44 @@ export default { avatar: '', position: '', address: '' + }, + permissions: [], + roles: [], + routesConfig: [] + }, + getters: { + permissions: state => { + if (!state.permissions || state.permissions.length === 0) { + try { + const permissions = localStorage.getItem(process.env.VUE_APP_PERMISSIONS_KEY) + state.permissions = eval(permissions) ? JSON.parse(permissions) : state.permissions + } catch (e) { + console.error(e.message) + } + } + return state.permissions + }, + roles: state => { + if (!state.roles || state.roles.length === 0) { + try { + const roles = localStorage.getItem(process.env.VUE_APP_ROLES_KEY) + state.roles = eval(roles) ? JSON.parse(roles) : state.roles + } catch (e) { + console.error(e.message) + } + } + return state.roles + }, + routesConfig: state => { + if (!state.routesConfig || state.routesConfig.length === 0) { + try { + const routesConfig = localStorage.getItem(process.env.VUE_APP_ROUTES_KEY) + state.routesConfig = eval(routesConfig) ? JSON.parse(routesConfig) : state.routesConfig + } catch (e) { + console.error(e.message) + } + } + return state.routesConfig } }, mutations: { @@ -31,6 +69,18 @@ export default { throw e } }) + }, + setPermissions(state, permissions) { + state.permissions = permissions + localStorage.setItem(process.env.VUE_APP_PERMISSIONS_KEY, JSON.stringify(permissions)) + }, + setRoles(state, roles) { + state.roles = roles + localStorage.setItem(process.env.VUE_APP_ROLES_KEY, JSON.stringify(roles)) + }, + setRoutesConfig(state, routesConfig) { + state.routesConfig = routesConfig + localStorage.setItem(process.env.VUE_APP_ROUTES_KEY, JSON.stringify(routesConfig)) } } } diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js index 5792b30..57f0e06 100644 --- a/src/utils/routerUtil.js +++ b/src/utils/routerUtil.js @@ -1,6 +1,8 @@ import routerMap from '@/router/router.map' import {mergeI18nFromRoutes} from '@/utils/i18n' import Router from 'vue-router' +import {loginIgnore} from '@/router' +import {checkAuthorization} from '@/utils/request' /** * 根据 路由配置 和 路由组件注册 解析路由 @@ -10,7 +12,7 @@ import Router from 'vue-router' function parseRoutes(routesConfig, routerMap) { let routes = [] routesConfig.forEach(item => { - // 读取 router,初始化 routeCfg + // 获取注册在 routerMap 中的 router,初始化 routeCfg let router = undefined, routeCfg = {} if (typeof item === 'string') { router = routerMap[item] @@ -19,7 +21,7 @@ function parseRoutes(routesConfig, routerMap) { router = routerMap[item.router] routeCfg = item } - // 从 register 和 routeCfg 解析路由 + // 从 router 和 routeCfg 解析路由 if (!router) { console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`) } else { @@ -45,17 +47,88 @@ function parseRoutes(routesConfig, routerMap) { /** * 加载路由 + * @param router 应用路由实例 + * @param store 应用的 vuex.store 实例 + * @param i18n 应用的 vue-i18n 实例 * @param routesConfig - * @param router - * @param store */ -function loadRoutes(routesConfig, router, store, i18n) { - const routes = parseRoutes(routesConfig, routerMap) - router.matcher = new Router(router.options).matcher - router.addRoutes(routes) - const menuRoutes = routes.find(item => item.path === '/').children - mergeI18nFromRoutes(i18n, menuRoutes) - store.commit('setting/setMenuData', menuRoutes) +function loadRoutes({router, store, i18n}, routesConfig) { + // 如果 routesConfig 有值,则更新到本地localStorage,否则从本地localStorage获取 + if (routesConfig) { + store.commit('account/setRoutesConfig', routesConfig) + } else { + routesConfig = store.getters['account/routesConfig'] + } + if (routesConfig && routesConfig.length > 0) { + const routes = parseRoutes(routesConfig, routerMap) + router.matcher = new Router(router.options).matcher + router.addRoutes(routes) + const menuRoutes = routes.find(item => item.path === '/').children + mergeI18nFromRoutes(i18n, menuRoutes) + store.commit('setting/setMenuData', menuRoutes) + } } -export {parseRoutes, loadRoutes} +/** + * 登录守卫 + * @param router 应用路由实例 + */ +function loginGuard(router) { + router.beforeEach((to, from, next) => { + if (!loginIgnore.includes(to) && !checkAuthorization()) { + next({path: '/login'}) + } else { + next() + } + }) +} + +/** + * 权限守卫 + * @param router 应用路由实例 + * @param store 应用的 vuex.store 实例 + */ +function authorityGuard(router, store) { + router.beforeEach((to, form, next) => { + const permissions = store.getters['account/permissions'] + const roles = store.getters['account/roles'] + if (!hasPermission(to, permissions) && !hasRole(to, roles)) { + next({path: '/403'}) + } else { + next() + } + }) +} + +/** + * 判断是否有路由的权限 + * @param route 路由 + * @param permissions 用户权限集合 + * @returns {boolean|*} + */ +function hasPermission(route, permissions) { + const authority = route.meta.authority || '*' + let required = '*' + if (typeof authority === 'string') { + required = authority + } else if (typeof authority === 'object') { + required = authority.permission + } + return required === '*' || (permissions && permissions.findIndex(item => item === required || item.id === required) !== -1) +} + +/** + * 判断是否有路由需要的角色 + * @param route 路由 + * @param roles 用户角色集合 + */ +function hasRole(route, roles) { + const authority = route.meta.authority || '*' + let required = undefined + if (typeof authority === 'object') { + required = authority.role + } + return authority === '*' || (required && roles.findIndex(item => item === required || item.id === required) !== -1) +} + +export {parseRoutes, loadRoutes, loginGuard, authorityGuard} From bceb33996b9616c158aedb7ca34934cd9aedb6f2 Mon Sep 17 00:00:00 2001 From: iczer <1126263215@qq.com> Date: Tue, 28 Jul 2020 21:33:40 +0800 Subject: [PATCH 2/4] chore:replace pouchdb with localStorage; :star2: --- .env | 1 + .env.development | 1 + package.json | 1 - src/layouts/header/HeaderlAvatar.vue | 4 +- src/store/index.js | 9 +- src/store/modules/account.js | 43 ++-- yarn.lock | 341 +-------------------------- 7 files changed, 29 insertions(+), 371 deletions(-) diff --git a/.env b/.env index 58cfd7b..a776eb8 100644 --- a/.env +++ b/.env @@ -2,3 +2,4 @@ VUE_APP_NAME=admin VUE_APP_ROUTES_KEY=admin.routes VUE_APP_PERMISSIONS_KEY=admin.permissions VUE_APP_ROLES_KEY=admin.roles +VUE_APP_USER_KEY=admin.user diff --git a/.env.development b/.env.development index 58cfd7b..a776eb8 100644 --- a/.env.development +++ b/.env.development @@ -2,3 +2,4 @@ VUE_APP_NAME=admin VUE_APP_ROUTES_KEY=admin.routes VUE_APP_PERMISSIONS_KEY=admin.permissions VUE_APP_ROLES_KEY=admin.roles +VUE_APP_USER_KEY=admin.user diff --git a/package.json b/package.json index 8aa0a05..e0a99b4 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "enquire.js": "^2.1.6", "js-cookie": "^2.2.1", "mockjs": "^1.1.0", - "pouchdb": "^7.2.1", "viser-vue": "^2.4.8", "vue": "^2.6.11", "vue-i18n": "^8.18.2", diff --git a/src/layouts/header/HeaderlAvatar.vue b/src/layouts/header/HeaderlAvatar.vue index 256580a..5884727 100644 --- a/src/layouts/header/HeaderlAvatar.vue +++ b/src/layouts/header/HeaderlAvatar.vue @@ -25,13 +25,13 @@