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 transactionAPI from './transaction'
|
||||
import routesAPI from './routes'
|
||||
import roleAPI from './role'
|
||||
|
||||
// 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
|
||||
// https://github.com/nuysoft/Mock/issues/300
|
||||
|
@ -19,7 +20,7 @@ Mock.XHR.prototype.send = function() {
|
|||
// timeout: '350-600'
|
||||
// })
|
||||
|
||||
// 路由表相关
|
||||
// 路由相关
|
||||
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(/\/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\/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',
|
||||
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',
|
||||
children: [
|
||||
{
|
||||
id: '67',
|
||||
path: 'log',
|
||||
component: 'errorLog/index',
|
||||
name: 'ErrorLog',
|
||||
|
@ -450,23 +461,23 @@ const asyncRoutesMap = [
|
|||
]
|
||||
},
|
||||
|
||||
// {
|
||||
// id: '53',
|
||||
// path: '/zip',
|
||||
// component: 'layout/Layout',
|
||||
// redirect: '/zip/download',
|
||||
// alwaysShow: true,
|
||||
// meta: { title: 'zip', icon: 'zip', roles: ['admin', 'editor'] },
|
||||
// children: [
|
||||
// {
|
||||
// id: '54',
|
||||
// path: 'download',
|
||||
// component: 'zip/index',
|
||||
// name: 'ExportZip',
|
||||
// meta: { title: 'exportZip', roles: ['admin', 'editor'] }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
id: '53',
|
||||
path: '/zip',
|
||||
component: 'layout/Layout',
|
||||
redirect: '/zip/download',
|
||||
alwaysShow: true,
|
||||
meta: { title: 'zip', icon: 'zip', roles: ['admin', 'editor'] },
|
||||
children: [
|
||||
{
|
||||
id: '54',
|
||||
path: 'download',
|
||||
component: 'zip/index',
|
||||
name: 'ExportZip',
|
||||
meta: { title: 'exportZip', roles: ['admin', 'editor'] }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
id: '55',
|
||||
|
|
|
@ -8,6 +8,8 @@ const _import = path => () => import(`@/views/${path}`)
|
|||
* @param route
|
||||
*/
|
||||
function hasPermission(roles, route) {
|
||||
// 如果是隐藏的菜单, 都是可访问的, 因为隐藏的菜单不会出现在左侧菜单栏, 不可编辑权限
|
||||
if (route.hidden) return true
|
||||
if (route.meta && route.meta.roles) {
|
||||
return roles.some(role => route.meta.roles.includes(role))
|
||||
} 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