Files
leaudit-platform-frontend/app/routes/contract-template.detail.$id.tsx
T
2025-05-29 17:42:35 +08:00

326 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}