feat: 1. 完善起草合同页面的逻辑交互,对接minio的接口操作
This commit is contained in:
@@ -11,7 +11,9 @@ import { FilePreview, type FilePreviewHandle } from '~/components/reviews/FilePr
|
||||
import { PlaceholderForm } from '~/components/contracts/PlaceholderForm';
|
||||
import { getDraftById, deleteDraft } from '~/api/contracts/draft-service.server';
|
||||
import { getUserSession } from '~/api/login/auth.server';
|
||||
import { deleteFile } from '~/api/storage/minio-client';
|
||||
import { toastService } from '~/components/ui/Toast';
|
||||
import { messageService } from '~/components/ui/MessageModal';
|
||||
import { downloadFile } from '~/api/axios-client';
|
||||
import { extractPlaceholdersFromDocx, generateDefaultSchema } from '~/api/contracts/docx-parser.server';
|
||||
import path from 'path';
|
||||
@@ -51,16 +53,28 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
// 【临时测试】使用测试文档和模拟数据
|
||||
// const testDocPath = path.join(process.cwd(), 'public', 'testWork', '买卖合同 (1).docx');
|
||||
const testDocPath = 'contract-template/买卖/买卖合同范本.docx';
|
||||
// 从 URL 参数获取文件路径、模板 ID 和标题
|
||||
const url = new URL(request.url);
|
||||
const filePath = url.searchParams.get('filePath');
|
||||
const templateId = url.searchParams.get('templateId');
|
||||
const title = url.searchParams.get('title');
|
||||
|
||||
// 创建临时的草稿对象(用于测试)
|
||||
if (!filePath) {
|
||||
throw new Response('文件路径参数缺失', { status: 400 });
|
||||
}
|
||||
|
||||
if (!templateId) {
|
||||
throw new Response('模板ID参数缺失', { status: 400 });
|
||||
}
|
||||
|
||||
console.log('[Loader] 起草合同:', { filePath, templateId, title });
|
||||
|
||||
// 创建草稿对象
|
||||
const draft: DraftedContract = {
|
||||
id: draftId,
|
||||
template_id: 1,
|
||||
file_path: testDocPath,
|
||||
title: '买卖合同-测试草稿',
|
||||
template_id: parseInt(templateId),
|
||||
file_path: filePath,
|
||||
title: title || '未命名合同',
|
||||
placeholder_values: {},
|
||||
status: 'draft',
|
||||
created_by: parseInt(userInfo.sub),
|
||||
@@ -72,10 +86,10 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
let placeholderSchema: PlaceholderSchema | null = null;
|
||||
|
||||
try {
|
||||
console.log('[Loader] 使用测试文档:', testDocPath);
|
||||
console.log('[Loader] 使用文件:', filePath);
|
||||
|
||||
// 提取占位符
|
||||
const placeholders = await extractPlaceholdersFromDocx(testDocPath);
|
||||
const placeholders = await extractPlaceholdersFromDocx(filePath);
|
||||
console.log('[Loader] 提取到的占位符:', placeholders);
|
||||
|
||||
// 生成默认 schema
|
||||
@@ -86,15 +100,15 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
placeholderSchema = null;
|
||||
}
|
||||
|
||||
// 创建临时的模板对象(用于测试)
|
||||
// 创建模板对象
|
||||
const template: ContractTemplate = {
|
||||
id: 1,
|
||||
title: '买卖合同模板',
|
||||
template_code: 'TEST-001',
|
||||
id: parseInt(templateId),
|
||||
title: title || '合同模板',
|
||||
template_code: 'DRAFT-' + templateId,
|
||||
category_id: 1,
|
||||
file_path: testDocPath,
|
||||
file_path: filePath,
|
||||
file_format: 'docx',
|
||||
description: '测试用买卖合同模板',
|
||||
description: '起草中的合同',
|
||||
is_featured: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
@@ -103,12 +117,13 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
|
||||
return Response.json({
|
||||
draft,
|
||||
template
|
||||
template,
|
||||
returnUrl: `/contract-template/detail/${templateId}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action 函数:处理删除草稿
|
||||
* Action 函数:处理文件删除
|
||||
*/
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
const draftId = parseInt(params.draftId || '0');
|
||||
@@ -123,19 +138,26 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
||||
return Response.json({ error: '未登录' }, { status: 401 });
|
||||
}
|
||||
|
||||
const userId = parseInt(userInfo.sub);
|
||||
const jwt = frontendJWT || undefined;
|
||||
|
||||
try {
|
||||
// 解析表单数据
|
||||
const formData = await request.formData();
|
||||
const actionType = formData.get('_action') as string;
|
||||
|
||||
if (actionType === 'delete') {
|
||||
// 删除草稿记录
|
||||
await deleteDraft(draftId, userId, jwt);
|
||||
if (actionType === 'deleteFile') {
|
||||
const filePath = formData.get('filePath') as string;
|
||||
|
||||
return Response.json({ success: true, message: '草稿已删除' });
|
||||
if (!filePath) {
|
||||
return Response.json({ error: '文件路径缺失' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 删除 MinIO 文件,传递 JWT
|
||||
await deleteFile({ file_path: filePath }, jwt);
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: '文件删除成功'
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({ error: '无效的操作类型' }, { status: 400 });
|
||||
@@ -149,14 +171,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
export default function ContractDraftPage() {
|
||||
const { draft, template } = useLoaderData<typeof loader>();
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const { draft, template, returnUrl } = loaderData;
|
||||
const navigate = useNavigate();
|
||||
const fetcher = useFetcher<ActionData>();
|
||||
|
||||
const [placeholderValues, setPlaceholderValues] = useState<Record<string, string>>(
|
||||
draft.placeholder_values || {}
|
||||
);
|
||||
const [isReplacing, setIsReplacing] = useState(false);
|
||||
const [highlightValue, setHighlightValue] = useState<string | undefined>(undefined);
|
||||
const [aiSuggestionReplace, setAiSuggestionReplace] = useState<{
|
||||
searchText: string;
|
||||
@@ -165,56 +187,88 @@ export default function ContractDraftPage() {
|
||||
} | undefined>(undefined);
|
||||
|
||||
const filePreviewRef = useRef<FilePreviewHandle>(null);
|
||||
const hasDeletedNormally = useRef(false); // 标记是否已通过正常流程删除
|
||||
const currentPathRef = useRef(window.location.pathname); // 保存当前路由路径
|
||||
|
||||
// 从 fetcher.state 判断是否正在操作
|
||||
const isDeleting = fetcher.state !== 'idle';
|
||||
|
||||
// 处理 fetcher 响应(删除草稿)
|
||||
// 处理 fetcher 响应(文件删除)
|
||||
useEffect(() => {
|
||||
if (fetcher.data?.success && fetcher.data.message === '草稿已删除') {
|
||||
// 删除成功,跳转到模板列表
|
||||
navigate('/contract-template');
|
||||
if (fetcher.data?.success) {
|
||||
// 标记已通过正常流程删除
|
||||
hasDeletedNormally.current = true;
|
||||
|
||||
// 文件删除成功,显示提示并跳转
|
||||
// toastService.success('合同已完成');
|
||||
|
||||
// 延迟跳转,让用户看到成功提示
|
||||
setTimeout(() => {
|
||||
if (returnUrl) {
|
||||
navigate(returnUrl);
|
||||
} else {
|
||||
navigate('/contract-template');
|
||||
}
|
||||
}, 500);
|
||||
} else if (fetcher.data?.error) {
|
||||
toastService.error(fetcher.data.error);
|
||||
}
|
||||
}, [fetcher.data, navigate]);
|
||||
}, [fetcher.data, navigate, returnUrl]);
|
||||
|
||||
// 监听页面关闭事件 - 自动删除草稿
|
||||
// 页面卸载或路由切换时清理文件
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
// 发送删除请求(使用 sendBeacon 确保请求发送)
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'delete');
|
||||
const deleteFileSync = () => {
|
||||
// 如果已经通过正常流程删除,不再重复删除
|
||||
if (hasDeletedNormally.current || !draft.file_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.sendBeacon(
|
||||
`/contract-draft/${draft.id}`,
|
||||
formData
|
||||
);
|
||||
// console.log('[Cleanup] 尝试删除文件:', draft.file_path);
|
||||
// console.log('[Cleanup] 使用路径:', currentPathRef.current);
|
||||
|
||||
try {
|
||||
// 直接使用同步 XMLHttpRequest 确保删除请求真正执行
|
||||
// 使用保存的原始路径,而不是当前的 window.location.pathname(可能已切换)
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', currentPathRef.current, false); // false = 同步请求
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'deleteFile');
|
||||
formData.append('filePath', draft.file_path);
|
||||
|
||||
xhr.send(formData);
|
||||
// console.log('[Cleanup] 同步删除完成,状态:', xhr.status);
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
// console.log('[Cleanup] ✅ 文件删除成功:', response);
|
||||
} catch (e) {
|
||||
// console.log('[Cleanup] ✅ 文件删除成功(状态码200)');
|
||||
}
|
||||
} else {
|
||||
console.error('[Cleanup] ❌ 文件删除失败,状态码:', xhr.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Cleanup] 删除文件失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBeforeUnload = () => {
|
||||
deleteFileSync();
|
||||
};
|
||||
|
||||
// 监听页面卸载事件
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
|
||||
// 清理事件监听器
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, [draft.id]);
|
||||
|
||||
// 组件卸载时删除草稿(处理路由跳转的情况)
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// 组件卸载时删除草稿记录
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'delete');
|
||||
|
||||
fetch(`/contract-draft/${draft.id}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
keepalive: true // 确保请求在页面关闭后仍然发送
|
||||
}).catch(err => {
|
||||
console.error('[Draft] 删除草稿失败:', err);
|
||||
});
|
||||
// 组件卸载时(路由切换),执行删除
|
||||
deleteFileSync();
|
||||
};
|
||||
}, [draft.id]);
|
||||
}, [draft.file_path]);
|
||||
|
||||
// 单个替换占位符
|
||||
const handleSingleReplace = async (key: string, value: string) => {
|
||||
@@ -231,7 +285,7 @@ export default function ContractDraftPage() {
|
||||
// 短暂延迟后清除参数,以便下次可以重新触发
|
||||
setTimeout(() => {
|
||||
setAiSuggestionReplace(undefined);
|
||||
toastService.success(`已替换 ${key}`);
|
||||
// toastService.success(`已替换 ${key}`);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
@@ -249,52 +303,7 @@ export default function ContractDraftPage() {
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 批量替换占位符
|
||||
const handleBatchReplace = async () => {
|
||||
setIsReplacing(true);
|
||||
|
||||
try {
|
||||
// 获取 CollaboraViewer 引用
|
||||
const collaboraRef = filePreviewRef.current?.collaboraViewerRef.current;
|
||||
|
||||
if (!collaboraRef?.isReady) {
|
||||
toastService.warning('文档尚未加载完成,请稍候...');
|
||||
setIsReplacing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Draft] 开始批量替换占位符:', placeholderValues);
|
||||
|
||||
// 批量替换所有占位符
|
||||
let replaceCount = 0;
|
||||
for (const [key, value] of Object.entries(placeholderValues)) {
|
||||
if (value) { // 只替换有值的字段
|
||||
const placeholder = `{{${key}}}`;
|
||||
console.log(`[Draft] 替换: ${placeholder} -> ${value}`);
|
||||
|
||||
// 调用 unoCommands.replaceAll 方法
|
||||
if (collaboraRef.unoCommands?.replaceAll) {
|
||||
await collaboraRef.unoCommands.replaceAll(placeholder, value);
|
||||
replaceCount++;
|
||||
// 添加延迟避免 Collabora 响应不过来
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
} else {
|
||||
console.warn('[Draft] unoCommands.replaceAll 方法不可用');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Draft] 替换完成,共替换 ${replaceCount} 个占位符`);
|
||||
toastService.success(`占位符替换完成(${replaceCount}个)`);
|
||||
} catch (error) {
|
||||
console.error('[Draft] 替换失败:', error);
|
||||
toastService.error(`替换失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
} finally {
|
||||
setIsReplacing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 导出文档(下载文件)
|
||||
// 导出文档(下载当前编辑的文件)
|
||||
const handleExportDocument = async () => {
|
||||
if (!draft.file_path) {
|
||||
toastService.error('文件路径不存在,无法下载');
|
||||
@@ -304,7 +313,7 @@ export default function ContractDraftPage() {
|
||||
try {
|
||||
toastService.info('正在下载文件...');
|
||||
|
||||
// 使用统一的下载方法
|
||||
// 使用 axios-client 的 downloadFile 方法下载文件
|
||||
const blob = await downloadFile(draft.file_path);
|
||||
|
||||
// 创建Blob URL
|
||||
@@ -336,30 +345,43 @@ export default function ContractDraftPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 完成起草(下载文件 + 删除草稿记录)
|
||||
// 完成起草(下载文件 + 删除 MinIO 文件)
|
||||
const handleComplete = async () => {
|
||||
// 1. 先下载文件
|
||||
await handleExportDocument();
|
||||
try {
|
||||
// 1. 先下载文件
|
||||
await handleExportDocument();
|
||||
|
||||
// 2. 延迟后删除草稿记录并跳转
|
||||
setTimeout(() => {
|
||||
// 2. 删除 MinIO 文件
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'delete');
|
||||
formData.append('_action', 'deleteFile');
|
||||
formData.append('filePath', draft.file_path);
|
||||
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('[Complete] 操作失败:', error);
|
||||
toastService.error('操作失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 返回模板详情页(删除草稿)
|
||||
// 返回模板详情页
|
||||
const handleBack = () => {
|
||||
if (confirm('确定要返回吗?草稿将被删除。')) {
|
||||
// 删除草稿记录
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'delete');
|
||||
messageService.show({
|
||||
type: 'warning',
|
||||
title: '确认返回',
|
||||
message: '确定要返回吗?未保存的更改将丢失。',
|
||||
confirmText: '确定返回',
|
||||
cancelText: '取消',
|
||||
onConfirm: () => {
|
||||
// 删除 MinIO 文件
|
||||
const formData = new FormData();
|
||||
formData.append('_action', 'deleteFile');
|
||||
formData.append('filePath', draft.file_path);
|
||||
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
// 删除成功后会自动跳转(通过 useEffect)
|
||||
}
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
|
||||
// 注意:文件删除后会在 useEffect 中跳转
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -376,11 +398,11 @@ export default function ContractDraftPage() {
|
||||
</button>
|
||||
<div className="border-l border-gray-300 h-10"></div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-xl font-bold text-gray-900 tracking-tight">{draft.title}</h1>
|
||||
<p className="text-sm text-gray-500 flex items-center gap-2">
|
||||
<i className="ri-file-text-line text-base"></i>
|
||||
<span>基于模板:{template.title}</span>
|
||||
</p>
|
||||
{/* <h1 className="text-xl font-bold text-gray-900 tracking-tight">{draft.title}</h1> */}
|
||||
<h1 className="flex items-center gap-2">
|
||||
<i className="ri-file-text-line"></i>
|
||||
<span>基于模板:{template.title.replace(/-[\d-]+$/, '')}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -422,10 +444,7 @@ export default function ContractDraftPage() {
|
||||
schema={template.placeholder_schema as any}
|
||||
values={placeholderValues}
|
||||
onChange={setPlaceholderValues}
|
||||
onBatchReplace={handleBatchReplace}
|
||||
onExportDocument={handleExportDocument}
|
||||
onComplete={handleComplete}
|
||||
isReplacing={isReplacing}
|
||||
isDeleting={isDeleting}
|
||||
onSingleReplace={handleSingleReplace}
|
||||
onFieldFocus={handleFieldFocus}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
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 { useState, useEffect } 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';
|
||||
import { apiRequest, downloadFile } from '~/api/axios-client';
|
||||
|
||||
// 导入FilePreview组件
|
||||
import { FilePreview } from '~/components/reviews';
|
||||
// 导入统一的下载方法和提示服务
|
||||
import { downloadFile } from '~/api/axios-client';
|
||||
import { toastService } from '~/components/ui/Toast';
|
||||
|
||||
export const links = () => [
|
||||
@@ -70,7 +69,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
* Action 函数:处理起草合同请求
|
||||
*/
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
const templateId = parseInt(params.id || '0');
|
||||
const templateId = params.id!;
|
||||
|
||||
if (!templateId) {
|
||||
return Response.json({ error: '模板ID无效' }, { status: 400 });
|
||||
@@ -86,27 +85,60 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
||||
// 解析表单数据
|
||||
const formData = await request.formData();
|
||||
const title = formData.get('title') as string;
|
||||
const draftFilePath = formData.get('draftFilePath') as string | null;
|
||||
const originalFilePath = formData.get('originalFilePath') as string;
|
||||
|
||||
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
|
||||
// );
|
||||
if (!originalFilePath) {
|
||||
return Response.json({ error: '文件路径不存在' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 重定向到草稿编辑页面
|
||||
// return redirect(`/contract-draft/${draft.id}`);
|
||||
return redirect(`/contract-draft/1`);
|
||||
// 生成新文件路径
|
||||
const area = userInfo.area || 'unknown';
|
||||
const timestamp = Date.now();
|
||||
const uuid = crypto.randomUUID();
|
||||
|
||||
// 提取文件目录和文件名
|
||||
const lastSlashIndex = originalFilePath.lastIndexOf('/');
|
||||
const directory = lastSlashIndex >= 0 ? originalFilePath.substring(0, lastSlashIndex) : '';
|
||||
const fileName = lastSlashIndex >= 0 ? originalFilePath.substring(lastSlashIndex + 1) : originalFilePath;
|
||||
|
||||
// 提取文件扩展名
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
const baseName = lastDotIndex >= 0 ? fileName.substring(0, lastDotIndex) : fileName;
|
||||
const extension = lastDotIndex >= 0 ? fileName.substring(lastDotIndex) : '';
|
||||
|
||||
// 构建新文件名
|
||||
const newFileName = `${baseName}_${area}_${timestamp}_${uuid}${extension}`;
|
||||
const newFilePath = directory ? `${directory}/${newFileName}` : newFileName;
|
||||
|
||||
console.log('[Draft] 复制文件:', { originalFilePath, newFilePath });
|
||||
|
||||
// 调用 MinIO 复制文件 API(需要传递 JWT)
|
||||
const jwt = frontendJWT || undefined;
|
||||
const copyResponse = await apiRequest('/api/v2/storage/files/copy', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
source_path: originalFilePath,
|
||||
destination_path: newFilePath
|
||||
},
|
||||
headers: {
|
||||
'Authorization': jwt ? `Bearer ${jwt}` : ''
|
||||
}
|
||||
});
|
||||
|
||||
if (copyResponse.error) {
|
||||
console.error('[Draft] 文件复制失败:', copyResponse.error);
|
||||
return Response.json({ error: `文件复制失败: ${copyResponse.error}` }, { status: 500 });
|
||||
}
|
||||
|
||||
console.log('[Draft] 文件复制成功:', copyResponse.data);
|
||||
|
||||
// 重定向到草稿编辑页面,通过 URL 参数传递文件路径和模板 ID
|
||||
const draftUrl = `/contract-draft/1?filePath=${encodeURIComponent(newFilePath)}&templateId=${templateId}&title=${encodeURIComponent(title)}`;
|
||||
return redirect(draftUrl);
|
||||
} catch (error) {
|
||||
console.error('[Template Detail] 创建草稿失败:', error);
|
||||
return Response.json(
|
||||
@@ -124,6 +156,12 @@ export default function ContractTemplateDetail() {
|
||||
// 注释掉收藏功能
|
||||
// const [isFavorited, setIsFavorited] = useState(false);
|
||||
|
||||
// 防止页面加载时自动滚动到预览区域(由 Collabora iframe 的 tabIndex 导致)
|
||||
useEffect(() => {
|
||||
// 页面加载后立即滚动回顶部
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
}, []);
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
@@ -181,21 +219,20 @@ export default function ContractTemplateDetail() {
|
||||
const handleStartDraft = () => {
|
||||
if (isCreatingDraft) return;
|
||||
|
||||
// 生成默认标题
|
||||
// const defaultTitle = `${template.title}-${new Date().toLocaleDateString('zh-CN').replace(/\//g, '')}`;
|
||||
if (!template.file_path) {
|
||||
toastService.error('模板文件路径不存在,无法起草');
|
||||
return;
|
||||
}
|
||||
|
||||
// // 提示用户输入标题
|
||||
// const title = prompt('请输入合同标题:', defaultTitle);
|
||||
// if (!title) return;
|
||||
// 生成默认标题
|
||||
const defaultTitle = `${template.title}-${new Date().toLocaleDateString('zh-CN').replace(/\//g, '')}`;
|
||||
|
||||
setIsCreatingDraft(true);
|
||||
|
||||
// 使用 Remix 的 submit 提交表单
|
||||
const formData = new FormData();
|
||||
// formData.append('title', title.trim());
|
||||
formData.append('title', '买卖合同-拟起草合同');
|
||||
// 可选:如果需要复制文件,可以先调用文件复制服务,然后传递 draftFilePath
|
||||
// formData.append('draftFilePath', draftFilePath);
|
||||
formData.append('title', defaultTitle);
|
||||
formData.append('originalFilePath', template.file_path);
|
||||
|
||||
submit(formData, { method: 'post' });
|
||||
};
|
||||
@@ -430,7 +467,7 @@ export default function ContractTemplateDetail() {
|
||||
|
||||
{/* 合同预览 - 只有当存在pdf_file_path时才显示 */}
|
||||
{fileContent && (
|
||||
<div className="content-section mb-8" id="template-preview">
|
||||
<div className="content-section mb-8">
|
||||
<h3 className="section-title text-xl font-semibold mb-4">合同预览</h3>
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<div
|
||||
|
||||
@@ -188,7 +188,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
toastService.success('更新文档成功');
|
||||
return redirect("/documents");
|
||||
return redirect("/documents/list");
|
||||
} catch (error) {
|
||||
console.error("更新文档失败2:", error);
|
||||
return Response.json({
|
||||
|
||||
@@ -382,7 +382,7 @@ export default function RuleNew() {
|
||||
const fetchEvaluationPointGroups = useCallback(async () => {
|
||||
try {
|
||||
// console.log("🔍 [fetchEvaluationPointGroups] 开始获取评查点组数据");
|
||||
const response = await postgrestGet('evaluation_point_groups', { token: frontendJWT });
|
||||
const response = await postgrestGet('/api/postgrest/proxy/evaluation_point_groups', { token: frontendJWT });
|
||||
|
||||
// console.log("🔍 [fetchEvaluationPointGroups] API响应:", response);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user