feat: 完善角色管理增删改查功能

This commit is contained in:
lvfengfree 2026-01-21 16:27:59 +08:00
parent eebbacafde
commit 915a5ac60b
5 changed files with 408 additions and 58 deletions

View File

@ -58,6 +58,65 @@ export function fetchGetRoleList(params: Api.SystemManage.RoleSearchParams) {
}) })
} }
// 获取所有角色(下拉选择用)
export function fetchGetAllRoles() {
return request.get<{ roleId: number; roleName: string; roleCode: string }[]>({
url: '/api/role/all'
})
}
// 创建角色
export function fetchCreateRole(data: {
roleName: string
roleCode: string
description?: string
enabled?: boolean
}) {
return request.post({
url: '/api/role',
params: data,
showSuccessMessage: true
})
}
// 更新角色
export function fetchUpdateRole(id: number, data: {
roleName?: string
roleCode?: string
description?: string
enabled?: boolean
}) {
return request.put({
url: `/api/role/${id}`,
params: data,
showSuccessMessage: true
})
}
// 删除角色
export function fetchDeleteRole(id: number) {
return request.del({
url: `/api/role/${id}`,
showSuccessMessage: true
})
}
// 获取角色的菜单权限
export function fetchGetRoleMenus(roleId: number) {
return request.get<number[]>({
url: `/api/role/${roleId}/menus`
})
}
// 设置角色的菜单权限
export function fetchSetRoleMenus(roleId: number, menuIds: number[]) {
return request.put({
url: `/api/role/${roleId}/menus`,
params: { menuIds },
showSuccessMessage: true
})
}
// 获取菜单列表 // 获取菜单列表
export function fetchGetMenuList() { export function fetchGetMenuList() {
return request.get<AppRouteRecord[]>({ return request.get<AppRouteRecord[]>({

View File

@ -58,12 +58,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ButtonMoreItem } from '@/components/core/forms/art-button-more/index.vue' import { ButtonMoreItem } from '@/components/core/forms/art-button-more/index.vue'
import { useTable } from '@/hooks/core/useTable' import { useTable } from '@/hooks/core/useTable'
import { fetchGetRoleList } from '@/api/system-manage' import { fetchGetRoleList, fetchDeleteRole } from '@/api/system-manage'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue' import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import RoleSearch from './modules/role-search.vue' import RoleSearch from './modules/role-search.vue'
import RoleEditDialog from './modules/role-edit-dialog.vue' import RoleEditDialog from './modules/role-edit-dialog.vue'
import RolePermissionDialog from './modules/role-permission-dialog.vue' import RolePermissionDialog from './modules/role-permission-dialog.vue'
import { ElTag, ElMessageBox } from 'element-plus' import { ElTag, ElMessageBox, ElMessage } from 'element-plus'
defineOptions({ name: 'Role' }) defineOptions({ name: 'Role' })
@ -224,19 +224,20 @@
currentRoleData.value = row currentRoleData.value = row
} }
const deleteRole = (row: RoleListItem) => { const deleteRole = async (row: RoleListItem) => {
ElMessageBox.confirm(`确定删除角色"${row.roleName}"吗?此操作不可恢复!`, '删除确认', { try {
confirmButtonText: '确定', await ElMessageBox.confirm(`确定删除角色"${row.roleName}"吗?此操作不可恢复!`, '删除确认', {
cancelButtonText: '取消', confirmButtonText: '确定',
type: 'warning' cancelButtonText: '取消',
}) type: 'warning'
.then(() => {
// TODO:
ElMessage.success('删除成功')
refreshData()
})
.catch(() => {
ElMessage.info('已取消删除')
}) })
await fetchDeleteRole(row.roleId)
refreshData()
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除角色失败:', error)
}
}
} }
</script> </script>

View File

@ -27,13 +27,14 @@
</ElForm> </ElForm>
<template #footer> <template #footer>
<ElButton @click="handleClose">取消</ElButton> <ElButton @click="handleClose">取消</ElButton>
<ElButton type="primary" @click="handleSubmit">提交</ElButton> <ElButton type="primary" :loading="submitting" @click="handleSubmit">提交</ElButton>
</template> </template>
</ElDialog> </ElDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { fetchCreateRole, fetchUpdateRole } from '@/api/system-manage'
type RoleListItem = Api.SystemManage.RoleListItem type RoleListItem = Api.SystemManage.RoleListItem
@ -57,6 +58,7 @@
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const submitting = ref(false)
/** /**
* 弹窗显示状态双向绑定 * 弹窗显示状态双向绑定
@ -77,8 +79,7 @@
roleCode: [ roleCode: [
{ required: true, message: '请输入角色编码', trigger: 'blur' }, { required: true, message: '请输入角色编码', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' } { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
], ]
description: [{ required: true, message: '请输入角色描述', trigger: 'blur' }]
}) })
/** /**
@ -150,13 +151,30 @@
try { try {
await formRef.value.validate() await formRef.value.validate()
// TODO: / submitting.value = true
const message = props.dialogType === 'add' ? '新增成功' : '修改成功'
ElMessage.success(message) if (props.dialogType === 'add') {
await fetchCreateRole({
roleName: form.roleName,
roleCode: form.roleCode,
description: form.description || undefined,
enabled: form.enabled
})
} else {
await fetchUpdateRole(form.roleId, {
roleName: form.roleName,
roleCode: form.roleCode,
description: form.description || undefined,
enabled: form.enabled
})
}
emit('success') emit('success')
handleClose() handleClose()
} catch (error) { } catch (error) {
console.log('表单验证失败:', error) console.log('表单验证失败:', error)
} finally {
submitting.value = false
} }
} }
</script> </script>

View File

@ -12,9 +12,9 @@
ref="treeRef" ref="treeRef"
:data="processedMenuList" :data="processedMenuList"
show-checkbox show-checkbox
node-key="name" node-key="id"
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
:default-checked-keys="[1, 2, 3]" :default-checked-keys="checkedMenuIds"
:props="defaultProps" :props="defaultProps"
@check="handleTreeCheck" @check="handleTreeCheck"
> >
@ -29,13 +29,11 @@
</ElTree> </ElTree>
</ElScrollbar> </ElScrollbar>
<template #footer> <template #footer>
<ElButton @click="outputSelectedData" style="margin-left: 8px">获取选中数据</ElButton>
<ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton> <ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton>
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{ <ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
isSelectAll ? '取消全选' : '全部选择' isSelectAll ? '取消全选' : '全部选择'
}}</ElButton> }}</ElButton>
<ElButton type="primary" @click="savePermission">保存</ElButton> <ElButton type="primary" :loading="saving" @click="savePermission">保存</ElButton>
</template> </template>
</ElDialog> </ElDialog>
</template> </template>
@ -43,6 +41,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMenuStore } from '@/store/modules/menu' import { useMenuStore } from '@/store/modules/menu'
import { formatMenuTitle } from '@/utils/router' import { formatMenuTitle } from '@/utils/router'
import { fetchGetRoleMenus, fetchSetRoleMenus } from '@/api/system-manage'
type RoleListItem = Api.SystemManage.RoleListItem type RoleListItem = Api.SystemManage.RoleListItem
@ -67,6 +66,8 @@
const treeRef = ref() const treeRef = ref()
const isExpandAll = ref(true) const isExpandAll = ref(true)
const isSelectAll = ref(false) const isSelectAll = ref(false)
const checkedMenuIds = ref<number[]>([])
const saving = ref(false)
/** /**
* 弹窗显示状态双向绑定 * 弹窗显示状态双向绑定
@ -141,10 +142,19 @@
*/ */
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { async (newVal) => {
if (newVal && props.roleData) { if (newVal && props.roleData) {
// TODO: try {
console.log('设置权限:', props.roleData) const menuIds = await fetchGetRoleMenus(props.roleData.roleId)
checkedMenuIds.value = menuIds || []
// DOM
nextTick(() => {
treeRef.value?.setCheckedKeys(checkedMenuIds.value)
})
} catch (error) {
console.error('获取角色菜单权限失败:', error)
checkedMenuIds.value = []
}
} }
} }
) )
@ -154,17 +164,35 @@
*/ */
const handleClose = () => { const handleClose = () => {
visible.value = false visible.value = false
checkedMenuIds.value = []
treeRef.value?.setCheckedKeys([]) treeRef.value?.setCheckedKeys([])
} }
/** /**
* 保存权限配置 * 保存权限配置
*/ */
const savePermission = () => { const savePermission = async () => {
// TODO: if (!props.roleData) return
ElMessage.success('权限保存成功')
emit('success') const tree = treeRef.value
handleClose() if (!tree) return
// IDID
const checkedKeys = tree.getCheckedKeys()
const halfCheckedKeys = tree.getHalfCheckedKeys()
const allKeys = [...checkedKeys, ...halfCheckedKeys]
const menuIds = allKeys.filter((key: any) => typeof key === 'number') as number[]
saving.value = true
try {
await fetchSetRoleMenus(props.roleData.roleId, menuIds)
emit('success')
handleClose()
} catch (error) {
console.error('保存权限失败:', error)
} finally {
saving.value = false
}
} }
/** /**
@ -205,11 +233,11 @@
* @param nodes 节点列表 * @param nodes 节点列表
* @returns 所有节点的 key 数组 * @returns 所有节点的 key 数组
*/ */
const getAllNodeKeys = (nodes: MenuNode[]): string[] => { const getAllNodeKeys = (nodes: MenuNode[]): (string | number)[] => {
const keys: string[] = [] const keys: (string | number)[] = []
const traverse = (nodeList: MenuNode[]): void => { const traverse = (nodeList: MenuNode[]): void => {
nodeList.forEach((node) => { nodeList.forEach((node) => {
if (node.name) keys.push(node.name) if (node.id !== undefined) keys.push(node.id)
if (node.children?.length) traverse(node.children) if (node.children?.length) traverse(node.children)
}) })
} }
@ -230,25 +258,4 @@
isSelectAll.value = checkedKeys.length === allKeys.length && allKeys.length > 0 isSelectAll.value = checkedKeys.length === allKeys.length && allKeys.length > 0
} }
/**
* 输出选中的权限数据到控制台
* 用于调试和查看当前选中的权限配置
*/
const outputSelectedData = () => {
const tree = treeRef.value
if (!tree) return
const selectedData = {
checkedKeys: tree.getCheckedKeys(),
halfCheckedKeys: tree.getHalfCheckedKeys(),
checkedNodes: tree.getCheckedNodes(),
halfCheckedNodes: tree.getHalfCheckedNodes(),
totalChecked: tree.getCheckedKeys().length,
totalHalfChecked: tree.getHalfCheckedKeys().length
}
console.log('=== 选中的权限数据 ===', selectedData)
ElMessage.success(`已输出选中数据到控制台,共选中 ${selectedData.totalChecked} 个节点`)
}
</script> </script>

