feat role management

This commit is contained in:
Serge Gao 2019-02-15 11:06:02 +08:00
parent a7ccc6078b
commit 4080fba0bf
6 changed files with 483 additions and 18 deletions

31
src/api/role.js Normal file
View File

@ -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
})
}

View File

@ -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)

174
src/mock/role.js Normal file
View File

@ -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
}
}

View File

@ -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',

View File

@ -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 {

View File

@ -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 nodecheckbug
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>