'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' && (
)}
{/* 筛选栏 */}
{/* 数据表格 */}
{loading ? (
) : contracts.length === 0 ? (
) : (
)}
{/* 分页 */}
{totalPages > 0 && (
{getPageNumbers().map((p) => (
))}
共 {total} 条
)}
);
}