diff --git a/crop-x/next-env.d.ts b/crop-x/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/crop-x/next-env.d.ts +++ b/crop-x/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx index 34728f5..d8a090d 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx @@ -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({

编辑员工

- - - - - -

重置密码

-
-
- + - -
- {Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((pageNum) => ( - - ))} -
- - - - {onPageSizeChange && ( - <> - - 条/页 - - )} - - + {!loading && pagination && pagination.total > 0 && ( + onPageChange?.(page)} + onPageSizeChange={(size) => onPageSizeChange?.(size)} + canPreviousPage={pagination.hasPrev} + canNextPage={pagination.hasNext} + pageSizeOptions={[10, 20, 50, 100]} + /> )} ); diff --git a/crop-x/src/app/(app)/central-config/user/role/components/roleApi.ts b/crop-x/src/app/(app)/central-config/user/role/components/roleApi.ts index 376e68d..324ac30 100644 --- a/crop-x/src/app/(app)/central-config/user/role/components/roleApi.ts +++ b/crop-x/src/app/(app)/central-config/user/role/components/roleApi.ts @@ -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(null); const [selectedRole, setSelectedRole] = useState(null); + const [deletingRoleId, setDeletingRoleId] = useState(null); + const [detailLoading, setDetailLoading] = useState(false); + const [editFormLoading, setEditFormLoading] = useState(false); const [formData, setFormData] = useState({ 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() { {/* 角色列表 */} {/* 详情对话框 */} + {/* 删除确认对话框 */} + + + + 确认删除角色 + + 您确定要删除这个角色吗?此操作不可撤销,删除后相关的权限配置也将被清除。 + + + + 取消 + + 确认删除 + + + + + {/* 使用说明 */}