From 15332eb2a66375b0b2aff130737202682980a23d Mon Sep 17 00:00:00 2001
From: chengmw <chengmingwei_1984122@126.com>
Date: Tue, 31 Mar 2026 15:47:47 +0800
Subject: [PATCH] feat: 优化外呼组件和 Tab 容器功能

---
 web-app/src/components/dashboard/TabContainer.jsx |  235 ++++++++++++++++++++--------------------------------------
 1 files changed, 83 insertions(+), 152 deletions(-)

diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index a102314..90b1e5e 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;
 
@@ -133,7 +135,7 @@
             <div className="success-label">预计调解成功概率</div>
             <div className="success-change">
               
-              {/* <i className="fas fa-arrow-up"></i><span>较{updateTime} +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 }}>{roundCount}</span>
@@ -153,24 +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;
-  
+
+
+  // 格式化时间戳为 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';
@@ -188,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,
@@ -352,68 +368,6 @@
     return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
   };
 
-  // 状态控制按钮显示逻辑
-  const shouldShowControlButton = () => {
-    return caseState === 0 || caseState === 1 || caseState === 5;
-  };
-
-  const getControlButtonProps = () => {
-    if (caseState === 0 || caseState === 1) {
-      return {
-        text: '终止',
-        style: 'terminate',
-        action: 'terminate'
-      };
-    } else if (caseState === 5) {
-      return {
-        text: '恢复',
-        style: 'resume',
-        action: 'resume'
-      };
-    }
-    return null;
-  };
-
-  // 处理状态控制按钮点击
-  const handleControlButtonClick = (action) => {
-    setControlAction(action);
-    setConfirmModalVisible(true);
-  };
-
-  // 处理确认对话框确认
-  const handleConfirmOk = async () => {
-    if (!controlAction) return;
-    
-    setControlLoading(true);
-    try {
-      const params = getMergedParams();
-      const actionCode = controlAction === 'terminate' ? 0 : 1;
-      
-      await ProcessAPIService.updateMediationState(params.caseId, {
-        action: actionCode,
-        userName: localStorage.getItem('userName') || '调解员'
-      });
-      
-      message.success('案件状态更新成功');
-      setConfirmModalVisible(false);
-      setRemark('');
-      
-      // 刷新数据
-      refreshData();
-    } catch (error) {
-      console.error('状态更新失败:', error);
-      message.error(error.message || '状态更新失败,请稍后重试');
-    } finally {
-      setControlLoading(false);
-    }
-  };
-
-  // 处理确认对话框取消
-  const handleConfirmCancel = () => {
-    setConfirmModalVisible(false);
-    setControlAction(null);
-    setRemark('');
-  };
   return (
     <>
       <div className="mediation-summary" style={{
@@ -462,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>
@@ -508,76 +495,13 @@
           </div>
         ))}
       </div>
-
-      {/* 状态控制按钮区域 */}
-      {shouldShowControlButton() && (
-        <div style={{
-          marginTop: 20,
-          paddingTop: 15,
-          borderTop: '1px solid #e9ecef',
-          display: 'flex',
-          justifyContent: 'center',
-          gap: 12
-        }}>
-          <button
-            onClick={() => handleControlButtonClick(getControlButtonProps().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',
-              ...(getControlButtonProps().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>{getControlButtonProps().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}
+      />
     </>
   );
 };
@@ -1797,3 +1721,10 @@
 };
 
 export default TabContainer;
+
+
+
+
+
+
+

--
Gitblit v1.8.0