import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'; import { useLoaderData, useSearchParams, useNavigate } from '@remix-run/react'; import { useState, useEffect } from 'react'; import { FilterTabs } from '~/components/contract-template/FilterTabs'; import { TemplateGrid } from '~/components/contract-template/TemplateGrid'; import { Pagination } from '~/components/ui/Pagination'; import { getContractTemplates, getContractCategoriesWithCount } from '~/api/contract-template/templates'; import type { ContractTemplate, TemplateSearchParams, ContractCategoryWithCount } from '~/api/contract-template/templates'; import styles from '~/styles/pages/contract-template.css?url'; import { getUserSession } from '~/api/login/auth.server'; export const links = () => [ { rel: 'stylesheet', href: styles } ]; export const meta: MetaFunction = () => { return [ { title: '合同模板列表 - 智慧法务' }, { name: 'description', content: '浏览和管理所有合同模板,按分类查看各种类型的合同模板。' } ]; }; // 将数据库模板转换为前端显示格式 function transformTemplate(template: ContractTemplate) { return { id: template.id.toString(), title: template.title, // type: templateType, description: template.description || '', updateTime: new Date(template.updated_at).toLocaleDateString('zh-CN'), // useCount: mockUsageCount, // rating: parseFloat(mockRating), category: template.category?.name || '其他', file_path: template.file_path, file_format: template.file_format }; } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const category = url.searchParams.get('category') || ''; const category_id = url.searchParams.get('category_id') || ''; const type = url.searchParams.get('type') || ''; const sortBy = url.searchParams.get('sortBy') || 'relevance'; const page = parseInt(url.searchParams.get('page') || '1'); const pageSize = 12 // 获取 JWT const { frontendJWT } = await getUserSession(request); const jwt = frontendJWT || undefined; try { // 根据sortBy值设置数据库排序参数 let dbSortBy = 'id'; let dbSortOrder: 'asc' | 'desc' = 'asc'; switch (sortBy) { case 'relevance': dbSortBy = 'id'; dbSortOrder = 'asc'; break; case 'newest': dbSortBy = 'updated_at'; dbSortOrder = 'desc'; break; /* case 'popular': // 暂时按创建时间排序,后续可以加入使用频率字段 dbSortBy = 'created_at'; dbSortOrder = 'desc'; break; case 'rating': // 暂时按特色推荐排序,后续可以加入评分字段 dbSortBy = 'is_featured'; dbSortOrder = 'desc'; break; */ default: dbSortBy = 'id'; dbSortOrder = 'asc'; } // 构建搜索参数 const searchParams: TemplateSearchParams = { page, pageSize, sortBy: dbSortBy, sortOrder: dbSortOrder }; // 优先使用category_id,其次使用category名称 if (category_id) { searchParams.category_id = parseInt(category_id); } else if (category) { searchParams.category = category; } // 并行获取模板数据和分类数据 const [templatesResponse, categoriesResponse] = await Promise.all([ getContractTemplates({ ...searchParams, token: jwt }), getContractCategoriesWithCount(jwt) ]); // 处理模板数据 if (templatesResponse.error) { console.error('获取模板列表失败:', templatesResponse.error); return { templates: [], total: 0, page, pageSize, category, category_id, type, sortBy, categories: [] }; } // 处理分类数据 const categories: ContractCategoryWithCount[] = categoriesResponse.error ? [] : categoriesResponse.data || []; // 转换模板数据格式 const transformedTemplates = templatesResponse.data?.templates.map(transformTemplate) || []; // 注释掉类型筛选,因为数据库中没有type字段且已隐藏该功能 /* if (type) { transformedTemplates = transformedTemplates.filter(t => t.type === type); } */ // 获取当前分类信息(用于显示) let currentCategory = '全部'; if (category_id) { const cat = categories.find(c => c.id === parseInt(category_id)); currentCategory = cat?.name || '全部'; } else if (category) { currentCategory = category; } return { templates: transformedTemplates, total: templatesResponse.data?.total || 0, page, pageSize, category: currentCategory, category_id, type, sortBy, categories }; } catch (error) { console.error('加载模板列表失败:', error); return { templates: [], total: 0, page, pageSize, category, category_id, type, sortBy, categories: [] }; } } export default function ContractTemplateList() { const { templates, total, page, pageSize, category, category_id, categories, sortBy } = useLoaderData(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [activeFilter, setActiveFilter] = useState(category || '全部'); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); // 监听category变化,同步更新activeFilter状态 useEffect(() => { setActiveFilter(category || '全部'); }, [category]); const handleTemplateClick = (templateId: string) => { navigate(`/contract-template/detail/${templateId}`); }; const handleFilterChange = (filter: string) => { setActiveFilter(filter); const params = new URLSearchParams(searchParams); if (filter === '全部') { params.delete('category'); params.delete('category_id'); } else { // 根据分类名称找到对应的ID const selectedCategory = categories.find(cat => cat.name === filter); if (selectedCategory) { params.set('category_id', selectedCategory.id.toString()); params.delete('category'); // 删除旧的category参数 } else { params.set('category', filter); params.delete('category_id'); } } params.delete('page'); // 重置页码 navigate(`/contract-template/list?${params.toString()}`); }; const handlePageChange = (newPage: number) => { const params = new URLSearchParams(searchParams); params.set('page', newPage.toString()); navigate(`/contract-template/list?${params.toString()}`); }; const handleSortChange = (newSort: string) => { const params = new URLSearchParams(searchParams); params.set('sortBy', newSort); params.delete('page'); // 重置页码 navigate(`/contract-template/list?${params.toString()}`); }; // 动态生成筛选选项 // 计算所有分类的总模板数量 const totalAllTemplates = categories.reduce((sum, cat) => sum + (cat.template_count || 0), 0); const filters = [ { label: '全部', count: totalAllTemplates }, ...categories.map(cat => ({ label: cat.name, count: cat.template_count || 0 })) ]; const totalPages = Math.ceil(total / pageSize); const currentCategory = category || '全部'; return (
{/* 页面头部 */}

{currentCategory === '全部' ? '合同模板库' : `${currentCategory}模板`}

{total} 个模板
{/* 视图切换 */}
{/* 排序选择 */}
{/* 筛选标签 */} {/* 模板网格 */} {/* 分页 */} {totalPages > 1 && ( )}
); } // 面包屑导航配置 export const handle = { breadcrumb: "合同列表" };