shimai
yesterday 6f45735adfdcd973a19f638f9ced9629f79cd6de
web-app/src/components/dashboard/TabContainer.jsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import React, { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from 'react';
import { useCaseData } from '../../contexts/CaseDataContext';
import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator';
import { formatDuration, formatSuccessRate } from '../../utils/stateTranslator';
import ProcessAPIService from '../../services/ProcessAPIService';
import EvidenceAPIService from '../../services/EvidenceAPIService';
import MediationAgreementAPIService from '../../services/MediationAgreementAPIService';
@@ -8,6 +8,11 @@
import { message, Spin, Tag, Modal, Button, Input, Image } from 'antd';
import { PhoneOutlined } from '@ant-design/icons';
import { CallRecordModal } from '../call-record';
// 新增组件导入
import PartyInfoCard from './PartyInfoCard';
import NegotiationProgress from './NegotiationProgress';
import AISuggestionCard from './AISuggestionCard';
const { TextArea } = Input;
@@ -89,22 +94,53 @@
});
/**
 * 获取成功率同比数据
 */
const getSuccessRateYoY = (mediation) => {
  // 优先使用API返回的同比值
  if (mediation?.yoy_success_rate !== undefined && mediation?.yoy_success_rate !== null) {
    return {
      rate: mediation.yoy_success_rate,
      hours: mediation.yoy_before_hours || 0
    };
  }
  // 计算同比值
  const currentRate = mediation?.success_rate || 0;
  const lastRate = mediation?.last_success_rate || 0;
  const diff = (currentRate - lastRate) * 100;
  return {
    rate: diff,
    hours: mediation?.yoy_before_hours || 0
  };
};
/**
 * 调解数据看板
 */
const MediationDataBoard = () => {
  const { caseData } = useCaseData();
  const timeline = caseData || {};
  const mediation = timeline.mediation || {};
  
  // 从 timeline 获取数据
  const gapContent = timeline.result || '暂无分歧分析';
  const updateTime = formatDuration(timeline.before_duration);
  const successRate = formatSuccessRate(timeline.mediation?.success_rate);
  const roundCount = formatRoundCount(timeline.mediation?.mediation_count);
  const successRate = formatSuccessRate(mediation.success_rate);
  // 获取成功率数值(用于进度条)
  const successRateValue = (mediation.success_rate || 0) * 100;
  // 获取同比数据
  const yoyData = getSuccessRateYoY(mediation);
  const yoyRate = yoyData.rate >= 0 ? `+${yoyData.rate.toFixed(0)}%` : `${yoyData.rate.toFixed(0)}%`;
  const yoyHours = yoyData.hours;
  
  return (
    <div className="mediation-metrics">
      {/* 左侧:诉求差距分析 */}
      <div className="metric-card">
      {/* 左侧:诉求差距分析 + AI建议 */}
      <div className="metric-card left-column">
        <div className="metric-title">
          <i className="fas fa-exclamation-circle"></i>
          <span>诉求差距分析</span>
@@ -120,28 +156,36 @@
              {gapContent}
            </div>
          </div>
          {/* AI调解建议 */}
          <AISuggestionCard />
        </div>
      </div>
      {/* 右侧:调解数据 */}
      <div className="metric-card">
        <div className="metric-title">
          <i className="fas fa-exchange-alt"></i>
          <span>调解数据</span>
        </div>
        <div className="metric-content">
          <div className="success-metric">
            <div className="success-value">{successRate}</div>
            <div className="success-label">预计调解成功概率</div>
            <div className="success-change">
              <i className="fas fa-arrow-up"></i><span>较{updateTime} +8%</span>
      {/* 右侧:申请双方 + 成功率 + 协商沟通 */}
      <div className="metric-card right-column">
        {/* 申请双方信息 */}
        <PartyInfoCard />
        {/* 预计调解成功率 */}
        <div className="success-rate-section">
          <div className="success-rate-label">预计调解成功率</div>
          <div className="success-rate-row">
            <span className="success-rate-value">{successRate}</span>
            <div className="success-rate-yoy">
              <img src="/mom.png" alt="" className="yoy-icon-img" />
              <span className="yoy-rate">{yoyRate}</span>
              <span className="yoy-time">较{yoyHours}小时前</span>
            </div>
            <div style={{ marginTop: 15, fontSize: '0.9rem', color: 'var(--gray-color)' }}>
              协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>{roundCount}</span>
          </div>
          <div className="success-rate-progress">
            <div className="progress-bar-bg">
              <div className="progress-bar-fill" style={{ width: `${successRateValue}%` }}></div>
            </div>
          </div>
        </div>
        {/* 协商沟通进度 */}
        <NegotiationProgress />
      </div>
    </div>
  );
@@ -229,16 +273,23 @@
    }));
  };
  
  // 获取调解记录数据
  const loadMediationRecords = async () => {
    setLoading(true);
  // 看板轮询间隔(毫秒)
  const BOARD_POLL_INTERVAL = 5000; // 5秒
  const isBoardMountedRef = useRef(true);
  // 获取调解记录数据(首次加载带 loading,后续静默刷新)
  const loadMediationRecords = useCallback(async (silent = false) => {
    if (!silent) {
      setLoading(true);
    }
    setError(null);
    
    try {
      // 从timeline中获取mediation_id
      const mediationId = timeline.mediation?.id;
      if (!mediationId) {
        throw new Error('未找到调解ID');
        if (!silent) throw new Error('未找到调解ID');
        return;
      }
      
      // 调用API获取记录列表
@@ -248,23 +299,50 @@
      
      // 格式化数据
      const formattedRecords = formatRecordData(response.data || []);
      setRecords(formattedRecords);
      if (isBoardMountedRef.current) {
        setRecords(formattedRecords);
      }
      
    } catch (err) {
      setError(err.message);
      console.error('获取调解记录失败:', err);
      message.error(`获取调解记录失败: ${err.message}`);
      if (!silent) {
        setError(err.message);
        console.error('获取调解记录失败:', err);
        message.error(`获取调解记录失败: ${err.message}`);
      } else {
        console.warn('[MediationBoard] 静默刷新失败:', err.message);
      }
    } finally {
      setLoading(false);
      if (!silent) {
        setLoading(false);
      }
    }
  };
  }, [timeline.mediation?.id]);
  
  // 监听Tab切换
  // Tab激活时:首次加载 + 启动周期性轮询
  useEffect(() => {
    isBoardMountedRef.current = true;
    if (activeTab === 'mediation-board') {
      loadMediationRecords();
      // 首次加载(带 loading 效果)
      loadMediationRecords(false);
      // 周期性静默刷新(不显示 loading)
      const pollTimer = setInterval(() => {
        if (isBoardMountedRef.current) {
          loadMediationRecords(true);
        }
      }, BOARD_POLL_INTERVAL);
      console.log('[MediationBoard] 启动周期轮询,间隔:', BOARD_POLL_INTERVAL, 'ms');
      return () => {
        clearInterval(pollTimer);
        isBoardMountedRef.current = false;
      };
    }
  }, [activeTab]);
    return () => { isBoardMountedRef.current = false; };
  }, [activeTab, loadMediationRecords]);
  
  // 如果还在加载中,显示Loading状态
  if (loading) {