合同基本列表数据查询基本完善
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
|
||||
import { useLoaderData, useSearchParams, useNavigate } from '@remix-run/react';
|
||||
import { useState } from 'react';
|
||||
import { SearchResultHeader } from '~/components/contract-template/SearchResultHeader';
|
||||
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';
|
||||
|
||||
export const links = () => [
|
||||
@@ -21,130 +22,137 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
// 模拟数据 - 完整的模板库
|
||||
const mockTemplates = [
|
||||
{
|
||||
id: '1',
|
||||
title: '烟草产品销售合同(2023版)',
|
||||
type: '标准版',
|
||||
description: '最新版本的烟草产品销售合同模板,包含完整的法律条款、风险控制措施和行业标准要求。',
|
||||
updateTime: '2023-10-25',
|
||||
useCount: 2156,
|
||||
rating: 4.9,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '零售商销售协议模板',
|
||||
type: '标准版',
|
||||
description: '专为零售商设计的销售协议,涵盖商品配送、结算方式、退换货政策等关键条款。',
|
||||
updateTime: '2023-10-20',
|
||||
useCount: 1834,
|
||||
rating: 4.8,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '大客户销售合同模板',
|
||||
type: '专业版',
|
||||
description: '适用于大客户的专业销售合同,包含定制化条款、特殊优惠政策和长期合作框架。',
|
||||
updateTime: '2023-10-18',
|
||||
useCount: 1245,
|
||||
rating: 4.7,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '小额销售合同(简化版)',
|
||||
type: '简化版',
|
||||
description: '适用于小额交易的简化版销售合同,条款精简但保证法律效力。',
|
||||
updateTime: '2023-10-15',
|
||||
useCount: 956,
|
||||
rating: 4.6,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: '区域代理销售合同',
|
||||
type: '标准版',
|
||||
description: '区域代理商专用销售合同,明确代理权限、销售目标和考核标准。',
|
||||
updateTime: '2023-10-12',
|
||||
useCount: 743,
|
||||
rating: 4.5,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: '批发销售合同模板',
|
||||
type: '标准版',
|
||||
description: '适用于大宗批发业务的销售合同,包含数量折扣、物流配送等专业条款。',
|
||||
updateTime: '2023-10-10',
|
||||
useCount: 612,
|
||||
rating: 4.4,
|
||||
category: '销售合同'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
title: '设备采购合同标准模板',
|
||||
type: '标准版',
|
||||
description: '设备采购专用合同模板,包含详细的技术要求、验收标准、质保条款等内容。',
|
||||
updateTime: '2023-10-08',
|
||||
useCount: 589,
|
||||
rating: 4.6,
|
||||
category: '采购合同'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
title: '服务类采购合同',
|
||||
type: '标准版',
|
||||
description: '适用于各类服务采购,包含服务标准、交付要求、考核指标等条款。',
|
||||
updateTime: '2023-10-05',
|
||||
useCount: 432,
|
||||
rating: 4.5,
|
||||
category: '采购合同'
|
||||
// 将数据库模板转换为前端显示格式
|
||||
function transformTemplate(template: ContractTemplate) {
|
||||
// 模拟使用次数和评分(实际项目中可以从其他表获取)
|
||||
const mockUsageCount = Math.floor(Math.random() * 2000) + 100;
|
||||
const mockRating = (Math.random() * 1.5 + 3.5).toFixed(1);
|
||||
|
||||
// 根据模板属性确定类型
|
||||
let templateType = '标准版';
|
||||
if (template.is_featured) {
|
||||
templateType = '推荐版';
|
||||
} else if (template.description && template.description.includes('简化')) {
|
||||
templateType = '简化版';
|
||||
} else if (template.description && (template.description.includes('专业') || template.description.includes('大客户'))) {
|
||||
templateType = '专业版';
|
||||
}
|
||||
];
|
||||
|
||||
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 || '其他'
|
||||
};
|
||||
}
|
||||
|
||||
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 page = parseInt(url.searchParams.get('page') || '1');
|
||||
const pageSize = 6;
|
||||
|
||||
// 筛选模板
|
||||
let filteredTemplates = mockTemplates;
|
||||
|
||||
if (category) {
|
||||
filteredTemplates = filteredTemplates.filter(t => t.category === category);
|
||||
}
|
||||
|
||||
if (type) {
|
||||
filteredTemplates = filteredTemplates.filter(t => t.type === type);
|
||||
}
|
||||
|
||||
const total = filteredTemplates.length;
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const templates = filteredTemplates.slice(startIndex, startIndex + pageSize);
|
||||
try {
|
||||
// 构建搜索参数
|
||||
const searchParams: TemplateSearchParams = {
|
||||
page,
|
||||
pageSize,
|
||||
sortBy: 'updated_at',
|
||||
sortOrder: 'desc'
|
||||
};
|
||||
|
||||
return {
|
||||
templates,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
category,
|
||||
type
|
||||
};
|
||||
// 优先使用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),
|
||||
getContractCategoriesWithCount()
|
||||
]);
|
||||
|
||||
// 处理模板数据
|
||||
if (templatesResponse.error) {
|
||||
console.error('获取模板列表失败:', templatesResponse.error);
|
||||
return {
|
||||
templates: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
category,
|
||||
category_id,
|
||||
type,
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
|
||||
// 处理分类数据
|
||||
const categories: ContractCategoryWithCount[] = categoriesResponse.error ? [] : categoriesResponse.data || [];
|
||||
|
||||
// 转换模板数据格式
|
||||
let 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,
|
||||
categories
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('加载模板列表失败:', error);
|
||||
return {
|
||||
templates: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
category,
|
||||
category_id,
|
||||
type,
|
||||
categories: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function ContractTemplateList() {
|
||||
const { templates, total, page, pageSize, category } = useLoaderData<typeof loader>();
|
||||
const { templates, total, page, pageSize, category, category_id, categories } = useLoaderData<typeof loader>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const [activeFilter, setActiveFilter] = useState(category || '全部');
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||
const [sortBy, setSortBy] = useState('newest');
|
||||
|
||||
// 监听category变化,同步更新activeFilter状态
|
||||
useEffect(() => {
|
||||
setActiveFilter(category || '全部');
|
||||
}, [category]);
|
||||
|
||||
const handleTemplateClick = (templateId: string) => {
|
||||
navigate(`/contract-template/detail/${templateId}`);
|
||||
};
|
||||
@@ -155,8 +163,17 @@ export default function ContractTemplateList() {
|
||||
|
||||
if (filter === '全部') {
|
||||
params.delete('category');
|
||||
params.delete('category_id');
|
||||
} else {
|
||||
params.set('category', filter);
|
||||
// 根据分类名称找到对应的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'); // 重置页码
|
||||
|
||||
@@ -170,12 +187,14 @@ export default function ContractTemplateList() {
|
||||
};
|
||||
|
||||
// 动态生成筛选选项
|
||||
const allCategories = [...new Set(mockTemplates.map(t => t.category))];
|
||||
// 计算所有分类的总模板数量
|
||||
const totalAllTemplates = categories.reduce((sum, cat) => sum + (cat.template_count || 0), 0);
|
||||
|
||||
const filters = [
|
||||
{ label: '全部', count: mockTemplates.length },
|
||||
...allCategories.map(cat => ({
|
||||
label: cat,
|
||||
count: mockTemplates.filter(t => t.category === cat).length
|
||||
{ label: '全部', count: totalAllTemplates },
|
||||
...categories.map(cat => ({
|
||||
label: cat.name,
|
||||
count: cat.template_count || 0
|
||||
}))
|
||||
];
|
||||
|
||||
@@ -183,7 +202,7 @@ export default function ContractTemplateList() {
|
||||
const currentCategory = category || '全部';
|
||||
|
||||
return (
|
||||
<div className="contract-search-results">
|
||||
<div className="contract-search-results" key={`${category}-${category_id}-${page}`}>
|
||||
{/* 页面头部 */}
|
||||
<div className="result-header">
|
||||
<div>
|
||||
@@ -195,13 +214,33 @@ export default function ContractTemplateList() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<SearchResultHeader
|
||||
total={0}
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
sortBy={sortBy}
|
||||
onSortChange={setSortBy}
|
||||
/>
|
||||
{/* 视图切换 */}
|
||||
<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) => setSortBy(e.target.value)}
|
||||
>
|
||||
<option value="newest">最新更新</option>
|
||||
<option value="popular">使用频率</option>
|
||||
<option value="rating">评分最高</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user