Files
leaudit-platform-frontend/app/routes/contract-template.list._index.tsx
T

310 lines
9.7 KiB
TypeScript

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<typeof loader>();
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 (
<div className="contract-search-results" key={`${category}-${category_id}-${page}`}>
{/* 页面头部 */}
<div className="result-header">
<div>
<h2 className="text-2xl font-semibold mb-2">
{currentCategory === '全部' ? '合同模板库' : `${currentCategory}模板`}
</h2>
<div className="result-info">
<span className="result-count">{total}</span>
</div>
</div>
<div className="flex items-center gap-4">
{/* 视图切换 */}
<div className="view-toggle">
<button
className={`view-btn ${viewMode === 'grid' ? 'active' : ''}`}
onClick={() => setViewMode('grid')}
aria-label="网格视图"
>
<i className="ri-grid-line"></i>
</button>
<button
className={`view-btn ${viewMode === 'list' ? 'active' : ''}`}
onClick={() => setViewMode('list')}
aria-label="列表视图"
>
<i className="ri-list-check"></i>
</button>
</div>
{/* 排序选择 */}
<select
className="px-3 py-3 border border-gray-200 rounded-lg text-sm"
value={sortBy}
onChange={(e) => handleSortChange(e.target.value)}
>
<option value="relevance"></option>
<option value="newest"></option>
{/* <option value="popular">使用频率</option>
<option value="rating">评分最高</option> */}
</select>
</div>
</div>
{/* 筛选标签 */}
<FilterTabs
filters={filters}
activeFilter={activeFilter}
onFilterChange={handleFilterChange}
/>
{/* 模板网格 */}
<TemplateGrid
templates={templates}
viewMode={viewMode}
onTemplateClick={handleTemplateClick}
/>
{/* 分页 */}
{totalPages > 1 && (
<Pagination
currentPage={page}
total={total}
pageSize={pageSize}
onChange={handlePageChange}
showPageSizeChanger={false}
/>
)}
</div>
);
}
// 面包屑导航配置
export const handle = {
breadcrumb: "合同列表"
};