feat(auth): 角色管理CRUD与角色权限绑定功能
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (java-kotlin) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled

新增角色创建/编辑/删除接口和角色-权限绑定接口,支持管理员自定义角色并灵活配置权限。
前端新增角色CRUD弹窗、按模块分组的权限配置面板,内置角色禁止删除但允许编辑和配置权限。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 00:09:48 +08:00
parent ea7ca5474e
commit 06a7cd9abd
12 changed files with 583 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse; import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthUserView; import com.datamate.common.auth.interfaces.rest.dto.AuthUserView;
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse; import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
import com.datamate.common.auth.interfaces.rest.dto.RoleWithPermissionsResponse;
import com.datamate.common.infrastructure.exception.BusinessAssert; import com.datamate.common.infrastructure.exception.BusinessAssert;
import com.datamate.common.security.JwtUtils; import com.datamate.common.security.JwtUtils;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
@@ -17,6 +18,7 @@ import io.jsonwebtoken.JwtException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@@ -26,6 +28,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -129,6 +132,56 @@ public class AuthApplicationService {
authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds)); authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds));
} }
public AuthRoleInfo createRole(String roleCode, String roleName, String description) {
AuthRoleInfo existing = authMapper.findRoleByCode(roleCode);
BusinessAssert.isTrue(existing == null, AuthErrorCode.ROLE_CODE_DUPLICATE);
String id = UUID.randomUUID().toString();
authMapper.insertRole(id, roleCode, roleName, description);
return authMapper.findRoleById(id);
}
public AuthRoleInfo updateRole(String roleId, String roleName, String description, Boolean enabled) {
AuthRoleInfo role = authMapper.findRoleById(roleId);
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
authMapper.updateRole(roleId, roleName, description, enabled);
return authMapper.findRoleById(roleId);
}
@Transactional
public void deleteRole(String roleId) {
AuthRoleInfo role = authMapper.findRoleById(roleId);
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
BusinessAssert.isTrue(!Boolean.TRUE.equals(role.getIsBuiltIn()), AuthErrorCode.ROLE_DELETE_BUILT_IN);
authMapper.deleteRolePermissions(roleId);
authMapper.deleteUserRolesByRoleId(roleId);
authMapper.deleteRoleById(roleId);
}
public RoleWithPermissionsResponse getRoleWithPermissions(String roleId) {
AuthRoleInfo role = authMapper.findRoleById(roleId);
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
List<String> permissionIds = authMapper.findPermissionIdsByRoleId(roleId);
return new RoleWithPermissionsResponse(role, permissionIds);
}
@Transactional
public void bindRolePermissions(String roleId, List<String> permissionIds) {
AuthRoleInfo role = authMapper.findRoleById(roleId);
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
authMapper.deleteRolePermissions(roleId);
if (permissionIds != null && !permissionIds.isEmpty()) {
Set<String> distinctIds = new LinkedHashSet<>(permissionIds);
int existingCount = authMapper.countPermissionsByIds(new ArrayList<>(distinctIds));
BusinessAssert.isTrue(existingCount == distinctIds.size(), AuthErrorCode.PERMISSION_NOT_FOUND);
authMapper.insertRolePermissions(roleId, new ArrayList<>(distinctIds));
}
}
private String buildToken(AuthBundle authBundle) { private String buildToken(AuthBundle authBundle) {
Map<String, Object> claims = Map.of( Map<String, Object> claims = Map.of(
"userId", authBundle.user().getId(), "userId", authBundle.user().getId(),

View File

@@ -14,5 +14,6 @@ public class AuthRoleInfo {
private String roleName; private String roleName;
private String description; private String description;
private Boolean enabled; private Boolean enabled;
private Boolean isBuiltIn;
} }

View File

@@ -15,7 +15,10 @@ public enum AuthErrorCode implements ErrorCode {
TOKEN_INVALID("auth.0003", "登录状态已失效"), TOKEN_INVALID("auth.0003", "登录状态已失效"),
USER_NOT_FOUND("auth.0004", "用户不存在"), USER_NOT_FOUND("auth.0004", "用户不存在"),
ROLE_NOT_FOUND("auth.0005", "角色不存在"), ROLE_NOT_FOUND("auth.0005", "角色不存在"),
AUTHORIZATION_DENIED("auth.0006", "无权限执行该操作"); AUTHORIZATION_DENIED("auth.0006", "无权限执行该操作"),
ROLE_CODE_DUPLICATE("auth.0007", "角色编码已存在"),
ROLE_DELETE_BUILT_IN("auth.0008", "内置角色不允许删除"),
PERMISSION_NOT_FOUND("auth.0009", "权限不存在");
private final String code; private final String code;
private final String message; private final String message;

View File

@@ -35,5 +35,27 @@ public interface AuthMapper {
int deleteUserRoles(@Param("userId") Long userId); int deleteUserRoles(@Param("userId") Long userId);
int insertUserRoles(@Param("userId") Long userId, @Param("roleIds") List<String> roleIds); int insertUserRoles(@Param("userId") Long userId, @Param("roleIds") List<String> roleIds);
AuthRoleInfo findRoleById(@Param("roleId") String roleId);
AuthRoleInfo findRoleByCode(@Param("roleCode") String roleCode);
int insertRole(@Param("id") String id, @Param("roleCode") String roleCode,
@Param("roleName") String roleName, @Param("description") String description);
int updateRole(@Param("roleId") String roleId, @Param("roleName") String roleName,
@Param("description") String description, @Param("enabled") Boolean enabled);
int deleteRoleById(@Param("roleId") String roleId);
List<String> findPermissionIdsByRoleId(@Param("roleId") String roleId);
int deleteRolePermissions(@Param("roleId") String roleId);
int insertRolePermissions(@Param("roleId") String roleId, @Param("permissionIds") List<String> permissionIds);
int countPermissionsByIds(@Param("permissionIds") List<String> permissionIds);
int deleteUserRolesByRoleId(@Param("roleId") String roleId);
} }

View File

@@ -7,12 +7,17 @@ import com.datamate.common.auth.interfaces.rest.dto.AssignUserRolesRequest;
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse; import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse; import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse; import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
import com.datamate.common.auth.interfaces.rest.dto.BindRolePermissionsRequest;
import com.datamate.common.auth.interfaces.rest.dto.CreateRoleRequest;
import com.datamate.common.auth.interfaces.rest.dto.LoginRequest; import com.datamate.common.auth.interfaces.rest.dto.LoginRequest;
import com.datamate.common.auth.interfaces.rest.dto.RoleWithPermissionsResponse;
import com.datamate.common.auth.interfaces.rest.dto.UpdateRoleRequest;
import com.datamate.common.infrastructure.exception.BusinessAssert; import com.datamate.common.infrastructure.exception.BusinessAssert;
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode; import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -69,6 +74,33 @@ public class AuthController {
return authApplicationService.listPermissions(); return authApplicationService.listPermissions();
} }
@PostMapping("/roles")
public AuthRoleInfo createRole(@RequestBody @Valid CreateRoleRequest request) {
return authApplicationService.createRole(request.roleCode(), request.roleName(), request.description());
}
@PutMapping("/roles/{roleId}")
public AuthRoleInfo updateRole(@PathVariable("roleId") String roleId,
@RequestBody @Valid UpdateRoleRequest request) {
return authApplicationService.updateRole(roleId, request.roleName(), request.description(), request.enabled());
}
@DeleteMapping("/roles/{roleId}")
public void deleteRole(@PathVariable("roleId") String roleId) {
authApplicationService.deleteRole(roleId);
}
@GetMapping("/roles/{roleId}/permissions")
public RoleWithPermissionsResponse getRolePermissions(@PathVariable("roleId") String roleId) {
return authApplicationService.getRoleWithPermissions(roleId);
}
@PutMapping("/roles/{roleId}/permissions")
public void bindRolePermissions(@PathVariable("roleId") String roleId,
@RequestBody @Valid BindRolePermissionsRequest request) {
authApplicationService.bindRolePermissions(roleId, request.permissionIds());
}
private String extractBearerToken(String authorizationHeader) { private String extractBearerToken(String authorizationHeader) {
BusinessAssert.isTrue( BusinessAssert.isTrue(
authorizationHeader != null && authorizationHeader.startsWith("Bearer "), authorizationHeader != null && authorizationHeader.startsWith("Bearer "),

View File

@@ -0,0 +1,13 @@
package com.datamate.common.auth.interfaces.rest.dto;
import jakarta.validation.constraints.NotNull;
import java.util.List;
/**
* 角色权限绑定请求
*/
public record BindRolePermissionsRequest(
@NotNull(message = "权限列表不能为null") List<String> permissionIds
) {
}

View File

@@ -0,0 +1,13 @@
package com.datamate.common.auth.interfaces.rest.dto;
import jakarta.validation.constraints.NotBlank;
/**
* 创建角色请求
*/
public record CreateRoleRequest(
@NotBlank(message = "角色编码不能为空") String roleCode,
@NotBlank(message = "角色名称不能为空") String roleName,
String description
) {
}

View File

@@ -0,0 +1,14 @@
package com.datamate.common.auth.interfaces.rest.dto;
import com.datamate.common.auth.domain.model.AuthRoleInfo;
import java.util.List;
/**
* 角色及其权限响应
*/
public record RoleWithPermissionsResponse(
AuthRoleInfo role,
List<String> permissionIds
) {
}

View File

@@ -0,0 +1,13 @@
package com.datamate.common.auth.interfaces.rest.dto;
import jakarta.validation.constraints.NotBlank;
/**
* 更新角色请求
*/
public record UpdateRoleRequest(
@NotBlank(message = "角色名称不能为空") String roleName,
String description,
Boolean enabled
) {
}

View File

@@ -76,7 +76,8 @@
role_code AS roleCode, role_code AS roleCode,
role_name AS roleName, role_name AS roleName,
description, description,
enabled enabled,
is_built_in AS isBuiltIn
FROM t_auth_roles FROM t_auth_roles
ORDER BY role_code ASC ORDER BY role_code ASC
</select> </select>
@@ -116,5 +117,80 @@
(#{userId}, #{roleId}) (#{userId}, #{roleId})
</foreach> </foreach>
</insert> </insert>
<select id="findRoleById" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
SELECT id,
role_code AS roleCode,
role_name AS roleName,
description,
enabled,
is_built_in AS isBuiltIn
FROM t_auth_roles
WHERE id = #{roleId}
LIMIT 1
</select>
<select id="findRoleByCode" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
SELECT id,
role_code AS roleCode,
role_name AS roleName,
description,
enabled,
is_built_in AS isBuiltIn
FROM t_auth_roles
WHERE role_code = #{roleCode}
LIMIT 1
</select>
<insert id="insertRole">
INSERT INTO t_auth_roles (id, role_code, role_name, description, is_built_in, enabled)
VALUES (#{id}, #{roleCode}, #{roleName}, #{description}, 0, 1)
</insert>
<update id="updateRole">
UPDATE t_auth_roles
SET role_name = #{roleName},
description = #{description},
enabled = #{enabled}
WHERE id = #{roleId}
</update>
<delete id="deleteRoleById">
DELETE FROM t_auth_roles
WHERE id = #{roleId}
</delete>
<select id="findPermissionIdsByRoleId" resultType="string">
SELECT permission_id
FROM t_auth_role_permissions
WHERE role_id = #{roleId}
</select>
<delete id="deleteRolePermissions">
DELETE FROM t_auth_role_permissions
WHERE role_id = #{roleId}
</delete>
<insert id="insertRolePermissions">
INSERT INTO t_auth_role_permissions (role_id, permission_id)
VALUES
<foreach collection="permissionIds" item="permissionId" separator=",">
(#{roleId}, #{permissionId})
</foreach>
</insert>
<select id="countPermissionsByIds" resultType="int">
SELECT COUNT(1)
FROM t_auth_permissions
WHERE id IN
<foreach collection="permissionIds" item="permissionId" open="(" separator="," close=")">
#{permissionId}
</foreach>
</select>
<delete id="deleteUserRolesByRoleId">
DELETE FROM t_auth_user_roles
WHERE role_id = #{roleId}
</delete>
</mapper> </mapper>

View File

@@ -2,11 +2,18 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { import {
Button, Button,
Card, Card,
Checkbox,
Col,
Empty, Empty,
Form,
Input,
message, message,
Modal, Modal,
Popconfirm,
Row,
Select, Select,
Space, Space,
Switch,
Table, Table,
Tag, Tag,
Typography, Typography,
@@ -14,14 +21,20 @@ import {
import type { ColumnsType } from "antd/es/table"; import type { ColumnsType } from "antd/es/table";
import { import {
assignUserRolesUsingPut, assignUserRolesUsingPut,
bindRolePermissionsUsingPut,
createRoleUsingPost,
deleteRoleUsingDelete,
getRolePermissionsUsingGet,
listAuthPermissionsUsingGet, listAuthPermissionsUsingGet,
listAuthRolesUsingGet, listAuthRolesUsingGet,
listAuthUsersUsingGet, listAuthUsersUsingGet,
updateRoleUsingPut,
} from "./settings.apis"; } from "./settings.apis";
import type { import type {
AuthPermissionInfo, AuthPermissionInfo,
AuthRoleInfo, AuthRoleInfo,
AuthUserWithRoles, AuthUserWithRoles,
RoleWithPermissionsResponse,
} from "./settings.apis"; } from "./settings.apis";
interface ApiResponse<T> { interface ApiResponse<T> {
@@ -49,6 +62,17 @@ export default function UserPermissionManagement({
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]); const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
// Role CRUD state
const [createRoleOpen, setCreateRoleOpen] = useState(false);
const [editingRole, setEditingRole] = useState<AuthRoleInfo | null>(null);
const [createRoleForm] = Form.useForm();
const [editRoleForm] = Form.useForm();
// Permission config state
const [permConfigRole, setPermConfigRole] = useState<AuthRoleInfo | null>(null);
const [selectedPermissionIds, setSelectedPermissionIds] = useState<string[]>([]);
const [permConfigLoading, setPermConfigLoading] = useState(false);
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions; const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
const canAssignRoles = canManageUsers && roles.length > 0; const canAssignRoles = canManageUsers && roles.length > 0;
@@ -61,6 +85,17 @@ export default function UserPermissionManagement({
[roles] [roles]
); );
const permissionsByModule = useMemo(() => {
const map = new Map<string, AuthPermissionInfo[]>();
for (const perm of permissions) {
const module = perm.module || "其他";
const list = map.get(module) ?? [];
list.push(perm);
map.set(module, list);
}
return map;
}, [permissions]);
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
setLoading(true); setLoading(true);
try { try {
@@ -169,6 +204,14 @@ export default function UserPermissionManagement({
const roleColumns: ColumnsType<AuthRoleInfo> = [ const roleColumns: ColumnsType<AuthRoleInfo> = [
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 }, { title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 }, { title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
{
title: "类型",
dataIndex: "isBuiltIn",
key: "isBuiltIn",
width: 100,
render: (isBuiltIn?: boolean) =>
isBuiltIn ? <Tag color="blue"></Tag> : <Tag color="orange"></Tag>,
},
{ {
title: "状态", title: "状态",
dataIndex: "enabled", dataIndex: "enabled",
@@ -183,6 +226,49 @@ export default function UserPermissionManagement({
key: "description", key: "description",
render: (value?: string) => value || "-", render: (value?: string) => value || "-",
}, },
{
title: "操作",
key: "actions",
width: 220,
render: (_, record) => (
<Space>
<Button
type="link"
size="small"
onClick={() => {
setEditingRole(record);
editRoleForm.setFieldsValue({
roleName: record.roleName,
description: record.description ?? "",
enabled: record.enabled ?? true,
});
}}
>
</Button>
<Button
type="link"
size="small"
onClick={() => void openPermissionConfig(record)}
>
</Button>
{!record.isBuiltIn && (
<Popconfirm
title="确定要删除该角色吗?"
description="删除后关联的用户绑定将同步移除"
onConfirm={() => void handleDeleteRole(record.id)}
okText="确定"
cancelText="取消"
>
<Button type="link" size="small" danger>
</Button>
</Popconfirm>
)}
</Space>
),
},
]; ];
const permissionColumns: ColumnsType<AuthPermissionInfo> = [ const permissionColumns: ColumnsType<AuthPermissionInfo> = [
@@ -250,6 +336,97 @@ export default function UserPermissionManagement({
} }
}; };
const handleCreateRole = async () => {
try {
const values = await createRoleForm.validateFields();
setSubmitting(true);
await createRoleUsingPost({
roleCode: values.roleCode,
roleName: values.roleName,
description: values.description,
});
message.success("角色创建成功");
setCreateRoleOpen(false);
createRoleForm.resetFields();
await loadData();
} catch (error) {
if (error && typeof error === "object" && "errorFields" in error) {
return;
}
message.error("角色创建失败");
console.error("角色创建失败:", error);
} finally {
setSubmitting(false);
}
};
const handleUpdateRole = async () => {
if (!editingRole) return;
try {
const values = await editRoleForm.validateFields();
setSubmitting(true);
await updateRoleUsingPut(editingRole.id, {
roleName: values.roleName,
description: values.description,
enabled: values.enabled,
});
message.success("角色更新成功");
setEditingRole(null);
editRoleForm.resetFields();
await loadData();
} catch (error) {
if (error && typeof error === "object" && "errorFields" in error) {
return;
}
message.error("角色更新失败");
console.error("角色更新失败:", error);
} finally {
setSubmitting(false);
}
};
const handleDeleteRole = async (roleId: string) => {
try {
await deleteRoleUsingDelete(roleId);
message.success("角色删除成功");
await loadData();
} catch (error) {
message.error("角色删除失败");
console.error("角色删除失败:", error);
}
};
const openPermissionConfig = async (role: AuthRoleInfo) => {
setPermConfigRole(role);
setPermConfigLoading(true);
try {
const response = (await getRolePermissionsUsingGet(role.id)) as ApiResponse<RoleWithPermissionsResponse>;
setSelectedPermissionIds(response?.data?.permissionIds ?? []);
} catch (error) {
message.error("加载角色权限失败");
console.error("加载角色权限失败:", error);
setPermConfigRole(null);
} finally {
setPermConfigLoading(false);
}
};
const handleBindPermissions = async () => {
if (!permConfigRole) return;
setSubmitting(true);
try {
await bindRolePermissionsUsingPut(permConfigRole.id, selectedPermissionIds);
message.success("权限配置成功");
setPermConfigRole(null);
setSelectedPermissionIds([]);
} catch (error) {
message.error("权限配置失败");
console.error("权限配置失败:", error);
} finally {
setSubmitting(false);
}
};
if (!canShowAnything) { if (!canShowAnything) {
return <Empty description="当前账号无用户与权限管理权限" />; return <Empty description="当前账号无用户与权限管理权限" />;
} }
@@ -266,7 +443,14 @@ export default function UserPermissionManagement({
/> />
</Card> </Card>
{canViewRoles && ( {canViewRoles && (
<Card title="角色列表"> <Card
title="角色列表"
extra={
<Button type="primary" onClick={() => setCreateRoleOpen(true)}>
</Button>
}
>
<Table <Table
loading={loading} loading={loading}
rowKey="id" rowKey="id"
@@ -287,6 +471,8 @@ export default function UserPermissionManagement({
/> />
</Card> </Card>
)} )}
{/* 分配角色 Modal */}
<Modal <Modal
title={`分配角色 - ${editingUser?.username || ""}`} title={`分配角色 - ${editingUser?.username || ""}`}
open={Boolean(editingUser)} open={Boolean(editingUser)}
@@ -315,7 +501,122 @@ export default function UserPermissionManagement({
/> />
)} )}
</Modal> </Modal>
{/* 创建角色 Modal */}
<Modal
title="创建角色"
open={createRoleOpen}
confirmLoading={submitting}
onOk={() => {
void handleCreateRole();
}}
onCancel={() => {
setCreateRoleOpen(false);
createRoleForm.resetFields();
}}
destroyOnClose
>
<Form form={createRoleForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item
name="roleCode"
label="角色编码"
rules={[{ required: true, message: "请输入角色编码" }]}
>
<Input placeholder="如:data_analyst" />
</Form.Item>
<Form.Item
name="roleName"
label="角色名称"
rules={[{ required: true, message: "请输入角色名称" }]}
>
<Input placeholder="如:数据分析师" />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
</Form.Item>
</Form>
</Modal>
{/* 编辑角色 Modal */}
<Modal
title="编辑角色"
open={Boolean(editingRole)}
confirmLoading={submitting}
onOk={() => {
void handleUpdateRole();
}}
onCancel={() => {
setEditingRole(null);
editRoleForm.resetFields();
}}
destroyOnClose
>
<Form form={editRoleForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item label="角色编码">
<Input value={editingRole?.roleCode} disabled />
</Form.Item>
<Form.Item
name="roleName"
label="角色名称"
rules={[{ required: true, message: "请输入角色名称" }]}
>
<Input placeholder="角色名称" />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
</Form.Item>
<Form.Item name="enabled" label="启用状态" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
{/* 权限配置 Modal */}
<Modal
title={`权限配置 - ${permConfigRole?.roleName || ""}`}
open={Boolean(permConfigRole)}
confirmLoading={submitting}
onOk={() => {
void handleBindPermissions();
}}
onCancel={() => {
setPermConfigRole(null);
setSelectedPermissionIds([]);
}}
width={640}
destroyOnClose
>
{permConfigLoading ? (
<Typography.Text type="secondary">...</Typography.Text>
) : permissions.length === 0 ? (
<Typography.Text type="secondary"></Typography.Text>
) : (
<div style={{ maxHeight: 400, overflow: "auto" }}>
<Checkbox.Group
value={selectedPermissionIds}
onChange={(values) => setSelectedPermissionIds(values as string[])}
style={{ width: "100%" }}
>
{Array.from(permissionsByModule.entries()).map(([module, perms]) => (
<div key={module} style={{ marginBottom: 16 }}>
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
{module}
</Typography.Text>
<Row>
{perms.map((perm) => (
<Col span={12} key={perm.id}>
<Checkbox value={perm.id}>
{perm.permissionName}
</Checkbox>
</Col>
))}
</Row>
</div>
))}
</Checkbox.Group>
</div>
)}
</Modal>
</Space> </Space>
); );
} }

View File

@@ -56,6 +56,7 @@ export interface AuthRoleInfo {
roleName: string; roleName: string;
description?: string; description?: string;
enabled?: boolean; enabled?: boolean;
isBuiltIn?: boolean;
} }
export interface AuthPermissionInfo { export interface AuthPermissionInfo {
@@ -85,3 +86,38 @@ export function listAuthPermissionsUsingGet() {
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) { export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
return put(`/api/auth/users/${userId}/roles`, { roleIds }); return put(`/api/auth/users/${userId}/roles`, { roleIds });
} }
export interface RoleWithPermissionsResponse {
role: AuthRoleInfo;
permissionIds: string[];
}
export function createRoleUsingPost(data: {
roleCode: string;
roleName: string;
description?: string;
}) {
return post("/api/auth/roles", data);
}
export function updateRoleUsingPut(
roleId: string,
data: { roleName: string; description?: string; enabled?: boolean }
) {
return put(`/api/auth/roles/${roleId}`, data);
}
export function deleteRoleUsingDelete(roleId: string) {
return del(`/api/auth/roles/${roleId}`);
}
export function getRolePermissionsUsingGet(roleId: string) {
return get(`/api/auth/roles/${roleId}/permissions`);
}
export function bindRolePermissionsUsingPut(
roleId: string,
permissionIds: string[]
) {
return put(`/api/auth/roles/${roleId}/permissions`, { permissionIds });
}