接入feat(cross-checking): 整合组织架构数据并优化意见列表功能

- 更新 API 配置,使用新的后端服务地址- 移除前端模拟数据,改为从后端获取真实数据- 优化意见列表接口,支持分页和用户身份验证
- 调整前端界面,适应新的数据结构和功能需求
This commit is contained in:
2025-07-20 21:29:42 +08:00
parent e4ce41cebe
commit 4d5ec6cdb7
7 changed files with 758 additions and 597 deletions
+2 -2
View File
@@ -653,8 +653,8 @@ export default function CrossCheckingIndex() {
pageSize={pageSize}
onChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
showTotal={true}
showPageSizeChanger={true}
showTotal={false}
showPageSizeChanger={false}
pageSizeOptions={[10, 20, 30, 50]}
/>
)}
+127 -321
View File
@@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { type MetaFunction, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useNavigation, useNavigate } from "@remix-run/react";
import { UploadArea, type UploadAreaRef } from "~/components/ui/UploadArea";
@@ -16,6 +16,10 @@ import {
formatFileSize,
batchUploadCrossCheckingFiles
} from "~/api/cross-checking/cross-files-upload";
import {
getOrganizationTree,
convertToTreeData
} from "~/api/user";
import React from "react"; // Added for React.useState
export const meta: MetaFunction = () => {
@@ -50,310 +54,15 @@ export interface TreeNode {
children?: TreeNode[];
}
// 无限层级组织架构数据结构
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: "梅州市-梅州市烟草局-法规科-周专员" }
]
}
]
},
{
label: "梅江区", // 区级
value: "梅江区",
children: [
{
label: "梅江区烟草分局", // 区级分局
value: "梅江区烟草分局",
children: [
{ 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: "梅州市-丰顺县-丰顺县烟草分局-专卖管理所-罗管理员" }
]
}
]
}
]
}
]
},
{
label: "揭阳市",
value: "揭阳市",
children: [
{
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: "揭阳市-榕城区-榕城区烟草分局-市场检查组-陈组长" },
{ 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: "汕头市-南澳县-南澳县烟草分局-林管理员" }
]
}
]
}
]
}
];
// 默认的空组织架构数据(作为备用)
const DEFAULT_TREE: TreeNode[] = [];
// 用户选择状态管理
interface UserSelectionState {
treeData: TreeNode[];
loading: boolean;
error: string | null;
}
function isAllChildrenChecked(node: TreeNode, checked: string[]): boolean {
@@ -440,6 +149,11 @@ export default function CrossCheckingUpload() {
});
// 步骤2状态
const [groupChecked, setGroupChecked] = useState<string[]>([]);
const [userSelectionState, setUserSelectionState] = useState<UserSelectionState>({
treeData: DEFAULT_TREE,
loading: false,
error: null
});
// 上传配置状态 - 设置默认值
const [priority] = useState<string>("normal");
@@ -696,6 +410,65 @@ export default function CrossCheckingUpload() {
const navigate = useNavigate();
// 加载组织架构数据
useEffect(() => {
const loadOrganizationData = async () => {
// 只在步骤2且数据为空且未在加载时执行
if (currentStep === 2 && userSelectionState.treeData.length === 0 && !userSelectionState.loading) {
setUserSelectionState(prev => ({ ...prev, loading: true, error: null }));
try {
console.log('开始加载组织架构数据');
const response = await getOrganizationTree(true);
if (response.success && response.data) {
console.log('原始API数据:', response.data);
const treeData = convertToTreeData(response.data.organizations);
console.log('转换后的树形数据:', treeData);
// 验证数据转换是否正确
treeData.forEach(org => {
console.log(`组织: ${org.label} (${org.value})`);
if (org.children) {
org.children.forEach(child => {
if (child.isUser) {
console.log(` - 用户: ${child.label} (${child.value})`);
} else {
console.log(` - 子组织: ${child.label} (${child.value})`);
}
});
}
});
setUserSelectionState({
treeData,
loading: false,
error: null
});
} else {
console.error('获取组织架构失败:', response.error);
setUserSelectionState({
treeData: DEFAULT_TREE,
loading: false,
error: response.error || '获取组织架构失败'
});
toastService.error('获取组织架构失败,请刷新页面重试');
}
} catch (error) {
console.error('加载组织架构数据失败:', error);
setUserSelectionState({
treeData: DEFAULT_TREE,
loading: false,
error: error instanceof Error ? error.message : '加载组织架构数据失败'
});
toastService.error('加载组织架构数据失败,请刷新页面重试');
}
}
};
loadOrganizationData();
}, [currentStep]); // 只依赖 currentStep,避免无限循环
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
@@ -751,7 +524,10 @@ export default function CrossCheckingUpload() {
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
onClick={() => {
console.log('点击返回列表按钮');
navigate('/cross-checking');
}}
>
</Button>
@@ -771,14 +547,26 @@ export default function CrossCheckingUpload() {
<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);
}}
/>
{userSelectionState.loading ? (
<div className="flex items-center justify-center p-4 border border-gray-200 rounded-md bg-gray-50">
<i className="ri-loader-4-line ri-spin animate-spin text-xl mr-2 text-blue-600"></i>
<span className="text-gray-600">...</span>
</div>
) : userSelectionState.error ? (
<div className="flex items-center justify-center p-4 border border-red-200 rounded-md bg-red-50">
<i className="ri-error-warning-line text-xl mr-2 text-red-600"></i>
<span className="text-red-600">: {userSelectionState.error}</span>
</div>
) : (
<MultiCascader
options={userSelectionState.treeData}
placeholder="请选择评查小组成员"
value={groupChecked}
onChange={(values: string[]) => {
setGroupChecked(values);
}}
/>
)}
</div>
</div>
{/* 右侧已选择成员显示区域 */}
@@ -788,13 +576,25 @@ export default function CrossCheckingUpload() {
{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(' - ');
// 处理用户选择值,支持新的API格式
let displayName = member;
let displayOrg = '';
if (member.startsWith('user_')) {
// 用户选择,格式为 user_123
displayName = `用户ID: ${member.replace('user_', '')}`;
displayOrg = '用户';
} else {
// 组织选择,格式为 ou_id 或 ou_id-ou_id
const parts = member.split('-');
displayName = parts[parts.length - 1];
displayOrg = 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 className="font-medium text-gray-800">{displayName}</div>
<div className="text-gray-500 mt-1">{displayOrg}</div>
</div>
);
})}
@@ -819,7 +619,10 @@ export default function CrossCheckingUpload() {
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
onClick={() => {
console.log('点击返回列表按钮');
navigate('/cross-checking');
}}
>
</Button>
@@ -989,7 +792,10 @@ export default function CrossCheckingUpload() {
<Button
type="default"
icon="ri-arrow-left-line"
onClick={() => navigate('/cross-checking')}
onClick={() => {
console.log('点击返回列表按钮');
navigate('/cross-checking');
}}
>
</Button>