生产管理系统 - 企业审核与审核历史联调

This commit is contained in:
2025-11-03 22:30:49 +08:00
parent c690d50baa
commit 394e6d8342
13 changed files with 2580 additions and 582 deletions

View File

@@ -1,257 +1,355 @@
/**
* filekorolheader: 企业审核页面 - 企业注册审核管理页面
* 功能:企业审核列表、搜索筛选、审核操作、详情查看
* 路径:/central-config/tenant/enterprise-audit
* 规范遵循crop-x/docs/开发项目规范.md使用useReducer状态管理API集成模块化组件
*/
'use client';
import { useState, useEffect } from 'react';
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 { SearchFilters } from './components/SearchFilters';
import { EnterpriseList } from './components/EnterpriseList';
import { AuditSearchAndFilter } from './components/AuditSearchAndFilter';
import { EnterpriseAuditTable } from './components/EnterpriseAuditTable';
import { EnterpriseDetailDialog } from './components/EnterpriseDetailDialog';
import { Enterprise, AuditStatus } from './types';
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 [enterprises, setEnterprises] = useState<Enterprise[]>([]);
const [searchKeyword, setSearchKeyword] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [selectedEnterprise, setSelectedEnterprise] = useState<Enterprise | null>(null);
const [auditReason, setAuditReason] = useState('');
const [state, dispatch] = useReducer(auditReducer, initialState);
useEffect(() => {
loadEnterprises();
}, []);
// 加载企业数据
const loadEnterprises = async (resetPage = false) => {
try {
dispatch({ type: 'SET_LOADING', payload: true });
const loadEnterprises = () => {
const data = localStorage.getItem('smart_agriculture_enterprises');
if (data) {
setEnterprises(JSON.parse(data));
} else {
// 初始化示例数据
const mockEnterprises: Enterprise[] = [
{
id: 'ent-1',
name: '绿野农业科技有限公司',
type: '有限责任公司',
province: '北京市',
city: '海淀区',
district: '中关村街道',
companySize: '50-200人',
registeredCapital: '1000万元',
establishmentDate: '2020-03-15',
invoiceType: '增值税专用发票',
socialCreditCode: '91110000123456789X',
businessScope: '农业技术开发、技术咨询、技术服务;销售机械设备、电子产品。',
bankAccount: '1234567890123456789',
bankName: '中国工商银行',
bankFullName: '中国工商银行股份有限公司北京中关村支行',
bankAddress: '北京市海淀区中关村大街1号',
legalPerson: '张伟',
registrant: '张经理',
contactPhone: '13800138001',
address: '北京市海淀区中关村大街1号科技大厦',
status: 'active',
auditStatus: 'pending',
createdAt: '2024-10-10T08:00:00',
updatedAt: '2024-10-10T08:00:00',
},
{
id: 'ent-2',
name: '丰收现代农业集团',
type: '股份有限公司',
province: '江苏省',
city: '南京市',
district: '江宁区',
companySize: '200-500人',
registeredCapital: '5000万元',
establishmentDate: '2018-06-20',
invoiceType: '增值税专用发票',
socialCreditCode: '91320000987654321Y',
businessScope: '现代农业种植、农产品加工与销售、农业技术推广服务。',
bankAccount: '9876543210987654321',
bankName: '中国农业银行',
bankFullName: '中国农业银行股份有限公司南京江宁支行',
bankAddress: '江苏省南京市江宁区农业大道88号',
legalPerson: '李明',
registrant: '李总',
contactPhone: '13900139002',
address: '江苏省南京市江宁区农业大道88号',
status: 'active',
auditStatus: 'approved',
auditTime: '2024-10-08T14:30:00',
auditor: '系统管理员',
createdAt: '2024-10-05T10:00:00',
updatedAt: '2024-10-08T14:30:00',
},
{
id: 'ent-3',
name: '金穗农机服务中心',
type: '个人独资企业',
province: '山东省',
city: '济南市',
district: '历城区',
companySize: '1-50人',
registeredCapital: '200万元',
establishmentDate: '2021-09-10',
invoiceType: '增值税普通发票',
socialCreditCode: '91370000456789012Z',
businessScope: '农业机械租赁、维修服务、农机作业服务。',
bankAccount: '5555666677778888',
bankName: '中国建设银行',
bankFullName: '中国建设银行股份有限公司济南历城支行',
bankAddress: '山东省济南市历城区农机路66号',
legalPerson: '王刚',
registrant: '王主任',
contactPhone: '13700137003',
address: '山东省济南市历城区农机路66号',
status: 'inactive',
auditStatus: 'rejected',
auditReason: '资质材料不完整,请补充营业执照副本和法人身份证复印件',
auditTime: '2024-10-09T16:00:00',
auditor: '系统管理员',
createdAt: '2024-10-06T09:00:00',
updatedAt: '2024-10-09T16:00:00',
},
];
localStorage.setItem('smart_agriculture_enterprises', JSON.stringify(mockEnterprises));
setEnterprises(mockEnterprises);
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);
}
};
const filteredEnterprises = enterprises.filter(ent => {
const matchKeyword = !searchKeyword ||
ent.name.includes(searchKeyword) ||
ent.socialCreditCode.includes(searchKeyword) ||
ent.registrant.includes(searchKeyword);
// 初始加载
useEffect(() => {
loadEnterprises(true);
}, [state.filters.search, state.filters.audit_status, state.sortBy, state.sortOrder]);
const matchStatus = statusFilter === 'all' || ent.auditStatus === statusFilter;
// 分页加载
useEffect(() => {
if (state.pagination.page > 1) {
loadEnterprises(false);
}
}, [state.pagination.page]);
return matchKeyword && matchStatus;
});
// 计算统计数据
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) => {
setSelectedEnterprise(enterprise);
setAuditReason('');
setShowDetailDialog(true);
dispatch({ type: 'SET_SELECTED_ENTERPRISE', payload: enterprise });
dispatch({ type: 'SET_AUDIT_REASON', payload: '' });
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: true });
};
const handleApprove = () => {
if (!selectedEnterprise) return;
const now = new Date().toISOString();
const updated = enterprises.map(ent =>
ent.id === selectedEnterprise.id
? {
...ent,
auditStatus: 'approved' as AuditStatus,
status: 'active' as const,
auditTime: now,
auditor: '系统管理员',
auditReason: auditReason || undefined,
updatedAt: now,
}
: ent
);
// 创建审核历史记录
const auditRecords = JSON.parse(localStorage.getItem('smart_agriculture_audit_records') || '[]');
const newRecord = {
id: `audit-${Date.now()}`,
enterpriseId: selectedEnterprise.id,
enterpriseName: selectedEnterprise.name,
auditType: 'register',
submitTime: selectedEnterprise.createdAt,
auditTime: now,
auditor: '系统管理员',
result: 'approved',
remarks: auditReason || '审核通过',
};
auditRecords.push(newRecord);
localStorage.setItem('smart_agriculture_audit_records', JSON.stringify(auditRecords));
setEnterprises(updated);
localStorage.setItem('smart_agriculture_enterprises', JSON.stringify(updated));
setShowDetailDialog(false);
toast.success('审核通过');
const handleAuditReasonChange = (value: string) => {
dispatch({ type: 'SET_AUDIT_REASON', payload: value });
};
const handleReject = () => {
if (!selectedEnterprise) return;
if (!auditReason.trim()) {
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;
}
const now = new Date().toISOString();
const updated = enterprises.map(ent =>
ent.id === selectedEnterprise.id
? {
...ent,
auditStatus: 'rejected' as AuditStatus,
status: 'inactive' as const,
auditTime: now,
auditor: '系统管理员',
auditReason: auditReason,
updatedAt: now,
}
: ent
);
try {
dispatch({ type: 'SET_ACTION_LOADING', payload: true });
// 创建审核历史记录
const auditRecords = JSON.parse(localStorage.getItem('smart_agriculture_audit_records') || '[]');
const newRecord = {
id: `audit-${Date.now()}`,
enterpriseId: selectedEnterprise.id,
enterpriseName: selectedEnterprise.name,
auditType: 'register',
submitTime: selectedEnterprise.createdAt,
auditTime: now,
auditor: '系统管理员',
result: 'rejected',
reason: auditReason,
remarks: '审核驳回',
};
auditRecords.push(newRecord);
localStorage.setItem('smart_agriculture_audit_records', JSON.stringify(auditRecords));
const updatedTenant = await auditTenant(state.selectedEnterprise.id, {
audit_status: '已驳回',
audit_comment: state.auditReason,
});
setEnterprises(updated);
localStorage.setItem('smart_agriculture_enterprises', JSON.stringify(updated));
setShowDetailDialog(false);
toast.success('已驳回');
// 更新本地状态
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={enterprises} />
<AuditStatsCards
enterprises={state.enterprises}
loading={state.loading}
/>
{/* 搜索和筛选 */}
<SearchFilters
searchKeyword={searchKeyword}
setSearchKeyword={setSearchKeyword}
statusFilter={statusFilter}
setStatusFilter={setStatusFilter}
<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}
/>
{/* 企业列表 */}
<EnterpriseList
enterprises={filteredEnterprises}
onViewDetail={handleViewDetail}
<EnterpriseAuditTable
enterprises={state.enterprises}
loading={state.loading}
onViewDetails={handleViewDetail}
/>
{/* 详情审核对话框 */}
{/* 分页 */}
{state.pagination.total > 0 && (
<AuditPagination
pagination={state.pagination}
onPageChange={handlePageChange}
loading={state.loading}
/>
)}
{/* 企业详情对话框 */}
<EnterpriseDetailDialog
enterprise={selectedEnterprise}
open={showDetailDialog}
onOpenChange={setShowDetailDialog}
auditReason={auditReason}
onAuditReasonChange={setAuditReason}
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>
);
}
}