'use client'; import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useUser } from '../layout'; import { authFetch, STATUS_NAMES, ROLE_NAMES, STATUS_COLORS } from '@/lib/auth-client'; const PAGE_SIZE = 10; function Toast({ message, type, onClose }) { useEffect(() => { const timer = setTimeout(onClose, 3000); return () => clearTimeout(timer); }, [onClose]); return (
{type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'} {message}
); } export default function ContractsPage() { const user = useUser(); const router = useRouter(); // 筛选状态 const [filters, setFilters] = useState({ status: '', date_from: '', date_to: '', search: '', }); const [appliedFilters, setAppliedFilters] = useState({ status: '', date_from: '', date_to: '', search: '', }); // 数据状态 const [contracts, setContracts] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const [exporting, setExporting] = useState(false); const [toasts, setToasts] = useState([]); const showToast = useCallback((message, type = 'info') => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); }, []); const removeToast = useCallback((id) => { setToasts(prev => prev.filter(t => t.id !== id)); }, []); // 构建查询参数 const buildQuery = useCallback((pageNum, filterObj) => { const params = new URLSearchParams(); params.set('page', pageNum); params.set('page_size', PAGE_SIZE); if (filterObj.status) params.set('status', filterObj.status); if (filterObj.date_from) params.set('date_from', filterObj.date_from); if (filterObj.date_to) params.set('date_to', filterObj.date_to); if (filterObj.search) params.set('search', filterObj.search); return params.toString(); }, []); // 加载数据 const loadContracts = useCallback(async (pageNum = 1, filterObj = appliedFilters) => { setLoading(true); try { const query = buildQuery(pageNum, filterObj); const res = await authFetch(`/api/contracts?${query}`); const data = await res.json(); setContracts(data.contracts || []); setTotal(data.total || 0); setPage(pageNum); } catch (err) { showToast('加载合同列表失败', 'error'); } finally { setLoading(false); } }, [appliedFilters, buildQuery, showToast]); useEffect(() => { loadContracts(1, appliedFilters); }, []); // 查询 const handleSearch = () => { setAppliedFilters({ ...filters }); loadContracts(1, { ...filters }); }; // 重置 const handleReset = () => { const empty = { status: '', date_from: '', date_to: '', search: '' }; setFilters(empty); setAppliedFilters(empty); loadContracts(1, empty); }; // 导出 const handleExport = async () => { setExporting(true); try { const params = new URLSearchParams(); if (appliedFilters.status) params.set('status', appliedFilters.status); if (appliedFilters.date_from) params.set('date_from', appliedFilters.date_from); if (appliedFilters.date_to) params.set('date_to', appliedFilters.date_to); if (appliedFilters.search) params.set('search', appliedFilters.search); const res = await authFetch(`/api/contracts/export?${params.toString()}`); if (!res.ok) throw new Error('导出失败'); const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `合同列表_${new Date().toLocaleDateString('zh-CN')}.xlsx`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showToast('导出成功', 'success'); } catch (err) { showToast('导出失败,请稍后重试', 'error'); } finally { setExporting(false); } }; const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr).toLocaleDateString('zh-CN'); }; const formatMoney = (val) => { if (val == null) return '-'; return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(val); }; const totalPages = Math.ceil(total / PAGE_SIZE); // 生成分页页码 const getPageNumbers = () => { const pages = []; const maxVisible = 5; let start = Math.max(1, page - Math.floor(maxVisible / 2)); let end = Math.min(totalPages, start + maxVisible - 1); if (end - start + 1 < maxVisible) { start = Math.max(1, end - maxVisible + 1); } for (let i = start; i <= end; i++) { pages.push(i); } return pages; }; return (
{/* Toast 通知 */}
{toasts.map(t => ( removeToast(t.id)} /> ))}

合同流转

{user?.role === 'employee' && ( )}
{/* 筛选栏 */}
setFilters(prev => ({ ...prev, date_from: e.target.value }))} placeholder="开始日期" /> setFilters(prev => ({ ...prev, date_to: e.target.value }))} placeholder="结束日期" /> setFilters(prev => ({ ...prev, search: e.target.value }))} placeholder="搜索项目名称 / 合作单位..." onKeyDown={(e) => { if (e.key === 'Enter') handleSearch(); }} />
{/* 数据表格 */}
{loading ? (
) : contracts.length === 0 ? (
📭

暂无合同数据

) : (
{/* */} {contracts.map((c) => ( {/* */} ))}
经办日期 项目名称 合作单位 合同金额预估利润收/付款 状态 操作
{formatDate(c.created_at)} { e.preventDefault(); router.push(`/dashboard/contracts/${c.id}`); }} style={{ fontWeight: 600, color: 'var(--primary)' }} > {c.project_name || '-'} {c.partner_name || '-'} {formatMoney(c.contract_amount)} {c.payment_type === 'pay' ? '-' : formatMoney(c.estimated_profit)}{c.payment_type === 'receive' ? '收款' : c.payment_type === 'pay' ? '付款' : '-'} {STATUS_NAMES[c.status] || c.status}
)}
{/* 分页 */} {totalPages > 0 && (
{getPageNumbers().map((p) => ( ))} 共 {total} 条
)}
); }