From ea12f670293895bc21bf52db1aa3cee9ab454fda Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Tue, 17 Mar 2026 14:12:03 +0800
Subject: [PATCH] refactor: 将终止/恢复按钮从TabContainer移动到FloatingControlPanel组件

---
 web-app/src/components/dashboard/FloatingControlPanel.jsx |  179 +++++++++++++++++++++++++
 web-app/src/components/dashboard/TabContainer.jsx         |  201 ----------------------------
 document/原型/index.html                                    |    4 
 3 files changed, 181 insertions(+), 203 deletions(-)

diff --git "a/document/\345\216\237\345\236\213/index.html" "b/document/\345\216\237\345\236\213/index.html"
index 39f6197..8260512 100644
--- "a/document/\345\216\237\345\236\213/index.html"
+++ "b/document/\345\216\237\345\236\213/index.html"
@@ -1997,6 +1997,10 @@
 					<i class="fas fa-user-tie"></i>
 					人工接管
 				</button>
+				<button class="floating-control-btn" id="floatingStopBtn">
+					<i class="fas fa-user-tie"></i>
+					人工接管
+				</button>
 			</div>
 		</div>
 
diff --git a/web-app/src/components/dashboard/FloatingControlPanel.jsx b/web-app/src/components/dashboard/FloatingControlPanel.jsx
index a0f75c3..093ba6d 100644
--- a/web-app/src/components/dashboard/FloatingControlPanel.jsx
+++ b/web-app/src/components/dashboard/FloatingControlPanel.jsx
@@ -1,14 +1,17 @@
 import React, { useState } from 'react';
-import { Modal, message } from 'antd';
+import { Modal, message, Input } from 'antd';
 import { useCaseData } from '../../contexts/CaseDataContext';
 import { translateMediationState } from '../../utils/stateTranslator';
 import { useTaskTimer } from '../../hooks/useTaskTimer';
 import ProcessAPIService from '../../services/ProcessAPIService';
 import { getMergedParams } from '../../utils/urlParams';
 
+const { TextArea } = Input;
+
 // 终态状态(不显示人工接管按钮)
 const TERMINAL_STATES = [2, 3]; // 调解成功、调解失败
 const TAKEOVER_STATE = 4; // 人工接管
