// Author: fruit.com.cn import { computed, nextTick, onMounted, ref, watch } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { createRoleApi, deleteRoleApi, listPermissionGroupsApi, listRoleCategoriesApi, listRolesApi, updateRolePermissionsApi } from '@/api/admin/role' import { useUserStoreWithOut } from '@/store/modules/user' import { useRbac } from '@/stores/rbac' export function useRolePermissionEditor() { const userStore = useUserStoreWithOut() const { can } = useRbac() const roleData = ref([]) const categories = ref([]) const permissionGroups = ref([]) const selectedRoleId = ref(null) const dialogVisible = ref(false) const newRoleName = ref('') const newRoleCategoryId = ref(undefined) const treeRef = ref() const syncingTree = ref(false) const permissionTree = computed(() => permissionGroups.value.map((group) => ({ id: `group-${group.name}`, label: group.name, children: group.items.map((item) => ({ id: item.code, label: item.name, permission: item.code })) })) ) const selectedRoleRecord = computed(() => roleData.value.find((role) => role.id === selectedRoleId.value) ) const selectedRole = computed(() => selectedRoleRecord.value?.name || '—') const currentLevel = computed(() => { const code = userStore.getUserInfo?.role return roleData.value.find((role) => role.code === code)?.level ?? 1 }) const canEditSelectedRole = computed( () => can('system:role') && !!selectedRoleRecord.value && selectedRoleRecord.value.level >= currentLevel.value ) async function load() { const [rolesRes, groupsRes, categoriesRes] = await Promise.all([ listRolesApi(), listPermissionGroupsApi(), listRoleCategoriesApi() ]) roleData.value = rolesRes.data || [] permissionGroups.value = groupsRes.data || [] categories.value = categoriesRes.data || [] if (!selectedRoleId.value && roleData.value.length) { selectedRoleId.value = roleData.value[0].id } } watch( [selectedRoleId, permissionGroups], async () => { syncingTree.value = true await nextTick() treeRef.value?.setCheckedKeys(selectedRoleRecord.value?.permissions || []) await nextTick() syncingTree.value = false }, { immediate: true } ) function selectRole(row) { selectedRoleId.value = row.id } function readCheckedCodes() { const checked = treeRef.value?.getCheckedKeys() || [] return checked.filter((key) => typeof key === 'string' && !key.startsWith('group-')) } /** 勾选即保存:把整棵树的权限码提交给后端覆盖。 */ async function handleCheck() { if (syncingTree.value || !canEditSelectedRole.value) return const role = selectedRoleRecord.value if (!role) return const codes = readCheckedCodes() await updateRolePermissionsApi(role.id, codes) role.permissions = codes ElMessage.success('权限已保存') } async function addRole() { const name = newRoleName.value.trim() if (!name) return ElMessage.warning('请输入角色名称') if (roleData.value.some((role) => role.name === name)) { return ElMessage.warning('角色名称已存在') } await createRoleApi({ name, categoryId: newRoleCategoryId.value || categories.value[0]?.id || 0, level: currentLevel.value + 1, description: '自定义角色', permissions: [] }) newRoleName.value = '' newRoleCategoryId.value = undefined dialogVisible.value = false ElMessage.success('角色已创建') await load() const created = roleData.value.find((role) => role.name === name) if (created) selectedRoleId.value = created.id } async function removeRole() { const role = selectedRoleRecord.value if (!role) return ElMessage.warning('请先选择角色') if (role.builtin) return ElMessage.warning('内置角色不可删除') try { await ElMessageBox.confirm(`确定删除角色「${role.name}」吗?`, '删除确认', { confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'el-button--danger' }) } catch { return } await deleteRoleApi(role.id) ElMessage.success('角色已删除') selectedRoleId.value = null await load() } onMounted(load) return { roleData, categories, selectedRole, selectedRoleRecord, canEditSelectedRole, permissionTree, treeRef, dialogVisible, newRoleName, newRoleCategoryId, selectRole, handleCheck, addRole, removeRole } }