生产管理系统 - 角色管理联调

This commit is contained in:
2025-11-06 10:17:44 +08:00
parent 279bbe8536
commit 9f1cf21042
8 changed files with 600 additions and 189 deletions

View File

@@ -5,18 +5,21 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Loader2 } from 'lucide-react';
import { Role, RoleType } from '../types';
interface RoleDetailDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
selectedRole: Role | null;
detailLoading?: boolean;
}
export function RoleDetailDialog({
open,
onOpenChange,
selectedRole
selectedRole,
detailLoading = false
}: RoleDetailDialogProps) {
const getRoleTypeBadge = (type: RoleType) => {
return type === 'system' ? (
@@ -34,8 +37,6 @@ export function RoleDetailDialog({
);
};
if (!selectedRole) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl">
@@ -46,58 +47,69 @@ export function RoleDetailDialog({
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="mt-1">{selectedRole.name}</div>
{detailLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>...</span>
</div>
<div>
<Label></Label>
<div className="mt-1">{selectedRole.code}</div>
</div>
<div className="col-span-2">
<Label></Label>
<div className="mt-1">{selectedRole.description || '-'}</div>
</div>
<div>
<Label></Label>
<div className="mt-1">{getRoleTypeBadge(selectedRole.type)}</div>
</div>
<div>
<Label></Label>
<div className="mt-1">{getStatusBadge(selectedRole.status)}</div>
</div>
{selectedRole.defaultHomePage && (
) : selectedRole ? (
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="mt-1">{selectedRole.name}</div>
</div>
<div>
<Label></Label>
<div className="mt-1">{selectedRole.code}</div>
</div>
<div className="col-span-2">
<Label></Label>
<div className="mt-1">{selectedRole.defaultHomePage}</div>
<Label></Label>
<div className="mt-1">{selectedRole.description || '-'}</div>
</div>
)}
<div>
<Label></Label>
<div className="mt-1">
{selectedRole.menuIds.includes('*') ? '全部' : selectedRole.menuIds.length}
<div>
<Label></Label>
<div className="mt-1">{getRoleTypeBadge(selectedRole.type)}</div>
</div>
<div>
<Label></Label>
<div className="mt-1">{getStatusBadge(selectedRole.status)}</div>
</div>
{selectedRole.defaultHomePage && (
<div className="col-span-2">
<Label></Label>
<div className="mt-1">{selectedRole.defaultHomePage}</div>
</div>
)}
<div>
<Label></Label>
<div className="mt-1">
{selectedRole.menuIds?.includes('*') ? '全部' : (selectedRole.menuIds?.length || 0)}
</div>
</div>
<div>
<Label></Label>
<div className="mt-1">
{selectedRole.permissionIds?.includes('*') ? '全部' : (selectedRole.permissionIds?.length || 0)}
</div>
</div>
<div>
<Label></Label>
<div className="mt-1">
{new Date(selectedRole.createdAt).toLocaleString('zh-CN')}
</div>
</div>
<div>
<Label></Label>
<div className="mt-1">
{new Date(selectedRole.updatedAt).toLocaleString('zh-CN')}
</div>
</div>
</div>
<div>
<Label></Label>
<div className="mt-1">
{selectedRole.permissionIds.includes('*') ? '全部' : selectedRole.permissionIds.length}
</div>
) : (
<div className="flex items-center justify-center py-8">
<span></span>
</div>
<div>
<Label></Label>
<div className="mt-1">
{new Date(selectedRole.createdAt).toLocaleString('zh-CN')}
</div>
</div>
<div>
<Label></Label>
<div className="mt-1">
{new Date(selectedRole.updatedAt).toLocaleString('zh-CN')}
</div>
</div>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@@ -9,7 +9,7 @@ import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox';
import { Card } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { ChevronRight } from 'lucide-react';
import { ChevronRight, Loader2 } from 'lucide-react';
import { Role, RoleFormData, allSystemMenus } from '../types';
interface RoleFormDialogProps {
@@ -19,6 +19,7 @@ interface RoleFormDialogProps {
formData: RoleFormData;
onFormDataChange: (data: RoleFormData) => void;
onSave: () => void;
editFormLoading?: boolean;
}
export function RoleFormDialog({
@@ -27,7 +28,8 @@ export function RoleFormDialog({
editingRole,
formData,
onFormDataChange,
onSave
onSave,
editFormLoading = false
}: RoleFormDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -38,8 +40,16 @@ export function RoleFormDialog({
{editingRole ? '编辑角色信息' : '添加新角色'}
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[calc(90vh-180px)]">
<div className="space-y-6 pr-4">
{/* Loading state for edit form */}
{editFormLoading && editingRole ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>...</span>
</div>
) : (
<ScrollArea className="max-h-[calc(90vh-180px)]">
<div className="space-y-6 pr-4">
{/* 基本信息 */}
<div className="space-y-4">
<h4 className="text-green-800"></h4>
@@ -222,7 +232,8 @@ export function RoleFormDialog({
</Card>
</div>
</div>
</ScrollArea>
</ScrollArea>
)}
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@@ -5,6 +5,7 @@ import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { DataPagination } from '@/components/ui/data-pagination';
import { Eye, Edit, Trash2, Shield } from 'lucide-react';
import { Role, RoleType } from '../types';
import { PaginationState } from './roleApi';
@@ -125,60 +126,20 @@ export function RoleList({
</Table>
{/* 分页控制 */}
{!loading && pagination && pagination.totalPages > 1 && (
<div className="flex items-center justify-between px-2 py-4">
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
<span> {(pagination.page - 1) * pagination.size + 1} {Math.min(pagination.page * pagination.size, pagination.total)} {pagination.total} </span>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => onPageChange?.(pagination.page - 1)}
disabled={!pagination.hasPrev}
>
</Button>
<div className="flex items-center space-x-1">
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((pageNum) => (
<Button
key={pageNum}
variant={pageNum === pagination.page ? "default" : "outline"}
size="sm"
className="w-8 h-8 p-0"
onClick={() => onPageChange?.(pageNum)}
>
{pageNum}
</Button>
))}
</div>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange?.(pagination.page + 1)}
disabled={!pagination.hasNext}
>
</Button>
{onPageSizeChange && (
<>
<select
value={pagination.size}
onChange={(e) => onPageSizeChange(Number(e.target.value))}
className="ml-2 h-8 w-16 rounded-md border border-input bg-background text-sm"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
<span className="text-sm text-muted-foreground">/</span>
</>
)}
</div>
</div>
{!loading && pagination && pagination.total > 0 && (
<DataPagination
currentPage={pagination.page}
totalPages={pagination.totalPages}
pageSize={pagination.size}
totalItems={pagination.total}
startIndex={(pagination.page - 1) * pagination.size + 1}
endIndex={Math.min(pagination.page * pagination.size, pagination.total)}
onPageChange={(page) => onPageChange?.(page)}
onPageSizeChange={(size) => onPageSizeChange?.(size)}
canPreviousPage={pagination.hasPrev}
canNextPage={pagination.hasNext}
pageSizeOptions={[10, 20, 50, 100]}
/>
)}
</Card>
);

View File

@@ -8,10 +8,16 @@
import { getAuthToken } from "@/utils/token";
import {
getRolesApiV1UsersPermissionsRolesGet,
createRoleApiV1UsersPermissionsRolesPost,
getRoleApiV1UsersPermissionsRolesRoleIdGet,
deleteRoleApiV1UsersPermissionsRolesRoleIdDelete,
updateRoleApiV1UsersPermissionsRolesRoleIdPut,
} from "@/lib/api/sdk.gen";
import {
RoleApiData,
Role,
RolesApiResponse,
RolesQueryParams,
} from '../types';
// 本地定义PaginationState以避免导入问题
@@ -102,9 +108,9 @@ export async function fetchRoles(params: RolesQueryParams = {}): Promise<RolesAp
*/
export function transformRoleData(apiRole: RoleApiData): Role {
return {
id: apiRole.id,
id: apiRole.id, // 使用API返回的真实ID
name: apiRole.name,
code: apiRole.name.toLowerCase().replace(/\s+/g, '_'), // 生成code
code: apiRole.id, // 使用API返回的ID作为code确保唯一性
description: apiRole.description,
type: 'custom', // 默认为自定义角色
menuIds: [],
@@ -123,6 +129,140 @@ export function transformRolesList(apiRoles: RoleApiData[]): Role[] {
return apiRoles.map(transformRoleData);
}
/**
* 创建新角色
*/
export async function createRole(roleData: {
name: string;
description: string;
permission_ids: string[];
}) {
try {
// 获取认证token
const token = getAuthToken();
console.log('创建角色API调用参数:', roleData);
// 使用SDK API调用创建角色
const response = await createRoleApiV1UsersPermissionsRolesPost({
body: roleData,
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '创建角色失败');
}
console.log('创建角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to create role:', error);
throw error;
}
}
/**
* 获取角色详情
*/
export async function getRoleDetail(roleId: string) {
try {
// 获取认证token
const token = getAuthToken();
console.log('获取角色详情API调用参数:', roleId);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用获取角色详情
const response = await getRoleApiV1UsersPermissionsRolesRoleIdGet({
path: {
role_id: roleId,
},
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '获取角色详情失败');
}
console.log('获取角色详情API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to get role detail:', error);
throw error;
}
}
/**
* 更新角色
*/
export async function updateRole(roleId: string, roleData: {
name: string;
description: string;
permission_ids: string[];
}) {
try {
// 获取认证token
const token = getAuthToken();
console.log('更新角色API调用参数:', roleId, roleData);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用更新角色
const response = await updateRoleApiV1UsersPermissionsRolesRoleIdPut({
path: {
role_id: roleId,
},
body: roleData,
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '更新角色失败');
}
console.log('更新角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to update role:', error);
throw error;
}
}
/**
* 删除角色
*/
export async function deleteRole(roleId: string) {
try {
// 获取认证token
const token = getAuthToken();
console.log('删除角色API调用参数:', roleId);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用删除角色
const response = await deleteRoleApiV1UsersPermissionsRolesRoleIdDelete({
path: {
role_id: roleId,
},
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '删除角色失败');
}
console.log('删除角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to delete role:', error);
throw error;
}
}
/**
* 格式化日期
*/