web-app/src/contexts/CaseDataContext.jsx
@@ -3,10 +3,12 @@
 * 提供案件数据的全局状态管理和localStorage持久化
 */
import React, { createContext, useContext, useState, useEffect } from 'react';
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
import { message } from 'antd';
import ProcessAPIService from '../services/ProcessAPIService';
import { getMergedParams } from '../utils/urlParams';
import { mockTimelineData } from '../mocks/timeline';
import { getFallbackStartTime, parseTimeString } from '../utils/timeFormatter';
// 创建Context
const CaseDataContext = createContext(null);
@@ -20,8 +22,12 @@
 */
export const CaseDataProvider = ({ children }) => {
  const [caseData, setCaseData] = useState(null);
  const [processNodes, setProcessNodes] = useState([]);  // 流程节点数据
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [taskStartTime, setTaskStartTime] = useState(null);  // 任务开始时间
  const [isTaskTimeFallback, setIsTaskTimeFallback] = useState(false);  // 是否降级模式
  const [hasLoaded, setHasLoaded] = useState(false);  // 防止重复加载
  /**
   * 从localStorage读取数据
@@ -50,16 +56,53 @@
  };
  /**
   * 加载任务时间数据
   */
  const loadTaskTime = async (timeline) => {
    try {
      const mediationId = timeline.id;
      const nodeId = timeline.current_node?.id;
      if (!mediationId || !nodeId) {
        throw new Error('缺少必要的参数: mediation_id 或 node_id');
      }
      console.log('Loading task time with:', { mediationId, nodeId });
      const response = await ProcessAPIService.getTaskTime(mediationId, nodeId);
      // 解析API返回的开始时间
      const startTimeStr = response.data?.startTime;
      if (startTimeStr) {
        const startTime = parseTimeString(startTimeStr);
        if (startTime) {
          setTaskStartTime(startTime);
          setIsTaskTimeFallback(false);
          console.log('Task start time loaded:', new Date(startTime).toLocaleString());
          return;
        }
      }
      throw new Error('无法解析开始时间');
    } catch (err) {
      console.error('Failed to load task time:', err);
      // 降级到本地计时
      const fallbackTime = getFallbackStartTime();
      setTaskStartTime(fallbackTime);
      setIsTaskTimeFallback(true);
      message.warning('任务时间API不可用,使用本地计时');
      console.log('Using fallback start time:', new Date(fallbackTime).toLocaleString());
    }
  };
  /**
   * 加载案件数据
   */
  const loadCaseData = async (forceRefresh = false) => {
    // 如果不是强制刷新,先尝试从localStorage读取
    if (!forceRefresh) {
      const cachedData = loadFromStorage();
      if (cachedData) {
        setCaseData(cachedData);
        console.log('Loaded case data from localStorage');
      }
    // 防止重复加载(除非强制刷新)
    if (hasLoaded && !forceRefresh) {
      console.log('Data already loaded, skipping...');
      return;
    }
    setLoading(true);
@@ -82,12 +125,23 @@
      // 提取timeline数据
      const timelineData = response.timeline || response.data?.timeline || response;
      // 提取nodes数据(确保为数组)
      const nodesData = response.nodes || response.data?.nodes || [];
      console.log('API Response:', response);
      console.log('Extracted nodesData:', nodesData);
      // 更新状态
      setCaseData(timelineData);
      setProcessNodes(Array.isArray(nodesData) ? nodesData : []);  // 确保为数组
      setHasLoaded(true);  // 标记已加载
      
      // 保存到localStorage
      saveToStorage(timelineData);
      // 加载任务时间数据
      await loadTaskTime(timelineData);
      console.log('Case data loaded successfully:', timelineData);
    } catch (err) {
@@ -97,12 +151,19 @@
      // 显示错误提示
      message.error('加载案件数据失败,请稍后重试');
      
      // 如果localStorage有数据,尝试使用缓存
      const cachedData = loadFromStorage();
      if (cachedData && !forceRefresh) {
        message.warning('已加载历史数据');
        setCaseData(cachedData);
      }
      // 使用Mock数据(缓存数据不包含nodes,所以统一使用Mock)
      console.log('===== 使用Mock数据 =====');
      const mockData = mockTimelineData.data.timeline;
      const mockNodes = mockTimelineData.data.nodes || [];
      console.log('mockData:', mockData);
      console.log('mockNodes:', mockNodes);
      setCaseData(mockData);
      setProcessNodes(mockNodes);
      saveToStorage(mockData);
      setHasLoaded(true);
      // Mock数据也加载任务时间
      await loadTaskTime(mockData);
    } finally {
      setLoading(false);
    }
@@ -115,21 +176,39 @@
    loadCaseData(true);
  };
  // 防止重复加载的引用标识
  const loadInitRef = useRef(false);
  /**
   * 组件挂载时加载数据
   */
  useEffect(() => {
    loadCaseData();
    console.log('===== CaseDataContext useEffect triggered =====');
    console.log('loadInitRef.current:', loadInitRef.current);
    console.log('hasLoaded:', hasLoaded);
    console.log('processNodes:', processNodes);
    // StrictMode下防止双重调用
    if (!loadInitRef.current) {
      loadInitRef.current = true;
      console.log('Calling loadCaseData()...');
      loadCaseData();
    } else {
      console.log('loadCaseData() skipped due to loadInitRef.current === true');
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // 只在挂载时执行一次
  // 提供的值
  const value = {
    caseData,
    processNodes,      // 流程节点数据
    loading,
    error,
    refreshData,
    loadCaseData
    loadCaseData,
    taskStartTime,      // 任务开始时间
    isTaskTimeFallback  // 是否降级模式
  };
  return (