生茶难管理系统 员工管理和角色管理 表单组件重构
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
/**
|
||||
* filekorolheader: 员工管理页面 - 企业员工账户管理页面
|
||||
* 功能:员工列表查询、添加编辑、状态管理、角色分配
|
||||
* 路径:/central-config/user/employee
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用事件驱动模式,SearchFormPagination重构
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus, Eye, Edit, Trash2, Power, PowerOff } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
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 { SearchFormPagination, type SearchFieldConfig, type TableColumnConfig } from '@/components/common/searchFormPagination';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -18,9 +26,8 @@ import {
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Employee, Role, EmployeeFilters, EmployeeFormData } from './types';
|
||||
import { Employee, Role, EmployeeFormData } from './types';
|
||||
import {
|
||||
fetchEmployees,
|
||||
transformEmployeesList,
|
||||
@@ -46,6 +53,7 @@ export default function EmployeeManagementPage() {
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [toggling, setToggling] = useState<string | null>(null); // 记录正在操作的用户ID
|
||||
const isFirstLoad = useRef(true);
|
||||
|
||||
// 确认对话框状态
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
@@ -60,9 +68,9 @@ export default function EmployeeManagementPage() {
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
});
|
||||
const [filters, setFilters] = useState<EmployeeFilters>({
|
||||
searchKeyword: '',
|
||||
statusFilter: 'all'
|
||||
const [searchFilters, setSearchFilters] = useState<Record<string, string>>({
|
||||
search: '',
|
||||
status: 'all'
|
||||
});
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
||||
@@ -79,60 +87,198 @@ export default function EmployeeManagementPage() {
|
||||
address: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadEmployees();
|
||||
loadRoles();
|
||||
}, [pagination.page, pagination.size,filters.searchKeyword, filters.statusFilter]);
|
||||
// 搜索字段配置
|
||||
const searchFields: SearchFieldConfig[] = [
|
||||
{
|
||||
key: 'search',
|
||||
label: '搜索',
|
||||
type: 'text',
|
||||
placeholder: '搜索用户名、姓名、手机号...',
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: '账户状态',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
options: [
|
||||
{ value: 'all', label: '全部状态' },
|
||||
{ value: 'active', label: '正常' },
|
||||
{ value: 'frozen', label: '停用' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const loadRoles = async () => {
|
||||
// 表格列配置
|
||||
const columns: TableColumnConfig[] = [
|
||||
{
|
||||
key: 'username',
|
||||
label: '用户名',
|
||||
render: (value: string) => (
|
||||
<div className="font-medium text-foreground">{value}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'fullName',
|
||||
label: '姓名',
|
||||
render: (value: string) => (
|
||||
<div className="text-foreground">{value || '-'}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
label: '手机号',
|
||||
render: (value: string) => (
|
||||
<div className="font-mono text-sm">{value || '-'}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'roles',
|
||||
label: '角色',
|
||||
render: (value: string[]) => (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{value && value.length > 0 ? (
|
||||
value.map((role, index) => (
|
||||
<Badge key={index} variant="secondary" className="font-light">
|
||||
{role}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'department',
|
||||
label: '部门',
|
||||
render: (value: string) => (
|
||||
<div className="text-sm text-muted-foreground">{value || '-'}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'isActive',
|
||||
label: '状态',
|
||||
render: (value: boolean) => (
|
||||
<Badge className={`font-light ${
|
||||
value
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||||
}`}>
|
||||
{value ? '正常' : '停用'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: '创建时间',
|
||||
render: (value: string) => (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{value ? new Date(value).toLocaleDateString('zh-CN') : '-'}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: '操作',
|
||||
render: (_: any, row: Employee) => (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleViewDetail(row)}
|
||||
className="h-8 px-2"
|
||||
>
|
||||
<Eye className="w-3 h-3 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleEdit(row)}
|
||||
className="h-8 px-2"
|
||||
>
|
||||
<Edit className="w-3 h-3 mr-1" />
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleToggleStatus(row)}
|
||||
disabled={toggling === row.id}
|
||||
className={`h-8 px-2 ${
|
||||
row.isActive
|
||||
? 'text-orange-600 border-orange-300 hover:bg-orange-50'
|
||||
: 'text-green-600 border-green-300 hover:bg-green-50'
|
||||
}`}
|
||||
>
|
||||
{row.isActive ? (
|
||||
<PowerOff className="w-3 h-3 mr-1" />
|
||||
) : (
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
)}
|
||||
{row.isActive ? '停用' : '启用'}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDelete(row.id)}
|
||||
disabled={toggling === row.id}
|
||||
className="h-8 px-2 text-red-600 border-red-300 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="w-3 h-3 mr-1" />
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// 加载角色数据 - 事件驱动
|
||||
const loadRoles = useCallback(async () => {
|
||||
try {
|
||||
// 调用角色API获取角色数据
|
||||
const response = await fetchRoles({
|
||||
page: 1,
|
||||
size: 100, // 获取所有角色
|
||||
size: 100,
|
||||
sort_order: 'desc'
|
||||
});
|
||||
|
||||
// 转换数据格式
|
||||
const transformedRoles = transformRolesList(response.data);
|
||||
setRoles(transformedRoles);
|
||||
} catch (error) {
|
||||
console.error('Failed to load roles:', error);
|
||||
// API失败时设置为空数组
|
||||
setRoles([]);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadEmployees = async () => {
|
||||
setLoading(true);
|
||||
// 加载员工数据 - 事件驱动,移除依赖项
|
||||
const loadEmployees = useCallback(async (params?: {
|
||||
filters?: Record<string, string>;
|
||||
pagination?: { page: number; size: number };
|
||||
resetPage?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const queryParams: EmployeesQueryParams = {
|
||||
page: pagination.page,
|
||||
size: pagination.size,
|
||||
page: params?.resetPage ? 1 : (params?.pagination?.page || pagination.page),
|
||||
size: params?.pagination?.size || pagination.size,
|
||||
sort_order: 'desc'
|
||||
};
|
||||
|
||||
// 如果有搜索关键词,添加到查询参数
|
||||
if (filters.searchKeyword) {
|
||||
queryParams.search = filters.searchKeyword;
|
||||
}
|
||||
|
||||
// 如果有状态筛选,添加到查询参数
|
||||
if (filters.statusFilter !== 'all') {
|
||||
// 注意:API可能不支持直接的状态筛选,这里暂时在客户端过滤
|
||||
// 搜索关键词
|
||||
const searchKeyword = params?.filters?.search ?? searchFilters.search;
|
||||
if (searchKeyword) {
|
||||
queryParams.search = searchKeyword;
|
||||
}
|
||||
|
||||
const response = await fetchEmployees(queryParams);
|
||||
|
||||
// 转换数据格式
|
||||
const transformedEmployees = transformEmployeesList(response.data);
|
||||
|
||||
// 应用状态筛选(如果API不支持)
|
||||
const filteredEmployees = filters.statusFilter === 'all'
|
||||
// 状态筛选(客户端过滤)
|
||||
const statusFilter = params?.filters?.status ?? searchFilters.status;
|
||||
const filteredEmployees = statusFilter === 'all'
|
||||
? transformedEmployees
|
||||
: transformedEmployees.filter(emp => {
|
||||
const status = emp.isActive ? 'active' : 'frozen';
|
||||
return status === filters.statusFilter;
|
||||
return status === statusFilter;
|
||||
});
|
||||
|
||||
setEmployees(filteredEmployees);
|
||||
@@ -147,35 +293,45 @@ export default function EmployeeManagementPage() {
|
||||
} catch (error) {
|
||||
console.error('Failed to load employees:', error);
|
||||
toast.error('加载员工数据失败');
|
||||
// 如果API失败,使用localStorage中的数据
|
||||
setEmployees([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [pagination.page, pagination.size, searchFilters]);
|
||||
|
||||
// 搜索处理函数
|
||||
const handleSearch = (searchKeyword: string) => {
|
||||
setFilters(prev => ({ ...prev, searchKeyword }));
|
||||
// 重置到第一页
|
||||
setPagination(prev => ({ ...prev, page: 1 }));
|
||||
};
|
||||
// 初始化数据 - 只在组件挂载时执行一次
|
||||
useEffect(() => {
|
||||
if (isFirstLoad.current) {
|
||||
isFirstLoad.current = false;
|
||||
loadRoles();
|
||||
loadEmployees({ resetPage: true });
|
||||
}
|
||||
}, [loadRoles, loadEmployees]);
|
||||
|
||||
// 状态筛选处理函数
|
||||
const handleStatusFilter = (statusFilter: string) => {
|
||||
setFilters(prev => ({ ...prev, statusFilter }));
|
||||
// 重置到第一页
|
||||
setPagination(prev => ({ ...prev, page: 1 }));
|
||||
};
|
||||
// 事件处理器 - 事件驱动模式
|
||||
const handleSearch = useCallback((filters: Record<string, string>) => {
|
||||
setSearchFilters(filters);
|
||||
loadEmployees({
|
||||
filters,
|
||||
pagination: { page: 1, size: pagination.size }
|
||||
});
|
||||
}, [loadEmployees, pagination.size]);
|
||||
|
||||
// 分页处理函数
|
||||
const handlePageChange = (page: number) => {
|
||||
const handlePageChange = useCallback((page: number) => {
|
||||
setPagination(prev => ({ ...prev, page }));
|
||||
};
|
||||
loadEmployees({
|
||||
filters: searchFilters,
|
||||
pagination: { page, size: pagination.size }
|
||||
});
|
||||
}, [loadEmployees, searchFilters, pagination.size]);
|
||||
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
const handleSizeChange = useCallback((size: number) => {
|
||||
setPagination(prev => ({ ...prev, size, page: 1 }));
|
||||
};
|
||||
loadEmployees({
|
||||
filters: searchFilters,
|
||||
pagination: { page: 1, size }
|
||||
});
|
||||
}, [loadEmployees, searchFilters]);
|
||||
|
||||
const handleAddEmployee = () => {
|
||||
setEditingEmployee(null);
|
||||
@@ -185,7 +341,7 @@ export default function EmployeeManagementPage() {
|
||||
};
|
||||
|
||||
const clearForm = () => {
|
||||
// 先设置一个空的表单对象
|
||||
// 直接设置空表单,无需setTimeout
|
||||
const emptyForm = {
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
@@ -199,14 +355,7 @@ export default function EmployeeManagementPage() {
|
||||
department: '',
|
||||
position: '',
|
||||
};
|
||||
|
||||
// 强制清空表单
|
||||
setFormData(emptyForm);
|
||||
|
||||
// 使用setTimeout确保状态更新完成
|
||||
setTimeout(() => {
|
||||
setFormData({...emptyForm});
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const handleEdit = (employee: Employee) => {
|
||||
@@ -268,8 +417,8 @@ export default function EmployeeManagementPage() {
|
||||
setShowForm(false);
|
||||
clearForm();
|
||||
|
||||
// 刷新员工列表数据
|
||||
await loadEmployees();
|
||||
// 立即刷新员工列表数据,无需延迟
|
||||
loadEmployees({ resetPage: true });
|
||||
} catch (error) {
|
||||
console.error('更新员工失败:', error);
|
||||
|
||||
@@ -307,8 +456,8 @@ export default function EmployeeManagementPage() {
|
||||
setShowForm(false);
|
||||
clearForm();
|
||||
|
||||
// 刷新员工列表数据
|
||||
await loadEmployees();
|
||||
// 立即刷新员工列表数据,无需延迟
|
||||
loadEmployees({ resetPage: true });
|
||||
} catch (error) {
|
||||
console.error('创建员工失败:', error);
|
||||
|
||||
@@ -353,8 +502,8 @@ export default function EmployeeManagementPage() {
|
||||
|
||||
toast.success('用户删除成功');
|
||||
|
||||
// 刷新列表确保数据同步
|
||||
await loadEmployees();
|
||||
// 立即刷新列表确保数据同步
|
||||
loadEmployees({ resetPage: true });
|
||||
|
||||
// 关闭确认对话框
|
||||
setDeleteConfirmOpen(false);
|
||||
@@ -402,8 +551,8 @@ export default function EmployeeManagementPage() {
|
||||
toast.success('账户已激活');
|
||||
}
|
||||
|
||||
// 成功后刷新列表
|
||||
await loadEmployees();
|
||||
// 成功后立即刷新列表
|
||||
loadEmployees({ resetPage: true });
|
||||
|
||||
// 关闭停用确认对话框
|
||||
if (deactivateConfirmOpen) {
|
||||
@@ -471,32 +620,35 @@ export default function EmployeeManagementPage() {
|
||||
onAddEmployee={handleAddEmployee}
|
||||
/>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
{/* 统计卡片 - 保留原有功能 */}
|
||||
<EmployeeManagementStatsCards employees={employees} />
|
||||
|
||||
{/* 搜索和筛选 */}
|
||||
<EmployeeManagementFilters
|
||||
filters={filters}
|
||||
onSearchChange={handleSearch}
|
||||
onStatusFilterChange={handleStatusFilter}
|
||||
/>
|
||||
|
||||
{/* 员工列表 */}
|
||||
<EmployeeList
|
||||
employees={employees}
|
||||
{/* 搜索、表格和分页 - 使用重构后的组件 */}
|
||||
<SearchFormPagination
|
||||
formTitle="员工列表"
|
||||
formRightContent={
|
||||
<Button onClick={handleAddEmployee} disabled={loading}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加员工
|
||||
</Button>
|
||||
}
|
||||
searchFields={searchFields}
|
||||
columns={columns}
|
||||
data={employees}
|
||||
loading={loading}
|
||||
error={null}
|
||||
pagination={pagination}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
onViewDetail={handleViewDetail}
|
||||
onEdit={handleEdit}
|
||||
onToggleStatus={handleToggleStatus}
|
||||
onDelete={handleDelete}
|
||||
onAudit={handleAudit}
|
||||
togglingId={toggling}
|
||||
onSizeChange={handleSizeChange}
|
||||
onSearch={handleSearch}
|
||||
emptyIcon={<div className="w-12 h-12 mx-auto mb-4 opacity-20" />}
|
||||
emptyText="暂无员工数据"
|
||||
showSizeSelector={true}
|
||||
showPageInfo={true}
|
||||
sizeOptions={[10, 20, 50, 100]}
|
||||
/>
|
||||
|
||||
{/* 添加/编辑表单 */}
|
||||
{/* 添加/编辑表单 - 保留原有功能 */}
|
||||
<EmployeeFormDialog
|
||||
key={formKey} // 使用key强制重新渲染,清除浏览器缓存
|
||||
open={showForm}
|
||||
@@ -511,14 +663,14 @@ export default function EmployeeManagementPage() {
|
||||
onClearForm={clearForm}
|
||||
/>
|
||||
|
||||
{/* 详情对话框 */}
|
||||
{/* 详情对话框 - 保留原有功能 */}
|
||||
<EmployeeDetailDialog
|
||||
open={showDetailDialog}
|
||||
onOpenChange={setShowDetailDialog}
|
||||
selectedEmployee={selectedEmployee}
|
||||
/>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
{/* 删除确认对话框 - 保留原有功能 */}
|
||||
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
@@ -552,7 +704,7 @@ export default function EmployeeManagementPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* 停用确认对话框 */}
|
||||
{/* 停用确认对话框 - 保留原有功能 */}
|
||||
<AlertDialog open={deactivateConfirmOpen} onOpenChange={setDeactivateConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user