合同初步可以访问

This commit is contained in:
2025-05-29 17:42:35 +08:00
parent 9200654c35
commit b92d87a3b4
16 changed files with 2239 additions and 0 deletions
+326
View File
@@ -0,0 +1,326 @@
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import { useState } from 'react';
import styles from '~/styles/pages/contract-template.css?url';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: `${data?.template.title || '合同模板详情'} - 智慧法务` },
{
name: 'description',
content: data?.template.description || '查看合同模板详细信息'
}
];
};
// 面包屑导航配置
export const handle = {
breadcrumb: (data: { template: { title: string } }) => {
return data?.template?.title || "模板详情";
}
};
// 模拟详细数据
const getTemplateDetail = (id: string) => {
const templates = {
'1': {
id: '1',
title: '烟草产品销售合同(2023版)',
type: '销售合同 · 标准版',
description: '本模板是专为烟草行业设计的标准销售合同,严格遵循《烟草专卖法》等相关法律法规,涵盖了烟草产品销售过程中的各个关键环节。',
updateTime: '2023年10月25日',
useCount: 2156,
rating: 4.9,
fileSize: '245KB',
scope: '烟草产品销售',
legalBasis: '《合同法》《烟草专卖法》',
templateCode: 'XS-2023-001',
reviews: [
{
user: '李经理',
rating: 5,
comment: '模板非常专业,条款完整,符合行业规范。我们公司一直在使用这个模板,效果很好。',
date: '2023-10-20'
},
{
user: '王总',
rating: 4,
comment: '模板结构清晰,易于理解和使用。特别是违约责任条款写得很详细,对我们很有帮助。',
date: '2023-10-18'
}
],
features: [
{ title: '法律合规', description: '严格遵循烟草行业法律法规,确保合同条款合法有效', icon: 'ri-shield-check-line', color: 'green' },
{ title: '条款完整', description: '涵盖销售全流程,条款结构完整,逻辑清晰', icon: 'ri-settings-3-line', color: 'blue' },
{ title: '易于定制', description: '模板化设计,可根据具体业务需求灵活调整', icon: 'ri-edit-line', color: 'purple' },
{ title: '行业标准', description: '符合烟草行业标准,被广泛使用和认可', icon: 'ri-award-line', color: 'orange' }
],
structure: [
{ step: 1, title: '合同主体', description: '甲乙双方基本信息、资质证明' },
{ step: 2, title: '标的物条款', description: '产品名称、规格、数量、质量标准' },
{ step: 3, title: '价格与付款', description: '价格条款、付款方式、结算周期' },
{ step: 4, title: '交付与验收', description: '交付时间、地点、方式、验收标准' },
{ step: 5, title: '违约责任', description: '违约情形、责任承担、损失赔偿' },
{ step: 6, title: '争议解决', description: '争议处理方式、管辖法院' }
],
preview: `
中文合同预览内容...
烟草产品销售合同
合同编号:_______________
甲方(销售方):_________________________
地址:_____________________________________
法定代表人:_______________ 联系电话:_______________
烟草专卖许可证号:_________________________
乙方(采购方):_________________________
地址:_____________________________________
法定代表人:_______________ 联系电话:_______________
烟草专卖零售许可证号:_____________________
根据《中华人民共和国合同法》、《中华人民共和国烟草专卖法》等相关法律法规,
甲乙双方在平等、自愿、公平、诚信的基础上,就烟草产品销售事宜达成如下协议:
第一条 标的物
1.1 产品名称:_________________________
1.2 产品规格:_________________________
1.3 产品数量:_________________________
1.4 产品单价:_________________________
1.5 合同总金额:_______________________
... 更多条款内容请下载完整模板查看 ...
`
}
};
return templates[id as keyof typeof templates] || null;
};
export async function loader({ params }: LoaderFunctionArgs) {
const template = getTemplateDetail(params.id!);
if (!template) {
throw new Response('模板未找到', { status: 404 });
}
return { template };
}
export default function ContractTemplateDetail() {
const { template } = useLoaderData<typeof loader>();
const navigate = useNavigate();
const [isFavorited, setIsFavorited] = useState(false);
const handleBack = () => {
navigate(-1);
};
const handleDownload = () => {
console.log('下载模板:', template.id);
// 这里应该是实际的下载逻辑
};
const handlePreview = () => {
console.log('预览模板:', template.id);
// 这里应该打开预览模态框或新页面
};
const handleFavorite = () => {
setIsFavorited(!isFavorited);
console.log('收藏状态:', !isFavorited);
};
const handleShare = () => {
console.log('分享模板:', template.id);
// 这里应该是分享功能
};
const renderStars = (rating: number) => {
const stars = [];
const fullStars = Math.floor(rating);
for (let i = 0; i < 5; i++) {
if (i < fullStars) {
stars.push(<i key={i} className="ri-star-fill"></i>);
} else {
stars.push(<i key={i} className="ri-star-line"></i>);
}
}
return stars;
};
return (
<div className="contract-search-results">
{/* 返回按钮 */}
<div className="mb-6">
<button
onClick={handleBack}
className="flex items-center px-3 py-2 text-sm border border-gray-200 rounded-lg hover:border-primary-color transition-colors"
>
<i className="ri-arrow-left-line mr-2"></i>
</button>
</div>
{/* 模板详情 */}
<div className="template-detail max-w-4xl mx-auto">
{/* 详情头部 */}
<div className="detail-header bg-white rounded-xl p-8 mb-6 border border-gray-100">
<div className="flex justify-between items-start mb-4">
<div className="template-type">{template.type}</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 text-yellow-500">
{renderStars(template.rating)}
<span className="text-sm ml-1">{template.rating} (156)</span>
</div>
</div>
</div>
<h1 className="detail-title text-3xl font-semibold mb-6">{template.title}</h1>
<div className="detail-meta grid grid-cols-2 md:grid-cols-3 gap-4 mb-6">
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.templateCode}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.updateTime}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500">使</span>
<span>{template.useCount.toLocaleString()}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.fileSize}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.scope}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.legalBasis}</span>
</div>
</div>
<div className="detail-actions flex gap-3">
<button
className="detail-btn primary bg-primary text-white px-6 py-3 rounded-lg flex items-center gap-2 hover:bg-primary-hover"
onClick={handleDownload}
>
<i className="ri-download-line"></i>
使
</button>
<button
className="detail-btn secondary bg-white border border-gray-200 px-6 py-3 rounded-lg flex items-center gap-2 hover:border-primary"
onClick={handlePreview}
>
<i className="ri-eye-line"></i>
线
</button>
<button
className={`detail-btn secondary bg-white border border-gray-200 px-6 py-3 rounded-lg flex items-center gap-2 hover:border-primary ${isFavorited ? 'text-yellow-500' : ''}`}
onClick={handleFavorite}
>
<i className={isFavorited ? 'ri-star-fill' : 'ri-star-line'}></i>
</button>
<button
className="detail-btn secondary bg-white border border-gray-200 px-6 py-3 rounded-lg flex items-center gap-2 hover:border-primary"
onClick={handleShare}
>
<i className="ri-share-line"></i>
</button>
</div>
</div>
{/* 详情内容 */}
<div className="detail-content bg-white rounded-xl p-8 border border-gray-100">
{/* 模板简介 */}
<div className="content-section mb-8">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<p className="text-gray-600 leading-relaxed">
{template.description}
</p>
</div>
{/* 主要特点 */}
<div className="content-section mb-8">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{template.features.map((feature, index) => (
<div key={index} className={`bg-${feature.color}-50 p-4 rounded-lg border border-${feature.color}-200`}>
<div className="flex items-center mb-2">
<i className={`${feature.icon} text-${feature.color}-600 mr-2`}></i>
<span className="font-medium">{feature.title}</span>
</div>
<p className="text-sm text-gray-600">{feature.description}</p>
</div>
))}
</div>
</div>
{/* 合同条款结构 */}
<div className="content-section mb-8">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="space-y-3">
{template.structure.map((item) => (
<div key={item.step} className="flex items-center p-3 bg-gray-50 rounded-lg">
<div className="w-8 h-8 bg-primary text-white rounded-full flex items-center justify-center text-sm font-medium mr-3">
{item.step}
</div>
<div>
<div className="font-medium">{item.title}</div>
<div className="text-sm text-gray-600">{item.description}</div>
</div>
</div>
))}
</div>
</div>
{/* 合同预览 */}
<div className="content-section mb-8">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="content-preview bg-gray-50 rounded-lg p-6 font-mono text-sm line-height-6 border-l-4 border-primary">
<pre className="whitespace-pre-wrap">{template.preview}</pre>
</div>
</div>
{/* 用户评价 */}
<div className="content-section">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="space-y-4">
{template.reviews.map((review, index) => (
<div key={index} className="border border-gray-200 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm font-medium">
{review.user[0]}
</div>
<span className="font-medium">{review.user}</span>
<div className="flex items-center gap-1 text-yellow-500">
{renderStars(review.rating)}
</div>
</div>
<span className="text-sm text-gray-500">{review.date}</span>
</div>
<p className="text-gray-600">{review.comment}</p>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,239 @@
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 { FilterTabs } from '~/components/contract-template/FilterTabs';
import { TemplateGrid } from '~/components/contract-template/TemplateGrid';
import { Pagination } from '~/components/ui/Pagination';
import styles from '~/styles/pages/contract-template.css?url';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: '合同模板列表 - 智慧法务' },
{
name: 'description',
content: '浏览和管理所有合同模板,按分类查看各种类型的合同模板。'
}
];
};
// 模拟数据 - 完整的模板库
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: '采购合同'
}
];
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const category = url.searchParams.get('category') || '';
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);
return {
templates,
total,
page,
pageSize,
category,
type
};
}
export default function ContractTemplateList() {
const { templates, total, page, pageSize, category } = 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');
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/list?${params.toString()}`);
};
const handlePageChange = (newPage: number) => {
const params = new URLSearchParams(searchParams);
params.set('page', newPage.toString());
navigate(`/contract-template/list?${params.toString()}`);
};
// 动态生成筛选选项
const allCategories = [...new Set(mockTemplates.map(t => t.category))];
const filters = [
{ label: '全部', count: mockTemplates.length },
...allCategories.map(cat => ({
label: cat,
count: mockTemplates.filter(t => t.category === cat).length
}))
];
const totalPages = Math.ceil(total / pageSize);
const currentCategory = category || '全部';
return (
<div className="contract-search-results">
{/* 页面头部 */}
<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">
<SearchResultHeader
total={0}
viewMode={viewMode}
onViewModeChange={setViewMode}
sortBy={sortBy}
onSortChange={setSortBy}
/>
</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: "合同列表"
};
@@ -0,0 +1,77 @@
import type { MetaFunction } from '@remix-run/node';
import { useNavigate } from '@remix-run/react';
import { ContractSearchHero } from '~/components/contract-template/ContractSearchHero';
import styles from '~/styles/pages/contract-template.css?url';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: 'AI智能合同模板搜索 - 智慧法务' },
{
name: 'description',
content: '使用AI智能搜索快速找到最适合的合同模板,支持自然语言描述搜索。'
}
];
};
// 面包屑导航配置
export const handle = {
breadcrumb: "智能搜索"
};
export default function ContractTemplateSearchIndex() {
const navigate = useNavigate();
// 模拟分类数据
const categories = [
{ name: '销售合同', icon: 'ri-handshake-line', count: 128 },
{ name: '采购合同', icon: 'ri-shopping-cart-line', count: 96 },
{ name: '物流运输', icon: 'ri-truck-line', count: 64 },
{ name: '人事劳务', icon: 'ri-user-settings-line', count: 52 },
{ name: '租赁合同', icon: 'ri-building-line', count: 38 },
{ name: '保密协议', icon: 'ri-shield-check-line', count: 24 }
];
const handleSearch = (query: string) => {
if (query.trim()) {
navigate(`/contract-template/search/results?q=${encodeURIComponent(query)}`);
}
};
const handleCategoryClick = (categoryName: string) => {
navigate(`/contract-template/list?category=${encodeURIComponent(categoryName)}`);
};
return (
<div className="contract-template-search">
<ContractSearchHero onSearch={handleSearch} />
<div className="quick-categories">
{categories.map((category, index) => (
<div
key={index}
className="category-card"
onClick={() => handleCategoryClick(category.name)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleCategoryClick(category.name);
}
}}
role="button"
tabIndex={0}
aria-label={`选择${category.name}分类,共${category.count}个模板`}
>
<div className="category-icon">
<i className={category.icon}></i>
</div>
<div className="category-title">{category.name}</div>
<div className="category-count">{category.count}</div>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,276 @@
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 styles from '~/styles/pages/contract-template.css?url';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const meta: MetaFunction = () => {
return [
{ title: '搜索结果 - AI智能合同模板搜索 - 智慧法务' },
{
name: 'description',
content: 'AI智能搜索合同模板结果,快速找到最适合的模板。'
}
];
};
// 面包屑导航配置
export const handle = {
breadcrumb: "搜索结果"
};
// 模拟数据 - 扩展搜索结果
const mockSearchResults = [
{
id: '1',
title: '烟草产品销售合同标准模板',
type: '销售合同',
description: '适用于烟草产品销售业务,包含完整的违约责任条款、付款方式、交付条件等核心要素,符合行业规范要求。',
updateTime: '2023-10-25',
useCount: 1248,
rating: 4.8,
category: '销售合同'
},
{
id: '2',
title: '零售商销售协议模板',
type: '销售合同',
description: '专为零售商设计的销售协议,详细规定了违约责任、退换货政策、结算方式等条款。',
updateTime: '2023-10-20',
useCount: 856,
rating: 4.6,
category: '销售合同'
},
{
id: '3',
title: '设备采购合同(含违约条款)',
type: '采购合同',
description: '设备采购专用合同模板,包含详细的违约责任条款、质量保证、验收标准等内容。',
updateTime: '2023-10-18',
useCount: 642,
rating: 4.7,
category: '采购合同'
},
{
id: '4',
title: '批发销售合同模板',
type: '销售合同',
description: '适用于大宗批发业务的销售合同,强化了违约责任条款和风险控制措施。',
updateTime: '2023-10-15',
useCount: 534,
rating: 4.5,
category: '销售合同'
},
{
id: '5',
title: '技术服务合同(标准版)',
type: '服务合同',
description: '技术服务类合同模板,包含服务标准、违约责任、知识产权保护等关键条款。',
updateTime: '2023-10-12',
useCount: 423,
rating: 4.4,
category: '服务合同'
},
{
id: '6',
title: '区域代理销售合同',
type: '销售合同',
description: '区域代理商专用销售合同,明确代理权限、销售目标、违约责任等核心条款。',
updateTime: '2023-10-10',
useCount: 312,
rating: 4.3,
category: '销售合同'
},
{
id: '7',
title: '物流运输服务合同',
type: '物流运输',
description: '专业的物流运输服务合同模板,涵盖运输责任、保险、违约赔偿等关键条款。',
updateTime: '2023-10-08',
useCount: 267,
rating: 4.2,
category: '物流运输'
},
{
id: '8',
title: '仓储管理服务协议',
type: '物流运输',
description: '仓储管理专用合同,包含货物保管、出入库管理、损失责任等详细条款。',
updateTime: '2023-10-05',
useCount: 189,
rating: 4.1,
category: '物流运输'
},
{
id: '9',
title: '劳务派遣合同模板',
type: '人事劳务',
description: '劳务派遣服务合同,明确派遣关系、工资福利、社保缴纳等人事管理条款。',
updateTime: '2023-10-03',
useCount: 345,
rating: 4.6,
category: '人事劳务'
},
{
id: '10',
title: '商业机密保护协议',
type: '保密协议',
description: '企业商业机密保护专用协议,涵盖信息范围、保密义务、违约责任等核心内容。',
updateTime: '2023-10-01',
useCount: 156,
rating: 4.5,
category: '保密协议'
},
{
id: '11',
title: '办公场地租赁合同',
type: '租赁合同',
description: '办公场地租赁标准合同,包含租金支付、物业管理、违约处理等全面条款。',
updateTime: '2023-09-28',
useCount: 278,
rating: 4.3,
category: '租赁合同'
},
{
id: '12',
title: '设备租赁协议(长期)',
type: '租赁合同',
description: '长期设备租赁专用协议,详细规定租赁期限、维护责任、续租条件等关键条款。',
updateTime: '2023-09-25',
useCount: 198,
rating: 4.4,
category: '租赁合同'
}
];
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 page = parseInt(url.searchParams.get('page') || '1');
// 模拟搜索耗时
const startTime = Date.now();
// 这里应该是实际的搜索逻辑
// 目前返回模拟数据
let filteredResults = mockSearchResults;
// 如果有查询条件,进行筛选
if (query || category) {
filteredResults = mockSearchResults.filter(item => {
const matchesQuery = !query ||
item.title.toLowerCase().includes(query.toLowerCase()) ||
item.description.toLowerCase().includes(query.toLowerCase());
const matchesCategory = !category || item.category === category;
return matchesQuery && matchesCategory;
});
}
// 计算搜索耗时
const endTime = Date.now();
const searchTime = (endTime - startTime) / 1000;
const searchTimeText = `搜索用时 ${searchTime.toFixed(1)}`;
return {
results: filteredResults,
query,
category,
total: filteredResults.length,
page,
pageSize: 6,
searchTime: searchTimeText
};
}
export default function ContractTemplateSearchResults() {
const { results, query, total, page, pageSize, searchTime } = useLoaderData<typeof loader>();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [activeFilter, setActiveFilter] = useState('全部');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [sortBy, setSortBy] = useState('relevance');
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 handlePageChange = (newPage: number) => {
const params = new URLSearchParams(searchParams);
params.set('page', newPage.toString());
navigate(`/contract-template/search/results?${params.toString()}`);
};
const filters = [
{ label: '全部', count: total },
{ label: '销售合同', count: results.filter(r => r.category === '销售合同').length },
{ label: '采购合同', count: results.filter(r => r.category === '采购合同').length },
{ label: '服务合同', count: results.filter(r => r.category === '服务合同').length }
];
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={setSortBy}
/>
{/* 筛选标签 */}
<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>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { Outlet } from '@remix-run/react';
// 面包屑导航配置
export const handle = {
breadcrumb: "合同模板"
};
export default function ContractTemplate() {
return <Outlet />;
}