From d10309db997cf5ec92fb8fef0a0caa05be503fcf Mon Sep 17 00:00:00 2001 From: Wren Date: Wed, 16 Jul 2025 22:10:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A4=E5=8F=89=E8=AF=84=E6=9F=A5=20?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E4=BB=BB=E5=8A=A1=E6=B5=81=E7=A8=8B=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=BC=98=E5=8C=96=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/cross-checking._index.tsx | 2 +- app/routes/cross-checking.upload.tsx | 773 ++++++++++++++------- app/styles/main.css | 4 +- app/styles/pages/cross-checking-upload.css | 6 +- 4 files changed, 529 insertions(+), 256 deletions(-) diff --git a/app/routes/cross-checking._index.tsx b/app/routes/cross-checking._index.tsx index 802e534..081b6e0 100644 --- a/app/routes/cross-checking._index.tsx +++ b/app/routes/cross-checking._index.tsx @@ -458,7 +458,7 @@ export default function CrossCheckingIndex() { - diff --git a/app/routes/cross-checking.upload.tsx b/app/routes/cross-checking.upload.tsx index 7bf8de0..258b5c1 100644 --- a/app/routes/cross-checking.upload.tsx +++ b/app/routes/cross-checking.upload.tsx @@ -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<{ ); }; -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 ( -
- {treeData.map(node => ( - - ))} -
- ); -}; - 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 (
- {/* 步骤指示器 */} + {/* 步骤指示器 */
{STEPS.map((step) => (
))}
+ } - {/* 案卷类型选择器 */} -
-
- - -
-
- - {/* 步骤1:创建任务 */} + {/* 步骤1:创建任务 */} {currentStep === 1 && ( -
-
- - setTaskInfo({ ...taskInfo, name: e.target.value })} - placeholder="请输入任务名称" - /> -
-
- - setTaskInfo({ ...taskInfo, date: value })} - className="w-full" - id="task-date" - placeholder="请选择日期" - /> -
-
- - setTaskInfo({ ...taskInfo, type: e.target.value })} - placeholder="请输入任务类型" - /> -
-
- +
+
+
+ + setTaskInfo({ ...taskInfo, name: e.target.value })} + placeholder="请输入任务名称" + /> +
+
+ + setTaskInfo({ ...taskInfo, date: value })} + className="w-full" + id="task-date" + placeholder="请选择日期" + /> +
+
+ + setTaskInfo({ ...taskInfo, type: e.target.value })} + placeholder="请输入任务类型" + /> +
+
+ + +
)} @@ -553,56 +764,116 @@ export default function CrossCheckingUpload() { {/* 步骤2:创建评查小组 */} {currentStep === 2 && ( <> -
- {/* 左侧树状多选 */} -
-
- - { - setGroupChecked(values); - }} - /> +
+
+
+ {/* 左侧树状多选 */} +
+
+ + { + setGroupChecked(values); + }} + /> +
+
+ {/* 右侧已选择成员显示区域 */} +
+
+

已选择的评查小组成员

+ {groupChecked.length > 0 ? ( +
+ {groupChecked.map((member, index) => { + const parts = member.split('-'); + const name = parts[parts.length - 1]; + const org = parts.slice(0, -1).join(' - '); + return ( +
+
{name}
+
{org}
+
+ ); + })} +
+ ) : ( +
+ + 暂未选择评查小组成员 +
+ )} +
+
+ 共选择 {groupChecked.length} 名成员 +
+
+
+
+
+ + {/* 按钮区域移到卡片内部 */} +
+ +
+ + +
- {/* 右侧预留区域 */} -
-
-
- -
)} {/* 步骤3:原有上传区域 */} {currentStep === 3 && ( - <> - {/* 文件上传区域 */} -
+
+
+ {/* 案卷类型选择器 */} +
+
+
选择案卷类型
+
+ + +
+
+
+ + {/* 文件上传区域 */} + + {/* 上传框区域 */}
{/* 单案件导入 */}
单案件导入 - {uploadType === 'single' && singleFiles.length > 0 && ( - - )}
- - {/* 单案件文件列表 */} - {singleFiles.length > 0 && ( -
-
- 已选择 {singleFiles.length} 个文件: -
-
- {singleFiles.map((file) => ( -
-
- - {file.name} - {formatFileSize(file.size)} -
- -
- ))} -
-
- )}
{/* 多案件导入 */} @@ -655,17 +898,6 @@ export default function CrossCheckingUpload() {
多案件导入 - {uploadType === 'multiple' && multipleFiles.length > 0 && ( - - )}
+
+
+ + {/* 文件预览区域 */} + {(singleFiles.length > 0 || multipleFiles.length > 0) && ( +
+
+
+ 已选择 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件 +
+ +
+ + {/* 单案件文件列表 */} + {uploadType === 'single' && singleFiles.length > 0 && ( +
+ {singleFiles.map((file) => ( +
+
+ + {file.name} + {formatFileSize(file.size)} +
+ +
+ ))} +
+ )} {/* 多案件文件列表 */} - {multipleFiles.length > 0 && ( -
-
- 已选择 {multipleFiles.length} 个压缩包: -
-
- {multipleFiles.map((file) => ( -
-
- - {file.name} - {formatFileSize(file.size)} -
- + {uploadType === 'multiple' && multipleFiles.length > 0 && ( +
+ {multipleFiles.map((file) => ( +
+
+ + {file.name} + {formatFileSize(file.size)}
- ))} -
+ +
+ ))}
)}
-
+ )} - {/* 完成按钮 */} -
- -
- - - {/* 文件选择状态提示 */} - {!canComplete && !isUploading && ( -
- 请至少选择一种导入方式的文件 -
- )} - - {/* 上传进度提示 */} - {isUploading && ( -
-
-
- - 正在上传文件... + {/* 完成按钮 */} +
+ +
+ +
-

- 正在上传 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候 -

-
- )} - + + + {/* 文件选择状态提示 */} + {!canComplete && !isUploading && ( +
+ 请至少选择一种导入方式的文件 +
+ )} + + {/* 上传进度提示 */} + {isUploading && ( +
+
+
+ + 正在上传文件... +
+

+ 正在上传 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候 +

+
+
+ )} +
+
)}
diff --git a/app/styles/main.css b/app/styles/main.css index 0c7c161..2d91c53 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -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; -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/styles/pages/cross-checking-upload.css b/app/styles/pages/cross-checking-upload.css index 91e098d..d257f91 100644 --- a/app/styles/pages/cross-checking-upload.css +++ b/app/styles/pages/cross-checking-upload.css @@ -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; } -} \ No newline at end of file +} \ No newline at end of file