2 files added
5 files modified
21077 ■■■■■ changed files
cloud-melody-front-build.zip patch | view | raw | blame | history
web-app/package-lock.json 18009 ●●●●● patch | view | raw | blame | history
web-app/src/components/common/OutboundCallWidget.jsx 24 ●●●●● patch | view | raw | blame | history
web-app/src/config/env.js 8 ●●●● patch | view | raw | blame | history
web-app/src/contexts/CaseDataContext.jsx 43 ●●●● patch | view | raw | blame | history
web-app/src/services/ProcessAPIService.js 23 ●●●● patch | view | raw | blame | history
web-app/yarn.lock 2970 ●●●● patch | view | raw | blame | history
cloud-melody-front-build.zip
Binary files differ
web-app/package-lock.json
New file
Diff too large
web-app/src/components/common/OutboundCallWidget.jsx
@@ -110,8 +110,14 @@
        }
      });
      
      // 获取成功任务的 personId 集合
      const successPersonIds = new Set(successJobs.map(job => job.personId));
      // 过滤掉已有成功任务的 personId 对应的失败任务(成功任务优先)
      const filteredFailedJobs = uniqueFailedJobs.filter(job => !successPersonIds.has(job.personId));
      // 合并所有任务
      return [...successJobs, ...uniqueFailedJobs];
      return [...successJobs, ...filteredFailedJobs];
    } catch (err) {
      console.error('读取外呼任务失败:', err);
      return [];
@@ -335,15 +341,26 @@
  // 定时轮询通话状态
  useEffect(() => {
    // 组件挂载时设置为 true
    isMountedRef.current = true;
    // 初始加载
    fetchCallStatus();
    
    // 设置轮询定时器(10秒间隔)
    const interval = setInterval(fetchCallStatus, POLL_INTERVAL);
    
    // 监听外呼任务更新事件(立即刷新)
    const handleOutboundJobsUpdated = () => {
      console.log('收到外呼任务更新事件,立即刷新');
      fetchCallStatus();
    };
    window.addEventListener('outbound-jobs-updated', handleOutboundJobsUpdated);
    // 清理函数
    return () => {
      clearInterval(interval);
      window.removeEventListener('outbound-jobs-updated', handleOutboundJobsUpdated);
      isMountedRef.current = false;
    };
  }, [fetchCallStatus]);
@@ -385,6 +402,11 @@
    return true;
  });
  // 如果没有活跃任务且不可见,不渲染任何内容
  if (activeCalls.length === 0 && !isVisible) {
    return null;
  }
  // 如果最小化,显示AI客服图标
  if (isMinimized) {
    return (
web-app/src/config/env.js
@@ -25,9 +25,9 @@
  },
  [ENV_TYPES.SIT]: {
    // 集成测试环境 - 通过 Nginx 代理访问后端,避免跨域
    baseURL: 'http://localhost:9015',
    baseURL: '',  // 使用相对路径,通过Nginx代理转发
    timeout: 30000,
    withCredentials: true,
    withCredentials: false,
    name: '集成测试环境'
  },
  [ENV_TYPES.UAT]: {
@@ -38,8 +38,8 @@
    name: '用户验收环境'
  },
  [ENV_TYPES.PRD]: {
    // 生产环境
    baseURL: 'https://api.example.com',
    // 生产环境 - 通过 Nginx 代理访问后端
    baseURL: '',  // 使用相对路径,通过Nginx代理转发
    timeout: 30000,
    withCredentials: true,
    name: '生产环境'
web-app/src/contexts/CaseDataContext.jsx
@@ -143,9 +143,10 @@
              personId: item.personId,
              mediationId: item.mediationId,
              caseId: String(caseId), // 添加 caseId 字段用于轮询
              perTypeName: item.perTypeName || '', // 当事人类型名称(申请方当事人/被申请方当事人)
              perClassName: item.perClassName || '', // 添加人员类型名称
              trueName: item.trueName || '', // 添加真实姓名
              startTime: item.createdTime || item.start_time,
              startTime: item.createTime || item.createdTime || item.start_time, // 兼容 createTime 和 createdTime
              pollStartTime: Date.now(),
              retryCount: 0
            });
@@ -153,6 +154,7 @@
            failedJobs.push({
              personId: item.personId,
              message: item.message || '未知错误',
              perTypeName: item.perTypeName || '', // 当事人类型名称(申请方当事人/被申请方当事人)
              perClassName: item.perClassName || '', // 添加人员类型名称
              trueName: item.trueName || '', // 添加真实姓名
              errorCode: item.errorCode, // 添加错误码
@@ -163,19 +165,18 @@
        // 存储成功的任务到 localStorage
        if (successJobs.length > 0) {
          // 新任务发起时,先清除所有旧的成功任务(替换而不是追加)
          localStorage.setItem(OUTBOUND_JOBS_KEY, JSON.stringify(successJobs));
          console.log('存储外呼任务成功,数量:', successJobs.length);
          
          // 外呼成功后,清除对应的失败记录
          const storedFailedJobs = JSON.parse(localStorage.getItem(`${OUTBOUND_JOBS_KEY}_failed`) || '[]');
          if (storedFailedJobs.length > 0) {
            // 获取成功任务的 personId 列表
            const successPersonIds = successJobs.map(job => job.personId);
            // 过滤掉已成功的 personId 对应的失败记录
            const remainingFailedJobs = storedFailedJobs.filter(job => !successPersonIds.includes(job.personId));
            localStorage.setItem(`${OUTBOUND_JOBS_KEY}_failed`, JSON.stringify(remainingFailedJobs));
            console.log('外呼成功后清除失败记录,清除数量:', storedFailedJobs.length - remainingFailedJobs.length);
          }
          // 外呼成功后,清除所有失败记录(新任务开始时应清空旧记录)
          localStorage.removeItem(`${OUTBOUND_JOBS_KEY}_failed`);
          console.log('外呼成功后清除所有失败记录');
          // 触发自定义事件,通知 OutboundCallWidget 组件立即刷新
          setTimeout(() => {
            window.dispatchEvent(new CustomEvent('outbound-jobs-updated'));
          }, 300);
        }
        // 存储失败的任务到 localStorage(用于气泡显示)
@@ -384,11 +385,13 @@
      }
      // 调用API获取数据
      // 将URL中的auth_token转换为authorization传入API
      const response = await ProcessAPIService.getCaseProcessInfo(
        params.caseId,
        {
          caseTypeFirst: params.caseTypeFirst,
          platform_code: params.platform_code
          platform_code: params.platform_code,
          authorization: params.auth_token || params.authorization || ''
        }
      );
@@ -407,15 +410,13 @@
      setHasLoaded(true);  // 标记已加载
      
      // 并行加载证据材料和调解协议数据(在终态检查之前,确保数据完整性)
      await Promise.all([
        loadEvidenceData({
          caseId: params.caseId,
          caseType: params.caseType || params.caseTypeFirst,
          caseTypeFirst: params.caseTypeFirst,
          platformCode: params.platform_code
        }),
        loadAgreementData(params.caseId)
      ]);
      await loadEvidenceData({
        caseId: params.caseId,
        caseType: params.caseType || params.caseTypeFirst,
        caseTypeFirst: params.caseTypeFirst,
        platformCode: params.platform_code
      });
      loadAgreementData(params.caseId);
      
      // 检查终态状态(调解成功/失败/人工接管),终态不执行外呼和存储
      const mediationState = timelineData.mediation?.state;
web-app/src/services/ProcessAPIService.js
@@ -39,10 +39,14 @@
   * @param {Object} params - 查询参数
   * @param {string} params.caseTypeFirst - 案件一级分类
   * @param {string} params.platform_code - 外部平台编号
   * @param {string} params.authorization - 授权token
   * @returns {Promise} 调解时间线数据
   */
  static getMediationTimeline(caseId, params = {}) {
    return request.get(`/api/v1/mediation-timeline/v2/case/${caseId}`, params);
    const { authorization, ...queryParams } = params;
    queryParams.authorization = authorization;
    const config = authorization ? { headers: { Authorization: authorization } } : {};
    return request.get(`/api/v1/mediation-timeline/v2/case/${caseId}`, queryParams, config);
  }
  /**
@@ -52,10 +56,13 @@
   * @param {string} params.caseTypeFirst - 案件一级分类
   * @param {string} params.platformCode - 外部平台编号
   * @param {string} params.caseId - 案件ID
   * @param {string} params.authorization - 授权token
   * @returns {Promise} 流程节点列表
   */
  static getProcessNodes(params = {}) {
    return request.get('/api/v1/process/node', params);
    const { authorization, ...queryParams } = params;
    const config = authorization ? { headers: { Authorization: authorization } } : {};
    return request.get('/api/v1/process/node', queryParams, config);
  } 
@@ -65,6 +72,7 @@
   * @param {Object} params - 查询参数
   * @param {string} params.caseTypeFirst - 案件一级分类
   * @param {string} params.platform_code - 外部平台编号
   * @param {string} params.authorization - 授权token(来自URL的auth_token)
   * @returns {Promise} 完整流程信息
   */
  static async getCaseProcessInfo(caseId, params = {}) {
@@ -74,13 +82,18 @@
      const nodeParams = {
        caseTypeFirst: params.caseTypeFirst,
        platformCode: params.platform_code,
        caseId
        caseId,
        authorization: params.authorization
      };
      
      // 提取authorization用于子请求
      const { authorization, ...timelineParams } = params;
      console.log('Timeline params:', timelineParams);
      timelineParams.authorization = authorization;
      // 并行获取时间线和流程节点
      const promises = [
        this.getMediationTimeline(caseId, params),
        this.getProcessNodes(nodeParams)
        this.getMediationTimeline(caseId, { ...timelineParams, authorization }),
        this.getProcessNodes({ ...nodeParams, authorization })
      ];
      const results = await Promise.all(promises);
web-app/yarn.lock
Diff too large