tony.cheng
2026-03-17 ea12f670293895bc21bf52db1aa3cee9ab454fda
refactor: 将终止/恢复按钮从TabContainer移动到FloatingControlPanel组件
3 files modified
384 ■■■■ changed files
document/原型/index.html 4 ●●●● patch | view | raw | blame | history
web-app/src/components/dashboard/FloatingControlPanel.jsx 179 ●●●●● patch | view | raw | blame | history
web-app/src/components/dashboard/TabContainer.jsx 201 ●●●●● patch | view | raw | blame | history
document/原型/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>
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="人工接管确认"
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>
    </>
  );
};