310 lines
9.7 KiB
TypeScript
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: "合同列表"
|
|
};
|