Files
smart-crop-ui/crop-x/src/app/(app)/central-config/user/employee/page.tsx

581 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import { toast } from 'sonner';
import { EmployeeManagementHeader } from './components/EmployeeManagementHeader';
import { EmployeeManagementStatsCards } from './components/EmployeeManagementStatsCards';
import { EmployeeManagementFilters } from './components/EmployeeManagementFilters';
import { EmployeeList } from './components/EmployeeList';
import { EmployeeFormDialog } from './components/EmployeeFormDialog';
import { EmployeeDetailDialog } from './components/EmployeeDetailDialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { Employee, Role, EmployeeFilters, EmployeeFormData } from './types';
import {
fetchEmployees,
transformEmployeesList,
createEmployee,
updateEmployee,
activateUser,
deactivateUser,
deleteUser,
CreateEmployeeRequest,
UpdateEmployeeRequest,
PaginationState,
EmployeesQueryParams
} from './components/employeeApi';
export default function EmployeeManagementPage() {
const [employees, setEmployees] = useState<Employee[]>([]);
const [roles, setRoles] = useState<Role[]>([]);
const [loading, setLoading] = useState(false);
const [creating, setCreating] = useState(false);
const [updating, setUpdating] = useState(false);
const [toggling, setToggling] = useState<string | null>(null); // 记录正在操作的用户ID
// 确认对话框状态
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [userToDelete, setUserToDelete] = useState<Employee | null>(null);
const [deactivateConfirmOpen, setDeactivateConfirmOpen] = useState(false);
const [userToDeactivate, setUserToDeactivate] = useState<Employee | null>(null);
const [pagination, setPagination] = useState<PaginationState>({
page: 1,
size: 10,
total: 0,
totalPages: 0,
hasNext: false,
hasPrev: false,
});
const [filters, setFilters] = useState<EmployeeFilters>({
searchKeyword: '',
statusFilter: 'all'
});
const [showForm, setShowForm] = useState(false);
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [formKey, setFormKey] = useState(0); // 添加key来强制重新渲染表单
const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null);
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null);
const [formData, setFormData] = useState<EmployeeFormData>({
enterpriseId: 'ent-2',
enterpriseName: '丰收现代农业集团',
status: 'active',
auditStatus: 'pending',
roleIds: [],
idCard: '',
address: '',
});
useEffect(() => {
loadEmployees();
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 loadEmployees = async () => {
setLoading(true);
try {
const queryParams: EmployeesQueryParams = {
page: pagination.page,
size: pagination.size,
sort_order: 'desc'
};
// 如果有搜索关键词,添加到查询参数
if (filters.searchKeyword) {
queryParams.search = filters.searchKeyword;
}
// 如果有状态筛选,添加到查询参数
if (filters.statusFilter !== 'all') {
// 注意API可能不支持直接的状态筛选这里暂时在客户端过滤
}
const response = await fetchEmployees(queryParams);
// 转换数据格式
const transformedEmployees = transformEmployeesList(response.data);
// 应用状态筛选如果API不支持
const filteredEmployees = filters.statusFilter === 'all'
? transformedEmployees
: transformedEmployees.filter(emp => {
const status = emp.isActive ? 'active' : 'frozen';
return status === filters.statusFilter;
});
setEmployees(filteredEmployees);
setPagination({
page: response.page,
size: response.size,
total: response.total,
totalPages: response.total_pages,
hasNext: response.has_next,
hasPrev: response.has_prev,
});
} catch (error) {
console.error('Failed to load employees:', error);
toast.error('加载员工数据失败');
// 如果API失败使用localStorage中的数据
setEmployees([]);
} finally {
setLoading(false);
}
};
// 搜索处理函数
const handleSearch = (searchKeyword: string) => {
setFilters(prev => ({ ...prev, searchKeyword }));
// 重置到第一页
setPagination(prev => ({ ...prev, page: 1 }));
};
// 状态筛选处理函数
const handleStatusFilter = (statusFilter: string) => {
setFilters(prev => ({ ...prev, statusFilter }));
// 重置到第一页
setPagination(prev => ({ ...prev, page: 1 }));
};
// 分页处理函数
const handlePageChange = (page: number) => {
setPagination(prev => ({ ...prev, page }));
};
const handlePageSizeChange = (size: number) => {
setPagination(prev => ({ ...prev, size, page: 1 }));
};
const handleAddEmployee = () => {
setEditingEmployee(null);
clearForm();
setFormKey(prev => prev + 1); // 增加key强制重新渲染
setShowForm(true);
};
const clearForm = () => {
// 先设置一个空的表单对象
const emptyForm = {
enterpriseId: 'ent-2',
enterpriseName: '丰收现代农业集团',
status: 'active' as const,
auditStatus: 'pending' as const,
roleIds: [],
username: '',
name: '',
phone: '',
email: '',
department: '',
position: '',
};
// 强制清空表单
setFormData(emptyForm);
// 使用setTimeout确保状态更新完成
setTimeout(() => {
setFormData({...emptyForm});
}, 0);
};
const handleEdit = (employee: Employee) => {
setEditingEmployee(employee);
setFormData(employee);
setShowForm(true);
};
const handleSave = async () => {
if (!formData.username || !formData.name || !formData.phone) {
toast.error('请填写必填项');
return;
}
// 验证角色选择
if (!formData.roleIds || formData.roleIds.length === 0) {
toast.error('请至少选择一个角色');
return;
}
// 根据角色ID设置角色名称
const selectedRoles = roles.filter(r => formData.roleIds?.includes(r.id));
const roleNames = selectedRoles.map(r => r.name);
if (editingEmployee) {
// 更新 - 调用API
setUpdating(true);
try {
// 构建API请求参数
const updateRequest: UpdateEmployeeRequest = {
email: formData.email || '',
username: formData.username,
full_name: formData.name,
phone: formData.phone,
password: '', // 编辑时不传密码
tenant_id: formData.enterpriseId,
scope: 'tenant',
department_id: formData.departmentId || '',
is_superuser: formData.isSuperuser || false,
};
// 调用API更新用户
const updatedEmployee = await updateEmployee(editingEmployee.id, updateRequest);
// 更新本地列表中的员工数据
const updated = employees.map(emp =>
emp.id === editingEmployee.id
? {
...emp,
...updatedEmployee,
roles: roleNames,
updatedAt: new Date().toISOString(),
}
: emp
);
setEmployees(updated);
toast.success('员工信息更新成功');
setShowForm(false);
clearForm();
// 刷新员工列表数据
await loadEmployees();
} catch (error) {
console.error('更新员工失败:', error);
// 处理错误,显示具体的错误消息
const errorMessage = error instanceof Error ? error.message : '员工信息更新失败';
toast.error(errorMessage);
} finally {
setUpdating(false);
}
} else {
// 新增 - 调用API
setCreating(true);
try {
// 构建API请求参数
const createRequest: CreateEmployeeRequest = {
email: formData.email || '', // 没有邮箱就传空字符串
username: formData.username,
full_name: formData.name,
phone: formData.phone,
password: '', // 传递空字符串给后端
tenant_id: formData.enterpriseId,
scope: 'tenant',
department_id: formData.departmentId || '',
is_superuser: formData.isSuperuser || false,
};
// 调用API创建用户
const newEmployee = await createEmployee(createRequest);
// 将新员工添加到列表
const updated = [newEmployee, ...employees];
setEmployees(updated);
toast.success('员工添加成功');
setShowForm(false);
clearForm();
// 刷新员工列表数据
await loadEmployees();
} catch (error) {
console.error('创建员工失败:', error);
// 处理错误,显示具体的错误消息
const errorMessage = error instanceof Error ? error.message : '员工添加失败';
toast.error(errorMessage);
} finally {
setCreating(false);
}
}
};
const handleDelete = (userId: string) => {
// 防止重复操作
if (toggling) {
toast.warning('操作进行中,请稍候...');
return;
}
// 查找要删除的员工信息
const employeeToDelete = employees.find(emp => emp.id === userId);
if (!employeeToDelete) {
toast.error('未找到要删除的用户');
return;
}
// 设置要删除的用户并显示确认对话框
setUserToDelete(employeeToDelete);
setDeleteConfirmOpen(true);
};
const executeDelete = async (userId: string) => {
setToggling(userId);
try {
// 调用API删除用户
await deleteUser(userId);
// 成功后从本地列表中移除
const updated = employees.filter(emp => emp.id !== userId);
setEmployees(updated);
toast.success('用户删除成功');
// 刷新列表确保数据同步
await loadEmployees();
// 关闭确认对话框
setDeleteConfirmOpen(false);
setUserToDelete(null);
} catch (error) {
console.error('删除用户失败:', error);
// 处理错误,显示具体的错误消息
const errorMessage = error instanceof Error ? error.message : '删除失败,请稍后重试';
toast.error(errorMessage);
// 失败时不关闭确认对话框,用户可以重试
} finally {
setToggling(null);
}
};
const handleToggleStatus = (employee: Employee) => {
if (toggling) {
toast.warning('操作进行中,请稍候...');
return;
}
if (employee.isActive) {
// 当前是激活状态,进行停用操作,需要二次确认
setUserToDeactivate(employee);
setDeactivateConfirmOpen(true);
} else {
// 当前是停用状态,进行激活操作(不需要确认)
executeToggleStatus(employee);
}
};
const executeToggleStatus = async (employee: Employee) => {
setToggling(employee.id);
try {
if (employee.isActive) {
// 当前是激活状态,进行停用操作
await deactivateUser(employee.id);
toast.success('账户已停用');
} else {
// 当前是停用状态,进行激活操作
await activateUser(employee.id);
toast.success('账户已激活');
}
// 成功后刷新列表
await loadEmployees();
// 关闭停用确认对话框
if (deactivateConfirmOpen) {
setDeactivateConfirmOpen(false);
setUserToDeactivate(null);
}
} catch (error) {
console.error('切换用户状态失败:', error);
// 处理错误,显示具体的错误消息
const errorMessage = error instanceof Error ? error.message : '操作失败,请稍后重试';
toast.error(errorMessage);
// 失败时不关闭确认对话框,用户可以重试
} finally {
setToggling(null);
}
};
const handleResetPassword = (employee: Employee) => {
if (!confirm(`确定要重置 ${employee.name} 的密码吗?`)) return;
toast.success('密码已重置为123456');
};
const handleViewDetail = (employee: Employee) => {
setSelectedEmployee(employee);
setShowDetailDialog(true);
};
const handleAudit = (employee: Employee, action: 'approve' | 'reject') => {
if (action === 'approve') {
const updated = employees.map(emp =>
emp.id === employee.id
? {
...emp,
auditStatus: 'approved' as const,
auditTime: new Date().toISOString(),
auditor: '当前用户',
updatedAt: new Date().toISOString(),
}
: emp
);
setEmployees(updated);
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
toast.success('审核通过');
} else {
const reason = prompt('请输入驳回原因:');
if (reason) {
const updated = employees.map(emp =>
emp.id === employee.id
? {
...emp,
auditStatus: 'rejected' as const,
auditReason: reason,
auditTime: new Date().toISOString(),
auditor: '当前用户',
updatedAt: new Date().toISOString(),
}
: emp
);
setEmployees(updated);
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
toast.success('已驳回');
}
}
};
return (
<div className="space-y-6">
<EmployeeManagementHeader
onAddEmployee={handleAddEmployee}
/>
{/* 统计卡片 */}
<EmployeeManagementStatsCards employees={employees} />
{/* 搜索和筛选 */}
<EmployeeManagementFilters
filters={filters}
onSearchChange={handleSearch}
onStatusFilterChange={handleStatusFilter}
/>
{/* 员工列表 */}
<EmployeeList
employees={employees}
loading={loading}
pagination={pagination}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
onViewDetail={handleViewDetail}
onEdit={handleEdit}
onResetPassword={handleResetPassword}
onToggleStatus={handleToggleStatus}
onDelete={handleDelete}
onAudit={handleAudit}
togglingId={toggling}
/>
{/* 添加/编辑表单 */}
<EmployeeFormDialog
key={formKey} // 使用key强制重新渲染清除浏览器缓存
open={showForm}
onOpenChange={setShowForm}
editingEmployee={editingEmployee}
formData={formData}
onFormDataChange={setFormData}
onSave={handleSave}
roles={roles}
creating={creating}
updating={updating}
onClearForm={clearForm}
/>
{/* 详情对话框 */}
<EmployeeDetailDialog
open={showDetailDialog}
onOpenChange={setShowDetailDialog}
selectedEmployee={selectedEmployee}
/>
{/* 删除确认对话框 */}
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
"
{userToDelete?.displayName || userToDelete?.fullName || userToDelete?.username || ''}
"
<br /><br />
<span className="text-red-600 font-semibold">
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={toggling !== null}>
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (userToDelete) {
executeDelete(userToDelete.id);
}
}}
disabled={toggling !== null}
className="bg-red-600 hover:bg-red-700"
>
{toggling ? '删除中...' : '确认删除'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 停用确认对话框 */}
<AlertDialog open={deactivateConfirmOpen} onOpenChange={setDeactivateConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
"
{userToDeactivate?.displayName || userToDeactivate?.fullName || userToDeactivate?.username || ''}
"
<br /><br />
<span className="text-orange-600 font-semibold">
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={toggling !== null}>
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (userToDeactivate) {
executeToggleStatus(userToDeactivate);
}
}}
disabled={toggling !== null}
className="bg-orange-600 hover:bg-orange-700"
>
{toggling ? '停用中...' : '确认停用'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}