import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'; import { useLoaderData, useSearchParams, useNavigate } from '@remix-run/react'; import { useState } from 'react'; import { CompactSearchBox } from '~/components/contract-template/CompactSearchBox'; import { SearchResultHeader } from '~/components/contract-template/SearchResultHeader'; import { FilterTabs } from '~/components/contract-template/FilterTabs'; import { TemplateGrid } from '~/components/contract-template/TemplateGrid'; import { Pagination } from '~/components/ui/Pagination'; import { searchContractTemplates, getContractCategories } from '~/api/contract-template/templates'; import type { ContractTemplate, ContractCategory } 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: '搜索结果 - AI智能合同模板搜索 - 智慧法务' }, { name: 'description', content: 'AI智能搜索合同模板结果,快速找到最适合的模板。' } ]; }; // 面包屑导航配置 export const handle = { breadcrumb: "搜索结果" }; // 带搜索统计的分类类型 interface CategoryWithSearchCount extends ContractCategory { searchCount: number; } // 前端显示的模板类型 interface DisplayTemplate { id: string; title: string; // type: string; description: string; updateTime: string; // useCount: number; // rating: number; category: string; file_path?: string; file_format?: string; } // 将数据库模板转换为前端显示格式 function transformTemplate(template: ContractTemplate) { // 模拟使用次数和评分(实际项目中可以从其他表获取) /* const mockUsageCount = Math.floor(Math.random() * 2000) + 100; const mockRating = (Math.random() * 1.5 + 3.5).toFixed(1); */ return { id: template.id.toString(), title: template.title, // type: template.is_featured ? '推荐版' : '标准版', 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 query = url.searchParams.get('q') || ''; const category = url.searchParams.get('category') || ''; const sortBy = url.searchParams.get('sortBy') || 'relevance'; const page = parseInt(url.searchParams.get('page') || '1'); const pageSize = 6; // 记录搜索开始时间 const startTime = Date.now(); // 根据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'; } // 获取 JWT const { frontendJWT } = await getUserSession(request); const jwt = frontendJWT || undefined; try { // 并行获取搜索结果和分类数据 const [searchResponse, categoriesResponse] = await Promise.all([ searchContractTemplates(query, { category, page, pageSize, sortBy: dbSortBy, sortOrder: dbSortOrder, token: jwt }), getContractCategories(jwt) ]); // 处理搜索结果 if (searchResponse.error) { console.error('搜索合同模板失败:', searchResponse.error); return { results: [], query, category, total: 0, page, pageSize, sortBy, searchTime: '搜索失败', categories: [] }; } // 处理分类数据 const categories = categoriesResponse.error ? [] : categoriesResponse.data || []; // 转换模板数据格式 const transformedResults = searchResponse.data?.templates.map(transformTemplate) || []; // 为每个分类获取搜索结果统计 let categoriesWithSearchCount: CategoryWithSearchCount[] = []; if (query && query.trim()) { // 并行为每个分类获取搜索结果数量 const categorySearchPromises = categories.map(async (cat): Promise => { try { const categorySearchResponse = await searchContractTemplates(query, { category: cat.name, page: 1, pageSize: 1000, // 设置较大的pageSize来获取总数 token: jwt }); const count = categorySearchResponse.error ? 0 : (categorySearchResponse.data?.total || 0); return { ...cat, searchCount: count }; } catch (error) { console.error(`获取分类${cat.name}的搜索统计失败:`, error); return { ...cat, searchCount: 0 }; } }); categoriesWithSearchCount = await Promise.all(categorySearchPromises); } else { // 如果没有搜索关键词,searchCount设为0 categoriesWithSearchCount = categories.map(cat => ({ ...cat, searchCount: 0 })); } // 计算搜索耗时 const endTime = Date.now(); const searchTime = (endTime - startTime) / 1000; const searchTimeText = `搜索用时 ${searchTime.toFixed(1)}秒`; return { results: transformedResults, query, category, total: searchResponse.data?.total || 0, page, pageSize, sortBy, searchTime: searchTimeText, categories: categoriesWithSearchCount }; } catch (error) { console.error('加载搜索结果失败:', error); return { results: [], query, category, total: 0, page, pageSize, sortBy, searchTime: '搜索失败', categories: [] }; } } export default function ContractTemplateSearchResults() { const { results, query, total, page, pageSize, sortBy, searchTime, categories }: { results: DisplayTemplate[]; query: string; total: number; page: number; pageSize: number; sortBy: string; searchTime: string; categories: CategoryWithSearchCount[]; } = useLoaderData(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [activeFilter, setActiveFilter] = useState('全部'); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const handleSearch = (newQuery: string) => { if (newQuery.trim()) { navigate(`/contract-template/search/results?q=${encodeURIComponent(newQuery)}`); } }; 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'); } else { params.set('category', filter); } params.delete('page'); // 重置页码 navigate(`/contract-template/search/results?${params.toString()}`); }; const handlePageChange = (newPage: number) => { const params = new URLSearchParams(searchParams); params.set('page', newPage.toString()); navigate(`/contract-template/search/results?${params.toString()}`); }; const handleSortChange = (newSort: string) => { const params = new URLSearchParams(searchParams); params.set('sortBy', newSort); params.delete('page'); // 重置页码 navigate(`/contract-template/search/results?${params.toString()}`); }; // 动态生成筛选选项 const filters = [ { label: '全部', count: total }, ...categories.map(cat => ({ label: cat.name, count: cat.searchCount || 0 })) ]; const totalPages = Math.ceil(total / pageSize); return (
{/* 紧凑搜索框 */} {/* 搜索结果头部 */} {/* 筛选标签 */} {/* 模板网格 */} {/* 分页 */} {totalPages > 1 && ( )}
); }