feat role management
This commit is contained in:
parent
a7ccc6078b
commit
4080fba0bf
|
@ -0,0 +1,31 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function fetchRoles() {
|
||||||
|
return request({
|
||||||
|
url: '/roles',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteRole(id) {
|
||||||
|
return request({
|
||||||
|
url: `/roles/${id}`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newRole(data) {
|
||||||
|
return request({
|
||||||
|
url: '/roles',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRole(id, data) {
|
||||||
|
return request({
|
||||||
|
url: `/roles/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import articleAPI from './article'
|
||||||
import remoteSearchAPI from './remoteSearch'
|
import remoteSearchAPI from './remoteSearch'
|
||||||
import transactionAPI from './transaction'
|
import transactionAPI from './transaction'
|
||||||
import routesAPI from './routes'
|
import routesAPI from './routes'
|
||||||
|
import roleAPI from './role'
|
||||||
|
|
||||||
// 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
|
// 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
|
||||||
// https://github.com/nuysoft/Mock/issues/300
|
// https://github.com/nuysoft/Mock/issues/300
|
||||||
|
@ -19,7 +20,7 @@ Mock.XHR.prototype.send = function() {
|
||||||
// timeout: '350-600'
|
// timeout: '350-600'
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// 路由表相关
|
// 路由相关
|
||||||
Mock.mock(/\/routes/, 'get', routesAPI.getAsyncRoutesMap)
|
Mock.mock(/\/routes/, 'get', routesAPI.getAsyncRoutesMap)
|
||||||
|
|
||||||
// 登录相关
|
// 登录相关
|
||||||
|
@ -27,6 +28,12 @@ Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername)
|
||||||
Mock.mock(/\/login\/logout/, 'post', loginAPI.logout)
|
Mock.mock(/\/login\/logout/, 'post', loginAPI.logout)
|
||||||
Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo)
|
Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo)
|
||||||
|
|
||||||
|
// 角色相关
|
||||||
|
Mock.mock(/\/roles/, 'get', roleAPI.getRoles)
|
||||||
|
Mock.mock(/\/roles$/, 'post', roleAPI.newRole)
|
||||||
|
Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'put', roleAPI.updateRole)
|
||||||
|
Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'delete', roleAPI.deleteRole)
|
||||||
|
|
||||||
// 文章相关
|
// 文章相关
|
||||||
Mock.mock(/\/article\/list/, 'get', articleAPI.getList)
|
Mock.mock(/\/article\/list/, 'get', articleAPI.getList)
|
||||||
Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle)
|
Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle)
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
import Mock from 'mockjs'
|
||||||
|
|
||||||
|
// admin 角色可以访问所有菜单
|
||||||
|
const roles = [
|
||||||
|
{
|
||||||
|
id: 'editor', // 角色id
|
||||||
|
name: 'editor',
|
||||||
|
description: '编辑',
|
||||||
|
accessibleRoutes: [
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'10',
|
||||||
|
'11',
|
||||||
|
'12',
|
||||||
|
'13',
|
||||||
|
'14',
|
||||||
|
'15',
|
||||||
|
'16',
|
||||||
|
'17',
|
||||||
|
'18',
|
||||||
|
'19',
|
||||||
|
'20',
|
||||||
|
'21',
|
||||||
|
'22',
|
||||||
|
'23',
|
||||||
|
'24',
|
||||||
|
'25',
|
||||||
|
'26',
|
||||||
|
'27',
|
||||||
|
'28',
|
||||||
|
'29',
|
||||||
|
'30',
|
||||||
|
'31',
|
||||||
|
'32',
|
||||||
|
'33',
|
||||||
|
'34',
|
||||||
|
'35',
|
||||||
|
'36',
|
||||||
|
'37',
|
||||||
|
'38',
|
||||||
|
'39',
|
||||||
|
'40',
|
||||||
|
'41',
|
||||||
|
'42',
|
||||||
|
'43',
|
||||||
|
'44',
|
||||||
|
'45',
|
||||||
|
'46',
|
||||||
|
'47',
|
||||||
|
'48',
|
||||||
|
'49',
|
||||||
|
'50',
|
||||||
|
'51',
|
||||||
|
'52',
|
||||||
|
'53',
|
||||||
|
'54',
|
||||||
|
'55',
|
||||||
|
'56',
|
||||||
|
'57',
|
||||||
|
'58',
|
||||||
|
'59',
|
||||||
|
'60',
|
||||||
|
'61',
|
||||||
|
'62',
|
||||||
|
'63',
|
||||||
|
'64',
|
||||||
|
'65',
|
||||||
|
'66',
|
||||||
|
'67'
|
||||||
|
] // 可访问的菜单id列表
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2', // 角色id
|
||||||
|
name: 'operator',
|
||||||
|
description: '运营',
|
||||||
|
accessibleRoutes: [
|
||||||
|
'1',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'10',
|
||||||
|
'11',
|
||||||
|
'12',
|
||||||
|
'13',
|
||||||
|
'14',
|
||||||
|
'15',
|
||||||
|
'16',
|
||||||
|
'17',
|
||||||
|
'18',
|
||||||
|
'19',
|
||||||
|
'20',
|
||||||
|
'21',
|
||||||
|
'22',
|
||||||
|
'23',
|
||||||
|
'24',
|
||||||
|
'25',
|
||||||
|
'26',
|
||||||
|
'27',
|
||||||
|
'28',
|
||||||
|
'29',
|
||||||
|
'30',
|
||||||
|
'31',
|
||||||
|
'32',
|
||||||
|
'33',
|
||||||
|
'34',
|
||||||
|
'35',
|
||||||
|
'36',
|
||||||
|
'37',
|
||||||
|
'38',
|
||||||
|
'39',
|
||||||
|
'40',
|
||||||
|
'41',
|
||||||
|
'42',
|
||||||
|
'43',
|
||||||
|
'44',
|
||||||
|
'45',
|
||||||
|
'46',
|
||||||
|
'47',
|
||||||
|
'48',
|
||||||
|
'49',
|
||||||
|
'50',
|
||||||
|
'51',
|
||||||
|
'52',
|
||||||
|
'53',
|
||||||
|
'54',
|
||||||
|
'55',
|
||||||
|
'56',
|
||||||
|
'57',
|
||||||
|
'58',
|
||||||
|
'59',
|
||||||
|
'60',
|
||||||
|
'61',
|
||||||
|
'62',
|
||||||
|
'63',
|
||||||
|
'64',
|
||||||
|
'65'
|
||||||
|
] // 可访问的菜单id列表
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getRoles() {
|
||||||
|
return roles
|
||||||
|
},
|
||||||
|
newRole() {
|
||||||
|
const res = {
|
||||||
|
data: Mock.mock('id')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
updateRole() {
|
||||||
|
const res = {
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
deleteRole() {
|
||||||
|
const res = {
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,16 @@ const asyncRoutesMap = [
|
||||||
title: 'directivePermission',
|
title: 'directivePermission',
|
||||||
roles: ['admin', 'editor']
|
roles: ['admin', 'editor']
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '66',
|
||||||
|
path: 'role',
|
||||||
|
component: 'permission/role',
|
||||||
|
name: 'role',
|
||||||
|
meta: {
|
||||||
|
title: 'roleManagement',
|
||||||
|
roles: ['admin']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -406,6 +416,7 @@ const asyncRoutesMap = [
|
||||||
redirect: 'noredirect',
|
redirect: 'noredirect',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
id: '67',
|
||||||
path: 'log',
|
path: 'log',
|
||||||
component: 'errorLog/index',
|
component: 'errorLog/index',
|
||||||
name: 'ErrorLog',
|
name: 'ErrorLog',
|
||||||
|
@ -450,23 +461,23 @@ const asyncRoutesMap = [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
{
|
||||||
// id: '53',
|
id: '53',
|
||||||
// path: '/zip',
|
path: '/zip',
|
||||||
// component: 'layout/Layout',
|
component: 'layout/Layout',
|
||||||
// redirect: '/zip/download',
|
redirect: '/zip/download',
|
||||||
// alwaysShow: true,
|
alwaysShow: true,
|
||||||
// meta: { title: 'zip', icon: 'zip', roles: ['admin', 'editor'] },
|
meta: { title: 'zip', icon: 'zip', roles: ['admin', 'editor'] },
|
||||||
// children: [
|
children: [
|
||||||
// {
|
{
|
||||||
// id: '54',
|
id: '54',
|
||||||
// path: 'download',
|
path: 'download',
|
||||||
// component: 'zip/index',
|
component: 'zip/index',
|
||||||
// name: 'ExportZip',
|
name: 'ExportZip',
|
||||||
// meta: { title: 'exportZip', roles: ['admin', 'editor'] }
|
meta: { title: 'exportZip', roles: ['admin', 'editor'] }
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
// },
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: '55',
|
id: '55',
|
||||||
|
|
|
@ -8,6 +8,8 @@ const _import = path => () => import(`@/views/${path}`)
|
||||||
* @param route
|
* @param route
|
||||||
*/
|
*/
|
||||||
function hasPermission(roles, route) {
|
function hasPermission(roles, route) {
|
||||||
|
// 如果是隐藏的菜单, 都是可访问的, 因为隐藏的菜单不会出现在左侧菜单栏, 不可编辑权限
|
||||||
|
if (route.hidden) return true
|
||||||
if (route.meta && route.meta.roles) {
|
if (route.meta && route.meta.roles) {
|
||||||
return roles.some(role => route.meta.roles.includes(role))
|
return roles.some(role => route.meta.roles.includes(role))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container wrapper">
|
||||||
|
<el-button type="primary" @click="handleNewRole">新增角色</el-button>
|
||||||
|
|
||||||
|
<el-table :data="rolesData" style="width: 100%" class="roles-table">
|
||||||
|
<el-table-column label="角色名称" width="220">
|
||||||
|
<template slot-scope="scope">{{ scope.row.name }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="角色描述">
|
||||||
|
<template slot-scope="scope">{{ scope.row.describe }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button type="primary" size="small" @click="handleEdit(scope)">修改权限</el-button>
|
||||||
|
<el-button type="danger" size="small" @click="handleDelete(scope)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-dialog :visible.sync="newRoleDialogVisible" title="新增角色">
|
||||||
|
<el-form :model="newRole" class="new-role-form">
|
||||||
|
<el-form-item label="角色名称">
|
||||||
|
<el-input v-model="newRole.name" placeholder="角色名称"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角色描述">
|
||||||
|
<el-input v-model="newRole.describe" placeholder="角色描述"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" @click="confirmNewRole">确定</el-button>
|
||||||
|
<el-button type="danger" @click="cancleNewRole">取消</el-button>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog :visible.sync="permissionDialogVisible" title="修改权限">
|
||||||
|
<el-form label-width="100px">
|
||||||
|
<el-form-item label="角色名称">
|
||||||
|
<span>{{ checkedRole.data.name }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="菜单列表">
|
||||||
|
<el-tree ref="tree" :check-strictly="checkStrictly" :data="MapRoutesData" :props="defaultProps" :filter-node-method="filterNode" show-checkbox node-key="id" class="permission-tree"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="confirmPermission">确定</el-button>
|
||||||
|
<el-button type="danger" @click="canclePermission">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { deepClone } from '@/utils'
|
||||||
|
import { generateTitle } from '@/utils/i18n'
|
||||||
|
import { newRole, deleteRole, updateRole, fetchRoles } from '@/api/role'
|
||||||
|
import { fetchAsyncRoutes } from '@/api/routes'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checkStrictly: false,
|
||||||
|
routesData: [],
|
||||||
|
newRoleDialogVisible: false,
|
||||||
|
permissionDialogVisible: false,
|
||||||
|
defaultProps: {
|
||||||
|
children: 'children',
|
||||||
|
label: 'name'
|
||||||
|
},
|
||||||
|
newRole: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
describe: '',
|
||||||
|
accessibleRoutes: []
|
||||||
|
},
|
||||||
|
checkedRole: {
|
||||||
|
index: null,
|
||||||
|
data: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
describe: '',
|
||||||
|
accessibleRoutes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rolesData: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
MapRoutesData() {
|
||||||
|
const traverseRoutes = routes => {
|
||||||
|
return routes.map(route => {
|
||||||
|
if (route.children && this.onlyOneShowingChild(route.children, route) && !route.alwaysShow) {
|
||||||
|
route = this.onlyOneShowingChild(route.children, route)
|
||||||
|
}
|
||||||
|
route.children && (route.children = traverseRoutes(route.children))
|
||||||
|
route.name = this.generateTitle((route.meta && route.meta.title) || route.name || route.path || Math.random() + '')
|
||||||
|
return route
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return traverseRoutes(this.routesData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
fetchAsyncRoutes().then(res => {
|
||||||
|
this.routesData = res.data
|
||||||
|
})
|
||||||
|
fetchRoles().then(res => {
|
||||||
|
console.log(res)
|
||||||
|
this.rolesData = res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateTitle,
|
||||||
|
// 过滤掉隐藏的菜单
|
||||||
|
filterNode(value, data) {
|
||||||
|
return !data.hidden
|
||||||
|
},
|
||||||
|
// reference: src/view/layout/components/Sidebar/SidebarItem.vue
|
||||||
|
onlyOneShowingChild(children, parent) {
|
||||||
|
let onlyOneChild = null
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
if (item.hidden) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// When there is only one child router, the child router is displayed by default
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
onlyOneChild = showingChildren[0]
|
||||||
|
return onlyOneChild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show parent if there are no child router to display
|
||||||
|
if (showingChildren.length === 0) {
|
||||||
|
onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
||||||
|
return onlyOneChild
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
handleNewRole() {
|
||||||
|
this.newRoleDialogVisible = true
|
||||||
|
},
|
||||||
|
handleEdit(scope) {
|
||||||
|
this.checkedRole.index = scope.$index
|
||||||
|
// 修复setCheckedKeys leaf only情况下, none-leaf node不会被check的bug
|
||||||
|
this.checkStrictly = true
|
||||||
|
this.checkedRole.data = deepClone(scope.row)
|
||||||
|
this.permissionDialogVisible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.tree.filter()
|
||||||
|
this.$refs.tree.setCheckedKeys(
|
||||||
|
this.checkedRole.data.accessibleRoutes,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
this.checkStrictly = false
|
||||||
|
})
|
||||||
|
// this.checkStrictly = false
|
||||||
|
},
|
||||||
|
handleDelete(scope) {
|
||||||
|
this.$confirm('此操作将删除角色, 是否继续?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return deleteRole(scope.row.id)
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.rolesData.splice(scope.$index, 1)
|
||||||
|
this.$message({
|
||||||
|
type: 'success',
|
||||||
|
message: '删除成功!'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
this.$message({
|
||||||
|
type: 'info',
|
||||||
|
message: '已取消删除'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
confirmPermission() {
|
||||||
|
let routeIds = this.$refs.tree
|
||||||
|
.getCheckedNodes(false, true).reduce((result, node) => {
|
||||||
|
// 如果父菜单被隐藏(onlyOneShowingChild),需要把父菜单的id也添加进去
|
||||||
|
return result.concat(node.id, node.parentId)
|
||||||
|
}, [])
|
||||||
|
routeIds = Array.from(new Set(routeIds))
|
||||||
|
updateRole(this.checkedRole.data.id, {
|
||||||
|
...this.rolesData[this.checkedRole.index],
|
||||||
|
accessibleRoutes: routeIds
|
||||||
|
}).then(res => {
|
||||||
|
this.rolesData[this.checkedRole.index].accessibleRoutes = routeIds
|
||||||
|
this.$message({
|
||||||
|
message: '编辑成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.permissionDialogVisible = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
canclePermission() {
|
||||||
|
this.permissionDialogVisible = false
|
||||||
|
},
|
||||||
|
resetNewRole() {
|
||||||
|
this.newRole = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
describe: '',
|
||||||
|
accessibleRoutes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmNewRole() {
|
||||||
|
newRole(this.newRole).then(res => {
|
||||||
|
this.newRole.id = res.data.id
|
||||||
|
this.rolesData.push(deepClone(this.newRole))
|
||||||
|
this.resetNewRole()
|
||||||
|
this.newRoleDialogVisible = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancleNewRole() {
|
||||||
|
this.resetNewRole()
|
||||||
|
this.newRoleDialogVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
.roles-table {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
.permission-tree {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue