356 lines
11 KiB
TypeScript
356 lines
11 KiB
TypeScript
/**
|
||
* filekorolheader: 企业审核页面 - 企业注册审核管理页面
|
||
* 功能:企业审核列表、搜索筛选、审核操作、详情查看
|
||
* 路径:/central-config/tenant/enterprise-audit
|
||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,API集成,模块化组件
|
||
*/
|
||
'use client';
|
||
|
||
import { useReducer, useEffect, useMemo } from 'react';
|
||
import { toast } from 'sonner';
|
||
import { Building2, RefreshCw } from 'lucide-react';
|
||
|
||
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 {
|
||
enterprises: Enterprise[];
|
||
loading: boolean;
|
||
error: string | null;
|
||
pagination: {
|
||
page: number;
|
||
size: number;
|
||
total: number;
|
||
totalPages: number;
|
||
hasNext: boolean;
|
||
hasPrev: boolean;
|
||
};
|
||
filters: {
|
||
search: string;
|
||
audit_status: string;
|
||
};
|
||
sortBy?: string;
|
||
sortOrder: 'asc' | 'desc';
|
||
selectedEnterprise: Enterprise | null;
|
||
showDetailDialog: boolean;
|
||
auditReason: string;
|
||
actionLoading: boolean;
|
||
}
|
||
|
||
type AuditAction =
|
||
| { type: 'SET_ENTERPRISES'; payload: { data: Enterprise[]; pagination: AuditState['pagination'] } }
|
||
| { type: 'SET_LOADING'; payload: boolean }
|
||
| { type: 'SET_ERROR'; payload: string | null }
|
||
| { type: 'SET_FILTERS'; payload: Partial<AuditState['filters']> }
|
||
| { type: 'SET_SORT'; payload: { sortBy?: string; sortOrder: 'asc' | 'desc' } }
|
||
| { type: 'SET_PAGINATION'; payload: Partial<AuditState['pagination']> }
|
||
| { type: 'SET_SELECTED_ENTERPRISE'; payload: Enterprise | null }
|
||
| { type: 'TOGGLE_DETAIL_DIALOG'; payload: boolean }
|
||
| { type: 'SET_AUDIT_REASON'; payload: string }
|
||
| { type: 'SET_ACTION_LOADING'; payload: boolean }
|
||
| { type: 'REFRESH_DATA' };
|
||
|
||
const auditReducer = (state: AuditState, action: AuditAction): AuditState => {
|
||
switch (action.type) {
|
||
case 'SET_ENTERPRISES':
|
||
return {
|
||
...state,
|
||
enterprises: action.payload.data,
|
||
pagination: action.payload.pagination,
|
||
loading: false,
|
||
error: null,
|
||
};
|
||
case 'SET_LOADING':
|
||
return { ...state, loading: action.payload };
|
||
case 'SET_ERROR':
|
||
return { ...state, error: action.payload, loading: false };
|
||
case 'SET_FILTERS':
|
||
return { ...state, filters: { ...state.filters, ...action.payload } };
|
||
case 'SET_SORT':
|
||
return { ...state, sortBy: action.payload.sortBy, sortOrder: action.payload.sortOrder };
|
||
case 'SET_PAGINATION':
|
||
return { ...state, pagination: { ...state.pagination, ...action.payload } };
|
||
case 'SET_SELECTED_ENTERPRISE':
|
||
return { ...state, selectedEnterprise: action.payload };
|
||
case 'TOGGLE_DETAIL_DIALOG':
|
||
return { ...state, showDetailDialog: !state.showDetailDialog };
|
||
case 'SET_AUDIT_REASON':
|
||
return { ...state, auditReason: action.payload };
|
||
case 'SET_ACTION_LOADING':
|
||
return { ...state, actionLoading: action.payload };
|
||
case 'REFRESH_DATA':
|
||
return { ...state, error: null };
|
||
default:
|
||
return state;
|
||
}
|
||
};
|
||
|
||
const initialState: AuditState = {
|
||
enterprises: [],
|
||
loading: false,
|
||
error: null,
|
||
pagination: {
|
||
page: 1,
|
||
size: 10,
|
||
total: 0,
|
||
totalPages: 0,
|
||
hasNext: false,
|
||
hasPrev: false,
|
||
},
|
||
filters: {
|
||
search: '',
|
||
audit_status: 'all',
|
||
},
|
||
sortBy: 'created_at',
|
||
sortOrder: 'desc',
|
||
selectedEnterprise: null,
|
||
showDetailDialog: false,
|
||
auditReason: '',
|
||
actionLoading: false,
|
||
};
|
||
|
||
|
||
export default function EnterpriseAuditPage() {
|
||
const [state, dispatch] = useReducer(auditReducer, initialState);
|
||
|
||
// 加载企业数据
|
||
const loadEnterprises = async (resetPage = false) => {
|
||
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 response = await fetchTenantsForAudit(params);
|
||
const transformedData = response.data.map(transformTenantData);
|
||
|
||
dispatch({
|
||
type: 'SET_ENTERPRISES',
|
||
payload: {
|
||
data: transformedData,
|
||
pagination: {
|
||
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 enterprises for audit:', error);
|
||
const errorMessage = error instanceof Error ? error.message : '加载企业审核数据失败';
|
||
dispatch({ type: 'SET_ERROR', payload: errorMessage });
|
||
toast.error(errorMessage);
|
||
}
|
||
};
|
||
|
||
// 初始加载
|
||
useEffect(() => {
|
||
loadEnterprises(true);
|
||
}, [state.filters.search, state.filters.audit_status, state.sortBy, state.sortOrder]);
|
||
|
||
// 分页加载
|
||
useEffect(() => {
|
||
if (state.pagination.page > 1) {
|
||
loadEnterprises(false);
|
||
}
|
||
}, [state.pagination.page]);
|
||
|
||
// 计算统计数据
|
||
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,
|
||
}), [state.enterprises, state.pagination.total]);
|
||
|
||
// 事件处理器
|
||
const handleSearch = (value: string) => {
|
||
dispatch({ type: 'SET_FILTERS', payload: { search: value } });
|
||
};
|
||
|
||
const handleAuditStatusFilter = (value: string) => {
|
||
dispatch({ type: 'SET_FILTERS', payload: { audit_status: value === 'all' ? 'all' : value } });
|
||
};
|
||
|
||
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) => {
|
||
// 边界检查,确保页码在有效范围内
|
||
if (page < 1) {
|
||
page = 1;
|
||
} else if (page > state.pagination.totalPages && state.pagination.totalPages > 0) {
|
||
page = state.pagination.totalPages;
|
||
}
|
||
dispatch({ type: 'SET_PAGINATION', payload: { page } });
|
||
};
|
||
|
||
const handleRefresh = () => {
|
||
dispatch({ type: 'REFRESH_DATA' });
|
||
loadEnterprises(true);
|
||
toast.success('数据已刷新');
|
||
};
|
||
|
||
const handleViewDetail = (enterprise: Enterprise) => {
|
||
dispatch({ type: 'SET_SELECTED_ENTERPRISE', payload: enterprise });
|
||
dispatch({ type: 'SET_AUDIT_REASON', payload: '' });
|
||
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: true });
|
||
};
|
||
|
||
const handleAuditReasonChange = (value: string) => {
|
||
dispatch({ type: 'SET_AUDIT_REASON', payload: value });
|
||
};
|
||
|
||
const handleApprove = async () => {
|
||
if (!state.selectedEnterprise) return;
|
||
|
||
try {
|
||
dispatch({ type: 'SET_ACTION_LOADING', payload: true });
|
||
|
||
const updatedTenant = await auditTenant(state.selectedEnterprise.id, {
|
||
audit_status: '已通过',
|
||
audit_comment: state.auditReason || '审核通过',
|
||
});
|
||
|
||
// 更新本地状态
|
||
const updatedEnterprise = transformTenantData(updatedTenant);
|
||
dispatch({
|
||
type: 'SET_ENTERPRISES',
|
||
payload: {
|
||
data: state.enterprises.map(ent =>
|
||
ent.id === state.selectedEnterprise?.id ? updatedEnterprise : ent
|
||
),
|
||
pagination: state.pagination
|
||
}
|
||
});
|
||
|
||
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false });
|
||
toast.success('审核通过');
|
||
|
||
// 1秒后刷新列表
|
||
setTimeout(() => {
|
||
loadEnterprises(true);
|
||
}, 1000);
|
||
} catch (error) {
|
||
console.error('Approve failed:', error);
|
||
const errorMessage = error instanceof Error ? error.message : '审核通过失败';
|
||
toast.error(errorMessage);
|
||
} finally {
|
||
dispatch({ type: 'SET_ACTION_LOADING', payload: false });
|
||
}
|
||
};
|
||
|
||
const handleReject = async () => {
|
||
if (!state.selectedEnterprise) return;
|
||
if (!state.auditReason.trim()) {
|
||
toast.error('请填写驳回原因');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
dispatch({ type: 'SET_ACTION_LOADING', payload: true });
|
||
|
||
const updatedTenant = await auditTenant(state.selectedEnterprise.id, {
|
||
audit_status: '已驳回',
|
||
audit_comment: state.auditReason,
|
||
});
|
||
|
||
// 更新本地状态
|
||
const updatedEnterprise = transformTenantData(updatedTenant);
|
||
dispatch({
|
||
type: 'SET_ENTERPRISES',
|
||
payload: {
|
||
data: state.enterprises.map(ent =>
|
||
ent.id === state.selectedEnterprise?.id ? updatedEnterprise : ent
|
||
),
|
||
pagination: state.pagination
|
||
}
|
||
});
|
||
|
||
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: false });
|
||
toast.success('已驳回');
|
||
|
||
// 1秒后刷新列表
|
||
setTimeout(() => {
|
||
loadEnterprises(true);
|
||
}, 1000);
|
||
} catch (error) {
|
||
console.error('Reject failed:', error);
|
||
const errorMessage = error instanceof Error ? error.message : '审核驳回失败';
|
||
toast.error(errorMessage);
|
||
} finally {
|
||
dispatch({ type: 'SET_ACTION_LOADING', payload: false });
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* 页面标题和描述 */}
|
||
<div>
|
||
<h2 className="text-green-800">企业审核</h2>
|
||
<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}
|
||
loading={state.loading}
|
||
/>
|
||
|
||
{/* 企业列表 */}
|
||
<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 })}
|
||
onApprove={handleApprove}
|
||
onReject={handleReject}
|
||
loading={state.actionLoading}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|