Most of the time we do the background system, the crowd may be multifarious, most of the data displayed in the background system is the company’s related operation data, so it must strictly control the user’s rights. Whether the user has the right to access this menu, after the user visits this menu, whether the user has the right to add, delete, change and check, this is a qualified drop background system to have the function (type blackboard).
Define the JSON structure of the data returned by the permission interface
Authority module can be said to be the most important background system, it can be simple, can be complex, depending on how to define the product.
Usually background brother interface returned data body structure, he is in charge, he how to drop, we how to render. But in fact, this is very passive, in order to improve our development efficiency, we should put more energy on the page rather than put energy on racking their brains to think how to background data traversal into the structure I want, the secondary processing of data is sometimes one of the reasons we are mocked development slow ah! (slam cup
Therefore, properly communicate with the big brother of the background about the structure of the returned data body, and let the big brother of the background deal with it. Believe me, in fact, it is not so difficult to communicate.
But again, because of the special nature of the permission module, what is the structure of this piece returned? We need to provide the general dimension structure for the big brothers in the background.
My project defines the structure like this:
Here is the simplified structure, keeping the core fields. In this project, the menu has a secondary structure, the first level is the menu category, children represents the second level page, and below the second level is the route name of the page and the permission of the user under this menu. Here defined add, delete delete, edit, check four
[
{
name: 'Table',
children: [
{
name: 'TableDemo',
auth: {
add: true,
check: true,
delete: true,
edit: true}}]}]Copy the code
Define the routes that need to be dynamically loaded and mock interfaces
If there is a route that requires permission to access, we define a table.js file under router/ Modules, and the demo page below is only accessible if the relevant menu is returned in the background.
// table.js
const table = {
path: 'table'.component: (a)= > import('@/layout'),
redirect: '/table/demo'.name: 'Table'.meta: {
title: 'parentTitle'.icon: 'table'
},
children: [{path: '/table/demo'.name: 'TableDemo'.component: resolve= > void require(['@/views/table/demo'], resolve),
meta: {
title: 'tableDemo'}}, {path: '/table/demoTest'.name: 'DemoTest'.component: resolve= > void require(['@/views/table/demoTest'], resolve),
meta: {
title: 'demoTest'}}}]export default table
Copy the code
Mock interface data, here we only show the user the first submenu, the second is not shown
// mock/index.js
const permissionData = (a)= > {
result.data = [
{
name: 'Table'.children: [{name: 'TableDemo'.auth: {
add: true.check: true.delete: true.edit: true}}]}]return result
}
Mock.mock('/apiReplace/permission'.'post', permissionData)
Copy the code
The interface data has been defined in the mock. Can we start writing the logic of how to get dynamic routes
Define vuEX repository files that handle permission related logic.
To create permission.js in store/modules, we need to define the logic of routes and permissions in VUex, including initializing dynamic routes, resetting routes, etc.
// permission.js
/** The router file is the same as the router file. The router file is the same as the router file. Access without permission * asyncRoutes Route that requires access permission * notFoundRoutes 404 route * resetRouter route reset method */
import { asyncRoutes, constantRoutes, notFoundRoutes, resetRouter } from '@/router'
import API from '@/assets/http/apiUrl'
import Request from '@/assets/http'
const permission = {
state: {
routes: [].addRoutes: [] // Asynchronously loaded routes
},
mutations: {
SET_ROUTES: (state, routes) = > {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
},
actions: {
// Get the dynamic route
GenerateRoutes({ commit }, isSuperAdmin) {
resetRouter() // Initialize the route
return new Promise((resolve, reject) = > {
// If you are the super administrator, mount all routes and permissions
if (isSuperAdmin) {
// Redirect 404's matching rule must be at the end of the entire route definition, otherwise the refresh will fail.
const accessedRoutes = [...asyncRoutes, ...notFoundRoutes]
accessedRoutes.forEach(item= > {
if (item.children) {
// Assign all permissions to the super administrator
item.children.forEach(elem= >{ elem.meta = { ... elem.meta,check: true.delete: true.add: true.edit: true
}
})
}
})
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
} else {
Request.httpRequest({
method: 'post'.url: API.GetPermissionData,
noLoading: true.params: {},
success: (data) = > {
console.log(data)
let accessedRoutes = []
// Match the front end route with the back end menu
accessedRoutes = filterAsyncRoutes(asyncRoutes, data)
// Redirect 404's matching rule must be at the end of the entire route definition, otherwise the refresh will fail.accessedRoutes.push(... notFoundRoutes) commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
},
error: res= > {
reject(res)
}
})
}
})
}
}
}
/** * Filter asynchronous routing tables by recursion * @param routes @param menus The menu returned in the background */
export function filterAsyncRoutes(routes = [], menus = []) {
const res = []
routes.forEach(route= > {
// Copy the route so that changes to TMP will not affect the route
consttmp = { ... route }// Whether a match is found
if (hasPermission(menus, tmp)) { // There are matches
// Find the successful route entry
const findMenu = menus.find((menu, index, menus) = > {
return menu.name.includes(tmp.name)
})
/ / empowerment
if (findMenu.hasOwnProperty('auth')) { tmp.meta = { ... tmp.meta, ... findMenu.auth } }// If the route entry contains child routes, the child routes also need to be matched with the menu
if (findMenu.hasOwnProperty('children') && findMenu.children.length) {
// The steps for child routes are the same as those for parent routes
tmp.children = filterAsyncRoutes(tmp.children, findMenu.children)
} else {
// Delete the unmatched child route from the route
delete tmp.children
}
// The result is an asynchronous route value matching the background return menu
res.push(tmp)
}
})
return res
}
/** * Use meta. Role to determine if the current user has permission * @param menus back menu * @param route defined by the front end of the asynchronous route * /
function hasPermission(menus, route) {
// Match
if (route.name) { // The asynchronous route must have a name
// The matching rule is that the name must be the same, as long as the match is true, stop the loop
return menus.some(menu= > route.name.includes(menu.name))
} else {
return true}}export default permission
Copy the code
Generate dynamic routes in the project
With everything in place, it’s just a matter of where to call the method that generates the dynamic route. I prefer to make a judgment call each time I switch routes. If the current user is entering the project for the first time, I call the method of generating dynamic routes before the route jump, and then go down after the route is generated. So we can call methods that generate dynamic routes in the router.beforeEach hook function.
Create permission.js in the SRC directory to define the logic in router.beforeeach
import router from '@/router'
import store from '@/store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // Progress bar
import 'nprogress/nprogress.css'// Progress Indicates the Progress bar style
import getPageTitle from '@/assets/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'.'/register'.'/resetPsw'] // Do not redirect the whitelist
router.beforeEach(async(to, from, next) => {
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// Check whether there is a token
const token = localStorage.getItem('ADMIN_TOKEN')
if (token) {
if (whiteList.includes(to.path)) {
next()
NProgress.done()
} else {
// Check whether the current user refreshes the vuEX to prevent an infinite loop. If the user refreshes the VUEX, it indicates that the vuEX is normal. If the user does not refresh the VUEX, it indicates that the VUEX is refreshed
const hasUser = store.state.user.token
if (hasUser) {
next()
} else {
try {
// Prevent an infinite loop
await store.commit('SET_TOKEN', token)
// Is the super administrator
const isSuperAdmin = store.state.user.roles.some(item= > item.id === 1)
const accessRoutes = await store.dispatch('GenerateRoutes', isSuperAdmin)
// Load routes asynchronously
router.addRoutes(accessRoutes)
router.options.routes = store.state.permission.routes
// Set replace: true, navigation will not leave historynext({ ... to,replace: true})}catch (error) {
// Remove the token and redirect to the login page
await store.dispatch('ResetToken')
Message.error(error || 'Authentication error, please log in again. ')
next(`/login? redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/ / not token
if(whiteList.indexOf(to.path) ! = =- 1) {
next()
} else {
// next(`/login? Redirect =${to.path} ') // Otherwise all redirect to the login page
next('/login') // Otherwise all redirects to the login page
NProgress.done()
}
}
})
router.afterEach((a)= > {
NProgress.done() / / end of Progress
})
Copy the code
Then in the entry file import, global registration:
// main.js
import '@/permission'
Copy the code
Then run the project and you’ll see that the user can only access the first submenu.
Five, according to the permission to add restrictions for the page
If you are careful, you will notice that we have defined four permissions: Add, delete, delete, edit, and check under the meta header of each page route. $route.meta allows you to add, delete, change, and check the url. Here is a chestnut, and we define a table:
<template>
<div class="table-demo">
<el-card class="list-content" shadow="hover">
<template v-if="$route.meta.check">
<el-table
v-loading="tableLoading"
:data="tableData"
:cell-style="{ whiteSpace: 'nowrap'}"
:header-row-style="{ background: '#EBEEF5'}"
style="width: 100%"
class="table-content"
>
<el-table-column
type="index"
label="Serial number"
align="center"
sortable
width="50"
/>
<el-table-column
v-for="(item,index) in tableHeader"
:key="index"
:prop="index"
sortable
:label="item"
align="center"
/>
<el-table-column
label="Operation"
width="230"
align="center"
class-name="operation"
>
<template slot-scope="scope">
<a v-if="$route.meta.edit" class="item" @click="test(scope.row)"> modify </a> <a v-if="$route.meta.delete" class="item" @click="test(scope.row)"> Delete </a> </template> </el-table-column> </el-table> </template> <div V-else class="no-data"</div> </el-card> <! --> <el-pagination v-if="$route.meta.check"
:total="total"
:pager-count="5"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
:current-page="currentPage"
background
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
Copy the code
We can base it on
$route.meta.add
$route.meta.edit
$route.meta.delete
$route.meta.check
Copy the code
To control whether the corresponding entry is displayed or not
There are many details that have not been written out in detail. I will post the project address here. If you are interested, you can have a look
- A responsive background management system based on Vuecli3 and vuE-admin-Template transformation
Effect: