Files
leaudit-platform-frontend/app/routes/contract-template.detail.$id.tsx
T

398 lines
15 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 { getContractTemplate } from '~/api/contract-template/templates';
import type { ContractTemplate } from '~/api/contract-template/templates';
import styles from '~/styles/pages/contract-template.css?url';
import filePreviewStyles from '~/styles/components/file-preview-isolation.css?url';
// 导入FilePreview组件
import { FilePreview } from '~/components/reviews';
export const links = () => [
{ rel: 'stylesheet', href: styles },
{ rel: 'stylesheet', href: filePreviewStyles },
];
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 || "模板详情";
}
};
export async function loader({ params }: LoaderFunctionArgs) {
const templateId = params.id!;
try {
const response = await getContractTemplate(templateId);
if (response.error) {
throw new Response(response.error, { status: response.status || 404 });
}
if (!response.data) {
throw new Response('模板未找到', { status: 404 });
}
// 添加调试信息
// console.log('模板详情数据:', response.data);
// console.log('分类信息:', response.data.category);
return { template: response.data };
} catch (error) {
console.error('加载模板详情失败:', error);
throw new Response('模板未找到', { status: 404 });
}
}
export default function ContractTemplateDetail() {
const { template }: { template: ContractTemplate } = useLoaderData<typeof loader>();
const navigate = useNavigate();
// 注释掉收藏功能
// const [isFavorited, setIsFavorited] = useState(false);
const handleBack = () => {
navigate(-1);
};
// MinIO下载URL构建函数
const buildDownloadUrl = (filePath: string): string => {
const minioHost = 'http://nas.7bm.co:9000';
const bucketName = 'docauditai';
const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
return `${minioHost}/${bucketName}/${cleanPath}`;
};
// 下载文件函数
const downloadFile = async (filePath: string, fileName: string) => {
try {
const downloadUrl = buildDownloadUrl(filePath);
const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_');
const link = document.createElement('a');
link.href = downloadUrl;
link.download = cleanFileName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// console.log('开始下载文件:', cleanFileName);
} catch (error) {
console.error('下载文件失败:', error);
alert('下载失败,请稍后重试');
}
};
const handleDownload = () => {
if (template.file_path) {
const fileExtension = template.file_format || 'docx';
const fileName = `${template.title}.${fileExtension}`;
downloadFile(template.file_path, fileName);
} else {
alert('文件路径不存在,无法下载');
}
};
const handlePreview = () => {
// console.log('预览模板:', template.id);
// 页面内预览,滚动到预览区域
const previewElement = document.getElementById('template-preview');
if (previewElement) {
previewElement.scrollIntoView({ behavior: 'smooth' });
}
};
/* const handleFavorite = () => {
setIsFavorited(!isFavorited);
console.log('收藏状态:', !isFavorited);
}; */
/* const handleShare = () => {
// 复制当前页面URL到剪贴板
navigator.clipboard.writeText(window.location.href).then(() => {
alert('链接已复制到剪贴板');
}).catch(() => {
alert('复制失败,请手动复制链接');
});
}; */
// 注释掉评分相关功能
/* 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;
}; */
// 创建文件内容对象用于FilePreview组件
const fileContent = template.pdf_file_path ? {
title: template.title,
contractNumber: template.template_code,
// 使用pdf_file_path字段
path: template.pdf_file_path,
parties: {
partyA: {
name: '',
address: '',
representative: '',
phone: ''
},
partyB: {
name: '',
address: '',
representative: '',
phone: ''
}
},
sections: []
} : null;
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.template_code}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{new Date(template.updated_at).toLocaleDateString('zh-CN')}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.category?.name || '其他'}</span>
</div>
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span>{template.file_format?.toUpperCase()}</span>
</div>
{template.is_featured && (
<div className="meta-item">
<span className="meta-label text-gray-500"></span>
<span className="text-primary"></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>
{template.pdf_file_path && (
<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 || '该合同模板为标准格式,包含完整的合同条款结构,适用于相关业务场景的合同签署。'}
{template.category?.description && (
<>
<br /><br />
<strong></strong>{template.category.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> */}
{/* 合同预览 - 只有当存在pdf_file_path时才显示 */}
{fileContent && (
<div className="content-section mb-8" id="template-preview">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* 使用更强的样式隔离 */}
<div
className="file-preview-isolation"
style={{
// 使用CSS变量避免继承
'--font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
'--font-size': '14px',
'--line-height': '1.5',
'--text-color': '#333333',
'--bg-color': '#ffffff',
// 强制重置所有可能的样式
all: 'unset',
display: 'block',
fontFamily: 'var(--font-family)',
fontSize: 'var(--font-size)',
lineHeight: 'var(--line-height)',
color: 'var(--text-color)',
backgroundColor: 'var(--bg-color)',
width: '100%',
minHeight: '600px',
position: 'relative',
isolation: 'isolate', // 创建新的层叠上下文
contain: 'layout style', // CSS容器化
zIndex: 0
} as React.CSSProperties}
>
<FilePreview
fileContent={fileContent}
activeReviewPointResultId={null}
targetPage={undefined}
isStructuredView={false}
/>
</div>
</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>
);
}