| | |
| | | import React from 'react'; |
| | | import React, { useState } from 'react'; |
| | | 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 |
| | | * 优先级:Context > URL参数 > localStorage |
| | | */ |
| | | const resolveCaseId = (caseData) => { |
| | | // 1. 从Context获取 |
| | | if (caseData?.case_id) return String(caseData.case_id); |
| | | |
| | | // 2. 从 URL 参数获取 |
| | | const params = getMergedParams(); |
| | | if (params.caseId) return String(params.caseId); |
| | | |
| | | // 3. 从localStorage获取 |
| | | try { |
| | | const stored = JSON.parse(localStorage.getItem('case_data_timeline') || '{}'); |
| | | if (stored.case_id) return String(stored.case_id); |
| | | } catch { /* ignore */ } |
| | | |
| | | return null; |
| | | }; |
| | | |
| | | /** |
| | | * 获取当前用户名 |
| | | */ |
| | | const resolveUserName = () => { |
| | | try { |
| | | const user = JSON.parse(localStorage.getItem('currentUser') || '{}'); |
| | | return user.user_name || 'No User'; |
| | | } catch { |
| | | return 'No User'; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * 印章组件 - 终态状态展示(调解成功/失败/人工接管) |
| | | */ |
| | | const TakeoverStamp = ({ state }) => { |
| | | const stateTextMap = { |
| | | 2: '调解成功', |
| | | 3: '调解失败', |
| | | 4: '已人工接管' |
| | | }; |
| | | const text = stateTextMap[state] || '已人工接管'; |
| | | |
| | | return ( |
| | | <div className="takeover-stamp"> |
| | | <span className="takeover-stamp-text"> |
| | | <i className="fas fa-stamp"></i> |
| | | {text} |
| | | </span> |
| | | </div> |
| | | ); |
| | | }; |
| | | |
| | | /** |
| | | * 接管按钮组件 |
| | | */ |
| | | const TakeoverButton = ({ loading, onClick }) => ( |
| | | <button |
| | | className="floating-control-btn" |
| | | onClick={onClick} |
| | | disabled={loading} |
| | | style={loading ? { opacity: 0.6, cursor: 'not-allowed' } : {}} |
| | | > |
| | | {loading ? ( |
| | | <><i className="fas fa-spinner fa-spin"></i>接管中...</> |
| | | ) : ( |
| | | <><i className="fas fa-user-tie"></i>人工接管</> |
| | | )} |
| | | </button> |
| | | ); |
| | | |
| | | /** |
| | | * 底部悬浮控制面板 |
| | | */ |
| | | const FloatingControlPanel = ({ currentStep = 1, elapsedTime = '25分钟', onManualTakeover }) => { |
| | | const stepNames = ['阶段1:意愿调查', '阶段2:材料核实', '阶段3:事实认定', '阶段4:达成协议', '阶段5:履约回访']; |
| | | const FloatingControlPanel = () => { |
| | | const { caseData, taskStartTime, isTaskTimeFallback, refreshData } = useCaseData(); |
| | | const [takeoverLoading, setTakeoverLoading] = useState(false); |
| | | const [confirmVisible, setConfirmVisible] = useState(false); |
| | | |
| | | const handleTakeover = () => { |
| | | if (onManualTakeover) { |
| | | onManualTakeover(); |
| | | } else { |
| | | if (window.confirm('确认要人工接管调解吗?接管后将结束AI调解,由工作人员继续处理。')) { |
| | | alert('AI调解已结束,已转为人工接管模式。工作人员将继续处理本次调解。'); |
| | | // 状态控制相关状态 |
| | | 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; |
| | | const nodeName = timeline.current_node?.node_name || ''; |
| | | const orderNo = timeline.current_node?.order_no || 1; |
| | | |
| | | const { formattedTime } = useTaskTimer(taskStartTime, isTaskTimeFallback); |
| | | |
| | | // 生成状态文本 |
| | | 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调用 |
| | | */ |
| | | const executeTakeover = async () => { |
| | | console.log('executeTakeover 开始执行'); |
| | | const caseId = resolveCaseId(caseData); |
| | | console.log('获取到的 caseId:', caseId); |
| | | |
| | | if (!caseId) { |
| | | message.error('无法获取案件ID,请刷新页面后重试'); |
| | | return; |
| | | } |
| | | |
| | | const userName = resolveUserName(); |
| | | console.log('获取到的 userName:', userName); |
| | | |
| | | setTakeoverLoading(true); |
| | | try { |
| | | console.log('调用 ProcessAPIService.takeover, caseId:', caseId, ', userName:', userName); |
| | | const response = await ProcessAPIService.takeover(caseId, { userName }); |
| | | console.log('接管API返回:', response); |
| | | message.success('人工接管成功'); |
| | | refreshData(); |
| | | } catch (err) { |
| | | console.error('接管API异常:', err); |
| | | handleTakeoverError(err); |
| | | } finally { |
| | | setTakeoverLoading(false); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * 处理接管错误响应 |
| | | */ |
| | | const handleTakeoverError = (err) => { |
| | | const status = err?.response?.status || err?.status; |
| | | const msg = err?.response?.data?.message || err?.message || ''; |
| | | |
| | | // 400错误:已被接管或已结束 → 重新加载页面 |
| | | if (status === 400) { |
| | | console.warn('接管失败(400):', msg); |
| | | refreshData(); |
| | | return; |
| | | } |
| | | |
| | | // 其他错误 → 提示用户 |
| | | console.error('人工接管失败:', err); |
| | | message.error(msg || '人工接管失败,请稍后重试'); |
| | | }; |
| | | |
| | | /** |
| | | * 点击接管按钮 - 显示确认对话框 |
| | | */ |
| | | const handleTakeover = () => { |
| | | console.log('点击人工接管按钮'); |
| | | setConfirmVisible(true); |
| | | }; |
| | | |
| | | /** |
| | | * 确认接管 |
| | | */ |
| | | const handleConfirmOk = async () => { |
| | | console.log('用户点击确定,开始执行接管'); |
| | | setConfirmVisible(false); |
| | | await executeTakeover(); |
| | | }; |
| | | |
| | | /** |
| | | * 取消接管 |
| | | */ |
| | | const handleConfirmCancel = () => { |
| | | console.log('用户点击取消'); |
| | | setConfirmVisible(false); |
| | | }; |
| | | |
| | | /** |
| | | * 渲染状态控制按钮(终止/恢复) |
| | | */ |
| | | const renderStateControlButton = () => { |
| | | if (!shouldShowControlButton()) return null; |
| | | |
| | | const buttonProps = getControlButtonProps(); |
| | | if (!buttonProps) return null; |
| | | |
| | | const isTerminate = buttonProps.style === 'terminate'; |
| | | |
| | | return ( |
| | | <button |
| | | className={`state-control-btn ${isTerminate ? 'state-control-btn--terminate' : 'state-control-btn--resume'}`} |
| | | onClick={handleControlButtonClick} |
| | | disabled={controlLoading} |
| | | > |
| | | {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 = () => { |
| | | // 终态状态(调解成功/失败/人工接管):显示印章 |
| | | if (TERMINAL_STATES.includes(state) || state === TAKEOVER_STATE) { |
| | | return <TakeoverStamp state={state} />; |
| | | } |
| | | |
| | | |
| | | // 调解中(1):显示终止按钮和人工接管按钮 |
| | | return ( |
| | | <> |
| | | {renderStateControlButton()} |
| | | <TakeoverButton loading={takeoverLoading} onClick={handleTakeover} /> |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | return ( |
| | | <div className="floating-control-panel"> |
| | | <div className="control-status"> |
| | | <div className="status-indicator"> |
| | | <span className="status-dot"></span> |
| | | <span className="status-text"> |
| | | 调解进行中-{stepNames[currentStep]}{' '} |
| | | <span style={{ color: 'gray' }}>(已进行:{elapsedTime})</span> |
| | | </span> |
| | | <> |
| | | <div className="floating-control-panel"> |
| | | <div className="control-status"> |
| | | <div className="status-indicator"> |
| | | <span className="status-dot"></span> |
| | | <span className="status-text"> |
| | | {statusText}{' '} |
| | | <span style={{ color: 'gray' }}> |
| | | (已进行:{formattedTime} |
| | | {isTaskTimeFallback && <span style={{ color: '#faad14' }}> *</span>} |
| | | ) |
| | | </span> |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div className="control-action"> |
| | | {renderControlAction()} |
| | | </div> |
| | | </div> |
| | | <div className="control-action"> |
| | | <button className="floating-control-btn" onClick={handleTakeover}> |
| | | <i className="fas fa-user-tie"></i> |
| | | 人工接管 |
| | | </button> |
| | | </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="人工接管确认" |
| | | visible={confirmVisible} |
| | | onOk={handleConfirmOk} |
| | | onCancel={handleConfirmCancel} |
| | | okText="确定" |
| | | cancelText="取消" |
| | | confirmLoading={takeoverLoading} |
| | | > |
| | | <p>确定人工接管本调解案件吗?</p> |
| | | </Modal> |
| | | </> |
| | | ); |
| | | }; |
| | | |