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/call-record/CallRecordModal.jsx | 243 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 243 insertions(+), 0 deletions(-)
diff --git a/web-app/src/components/call-record/CallRecordModal.jsx b/web-app/src/components/call-record/CallRecordModal.jsx
new file mode 100644
index 0000000..aaad755
--- /dev/null
+++ b/web-app/src/components/call-record/CallRecordModal.jsx
@@ -0,0 +1,243 @@
+import React, { useState, useEffect } from 'react';
+import { Modal, Spin, Button, message } from 'antd';
+import { ReloadOutlined } from '@ant-design/icons';
+import OutboundBotAPIService from '../../services/OutboundBotAPIService';
+import AudioPlayer from './AudioPlayer';
+import ConversationList from './ConversationList';
+import './CallRecordModal.css';
+
+// 已接通状态值(数字格式)
+const CONNECTED_STATUSES_NUM = [1, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
+// 已接通状态值(字符串格式,如API返回"Succeeded")
+const CONNECTED_STATUSES_STR = ['Succeeded', 'succeeded', 'Success', 'success'];
+
+/**
+ * 判断是否为已接通状态
+ * 兼容数字和字符串两种状态格式
+ */
+const isConnectedStatus = (callStatus) => {
+ if (callStatus === null || callStatus === undefined) return false;
+ // 如果是数字
+ if (typeof callStatus === 'number') {
+ return CONNECTED_STATUSES_NUM.includes(callStatus);
+ }
+ // 如果是字符串(可能是"Succeeded"等)
+ if (typeof callStatus === 'string') {
+ return CONNECTED_STATUSES_STR.includes(callStatus);
+ }
+ return false;
+};
+
+/**
+ * 获取caseId
+ */
+const getCaseId = () => {
+ // 优先从URL参数获取
+ const urlParams = new URLSearchParams(window.location.search);
+ const caseIdFromUrl = urlParams.get('caseId');
+ if (caseIdFromUrl) return caseIdFromUrl;
+
+ // 从localStorage获取
+ try {
+ const timelineData = localStorage.getItem('case_data_timeline');
+ if (timelineData) {
+ const parsed = JSON.parse(timelineData);
+ return parsed.case_id || null;
+ }
+ } catch (e) {
+ console.error('解析localStorage数据失败:', e);
+ }
+ return null;
+};
+
+/**
+ * 解析conversations JSON字符串
+ */
+const parseConversations = (conversationsStr) => {
+ if (!conversationsStr) return [];
+ try {
+ return JSON.parse(conversationsStr);
+ } catch (e) {
+ console.error('解析对话记录失败:', e);
+ return [];
+ }
+};
+
+/**
+ * 通话记录弹窗组件
+ */
+const CallRecordModal = ({ visible, onClose, record }) => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [callData, setCallData] = useState(null);
+ const [audioBlob, setAudioBlob] = useState(null);
+ const [audioLoading, setAudioLoading] = useState(false);
+
+ // 获取通话记录数据
+ const fetchCallRecord = async () => {
+ if (!record) return;
+
+ setLoading(true);
+ setError(null);
+ setAudioBlob(null);
+
+ try {
+ const caseId = getCaseId();
+ if (!caseId) {
+ throw new Error('未找到案件ID');
+ }
+
+ const params = {
+ caseId,
+ personId: record.person_id,
+ jobId: record.job_id
+ };
+
+ const response = await OutboundBotAPIService.getConversationLog(params);
+
+ if (response && response.data && response.data.length > 0) {
+ // 取最后一条记录
+ const lastRecord = response.data[response.data.length - 1];
+ console.log('📞 通话记录数据:', lastRecord);
+ console.log('📞 callStatus值:', lastRecord.callStatus, '类型:', typeof lastRecord.callStatus);
+ setCallData(lastRecord);
+
+ // 如果有recordUrl,加载音频文件
+ const recordUrl = lastRecord.record_url || lastRecord.recordUrl;
+ if (recordUrl) {
+ loadAudioFile(recordUrl);
+ }
+ } else {
+ throw new Error('未找到通话记录');
+ }
+ } catch (err) {
+ console.error('获取通话记录失败:', err);
+ setError(err.message || '获取通话记录失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 加载音频文件
+ const loadAudioFile = async (recordUrl) => {
+ setAudioLoading(true);
+ try {
+ const blob = await OutboundBotAPIService.getAudioFile(recordUrl);
+ setAudioBlob(blob);
+ } catch (err) {
+ console.error('加载音频文件失败:', err);
+ message.error('加载录音文件失败');
+ } finally {
+ setAudioLoading(false);
+ }
+ };
+
+ // 弹窗打开时加载数据
+ useEffect(() => {
+ if (visible && record) {
+ fetchCallRecord();
+ }
+ }, [visible, record]);
+
+ // 关闭弹窗时重置状态
+ useEffect(() => {
+ if (!visible) {
+ setCallData(null);
+ setError(null);
+ setAudioBlob(null);
+ }
+ }, [visible]);
+
+ // 生成弹窗标题
+ const getModalTitle = () => {
+ if (!record) return '通话记录';
+ const creatorName = record.creator || '当事人';
+ return `AI调解员与${creatorName}的通话`;
+ };
+
+ // 渲染加载状态
+ const renderLoading = () => (
+ <div className="modal-loading">
+ <Spin tip="加载中..." />
+ </div>
+ );
+
+ // 渲染错误状态
+ const renderError = () => (
+ <div className="modal-error">
+ <p className="error-text">{error}</p>
+ <Button
+ type="primary"
+ icon={<ReloadOutlined />}
+ onClick={fetchCallRecord}
+ >
+ 重试
+ </Button>
+ </div>
+ );
+
+ // 渲染未接通状态
+ const renderNotConnected = () => (
+ <div className="modal-not-connected">
+ <p>未接通,无通话记录</p>
+ </div>
+ );
+
+ // 渲染通话记录内容
+ const renderContent = () => {
+ if (!callData) {
+ console.log('📞 renderContent: callData为空');
+ return renderNotConnected();
+ }
+
+ const callStatus = callData.call_status ?? callData.callStatus;
+ console.log('📞 renderContent: callStatus=', callStatus, 'isConnected=', isConnectedStatus(callStatus));
+
+ // 未接通状态
+ if (!isConnectedStatus(callStatus)) {
+ return renderNotConnected();
+ }
+
+ // 已接通状态
+ const conversations = parseConversations(callData.conversations);
+ const recordUrl = callData.record_url || callData.recordUrl;
+ const creatorName = record?.creator || '当事人';
+
+ return (
+ <div className="modal-content">
+ {/* 录音播放器 - 根据recordUrl和audioLoading状态显示 */}
+ <AudioPlayer
+ recordUrl={recordUrl}
+ audioBlob={audioBlob}
+ loading={audioLoading}
+ loadingText="正在加载录音..."
+ />
+
+ {/* 对话记录列表 */}
+ <ConversationList
+ conversations={conversations}
+ contactName={creatorName}
+ />
+ </div>
+ );
+ };
+
+ return (
+ <Modal
+ className="call-record-modal"
+ title={getModalTitle()}
+ open={visible}
+ onCancel={onClose}
+ footer={null}
+ width={600}
+ centered
+ destroyOnClose
+ >
+ {loading && renderLoading()}
+ {!loading && error && renderError()}
+ {!loading && !error && renderContent()}
+ </Modal>
+ );
+};
+
+export default CallRecordModal;
--
Gitblit v1.8.0