fix: 1.将主页和法务助手对话设置成手机也能够正确加载的响应式布局。

2. 修改合同重新上传模板的可接受文件类型,修改对接的上传模板对应的接口。
3. 交叉评查任务列表去除任务名称的点击效果。
4. 交叉评查文件预览在点击完成评查的按钮后会返回任务列表并打开任务的文档列表。
5.修复点击完成评查按钮造成页面刷新。
6. 修复创建任务的第3步无法返回列表。
This commit is contained in:
2025-11-12 15:51:39 +08:00
parent c20c168a13
commit 8a50671c39
11 changed files with 578 additions and 182 deletions
+14 -6
View File
@@ -388,7 +388,9 @@ export async function uploadDocumentToServer(
let responseData;
try {
responseData = await response.json();
// console.log('【调试】JSON响应解析成功:', responseData);
// console.log('【上传调试】服务器原始JSON响应:', responseData);
// console.log('【上传调试】响应类型:', typeof responseData);
// console.log('【上传调试】响应keys:', Object.keys(responseData));
} catch (jsonError) {
console.error('【调试】JSON解析失败:', jsonError);
return {
@@ -396,15 +398,21 @@ export async function uploadDocumentToServer(
status: 500
};
}
const extractedData = extractApiData<FileUploadResponse>(responseData);
// console.log('【调试】提取的数据:', extractedData);
// console.log('【上传调试】提取的数据:', extractedData);
// console.log('【上传调试】提取数据详情:', {
// hasData: !!extractedData,
// success: extractedData?.success,
// hasResult: !!extractedData?.result,
// error: extractedData?.error
// });
if (!extractedData) {
console.error('【调试】无法提取数据');
console.error('【调试】无法提取数据,原始响应:', JSON.stringify(responseData));
return { error: '处理上传响应失败', status: 500 };
}
// console.log('【调试】上传成功,返回数据');
return { data: extractedData };
} catch (fetchError) {
+12 -5
View File
@@ -38,21 +38,28 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ
const [selectedApp, setSelectedApp] = useState<AppModule>('');
const matches = useMatches() as Match[];
const location = useLocation();
// 检查当前路径是否应该隐藏侧边栏
const noLayoutPaths = ['/login', '/'];
const shouldHideSidebar = noLayoutPaths.includes(location.pathname);
// 检查当前路由是否应该隐藏默认面包屑
const shouldHideBreadcrumb = shouldHideSidebar || matches.some(match =>
const shouldHideBreadcrumb = shouldHideSidebar || matches.some(match =>
match.handle && match.handle.hideBreadcrumb === true
);
// 从sessionStorage中获取侧边栏状态和reviewType
useEffect(() => {
// 检查是否为移动端
const isMobile = window.innerWidth <= 768;
// 从localStorage获取侧边栏状态
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState) {
// 移动端默认收起,桌面端使用保存的状态
if (isMobile) {
setSidebarCollapsed(true);
} else if (savedState) {
setSidebarCollapsed(savedState === 'true');
}
+38 -3
View File
@@ -39,8 +39,24 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
const [isLoading, setIsLoading] = useState<boolean>(true); // 添加加载状态
const [menuItems, setMenuItems] = useState<MenuItem[]>([]); // 动态菜单项
const [isLoadingRoutes, setIsLoadingRoutes] = useState<boolean>(true); // 路由加载状态
const [isMobile, setIsMobile] = useState<boolean>(false); // 移动端检测
const navigate = useNavigate();
// 移动端检测
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth <= 768; // 768px以下视为移动端
setIsMobile(mobile);
};
// 初始检测
checkMobile();
// 监听窗口大小变化
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// 获取用户路由权限
useEffect(() => {
const fetchUserRoutes = async () => {
@@ -254,8 +270,26 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
// })
return (
<div className={`sidebar ${collapsed ? 'collapsed' : ''} flex flex-col`}>
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
<>
{/* 移动端遮罩层 */}
{isMobile && !collapsed && (
<div
className="sidebar-overlay"
onClick={onToggle}
role="button"
tabIndex={0}
aria-label="关闭侧边栏"
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onToggle();
}
}}
/>
)}
<div className={`sidebar ${collapsed ? 'collapsed' : ''} ${isMobile ? 'sidebar-mobile' : ''} flex flex-col`}>
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
<div className="flex items-center"
onClick={() => {
navigate('/');
@@ -396,6 +430,7 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', selec
{!collapsed && <span className="text-base"></span>}
</a>
</div>
</div>
</div>
</>
);
}
+90 -83
View File
@@ -10,7 +10,7 @@ import { UploadArea, type UploadAreaRef } from '~/components/ui/UploadArea';
import { Button } from '~/components/ui/Button';
import { toastService } from '~/components/ui/Toast';
// import { DOCUMENT_URL } from "~/api/axios-client";
import { uploadFileToBinary, uploadDocumentToServer } from '~/api/files/files-upload';
import { uploadContractTemplate } from '~/api/files/files-upload';
interface ReviewTabsProps {
activeTab: string;
@@ -22,6 +22,7 @@ interface ReviewTabsProps {
path?: string;
auditStatus?: number;
type?: string;
comparisonId?: number;
};
onConfirmResults: () => void;
jwtToken?: string | null;
@@ -117,23 +118,32 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
const handleTemplateFilesSelected = (files: FileList) => {
try {
if (files.length > 0) {
// 验证文件类型,允许PDF文件
// 验证文件类型,允许PDF和Word文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
const fileName = file.name.toLowerCase();
const isValidType =
file.type === 'application/pdf' ||
fileName.endsWith('.pdf') ||
file.type === 'application/msword' ||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
fileName.endsWith('.doc') ||
fileName.endsWith('.docx');
if (isValidType) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`无效的文件类型: ${file.name}, 类型: ${file.type}`);
}
});
if (hasInvalidFiles) {
toastService.error('只能上传PDF格式的文件');
toastService.error('只能上传PDF或Word格式的文件');
}
if (validFiles.length > 0) {
setSelectedTemplateFiles(validFiles);
}
@@ -152,57 +162,42 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
return;
}
// 验证必需的参数
if (!fileInfo.id) {
toastService.error('文档ID不能为空');
return;
}
try {
setIsUploading(true);
// 这里可以调用上传API
let binaryData: ArrayBuffer;
try {
binaryData = await uploadFileToBinary(selectedTemplateFiles[0]);
} catch (error) {
console.error('上传文件失败:', error);
throw new Error(`文件 ${selectedTemplateFiles[0].name} 转换失败: ${error instanceof Error ? error.message : '未知错误'}`);
console.log('【重新上传模板】开始上传:', {
fileName: selectedTemplateFiles[0].name,
documentId: fileInfo.id,
comparisonId: fileInfo.comparisonId
});
// 调用专门的合同模板上传接口
const uploadResult = await uploadContractTemplate(
selectedTemplateFiles[0], // file: File 对象
fileInfo.id, // documentId: 合同文件的id
fileInfo.comparisonId, // comparisonId: 模板预览文件的id (可选)
jwtToken || undefined // jwtToken
);
console.log('【重新上传模板】上传结果:', uploadResult);
if (uploadResult.error) {
throw new Error(uploadResult.error);
}
// const uploadInfo = {
// binaryData,
// fileName: selectedTemplateFiles[0].name,
// fileType: 'pdf',
// documentType: '1',
// priority: 'normal',
// documentNumber: null,
// remark: null,
// isTestDocument: false,
// documentId: fileInfo
// };
// console.log('uploadInfo',uploadInfo);
const uploadResult = await uploadDocumentToServer(
binaryData,
selectedTemplateFiles[0].name,
'pdf', //file_type 文件类型:pdf
'1', //fileType(type_id) 合同id1
'normal', //priority 优先级:normal
null, //document_number 文档编号
null, //remark 备注
false, //is_test_document 是否为测试文档:false
fileInfo.id, //document_id 主文档id
true, //is_reupload 是否为重新上传:true
jwtToken || undefined, //jwtToken
);
// console.log('重新上传合同模板',uploadResult);
if (uploadResult.error) {
throw new Error(uploadResult.error);
}
toastService.success('模板文件上传成功,结构比对数据将会发生更新,即将返回上一页...');
await new Promise(resolve => setTimeout(resolve, 2000));
handleCloseReuploadModal();
handleBack();
} catch (error) {
console.error('上传模板文件失败:', error);
toastService.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
@@ -335,21 +330,21 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
<p></p>
<p className="mt-2 text-orange-600">
<i className="ri-information-line mr-1"></i>
PDF格式的文件
PDF和Word格式的文件
</p>
</div>
<UploadArea
ref={uploadAreaRef}
onFilesSelected={handleTemplateFilesSelected}
accept=".pdf,application/pdf"
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
multiple={false}
icon="ri-file-pdf-line"
icon="ri-file-text-line"
buttonText="选择模板文件"
mainText="点击或拖拽PDF文件到此区域"
mainText="点击或拖拽文件到此区域"
tipText={
<span className="text-xs text-gray-500">
PDF
PDFDOCDOCX
</span>
}
disabled={isUploading}
@@ -360,38 +355,50 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi
<div className="mt-4">
<h4 className="text-sm font-medium text-gray-900 mb-2"></h4>
<div className="space-y-2">
{selectedTemplateFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border"
>
<div className="flex items-center">
<i className="ri-file-pdf-line text-red-500 mr-2"></i>
<div>
<div className="text-sm font-medium text-gray-900">
{file.name}
</div>
<div className="text-xs text-gray-500">
{formatFileSize(file.size)}
{selectedTemplateFiles.map((file, index) => {
// 根据文件类型确定图标和颜色
const fileName = file.name.toLowerCase();
let fileIcon = 'ri-file-pdf-line';
let iconColor = 'text-red-500';
if (fileName.endsWith('.doc') || fileName.endsWith('.docx')) {
fileIcon = 'ri-file-word-2-line';
iconColor = 'text-blue-600';
}
return (
<div
key={index}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border"
>
<div className="flex items-center">
<i className={`${fileIcon} ${iconColor} mr-2`}></i>
<div>
<div className="text-sm font-medium text-gray-900">
{file.name}
</div>
<div className="text-xs text-gray-500">
{formatFileSize(file.size)}
</div>
</div>
</div>
<button
className="text-gray-400 hover:text-red-500 transition-colors"
onClick={() => {
setSelectedTemplateFiles(prev =>
prev.filter((_, i) => i !== index)
);
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
}}
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
<button
className="text-gray-400 hover:text-red-500 transition-colors"
onClick={() => {
setSelectedTemplateFiles(prev =>
prev.filter((_, i) => i !== index)
);
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
}}
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
);
})}
</div>
</div>
)}
+38 -13
View File
@@ -218,9 +218,10 @@ export default function CrossCheckingIndex() {
const dateTo = searchParams.get('dateTo') || '';
const navigate = useNavigate();
const fetcher = useFetcher();
// 状态管理
const [isDeleting, setIsDeleting] = useState(false);
const [hasAutoOpened, setHasAutoOpened] = useState(false); // 标记是否已自动打开模态框
const [modalState, setModalState] = useState<{
isOpen: boolean;
title: string;
@@ -280,7 +281,13 @@ export default function CrossCheckingIndex() {
// 处理文档查看 - 导航到评查详情页
const handleViewFile = (fileId: string) => {
navigate(`/cross-checking/result?id=${fileId}&tId=${currentTaskInfo?.taskId}&previousRoute=crossChecking`);
const params = new URLSearchParams({
id: fileId,
tId: currentTaskInfo?.taskId?.toString() || '',
tName: currentTaskInfo?.taskName || '',
previousRoute: 'crossChecking'
});
navigate(`/cross-checking/result?${params.toString()}`);
};
// 存储当前任务信息用于分页
@@ -465,11 +472,38 @@ export default function CrossCheckingIndex() {
// 检测URL参数,自动打开模态框
useEffect(() => {
const openModal = searchParams.get('openModal');
const taskId = searchParams.get('taskId');
const taskName = searchParams.get('taskName');
if (openModal === 'true' && taskId && !hasAutoOpened) {
console.log('[自动打开模态框] taskId:', taskId, 'taskName:', taskName);
// 标记已自动打开,防止重复触发
setHasAutoOpened(true);
// 清除URL参数,避免刷新页面时再次打开
const newParams = new URLSearchParams(searchParams);
newParams.delete('openModal');
newParams.delete('taskId');
newParams.delete('taskName');
setSearchParams(newParams, { replace: true });
// 延迟自动打开模态框,确保状态已更新
setTimeout(() => {
handleViewResult(Number(taskId), taskName || '任务详情');
}, 100);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, hasAutoOpened]);
// 监听fetcher状态变化 - 删除操作
useEffect(() => {
if (fetcher.data && fetcher.state === 'idle' && isDeleting) {
setIsDeleting(false);
const data = fetcher.data as { result?: boolean; message?: string };
if (data.result) {
toastService.success(data.message || '操作成功');
@@ -533,16 +567,7 @@ export default function CrossCheckingIndex() {
dataIndex: "taskName" as keyof CrossCheckingTask,
key: "taskName",
align: "left" as const,
width: "16%",
render: (value: string, record: CrossCheckingTask) => (
<button
className="task-name text-left w-full"
onClick={() => navigate(`/cross-checking/${record.id}`)}
type="button"
>
{value}
</button>
)
width: "16%"
},
{
title: "评查开始时间",
+101 -43
View File
@@ -23,7 +23,7 @@
*/
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { useNavigate, useLoaderData } from "@remix-run/react";
import crossCheckingStyles from "~/styles/cross-checking-result.css?url";
import { getReviewPoints, updateReviewResult} from "~/api/evaluation_points/reviews";
@@ -185,6 +185,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const id = url.searchParams.get('id') || undefined;
const taskId = url.searchParams.get('tId') || undefined;
const taskName = url.searchParams.get('tName') || undefined;
const previousRoute = url.searchParams.get('previousRoute') || '';
// console.log("id-------",id);
if (!id) {
@@ -225,7 +226,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
scoring_proposals: reviewData.scoring_proposals || [],
userInfo: userInfo,
jwtToken: frontendJWT, // 传递JWT token
isProposer: isProposer
isProposer: isProposer,
taskId: taskId, // 传递任务ID
taskName: taskName // 传递任务名称
});
} else {
console.error("返回的评查数据格式不正确",JSON.stringify(reviewData,null,2));
@@ -294,14 +297,27 @@ export async function action({ request }: ActionFunctionArgs) {
}
export default function CrossCheckingResult() {
console.log('[组件] CrossCheckingResult 渲染');
const navigate = useNavigate();
const loaderData = useLoaderData<typeof loader>();
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken, userInfo, isProposer } = loaderData;
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken, userInfo, isProposer, taskId, taskName } = loaderData;
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
const [localScoringProposals, setLocalScoringProposals] = useState<ScoringProposal[]>(scoring_proposals || []); // 本地状态管理scoringProposals
// 使用ref来跟踪loading状态,避免不必要的重新渲染
const isProcessingRef = useRef(false);
// 添加组件挂载/卸载日志
useEffect(() => {
console.log('[组件] CrossCheckingResult 挂载');
return () => {
console.log('[组件] CrossCheckingResult 卸载');
};
}, []);
// 同步外部scoring_proposals到本地状态
useEffect(() => {
@@ -309,9 +325,9 @@ export default function CrossCheckingResult() {
}, [scoring_proposals]);
// 处理意见提交成功的回调
const handleOpinionSubmitted = (newProposal: ScoringProposal) => {
const handleOpinionSubmitted = useCallback((newProposal: ScoringProposal) => {
setLocalScoringProposals(prev => [...prev, newProposal]);
};
}, []);
// loader 数据加载出错
useEffect(()=>{
@@ -371,7 +387,7 @@ export default function CrossCheckingResult() {
}, [document, reviewPoints, statistics, reviewInfo]);
const handleReviewPointSelect = (reviewPointId: string, page?: number) => {
const handleReviewPointSelect = useCallback((reviewPointId: string, page?: number) => {
// 如果点击的是相同的评查点,但有page参数,先重置targetPage以确保useEffect能够触发
if (reviewPointId === activeReviewPointResultId && page) {
setTargetPage(undefined);
@@ -385,7 +401,7 @@ export default function CrossCheckingResult() {
setActiveReviewPointResultId(reviewPointId);
setTargetPage(page);
}
};
}, [activeReviewPointResultId]);
// 处理评审点状态变更
@@ -500,28 +516,38 @@ export default function CrossCheckingResult() {
* 2. 根据结果弹出确认模态框
* 3. 用户确认后更新文档状态并跳转
*/
const handleConfirmResults = async (event?: React.MouseEvent) => {
// 阻止默认行为,防止页面刷新
const handleConfirmResults = useCallback(async (event?: React.MouseEvent) => {
console.log('[完成评查] 开始处理');
// 首先阻止默认行为和事件冒泡,防止页面刷新
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (!document || !document.id) {
toastService.error('文档数据不完整,无法确认评查结果');
return;
}
// 使用ref防止重复点击,避免触发状态更新
if (isProcessingRef.current) {
console.log('[完成评查] 正在处理中,跳过');
return;
}
try {
setIsLoading(true);
// 1. 先检查未投票
console.log('[完成评查] 标记为处理中');
isProcessingRef.current = true;
// 1. 先检查未投票(不触发loading状态更新,避免重新渲染)
console.log('[完成评查] 开始检查未投票提案');
const checkRes = await checkProposalVotes(document.id, jwtToken);
console.log("checkRes", checkRes);
console.log("[完成评查] 检查结果:", checkRes);
if (checkRes.error) {
toastService.error(checkRes.error);
setIsLoading(false);
isProcessingRef.current = false;
return;
}
@@ -551,7 +577,12 @@ export default function CrossCheckingResult() {
modalMessage = '是否完成评查?';
}
// 4. 弹出模态框
// 4. 重置处理状态标记,准备显示模态框(不触发状态更新)
console.log('[完成评查] 重置处理标记,准备显示模态框');
isProcessingRef.current = false;
// 5. 弹出模态框
console.log('[完成评查] 显示确认模态框');
messageService.show({
title: '提示',
message: modalMessage,
@@ -559,43 +590,58 @@ export default function CrossCheckingResult() {
confirmText: '确认',
cancelText: '取消',
onConfirm: async () => {
// 用户点击确认后才开始处理,此时才显示loading
console.log('[完成评查] 用户点击确认,开始更新状态');
setIsLoading(true);
const res = await confirmReviewResults(document.id, jwtToken);
setIsLoading(false);
try {
const res = await confirmReviewResults(document.id, jwtToken);
if (res.error) {
toastService.error(res.error);
return;
if (res.error) {
toastService.error(res.error);
setIsLoading(false);
return;
}
toastService.success('评查结果已确认,文档审核状态已更新');
// 跳转到交叉评查列表页,并带上任务信息以自动打开模态框
const params = new URLSearchParams({
openModal: 'true',
taskId: taskId || '',
taskName: taskName || '任务详情'
});
navigate(`/cross-checking?${params.toString()}`);
} catch (error) {
console.error('确认评查结果失败:', error);
toastService.error('确认评查结果失败,请重试');
setIsLoading(false);
}
toastService.success('评查结果已确认,文档审核状态已更新');
// 注释掉自动跳转,让用户停留在当前页面
navigate('/cross-checking');
},
onCancel: () => {
// 用户取消时不需要做任何处理
console.log('[完成评查] 用户取消了确认操作');
}
});
setIsLoading(false);
} catch (error) {
setIsLoading(false);
isProcessingRef.current = false;
toastService.error(`确认评查结果失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
};
}, [document, jwtToken, navigate]);
// 构建自定义面包屑项
const getBreadcrumbItems = () => {
// 构建自定义面包屑项 - 使用 useMemo 缓存
const breadcrumbItems = useMemo(() => {
const items = [
{ title: "交叉评查详情", to: `/cross-checking/result?id=${document?.id}` }
];
// 添加前置路由
if (loaderData.previousRoute) {
if (loaderData.previousRoute === 'crossChecking') {
items.unshift({ title: "交叉评查", to: "/cross-checking" });
}
}
return items;
};
}, [document?.id, loaderData.previousRoute]);
return (
<div className="cross-checking-result-container">
@@ -608,8 +654,8 @@ export default function CrossCheckingResult() {
<>
{/* 自定义面包屑 */}
<div className="flex justify-between items-center mb-4">
<Breadcrumb
items={getBreadcrumbItems()}
<Breadcrumb
items={breadcrumbItems}
className="items-center flex !mb-0"
/>
@@ -638,17 +684,29 @@ export default function CrossCheckingResult() {
{/* 完成评查按钮 */}
{isProposer && (
<button
<button
type="button"
onClick={(event) => {
// 立即阻止所有默认行为
event.preventDefault();
event.stopPropagation();
handleConfirmResults(event);
// 异步调用处理函数
void handleConfirmResults(event);
}}
className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-green-800 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-800"
disabled={isLoading}
className="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-green-800 border border-transparent rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-800 disabled:opacity-50 disabled:cursor-not-allowed"
>
<i className="ri-check-double-line mr-1.5"></i>
{isLoading ? (
<>
<i className="ri-loader-4-line ri-spin animate-spin mr-1.5"></i>
...
</>
) : (
<>
<i className="ri-check-double-line mr-1.5"></i>
</>
)}
</button>
)}
</div>
+21 -23
View File
@@ -837,10 +837,9 @@ export default function CrossCheckingUpload() {
</div>
{/* 文件上传区域 */}
<Form method="post" encType="multipart/form-data">
<input type="hidden" name="caseType" value={caseType} />
<input type="hidden" name="uploadType" value={uploadType} />
{/* 上传框区域 */}
<div className="upload-section">
{/* 单案件导入 */}
@@ -958,30 +957,29 @@ export default function CrossCheckingUpload() {
</div>
)}
{/* 完成按钮 */}
<div className="flex justify-between items-center mt-8">
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => {
console.log('点击返回列表按钮');
navigate('/cross-checking');
}}
{/* 完成按钮 */}
<div className="flex justify-between items-center mt-8">
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => {
console.log('点击返回列表按钮');
navigate('/cross-checking');
}}
>
</Button>
<div className="flex space-x-4">
<Button type="default" onClick={handlePrev}></Button>
<Button
type="primary"
disabled={!canComplete || isUploading || isCreatingTask}
onClick={handleCreateTask}
>
{isCreatingTask ? "创建任务中..." : isUploading ? "上传中..." : "开始创建任务"}
</Button>
<div className="flex space-x-4">
<Button type="default" onClick={handlePrev}></Button>
<Button
type="primary"
disabled={!canComplete || isUploading || isCreatingTask}
onClick={handleCreateTask}
>
{isCreatingTask ? "创建任务中..." : isUploading ? "上传中..." : "开始创建任务"}
</Button>
</div>
</div>
</Form>
</div>
{/* 文件选择状态提示 */}
{!canComplete && !isUploading && (
+34 -3
View File
@@ -130,6 +130,14 @@ async function handleFileUpload(
jwtToken?: string,
attachments?: File[]
): Promise<FileUploadResponse> {
// console.log('【handleFileUpload】开始上传:', {
// fileName,
// fileSize: binaryData.byteLength,
// documentType,
// hasAttachments: !!(attachments && attachments.length > 0),
// attachmentCount: attachments?.length || 0
// });
const response = await uploadDocumentToServer(
binaryData,
fileName,
@@ -144,11 +152,20 @@ async function handleFileUpload(
jwtToken,
attachments
);
// console.log('【handleFileUpload】uploadDocumentToServer返回:', {
// hasError: !!response.error,
// hasData: !!response.data,
// error: response.error,
// dataKeys: response.data ? Object.keys(response.data) : []
// });
if (response.error || !response.data) {
console.error('【handleFileUpload】上传失败:', response.error);
throw new Error(response.error || '上传失败');
}
// console.log('【handleFileUpload】返回数据:', response.data);
return response.data;
}
@@ -1088,7 +1105,21 @@ export default function FilesUpload() {
attachmentFiles
);
if (!uploadResp.result) throw new Error('主文件上传失败');
// console.log('【合同上传】服务器响应数据:', uploadResp);
// console.log('【合同上传】响应详情:', {
// success: uploadResp.success,
// hasResult: !!uploadResp.result,
// error: uploadResp.error,
// fullResponse: JSON.stringify(uploadResp)
// });
if (!uploadResp.success) {
throw new Error(uploadResp.error || '上传失败,服务器返回success=false');
}
if (!uploadResp.result) {
throw new Error('主文件上传失败:服务器未返回文档信息');
}
const documentId = uploadResp.result.id;
// 可选:模板上传
+2 -1
View File
@@ -702,7 +702,8 @@ export default function ReviewDetails() {
previousRoute: loaderData.previousRoute,
path: document?.path,
auditStatus: document?.auditStatus,
type: document?.type
type: document?.type,
comparisonId: comparison_document?.id ? Number(comparison_document.id) : undefined
}}
onConfirmResults={handleConfirmResults}
jwtToken={frontendJWT}
+48 -2
View File
@@ -162,7 +162,31 @@
.sidebar.collapsed {
@apply w-20;
}
/* 移动端侧边栏遮罩层 */
.sidebar-overlay {
@apply fixed inset-0 bg-black bg-opacity-50 z-[99];
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 移动端侧边栏样式 */
.sidebar-mobile {
@apply shadow-[0_0_20px_rgba(0,0,0,0.15)];
}
.sidebar-mobile.collapsed {
transform: translateX(-100%);
}
.sidebar-menu {
@apply flex-1 overflow-y-auto py-4 px-3;
}
@@ -277,7 +301,29 @@
@apply p-6;
}
}
/* 移动端响应式样式 (768px以下) */
@media (max-width: 768px) {
/* 移动端主内容区域无左边距 */
.main-content {
@apply ml-0;
}
.main-content.sidebar-collapsed {
@apply ml-0;
}
/* 移动端侧边栏默认隐藏 */
.sidebar {
@apply w-[280px];
}
/* 移动端侧边栏折叠时完全隐藏 */
.sidebar.collapsed {
@apply w-0;
}
}
@screen md {
.sidebar-toggle {
@apply block;
+180
View File
@@ -163,4 +163,184 @@
background-position: center bottom;
background-repeat: no-repeat;
background-size: cover;
}
.logout-button {
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
background: transparent;
border: none;
color: #666;
cursor: pointer;
transition: all 0.2s;
border-radius: 4px;
}
.logout-button:hover {
background-color: rgba(0, 104, 74, 0.05);
color: #00684a;
}
.logout-button i {
font-size: 1.25rem;
}
/* ===== 移动端响应式样式 ===== */
@media (max-width: 768px) {
/* 头部样式调整 */
.header {
padding: 0.5rem 0.75rem;
}
.logo {
height: 40px;
margin-right: 0.5rem;
}
.logo-text {
font-size: 1.2rem;
font-weight: 700;
}
.logo-text-en {
font-size: 0.65rem;
margin-top: -0.15rem;
}
/* 隐藏日期时间以节省空间 */
.datetime {
display: none;
}
.user-info {
gap: 0.5rem;
}
.username {
display: none; /* 移动端隐藏用户名,只显示头像 */
}
.avatar {
width: 36px;
height: 36px;
}
/* 主内容区域调整 */
.index-main-content {
padding: 1rem 0;
}
.index-main-content-container {
width: 95%;
padding: 1rem 0;
transform: translateY(-2rem);
}
.welcome-text {
font-size: 1.3rem;
margin-bottom: 2.5rem;
padding: 0 1rem;
}
/* 模块容器改为纵向排列 */
.modules-container {
flex-direction: column;
gap: 1.25rem;
margin-bottom: 1.5rem;
align-items: center;
}
/* 模块卡片调整 */
.module-card {
width: 100%;
max-width: 340px;
height: 100px;
padding: 0 1.5rem;
gap: 1.25rem;
}
.module-card img {
width: 48px !important;
height: 48px !important;
}
.module-name {
font-size: 1.1rem;
}
.logout-button {
padding: 0.4rem;
}
.logout-button i {
font-size: 1.15rem;
}
}
/* 超小屏幕 (手机竖屏) */
@media (max-width: 480px) {
.header {
padding: 0.4rem 0.5rem;
}
.logo {
height: 36px;
}
.logo-text {
font-size: 1rem;
}
.logo-text-en {
font-size: 0.6rem;
}
.welcome-text {
font-size: 1.15rem;
margin-bottom: 2rem;
}
.module-card {
max-width: 300px;
height: 90px;
padding: 0 1.25rem;
gap: 1rem;
}
.module-card img {
width: 42px !important;
height: 42px !important;
}
.module-name {
font-size: 1rem;
}
.modules-container {
gap: 1rem;
}
}
/* 平板横屏 */
@media (min-width: 769px) and (max-width: 1024px) {
.index-main-content-container {
width: 85%;
transform: translateY(-5rem);
}
.modules-container {
gap: 2rem;
}
.module-card {
width: 260px;
height: 120px;
}
.welcome-text {
font-size: 1.75rem;
margin-bottom: 4rem;
}
}