fix: 1.接入ai_suggestion.

2. 接入合同起草功能。
This commit is contained in:
2025-12-05 00:04:45 +08:00
parent eca98fc540
commit 33f10896a0
29 changed files with 3184 additions and 981 deletions
+111 -37
View File
@@ -1,10 +1,13 @@
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
import { redirect } from '@remix-run/node';
import { useLoaderData, useNavigate, useSubmit } from '@remix-run/react';
import { useState } from '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';
import { getUserSession } from '~/api/login/auth.server';
import { createDraftContract } from '~/api/contracts/draft-service.server';
// 导入FilePreview组件
import { FilePreview } from '~/components/reviews';
@@ -36,26 +39,26 @@ export const handle = {
export async function loader({ params, request }: LoaderFunctionArgs) {
const templateId = params.id!;
// 获取 JWT
const { frontendJWT } = await getUserSession(request);
const jwt = frontendJWT || undefined;
try {
const response = await getContractTemplate(templateId, jwt);
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);
@@ -63,9 +66,61 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
}
}
/**
* Action 函数:处理起草合同请求
*/
export async function action({ request, params }: ActionFunctionArgs) {
const templateId = parseInt(params.id || '0');
if (!templateId) {
return Response.json({ error: '模板ID无效' }, { status: 400 });
}
// 获取用户信息和JWT
const { userInfo, frontendJWT } = await getUserSession(request);
if (!userInfo?.sub) {
return Response.json({ error: '未登录' }, { status: 401 });
}
try {
// 解析表单数据
const formData = await request.formData();
const title = formData.get('title') as string;
const draftFilePath = formData.get('draftFilePath') as string | null;
if (!title) {
return Response.json({ error: '标题不能为空' }, { status: 400 });
}
// 创建草稿记录(到时候可以换成接口,使用接口来在minio中生成备份文件:备份文件可以用时间戳+uuid来保证唯一性。)
// const draft = await createDraftContract(
// {
// templateId,
// title,
// draftFilePath: draftFilePath || undefined
// },
// parseInt(userInfo.sub),
// draftFilePath || undefined,
// frontendJWT || undefined
// );
// 重定向到草稿编辑页面
// return redirect(`/contract-draft/${draft.id}`);
return redirect(`/contract-draft/1`);
} catch (error) {
console.error('[Template Detail] 创建草稿失败:', error);
return Response.json(
{ error: error instanceof Error ? error.message : '创建草稿失败' },
{ status: 500 }
);
}
}
export default function ContractTemplateDetail() {
const { template }: { template: ContractTemplate } = useLoaderData<typeof loader>();
const navigate = useNavigate();
const submit = useSubmit();
const [isCreatingDraft, setIsCreatingDraft] = useState(false);
// 注释掉收藏功能
// const [isFavorited, setIsFavorited] = useState(false);
@@ -75,6 +130,7 @@ export default function ContractTemplateDetail() {
// 使用统一的下载方法(与 rules-files.tsx 相同)
const handleDownload = async () => {
if (!template.file_path) {
toastService.error('文件路径不存在,无法下载');
return;
@@ -84,6 +140,7 @@ export default function ContractTemplateDetail() {
// 使用axios封装的下载方法
const blob = await downloadFile(template.file_path);
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
@@ -120,6 +177,29 @@ export default function ContractTemplateDetail() {
}
};
// 起草合同
const handleStartDraft = () => {
if (isCreatingDraft) return;
// 生成默认标题
// const defaultTitle = `${template.title}-${new Date().toLocaleDateString('zh-CN').replace(/\//g, '')}`;
// // 提示用户输入标题
// const title = prompt('请输入合同标题:', defaultTitle);
// if (!title) return;
setIsCreatingDraft(true);
// 使用 Remix 的 submit 提交表单
const formData = new FormData();
// formData.append('title', title.trim());
formData.append('title', '买卖合同-拟起草合同');
// 可选:如果需要复制文件,可以先调用文件复制服务,然后传递 draftFilePath
// formData.append('draftFilePath', draftFilePath);
submit(formData, { method: 'post' });
};
/* const handleFavorite = () => {
setIsFavorited(!isFavorited);
console.log('收藏状态:', !isFavorited);
@@ -247,15 +327,32 @@ export default function ContractTemplateDetail() {
</div>
<div className="detail-actions flex gap-3">
<button
<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={handleStartDraft}
disabled={isCreatingDraft}
>
{isCreatingDraft ? (
<>
<i className="ri-loader-4-line animate-spin"></i>
...
</>
) : (
<>
<i className="ri-edit-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={handleDownload}
>
<i className="ri-download-line"></i>
使
</button>
{template.pdf_file_path && (
<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}
>
@@ -336,38 +433,15 @@ export default function ContractTemplateDetail() {
<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}
<div
className="file-preview-isolation w-full"
>
<FilePreview
fileContent={fileContent}
activeReviewPointResultId={null}
targetPage={undefined}
isStructuredView={false}
isTemplate={true}
/>
</div>
</div>