2026-03-06 15:43:05 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="role-auth-container">
|
|
|
|
|
<el-form
|
|
|
|
|
:model="formData"
|
|
|
|
|
class="role-info-form"
|
|
|
|
|
label-width="80px"
|
|
|
|
|
inline
|
|
|
|
|
>
|
|
|
|
|
<el-form-item label="角色编号:" class="form-col">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="formData.roleId"
|
|
|
|
|
disabled
|
|
|
|
|
class="info-input"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="角色名称:" class="form-col">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="formData.roleName"
|
|
|
|
|
class="info-input"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<div class="menu-tree-wrapper">
|
|
|
|
|
<div class="tree-content">
|
|
|
|
|
<el-skeleton v-if="loading" :rows="10" animated />
|
|
|
|
|
<el-tree
|
|
|
|
|
v-else
|
|
|
|
|
ref="menuTreeRef"
|
|
|
|
|
:data="menuTreeData"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
:show-checkbox="true"
|
|
|
|
|
:default-expand-all="false"
|
|
|
|
|
node-key="id"
|
|
|
|
|
@check-change="handleCheckChange"
|
|
|
|
|
@check="handleCheck"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, onMounted, defineProps, defineExpose, watch } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { getTreeMenuList } from '@/api/bizMenu'
|
|
|
|
|
import { getHomeRoleMenus } from '@/api/bizRole'
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
formData: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: true,
|
|
|
|
|
default: () => ({
|
|
|
|
|
roleId: '',
|
|
|
|
|
roleName: '',
|
|
|
|
|
menuIds: []
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const menuTreeData = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const menuTreeRef = ref(null)
|
|
|
|
|
const selectedMenuIds = ref([])
|
|
|
|
|
|
2026-03-07 12:13:00 +08:00
|
|
|
const getLeafNodeIds = (treeData, leafIds = []) => {
|
|
|
|
|
treeData.forEach(node => {
|
|
|
|
|
if (!node.children || node.children.length === 0) {
|
|
|
|
|
leafIds.push(String(node.id))
|
|
|
|
|
} else {
|
|
|
|
|
getLeafNodeIds(node.children, leafIds)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return leafIds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const filterOnlyLeafIds = (allIds, treeData) => {
|
|
|
|
|
const allLeafIds = getLeafNodeIds(treeData)
|
|
|
|
|
return allIds.filter(id => allLeafIds.includes(id))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 15:43:05 +08:00
|
|
|
const getTreeListData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getTreeMenuList()
|
|
|
|
|
menuTreeData.value = res ?? []
|
2026-03-07 12:13:00 +08:00
|
|
|
return menuTreeData.value
|
2026-03-06 15:43:05 +08:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载菜单树失败:', error)
|
|
|
|
|
menuTreeData.value = []
|
2026-03-07 12:13:00 +08:00
|
|
|
return []
|
2026-03-06 15:43:05 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getRoleMenuIds = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const reqParams = { roleId: props.formData.roleId }
|
|
|
|
|
const res = await getHomeRoleMenus(reqParams)
|
|
|
|
|
return (res ?? []).map(item => String(item?.menuId)).filter(id => id)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载角色菜单权限失败:', error)
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const treeProps = reactive({
|
|
|
|
|
label: 'label',
|
|
|
|
|
children: 'children',
|
|
|
|
|
disabled: 'disabled'
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-07 12:13:00 +08:00
|
|
|
function getIndeterminateKeys(node) {
|
|
|
|
|
let keys = []
|
|
|
|
|
if (node.indeterminate) {
|
|
|
|
|
keys.push(node.key)
|
|
|
|
|
}
|
|
|
|
|
if (node.childNodes) {
|
|
|
|
|
node.childNodes.forEach(c => keys.push(...getIndeterminateKeys(c)))
|
|
|
|
|
}
|
|
|
|
|
return keys
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCheckedAndIndeterminateKeys() {
|
|
|
|
|
if (!menuTreeRef.value) return []
|
|
|
|
|
const checked = menuTreeRef.value.getCheckedKeys(false)
|
|
|
|
|
const indeterminate = getIndeterminateKeys(menuTreeRef.value.root)
|
|
|
|
|
return [...new Set([...checked, ...indeterminate])]
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 15:43:05 +08:00
|
|
|
onMounted(async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
2026-03-07 12:13:00 +08:00
|
|
|
const treeData = await getTreeListData()
|
|
|
|
|
const roleMenuIds = await getRoleMenuIds()
|
2026-03-06 15:43:05 +08:00
|
|
|
const propsMenuIds = (props.formData?.menuIds ?? []).map(id => String(id)).filter(id => id)
|
2026-03-07 12:13:00 +08:00
|
|
|
const allMenuIds = roleMenuIds.length > 0 ? roleMenuIds : propsMenuIds
|
|
|
|
|
const leafMenuIds = filterOnlyLeafIds(allMenuIds, treeData)
|
|
|
|
|
|
|
|
|
|
if (leafMenuIds.length > 0) {
|
|
|
|
|
selectedMenuIds.value = [...leafMenuIds]
|
2026-03-06 15:43:05 +08:00
|
|
|
setTimeout(() => {
|
2026-03-07 12:13:00 +08:00
|
|
|
if (menuTreeRef.value && treeData.length) {
|
|
|
|
|
menuTreeRef.value.setCheckedKeys(leafMenuIds, false)
|
2026-03-06 15:43:05 +08:00
|
|
|
}
|
|
|
|
|
}, 300)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('初始化权限数据失败:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
watch(() => props.formData.menuIds, (newVal) => {
|
2026-03-07 12:13:00 +08:00
|
|
|
if (!newVal || !newVal.length || !menuTreeRef.value || !menuTreeData.value.length) return
|
|
|
|
|
|
|
|
|
|
const allMenuIds = newVal.map(id => String(id)).filter(id => id)
|
|
|
|
|
const leafMenuIds = filterOnlyLeafIds(allMenuIds, menuTreeData.value)
|
2026-03-06 15:43:05 +08:00
|
|
|
|
2026-03-07 12:13:00 +08:00
|
|
|
if (leafMenuIds.length) {
|
|
|
|
|
selectedMenuIds.value = [...leafMenuIds]
|
|
|
|
|
menuTreeRef.value.setCheckedKeys(leafMenuIds, false)
|
2026-03-06 15:43:05 +08:00
|
|
|
}
|
|
|
|
|
}, { deep: true })
|
|
|
|
|
|
|
|
|
|
const handleCheck = () => {
|
|
|
|
|
if (!menuTreeRef.value) return
|
|
|
|
|
selectedMenuIds.value = menuTreeRef.value.getCheckedKeys(false)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 12:13:00 +08:00
|
|
|
const handleCheckChange = () => {
|
2026-03-06 15:43:05 +08:00
|
|
|
handleCheck()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validate = async () => {
|
|
|
|
|
if (selectedMenuIds.value.length === 0) {
|
|
|
|
|
ElMessage.warning('请至少选择一个菜单权限!')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
if (menuTreeRef.value) {
|
|
|
|
|
menuTreeRef.value.setCheckedKeys([])
|
|
|
|
|
selectedMenuIds.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
validate,
|
|
|
|
|
resetForm,
|
2026-03-07 12:13:00 +08:00
|
|
|
getSelectedMenuIds: () => getCheckedAndIndeterminateKeys(),
|
2026-03-06 15:43:05 +08:00
|
|
|
getLeafMenuIds: () => menuTreeRef.value ? menuTreeRef.value.getCheckedKeys(true) : []
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.role-auth-container {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border: 1px solid rgba(64, 158, 255, 0.15);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.role-info-form {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
width: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
.form-col {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 200px;
|
|
|
|
|
}
|
|
|
|
|
.info-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
:deep(.role-info-form .el-form-item) {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
:deep(.role-info-form .el-input.is-disabled .el-input__inner) {
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-tree-wrapper {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tree-content {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 400px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-tree) {
|
|
|
|
|
--el-tree-node-content-hover-bg-color: #f5f7fa;
|
|
|
|
|
}
|
|
|
|
|
:deep(.el-tree-node__content) {
|
|
|
|
|
height: 36px;
|
|
|
|
|
line-height: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tree-content::-webkit-scrollbar {
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
.tree-content::-webkit-scrollbar-thumb {
|
|
|
|
|
background-color: #dcdfe6;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|