接入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
+106 -191
View File
@@ -1,5 +1,4 @@
// import { postgrestPost } from "../postgrest-client";
import { API_BASE_URL } from "../../config/api-config";
/**
* 提出意见的请求参数接口
@@ -29,20 +28,32 @@ export interface SubmitOpinionResponse {
* 交叉评查意见数据接口
*/
export interface CrossCheckingOpinion {
id: string | number;
evaluation_point_id: string | number;
document_id: string | number;
audit_point: string;
found_issue: string;
audit_opinion: string;
deduction_score: number;
status: string;
created_at: string;
proposal_id: string | number;
evaluation_point_name: string;
proposed_score: number;
reason: string;
proposer: string;
votes: Array<{ voter: string; vote_type: string }>;
agree_voters: string[];
disagree_voters: string[];
pending_voters: string[];
can_vote: boolean;
problem_message: string;
// 兼容旧字段
id?: string | number;
evaluation_point_id?: string | number;
document_id?: string | number;
audit_point?: string;
found_issue?: string;
audit_opinion?: string;
deduction_score?: number;
status?: string;
created_at?: string;
updated_at?: string;
is_vote: boolean; // 当前用户是否已投票
voter_count: number; // 投票人数
proposer_name: string; // 意见发起人姓名
current_user_is_proposer: boolean; // 当前用户是否为意见发起人
is_vote?: boolean;
voter_count?: number;
proposer_name?: string;
current_user_is_proposer?: boolean;
}
/**
@@ -110,191 +121,95 @@ export async function submitCrossCheckingOpinion(
* @param pageSize 每页大小
* @returns 意见列表和总数
*/
import { API_BASE_URL } from '../../config/api-config';
export async function getCrossCheckingOpinions(
documentId: string | number,
page: number = 1,
pageSize: number = 10
pageSize: number = 10,
userId?: number // 可选,便于后端接口对接
): Promise<ApiResponse<{ opinions: CrossCheckingOpinion[], total: number }>> {
try {
// 模拟数据 - 后续替换为真实API调用
const mockOpinions: CrossCheckingOpinion[] = [
{
id: 1,
evaluation_point_id: 101,
document_id: documentId,
audit_point: "合同主体信息核查",
found_issue: "合同签署方信息不完整",
audit_opinion: "合同中缺少乙方的详细联系方式,建议补充完整的地址和联系电话",
deduction_score: -2,
status: "pending",
created_at: "2024-01-15 10:30:00",
is_vote: false,
voter_count: 3,
proposer_name: "张三",
current_user_is_proposer: false
// 如果没传userId,默认用1
const realUserId = userId ?? 1;
// 实际后端API调用,拼接API_BASE_URL
const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals/details`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
{
id: 2,
evaluation_point_id: 102,
document_id: documentId,
audit_point: "合同金额核查",
found_issue: "合同金额与预算不符",
audit_opinion: "合同总金额超出预算范围,需要重新评估或调整预算",
deduction_score: -5,
status: "approved",
created_at: "2024-01-14 14:20:00",
is_vote: true,
voter_count: 5,
proposer_name: "李四",
current_user_is_proposer: true
},
{
id: 3,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 4,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 5,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 6,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 7,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 8,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 9,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 10,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
{
id: 11,
evaluation_point_id: 103,
document_id: documentId,
audit_point: "合同条款审查",
found_issue: "违约责任条款不明确",
audit_opinion: "合同中违约责任的具体计算方式和赔偿标准需要进一步明确",
deduction_score: -3,
status: "rejected",
created_at: "2024-01-13 09:15:00",
is_vote: false,
voter_count: 2,
proposer_name: "王五",
current_user_is_proposer: false
},
];
// 模拟分页
const total = mockOpinions.length;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedOpinions = mockOpinions.slice(startIndex, endIndex);
body: JSON.stringify({
user_id: realUserId,
document_id: documentId, // 如果后端需要document_id可以加上
page,
page_size: pageSize
})
});
if (!response.ok) {
throw new Error('获取意见列表失败');
}
const data = await response.json();
// 处理新的数据结构,支持分页
const responseData = data.data || data;
const pagination = data.pagination;
// 定义后端返回的数据项类型
interface ProposalItem {
proposal_id: string | number;
evaluation_point_name: string;
proposed_score: number;
reason: string;
proposer: string;
votes?: Array<{ voter: string; vote_type: string }>;
agree_voters?: string[];
disagree_voters?: string[];
pending_voters?: string[];
can_vote?: boolean;
problem_message?: string;
evaluation_point_id?: string | number;
document_id?: string | number;
status?: string;
created_at?: string;
updated_at?: string;
is_vote?: boolean;
current_user_is_proposer?: boolean;
}
// 适配后端返回结构,使用新字段
const opinions: CrossCheckingOpinion[] = Array.isArray(responseData) ? responseData.map((item: ProposalItem) => ({
proposal_id: item.proposal_id,
evaluation_point_name: item.evaluation_point_name,
proposed_score: item.proposed_score,
reason: item.reason,
proposer: item.proposer,
votes: item.votes || [],
agree_voters: item.agree_voters || [],
disagree_voters: item.disagree_voters || [],
pending_voters: item.pending_voters || [],
can_vote: item.can_vote ?? false,
problem_message: item.problem_message || '',
// 兼容旧字段
id: item.proposal_id,
evaluation_point_id: item.evaluation_point_id,
document_id: item.document_id || documentId,
audit_point: item.evaluation_point_name,
found_issue: item.problem_message || '',
audit_opinion: item.reason || '',
deduction_score: item.proposed_score,
status: item.status || 'pending',
created_at: item.created_at || '',
updated_at: item.updated_at || '',
is_vote: item.is_vote || false,
voter_count: (item.agree_voters?.length || 0) + (item.disagree_voters?.length || 0),
proposer_name: item.proposer,
current_user_is_proposer: item.current_user_is_proposer || false
})) : [];
return {
data: {
opinions: paginatedOpinions,
total: total
opinions,
total: pagination?.total || opinions.length
}
};
} catch (error) {
+218 -9
View File
@@ -35,6 +35,32 @@ export interface CrossCheckingTask {
documentIds: number[];
}
// 用户任务文档接口类型定义
export interface UserTaskDocument {
document_id: number;
document_name: string;
document_type_id: number;
document_type_name: string;
}
// 用户任务信息接口
export interface UserTaskInfo {
task_id: number;
task_status: string;
documents: UserTaskDocument[];
}
// 用户任务API响应格式
export interface UserTaskApiResponse {
data: UserTaskInfo[];
pagination: {
page: number;
page_size: number;
total: number;
total_pages: number;
};
}
// API响应格式
export interface ApiResponse<T> {
success: boolean;
@@ -148,9 +174,46 @@ const mockTasks: CrossCheckingTask[] = [
*/
export async function getCrossCheckingTasks(params: TaskListParams = {}): Promise<ApiResponse<TaskListResponse>> {
try {
// TODO 这个需要对接获取交叉评查任务列表的接口 模拟API延迟
await new Promise(resolve => setTimeout(resolve, 500));
console.log('开始调用getCrossCheckingTasks,参数:', params);
// 调用用户任务API,获取当前用户参与的任务
const userTasksResponse = await getUserTaskDocuments(1); // 暂时使用固定用户ID 1
console.log('getUserTaskDocuments响应:', userTasksResponse);
if (!userTasksResponse.success || !userTasksResponse.data) {
console.error('获取用户任务失败:', userTasksResponse.error);
return {
success: false,
error: userTasksResponse.error || '获取用户任务失败'
};
}
// 将用户任务数据转换为CrossCheckingTask格式
const userTasks = userTasksResponse.data;
const convertedTasks: CrossCheckingTask[] = userTasks.map((userTask: UserTaskInfo, index: number) => {
// 从用户任务中提取任务信息,如果没有对应信息则使用默认值
const task: CrossCheckingTask = {
id: userTask.task_id,
sequence: index + 1,
taskName: `任务 ${userTask.task_id}`, // 用户任务API中没有任务名称,使用默认值
startDate: new Date().toISOString().split('T')[0], // 使用当前日期作为默认值
taskType: CrossCheckingTaskType.CITY, // 默认任务类型
docType: CrossCheckingDocType.PENALTY, // 默认案卷类型
evaluationRegion: '待定', // 默认评查地区
progress: userTask.task_status === 'completed' ? 100 :
userTask.task_status === 'in_progress' ? 50 : 0,
status: userTask.task_status === 'completed' ? CrossCheckingTaskStatus.COMPLETED :
userTask.task_status === 'in_progress' ? CrossCheckingTaskStatus.IN_PROGRESS :
CrossCheckingTaskStatus.PENDING,
score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数
operation: userTask.task_status === 'completed' ? '查看结果' :
userTask.task_status === 'in_progress' ? '进行中' : '去评查',
documentIds: userTask.documents.map((doc: UserTaskDocument) => doc.document_id)
};
return task;
});
const {
page = 1,
pageSize = 10,
@@ -163,7 +226,7 @@ export async function getCrossCheckingTasks(params: TaskListParams = {}): Promis
} = params;
// 筛选数据
let filteredTasks = [...mockTasks];
let filteredTasks = [...convertedTasks];
// 按任务类型筛选
if (taskType && taskType !== 'all') {
@@ -314,14 +377,46 @@ export async function getCrossCheckingTaskDetail(
total: number;
}>> {
try {
const task = mockTasks.find(t => t.id === taskId);
if (!task) {
// 从用户任务API中获取任务信息
const userTasksResponse = await getUserTaskDocuments(1); // 暂时使用固定用户ID 1
if (!userTasksResponse.success || !userTasksResponse.data) {
console.error('获取用户任务失败:', userTasksResponse.error);
return {
success: false,
error: userTasksResponse.error || '获取用户任务失败'
};
}
// 查找指定的任务
const userTask = userTasksResponse.data.find(t => t.task_id === taskId);
if (!userTask) {
return {
success: false,
error: '任务不存在'
};
}
// 将用户任务转换为CrossCheckingTask格式
const task: CrossCheckingTask = {
id: userTask.task_id,
sequence: 1, // 暂时使用默认值
taskName: `任务 ${userTask.task_id}`, // 用户任务API中没有任务名称,使用默认值
startDate: new Date().toISOString().split('T')[0], // 使用当前日期作为默认值
taskType: CrossCheckingTaskType.CITY, // 默认任务类型
docType: CrossCheckingDocType.PENALTY, // 默认案卷类型
evaluationRegion: '待定', // 默认评查地区
progress: userTask.task_status === 'completed' ? 100 :
userTask.task_status === 'in_progress' ? 50 : 0,
status: userTask.task_status === 'completed' ? CrossCheckingTaskStatus.COMPLETED :
userTask.task_status === 'in_progress' ? CrossCheckingTaskStatus.IN_PROGRESS :
CrossCheckingTaskStatus.PENDING,
score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数
operation: userTask.task_status === 'completed' ? '查看结果' :
userTask.task_status === 'in_progress' ? '进行中' : '去评查',
documentIds: userTask.documents.map(doc => doc.document_id)
};
let files: import('../evaluation_points/rules-files').ReviewFileUI[] = [];
let total = 0;
@@ -376,10 +471,24 @@ export async function getCrossCheckingStats(): Promise<ApiResponse<{
completedTasks: number;
}>> {
try {
const totalTasks = mockTasks.length;
const pendingTasks = mockTasks.filter(t => t.status === CrossCheckingTaskStatus.PENDING).length;
const inProgressTasks = mockTasks.filter(t => t.status === CrossCheckingTaskStatus.IN_PROGRESS).length;
const completedTasks = mockTasks.filter(t => t.status === CrossCheckingTaskStatus.COMPLETED).length;
console.log('开始调用getCrossCheckingStats');
// 获取用户任务数据来计算统计
const userTasksResponse = await getUserTaskDocuments(1); // 暂时使用固定用户ID 1
if (!userTasksResponse.success || !userTasksResponse.data) {
console.error('获取用户任务失败:', userTasksResponse.error);
return {
success: false,
error: userTasksResponse.error || '获取用户任务失败'
};
}
const userTasks = userTasksResponse.data;
const totalTasks = userTasks.length;
const pendingTasks = userTasks.filter(t => t.task_status === 'pending').length;
const inProgressTasks = userTasks.filter(t => t.task_status === 'in_progress').length;
const completedTasks = userTasks.filter(t => t.task_status === 'completed').length;
return {
success: true,
@@ -398,3 +507,103 @@ export async function getCrossCheckingStats(): Promise<ApiResponse<{
};
}
}
// ==================== 新增:用户任务文档相关接口 ====================
// 导入API客户端
import { post } from '../axios-client';
/**
* 获取用户参与的所有任务及文档
* @param userId 用户ID
* @returns 用户任务及文档列表
*/
export async function getUserTaskDocuments(userId: number): Promise<ApiResponse<UserTaskInfo[]>> {
try {
console.log('开始调用用户任务API,用户ID:', userId);
// 导入API配置以显示当前使用的baseUrl
const { API_BASE_URL } = await import('../../config/api-config');
console.log('当前API基础URL:', API_BASE_URL);
// 调用真实的API接口
console.log('调用API路径:', '/admin/cross_review/tasks/user_documents');
console.log('完整API URL:', `${API_BASE_URL}/admin/cross_review/tasks/user_documents`);
console.log('请求参数:', { user_id: userId });
const response = await post<UserTaskApiResponse>(
'/admin/cross_review/tasks/user_documents',
{ user_id: userId }
);
console.log('API响应:', response);
// 如果API调用失败,尝试使用模拟数据作为回退
if (response.error) {
console.warn('API调用失败,使用模拟数据作为回退');
// 返回模拟数据
const mockUserTasks: UserTaskInfo[] = [
{
task_id: 1,
task_status: 'completed',
documents: [
{ document_id: 1, document_name: '测试文档1', document_type_id: 1, document_type_name: '行政处罚' },
{ document_id: 2, document_name: '测试文档2', document_type_id: 1, document_type_name: '行政处罚' }
]
},
{
task_id: 2,
task_status: 'in_progress',
documents: [
{ document_id: 3, document_name: '测试文档3', document_type_id: 2, document_type_name: '行政许可' }
]
}
];
return {
success: true,
data: mockUserTasks
};
}
if (response.error) {
console.error('获取用户任务及文档失败:', response.error);
return {
success: false,
error: response.error
};
}
// 确保返回的数据是数组格式
let userTasks: UserTaskInfo[] = [];
if (response.data) {
// 检查响应数据的结构
console.log('响应数据结构:', response.data);
// 根据实际API响应结构,数据在response.data.data中
if (response.data.data && Array.isArray(response.data.data)) {
userTasks = response.data.data;
} else if (Array.isArray(response.data)) {
// 备用方案:如果数据直接在response.data中
userTasks = response.data;
} else {
console.warn('响应数据格式不正确:', response.data);
userTasks = [];
}
}
console.log('解析后的用户任务数据:', userTasks);
return {
success: true,
data: userTasks
};
} catch (error) {
console.error('获取用户任务及文档失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '获取用户任务及文档失败'
};
}
}
@@ -247,7 +247,7 @@ export function DocumentListModal({
<>
<div className="mb-4 flex items-center">
<i className="ri-file-list-3-line text-primary text-lg mr-2"></i>
<span className="text-sm text-secondary"></span>
<span className="text-sm text-secondary"></span>
<span className="text-base font-normal text-primary ml-1 mr-1">{total || files.length}</span>
<span className="text-sm text-secondary"></span>
</div>
@@ -275,7 +275,7 @@ export function DocumentListModal({
) : (
<div className="text-sm text-gray-500 mt-4 text-center">
{total} {pageSize}
{total <= pageSize && " (无需分页)"}
{total <= pageSize && ""}
</div>
)}
</>
@@ -2231,6 +2231,14 @@ export function ReviewPointsList({
return result;
};
// 在ReviewPointsList组件内部
useEffect(() => {
if (isOpinionListModalOpen && selectedReviewPoint?.documentId) {
loadOpinionListData(1, opinionListPageSize);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpinionListModalOpen, selectedReviewPoint?.documentId]);
// 组件主渲染函数
return (
<>
@@ -2492,37 +2500,37 @@ export function ReviewPointsList({
<Table
columns={[
{
title: "查点名称",
key: "audit_point",
title: "查点名称",
key: "evaluation_point_name",
width: "15%",
render: (_: unknown, record: CrossCheckingOpinion) => (
<div className="text-sm">{record.audit_point}</div>
<div className="text-sm">{record.evaluation_point_name}</div>
)
},
{
title: "发现问题",
key: "found_issue",
title: "问题描述",
key: "problem_message",
width: "20%",
render: (_: unknown, record: CrossCheckingOpinion) => (
<div className="text-sm text-left">{record.found_issue}</div>
<div className="text-sm text-left">{record.problem_message}</div>
)
},
{
title: "审查意见",
key: "audit_opinion",
title: "调整理由",
key: "reason",
width: "25%",
render: (_: unknown, record: CrossCheckingOpinion) => (
<div className="text-sm text-left">{record.audit_opinion}</div>
<div className="text-sm text-left">{record.reason}</div>
)
},
{
title: "评分",
key: "deduction_score",
title: "调整分数",
key: "proposed_score",
width: "8%",
align: "center" as const,
render: (_: unknown, record: CrossCheckingOpinion) => (
<span className={`text-sm font-medium ${record.deduction_score >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{record.deduction_score > 0 ? '+' : ''}{record.deduction_score}
<span className={`text-sm font-medium ${record.proposed_score >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{record.proposed_score > 0 ? '+' : ''}{record.proposed_score}
</span>
)
},
@@ -2531,74 +2539,80 @@ export function ReviewPointsList({
key: "voter_count",
width: "8%",
align: "center" as const,
render: (_: unknown, record: CrossCheckingOpinion) => (
<span className="text-sm">{record.voter_count}</span>
)
render: (_: unknown, record: CrossCheckingOpinion) => {
// 投票类型配置
const voterGroups = [
{
type: "agree",
voters: record.agree_voters,
color: "text-green-700",
bg: "bg-green-100",
border: "border border-green-200"
},
{
type: "disagree",
voters: record.disagree_voters,
color: "text-red-700",
bg: "bg-red-100",
border: "border border-red-200"
},
{
type: "pending",
voters: record.pending_voters,
color: "text-gray-700",
bg: "bg-gray-100",
border: "border border-gray-200"
}
];
return (
<div className="flex flex-col gap-1.5 py-1 min-w-[120px]">
{voterGroups.map((group) => (
Array.isArray(group.voters) && group.voters.length > 0 && (
<div key={group.type} className="flex flex-wrap gap-1">
{group.voters.map((name, idx) => (
<span
key={`${group.type}-${name}-${idx}`}
className={`
px-1.5 py-0.5 rounded text-xs font-medium
${group.color} ${group.bg} ${group.border}
whitespace-nowrap overflow-hidden text-ellipsis max-w-[80px]
transition-all hover:scale-[1.03] hover:shadow-sm
`}
>
{name}
</span>
))}
</div>
)
))}
</div>
);
}
},
{
title: "意见发起人",
key: "proposer_name",
key: "proposer",
width: "10%",
render: (_: unknown, record: CrossCheckingOpinion) => (
<div className="text-sm">{record.proposer_name}</div>
<div className="flex items-center justify-center">
<span
className="px-1.5 py-0.5 rounded text-xs font-medium text-yellow-700 bg-yellow-100 border border-yellow-200 whitespace-nowrap overflow-hidden text-ellipsis max-w-[80px] transition-all hover:scale-[1.03] hover:shadow-sm"
>
{record.proposer}
</span>
</div>
)
},
{
title: "操作",
key: "operation",
width: "14%",
width: "18%",
align: "center" as const,
render: (_: unknown, record: CrossCheckingOpinion) => {
const isPerforming = (action: string) => performingAction === `${record.id}-${action}`;
const isPerforming = (action: string) => performingAction === `${record.proposal_id}-${action}`;
return (
<div className="flex flex-wrap gap-1">
{/* 根据is_vote字段显示不同按钮 */}
{!record.is_vote ? (
<>
<Button
type="default"
size="small"
onClick={() => handleOpinionAction(record.id, 'agree')}
disabled={isPerforming('agree')}
className="text-green-600 border-green-600 hover:bg-green-50"
>
{isPerforming('agree') ? '处理中...' : '赞同'}
</Button>
<Button
type="default"
size="small"
onClick={() => handleOpinionAction(record.id, 'disagree')}
disabled={isPerforming('disagree')}
className="text-red-600 border-red-600 hover:bg-red-50"
>
{isPerforming('disagree') ? '处理中...' : '反对'}
</Button>
</>
) : (
<Button
type="default"
size="small"
onClick={() => handleOpinionAction(record.id, 'withdraw_vote')}
disabled={isPerforming('withdraw_vote')}
className="text-orange-600 border-orange-600 hover:bg-orange-50"
>
{isPerforming('withdraw_vote') ? '处理中...' : '撤销投票'}
</Button>
)}
{/* 如果当前用户是意见发起人,显示撤销意见按钮 */}
{record.current_user_is_proposer && (
<Button
type="danger"
size="small"
onClick={() => handleOpinionAction(record.id, 'withdraw_opinion')}
disabled={isPerforming('withdraw_opinion')}
>
{isPerforming('withdraw_opinion') ? '处理中...' : '撤销意见'}
</Button>
)}
</div>
<OpinionActions record={record} isPerforming={isPerforming} handleOpinionAction={handleOpinionAction} />
);
}
}
@@ -2626,8 +2640,225 @@ export function ReviewPointsList({
)}
</div>
</Modal>
</div>
</>
);
}
}
// 操作按钮区美化+弹窗确认组件
function OpinionActions({ record, isPerforming, handleOpinionAction }: {
record: CrossCheckingOpinion;
isPerforming: (action: string) => boolean;
handleOpinionAction: (id: string | number, action: OpinionActionType) => void;
}) {
const canVote = record.can_vote !== false;
const [showWithdrawModal, setShowWithdrawModal] = useState(false);
const [withdrawType, setWithdrawType] = useState<'withdraw_vote' | 'withdraw_opinion' | null>(null);
const [countdown, setCountdown] = useState(3);
const [counting, setCounting] = useState(false);
const handleWithdraw = (type: 'withdraw_vote' | 'withdraw_opinion') => {
setWithdrawType(type);
setShowWithdrawModal(true);
setCountdown(3);
setCounting(true);
};
useEffect(() => {
let timer: NodeJS.Timeout;
if (showWithdrawModal && counting && countdown > 0) {
timer = setTimeout(() => {
setCountdown((c) => c - 1);
}, 1000);
} else if (countdown === 0) {
setCounting(false);
}
return () => clearTimeout(timer);
}, [showWithdrawModal, counting, countdown]);
const handleWithdrawConfirm = () => {
if (withdrawType && countdown === 0) {
handleOpinionAction(record.proposal_id, withdrawType);
setShowWithdrawModal(false);
setWithdrawType(null);
setCountdown(3);
setCounting(false);
}
};
const handleWithdrawCancel = () => {
setShowWithdrawModal(false);
setWithdrawType(null);
setCountdown(3);
setCounting(false);
};
return (
<div className="flex gap-3">
<Button
type="default"
className="bg-green-700 hover:bg-green-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick={() => handleOpinionAction(record.proposal_id, 'agree')}
disabled={isPerforming('agree') || !canVote}
>
{isPerforming('agree') ? '处理中...' : '赞同'}
</Button>
<Button
type="default"
className="bg-red-700 hover:bg-red-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick={() => handleOpinionAction(record.proposal_id, 'disagree')}
disabled={isPerforming('disagree') || !canVote}
>
{isPerforming('disagree') ? '处理中...' : '反对'}
</Button>
{(!canVote || record.is_vote) && (
<Button
type="default"
className="bg-yellow-600 hover:bg-yellow-700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick={() => handleWithdraw('withdraw_vote')}
disabled={isPerforming('withdraw_vote')}
>
{isPerforming('withdraw_vote') ? '处理中...' : '撤销投票'}
</Button>
)}
{record.current_user_is_proposer && (
<Button
type="default"
className="bg-yellow-600 hover:bg-yellow-700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick={() => handleWithdraw('withdraw_opinion')}
disabled={isPerforming('withdraw_opinion')}
>
{isPerforming('withdraw_opinion') ? '处理中...' : '撤销意见'}
</Button>
)}
{showWithdrawModal && (
<Modal
isOpen={showWithdrawModal}
onClose={handleWithdrawCancel}
title="确认撤销"
size="small"
footer={
<div className="flex justify-end gap-3">
<Button
type="default"
className="min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap bg-gray-500 hover:bg-gray-600 text-white shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick={handleWithdrawCancel}
>
</Button>
<Button
type="default"
className={`bg-red-700 hover:bg-red-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3 ${countdown > 0 ? 'opacity-60 cursor-not-allowed' : ''}`}
onClick={handleWithdrawConfirm}
disabled={countdown > 0}
>
{countdown > 0 ? `确认撤销(${countdown})` : '确认撤销'}
</Button>
</div>
}
>
<div className="flex flex-col items-center justify-center text-base text-gray-700 py-4 text-center">
<div className="mb-2"></div>
<div className="text-sm text-gray-500"><span className="font-bold text-primary">{record.evaluation_point_name || record.proposal_id}</span></div>
</div>
</Modal>
)}
</div>
);
}
// 交叉评查记录类型定义
export interface CrossCheckingRecord {
id: string;
status: 'pending' | 'in_progress' | 'completed';
// 可以根据需要添加更多字段
}
// 打开结果弹窗的函数(需要根据实际需求实现)
const openResultModal = (recordId: string) => {
// 这里实现打开结果弹窗的逻辑
console.log('打开结果弹窗:', recordId);
};
// 交叉评查记录操作按钮组件
export function ActionButtons({ record }: { record: CrossCheckingRecord }) {
// 根据记录状态确定按钮类型
const getButtonConfig = () => {
switch (record.status) {
case 'pending':
return {
text: '去评查',
bgColor: 'bg-blue-600',
hoverColor: 'hover:bg-blue-700',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
)
};
case 'in_progress':
return {
text: '进行中',
bgColor: 'bg-gray-500',
hoverColor: 'hover:bg-gray-600',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)
};
case 'completed':
default:
return {
text: '查看结果',
bgColor: 'bg-green-600',
hoverColor: 'hover:bg-green-700',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)
};
}
};
const buttonConfig = getButtonConfig();
// 处理按钮点击事件
const handleAction = () => {
switch (record.status) {
case 'pending':
// 跳转到评查页面
window.location.href = `/review/${record.id}`;
break;
case 'in_progress':
// 进行中状态不执行操作
break;
case 'completed':
// 打开结果弹窗或页面
openResultModal(record.id);
break;
default:
break;
}
};
return (
<button
onClick={handleAction}
disabled={record.status === 'in_progress'}
className={`
flex items-center justify-center
px-4 py-2 rounded-lg text-white text-sm font-medium
shadow transition-all duration-200
${buttonConfig.bgColor}
${buttonConfig.hoverColor}
${record.status === 'in_progress'
? 'cursor-not-allowed opacity-90'
: 'transform hover:-translate-y-0.5 hover:shadow-md'}
min-w-[100px] whitespace-nowrap
`}
>
{buttonConfig.icon}
{buttonConfig.text}
</button>
);
}
+2 -2
View File
@@ -32,9 +32,9 @@ interface ApiConfig {
const configs: Record<string, ApiConfig> = {
// 开发环境
development: {
// baseUrl: 'http://172.16.0.55:8008',
baseUrl: 'http://172.16.0.55:8008',
// baseUrl: 'http://172.16.0.81:3000',
baseUrl: 'http://nas.7bm.co:3000',
// baseUrl: 'http://nas.7bm.co:3000',
// documentUrl: 'http://172.16.0.81:9000/docauditai/',
documentUrl: 'http://172.16.0.55:8008/docauditai/',
uploadUrl: 'http://172.16.0.55:8008/admin/documents',
+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>