生产管理系统 - 企业管理和用户管理 列表重构

This commit is contained in:
2025-11-06 17:47:14 +08:00
parent 191d218ec4
commit 008fc12db9
8 changed files with 1154 additions and 499 deletions

View File

@@ -2,20 +2,20 @@
* filekorolheader: 企业审核页面 - 企业注册审核管理页面
* 功能:企业审核列表、搜索筛选、审核操作、详情查看
* 路径:/central-config/tenant/enterprise-audit
* 规范遵循crop-x/docs/开发项目规范.md使用useReducer状态管理API集成模块化组件
* 规范遵循crop-x/docs/开发项目规范.md使用useReducer状态管理API集成模块化组件SearchFormPagination重构
*/
'use client';
import { useReducer, useEffect, useMemo, useRef } from 'react';
import { useReducer, useEffect, useMemo, useRef, useCallback } from 'react';
import { toast } from 'sonner';
import { Building2, RefreshCw } from 'lucide-react';
import { Building2, RefreshCw, Eye, Check, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { SearchFormPagination, type SearchFieldConfig, type TableColumnConfig } from '@/components/common/searchFormPagination';
import { fetchTenantsForAudit, auditTenant, transformTenantData, TenantsQueryParams, Enterprise } from './components/enterpriseAuditApi';
import { AuditStatsCards } from './components/AuditStatsCards';
import { AuditSearchAndFilter } from './components/AuditSearchAndFilter';
import { EnterpriseAuditTable } from './components/EnterpriseAuditTable';
import { EnterpriseDetailDialog } from './components/EnterpriseDetailDialog';
import { AuditPagination } from './components/AuditPagination';
// 审核状态管理
interface AuditState {
@@ -119,21 +119,133 @@ export default function EnterpriseAuditPage() {
const [state, dispatch] = useReducer(auditReducer, initialState);
const isFirstLoad = useRef(true);
// 加载企业数据
const loadEnterprises = async (resetPage = false) => {
// 搜索字段配置
const searchFields: SearchFieldConfig[] = [
{
key: 'search',
label: '搜索',
type: 'text',
placeholder: '搜索企业名称、编码...',
},
{
key: 'audit_status',
label: '审核状态',
type: 'select',
defaultValue: 'all',
options: [
{ value: 'all', label: '全部状态' },
{ value: '待审核', label: '待审核' },
{ value: '已通过', label: '已通过' },
{ value: '已驳回', label: '已驳回' },
],
},
];
// 表格列配置
const columns: TableColumnConfig[] = [
{
key: 'name',
label: '企业名称',
sortable: false, // 禁用排序
render: (value: string) => (
<div className="font-medium text-foreground">{value}</div>
),
},
{
key: 'code',
label: '企业编码',
sortable: false, // 禁用排序
render: (value: string) => (
<div className="font-mono text-sm text-muted-foreground">{value}</div>
),
},
{
key: 'auditStatus',
label: '审核状态',
sortable: false, // 禁用排序
render: (value: string) => {
const statusConfig = {
'待审核': { label: '待审核', variant: 'default' as const, className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' },
'已通过': { label: '已通过', variant: 'default' as const, className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' },
'已驳回': { label: '已驳回', variant: 'default' as const, className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' },
};
const config = statusConfig[value as keyof typeof statusConfig] || statusConfig['待审核'];
return (
<Badge className={`font-light ${config.className}`}>
{config.label}
</Badge>
);
},
},
{
key: 'contactPerson',
label: '联系人',
sortable: false, // 禁用排序
},
{
key: 'contactPhone',
label: '联系电话',
sortable: false, // 禁用排序
render: (value: string) => (
<div className="font-mono text-sm">{value || '-'}</div>
),
},
{
key: 'createdAt',
label: '创建时间',
sortable: false, // 禁用排序
render: (value: string) => (
<div className="text-sm text-muted-foreground">
{value ? new Date(value).toLocaleDateString('zh-CN') : '-'}
</div>
),
},
{
key: 'actions',
label: '操作',
sortable: false, // 操作列不能排序
render: (_: any, row: Enterprise) => (
<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>
</div>
),
},
];
// 加载企业数据 - 移除依赖项,通过参数传递状态
const loadEnterprises = useCallback(async (params?: {
filters?: Record<string, string>;
pagination?: { page: number; size: number };
sort?: { sortBy?: string; sortOrder: 'asc' | 'desc' };
resetPage?: boolean;
}) => {
try {
dispatch({ type: 'SET_LOADING', payload: true });
const params: TenantsQueryParams = {
search: state.filters.search || undefined,
audit_status: state.filters.audit_status === 'all' ? undefined : state.filters.audit_status,
page: resetPage ? 1 : state.pagination.page,
size: state.pagination.size,
order_by: state.sortBy,
sort_order: state.sortOrder,
const finalParams: TenantsQueryParams = {
search: (params?.filters?.search ?? state.filters.search) || undefined,
audit_status: params?.filters?.audit_status ?? state.filters.audit_status,
page: params?.resetPage ? 1 : (params?.pagination?.page || state.pagination.page),
size: params?.pagination?.size || state.pagination.size,
order_by: params?.sort?.sortBy,
sort_order: params?.sort?.sortOrder,
};
const response = await fetchTenantsForAudit(params);
// 处理audit_status如果为'all'则不传该参数
if (finalParams.audit_status === 'all') {
finalParams.audit_status = undefined;
}
const response = await fetchTenantsForAudit(finalParams);
const transformedData = response.data.map(transformTenantData);
dispatch({
@@ -156,56 +268,48 @@ export default function EnterpriseAuditPage() {
dispatch({ type: 'SET_ERROR', payload: errorMessage });
toast.error(errorMessage);
}
};
}, []); // 移除所有依赖,使用参数传递状态变化
// 首次加载数据
useEffect(() => {
// 首次加载数据 - 使用事件驱动避免useEffect
const initializeData = useCallback(() => {
if (isFirstLoad.current) {
isFirstLoad.current = false;
loadEnterprises(true);
loadEnterprises({ resetPage: true });
}
}, []);
}, [loadEnterprises]);
// 监听筛选和排序变化(排除首次加载)
// 页面加载时初始化数据
useEffect(() => {
if (!isFirstLoad.current) {
const timer = setTimeout(() => {
loadEnterprises(true);
}, 300);
return () => clearTimeout(timer);
}
}, [state.filters.search, state.filters.audit_status, state.sortBy, state.sortOrder]);
// 分页加载
useEffect(() => {
if (!isFirstLoad.current && state.pagination.page > 1) {
loadEnterprises(false);
}
}, [state.pagination.page]);
initializeData();
}, []); // 只在组件挂载时执行一次
// 计算统计数据
const stats = useMemo(() => ({
total: state.pagination.total,
pending: state.enterprises.filter(e => e.auditStatus === 'pending').length,
approved: state.enterprises.filter(e => e.auditStatus === 'approved').length,
rejected: state.enterprises.filter(e => e.auditStatus === 'rejected').length,
pending: state.enterprises.filter(e => e.auditStatus === '待审核').length,
approved: state.enterprises.filter(e => e.auditStatus === '已通过').length,
rejected: state.enterprises.filter(e => e.auditStatus === '已驳回').length,
}), [state.enterprises, state.pagination.total]);
// 事件处理器
const handleSearch = (value: string) => {
dispatch({ type: 'SET_FILTERS', payload: { search: value } });
};
const handleSearch = useCallback((filters: Record<string, string>) => {
dispatch({ type: 'SET_FILTERS', payload: filters });
loadEnterprises({
filters,
pagination: { page: 1, size: state.pagination.size }
});
}, [loadEnterprises, state.pagination.size]);
const handleAuditStatusFilter = (value: string) => {
dispatch({ type: 'SET_FILTERS', payload: { audit_status: value === 'all' ? 'all' : value } });
};
const handleSort = useCallback((sortBy: string, sortOrder: 'asc' | 'desc') => {
dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder } });
loadEnterprises({
filters: state.filters,
sort: { sortBy, sortOrder },
resetPage: true
});
}, [loadEnterprises, state.filters]);
const handleSort = (sortBy?: string) => {
const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc';
dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } });
};
const handlePageChange = (page: number) => {
const handlePageChange = useCallback((page: number) => {
// 边界检查,确保页码在有效范围内
if (page < 1) {
page = 1;
@@ -213,13 +317,25 @@ export default function EnterpriseAuditPage() {
page = state.pagination.totalPages;
}
dispatch({ type: 'SET_PAGINATION', payload: { page } });
};
loadEnterprises({
filters: state.filters,
pagination: { page, size: state.pagination.size }
});
}, [loadEnterprises, state.filters, state.pagination.size, state.pagination.totalPages]);
const handleRefresh = () => {
const handleSizeChange = useCallback((size: number) => {
dispatch({ type: 'SET_PAGINATION', payload: { size, page: 1 } });
loadEnterprises({
filters: state.filters,
pagination: { page: 1, size }
});
}, [loadEnterprises, state.filters]);
const handleRefresh = useCallback(() => {
dispatch({ type: 'REFRESH_DATA' });
loadEnterprises(true);
loadEnterprises({ resetPage: true });
toast.success('数据已刷新');
};
}, [loadEnterprises]);
const handleViewDetail = (enterprise: Enterprise) => {
dispatch({ type: 'SET_SELECTED_ENTERPRISE', payload: enterprise });
@@ -257,10 +373,8 @@ export default function EnterpriseAuditPage() {
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false });
toast.success('审核通过');
// 1秒后刷新列表
setTimeout(() => {
loadEnterprises(true);
}, 1000);
// 立即刷新列表,无需延迟
loadEnterprises({ resetPage: true });
} catch (error) {
console.error('Approve failed:', error);
const errorMessage = error instanceof Error ? error.message : '审核通过失败';
@@ -300,10 +414,8 @@ export default function EnterpriseAuditPage() {
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false });
toast.success('已驳回');
// 1秒后刷新列表
setTimeout(() => {
loadEnterprises(true);
}, 1000);
// 立即刷新列表,无需延迟
loadEnterprises({ resetPage: true });
} catch (error) {
console.error('Reject failed:', error);
const errorMessage = error instanceof Error ? error.message : '审核驳回失败';
@@ -321,45 +433,47 @@ export default function EnterpriseAuditPage() {
<p className="text-muted-foreground"></p>
</div>
{/* 统计卡片 */}
{/* 统计卡片 - 保留原有功能 */}
<AuditStatsCards
enterprises={state.enterprises}
loading={state.loading}
/>
{/* 搜索和筛选 */}
<AuditSearchAndFilter
searchKeyword={state.filters.search}
onSearchChange={(value) => dispatch({ type: 'SET_FILTERS', payload: { search: value } })}
statusFilter={state.filters.audit_status}
onStatusFilterChange={(value) => dispatch({ type: 'SET_FILTERS', payload: { audit_status: value } })}
onRefresh={handleRefresh}
{/* 搜索、表格和分页 - 使用重构后的组件 */}
<SearchFormPagination
formTitle="企业列表"
formRightContent={
<Button variant="outline" onClick={handleRefresh} disabled={state.loading}>
<RefreshCw className={`w-4 h-4 mr-2 ${state.loading ? 'animate-spin' : ''}`} />
</Button>
}
searchFields={searchFields}
columns={columns}
data={state.enterprises}
loading={state.loading}
error={state.error}
pagination={state.pagination}
sortBy={state.sortBy}
sortOrder={state.sortOrder}
onPageChange={handlePageChange}
onSizeChange={handleSizeChange}
onSearch={handleSearch}
onSort={handleSort}
emptyIcon={<Building2 className="w-12 h-12" />}
emptyText="暂无企业审核数据"
showSizeSelector={true}
showPageInfo={true}
sizeOptions={[10, 20, 50, 100]}
/>
{/* 企业列表 */}
<EnterpriseAuditTable
enterprises={state.enterprises}
loading={state.loading}
onViewDetails={handleViewDetail}
/>
{/* 分页 */}
{state.pagination.total > 0 && (
<AuditPagination
pagination={state.pagination}
onPageChange={handlePageChange}
loading={state.loading}
/>
)}
{/* 企业详情对话框 */}
{/* 企业详情对话框 - 保留原有功能 */}
<EnterpriseDetailDialog
open={state.showDetailDialog}
onOpenChange={(open) => dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: open })}
enterprise={state.selectedEnterprise}
auditReason={state.auditReason}
onAuditReasonChange={(reason) => dispatch({ type: 'SET_AUDIT_REASON', payload: reason })}
onAuditReasonChange={handleAuditReasonChange}
onApprove={handleApprove}
onReject={handleReject}
loading={state.actionLoading}