交叉评查 创建任务流程页面优化细节

This commit is contained in:
2025-07-16 22:10:15 +08:00
parent 277c54f34d
commit d10309db99
4 changed files with 529 additions and 256 deletions
+1 -1
View File
@@ -458,7 +458,7 @@ export default function CrossCheckingIndex() {
</div>
</div>
</div>
<Button type="primary" icon="ri-add-line" to="/cross-checking/new">
<Button type="primary" icon="ri-add-line" to="/cross-checking/upload">
</Button>
</div>
+523 -250
View File
@@ -1,6 +1,6 @@
import { useState, useRef, useEffect } from "react";
import { useState, useRef } from "react";
import { type MetaFunction, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useNavigation } from "@remix-run/react";
import { Form, useNavigation, useNavigate } from "@remix-run/react";
import { UploadArea, type UploadAreaRef } from "~/components/ui/UploadArea";
import { Button } from "~/components/ui/Button";
import { messageService } from "~/components/ui/MessageModal";
@@ -50,21 +50,139 @@ export interface TreeNode {
children?: TreeNode[];
}
// 无限层级组织架构数据结构
const MOCK_TREE: TreeNode[] = [
{
label: "梅州市",
value: "梅州市",
children: [
{
label: "梅江区",
label: "梅州市烟草局", // 市级局
value: "梅州市烟草局",
children: [
{ label: "李局长", value: "梅州市-梅州市烟草局-李局长" },
{ label: "王副局长", value: "梅州市-梅州市烟草局-王副局长" },
{
label: "市场监管科", // 市级局下的科室
value: "梅州市烟草局-市场监管科",
children: [
{ label: "张科长", value: "梅州市-梅州市烟草局-市场监管科-张科长" },
{ label: "陈主任", value: "梅州市-梅州市烟草局-市场监管科-陈主任" }
]
},
{
label: "法规科",
value: "梅州市烟草局-法规科",
children: [
{ label: "刘科长", value: "梅州市-梅州市烟草局-法规科-刘科长" },
{ label: "周专员", value: "梅州市-梅州市烟草局-法规科-周专员" }
]
}
]
},
{
label: "梅江区", // 区级
value: "梅江区",
children: [
{
label: "梅江区烟草局",
value: "梅江区烟草局",
label: "梅江区烟草局", // 区级分局
value: "梅江区烟草局",
children: [
{ label: "张", value: "梅州市-梅江区-梅江区烟草局-张" },
{ label: "李", value: "梅州市-梅江区-梅江区烟草局-李" }
{ label: "张分局长", value: "梅州市-梅江区-梅江区烟草局-张分局长" },
{ label: "李副分局长", value: "梅州市-梅江区-梅江区烟草局-李副分局长" },
{
label: "执法大队", // 分局下的大队
value: "梅江区烟草分局-执法大队",
children: [
{ label: "王队长", value: "梅州市-梅江区-梅江区烟草分局-执法大队-王队长" },
{ label: "陈副队长", value: "梅州市-梅江区-梅江区烟草分局-执法大队-陈副队长" },
{
label: "第一中队", // 大队下的中队
value: "梅江区烟草分局-执法大队-第一中队",
children: [
{ label: "赵中队长", value: "梅州市-梅江区-梅江区烟草分局-执法大队-第一中队-赵中队长" },
{ label: "孙执法员", value: "梅州市-梅江区-梅江区烟草分局-执法大队-第一中队-孙执法员" },
{ label: "钱执法员", value: "梅州市-梅江区-梅江区烟草分局-执法大队-第一中队-钱执法员" }
]
},
{
label: "第二中队",
value: "梅江区烟草分局-执法大队-第二中队",
children: [
{ label: "吴中队长", value: "梅州市-梅江区-梅江区烟草分局-执法大队-第二中队-吴中队长" },
{ label: "郑执法员", value: "梅州市-梅江区-梅江区烟草分局-执法大队-第二中队-郑执法员" }
]
}
]
},
{
label: "办公室",
value: "梅江区烟草分局-办公室",
children: [
{ label: "林主任", value: "梅州市-梅江区-梅江区烟草分局-办公室-林主任" },
{ label: "黄秘书", value: "梅州市-梅江区-梅江区烟草分局-办公室-黄秘书" }
]
}
]
},
{
label: "梅江区市场监管局",
value: "梅江区市场监管局",
children: [
{ label: "刘局长", value: "梅州市-梅江区-梅江区市场监管局-刘局长" },
{ label: "周副局长", value: "梅州市-梅江区-梅江区市场监管局-周副局长" },
{
label: "执法监察科",
value: "梅江区市场监管局-执法监察科",
children: [
{ label: "谢科长", value: "梅州市-梅江区-梅江区市场监管局-执法监察科-谢科长" },
{ label: "何专员", value: "梅州市-梅江区-梅江区市场监管局-执法监察科-何专员" }
]
}
]
}
]
},
{
label: "梅县区", // 另一个区
value: "梅县区",
children: [
{
label: "梅县区烟草分局",
value: "梅县区烟草分局",
children: [
{ label: "黄分局长", value: "梅州市-梅县区-梅县区烟草分局-黄分局长" },
{ label: "林副分局长", value: "梅州市-梅县区-梅县区烟草分局-林副分局长" },
{
label: "稽查队",
value: "梅县区烟草分局-稽查队",
children: [
{ label: "吴队长", value: "梅州市-梅县区-梅县区烟草分局-稽查队-吴队长" },
{ label: "郑稽查员", value: "梅州市-梅县区-梅县区烟草分局-稽查队-郑稽查员" },
{ label: "谢稽查员", value: "梅州市-梅县区-梅县区烟草分局-稽查队-谢稽查员" }
]
}
]
}
]
},
{
label: "丰顺县", // 县级
value: "丰顺县",
children: [
{
label: "丰顺县烟草分局",
value: "丰顺县烟草分局",
children: [
{ label: "曾分局长", value: "梅州市-丰顺县-丰顺县烟草分局-曾分局长" },
{
label: "专卖管理所",
value: "丰顺县烟草分局-专卖管理所",
children: [
{ label: "邓所长", value: "梅州市-丰顺县-丰顺县烟草分局-专卖管理所-邓所长" },
{ label: "罗管理员", value: "梅州市-丰顺县-丰顺县烟草分局-专卖管理所-罗管理员" }
]
}
]
}
]
@@ -75,15 +193,160 @@ const MOCK_TREE: TreeNode[] = [
label: "揭阳市",
value: "揭阳市",
children: [
{
label: "揭阳市烟草局", // 市级局
value: "揭阳市烟草局",
children: [
{ label: "苏局长", value: "揭阳市-揭阳市烟草局-苏局长" },
{ label: "叶副局长", value: "揭阳市-揭阳市烟草局-叶副局长" },
{
label: "专卖监督管理处",
value: "揭阳市烟草局-专卖监督管理处",
children: [
{ label: "潘处长", value: "揭阳市-揭阳市烟草局-专卖监督管理处-潘处长" },
{ label: "方副处长", value: "揭阳市-揭阳市烟草局-专卖监督管理处-方副处长" }
]
}
]
},
{
label: "榕城区",
value: "榕城区",
children: [
{
label: "榕城区烟草局",
value: "榕城区烟草局",
label: "榕城区烟草局",
value: "榕城区烟草局",
children: [
{ label: "王", value: "揭阳市-榕城区-榕城区烟草局-王" }
{ label: "王分局长", value: "揭阳市-榕城区-榕城区烟草局-王分局长" },
{ label: "李明华", value: "揭阳市-榕城区-榕城区烟草分局-李明华" },
{ label: "张丽萍", value: "揭阳市-榕城区-榕城区烟草分局-张丽萍" },
{
label: "市场检查组",
value: "榕城区烟草分局-市场检查组",
children: [
{ label: "陈组长", value: "揭阳市-榕城区-榕城区烟草分局-市场检查组-陈组长" },
{ label: "林检查员", value: "揭阳市-榕城区-榕城区烟草分局-市场检查组-林检查员" }
]
}
]
},
{
label: "榕城区质监局",
value: "榕城区质监局",
children: [
{ label: "陈国强", value: "揭阳市-榕城区-榕城区质监局-陈国强" },
{ label: "林小芳", value: "揭阳市-榕城区-榕城区质监局-林小芳" }
]
}
]
},
{
label: "揭东区",
value: "揭东区",
children: [
{
label: "揭东区烟草分局",
value: "揭东区烟草分局",
children: [
{ label: "黄建军", value: "揭阳市-揭东区-揭东区烟草分局-黄建军" },
{ label: "吴秀英", value: "揭阳市-揭东区-揭东区烟草分局-吴秀英" },
{ label: "刘志华", value: "揭阳市-揭东区-揭东区烟草分局-刘志华" }
]
}
]
},
{
label: "惠来县", // 县级
value: "惠来县",
children: [
{
label: "惠来县烟草分局",
value: "惠来县烟草分局",
children: [
{ label: "杨分局长", value: "揭阳市-惠来县-惠来县烟草分局-杨分局长" },
{
label: "案件审理室",
value: "惠来县烟草分局-案件审理室",
children: [
{ label: "蔡主任", value: "揭阳市-惠来县-惠来县烟草分局-案件审理室-蔡主任" },
{ label: "郭审理员", value: "揭阳市-惠来县-惠来县烟草分局-案件审理室-郭审理员" }
]
}
]
}
]
}
]
},
{
label: "汕头市",
value: "汕头市",
children: [
{
label: "汕头市烟草局", // 市级局
value: "汕头市烟草局",
children: [
{ label: "何局长", value: "汕头市-汕头市烟草局-何局长" },
{ label: "许副局长", value: "汕头市-汕头市烟草局-许副局长" }
]
},
{
label: "龙湖区",
value: "龙湖区",
children: [
{
label: "龙湖区烟草分局",
value: "龙湖区烟草分局",
children: [
{ label: "许志明", value: "汕头市-龙湖区-龙湖区烟草分局-许志明" },
{ label: "蔡丽娜", value: "汕头市-龙湖区-龙湖区烟草分局-蔡丽娜" },
{ label: "郭建华", value: "汕头市-龙湖区-龙湖区烟草分局-郭建华" },
{ label: "何美霞", value: "汕头市-龙湖区-龙湖区烟草分局-何美霞" }
]
},
{
label: "龙湖区工商局",
value: "龙湖区工商局",
children: [
{ label: "方国庆", value: "汕头市-龙湖区-龙湖区工商局-方国庆" },
{ label: "杨小红", value: "汕头市-龙湖区-龙湖区工商局-杨小红" }
]
}
]
},
{
label: "金平区",
value: "金平区",
children: [
{
label: "金平区烟草分局",
value: "金平区烟草分局",
children: [
{ label: "邓志强", value: "汕头市-金平区-金平区烟草分局-邓志强" },
{ label: "罗美玲", value: "汕头市-金平区-金平区烟草分局-罗美玲" }
]
},
{
label: "金平区市场监管局",
value: "金平区市场监管局",
children: [
{ label: "苏建国", value: "汕头市-金平区-金平区市场监管局-苏建国" },
{ label: "叶丽华", value: "汕头市-金平区-金平区市场监管局-叶丽华" },
{ label: "潘志明", value: "汕头市-金平区-金平区市场监管局-潘志明" }
]
}
]
},
{
label: "南澳县", // 县级
value: "南澳县",
children: [
{
label: "南澳县烟草分局",
value: "南澳县烟草分局",
children: [
{ label: "陈分局长", value: "汕头市-南澳县-南澳县烟草分局-陈分局长" },
{ label: "林管理员", value: "汕头市-南澳县-南澳县烟草分局-林管理员" }
]
}
]
@@ -92,14 +355,7 @@ const MOCK_TREE: TreeNode[] = [
}
];
// 2. TreeMultiSelect递归组件
function getAllLeafValues(node: TreeNode): string[] {
if (!node.children || node.children.length === 0) return [node.value];
return node.children.flatMap(getAllLeafValues);
}
function getAllLeafValuesFromTree(tree: TreeNode[]): string[] {
return tree.flatMap(getAllLeafValues);
}
function isAllChildrenChecked(node: TreeNode, checked: string[]): boolean {
if (!node.children || node.children.length === 0) return checked.includes(node.value);
return node.children.every(child => isAllChildrenChecked(child, checked));
@@ -159,36 +415,6 @@ const TreeNodeCheckbox: React.FC<{
</div>
);
};
const TreeMultiSelect: React.FC<{
treeData: TreeNode[];
value: string[];
onChange: (v: string[]) => void;
}> = ({ treeData, value, onChange }) => {
// 递归处理选中/取消
const handleCheck = (node: TreeNode, checked: boolean) => {
const leafValues = getAllLeafValues(node);
let newValue: string[];
if (checked) {
newValue = Array.from(new Set([...value, ...leafValues]));
} else {
newValue = value.filter(v => !leafValues.includes(v));
}
onChange(newValue);
};
return (
<div>
{treeData.map(node => (
<TreeNodeCheckbox
key={node.value}
node={node}
checked={value}
onCheck={handleCheck}
/>
))}
</div>
);
};
export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.formData();
const caseType = formData.get("caseType") as string;
@@ -418,13 +644,11 @@ export default function CrossCheckingUpload() {
if (failures.length === 0) {
// 全部成功
toastService.success(`成功上传 ${successes.length} 个文件`);
// 立即清空文件列表
clearAllFiles();
messageService.success(`文件上传完成!成功上传 ${successes.length} 个文件,现在可以进行下一步操作。`, {
title: '上传成功',
confirmText: '确定',
onConfirm: () => {
// 清空文件列表
clearAllFiles();
}
confirmText: '确定'
});
} else if (successes.length === 0) {
// 全部失败
@@ -463,20 +687,19 @@ export default function CrossCheckingUpload() {
// 步骤1表单校验
const canNextStep1 = taskInfo.name.trim() && taskInfo.date.trim() && taskInfo.type.trim();
// 小组多选逻辑
useEffect(() => {
setGroupChecked(getAllLeafValuesFromTree(MOCK_TREE));
}, []);
// 小组多选逻辑 - 默认不选择任何项
// 检查是否可以完成
const canComplete = (singleFiles.length > 0 || multipleFiles.length > 0) && !isUploading;
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
const navigate = useNavigate();
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
{/* 步骤指示器 */}
{/* 步骤指示器 */
<div className="steps-indicator">
{STEPS.map((step) => (
<div
@@ -488,64 +711,52 @@ export default function CrossCheckingUpload() {
</div>
))}
</div>
}
{/* 案卷类型选择器 */}
<div className="case-type-selector">
<div className="case-type-options">
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PENALTY ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PENALTY)}
disabled={isUploading}
>
</button>
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PERMIT ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PERMIT)}
disabled={isUploading}
>
</button>
</div>
</div>
{/* 步骤1:创建任务 */}
{/* 步骤1:创建任务 */}
{currentStep === 1 && (
<div className="step-form-container">
<div className="form-group">
<label htmlFor="task-name"><span className="text-red-500">*</span></label>
<input
id="task-name"
className="form-input"
value={taskInfo.name}
onChange={e => setTaskInfo({ ...taskInfo, name: e.target.value })}
placeholder="请输入任务名称"
/>
</div>
<div className="form-group">
<label className="form-label required"></label>
<SingleDatePicker
date={taskInfo.date}
onDateChange={(value) => setTaskInfo({ ...taskInfo, date: value })}
className="w-full"
id="task-date"
placeholder="请选择日期"
/>
</div>
<div className="form-group">
<label htmlFor="task-type"></label>
<input
id="task-type"
className="form-input"
value={taskInfo.type}
onChange={e => setTaskInfo({ ...taskInfo, type: e.target.value })}
placeholder="请输入任务类型"
/>
</div>
<div className="flex justify-center mt-8">
<Button type="primary" disabled={!canNextStep1} onClick={handleNext}></Button>
<div className="flex justify-center">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6" style={{width: '1000px'}}>
<div className="form-group">
<label htmlFor="task-name"><span className="text-red-500">*</span></label>
<input
id="task-name"
className="form-input"
value={taskInfo.name}
onChange={e => setTaskInfo({ ...taskInfo, name: e.target.value })}
placeholder="请输入任务名称"
/>
</div>
<div className="form-group">
<label htmlFor="task-date" className="form-label required"></label>
<SingleDatePicker
date={taskInfo.date}
onDateChange={(value) => setTaskInfo({ ...taskInfo, date: value })}
className="w-full"
id="task-date"
placeholder="请选择日期"
/>
</div>
<div className="form-group">
<label htmlFor="task-type"></label>
<input
id="task-type"
className="form-input"
value={taskInfo.type}
onChange={e => setTaskInfo({ ...taskInfo, type: e.target.value })}
placeholder="请输入任务类型"
/>
</div>
<div className="flex justify-between items-center mt-6">
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
>
</Button>
<Button type="primary" disabled={!canNextStep1} onClick={handleNext}></Button>
</div>
</div>
</div>
)}
@@ -553,56 +764,116 @@ export default function CrossCheckingUpload() {
{/* 步骤2:创建评查小组 */}
{currentStep === 2 && (
<>
<div className="step-form-container flex flex-row justify-center items-start gap-12">
{/* 左侧树状多选 */}
<div style={{ minWidth: 260 }}>
<div className="form-group">
<label className="form-label required"></label>
<MultiCascader
options={MOCK_TREE}
placeholder="请选择评查小组"
defaultValue={groupChecked}
onChange={(values: string[]) => {
setGroupChecked(values);
}}
/>
<div className="flex justify-center">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6" style={{width: '1000px'}}>
<div className="flex flex-row justify-center gap-12">
{/* 左侧树状多选 */}
<div style={{ minWidth: 300, width: '40%' }}>
<div className="form-group">
<label htmlFor="review-group" className="form-label required"></label>
<MultiCascader
options={MOCK_TREE}
placeholder="请选择评查小组"
value={groupChecked}
onChange={(values: string[]) => {
setGroupChecked(values);
}}
/>
</div>
</div>
{/* 右侧已选择成员显示区域 */}
<div style={{ minWidth: 400, width: '50%', minHeight: 250, background: '#f9fafb', border: '1.5px solid #e5e7eb', borderRadius: 8, marginTop: '23px' }}>
<div className="p-4">
<h4 className="text-sm font-medium text-gray-700 mb-3"></h4>
{groupChecked.length > 0 ? (
<div className="space-y-2 max-h-64 overflow-y-auto">
{groupChecked.map((member, index) => {
const parts = member.split('-');
const name = parts[parts.length - 1];
const org = parts.slice(0, -1).join(' - ');
return (
<div key={index} className="bg-white p-2 rounded text-xs border">
<div className="font-medium text-gray-800">{name}</div>
<div className="text-gray-500 mt-1">{org}</div>
</div>
);
})}
</div>
) : (
<div className="text-gray-400 text-sm text-center mt-8">
<i className="ri-user-line text-2xl mb-2 block"></i>
</div>
)}
<div className="mt-4 pt-3 border-t border-gray-200">
<div className="text-xs text-gray-500">
{groupChecked.length}
</div>
</div>
</div>
</div>
</div>
{/* 按钮区域移到卡片内部 */}
<div className="flex justify-between items-center mt-6">
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
>
</Button>
<div className="flex space-x-4">
<Button type="default" onClick={handlePrev}></Button>
<Button type="primary" disabled={groupChecked.length === 0} onClick={handleNext}></Button>
</div>
</div>
</div>
{/* 右侧预留区域 */}
<div style={{ minWidth: 320, minHeight: 200, background: '#fff', border: '1.5px solid #e5e7eb', borderRadius: 8 }}></div>
</div>
<div className="flex justify-center mt-8 space-x-4">
<Button type="default" onClick={handlePrev}></Button>
<Button type="primary" disabled={groupChecked.length === 0} onClick={handleNext}></Button>
</div>
</>
)}
{/* 步骤3:原有上传区域 */}
{currentStep === 3 && (
<>
{/* 文件上传区域 */}
<Form method="post" encType="multipart/form-data">
<div className="flex justify-center">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6" style={{width: '1000px'}}>
{/* 案卷类型选择器 */}
<div className="flex justify-center mb-6">
<div>
<div className="text-sm font-medium text-gray-700 mb-3 text-center"></div>
<div className="case-type-options">
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PENALTY ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PENALTY)}
disabled={isUploading}
>
</button>
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PERMIT ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PERMIT)}
disabled={isUploading}
>
</button>
</div>
</div>
</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">
{/* 单案件导入 */}
<div className="upload-item">
<div className="upload-item-header">
<i className="upload-item-icon ri-file-text-line"></i>
<span></span>
{uploadType === 'single' && singleFiles.length > 0 && (
<Button
type="default"
size="small"
icon="ri-delete-bin-line"
onClick={() => handleClearFiles('single')}
disabled={isUploading}
>
</Button>
)}
</div>
<UploadArea
ref={singleUploadRef}
@@ -620,34 +891,6 @@ export default function CrossCheckingUpload() {
}
disabled={uploadType === 'multiple' || isUploading}
/>
{/* 单案件文件列表 */}
{singleFiles.length > 0 && (
<div className="mt-4 space-y-2">
<div className="text-sm font-medium text-gray-700">
{singleFiles.length} :
</div>
<div className="max-h-32 overflow-y-auto space-y-1">
{singleFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
<div className="flex items-center space-x-2 flex-1 min-w-0">
<i className="ri-file-pdf-line text-red-500"></i>
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'single')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
</div>
</div>
)}
</div>
{/* 多案件导入 */}
@@ -655,17 +898,6 @@ export default function CrossCheckingUpload() {
<div className="upload-item-header">
<i className="upload-item-icon ri-file-list-line"></i>
<span></span>
{uploadType === 'multiple' && multipleFiles.length > 0 && (
<Button
type="default"
size="small"
icon="ri-delete-bin-line"
onClick={() => handleClearFiles('multiple')}
disabled={isUploading}
>
</Button>
)}
</div>
<UploadArea
ref={multipleUploadRef}
@@ -683,79 +915,120 @@ export default function CrossCheckingUpload() {
}
disabled={uploadType === 'single' || isUploading}
/>
</div>
</div>
{/* 文件预览区域 */}
{(singleFiles.length > 0 || multipleFiles.length > 0) && (
<div className="mt-6 bg-green-50 border border-green-200 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div className="text-sm font-medium text-gray-700">
{uploadType === 'single' ? singleFiles.length : multipleFiles.length}
</div>
<Button
type="default"
size="small"
icon="ri-delete-bin-line"
onClick={() => handleClearFiles(uploadType === 'single' ? 'single' : 'multiple')}
disabled={isUploading}
>
</Button>
</div>
{/* 单案件文件列表 */}
{uploadType === 'single' && singleFiles.length > 0 && (
<div className="max-h-32 overflow-y-auto space-y-1">
{singleFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between bg-white p-2 rounded border">
<div className="flex items-center space-x-2 flex-1 min-w-0">
<i className="ri-file-pdf-line text-red-500"></i>
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'single')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
</div>
)}
{/* 多案件文件列表 */}
{multipleFiles.length > 0 && (
<div className="mt-4 space-y-2">
<div className="text-sm font-medium text-gray-700">
{multipleFiles.length} :
</div>
<div className="max-h-32 overflow-y-auto space-y-1">
{multipleFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
<div className="flex items-center space-x-2 flex-1 min-w-0">
<i className="ri-folder-zip-line text-orange-500"></i>
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'multiple')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
{uploadType === 'multiple' && multipleFiles.length > 0 && (
<div className="max-h-32 overflow-y-auto space-y-1">
{multipleFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between bg-white p-2 rounded border">
<div className="flex items-center space-x-2 flex-1 min-w-0">
<i className="ri-folder-zip-line text-orange-500"></i>
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
</div>
))}
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'multiple')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
</div>
)}
</div>
</div>
)}
{/* 完成按钮 */}
<div className="complete-button-container">
<button
type="button"
className="complete-button"
disabled={!canComplete}
onClick={handleCompleteUpload}
>
{isUploading || isSubmitting ? (
<>
<i className="ri-loader-4-line animate-spin mr-2"></i>
...
</>
) : (
"完成"
)}
</button>
</div>
</Form>
{/* 文件选择状态提示 */}
{!canComplete && !isUploading && (
<div className="text-center mt-4 text-gray-500 text-sm">
</div>
)}
{/* 上传进度提示 */}
{isUploading && (
<div className="text-center mt-4">
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
<div className="flex items-center justify-center text-blue-800 mb-2">
<i className="ri-loader-4-line ri-spin animate-spin text-xl mr-2"></i>
<span className="font-medium">...</span>
{/* 完成按钮 */}
<div className="flex justify-between items-center mt-8">
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
>
</Button>
<div className="flex space-x-4">
<Button type="default" onClick={handlePrev}></Button>
<Button
type="primary"
disabled={!canComplete || isUploading}
onClick={handleCompleteUpload}
>
{isUploading || isSubmitting ? "上传中..." : "开始创建任务"}
</Button>
</div>
<p className="text-sm text-blue-700">
{uploadType === 'single' ? singleFiles.length : multipleFiles.length}
</p>
</div>
</div>
)}
</>
</Form>
{/* 文件选择状态提示 */}
{!canComplete && !isUploading && (
<div className="text-center mt-4 text-gray-500 text-sm">
</div>
)}
{/* 上传进度提示 */}
{isUploading && (
<div className="text-center mt-4">
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
<div className="flex items-center justify-center text-blue-800 mb-2">
<i className="ri-loader-4-line ri-spin animate-spin text-xl mr-2"></i>
<span className="font-medium">...</span>
</div>
<p className="text-sm text-blue-700">
{uploadType === 'single' ? singleFiles.length : multipleFiles.length}
</p>
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
+2 -2
View File
@@ -45,7 +45,7 @@
--color-primary: #00684a;
--color-primary-hover: #005a3f;
--color-primary-light: rgba(0, 104, 74, 0.1);
/* 成功、警告、错误颜色 */
--color-success: #52c41a;
--color-warning: #faad14;
@@ -296,4 +296,4 @@ i[class^="ri-"],
i[class*=" ri-"] {
font-family: 'remixicon' !important;
font-style: normal !important;
}
}
+3 -3
View File
@@ -25,8 +25,8 @@
content: '';
position: absolute;
top: 25px;
left: 75px;
width: calc(100% - 50px);
left: 135px;
width: calc(100% - 70px);
height: 2px;
background-color: #e5e7eb;
z-index: 1;
@@ -340,4 +340,4 @@
.group-select-item label {
font-size: 0.95rem;
}
}
}