生产管理系统 - 角色管理联调
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
PaginationPrevious,
|
||||
PaginationEllipsis
|
||||
} from '@/components/ui/pagination';
|
||||
import { Eye, Edit, Lock, Trash2, UserX, UserCheck, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { Eye, Edit, Trash2, UserX, UserCheck, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { Employee, UserStatus } from '../types';
|
||||
import { PaginationState } from './employeeApi';
|
||||
|
||||
@@ -28,7 +28,6 @@ interface EmployeeListProps {
|
||||
onPageSizeChange?: (size: number) => void;
|
||||
onViewDetail: (employee: Employee) => void;
|
||||
onEdit: (employee: Employee) => void;
|
||||
onResetPassword: (employee: Employee) => void;
|
||||
onToggleStatus: (employee: Employee) => void;
|
||||
onDelete: (id: string) => void;
|
||||
onAudit?: (employee: Employee, action: 'approve' | 'reject') => void;
|
||||
@@ -43,7 +42,6 @@ export function EmployeeList({
|
||||
onPageSizeChange,
|
||||
onViewDetail,
|
||||
onEdit,
|
||||
onResetPassword,
|
||||
onToggleStatus,
|
||||
onDelete,
|
||||
onAudit,
|
||||
@@ -174,21 +172,7 @@ export function EmployeeList({
|
||||
<p>编辑员工</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onResetPassword(employee)}
|
||||
>
|
||||
<Lock className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>重置密码</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -34,6 +34,10 @@ import {
|
||||
PaginationState,
|
||||
EmployeesQueryParams
|
||||
} from './components/employeeApi';
|
||||
import {
|
||||
fetchRoles,
|
||||
transformRolesList
|
||||
} from '../role/components/roleApi';
|
||||
|
||||
export default function EmployeeManagementPage() {
|
||||
const [employees, setEmployees] = useState<Employee[]>([]);
|
||||
@@ -80,10 +84,22 @@ export default function EmployeeManagementPage() {
|
||||
loadRoles();
|
||||
}, [pagination.page, pagination.size,filters.searchKeyword, filters.statusFilter]);
|
||||
|
||||
const loadRoles = () => {
|
||||
const data = localStorage.getItem('smart_agriculture_roles');
|
||||
if (data) {
|
||||
setRoles(JSON.parse(data));
|
||||
const loadRoles = async () => {
|
||||
try {
|
||||
// 调用角色API获取角色数据
|
||||
const response = await fetchRoles({
|
||||
page: 1,
|
||||
size: 100, // 获取所有角色
|
||||
sort_order: 'desc'
|
||||
});
|
||||
|
||||
// 转换数据格式
|
||||
const transformedRoles = transformRolesList(response.data);
|
||||
setRoles(transformedRoles);
|
||||
} catch (error) {
|
||||
console.error('Failed to load roles:', error);
|
||||
// API失败时设置为空数组
|
||||
setRoles([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -407,11 +423,7 @@ export default function EmployeeManagementPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetPassword = (employee: Employee) => {
|
||||
if (!confirm(`确定要重置 ${employee.name} 的密码吗?`)) return;
|
||||
toast.success('密码已重置为:123456');
|
||||
};
|
||||
|
||||
|
||||
const handleViewDetail = (employee: Employee) => {
|
||||
setSelectedEmployee(employee);
|
||||
setShowDetailDialog(true);
|
||||
@@ -431,7 +443,6 @@ export default function EmployeeManagementPage() {
|
||||
: emp
|
||||
);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('审核通过');
|
||||
} else {
|
||||
const reason = prompt('请输入驳回原因:');
|
||||
@@ -449,7 +460,6 @@ export default function EmployeeManagementPage() {
|
||||
: emp
|
||||
);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('已驳回');
|
||||
}
|
||||
}
|
||||
@@ -480,7 +490,6 @@ export default function EmployeeManagementPage() {
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
onViewDetail={handleViewDetail}
|
||||
onEdit={handleEdit}
|
||||
onResetPassword={handleResetPassword}
|
||||
onToggleStatus={handleToggleStatus}
|
||||
onDelete={handleDelete}
|
||||
onAudit={handleAudit}
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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)}>
|
||||
取消
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
|
||||
import { RoleManagementHeader } from './components/RoleManagementHeader';
|
||||
import { RoleManagementStatsCards } from './components/RoleManagementStatsCards';
|
||||
@@ -20,6 +30,10 @@ import { RoleManagementInstructions } from './components/RoleManagementInstructi
|
||||
import { Role, RoleFormData, RoleFilters } from './types';
|
||||
import {
|
||||
fetchRoles,
|
||||
createRole,
|
||||
getRoleDetail,
|
||||
deleteRole,
|
||||
updateRole,
|
||||
transformRolesList,
|
||||
RolesApiResponse,
|
||||
RolesQueryParams,
|
||||
@@ -42,8 +56,12 @@ export default function RoleManagementPage() {
|
||||
});
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [editingRole, setEditingRole] = useState<Role | null>(null);
|
||||
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
|
||||
const [deletingRoleId, setDeletingRoleId] = useState<string | null>(null);
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
const [editFormLoading, setEditFormLoading] = useState(false);
|
||||
const [formData, setFormData] = useState<RoleFormData>({
|
||||
type: 'custom',
|
||||
status: 'active',
|
||||
@@ -82,11 +100,8 @@ export default function RoleManagementPage() {
|
||||
console.error('Failed to load roles:', error);
|
||||
toast.error('加载角色数据失败');
|
||||
|
||||
// 如果API失败,使用localStorage中的数据作为fallback
|
||||
const data = localStorage.getItem('smart_agriculture_roles');
|
||||
if (data) {
|
||||
setRoles(JSON.parse(data));
|
||||
}
|
||||
// API失败时设置为空数组
|
||||
setRoles([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -101,10 +116,23 @@ export default function RoleManagementPage() {
|
||||
|
||||
// 分页处理函数
|
||||
const handlePageChange = (page: number) => {
|
||||
setPagination(prev => ({ ...prev, page }));
|
||||
// 确保页码不超出范围
|
||||
const filteredCount = roles.filter(role => {
|
||||
const matchKeyword = !filters.searchKeyword ||
|
||||
role.name.includes(filters.searchKeyword) ||
|
||||
role.code.includes(filters.searchKeyword) ||
|
||||
(role.description && role.description.includes(filters.searchKeyword));
|
||||
return matchKeyword;
|
||||
}).length;
|
||||
|
||||
const maxPage = Math.ceil(filteredCount / pagination.size);
|
||||
const validPage = Math.min(Math.max(1, page), maxPage || 1);
|
||||
|
||||
setPagination(prev => ({ ...prev, page: validPage }));
|
||||
};
|
||||
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
// 当分页大小改变时,重置到第一页
|
||||
setPagination(prev => ({ ...prev, size, page: 1 }));
|
||||
};
|
||||
|
||||
@@ -117,6 +145,22 @@ export default function RoleManagementPage() {
|
||||
return matchKeyword;
|
||||
});
|
||||
|
||||
// 客户端分页逻辑
|
||||
const totalFilteredItems = filteredRoles.length;
|
||||
const totalPages = Math.ceil(totalFilteredItems / pagination.size);
|
||||
const startIndex = (pagination.page - 1) * pagination.size;
|
||||
const endIndex = Math.min(startIndex + pagination.size, totalFilteredItems);
|
||||
const paginatedRoles = totalFilteredItems > 0 ? filteredRoles.slice(startIndex, endIndex) : [];
|
||||
|
||||
// 计算分页状态
|
||||
const clientPagination = {
|
||||
...pagination,
|
||||
total: totalFilteredItems,
|
||||
totalPages,
|
||||
hasNext: pagination.page < totalPages,
|
||||
hasPrev: pagination.page > 1,
|
||||
};
|
||||
|
||||
const handleAddRole = () => {
|
||||
setEditingRole(null);
|
||||
setFormData({
|
||||
@@ -128,45 +172,185 @@ export default function RoleManagementPage() {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleEdit = (role: Role) => {
|
||||
setEditingRole(role);
|
||||
setFormData(role);
|
||||
setShowForm(true);
|
||||
// 处理表单弹窗关闭
|
||||
const handleFormDialogClose = (open: boolean) => {
|
||||
if (!open) {
|
||||
// 弹窗关闭时清空所有数据和loading状态
|
||||
setEditingRole(null);
|
||||
setEditFormLoading(false);
|
||||
setFormData({
|
||||
type: 'custom',
|
||||
status: 'active',
|
||||
menuIds: [],
|
||||
permissionIds: [],
|
||||
});
|
||||
}
|
||||
setShowForm(open);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!formData.name || !formData.code) {
|
||||
toast.error('请填写必填项');
|
||||
// 处理详情弹窗关闭
|
||||
const handleDetailDialogClose = (open: boolean) => {
|
||||
if (!open) {
|
||||
// 弹窗关闭时清空选中的角色和loading状态
|
||||
setSelectedRole(null);
|
||||
setDetailLoading(false);
|
||||
}
|
||||
setShowDetailDialog(open);
|
||||
};
|
||||
|
||||
const handleEdit = (role: Role) => {
|
||||
// 先打开弹框,设置初始状态
|
||||
setEditingRole(role);
|
||||
setFormData({
|
||||
type: 'custom',
|
||||
status: 'active',
|
||||
menuIds: [],
|
||||
permissionIds: [],
|
||||
});
|
||||
setShowForm(true);
|
||||
setEditFormLoading(true);
|
||||
|
||||
// 在弹框内部异步加载数据
|
||||
loadEditFormData(role.id);
|
||||
};
|
||||
|
||||
// 加载编辑表单数据的独立函数
|
||||
const loadEditFormData = async (roleId: string) => {
|
||||
try {
|
||||
console.log('编辑角色,使用角色ID:', roleId);
|
||||
|
||||
// 调用API获取角色详情
|
||||
const roleDetail = await getRoleDetail(roleId);
|
||||
|
||||
// 将API返回的数据转换为表单格式
|
||||
const formData: RoleFormData = {
|
||||
name: roleDetail.name,
|
||||
code: editingRole?.code || roleDetail.id, // 使用转换后的code(即API返回的ID)
|
||||
description: roleDetail.description,
|
||||
type: 'custom',
|
||||
status: 'active',
|
||||
menuIds: [],
|
||||
permissionIds: [],
|
||||
defaultHomePage: editingRole?.defaultHomePage,
|
||||
};
|
||||
|
||||
setFormData(formData);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('获取角色详情失败:', error);
|
||||
|
||||
// 处理API返回的错误信息
|
||||
if (error?.data?.message) {
|
||||
toast.error(error.data.message);
|
||||
} else if (error?.data?.detail?.original_detail) {
|
||||
toast.error(error.data.detail.original_detail);
|
||||
} else if (error?.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('获取角色详情失败');
|
||||
}
|
||||
// 加载失败时关闭弹框
|
||||
setShowForm(false);
|
||||
} finally {
|
||||
setEditFormLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formData.name) {
|
||||
toast.error('请填写角色名称');
|
||||
return;
|
||||
}
|
||||
|
||||
if (editingRole) {
|
||||
const updated = roles.map(role =>
|
||||
role.id === editingRole.id
|
||||
? {
|
||||
...role,
|
||||
...formData,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: role
|
||||
);
|
||||
setRoles(updated);
|
||||
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
|
||||
toast.success('角色更新成功');
|
||||
} else {
|
||||
const newRole: Role = {
|
||||
id: `role-${Date.now()}`,
|
||||
...formData as Role,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
const updated = [...roles, newRole];
|
||||
setRoles(updated);
|
||||
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
|
||||
toast.success('角色添加成功');
|
||||
}
|
||||
// 编辑角色 - 检查数据是否有变化
|
||||
const hasChanges =
|
||||
editingRole.name !== formData.name ||
|
||||
editingRole.description !== formData.description ||
|
||||
JSON.stringify(editingRole.permissionIds) !== JSON.stringify(formData.permissionIds);
|
||||
|
||||
setShowForm(false);
|
||||
if (hasChanges) {
|
||||
// 有数据变化,调用API更新角色
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 准备API参数
|
||||
const roleData = {
|
||||
name: formData.name,
|
||||
description: formData.description || '',
|
||||
permission_ids: formData.permissionIds || [],
|
||||
};
|
||||
|
||||
console.log('开始更新角色:', editingRole.id, roleData);
|
||||
|
||||
// 调用API更新角色
|
||||
await updateRole(editingRole.id, roleData);
|
||||
|
||||
// 成功后刷新列表
|
||||
await loadRoles();
|
||||
|
||||
toast.success('角色更新成功');
|
||||
setShowForm(false);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('更新角色失败:', error);
|
||||
|
||||
// 处理API返回的错误信息
|
||||
if (error?.data?.message) {
|
||||
toast.error(error.data.message);
|
||||
} else if (error?.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('更新操作失败');
|
||||
}
|
||||
// 更新失败时不关闭表单,不刷新列表
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
// 数据没有变化,直接关闭表单,不刷新列表
|
||||
toast.success('角色信息无变化');
|
||||
setShowForm(false);
|
||||
}
|
||||
} else {
|
||||
// 新增角色调用API
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 准备API参数
|
||||
const roleData = {
|
||||
name: formData.name,
|
||||
description: formData.description || '',
|
||||
permission_ids: formData.permissionIds || [],
|
||||
};
|
||||
|
||||
console.log('开始创建角色:', roleData);
|
||||
|
||||
// 调用API创建角色
|
||||
await createRole(roleData);
|
||||
|
||||
// 成功后刷新列表
|
||||
await loadRoles();
|
||||
|
||||
toast.success('角色添加成功');
|
||||
setShowForm(false);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('创建角色失败:', error);
|
||||
|
||||
// 处理API返回的错误信息
|
||||
if (error?.data?.message) {
|
||||
toast.error(error.data.message);
|
||||
} else if (error?.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('新增操作失败');
|
||||
}
|
||||
// 新增失败时不关闭表单,不刷新列表
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
@@ -176,17 +360,107 @@ export default function RoleManagementPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('确定要删除该角色吗?')) return;
|
||||
setDeletingRoleId(id);
|
||||
setShowDeleteDialog(true);
|
||||
};
|
||||
|
||||
const updated = roles.filter(role => role.id !== id);
|
||||
setRoles(updated);
|
||||
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
|
||||
toast.success('角色删除成功');
|
||||
// 确认删除角色
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!deletingRoleId) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
console.log('删除角色,使用角色ID:', deletingRoleId);
|
||||
|
||||
// 调用API删除角色
|
||||
await deleteRole(deletingRoleId);
|
||||
|
||||
// 成功后刷新列表
|
||||
await loadRoles();
|
||||
|
||||
toast.success('角色删除成功');
|
||||
setShowDeleteDialog(false);
|
||||
setDeletingRoleId(null);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('删除角色失败:', error);
|
||||
|
||||
// 处理API返回的错误信息
|
||||
if (error?.data?.message) {
|
||||
toast.error(error.data.message);
|
||||
} else if (error?.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('删除操作失败');
|
||||
}
|
||||
// 删除失败时不关闭对话框,不刷新列表
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消删除
|
||||
const handleCancelDelete = () => {
|
||||
setShowDeleteDialog(false);
|
||||
setDeletingRoleId(null);
|
||||
};
|
||||
|
||||
const handleViewDetail = (role: Role) => {
|
||||
setSelectedRole(role);
|
||||
// 先打开弹框,设置初始状态,确保所有必需属性都有默认值
|
||||
const roleWithDefaults: Role = {
|
||||
...role,
|
||||
menuIds: role.menuIds || [],
|
||||
permissionIds: role.permissionIds || [],
|
||||
};
|
||||
setSelectedRole(roleWithDefaults);
|
||||
setShowDetailDialog(true);
|
||||
setDetailLoading(true);
|
||||
|
||||
// 在弹框内部异步加载数据
|
||||
loadRoleDetail(role.id);
|
||||
};
|
||||
|
||||
// 加载角色详情的独立函数
|
||||
const loadRoleDetail = async (roleId: string) => {
|
||||
try {
|
||||
console.log('查看角色详情,使用角色ID:', roleId);
|
||||
|
||||
// 调用API获取角色详情
|
||||
const roleDetail = await getRoleDetail(roleId);
|
||||
|
||||
// 将API返回的数据转换为页面格式
|
||||
const detailedRole: Role = {
|
||||
...selectedRole,
|
||||
name: roleDetail.name,
|
||||
description: roleDetail.description,
|
||||
id: roleDetail.id, // 使用API返回的真实ID
|
||||
code: selectedRole?.code || roleDetail.id, // 保持原有的code
|
||||
tenant_id: roleDetail.tenant_id,
|
||||
createdAt: roleDetail.created_at,
|
||||
updatedAt: roleDetail.updated_at,
|
||||
menuIds: selectedRole?.menuIds || [],
|
||||
permissionIds: selectedRole?.permissionIds || [],
|
||||
};
|
||||
|
||||
setSelectedRole(detailedRole);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('获取角色详情失败:', error);
|
||||
|
||||
// 处理API返回的错误信息
|
||||
if (error?.data?.message) {
|
||||
toast.error(error.data.message);
|
||||
} else if (error?.data?.detail?.original_detail) {
|
||||
toast.error(error.data.detail.original_detail);
|
||||
} else if (error?.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('获取角色详情失败');
|
||||
}
|
||||
} finally {
|
||||
setDetailLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -206,9 +480,9 @@ export default function RoleManagementPage() {
|
||||
|
||||
{/* 角色列表 */}
|
||||
<RoleList
|
||||
roles={filteredRoles}
|
||||
roles={paginatedRoles}
|
||||
loading={loading}
|
||||
pagination={pagination}
|
||||
pagination={clientPagination}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
onViewDetail={handleViewDetail}
|
||||
@@ -219,20 +493,40 @@ export default function RoleManagementPage() {
|
||||
{/* 添加/编辑表单 */}
|
||||
<RoleFormDialog
|
||||
open={showForm}
|
||||
onOpenChange={setShowForm}
|
||||
onOpenChange={handleFormDialogClose}
|
||||
editingRole={editingRole}
|
||||
formData={formData}
|
||||
onFormDataChange={setFormData}
|
||||
onSave={handleSave}
|
||||
editFormLoading={editFormLoading}
|
||||
/>
|
||||
|
||||
{/* 详情对话框 */}
|
||||
<RoleDetailDialog
|
||||
open={showDetailDialog}
|
||||
onOpenChange={setShowDetailDialog}
|
||||
onOpenChange={handleDetailDialogClose}
|
||||
selectedRole={selectedRole}
|
||||
detailLoading={detailLoading}
|
||||
/>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除角色</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
您确定要删除这个角色吗?此操作不可撤销,删除后相关的权限配置也将被清除。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={handleCancelDelete}>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleConfirmDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* 使用说明 */}
|
||||
<RoleManagementInstructions />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user