feat: 完善角色管理增删改查功能
This commit is contained in:
parent
eebbacafde
commit
915a5ac60b
@ -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() {
|
||||
return request.get<AppRouteRecord[]>({
|
||||
|
||||
@ -58,12 +58,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonMoreItem } from '@/components/core/forms/art-button-more/index.vue'
|
||||
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 RoleSearch from './modules/role-search.vue'
|
||||
import RoleEditDialog from './modules/role-edit-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' })
|
||||
|
||||
@ -224,19 +224,20 @@
|
||||
currentRoleData.value = row
|
||||
}
|
||||
|
||||
const deleteRole = (row: RoleListItem) => {
|
||||
ElMessageBox.confirm(`确定删除角色"${row.roleName}"吗?此操作不可恢复!`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
// TODO: 调用删除接口
|
||||
ElMessage.success('删除成功')
|
||||
refreshData()
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.info('已取消删除')
|
||||
const deleteRole = async (row: RoleListItem) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除角色"${row.roleName}"吗?此操作不可恢复!`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await fetchDeleteRole(row.roleId)
|
||||
refreshData()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除角色失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -27,13 +27,14 @@
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="handleClose">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit">提交</ElButton>
|
||||
<ElButton type="primary" :loading="submitting" @click="handleSubmit">提交</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { fetchCreateRole, fetchUpdateRole } from '@/api/system-manage'
|
||||
|
||||
type RoleListItem = Api.SystemManage.RoleListItem
|
||||
|
||||
@ -57,6 +58,7 @@
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const submitting = ref(false)
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
@ -77,8 +79,7 @@
|
||||
roleCode: [
|
||||
{ required: true, message: '请输入角色编码', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
description: [{ required: true, message: '请输入角色描述', trigger: 'blur' }]
|
||||
]
|
||||
})
|
||||
|
||||
/**
|
||||
@ -150,13 +151,30 @@
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
// TODO: 调用新增/编辑接口
|
||||
const message = props.dialogType === 'add' ? '新增成功' : '修改成功'
|
||||
ElMessage.success(message)
|
||||
submitting.value = true
|
||||
|
||||
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')
|
||||
handleClose()
|
||||
} catch (error) {
|
||||
console.log('表单验证失败:', error)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -12,9 +12,9 @@
|
||||
ref="treeRef"
|
||||
:data="processedMenuList"
|
||||
show-checkbox
|
||||
node-key="name"
|
||||
node-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:default-checked-keys="[1, 2, 3]"
|
||||
:default-checked-keys="checkedMenuIds"
|
||||
:props="defaultProps"
|
||||
@check="handleTreeCheck"
|
||||
>
|
||||
@ -29,13 +29,11 @@
|
||||
</ElTree>
|
||||
</ElScrollbar>
|
||||
<template #footer>
|
||||
<ElButton @click="outputSelectedData" style="margin-left: 8px">获取选中数据</ElButton>
|
||||
|
||||
<ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton>
|
||||
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
|
||||
isSelectAll ? '取消全选' : '全部选择'
|
||||
}}</ElButton>
|
||||
<ElButton type="primary" @click="savePermission">保存</ElButton>
|
||||
<ElButton type="primary" :loading="saving" @click="savePermission">保存</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
@ -43,6 +41,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import { formatMenuTitle } from '@/utils/router'
|
||||
import { fetchGetRoleMenus, fetchSetRoleMenus } from '@/api/system-manage'
|
||||
|
||||
type RoleListItem = Api.SystemManage.RoleListItem
|
||||
|
||||
@ -67,6 +66,8 @@
|
||||
const treeRef = ref()
|
||||
const isExpandAll = ref(true)
|
||||
const isSelectAll = ref(false)
|
||||
const checkedMenuIds = ref<number[]>([])
|
||||
const saving = ref(false)
|
||||
|
||||
/**
|
||||
* 弹窗显示状态双向绑定
|
||||
@ -141,10 +142,19 @@
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
async (newVal) => {
|
||||
if (newVal && props.roleData) {
|
||||
// TODO: 根据角色加载对应的权限数据
|
||||
console.log('设置权限:', props.roleData)
|
||||
try {
|
||||
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 = () => {
|
||||
visible.value = false
|
||||
checkedMenuIds.value = []
|
||||
treeRef.value?.setCheckedKeys([])
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存权限配置
|
||||
*/
|
||||
const savePermission = () => {
|
||||
// TODO: 调用保存权限接口
|
||||
ElMessage.success('权限保存成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
const savePermission = async () => {
|
||||
if (!props.roleData) return
|
||||
|
||||
const tree = treeRef.value
|
||||
if (!tree) return
|
||||
|
||||
// 获取选中的菜单ID(只获取数字类型的ID,排除权限节点)
|
||||
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 节点列表
|
||||
* @returns 所有节点的 key 数组
|
||||
*/
|
||||
const getAllNodeKeys = (nodes: MenuNode[]): string[] => {
|
||||
const keys: string[] = []
|
||||
const getAllNodeKeys = (nodes: MenuNode[]): (string | number)[] => {
|
||||
const keys: (string | number)[] = []
|
||||
const traverse = (nodeList: MenuNode[]): void => {
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -230,25 +258,4 @@
|
||||
|
||||
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>
|
||||
|
||||
@ -67,6 +67,231 @@ public class RoleController : ControllerBase
|
||||
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>
|
||||
@ -81,3 +306,43 @@ public class RoleListItemDto
|
||||
public bool Enabled { get; set; }
|
||||
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; }
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user