+const PAUSED_STATE = 5; // 已终止/暂停状态
 
 /**
  * 获取案件ID
@@ -89,6 +92,12 @@
   const { caseData, taskStartTime, isTaskTimeFallback, refreshData } = useCaseData();
   const [takeoverLoading, setTakeoverLoading] = useState(false);
   const [confirmVisible, setConfirmVisible] = useState(false);
+  
+  // 状态控制相关状态
+  const [controlLoading, setControlLoading] = useState(false);
+  const [controlConfirmVisible, setControlConfirmVisible] = useState(false);
+  const [controlAction, setControlAction] = useState(null); // 'terminate' or 'resume'
+  const [remark, setRemark] = useState('');
 
   const timeline = caseData || {};
   const state = timeline.mediation?.state;
@@ -101,6 +110,98 @@
   const statusText = state === 1
     ? `调解进行中-阶段${orderNo}:${nodeName}`
     : (translateMediationState(state) || '调解进行中');
+
+  // ==================== 状态控制按钮逻辑 ====================
+  
+  /**
+   * 判断是否显示状态控制按钮
+   * 状态为0(未开始)、1(进行中)、5(已终止)时显示
+   */
+  const shouldShowControlButton = () => {
+    const stateNum = Number(state);
+    // 进行中(1)显示终止按钮,已终止(5)显示恢复按钮
+    return stateNum === 1 || stateNum === 5;
+  };
+
+  /**
+   * 获取状态控制按钮属性
+   */
+  const getControlButtonProps = () => {
+    const stateNum = Number(state);
+    
+    if (stateNum === 1) {
+      return {
+        text: '终止',
+        style: 'terminate',
+        action: 'terminate'
+      };
+    } else if (stateNum === 5) {
+      return {
+        text: '恢复',
+        style: 'resume',
+        action: 'resume'
+      };
+    }
+    
+    return null;
+  };
+
+  /**
+   * 处理状态控制按钮点击
+   */
+  const handleControlButtonClick = () => {
+    const buttonProps = getControlButtonProps();
+    if (!buttonProps) return;
+    
+    setControlAction(buttonProps.action);
+    setControlConfirmVisible(true);
+  };
+
+  /**
+   * 处理状态控制确认
+   */
+  const handleControlConfirmOk = async () => {
+    if (!controlAction) return;
+    
+    setControlLoading(true);
+    try {
+      const params = getMergedParams();
+      const actionCode = controlAction === 'terminate' ? 0 : 1;
+      
+      if (!params.caseId) {
+        throw new Error('案件ID不能为空');
+      }
+      
+      await ProcessAPIService.updateMediationState(params.caseId, {
+        action: actionCode,
+        userName: localStorage.getItem('userName') || '调解员',
+        remark: remark || ''
+      });
+      
+      message.success(controlAction === 'terminate' ? '调解已终止' : '调解已恢复');
+      setControlConfirmVisible(false);
+      setRemark('');
+      setControlAction(null);
+      
+      refreshData();
+    } catch (error) {
+      console.error('状态更新失败:', error);
+      message.error(error.message || '状态更新失败,请稍后重试');
+    } finally {
+      setControlLoading(false);
+    }
+  };
+
+  /**
+   * 处理状态控制取消
+   */
+  const handleControlConfirmCancel = () => {
+    setControlConfirmVisible(false);
+    setRemark('');
+    setControlAction(null);
+  };
+
+  // ==================== 人工接管逻辑 ====================
 
   /**
    * 处理接管API调用
@@ -178,6 +279,40 @@
   };
 
   /**
+   * 渲染状态控制按钮(终止/恢复)
+   */
+  const renderStateControlButton = () => {
+    if (!shouldShowControlButton()) return null;
+    
+    const buttonProps = getControlButtonProps();
+    if (!buttonProps) return null;
+    
+    const isTerminate = buttonProps.style === 'terminate';
+    
+    return (
+      <button
+        className="floating-control-btn"
+        onClick={handleControlButtonClick}
+        disabled={controlLoading}
+        style={{
+          ...(isTerminate ? {
+            background: '#1A6FB8',
+          } : {
+            background: '#52c41a',
+          }),
+          ...(controlLoading ? { opacity: 0.6, cursor: 'not-allowed' } : {})
+        }}
+      >
+        {controlLoading ? (
+          <><i className="fas fa-spinner fa-spin"></i>处理中...</>
+        ) : (
+          <><i className={isTerminate ? "fas fa-pause-circle" : "fas fa-play-circle"}></i>{buttonProps.text}</>
+        )}
+      </button>
+    );
+  };
+
+  /**
    * 渲染控制区域(按钮或印章)
    */
   const renderControlAction = () => {
@@ -186,8 +321,18 @@
       return <TakeoverStamp state={state} />;
     }
 
