From 4eb71775167ae903aea17bb410c6201e872daf66 Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Thu, 05 Feb 2026 16:35:50 +0800
Subject: [PATCH] feat: 优化类案推荐功能 - 相似度分级、分页加载、详情字段扩展及法条显示优化

---
 web-app/src/components/dashboard/TabContainer.jsx |  204 +++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 175 insertions(+), 29 deletions(-)

diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index 324729e..2b42c5f 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -1,4 +1,8 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
+import { useCaseData } from '../../contexts/CaseDataContext';
+import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator';
+import ProcessAPIService from '../../services/ProcessAPIService';
+import { message, Spin } from 'antd';
 
 /**
  * 选项卡容器组件 - 4个选项卡
@@ -40,7 +44,7 @@
         {/* AI调解实时看板 */}
         <div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}>
           <div className="tab-content-area">
-            <MediationBoard />
+            <MediationBoard activeTab={activeTab} />
           </div>
         </div>
 
@@ -66,6 +70,15 @@
  * 调解数据看板
  */
 const MediationDataBoard = () => {
+  const { caseData } = useCaseData();
+  const timeline = caseData || {};
+  
+  // 从 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);
+  
   return (
     <div className="mediation-metrics">
       {/* 左侧:诉求差距分析 */}
@@ -80,20 +93,9 @@
               <i className="fas fa-exclamation-circle"></i>
               主要分歧点
             </div>
-            <div className="gap-update-time">3小时前更新</div>
+            <div className="gap-update-time">{updateTime}</div>
             <div className="gap-content">
-              双方在以下方面存在明显分歧:
-              <ul>
-                <li>
-                  <strong>拖欠工资金额</strong>:李晓明主张拖欠3个月工资¥42,000,公司承认拖欠但只认可¥35,000
-                </li>
-                <li>
-                  <strong>克扣绩效奖金</strong>:李晓明主张应发绩效奖金¥10,800,公司以未完成KPI为由拒绝支付
-                </li>
-                <li>
-                  <strong>经济补偿金</strong>:李晓明要求支付解除劳动合同经济补偿¥12,000,公司认为员工主动辞职不应支付
-                </li>
-              </ul>
+              {gapContent}
             </div>
           </div>
         </div>
@@ -107,14 +109,14 @@
         </div>
         <div className="metric-content">
           <div className="success-metric">
-            <div className="success-value">68%</div>
+            <div className="success-value">{successRate}</div>
             <div className="success-label">预计调解成功概率</div>
             <div className="success-change">
-              <i className="fas fa-arrow-up"></i>
-              <span>较3小时前 +8%</span>
+              
+              {/* <i className="fas fa-arrow-up"></i><span>较{updateTime} +8%</span> */}
             </div>
             <div style={{ marginTop: 15, fontSize: '0.9rem', color: 'var(--gray-color)' }}>
-              协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>第6轮</span>
+              协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>{roundCount}</span>
             </div>
           </div>
         </div>
@@ -126,11 +128,152 @@
 /**
  * AI调解实时看板
  */
-const MediationBoard = () => {
-  const boardItems = [
+const MediationBoard = ({ activeTab }) => {
+  // 状态管理
+  const [records, setRecords] = useState([]);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState(null);
+  
+  // 获取案件数据
+  const { caseData } = useCaseData();
+  const timeline = caseData || {};
+  
+  // person_type到avatar类型的映射
+  const getAvatarType = (personType) => {
+    const typeMap = {
+      '1': 'ai',
+      '2': 'applicant',
+      '3': 'respondent',
+      '4': 'mediator'
+    };
+    return typeMap[personType] || 'ai';
+  };
+  
+  // tag_style到UI标签样式的映射
+  const getTagStyleType = (tagStyle) => {
+    const styleMap = {
+      'red': 'tag-type-5',
+      'blue': 'tag-type-1',
+      'green': 'tag-type-2',
+      'orange': 'tag-type-3'
+    };
+    return styleMap[tagStyle] || 'tag-type-1';
+  };
+  
+  // 获取角色显示名称
+  const getRoleDisplayName = (personType, creatorName) => {
+    const roleMap = {
+      '1': 'AI调解员',
+      '2': `申请人(${creatorName})`,
+      '3': `被申请人(${creatorName})`,
+      '4': `调解员(${creatorName})`
+    };
+    return roleMap[personType] || creatorName;
+  };
+  
+  // 数据格式化函数
+  const formatRecordData = (apiRecords) => {
+    return apiRecords.map(record => ({
+      avatar: getAvatarType(record.person_type),
+      name: getRoleDisplayName(record.person_type, record.creator),
+      avatarText: record.creator?.charAt(0) || '',  // 头像显示名字第一个字
+      time: record.create_time,
+      content: record.result,
+      tags: record.tagList?.map(tag => ({
+        text: tag.tag_name,
+        type: getTagStyleType(tag.tag_style)
+      })) || []
+    }));
+  };
+  
+  // 获取调解记录数据
+  const loadMediationRecords = async () => {
+    setLoading(true);
+    setError(null);
+    
+    try {
+      // 从timeline中获取mediation_id
+      const mediationId = timeline.mediation?.id;
+      if (!mediationId) {
+        throw new Error('未找到调解ID');
+      }
+      
+      // 调用API获取记录列表
+      const response = await ProcessAPIService.getProcessRecords({
+        mediation_id: mediationId
+      });
+      
+      // 格式化数据
+      const formattedRecords = formatRecordData(response.data || []);
+      setRecords(formattedRecords);
+      
+    } catch (err) {
+      setError(err.message);
+      console.error('获取调解记录失败:', err);
+      message.error(`获取调解记录失败: ${err.message}`);
+    } finally {
+      setLoading(false);
+    }
+  };
+  
+  // 监听Tab切换
+  useEffect(() => {
+    if (activeTab === 'mediation-board') {
+      loadMediationRecords();
+    }
+  }, [activeTab]);
+  
+  // 如果还在加载中,显示Loading状态
+  if (loading) {
+    return (
+      <div style={{ 
+        display: 'flex', 
+        justifyContent: 'center', 
+        alignItems: 'center', 
+        height: '400px' 
+      }}>
+        <Spin size="large" tip="正在加载调解记录..." />
+      </div>
+    );
+  }
+  
+  // 如果有错误,显示错误信息
+  if (error) {
+    return (
+      <div style={{ 
+        textAlign: 'center', 
+        padding: '40px', 
+        color: '#ff4d4f' 
+      }}>
+        <div style={{ fontSize: '1.2rem', marginBottom: '10px' }}>
+          <i className="fas fa-exclamation-circle"></i>
+          数据加载失败
+        </div>
+        <div>{error}</div>
+        <button 
+          onClick={loadMediationRecords}
+          style={{
+            marginTop: '15px',
+            padding: '8px 16px',
+            backgroundColor: '#1890ff',
+            color: 'white',
+            border: 'none',
+            borderRadius: '4px',
+            cursor: 'pointer'
+          }}
+        >
+          重新加载
+        </button>
+      </div>
+    );
+  }
+  
+  // 使用动态数据或默认mock数据
+  const displayRecords = records.length > 0 ? records : [
     {
       avatar: 'ai',
       name: 'AI调解员',
+      avatarText: '',
       time: '09:30:05',
       content: '已分别联系李晓明(申请人)和广东好又多贸易有限公司(被申请人)。双方均表示愿意接受调解,希望通过协商解决劳动争议/拖欠、克扣工资纠纷。',
       tags: [{ text: '意愿调查', type: 'tag-type-1' }, { text: '初步接触', type: 'tag-type-2' }],
@@ -138,6 +281,7 @@
     {
       avatar: 'applicant',
       name: '申请人(李晓明)',
+      avatarText: '李',
       time: '09:35:22',
       content: '我在好又多公司担任销售经理3年,公司拖欠我3个月工资共¥42,000,还克扣了季度绩效奖金¥10,800。另外,公司单方面解除劳动合同,应支付经济补偿金¥12,000。我要求公司支付总共¥52,800。',
       tags: [{ text: '诉求表达', type: 'tag-type-3' }, { text: '情绪激动', type: 'tag-type-5' }],
@@ -145,12 +289,16 @@
     {
       avatar: 'respondent',
       name: '被申请人(好又多公司)',
+      avatarText: '好',
       time: '09:40:15',
       content: '公司确实遇到了资金周转困难,拖欠工资问题承认。但李晓明主张的金额有误:1) 3个月工资应为¥35,000(含请假扣款);2) 绩效奖金因未完成KPI指标不应发放;3) 是李晓明主动提出辞职,不应支付经济补偿金。公司最多支付¥38,500。',
       tags: [{ text: '诉求表达', type: 'tag-type-3' }],
     },
   ];
 
+  // 从timeline获取沟通情况总结
+  const communicationSummary = timeline.mediation?.summary || '暂无沟通情况总结';
+  
   const getAvatarClass = (avatar) => {
     const map = {
       ai: 'avatar-ai',
@@ -171,12 +319,10 @@
     return map[avatar] || 'content-ai';
   };
 
-  const getAvatarContent = (avatar) => {
+  const getAvatarContent = (avatar, avatarText) => {
     if (avatar === 'ai') return <i className="fas fa-robot"></i>;
-    if (avatar === 'applicant') return '李';
-    if (avatar === 'respondent') return '好';
-    if (avatar === 'mediator') return '调';
-    return <i className="fas fa-robot"></i>;
+    // 显示名字的第一个字
+    return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
   };
 
   return (
@@ -195,12 +341,12 @@
           沟通情况总结
         </div>
         <div style={{ lineHeight: 1.5 }}>
-          截至目前,AI调解员已与申请双方进行协商沟通6轮,期间申请人补充材料2次,被申请人补充材料2次,双方对调解方案表现出积极协商态度。
+          {communicationSummary}
         </div>
       </div>
 
       <div className="board-container" style={{ maxHeight: 450, overflowY: 'auto' }}>
-        {boardItems.map((item, index) => (
+        {displayRecords.map((item, index) => (
           <div key={index} className="board-item" style={{
             background: '#f8f9fa',
             borderRadius: 'var(--border-radius)',
@@ -224,7 +370,7 @@
                   item.avatar === 'respondent' ? 'linear-gradient(135deg, #e9c46a, #e76f51)' :
                   'linear-gradient(135deg, #7209b7, #3a0ca3)',
               }}>
-                {getAvatarContent(item.avatar)}
+                {getAvatarContent(item.avatar, item.avatarText)}
               </div>
               <div className="item-source">
                 <div style={{ fontWeight: 600, fontSize: '0.95rem', color: 'var(--dark-color)', marginBottom: 2 }}>{item.name}</div>

--
Gitblit v1.8.0