From 6bb08c2297be1b6415c8bc02e6917eba6ee355e5 Mon Sep 17 00:00:00 2001
From: shimai <shimai@example.com>
Date: Fri, 03 Apr 2026 10:42:08 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/test/tony.cheng/260312' into test/shimai.huang/260309

---
 web-app/src/components/dashboard/TabContainer.jsx |  294 ++++++++++++++++------------------------------------------
 1 files changed, 82 insertions(+), 212 deletions(-)

diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index 490f498..ca215e3 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
+import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
 import { useCaseData } from '../../contexts/CaseDataContext';
 import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator';
 import ProcessAPIService from '../../services/ProcessAPIService';
@@ -6,6 +6,8 @@
 import MediationAgreementAPIService from '../../services/MediationAgreementAPIService';
 import { getMergedParams } from '../../utils/urlParams';
 import { message, Spin, Tag, Modal, Button, Input, Image } from 'antd';
+import { PhoneOutlined } from '@ant-design/icons';
+import { CallRecordModal } from '../call-record';
 
 const { TextArea } = Input;
 
@@ -153,34 +155,36 @@
   const [records, setRecords] = useState([]);
   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 [callRecordVisible, setCallRecordVisible] = useState(false);
+  const [currentRecord, setCurrentRecord] = useState(null);
   
   // 获取案件数据
-  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]);
-  
+
+
+  // 格式化时间戳为 YYYY-MM-DD HH:MM:SS
+  const formatTimestamp = (timestamp) => {
+    if (!timestamp) return '';
+    const date = new Date(timestamp);
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, '0');
+    const day = String(date.getDate()).padStart(2, '0');
+    const hours = String(date.getHours()).padStart(2, '0');
+    const minutes = String(date.getMinutes()).padStart(2, '0');
+    const seconds = String(date.getSeconds()).padStart(2, '0');
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+  };
+
   // person_type到avatar类型的映射
+  // 1: 申请人, 2: 被申请人, 3: AI调解员, 4: 调解员
   const getAvatarType = (personType) => {
     const typeMap = {
-      '1': 'ai',
-      '2': 'applicant',
-      '3': 'respondent',
+      '1': 'applicant',
+      '2': 'respondent',
+      '3': 'ai',
       '4': 'mediator'
     };
     return typeMap[personType] || 'ai';
@@ -198,23 +202,25 @@
   };
   
   // 获取角色显示名称
+  // 1: 申请人, 2: 被申请人, 3: AI调解员, 4: 调解员
   const getRoleDisplayName = (personType, creatorName) => {
     const roleMap = {
-      '1': 'AI调解员',
-      '2': `申请人(${creatorName})`,
-      '3': `被申请人(${creatorName})`,
+      '1': `申请人(${creatorName})`,
+      '2': `被申请人(${creatorName})`,
+      '3': 'AI调解员',
       '4': `调解员(${creatorName})`
     };
     return roleMap[personType] || creatorName;
   };
   
-  // 数据格式化函数
+  // 数据格式化函数(保留原始数据字段用于通话记录功能)
   const formatRecordData = (apiRecords) => {
     return apiRecords.map(record => ({
+      ...record, // 保留原始数据字段(person_id, job_id, creator等)
       avatar: getAvatarType(record.person_type),
       name: getRoleDisplayName(record.person_type, record.creator),
-      avatarText: record.creator?.charAt(0) || '',  // 头像显示名字第一个字
-      time: record.create_time,
+      avatarText: record.creator?.charAt(0) || '',
+      time: formatTimestamp(record.create_time),
       content: record.result,
       tags: record.tagList?.map(tag => ({
         text: tag.tag_name,
@@ -362,114 +368,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={{
@@ -518,7 +416,40 @@
                 {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>
+                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2 }}>
+                  <span style={{ fontWeight: 600, fontSize: '0.95rem', color: 'var(--dark-color)' }}>{item.name}</span>
+                  {item.avatar !== 'ai' && item.avatar !== 'mediator' && (
+                    <span 
+                      className="call-record-btn"
+                      style={{
+                        display: 'inline-flex',
+                        alignItems: 'center',
+                        gap: 4,
+                        padding: '2px 8px',
+                        fontSize: '0.75rem',
+                        background: '#e3f2fd',
+                        color: '#1890ff',
+                        borderRadius: 12,
+                        cursor: 'pointer',
+                        transition: 'all 0.2s'
+                      }}
+                      onClick={(e) => {
+                        e.stopPropagation();
+                        setCurrentRecord(item);
+                        setCallRecordVisible(true);
+                      }}
+                      onMouseEnter={(e) => {
+                        e.target.style.background = '#bbdefb';
+                      }}
+                      onMouseLeave={(e) => {
+                        e.target.style.background = '#e3f2fd';
+                      }}
+                    >
+                      <PhoneOutlined style={{ fontSize: 12 }} />
+                      通话记录
+                    </span>
+                  )}
+                </div>
                 <div style={{ fontSize: '0.8rem', color: 'var(--gray-color)', display: 'flex', alignItems: 'center', gap: 6 }}>
                   <i className="far fa-clock"></i>
                   <span>{item.time}</span>
@@ -564,81 +495,13 @@
           </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>
+      
+      {/* 通话记录弹窗 */}
+      <CallRecordModal
+        visible={callRecordVisible}
+        onClose={() => setCallRecordVisible(false)}
+        record={currentRecord}
+      />
     </>
   );
 };
@@ -1858,3 +1721,10 @@
 };
 
 export default TabContainer;
+
+
+
+
+
+
+

--
Gitblit v1.8.0