-    // 调解中:显示接管按钮
-    return <TakeoverButton loading={takeoverLoading} onClick={handleTakeover} />;
+    // 已终止状态(5):只显示恢复按钮
+    if (Number(state) === PAUSED_STATE) {
+      return renderStateControlButton();
+    }
+
+    // 调解中(1):显示终止按钮和人工接管按钮
+    return (
+      <>
+        {renderStateControlButton()}
+        <TakeoverButton loading={takeoverLoading} onClick={handleTakeover} />
+      </>
+    );
   };
 
   return (
@@ -211,6 +356,34 @@
         </div>
       </div>
 
+      {/* 状态控制确认对话框(终止/恢复) */}
+      <Modal
+        title={controlAction === 'terminate' ? '确认终止调解' : '确认恢复调解'}
+        visible={controlConfirmVisible}
+        onOk={handleControlConfirmOk}
+        onCancel={handleControlConfirmCancel}
+        okText="确定"
+        cancelText="取消"
+        confirmLoading={controlLoading}
+      >
+        <p>
+          {controlAction === 'terminate' 
+            ? '确定要终止当前AI调解流程吗?终止后调解将暂停,可在适当时机恢复。' 
+            : '确定要恢复AI调解流程吗?恢复后将从当前位置继续调解。'}
+        </p>
+        <div style={{ marginTop: 15 }}>
+          <label style={{ display: 'block', marginBottom: 5, fontWeight: 500 }}>
+            备注(可选):
+          </label>
+          <TextArea
+            value={remark}
+            onChange={(e) => setRemark(e.target.value)}
+            placeholder="请输入操作备注..."
+            rows={3}
+          />
+        </div>
+      </Modal>
+
       {/* 人工接管确认对话框 */}
       <Modal
         title="人工接管确认"
diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index 490f498..e732c80 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -154,26 +154,10 @@
   const [loading, setLoading] = useState(false);
   const [error, setError] = useState(null);
   
-  // 状态控制相关状态
-  const [controlLoading, setControlLoading] = useState(false);
-  const [confirmModalVisible, setConfirmModalVisible] = useState(false);
-  const [controlAction, setControlAction] = useState(null); // 'terminate' or 'resume'
-  const [remark, setRemark] = useState('');
-  
   // 获取案件数据
-  const { caseData, refreshData } = useCaseData();
+  const { caseData } = useCaseData();
   const timeline = caseData || {};
   const caseState = timeline.mediation?.state;
-  
-  // 调试日志:输出关键数据
-  useEffect(() => {
-    console.log('===== MediationBoard 数据状态 =====');
-    console.log('caseData:', caseData);
-    console.log('timeline:', timeline);
-    console.log('caseState:', caseState);
-    console.log('mediation:', timeline.mediation);
-    console.log('================================');
-  }, [caseData, caseState, timeline]);
   
   // person_type到avatar类型的映射
   const getAvatarType = (personType) => {
@@ -362,114 +346,6 @@
     return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
   };
 
-  // 状态控制按钮显示逻辑
-  const shouldShowControlButton = () => {
-    // 转换为数字类型进行比较,兼容字符串和数字
-    const stateNum = Number(caseState);
-    const show = stateNum === 0 || stateNum === 1 || stateNum === 5;
-    console.log('状态控制按钮显示检查:', {
-      caseState,
-      caseStateType: typeof caseState,
-      stateNum,
-      show,
-      conditions: {
-        'stateNum === 0': stateNum === 0,
-        'stateNum === 1': stateNum === 1,
-        'stateNum === 5': stateNum === 5
-      }
-    });
-    return show;
-  };
-
-  const getControlButtonProps = () => {
-    // 转换为数字类型进行比较
-    const stateNum = Number(caseState);
-    console.log('获取按钮属性:', { caseState, caseStateType: typeof caseState, stateNum });
-    
-    if (stateNum === 0 || stateNum === 1) {
-      return {
-        text: '终止',
-        style: 'terminate',
-        action: 'terminate'
-      };
-    } else if (stateNum === 5) {
-      return {
-        text: '恢复',
-        style: 'resume',
-        action: 'resume'
-      };
-    }
-    
-    console.log('未匹配到按钮属性,返回null');
-    return null;
-  };
-
-  // 处理状态控制按钮点击
-  const handleControlButtonClick = (action) => {
-    console.log('状态控制按钮点击:', { action });
-    setControlAction(action);
-    setConfirmModalVisible(true);
-  };
-
-  // 处理确认对话框确认
-  const handleConfirmOk = async () => {
-    console.log('确认对话框确认:', { controlAction, remark });
-    
-    if (!controlAction) {
-      console.warn('控制动作为空');
-      return;
-    }
-    
-    setControlLoading(true);
-    try {
-      const params = getMergedParams();
-      const actionCode = controlAction === 'terminate' ? 0 : 1;
-      
-      console.log('准备调用API:', {
-        caseId: params.caseId,
-        actionCode,
-        userName: localStorage.getItem('userName') || '调解员',
-        remark: remark || ''
-      });
-      
-      // 验证必要参数
-      if (!params.caseId) {
-        throw new Error('案件ID不能为空');
-      }
-      
-      await ProcessAPIService.updateMediationState(params.caseId, {
-        action: actionCode,
-        userName: localStorage.getItem('userName') || '调解员',
-        remark: remark || ''
-      });
-      
-      message.success('案件状态更新成功');
-      setConfirmModalVisible(false);
-      setRemark('');
-      setControlAction(null);
-      
-      // 刷新数据
-      refreshData();
-    } catch (error) {
-      console.error('状态更新失败:', error);
-      const errorMessage = error.message || '状态更新失败,请稍后重试';
-      message.error(errorMessage);
-      
-      // 如果是网络错误,提供更多帮助信息
-      if (errorMessage.includes('网络') || errorMessage.includes('Network')) {
-        message.info('请检查网络连接或联系管理员');
-      }
-    } finally {
-      setControlLoading(false);
-    }
-  };
-
-  // 处理确认对话框取消
-  const handleConfirmCancel = () => {
-    setConfirmModalVisible(false);
-    setControlAction(null);
-    setRemark('');
-  };
   return (
     <>
       <div className="mediation-summary" style={{
@@ -564,81 +440,6 @@
           </div>
         ))}
       </div>
-
-      {/* 状态控制按钮区域 */}
-      {shouldShowControlButton() && (() => {
-        const buttonProps = getControlButtonProps();
-        if (!buttonProps) return null;
-        
-        return (
-          <div style={{
-            marginTop: 20,
-            paddingTop: 15,
-            borderTop: '1px solid #e9ecef',
-            display: 'flex',
-            justifyContent: 'center',
-            gap: 12
-          }}>
-            <button
-              onClick={() => handleControlButtonClick(buttonProps.action)}
-              disabled={controlLoading}
-              style={{
-                padding: '10px 20px',
-                borderRadius: 'var(--border-radius)',
-                fontWeight: 600,
-                fontSize: '0.9rem',
-                cursor: controlLoading ? 'not-allowed' : 'pointer',
-                display: 'flex',
-                alignItems: 'center',
-                gap: 6,
-                border: 'none',
-                ...(buttonProps.style === 'terminate' ? {
-                  background: '#1A6FB8',
-                  color: 'white',
-                } : {
-                  background: '#52c41a',
-                  color: 'white',
-                }),
-                opacity: controlLoading ? 0.6 : 1,
-              }}
-            >
-              {controlLoading ? (
-                <><i className="fas fa-spinner fa-spin"></i>处理中...</>
-              ) : (
-                <><i className="fas fa-pause-circle"></i>{buttonProps.text}</>
-              )}
-            </button>
-          </div>
-        );
-      })()}
-
-      {/* 状态控制确认对话框 */}
-      <Modal
-        title={controlAction === 'terminate' ? '确认终止调解' : '确认恢复调解'}
-        visible={confirmModalVisible}
-        onOk={handleConfirmOk}
-        onCancel={handleConfirmCancel}
-        okText="确定"
-        cancelText="取消"
-        confirmLoading={controlLoading}
-      >
-        <p>
-          {controlAction === 'terminate' 
-            ? '确定要终止当前AI调解流程吗?终止后调解将暂停,可在适当时机恢复。' 
-            : '确定要恢复AI调解流程吗?恢复后将从当前位置继续调解。'}
-        </p>
-        <div style={{ marginTop: 15 }}>
-          <label style={{ display: 'block', marginBottom: 5, fontWeight: 500 }}>
-            备注(可选):
-          </label>
-          <Input.TextArea
-            value={remark}
-            onChange={(e) => setRemark(e.target.value)}
-            placeholder="请输入操作备注..."
-            rows={3}
-          />
-        </div>
-      </Modal>
     </>
   );
 };

--
Gitblit v1.8.0