<template>
|
<el-dialog
|
title="角色权限设置"
|
:visible.sync="dialogVisible"
|
width="860px"
|
append-to-body
|
:close-on-click-modal="false"
|
>
|
<div class="permission-head">
|
<div>
|
<span class="role-label">当前角色</span>
|
<strong>{{ role.name }}</strong>
|
<span class="role-code">{{ role.code }}</span>
|
</div>
|
<span class="selected-total">已选权限 {{ selectedRoleIds.length }} 个</span>
|
</div>
|
<div class="permission-toolbar">
|
<el-radio-group v-model="permissionScope" size="small" @change="handleScopeChange">
|
<el-radio-button label="all">全部</el-radio-button>
|
<el-radio-button label="assigned">已设置</el-radio-button>
|
</el-radio-group>
|
<el-checkbox v-if="permissionScope === 'all'" v-model="checkAll" @change="toggleAll">全选</el-checkbox>
|
<span class="permission-tip">{{ permissionTip }}</span>
|
</div>
|
<div v-loading="loading" class="permission-tree">
|
<el-tree
|
ref="permissionTree"
|
:data="displayMenus"
|
node-key="id"
|
show-checkbox
|
default-expand-all
|
empty-text="该角色尚未设置权限"
|
:render-content="renderContent"
|
:props="{ label: 'name', children: 'children' }"
|
@check="syncCheckAllState"
|
></el-tree>
|
</div>
|
<template slot="footer">
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary" :loading="saving" v-permission="'system:roles:permission'" @click="save">保存</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script>
|
import api from '@/api'
|
export default {
|
name: 'RolePermissionDialog',
|
props: {
|
visible: { type: Boolean, default: false },
|
role: { type: Object, default: () => ({}) }
|
},
|
data() {
|
return {
|
menus: [],
|
flatMenus: [],
|
loading: false,
|
saving: false,
|
checkAll: false,
|
permissionScope: 'all',
|
selectedRoleIds: []
|
}
|
},
|
computed: {
|
dialogVisible: {
|
get() { return this.visible },
|
set(value) { this.$emit('update:visible', value) }
|
},
|
allMenuIds() {
|
return this.flatMenus.map(item => String(item.id))
|
},
|
displayMenus() {
|
if (this.permissionScope === 'all') return this.menus
|
const assigned = new Set(this.selectedRoleIds.map(String))
|
const filterAssigned = nodes => (nodes || []).reduce((result, node) => {
|
const children = filterAssigned(node.children)
|
if (assigned.has(String(node.id)) || children.length) {
|
result.push({ ...node, children })
|
}
|
return result
|
}, [])
|
return filterAssigned(this.menus)
|
},
|
permissionTip() {
|
return this.permissionScope === 'all'
|
? '菜单和按钮权限均可勾选,保存后切换角色会自动反显。'
|
: '仅显示当前角色已设置的权限及其父级节点。'
|
}
|
},
|
watch: {
|
visible(value) {
|
if (value) this.open()
|
},
|
},
|
methods: {
|
open() {
|
if (!this.role || !this.role.id) return
|
this.loading = true
|
this.checkAll = false
|
this.permissionScope = 'all'
|
this.selectedRoleIds = []
|
Promise.all([
|
api.get('/system/menu/tree'),
|
api.get('/system/menu/list'),
|
api.get('/system/menu-permissions', { params: { roleId: this.role.id } })
|
]).then(([tree, flat, permissions]) => {
|
this.menus = tree.data || []
|
this.flatMenus = flat.data || []
|
const data = permissions.data || {}
|
this.selectedRoleIds = (data.roleMenuIds || []).map(String)
|
this.$nextTick(() => {
|
if (!this.$refs.permissionTree) return
|
this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
|
this.syncCheckAllState()
|
})
|
}).finally(() => { this.loading = false })
|
|
},
|
handleScopeChange() {
|
this.selectedRoleIds = this.checkedMenuIds()
|
this.$nextTick(() => {
|
if (!this.$refs.permissionTree) return
|
this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
|
this.syncCheckAllState()
|
})
|
},
|
renderContent(h, { data }) {
|
const label = data.menuType === 2 ? data.name + ' [按钮 ' + (data.permissions || '') + ']' : data.name
|
return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
|
},
|
displayCheckedKeys(ids) {
|
const selected = new Set((ids || []).map(String))
|
const children = {}
|
const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
|
this.flatMenus.forEach(item => {
|
const id = String(item.id)
|
const pid = item.pid == null ? '0' : String(item.pid)
|
if (!children[pid]) children[pid] = []
|
children[pid].push(id)
|
})
|
const result = []
|
const collectFullySelected = id => {
|
if (!selected.has(id)) return false
|
const childIds = children[id] || []
|
if (!childIds.length) {
|
result.push(id)
|
return true
|
}
|
const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
|
if (allChildrenSelected) result.push(id)
|
return allChildrenSelected
|
}
|
this.flatMenus
|
.filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid)))
|
.forEach(item => collectFullySelected(String(item.id)))
|
return result
|
},
|
checkedMenuIds() {
|
const tree = this.$refs.permissionTree
|
if (!tree) return []
|
return [...new Set([...(tree.getCheckedKeys(false) || []), ...(tree.getHalfCheckedKeys() || [])].map(String))]
|
},
|
syncCheckAllState() {
|
const checked = this.checkedMenuIds()
|
this.selectedRoleIds = checked
|
this.checkAll = checked.length === this.allMenuIds.length
|
},
|
toggleAll() {
|
if (!this.$refs.permissionTree) return
|
this.$refs.permissionTree.setCheckedKeys(this.checkAll ? this.allMenuIds : [])
|
this.$nextTick(this.syncCheckAllState)
|
},
|
save() {
|
const roleMenuIds = this.checkedMenuIds()
|
this.saving = true
|
api.post('/system/menu-permissions', { roleId: this.role.id, roleMenuIds }).then(() => {
|
this.selectedRoleIds = roleMenuIds
|
this.$message.success('角色权限已保存')
|
this.$emit('saved')
|
this.dialogVisible = false
|
}).finally(() => { this.saving = false })
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.permission-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 12px;
|
color: #31465d;
|
}
|
.role-label {
|
margin-right: 10px;
|
color: #7b8b9c;
|
font-size: 13px;
|
}
|
.role-code {
|
margin-left: 8px;
|
color: #93a2b1;
|
font-size: 12px;
|
}
|
.selected-total {
|
color: #2f658f;
|
font-size: 13px;
|
}
|
.permission-toolbar {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
margin-bottom: 10px;
|
}
|
.permission-tip {
|
color: #7b8a9a;
|
font-size: 12px;
|
}
|
.permission-tree {
|
height: 460px;
|
padding: 8px 10px;
|
border: 1px solid #dce4ec;
|
border-radius: 4px;
|
overflow: auto;
|
}
|
::v-deep .perm-button-node {
|
color: #b8791f;
|
}
|
</style>
|