327 lines
9.3 KiB
TypeScript
327 lines
9.3 KiB
TypeScript
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: '模板搜索结果 - 合同管理 - 智慧法务' },
|
|
{
|
|
name: 'description',
|
|
content: '查看合同管理模块的模板搜索结果,快速定位合适模板。'
|
|
}
|
|
];
|
|
};
|
|
|
|
// 面包屑导航配置
|
|
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<CategoryWithSearchCount> => {
|
|
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<typeof loader>();
|
|
|
|
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 (
|
|
<div className="contract-search-results">
|
|
{/* 紧凑搜索框 */}
|
|
<CompactSearchBox
|
|
initialQuery={query}
|
|
onSearch={handleSearch}
|
|
searchTime={searchTime}
|
|
/>
|
|
|
|
{/* 搜索结果头部 */}
|
|
<SearchResultHeader
|
|
total={total}
|
|
viewMode={viewMode}
|
|
onViewModeChange={setViewMode}
|
|
sortBy={sortBy}
|
|
onSortChange={handleSortChange}
|
|
/>
|
|
|
|
{/* 筛选标签 */}
|
|
<FilterTabs
|
|
filters={filters}
|
|
activeFilter={activeFilter}
|
|
onFilterChange={handleFilterChange}
|
|
/>
|
|
|
|
{/* 模板网格 */}
|
|
<TemplateGrid
|
|
templates={results}
|
|
viewMode={viewMode}
|
|
onTemplateClick={handleTemplateClick}
|
|
/>
|
|
|
|
{/* 分页 */}
|
|
{totalPages > 1 && (
|
|
<Pagination
|
|
currentPage={page}
|
|
total={total}
|
|
pageSize={pageSize}
|
|
onChange={handlePageChange}
|
|
showPageSizeChanger={false}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|