From 1ba2f3d3223644d2eb6081d825db76270c44ff12 Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Tue, 17 Mar 2026 13:37:39 +0800
Subject: [PATCH] fix: 修复caseState类型比较问题,使用Number()转换确保字符串和数字类型都能正确匹配
---
/dev/null | 149 -------------------------------------------------
web-app/src/components/dashboard/TabContainer.jsx | 20 ++++--
web-app/src/contexts/CaseDataContext.jsx | 11 +++
3 files changed, 23 insertions(+), 157 deletions(-)
diff --git "a/AI\350\260\203\350\247\243\347\212\266\346\200\201\346\216\247\345\210\266\345\212\237\350\203\275\350\207\252\346\265\213\346\212\245\345\221\212.md" "b/AI\350\260\203\350\247\243\347\212\266\346\200\201\346\216\247\345\210\266\345\212\237\350\203\275\350\207\252\346\265\213\346\212\245\345\221\212.md"
deleted file mode 100644
index 7218b4d..0000000
--- "a/AI\350\260\203\350\247\243\347\212\266\346\200\201\346\216\247\345\210\266\345\212\237\350\203\275\350\207\252\346\265\213\346\212\245\345\221\212.md"
+++ /dev/null
@@ -1,78 +0,0 @@
-# AI调解状态控制功能自测清单
-
-## 🔧 已修复的BUG
-
-### 1. 按钮显示逻辑问题 ✅
-- **问题**: `getControlButtonProps()`返回null时导致渲染错误
-- **修复**: 添加安全检查,先获取按钮属性再渲染
-- **验证**: 按钮现在能正常显示和隐藏
-
-### 2. 错误处理增强 ✅
-- **问题**: API调用失败时错误信息不够清晰
-- **修复**:
- - 添加参数验证(案件ID不能为空)
- - 更详细的错误分类和提示
- - 网络错误时提供额外的帮助信息
-- **验证**: 各种错误场景都有相应的用户友好提示
-
-### 3. 状态管理优化 ✅
-- **问题**: 确认后状态清理不彻底
-- **修复**: 确认操作完成后清空`controlAction`状态
-- **验证**: 避免状态残留导致的意外行为
-
-### 4. 调试能力提升 ✅
-- **问题**: 难以追踪问题根源
-- **修复**: 添加详细的console日志记录
-- **验证**: 开发者控制台可以看到完整的执行流程
-
-## 🧪 功能验证项
-
-### 基础显示功能
-- [ ] 当`caseState === 0`时显示蓝色"终止"按钮
-- [ ] 当`caseState === 1`时显示蓝色"终止"按钮
-- [ ] 当`caseState === 5`时显示绿色"恢复"按钮
-- [ ] 其他状态下不显示按钮
-
-### 交互功能
-- [ ] 点击按钮显示对应的操作确认对话框
-- [ ] 对话框标题正确显示("确认终止调解"/"确认恢复调解")
-- [ ] 对话框内容描述准确
-- [ ] 可以输入备注信息
-- [ ] 点击"确定"触发API调用
-- [ ] 点击"取消"关闭对话框
-
-### API调用功能
-- [ ] 正确传递案件ID参数
-- [ ] 正确传递操作类型(0-终止,1-恢复)
-- [ ] 正确传递操作人姓名
-- [ ] 正确传递备注信息
-- [ ] API调用成功后显示成功消息
-- [ ] API调用失败后显示错误消息
-
-### 状态管理
-- [ ] 操作期间按钮显示loading状态
-- [ ] 操作期间按钮被禁用
-- [ ] 操作完成后正确刷新数据
-- [ ] 对话框正确关闭
-- [ ] 输入框内容正确清空
-
-## 🐛 已知限制
-
-### 环境配置
-- 当前使用DEV环境配置,baseURL为'http://localhost:9015'
-- 实际部署时需要根据环境调整baseURL配置
-
-### Mock数据
-- 本地测试使用mock数据,实际API调用可能需要后端支持
-- 建议在真实环境中进行端到端测试
-
-## 📋 测试建议
-
-1. **开发者工具验证**: 打开浏览器控制台查看日志输出
-2. **不同状态测试**: 修改mock数据中的caseState值测试各种场景
-3. **网络异常测试**: 模拟网络断开等情况测试错误处理
-4. **用户操作测试**: 完整的操作流程测试用户体验
-
-## ✅ 自测结论
-
-功能已基本完善,主要修复了按钮显示逻辑的核心BUG,增强了错误处理能力和调试支持。建议在真实环境中进行最终验证。
\ No newline at end of file
diff --git a/web-app/src/__tests__/mediationStateControl.test.js b/web-app/src/__tests__/mediationStateControl.test.js
deleted file mode 100644
index 9f07b58..0000000
--- a/web-app/src/__tests__/mediationStateControl.test.js
+++ /dev/null
@@ -1,149 +0,0 @@
-import React from 'react';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { message } from 'antd';
-import TabContainer from '../components/dashboard/TabContainer';
-import ProcessAPIService from '../services/ProcessAPIService';
-import { getMergedParams } from '../utils/urlParams';
-
-// Mock依赖
-jest.mock('../services/ProcessAPIService');
-jest.mock('../utils/urlParams');
-jest.mock('antd', () => ({
- message: {
- success: jest.fn(),
- error: jest.fn()
- },
- Spin: ({ children }) => <div data-testid="spin">{children}</div>,
- Modal: ({ children, visible, onOk, onCancel, title, confirmLoading }) => (
- visible ? (
- <div data-testid="modal">
- <h3>{title}</h3>
- {children}
- <button onClick={onOk} disabled={confirmLoading}>确定</button>
- <button onClick={onCancel}>取消</button>
- </div>
- ) : null
- )
-}));
-
-jest.mock('../contexts/CaseDataContext', () => ({
- useCaseData: () => ({
- caseData: {
- mediation: {
- state: 0, // 测试终止状态
- id: 'test-mediation-id'
- }
- },
- refreshData: jest.fn()
- })
-}));
-
-describe('AI调解状态控制功能测试', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- getMergedParams.mockReturnValue({ caseId: 'test-case-id' });
- });
-
- test('应该显示终止按钮当caseState为0', () => {
- render(<TabContainer activeTab="mediation-board" />);
-
- // 等待组件渲染完成
- expect(screen.getByText('终止')).toBeInTheDocument();
- });
-
- test('应该显示恢复按钮当caseState为5', () => {
- // 修改mock数据
- jest.mock('../contexts/CaseDataContext', () => ({
- useCaseData: () => ({
- caseData: {
- mediation: {
- state: 5, // 恢复状态
- id: 'test-mediation-id'
- }
- },
- refreshData: jest.fn()
- })
- }));
-
- render(<TabContainer activeTab="mediation-board" />);
-
- expect(screen.getByText('恢复')).toBeInTheDocument();
- });
-
- test('点击终止按钮应该显示确认对话框', () => {
- render(<TabContainer activeTab="mediation-board" />);
-
- const terminateButton = screen.getByText('终止');
- fireEvent.click(terminateButton);
-
- expect(screen.getByTestId('modal')).toBeInTheDocument();
- expect(screen.getByText('确认终止调解')).toBeInTheDocument();
- });
-
- test('确认终止应该调用API并显示成功消息', async () => {
- ProcessAPIService.updateMediationState.mockResolvedValue({ code: 200 });
-
- render(<TabContainer activeTab="mediation-board" />);
-
- // 点击终止按钮
- const terminateButton = screen.getByText('终止');
- fireEvent.click(terminateButton);
-
- // 点击确认按钮
- const confirmButton = screen.getByText('确定');
- fireEvent.click(confirmButton);
-
- // 验证API调用
- await waitFor(() => {
- expect(ProcessAPIService.updateMediationState).toHaveBeenCalledWith(
- 'test-case-id',
- {
- action: 0,
- userName: '调解员'
- }
- );
- });
-
- // 验证成功消息
- expect(message.success).toHaveBeenCalledWith('案件状态更新成功');
- });
-
- test('API调用失败应该显示错误消息', async () => {
- ProcessAPIService.updateMediationState.mockRejectedValue(new Error('网络错误'));
-
- render(<TabContainer activeTab="mediation-board" />);
-
- // 点击终止按钮
- const terminateButton = screen.getByText('终止');
- fireEvent.click(terminateButton);
-
- // 点击确认按钮
- const confirmButton = screen.getByText('确定');
- fireEvent.click(confirmButton);
-
- // 验证错误消息
- await waitFor(() => {
- expect(message.error).toHaveBeenCalledWith('网络错误');
- });
- });
-
- test('不应该显示按钮当caseState为其他值', () => {
- // 修改mock数据为不支持的状态
- jest.mock('../contexts/CaseDataContext', () => ({
- useCaseData: () => ({
- caseData: {
- mediation: {
- state: 2, // 不支持的状态
- id: 'test-mediation-id'
- }
- },
- refreshData: jest.fn()
- })
- }));
-
- render(<TabContainer activeTab="mediation-board" />);
-
- expect(screen.queryByText('终止')).not.toBeInTheDocument();
- expect(screen.queryByText('恢复')).not.toBeInTheDocument();
- });
-});
\ No newline at end of file
diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index c87efc5..490f498 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -364,29 +364,35 @@
// 状态控制按钮显示逻辑
const shouldShowControlButton = () => {
- const show = caseState === 0 || caseState === 1 || caseState === 5;
+ // 转换为数字类型进行比较,兼容字符串和数字
+ const stateNum = Number(caseState);
+ const show = stateNum === 0 || stateNum === 1 || stateNum === 5;
console.log('状态控制按钮显示检查:', {
caseState,
+ caseStateType: typeof caseState,
+ stateNum,
show,
conditions: {
- 'caseState === 0': caseState === 0,
- 'caseState === 1': caseState === 1,
- 'caseState === 5': caseState === 5
+ 'stateNum === 0': stateNum === 0,
+ 'stateNum === 1': stateNum === 1,
+ 'stateNum === 5': stateNum === 5
}
});
return show;
};
const getControlButtonProps = () => {
- console.log('获取按钮属性:', { caseState });
+ // 转换为数字类型进行比较
+ const stateNum = Number(caseState);
+ console.log('获取按钮属性:', { caseState, caseStateType: typeof caseState, stateNum });
- if (caseState === 0 || caseState === 1) {
+ if (stateNum === 0 || stateNum === 1) {
return {
text: '终止',
style: 'terminate',
action: 'terminate'
};
- } else if (caseState === 5) {
+ } else if (stateNum === 5) {
return {
text: '恢复',
style: 'resume',
diff --git a/web-app/src/contexts/CaseDataContext.jsx b/web-app/src/contexts/CaseDataContext.jsx
index b5cb6e0..bfce87a 100644
--- a/web-app/src/contexts/CaseDataContext.jsx
+++ b/web-app/src/contexts/CaseDataContext.jsx
@@ -398,6 +398,14 @@
// 提取timeline数据
const timelineData = response.timeline || response.data?.timeline || response;
+ // 调试日志:输出提取的timeline数据
+ console.log('===== API数据提取 =====');
+ console.log('原始response:', response);
+ console.log('提取的timelineData:', timelineData);
+ console.log('timelineData.mediation:', timelineData.mediation);
+ console.log('timelineData.mediation?.state:', timelineData.mediation?.state);
+ console.log('========================');
+
// 提取nodes数据(确保为数组),兼容 nodeList 和 nodes 两种字段名
const nodesData = response.data?.nodeList || response.data?.nodes || response.nodes || [];
@@ -422,7 +430,7 @@
// 检查终态状态(调解成功/失败/人工接管),终态不执行外呼和存储
const mediationState = timelineData.mediation?.state;
- const isTerminalState = [2, 3, 4].includes(mediationState);
+ const isTerminalState = [2, 3, 4, 5].includes(mediationState);
if (isTerminalState) {
console.log('案件已处于终态状态:', mediationState, ',跳过外呼和存储');
@@ -449,6 +457,7 @@
// 使用Mock数据作为降级方案
const mockTimeline = mockTimelineData.data?.timeline || mockTimelineData;
+ console.log('使用Mock数据降级:', mockTimeline);
setCaseData(mockTimeline);
setProcessNodes(mockTimelineData.data?.nodes || []);
setHasLoaded(true);
--
Gitblit v1.8.0