689ef6bc3d
主要修复: 1. 修复所有RBAC API函数使用axios-client(自动添加JWT token) - getRoles, createRole, updateRole, deleteRole 从rbacFetch切换到axios-client - 解决401未授权导致的数据加载失败问题 2. 修复用户ID字段不匹配问题 - getAllUsers函数使用user_id字段(兼容user.user_id || user.id) - 确保角色分配时使用正确的用户ID 3. 修复路由ID不匹配问题 - getRoutes函数改用真实后端API(GET /rbac/user/routes) - 解决前端Mock路由ID与数据库不一致导致的400错误 4. 增强axios-client成功响应识别 - 支持code=200作为成功状态(原本只支持code=0) - 兼容不同后端API的响应格式 5. 实现用户单角色限制功能 - 添加getUserRoles API函数 - 分配角色前检查用户现有角色 - 在用户列表中显示当前角色标签 6. 改进创建角色的表单验证 - role_key必须以字母开头(正则:^[a-z][a-z0-9_]*$) - 添加实时验证提示 - 更新提示文案说明规则 7. 添加删除操作的安全确认机制 - 删除角色/移除用户角色前显示确认模态框 - 3秒倒计时后才能确认删除 - 成功删除后自动刷新数据 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1301 lines
38 KiB
TypeScript
1301 lines
38 KiB
TypeScript
/**
|
|
* 角色权限管理 API
|
|
* 用于角色、路由权限、用户角色的管理
|
|
*/
|
|
|
|
// ==================== 常量定义 ====================
|
|
|
|
/**
|
|
* RBAC API 基础路径
|
|
* 注意:使用相对路径,会命中Remix API路由而不是后端服务器
|
|
*/
|
|
const RBAC_API_BASE = '/api/v3/rbac';
|
|
|
|
/**
|
|
* RBAC专用API客户端 - 使用fetch直接请求Remix API路由
|
|
*/
|
|
async function rbacFetch<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|
console.log('🔗 [RBAC Fetch] 请求:', url, options.method || 'GET');
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers
|
|
}
|
|
});
|
|
|
|
console.log('📡 [RBAC Fetch] 响应状态:', response.status, response.statusText);
|
|
|
|
const data = await response.json();
|
|
console.log('📦 [RBAC Fetch] 响应数据:', data);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.detail || data.message || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* 统一响应处理函数
|
|
* 处理后端返回的统一格式响应
|
|
*/
|
|
function handleApiResponse<T>(response: ApiResponse<any>): T {
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
if (response.data && 'code' in response.data) {
|
|
if (response.data.code !== 200) {
|
|
throw new Error(response.data.message || '请求失败');
|
|
}
|
|
return response.data.data as T;
|
|
}
|
|
|
|
// 如果没有code字段,直接返回data
|
|
return response.data as T;
|
|
}
|
|
|
|
// ==================== 类型定义 ====================
|
|
|
|
/**
|
|
* 路由信息
|
|
*/
|
|
export interface RouteInfo {
|
|
id: number;
|
|
route_path: string;
|
|
route_name: string;
|
|
route_title: string;
|
|
component?: string;
|
|
parent_id?: number | null;
|
|
icon?: string;
|
|
sort_order: number;
|
|
is_hidden: boolean;
|
|
is_cache: boolean;
|
|
status: number;
|
|
children?: RouteInfo[];
|
|
}
|
|
|
|
/**
|
|
* 角色信息
|
|
*/
|
|
export interface RoleInfo {
|
|
id: number;
|
|
role_key: string;
|
|
role_name: string;
|
|
data_scope: string;
|
|
description: string;
|
|
parent_role_id?: number | null;
|
|
priority: number;
|
|
is_system_role: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
/**
|
|
* 角色-路由权限关联
|
|
*/
|
|
export interface RoleRoutePermission {
|
|
id: number;
|
|
role_id: number;
|
|
route_id: number;
|
|
permission: string; // 'R' | 'RW' | 'NONE'
|
|
created_at: string;
|
|
}
|
|
|
|
/**
|
|
* 用户信息
|
|
*/
|
|
export interface UserInfo {
|
|
id: number;
|
|
username: string;
|
|
nick_name: string;
|
|
phone_number?: string;
|
|
email?: string;
|
|
ou_name: string;
|
|
status: number;
|
|
is_leader: boolean;
|
|
}
|
|
|
|
/**
|
|
* 用户-角色关联
|
|
*/
|
|
export interface UserRoleRelation {
|
|
id: number;
|
|
user_id: number;
|
|
role_id: number;
|
|
created_at: string;
|
|
}
|
|
|
|
/**
|
|
* RBAC权限信息
|
|
*/
|
|
export interface Permission {
|
|
id: number;
|
|
permission_key: string; // 格式: module:resource:action
|
|
module: string; // 模块名
|
|
resource: string; // 资源名
|
|
action: string; // 操作名
|
|
display_name: string; // 显示名称
|
|
description: string | null;
|
|
permission_type: 'API' | 'MENU' | 'BUTTON';
|
|
is_system: boolean;
|
|
parent_id: number | null;
|
|
sort_order: number;
|
|
children?: Permission[]; // 树形结构
|
|
}
|
|
|
|
/**
|
|
* 角色权限配置
|
|
*/
|
|
export interface RolePermissionConfig {
|
|
permission_id: number;
|
|
grant_type?: 'GRANT' | 'DENY';
|
|
data_scope?: 'ALL' | 'DEPT' | 'SELF';
|
|
}
|
|
|
|
/**
|
|
* 角色权限详情
|
|
*/
|
|
export interface RolePermissionDetail {
|
|
id: number;
|
|
permission_id: number;
|
|
permission_key: string;
|
|
display_name: string;
|
|
grant_type: 'GRANT' | 'DENY';
|
|
data_scope: 'ALL' | 'DEPT' | 'SELF';
|
|
}
|
|
|
|
// ==================== 模拟数据 ====================
|
|
|
|
/**
|
|
* 模拟路由数据(树形结构)
|
|
*/
|
|
const mockRoutes: RouteInfo[] = [
|
|
{
|
|
id: 1,
|
|
route_path: '/documents',
|
|
route_name: 'documents',
|
|
route_title: '文档管理',
|
|
icon: 'ri-file-text-line',
|
|
sort_order: 1,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: null,
|
|
children: [
|
|
{
|
|
id: 11,
|
|
route_path: '/documents/list',
|
|
route_name: 'documents-list',
|
|
route_title: '文档列表',
|
|
icon: 'ri-list-check',
|
|
sort_order: 1,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 1
|
|
},
|
|
{
|
|
id: 12,
|
|
route_path: '/documents/upload',
|
|
route_name: 'documents-upload',
|
|
route_title: '文档上传',
|
|
icon: 'ri-upload-line',
|
|
sort_order: 2,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 1
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 2,
|
|
route_path: '/cross-checking',
|
|
route_name: 'cross-checking',
|
|
route_title: '交叉评查',
|
|
icon: 'ri-exchange-line',
|
|
sort_order: 2,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: null,
|
|
children: [
|
|
{
|
|
id: 21,
|
|
route_path: '/cross-checking/tasks',
|
|
route_name: 'cross-checking-tasks',
|
|
route_title: '评查任务',
|
|
icon: 'ri-task-line',
|
|
sort_order: 1,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 2
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 3,
|
|
route_path: '/settings',
|
|
route_name: 'settings',
|
|
route_title: '系统设置',
|
|
icon: 'ri-settings-3-line',
|
|
sort_order: 3,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: null,
|
|
children: [
|
|
{
|
|
id: 31,
|
|
route_path: '/settings/document-types',
|
|
route_name: 'document-types',
|
|
route_title: '文档类型管理',
|
|
icon: 'ri-file-list-line',
|
|
sort_order: 1,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 3
|
|
},
|
|
{
|
|
id: 32,
|
|
route_path: '/settings/rule-groups',
|
|
route_name: 'rule-groups',
|
|
route_title: '评查点分组',
|
|
icon: 'ri-folder-line',
|
|
sort_order: 2,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 3
|
|
},
|
|
{
|
|
id: 33,
|
|
route_path: '/settings/prompts',
|
|
route_name: 'prompts',
|
|
route_title: '提示词管理',
|
|
icon: 'ri-message-line',
|
|
sort_order: 3,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: 3
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 4,
|
|
route_path: '/role-permissions',
|
|
route_name: 'role-permissions',
|
|
route_title: '角色权限管理',
|
|
icon: 'ri-shield-user-line',
|
|
sort_order: 4,
|
|
is_hidden: false,
|
|
is_cache: true,
|
|
status: 1,
|
|
parent_id: null
|
|
}
|
|
];
|
|
|
|
/**
|
|
* 模拟角色数据(与数据库实际数据一致)
|
|
* 仅用于开发阶段API失败时的降级方案
|
|
*/
|
|
const mockRoles: RoleInfo[] = [
|
|
{
|
|
id: 1,
|
|
role_key: 'admin',
|
|
role_name: '市级管理员',
|
|
data_scope: 'DEPT',
|
|
description: '负责本地区的所有业务管理,不包括系统设置和角色权限管理',
|
|
priority: 0,
|
|
is_system_role: false,
|
|
created_at: '2025-07-18 10:35:39',
|
|
updated_at: '2025-07-18 10:35:39'
|
|
},
|
|
{
|
|
id: 2,
|
|
role_key: 'common',
|
|
role_name: '普通员工',
|
|
data_scope: 'SELF',
|
|
description: '仅能操作自己的数据',
|
|
priority: 0,
|
|
is_system_role: false,
|
|
created_at: '2025-07-18 10:35:39',
|
|
updated_at: '2025-07-18 10:35:39'
|
|
},
|
|
{
|
|
id: 52,
|
|
role_key: 'provincial_admin',
|
|
role_name: '省级管理员',
|
|
data_scope: 'ALL',
|
|
description: '拥有全部权限,可以管理所有地区的评查点规则、提示词、动态按钮、评查组',
|
|
priority: 1,
|
|
is_system_role: true,
|
|
created_at: '2025-11-19 17:25:45',
|
|
updated_at: '2025-11-19 17:25:45'
|
|
}
|
|
];
|
|
|
|
/**
|
|
* 模拟角色-路由权限关联数据
|
|
*/
|
|
const mockRoleRoutePermissions: RoleRoutePermission[] = [
|
|
// 系统管理员拥有所有权限
|
|
{ id: 1, role_id: 1, route_id: 1, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 2, role_id: 1, route_id: 11, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 3, role_id: 1, route_id: 12, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 4, role_id: 1, route_id: 2, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 5, role_id: 1, route_id: 21, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 6, role_id: 1, route_id: 3, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 7, role_id: 1, route_id: 31, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 8, role_id: 1, route_id: 32, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 9, role_id: 1, route_id: 33, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
{ id: 10, role_id: 1, route_id: 4, permission: 'RW', created_at: '2024-01-01 10:00:00' },
|
|
|
|
// 省级管理员
|
|
{ id: 11, role_id: 2, route_id: 1, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
{ id: 12, role_id: 2, route_id: 11, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
{ id: 13, role_id: 2, route_id: 12, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
{ id: 14, role_id: 2, route_id: 3, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
{ id: 15, role_id: 2, route_id: 31, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
{ id: 16, role_id: 2, route_id: 32, permission: 'RW', created_at: '2024-01-02 10:00:00' },
|
|
|
|
// 普通用户
|
|
{ id: 17, role_id: 4, route_id: 1, permission: 'R', created_at: '2024-01-04 10:00:00' },
|
|
{ id: 18, role_id: 4, route_id: 11, permission: 'R', created_at: '2024-01-04 10:00:00' },
|
|
];
|
|
|
|
/**
|
|
* 模拟用户数据
|
|
*/
|
|
const mockUsers: UserInfo[] = [
|
|
{
|
|
id: 1,
|
|
username: 'admin',
|
|
nick_name: '系统管理员',
|
|
phone_number: '13800138000',
|
|
email: 'admin@example.com',
|
|
ou_name: '系统管理部',
|
|
status: 1,
|
|
is_leader: true
|
|
},
|
|
{
|
|
id: 2,
|
|
username: 'zhangsan',
|
|
nick_name: '张三',
|
|
phone_number: '13800138001',
|
|
email: 'zhangsan@example.com',
|
|
ou_name: '广东省局',
|
|
status: 1,
|
|
is_leader: true
|
|
},
|
|
{
|
|
id: 3,
|
|
username: 'lisi',
|
|
nick_name: '李四',
|
|
phone_number: '13800138002',
|
|
email: 'lisi@example.com',
|
|
ou_name: '梅州市局',
|
|
status: 1,
|
|
is_leader: false
|
|
},
|
|
{
|
|
id: 4,
|
|
username: 'wangwu',
|
|
nick_name: '王五',
|
|
phone_number: '13800138003',
|
|
email: 'wangwu@example.com',
|
|
ou_name: '云浮市局',
|
|
status: 1,
|
|
is_leader: false
|
|
},
|
|
{
|
|
id: 5,
|
|
username: 'zhaoliu',
|
|
nick_name: '赵六',
|
|
phone_number: '13800138004',
|
|
email: 'zhaoliu@example.com',
|
|
ou_name: '揭阳市局',
|
|
status: 1,
|
|
is_leader: false
|
|
}
|
|
];
|
|
|
|
/**
|
|
* 模拟用户-角色关联数据
|
|
*/
|
|
const mockUserRoles: UserRoleRelation[] = [
|
|
{ id: 1, user_id: 1, role_id: 1, created_at: '2024-01-01 10:00:00' },
|
|
{ id: 2, user_id: 2, role_id: 2, created_at: '2024-01-02 10:00:00' },
|
|
{ id: 3, user_id: 3, role_id: 3, created_at: '2024-01-03 10:00:00' },
|
|
{ id: 4, user_id: 4, role_id: 4, created_at: '2024-01-04 10:00:00' },
|
|
{ id: 5, user_id: 5, role_id: 5, created_at: '2024-01-05 10:00:00' }
|
|
];
|
|
|
|
// ==================== API 函数 ====================
|
|
|
|
/**
|
|
* 获取所有角色列表
|
|
* @param params 查询参数
|
|
*/
|
|
export async function getRoles(params?: {
|
|
page?: number;
|
|
page_size?: number;
|
|
role_key?: string;
|
|
role_name?: string;
|
|
include_system?: boolean;
|
|
}): Promise<RoleInfo[]> {
|
|
try {
|
|
// 导入 axios-client 的 get 函数
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getRoles] 开始调用后端API:', `/api/v3/rbac/roles`, params);
|
|
|
|
// 使用 axios-client 的 get 函数调用真实后端API
|
|
const response = await get<any>(`/api/v3/rbac/roles`, params || {});
|
|
console.log('📦 [getRoles] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, message: "success", data: { total, page, page_size, items: [...] } }
|
|
let items: any[] = [];
|
|
if (response.data && response.data.data && Array.isArray(response.data.data.items)) {
|
|
items = response.data.data.items;
|
|
} else if (response.data && Array.isArray(response.data.items)) {
|
|
items = response.data.items;
|
|
}
|
|
|
|
console.log('✅ [getRoles] 解析出的角色数组:', items);
|
|
|
|
// 数据格式转换(后端字段 -> 前端字段)
|
|
const roles = items.map(role => ({
|
|
id: role.id,
|
|
role_key: role.role_key,
|
|
role_name: role.role_name,
|
|
data_scope: role.data_scope,
|
|
description: role.description || '',
|
|
parent_role_id: role.parent_role_id || null,
|
|
priority: role.priority || 0,
|
|
is_system_role: role.is_system || false,
|
|
created_at: role.created_at,
|
|
updated_at: role.updated_at
|
|
}));
|
|
|
|
console.log('✅ [getRoles] 最终返回的角色列表,共', roles.length, '个角色');
|
|
return roles;
|
|
} catch (error) {
|
|
console.error('❌ [getRoles] 获取角色列表失败:', error);
|
|
// 失败时返回空数组
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取角色详情
|
|
* @param roleId 角色ID
|
|
*/
|
|
export async function getRoleDetail(roleId: number): Promise<RoleInfo | null> {
|
|
try {
|
|
const response = await get<any>(`${RBAC_API_BASE}/roles/${roleId}`);
|
|
const role = handleApiResponse<any>(response);
|
|
|
|
return {
|
|
id: role.id,
|
|
role_key: role.role_key,
|
|
role_name: role.role_name,
|
|
data_scope: role.data_scope,
|
|
description: role.description || '',
|
|
parent_role_id: role.parent_role_id || null,
|
|
priority: role.priority || 0,
|
|
is_system_role: role.is_system,
|
|
created_at: role.created_at,
|
|
updated_at: role.updated_at
|
|
};
|
|
} catch (error) {
|
|
console.error('获取角色详情失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取所有路由(树形结构)
|
|
* 从后端API获取当前用户可访问的所有路由
|
|
*/
|
|
export async function getRoutes(): Promise<RouteInfo[]> {
|
|
try {
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getRoutes] 开始调用后端API: /rbac/user/routes');
|
|
|
|
// 调用后端API获取当前用户的路由(provincial_admin应该有所有路由权限)
|
|
const response = await get<any>('/rbac/user/routes');
|
|
|
|
if (response.error) {
|
|
console.error('❌ [getRoutes] API调用失败:', response.error);
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, msg: "success", data: { user_id, username, routes: [...], routes_flat: [...] } }
|
|
let routes: any[] = [];
|
|
if (response.data && response.data.data && Array.isArray(response.data.data.routes)) {
|
|
routes = response.data.data.routes;
|
|
} else if (response.data && Array.isArray(response.data.routes)) {
|
|
// 兼容可能的响应格式
|
|
routes = response.data.routes;
|
|
}
|
|
|
|
console.log('✅ [getRoutes] 成功获取路由数据,共', routes.length, '个顶级路由');
|
|
|
|
// 将后端数据转换为前端RouteInfo格式
|
|
const mapRouteData = (route: any): RouteInfo => ({
|
|
id: route.id,
|
|
route_path: route.route_path,
|
|
route_name: route.route_name,
|
|
route_title: route.route_title,
|
|
icon: route.icon || '',
|
|
sort_order: route.sort_order || 0,
|
|
is_hidden: route.is_hidden || false,
|
|
is_cache: route.is_cache !== false, // 默认true
|
|
status: route.status || 1,
|
|
parent_id: route.parent_id || null,
|
|
component: route.component,
|
|
children: route.children ? route.children.map(mapRouteData) : undefined
|
|
});
|
|
|
|
return routes.map(mapRouteData);
|
|
} catch (error) {
|
|
console.error('❌ [getRoutes] 获取路由数据失败:', error);
|
|
// 失败时返回空数组,让前端显示错误提示
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取指定角色的路由权限
|
|
* @param roleId 角色ID
|
|
*/
|
|
export async function getRoleRoutePermissions(roleId: number): Promise<RoleRoutePermission[]> {
|
|
try {
|
|
// 导入 axios-client 的 get 函数
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getRoleRoutePermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`);
|
|
|
|
// 使用 axios-client 的 get 函数调用真实后端API
|
|
const response = await get<any>(`/rbac/roles/${roleId}/routes`);
|
|
console.log('📦 [getRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, msg: "success", data: { role_id, routes: [...] } }
|
|
let routes: any[] = [];
|
|
if (response.data && response.data.data && Array.isArray(response.data.data.routes)) {
|
|
routes = response.data.data.routes;
|
|
}
|
|
|
|
// 将路由数据转换为RoleRoutePermission格式
|
|
const permissions = routes.map((route, index) => ({
|
|
id: index + 1,
|
|
role_id: roleId,
|
|
route_id: route.id,
|
|
permission: 'RW', // 默认读写权限
|
|
created_at: new Date().toISOString()
|
|
}));
|
|
|
|
console.log('✅ [getRoleRoutePermissions] 获取角色路由权限成功:', permissions);
|
|
return permissions;
|
|
} catch (error) {
|
|
console.error('❌ [getRoleRoutePermissions] 获取角色路由权限失败:', error);
|
|
// 失败时返回空数组,避免页面崩溃
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新角色的路由权限
|
|
* @param roleId 角色ID
|
|
* @param routeIds 路由ID数组
|
|
*/
|
|
export async function updateRoleRoutePermissions(
|
|
roleId: number,
|
|
routeIds: number[]
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
// 导入 axios-client 的 put 函数
|
|
const { put } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [updateRoleRoutePermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`, routeIds);
|
|
|
|
// 使用 axios-client 的 put 函数调用真实后端API
|
|
const response = await put<any>(`/rbac/roles/${roleId}/routes`, {
|
|
route_ids: routeIds,
|
|
permission: 'RW'
|
|
});
|
|
console.log('📦 [updateRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, msg: "success", data: { role_id, assigned_count, removed_count, route_ids } }
|
|
let message = '角色权限更新成功';
|
|
if (response.data && response.data.msg) {
|
|
message = response.data.msg;
|
|
} else if (response.data && response.data.data) {
|
|
const { assigned_count, removed_count } = response.data.data;
|
|
message = `成功分配 ${assigned_count} 个路由,移除了 ${removed_count} 个旧路由`;
|
|
}
|
|
|
|
console.log('✅ [updateRoleRoutePermissions] 角色权限更新成功');
|
|
return { success: true, message };
|
|
} catch (error) {
|
|
console.error('❌ [updateRoleRoutePermissions] 更新角色权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '更新角色权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
// ==================== 用户角色管理 API ====================
|
|
|
|
/**
|
|
* 获取指定角色的用户列表
|
|
* @param roleId 角色ID
|
|
* @param params 查询参数
|
|
*/
|
|
export async function getRoleUsers(
|
|
roleId: number,
|
|
params?: {
|
|
page?: number;
|
|
page_size?: number;
|
|
area?: string;
|
|
username?: string;
|
|
}
|
|
): Promise<UserInfo[]> {
|
|
try {
|
|
// 导入 axios-client 的 get 函数
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getRoleUsers] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/users`, params);
|
|
|
|
// 构建查询参数对象
|
|
const queryParams: Record<string, any> = {};
|
|
if (params?.page) queryParams.page = params.page;
|
|
if (params?.page_size) queryParams.page_size = params.page_size;
|
|
if (params?.area) queryParams.area = params.area;
|
|
if (params?.username) queryParams.username = params.username;
|
|
|
|
// 使用 axios-client 的 get 函数调用真实后端API
|
|
const response = await get<any>(`/api/v3/rbac/roles/${roleId}/users`, queryParams);
|
|
console.log('📦 [getRoleUsers] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, message: "success", data: { total, page, page_size, items: [...] } }
|
|
let items: any[] = [];
|
|
if (response.data && response.data.data && Array.isArray(response.data.data.items)) {
|
|
items = response.data.data.items;
|
|
} else if (response.data && Array.isArray(response.data.items)) {
|
|
items = response.data.items;
|
|
}
|
|
|
|
console.log('✅ [getRoleUsers] 解析出的用户数组:', items);
|
|
|
|
const users = items.map((user: any) => ({
|
|
id: user.user_id || user.id,
|
|
username: user.username,
|
|
nick_name: user.nick_name,
|
|
phone_number: user.phone_number || '',
|
|
email: user.email || '',
|
|
ou_name: user.ou_name,
|
|
status: user.status || 1,
|
|
is_leader: user.is_leader || false
|
|
}));
|
|
|
|
console.log('✅ [getRoleUsers] 最终返回的用户列表:', users);
|
|
return users;
|
|
} catch (error) {
|
|
console.error('❌ [getRoleUsers] 获取角色用户列表失败:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取所有用户列表
|
|
* @param params 查询参数
|
|
*/
|
|
export async function getAllUsers(params?: {
|
|
page?: number;
|
|
page_size?: number;
|
|
area?: string;
|
|
username?: string;
|
|
}): Promise<UserInfo[]> {
|
|
try {
|
|
// 导入 axios-client 的 get 函数
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getAllUsers] 开始调用后端API:', '/admin/users/users', params);
|
|
|
|
// 构建查询参数对象
|
|
const queryParams: Record<string, any> = {};
|
|
if (params?.page) queryParams.page = params.page;
|
|
if (params?.page_size) queryParams.page_size = params.page_size;
|
|
if (params?.username) queryParams.search = params.username;
|
|
|
|
// 使用 axios-client 的 get 函数,会自动添加 baseURL 和 Authorization
|
|
const response = await get<any>('/admin/users/users', queryParams);
|
|
console.log('📦 [getAllUsers] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// axios-client 返回格式: { data: { users: [...], total: number }, status: 200 }
|
|
// 后端实际返回: { users: [...], total: number }
|
|
let users: any[] = [];
|
|
|
|
if (response.data) {
|
|
// 如果 response.data 是对象且包含 users 字段
|
|
if (response.data.users && Array.isArray(response.data.users)) {
|
|
users = response.data.users;
|
|
}
|
|
// 如果 response.data 本身就是数组
|
|
else if (Array.isArray(response.data)) {
|
|
users = response.data;
|
|
}
|
|
}
|
|
|
|
console.log('✅ [getAllUsers] 解析出的用户数组:', users);
|
|
console.log('✅ [getAllUsers] 用户数量:', users.length);
|
|
|
|
const userList = users.map(user => ({
|
|
id: user.user_id || user.id, // 优先使用 user_id,兼容不同的后端响应格式
|
|
username: user.username,
|
|
nick_name: user.nick_name,
|
|
phone_number: user.phone_number || '',
|
|
email: user.email || '',
|
|
ou_name: user.ou_name,
|
|
status: user.status || 1,
|
|
is_leader: user.is_leader || false
|
|
}));
|
|
|
|
console.log('✅ [getAllUsers] 最终返回的用户列表:', userList);
|
|
return userList;
|
|
} catch (error) {
|
|
console.error('❌ [getAllUsers] 获取用户列表失败:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 为用户分配角色
|
|
* @param userId 用户ID
|
|
* @param roleIds 角色ID数组
|
|
*/
|
|
export async function assignUserRoles(
|
|
userId: number,
|
|
roleIds: number[]
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
// 导入 axios-client 的 post 函数
|
|
const { post } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [assignUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`, roleIds);
|
|
|
|
// 使用 axios-client 的 post 函数调用真实后端API
|
|
const response = await post<any>(`/api/v3/rbac/users/${userId}/roles`, {
|
|
role_ids: roleIds
|
|
});
|
|
console.log('📦 [assignUserRoles] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, message: "角色分配成功", data: { user_id, roles: [...] } }
|
|
let message = '用户角色分配成功';
|
|
if (response.data && response.data.message) {
|
|
message = response.data.message;
|
|
}
|
|
|
|
console.log('✅ [assignUserRoles] 角色分配成功');
|
|
return { success: true, message };
|
|
} catch (error) {
|
|
console.error('❌ [assignUserRoles] 分配用户角色失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '分配失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 移除用户角色
|
|
* @param userId 用户ID
|
|
* @param roleId 角色ID
|
|
*/
|
|
export async function revokeUserRole(
|
|
userId: number,
|
|
roleId: number
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
// 导入 axios-client 的 del 函数
|
|
const { del } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [revokeUserRole] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles/${roleId}`);
|
|
|
|
// 使用 axios-client 的 del 函数调用真实后端API
|
|
const response = await del<any>(`/api/v3/rbac/users/${userId}/roles/${roleId}`);
|
|
console.log('📦 [revokeUserRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, message: "角色移除成功" }
|
|
let message = '用户角色移除成功';
|
|
if (response.data && response.data.message) {
|
|
message = response.data.message;
|
|
}
|
|
|
|
console.log('✅ [revokeUserRole] 角色移除成功');
|
|
return { success: true, message };
|
|
} catch (error) {
|
|
console.error('❌ [revokeUserRole] 移除用户角色失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '移除失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建新角色
|
|
* @param roleData 角色数据
|
|
*/
|
|
export async function createRole(
|
|
roleData: Omit<RoleInfo, 'id' | 'created_at' | 'updated_at'>
|
|
): Promise<{ success: boolean; message: string; data?: RoleInfo }> {
|
|
try {
|
|
// 导入 axios-client 的 post 函数
|
|
const { post } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [createRole] 开始调用后端API:', `/api/v3/rbac/roles`, roleData);
|
|
|
|
// 使用 axios-client 的 post 函数调用真实后端API
|
|
const response = await post<any>(`/api/v3/rbac/roles`, {
|
|
role_key: roleData.role_key,
|
|
role_name: roleData.role_name,
|
|
description: roleData.description || '',
|
|
data_scope: roleData.data_scope || 'SELF',
|
|
metadata: {}
|
|
});
|
|
|
|
console.log('📦 [createRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, message: "角色创建成功", data: { id, role_key, ... } }
|
|
const data = response.data?.data || response.data;
|
|
|
|
return {
|
|
success: true,
|
|
message: response.data?.message || '角色创建成功',
|
|
data: {
|
|
id: data.id,
|
|
role_key: data.role_key,
|
|
role_name: data.role_name,
|
|
data_scope: data.data_scope,
|
|
description: data.description || '',
|
|
parent_role_id: data.parent_role_id || null,
|
|
priority: data.priority || 0,
|
|
is_system_role: data.is_system || false,
|
|
created_at: data.created_at,
|
|
updated_at: data.updated_at
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error('❌ [createRole] 创建角色失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '创建角色失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新角色信息
|
|
* @param roleId 角色ID
|
|
* @param roleData 角色数据
|
|
*/
|
|
export async function updateRole(
|
|
roleId: number,
|
|
roleData: Partial<Omit<RoleInfo, 'id' | 'created_at' | 'updated_at'>>
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
// 导入 axios-client 的 put 函数
|
|
const { put } = await import('~/api/axios-client');
|
|
|
|
const updatePayload: any = {};
|
|
|
|
if (roleData.role_name !== undefined) updatePayload.role_name = roleData.role_name;
|
|
if (roleData.description !== undefined) updatePayload.description = roleData.description;
|
|
if (roleData.data_scope !== undefined) updatePayload.data_scope = roleData.data_scope;
|
|
if (roleData.priority !== undefined) updatePayload.priority = roleData.priority;
|
|
if (roleData.parent_role_id !== undefined) updatePayload.parent_role_id = roleData.parent_role_id;
|
|
|
|
console.log('🔍 [updateRole] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}`, updatePayload);
|
|
|
|
const response = await put<any>(`/api/v3/rbac/roles/${roleId}`, updatePayload);
|
|
|
|
console.log('📦 [updateRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
return { success: true, message: response.data?.message || '角色更新成功' };
|
|
} catch (error) {
|
|
console.error('❌ [updateRole] 更新角色失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '更新角色失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除角色
|
|
* @param roleId 角色ID
|
|
* @param force 是否强制删除(会自动解除用户关联)
|
|
*/
|
|
export async function deleteRole(
|
|
roleId: number,
|
|
force = false
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
// 导入 axios-client 的 del 函数
|
|
const { del } = await import('~/api/axios-client');
|
|
|
|
const url = `/api/v3/rbac/roles/${roleId}${force ? '?force=true' : ''}`;
|
|
|
|
console.log('🔍 [deleteRole] 开始调用后端API:', url);
|
|
|
|
const response = await del<any>(url);
|
|
|
|
console.log('📦 [deleteRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
|
|
if (response.error) {
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
return { success: true, message: response.data?.message || '角色删除成功' };
|
|
} catch (error) {
|
|
console.error('❌ [deleteRole] 删除角色失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '删除角色失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
// ==================== 权限管理 API ====================
|
|
|
|
/**
|
|
* 获取权限列表(树形或平铺)
|
|
* @param format 格式:tree(树形)或 flat(平铺)
|
|
* @param params 查询参数
|
|
*/
|
|
export async function getPermissions(
|
|
format: 'tree' | 'flat' = 'tree',
|
|
params?: {
|
|
module?: string;
|
|
permission_type?: 'API' | 'MENU' | 'BUTTON';
|
|
include_system?: boolean;
|
|
}
|
|
): Promise<Permission[]> {
|
|
try {
|
|
const response = await get<any>(`${RBAC_API_BASE}/permissions`, {
|
|
format,
|
|
...params
|
|
});
|
|
const data = handleApiResponse<Permission[]>(response);
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('获取权限列表失败:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取权限详情
|
|
* @param permissionId 权限ID
|
|
*/
|
|
export async function getPermissionDetail(permissionId: number): Promise<Permission | null> {
|
|
try {
|
|
const response = await get<any>(`${RBAC_API_BASE}/permissions/${permissionId}`);
|
|
const data = handleApiResponse<Permission>(response);
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('获取权限详情失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建权限
|
|
* @param permissionData 权限数据
|
|
*/
|
|
export async function createPermission(
|
|
permissionData: Omit<Permission, 'id' | 'children'>
|
|
): Promise<{ success: boolean; message: string; data?: Permission }> {
|
|
try {
|
|
// 从 permission_key 解析 module, resource, action
|
|
const [module, resource, action] = permissionData.permission_key.split(':');
|
|
|
|
const response = await post<any>(`${RBAC_API_BASE}/permissions`, {
|
|
permission_key: permissionData.permission_key,
|
|
display_name: permissionData.display_name,
|
|
description: permissionData.description || '',
|
|
module,
|
|
resource,
|
|
action,
|
|
permission_type: permissionData.permission_type || 'API',
|
|
parent_id: permissionData.parent_id || null,
|
|
sort_order: permissionData.sort_order || 0,
|
|
metadata: {}
|
|
});
|
|
|
|
const data = handleApiResponse<Permission>(response);
|
|
|
|
return {
|
|
success: true,
|
|
message: '权限创建成功',
|
|
data
|
|
};
|
|
} catch (error) {
|
|
console.error('创建权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '创建权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新权限
|
|
* @param permissionId 权限ID
|
|
* @param permissionData 权限数据
|
|
*/
|
|
export async function updatePermission(
|
|
permissionId: number,
|
|
permissionData: Partial<Omit<Permission, 'id' | 'children'>>
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await put<any>(`${RBAC_API_BASE}/permissions/${permissionId}`, permissionData);
|
|
handleApiResponse<any>(response);
|
|
|
|
return { success: true, message: '权限更新成功' };
|
|
} catch (error) {
|
|
console.error('更新权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '更新权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除权限
|
|
* @param permissionId 权限ID
|
|
*/
|
|
export async function deletePermission(
|
|
permissionId: number
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await del<any>(`${RBAC_API_BASE}/permissions/${permissionId}`);
|
|
handleApiResponse<any>(response);
|
|
|
|
return { success: true, message: '权限删除成功' };
|
|
} catch (error) {
|
|
console.error('删除权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '删除权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
// ==================== 角色权限关联 API ====================
|
|
|
|
/**
|
|
* 获取角色的所有权限
|
|
* @param roleId 角色ID
|
|
*/
|
|
export async function getRolePermissions(roleId: number): Promise<RolePermissionDetail[]> {
|
|
try {
|
|
const response = await get<any>(`${RBAC_API_BASE}/roles/${roleId}/permissions`);
|
|
const data = handleApiResponse<{ permissions: any[] }>(response);
|
|
|
|
return data.permissions.map(perm => ({
|
|
id: perm.id,
|
|
permission_id: perm.permission_id,
|
|
permission_key: perm.permission_key,
|
|
display_name: perm.display_name,
|
|
grant_type: perm.grant_type || 'GRANT',
|
|
data_scope: perm.data_scope || 'ALL'
|
|
}));
|
|
} catch (error) {
|
|
console.error('获取角色权限失败:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量分配权限给角色
|
|
* @param roleId 角色ID
|
|
* @param permissions 权限配置列表
|
|
* @param replace 是否替换全部(true=替换,false=追加)
|
|
*/
|
|
export async function assignPermissionsToRole(
|
|
roleId: number,
|
|
permissions: RolePermissionConfig[],
|
|
replace = false
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await post<any>(`${RBAC_API_BASE}/roles/${roleId}/permissions`, {
|
|
permissions: permissions.map(p => ({
|
|
permission_id: p.permission_id,
|
|
grant_type: p.grant_type || 'GRANT',
|
|
data_scope: p.data_scope || 'ALL'
|
|
})),
|
|
replace
|
|
});
|
|
|
|
handleApiResponse<any>(response);
|
|
|
|
return { success: true, message: '权限分配成功' };
|
|
} catch (error) {
|
|
console.error('分配权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '分配权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新单个权限配置
|
|
* @param roleId 角色ID
|
|
* @param permissionId 权限ID
|
|
* @param config 权限配置
|
|
*/
|
|
export async function updateRolePermission(
|
|
roleId: number,
|
|
permissionId: number,
|
|
config: Partial<RolePermissionConfig>
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await put<any>(
|
|
`${RBAC_API_BASE}/roles/${roleId}/permissions/${permissionId}`,
|
|
config
|
|
);
|
|
handleApiResponse<any>(response);
|
|
|
|
return { success: true, message: '权限配置更新成功' };
|
|
} catch (error) {
|
|
console.error('更新权限配置失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '更新权限配置失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 移除角色权限
|
|
* @param roleId 角色ID
|
|
* @param permissionId 权限ID
|
|
*/
|
|
export async function revokeRolePermission(
|
|
roleId: number,
|
|
permissionId: number
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await del<any>(`${RBAC_API_BASE}/roles/${roleId}/permissions/${permissionId}`);
|
|
handleApiResponse<any>(response);
|
|
|
|
return { success: true, message: '权限移除成功' };
|
|
} catch (error) {
|
|
console.error('移除权限失败:', error);
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '移除权限失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取指定用户的所有角色
|
|
* @param userId 用户ID
|
|
* @returns 用户的角色列表
|
|
*/
|
|
export async function getUserRoles(userId: number): Promise<RoleInfo[]> {
|
|
try {
|
|
const { get } = await import('~/api/axios-client');
|
|
|
|
console.log('🔍 [getUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`);
|
|
|
|
const response = await get<any>(`/api/v3/rbac/users/${userId}/roles`);
|
|
|
|
if (response.error) {
|
|
console.error('❌ [getUserRoles] API调用失败:', response.error);
|
|
throw new Error(response.error);
|
|
}
|
|
|
|
// 后端响应格式: { code: 200, msg: "success", data: { user_id, username, roles: [...] } }
|
|
let roles: any[] = [];
|
|
if (response.data && response.data.data && Array.isArray(response.data.data.roles)) {
|
|
roles = response.data.data.roles;
|
|
} else if (response.data && Array.isArray(response.data.roles)) {
|
|
// 兼容可能的响应格式
|
|
roles = response.data.roles;
|
|
}
|
|
|
|
console.log('✅ [getUserRoles] 成功获取用户角色,共', roles.length, '个角色');
|
|
|
|
// 将后端数据转换为RoleInfo格式
|
|
return roles.map(role => ({
|
|
id: role.id || role.role_id,
|
|
role_key: role.role_key,
|
|
role_name: role.role_name,
|
|
data_scope: role.data_scope,
|
|
description: role.description || '',
|
|
priority: role.priority || 0,
|
|
is_system_role: role.is_system || false,
|
|
created_at: role.created_at || '',
|
|
updated_at: role.updated_at || ''
|
|
}));
|
|
} catch (error) {
|
|
console.error('❌ [getUserRoles] 获取用户角色失败:', error);
|
|
// 失败时返回空数组
|
|
return [];
|
|
}
|
|
}
|