View File

@ -67,6 +67,231 @@ public class RoleController : ControllerBase
Total = total Total = total
})); }));
} }
/// <summary>
/// 获取所有角色(下拉选择用)
/// </summary>
[HttpGet("all")]
public async Task<ActionResult<ApiResponse<List<RoleSimpleDto>>>> GetAllRoles()
{
var roles = await _context.Roles
.Where(r => r.Enabled)
.OrderBy(r => r.Id)
.Select(r => new RoleSimpleDto
{
RoleId = r.Id,
RoleName = r.RoleName,
RoleCode = r.RoleCode
})
.ToListAsync();
return Ok(ApiResponse<List<RoleSimpleDto>>.Success(roles));
}
/// <summary>
/// 获取角色详情
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<RoleListItemDto>>> GetRole(int id)
{
var role = await _context.Roles.FindAsync(id);
if (role == null)
{
return NotFound(ApiResponse<RoleListItemDto>.Fail(404, "角色不存在"));
}
return Ok(ApiResponse<RoleListItemDto>.Success(new RoleListItemDto
{
RoleId = role.Id,
RoleName = role.RoleName,
RoleCode = role.RoleCode,
Description = role.Description,
Enabled = role.Enabled,
CreateTime = role.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
}));
}
/// <summary>
/// 创建角色
/// </summary>
[HttpPost]
public async Task<ActionResult<ApiResponse<RoleListItemDto>>> CreateRole([FromBody] CreateRoleDto dto)
{
// 检查角色编码是否已存在
if (await _context.Roles.AnyAsync(r => r.RoleCode == dto.RoleCode))
{
return BadRequest(ApiResponse<RoleListItemDto>.Fail(400, "角色编码已存在"));
}
// 检查角色名称是否已存在
if (await _context.Roles.AnyAsync(r => r.RoleName == dto.RoleName))
{
return BadRequest(ApiResponse<RoleListItemDto>.Fail(400, "角色名称已存在"));
}
var role = new Role
{
RoleName = dto.RoleName,
RoleCode = dto.RoleCode,
Description = dto.Description,
Enabled = dto.Enabled,
CreatedAt = DateTime.UtcNow
};
_context.Roles.Add(role);
await _context.SaveChangesAsync();
return Ok(ApiResponse<RoleListItemDto>.Success(new RoleListItemDto
{
RoleId = role.Id,
RoleName = role.RoleName,
RoleCode = role.RoleCode,
Description = role.Description,
Enabled = role.Enabled,
CreateTime = role.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
}, "创建成功"));
}
/// <summary>
/// 更新角色
/// </summary>
[HttpPut("{id}")]
public async Task<ActionResult<ApiResponse<RoleListItemDto>>> UpdateRole(int id, [FromBody] UpdateRoleDto dto)
{
var role = await _context.Roles.FindAsync(id);
if (role == null)
{
return NotFound(ApiResponse<RoleListItemDto>.Fail(404, "角色不存在"));
}
// 检查角色编码是否与其他角色重复
if (!string.IsNullOrEmpty(dto.RoleCode) && dto.RoleCode != role.RoleCode)
{
if (await _context.Roles.AnyAsync(r => r.RoleCode == dto.RoleCode && r.Id != id))
{
return BadRequest(ApiResponse<RoleListItemDto>.Fail(400, "角色编码已存在"));
}
role.RoleCode = dto.RoleCode;
}
// 检查角色名称是否与其他角色重复
if (!string.IsNullOrEmpty(dto.RoleName) && dto.RoleName != role.RoleName)
{
if (await _context.Roles.AnyAsync(r => r.RoleName == dto.RoleName && r.Id != id))
{
return BadRequest(ApiResponse<RoleListItemDto>.Fail(400, "角色名称已存在"));
}
role.RoleName = dto.RoleName;
}
if (dto.Description != null)
{
role.Description = dto.Description;
}
if (dto.Enabled.HasValue)
{
role.Enabled = dto.Enabled.Value;
}
await _context.SaveChangesAsync();
return Ok(ApiResponse<RoleListItemDto>.Success(new RoleListItemDto
{
RoleId = role.Id,
RoleName = role.RoleName,
RoleCode = role.RoleCode,
Description = role.Description,
Enabled = role.Enabled,
CreateTime = role.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
}, "更新成功"));
}
/// <summary>
/// 删除角色
/// </summary>
[HttpDelete("{id}")]
public async Task<ActionResult<ApiResponse<object>>> DeleteRole(int id)
{
var role = await _context.Roles
.Include(r => r.UserRoles)
.Include(r => r.RoleMenus)
.FirstOrDefaultAsync(r => r.Id == id);
if (role == null)
{
return NotFound(ApiResponse<object>.Fail(404, "角色不存在"));
}
// 检查是否有用户使用该角色
if (role.UserRoles.Any())
{
return BadRequest(ApiResponse<object>.Fail(400, "该角色下存在用户,无法删除"));
}
// 删除角色菜单关联
_context.RoleMenus.RemoveRange(role.RoleMenus);
// 删除角色
_context.Roles.Remove(role);
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "删除成功"));
}
/// <summary>
/// 获取角色的菜单权限
/// </summary>
[HttpGet("{id}/menus")]
public async Task<ActionResult<ApiResponse<List<int>>>> GetRoleMenus(int id)
{
var role = await _context.Roles.FindAsync(id);
if (role == null)
{
return NotFound(ApiResponse<List<int>>.Fail(404, "角色不存在"));
}
var menuIds = await _context.RoleMenus
.Where(rm => rm.RoleId == id)
.Select(rm => rm.MenuId)
.ToListAsync();
return Ok(ApiResponse<List<int>>.Success(menuIds));
}
/// <summary>
/// 设置角色的菜单权限
/// </summary>
[HttpPut("{id}/menus")]
public async Task<ActionResult<ApiResponse<object>>> SetRoleMenus(int id, [FromBody] SetRoleMenusDto dto)
{
var role = await _context.Roles.FindAsync(id);
if (role == null)
{
return NotFound(ApiResponse<object>.Fail(404, "角色不存在"));
}
// 删除原有的菜单权限
var existingMenus = await _context.RoleMenus
.Where(rm => rm.RoleId == id)
.ToListAsync();
_context.RoleMenus.RemoveRange(existingMenus);
// 添加新的菜单权限
if (dto.MenuIds != null && dto.MenuIds.Any())
{
var newMenus = dto.MenuIds.Select(menuId => new RoleMenu
{
RoleId = id,
MenuId = menuId
});
_context.RoleMenus.AddRange(newMenus);
}
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "菜单权限设置成功"));
}
} }
/// <summary> /// <summary>
@ -81,3 +306,43 @@ public class RoleListItemDto
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string CreateTime { get; set; } = string.Empty; public string CreateTime { get; set; } = string.Empty;
} }
/// <summary>
/// 角色简单 DTO下拉选择用
/// </summary>
public class RoleSimpleDto
{
public int RoleId { get; set; }
public string RoleName { get; set; } = string.Empty;
public string RoleCode { get; set; } = string.Empty;
}
/// <summary>
/// 创建角色 DTO
/// </summary>
public class CreateRoleDto
{
public string RoleName { get; set; } = string.Empty;
public string RoleCode { get; set; } = string.Empty;
public string? Description { get; set; }
public bool Enabled { get; set; } = true;
}
/// <summary>
/// 更新角色 DTO
/// </summary>
public class UpdateRoleDto
{
public string? RoleName { get; set; }
public string? RoleCode { get; set; }
public string? Description { get; set; }
public bool? Enabled { get; set; }
}
/// <summary>
/// 设置角色菜单 DTO
/// </summary>
public class SetRoleMenusDto
{
public List<int>? MenuIds { get; set; }
}