chengmw
2026-04-03 d1034fc30e99091220ec867785e29e80be5c66b4
web-app/src/components/tools/SimilarCaseContent.jsx
@@ -1,5 +1,7 @@
import React, { useState } from 'react';
import { mockSimilarCases, mockRelatedLaws } from '../../mocks/similarCaseMocks';
import React, { useState, useEffect, useCallback } from 'react';
import { useCaseData } from '../../contexts/CaseDataContext';
import RecommendAPIService from '../../services/RecommendAPIService';
import { Spin, Alert } from 'antd';
import './SimilarCaseContent.css';
/**
@@ -7,9 +9,107 @@
 * 按原型 similar_case.html 和 UI 图实现
 */
const SimilarCaseContent = () => {
  // 状态管理
  const [cases, setCases] = useState([]);
  const [laws, setLaws] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [expandedId, setExpandedId] = useState(null);
  const [activeLawId, setActiveLawId] = useState(mockRelatedLaws[0]?.id || null);
  const [activeLawId, setActiveLawId] = useState(null);
  // 分页加载状态
  const [loadedCount, setLoadedCount] = useState(3); // 默认加载3条
  // 获取timeline数据
  const { caseData: timeline } = useCaseData();
  // 参数构建函数
  const buildRequestParams = (timelineData) => {
    return {
      caseDes: timelineData.case_des || '',
      caseClaim: timelineData.case_claim || '',
      caseId: timelineData.case_id
    };
  };
  // API调用函数
  const loadRecommendations = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const params = buildRequestParams(timeline);
      // 并行调用类案和法条推荐API
      const [casesResult, lawsResult] = await Promise.all([
        RecommendAPIService.getSimilarCases({
          caseDes: params.caseDes,
          caseClaim: params.caseClaim,
          caseId: params.caseId
        }),
        RecommendAPIService.getSimilarLaws({
          caseDes: params.caseDes,
          caseClaim: params.caseClaim,
          caseId: params.caseId
        })
      ]);
      // 从API返回的data中提取similarCases和provisions/laws数组
      const casesData = casesResult.data?.similarCases || casesResult.data?.cases || [];
      const lawsData = lawsResult.data?.provisions || lawsResult.data?.laws || [];
      console.log('类案推荐数据:', casesData);
      console.log('法条推荐数据:', lawsData);
      setCases(casesData);
      setLaws(lawsData);
      // 设置默认选中的法条
      if (lawsData && lawsData.length > 0) {
        setActiveLawId(lawsData[0].lawProvisionId || lawsData[0].id || lawsData[0].lawId || null);
      }
    } catch (err) {
      setError(err.message);
      console.error('获取推荐数据失败:', err);
      // 不使用mock数据降级,直接显示错误
      setCases([]);
      setLaws([]);
      setActiveLawId(null);
    } finally {
      setLoading(false);
    }
  }, [timeline]);
  // 自动加载数据
  useEffect(() => {
    if (timeline && (timeline.caseDes || timeline.case_claim || timeline.case_id)) {
      loadRecommendations();
    }
  }, [loadRecommendations]);
  // 使用API数据,如果没有数据则显示空状态,不使用mock数据
  // 确保displayCases和displayLaws始终为数组类型,避免TypeError
  const displayCases = Array.isArray(cases) ? cases : [];
  const displayLaws = Array.isArray(laws) ? laws : [];
  const activeLaw = displayLaws.length > 0 ? (displayLaws.find((law) => (law.lawProvisionId || law.id) === activeLawId) || displayLaws[0]) : null;
  // 分页显示的案例(默认3条,每次加载3条)
  const displayedCases = displayCases.slice(0, loadedCount);
  const hasMore = loadedCount < displayCases.length;
  const totalCases = displayCases.length;
  // 相似度分级函数
  const getSimilarityLevel = (score) => {
    const numScore = typeof score === 'string' ? parseFloat(score) : score;
    if (numScore >= 0.6) {
      return { text: '极高相似度', className: 'extreme-similarity' };
    }
    if (numScore >= 0.5) {
      return { text: '高相似度', className: 'high-similarity' };
    }
    return { text: '一般相似度', className: 'normal-similarity' };
  };
  // 事件处理函数
  const handleToggleCase = (id) => {
    setExpandedId((prev) => (prev === id ? null : id));
  };
@@ -17,139 +117,238 @@
  const handleSelectLaw = (id) => {
    setActiveLawId(id);
  };
  const activeLaw = mockRelatedLaws.find((law) => law.id === activeLawId) || mockRelatedLaws[0];
  // 加载更多案例
  const handleLoadMore = () => {
    setLoadedCount((prev) => Math.min(prev + 3, totalCases));
  };
  // 渲染Loading状态
  if (loading) {
    return (
      <div className="similar-case-container" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '600px' }}>
        <Spin size="large" tip="正在加载推荐数据..." />
      </div>
    );
  }
  // 渲染错误状态
  if (error) {
    return (
      <div className="similar-case-container">
        <Alert
          message="数据加载失败"
          description={error}
          type="error"
          showIcon
          style={{ marginBottom: '20px' }}
        />
        <div style={{ textAlign: 'center', padding: '40px' }}>
          <p>暂时无法获取推荐数据,请稍后重试。</p>
        </div>
      </div>
    );
  }
  return (
    <div className="similar-case-container">
      {/* 左侧:相似典型案例TOP3 */}
      {/* 左侧:相似典型案例TOP{loadedCount} */}
      <section className="cases-section">
        <h2 className="similar-section-title">
          <i className="fas fa-folder-open"></i>
          相似典型案例TOP3
          相似典型案例TOP{loadedCount}
        </h2>
        <div className="cases-list">
          {mockSimilarCases.map((item) => {
            const isExpanded = expandedId === item.id;
            return (
              <div className="case-card" key={item.id}>
                <div
                  className={isExpanded ? 'case-header active' : 'case-header'}
                  onClick={() => handleToggleCase(item.id)}
                >
                  <div className="case-title-container">
                    <h3 className="case-title">
                      {item.title}
                      <span className="similarity-tag">
                        <i className="fas fa-chart-line"></i>
                        {item.similarity}
                      </span>
                    </h3>
                    <div className="case-meta-container">
                      <div className="case-meta">
                        <div className="case-meta-item">
                          <i className="far fa-calendar-alt"></i>
                          <span>发生时间:{item.date}</span>
          {displayedCases.length > 0 ? (
            displayedCases.map((item) => {
              const isExpanded = expandedId === (item.cpwsCaseTextId || item.id || item.caseId);
              const similarity = item.similarity || item.score;
              const similarityLevel = similarity ? getSimilarityLevel(similarity) : null;
              return (
                <div className="case-card" key={item.cpwsCaseTextId || item.id || item.caseId}>
                  <div
                    className={isExpanded ? 'case-header active' : 'case-header'}
                    onClick={() => handleToggleCase(item.cpwsCaseTextId || item.id || item.caseId)}
                  >
                    <div className="case-title-container">
                      <h3 className="case-title">
                        {item.caseName || item.title || item.caseTitle || '未命名案例'}
                        {similarityLevel && (
                          <span className={`similarity-tag ${similarityLevel.className}`}>
                            <i className="fas fa-chart-line"></i>
                            {similarityLevel.text}
                          </span>
                        )}
                      </h3>
                      <div className="case-meta-container">
                        <div className="case-meta">
                          {item.caseNumber && (
                            <div className="case-meta-item">
                              <i className="fas fa-file-alt"></i>
                              <span>案号:{item.caseNumber}</span>
                            </div>
                          )}
                          {(item.date || item.occurTime || item.judgmentDate) && (
                            <div className="case-meta-item">
                              <i className="far fa-calendar-alt"></i>
                              <span>日期:{item.judgmentDate || item.date || item.occurTime}</span>
                            </div>
                          )}
                          {(item.court || item.location) && (
                            <div className="case-meta-item">
                              <i className="fas fa-map-marker-alt"></i>
                              <span>{item.court ? '法院:' : '地点:'}{item.court || item.location}</span>
                            </div>
                          )}
                          {item.caseType && (
                            <div className="case-meta-item">
                              <i className="fas fa-balance-scale"></i>
                              <span>案由类型:{item.caseType}</span>
                            </div>
                          )}
                        </div>
                        <div className="case-meta-item">
                          <i className="fas fa-map-marker-alt"></i>
                          <span>发生地点:{item.location}</span>
                        <div
                          className={
                            (item.caseType || item.type) === 'mediation'
                              ? 'case-type-badge mediation'
                              : 'case-type-badge judgment'
                          }
                        >
                          <i className={(item.caseType || item.type) === 'mediation' ? 'fas fa-handshake' : 'fas fa-gavel'}></i>
                          {(item.caseType || item.type) === 'mediation' ? '调解案例' : '判决文书'}
                        </div>
                        <div className="case-meta-item">
                          <i className="fas fa-balance-scale"></i>
                          <span>纠纷类型:{item.type}</span>
                        </div>
                      </div>
                      <div
                        className={
                          item.caseType === 'mediation'
                            ? 'case-type-badge mediation'
                            : 'case-type-badge judgment'
                        }
                      >
                        <i className={item.caseType === 'mediation' ? 'fas fa-handshake' : 'fas fa-gavel'}></i>
                        {item.caseType === 'mediation' ? '调解案例' : '判决文书'}
                      </div>
                    </div>
                    <button className="toggle-btn" onClick={() => handleToggleCase(item.cpwsCaseTextId || item.id || item.caseId)}>
                      <i className={isExpanded ? 'fas fa-chevron-up' : 'fas fa-chevron-down'}></i>
                    </button>
                  </div>
                  <button className="toggle-btn" onClick={() => handleToggleCase(item.id)}>
                    <i className={isExpanded ? 'fas fa-chevron-up' : 'fas fa-chevron-down'}></i>
                  </button>
                </div>
                <div className={isExpanded ? 'case-content expanded' : 'case-content'}>
                  <div className="case-detail">
                    {item.overview && (
                      <div className="detail-section">
                        <h4 className="detail-title">案例概述</h4>
                        <div className="detail-content">
                          <p>{item.overview}</p>
                  <div className={isExpanded ? 'case-content expanded' : 'case-content'}>
                    <div className="case-detail">
                      {(item.basicCaseInfo || item.overview || item.caseOverview) && (
                        <div className="detail-section">
                          <h4 className="detail-title">案例概述</h4>
                          <div className="detail-content">
                            <p>{item.basicCaseInfo || item.overview || item.caseOverview}</p>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                    {item.background && (
                      <div className="detail-section">
                        <h4 className="detail-title">调解/审理背景</h4>
                        <div className="detail-content">
                          <p>{item.background}</p>
                      {(item.background || item.caseBackground) && (
                        <div className="detail-section">
                          <h4 className="detail-title">调解/审理背景</h4>
                          <div className="detail-content">
                            <p>{item.background || item.caseBackground}</p>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                    {item.plaintiffDemand && item.plaintiffDemand.length > 0 && (
                      <div className="detail-section plaintiff-demand">
                        <h4 className="detail-title">原告诉讼请求</h4>
                        <div className="detail-content">
                          <ol>
                            {item.plaintiffDemand.map((demand, index) => (
                              <li key={index}>{demand}</li>
                            ))}
                          </ol>
                      {(item.plaintiffDemand || item.demands) && Array.isArray(item.plaintiffDemand || item.demands) && (
                        <div className="detail-section plaintiff-demand">
                          <h4 className="detail-title">原告诉讼请求</h4>
                          <div className="detail-content">
                            <ol>
                              {(item.plaintiffDemand || item.demands).map((demand, index) => (
                                <li key={index}>{demand}</li>
                              ))}
                            </ol>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                    {item.courtDecision && (
                      <div className="detail-section court-decision">
                        <h4 className="detail-title">法院审理与判决</h4>
                        <div className="detail-content">
                          <p>{item.courtDecision}</p>
                      {(item.judgment || item.courtDecision) && (
                        <div className="detail-section court-decision">
                          <h4 className="detail-title">法院审理与判决</h4>
                          <div className="detail-content">
                            <p>{item.judgment || item.courtDecision}</p>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                      {item.legalBasis && (
                        <div className="detail-section">
                          <h4 className="detail-title">法律依据</h4>
                          <div className="detail-content">
                            <p>{item.legalBasis}</p>
                          </div>
                        </div>
                      )}
                      {item.trialFinding && (
                        <div className="detail-section">
                          <h4 className="detail-title">审理查明</h4>
                          <div className="detail-content">
                            <p>{item.trialFinding}</p>
                          </div>
                        </div>
                      )}
                      {item.trialProcess && (
                        <div className="detail-section">
                          <h4 className="detail-title">审理经过</h4>
                          <div className="detail-content">
                            <p>{item.trialProcess}</p>
                          </div>
                        </div>
                      )}
                    {item.mediationScheme && item.mediationScheme.length > 0 && (
                      <div className="detail-section mediation-scheme">
                        <h4 className="detail-title">调解方案</h4>
                        <div className="detail-content">
                          <ul>
                            {item.mediationScheme.map((scheme, index) => (
                              <li key={index}>{scheme}</li>
                            ))}
                          </ul>
                      {item.mediationScheme && Array.isArray(item.mediationScheme) && (
                        <div className="detail-section mediation-scheme">
                          <h4 className="detail-title">调解方案</h4>
                          <div className="detail-content">
                            <ul>
                              {item.mediationScheme.map((scheme, index) => (
                                <li key={index}>{scheme}</li>
                              ))}
                            </ul>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                    {item.mediationResult && item.mediationResult.length > 0 && (
                      <div className="detail-section mediation-result">
                        <h4 className="detail-title">调解结果</h4>
                        <div className="detail-content">
                          <ol>
                            {item.mediationResult.map((r, index) => (
                              <li key={index}>{r}</li>
                            ))}
                          </ol>
                      {item.mediationResult && Array.isArray(item.mediationResult) && (
                        <div className="detail-section mediation-result">
                          <h4 className="detail-title">调解结果</h4>
                          <div className="detail-content">
                            <ol>
                              {item.mediationResult.map((r, index) => (
                                <li key={index}>{r}</li>
                              ))}
                            </ol>
                          </div>
                        </div>
                      </div>
                    )}
                      )}
                    </div>
                  </div>
                </div>
              );
            })
          ) : (
            <div className="empty-state">
              <div className="empty-icon">
                <i className="fas fa-file-alt"></i>
              </div>
            );
          })}
              <p className="empty-text">暂无类案推荐</p>
            </div>
          )}
          {/* 加载更多按钮 */}
          {displayedCases.length > 0 && (
            <div className="load-more-container">
              {hasMore ? (
                <button className="load-more-btn" onClick={handleLoadMore}>
                  <i className="fas fa-angle-down"></i>
                  加载更多
                </button>
              ) : (
                <div className="no-more-data">
                  <i className="fas fa-check-circle"></i>
                  没有更多案例数据
                </div>
              )}
            </div>
          )}
        </div>
      </section>
@@ -161,54 +360,82 @@
        </h2>
        <div className="laws-count">
          与本案相关的法律条文共 <strong>{mockRelatedLaws.length}条</strong>
          与本案相关的法律条文共 <strong>{displayLaws.length}条</strong>
        </div>
        <div className="laws-list">
          {mockRelatedLaws.map((law) => (
            <div
              key={law.id}
              className={law.id === activeLawId ? 'law-card active' : 'law-card'}
              onClick={() => handleSelectLaw(law.id)}
            >
              <h3 className="law-title">{law.name}</h3>
              <div className="law-meta">
                <div className="law-meta-item">
                  <i className="fas fa-check-circle" style={{ color: 'var(--success-color)' }}></i>
                  <span>时效性:{law.status}</span>
          {displayLaws.length > 0 ? (
            displayLaws.map((law) => (
              <div
                key={law.lawProvisionId || law.id || law.lawId}
                className={(law.lawProvisionId || law.id) === activeLawId ? 'law-card active' : 'law-card'}
                onClick={() => handleSelectLaw(law.lawProvisionId || law.id || law.lawId)}
              >
                <h3 className="law-title">{law.lawTitle || '未命名法条'}</h3>
                <div className="law-meta">
                  {law.lawValidityName && (
                    <div className="law-meta-item">
                      <i className="fas fa-check-circle" style={{ color: 'var(--success-color)' }}></i>
                      <span>时效性:{law.lawValidityName}</span>
                    </div>
                  )}
                  {law.authorityName && (
                    <div className="law-meta-item">
                      <i className="fas fa-landmark"></i>
                      <span>制定机关:{law.authorityName}</span>
                    </div>
                  )}
                </div>
                <div className="law-meta-item">
                  <i className="fas fa-landmark"></i>
                  <span>制定机关:{law.authority}</span>
                </div>
                <div className="law-meta-item">
                  <i className="far fa-calendar-alt"></i>
                  <span>公布日期:{law.publishDate}</span>
                </div>
              </div>
              {/* 法条内容区域 */}
              <div className="law-content">
                {law.articles.map((article, index) => (
                  <div className="law-article" key={index}>
                    <span className="article-number">{article.number}</span>
                    <span>{article.content}</span>
                {/* 法条内容区域 */}
                {(law.provisionIndex && law.provisionText) && (
                  <div className="law-content">
                    <div className="law-article">
                      <span className="article-number">{law.provisionIndex}</span>
                      <span>{law.provisionText}</span>
                    </div>
                  </div>
                ))}
                )}
              </div>
            ))
          ) : (
            <div className="empty-state">
              <div className="empty-icon">
                <i className="fas fa-gavel"></i>
              </div>
              <p className="empty-text">暂无相关专业法条数据</p>
            </div>
          ))}
          )}
          {/* 当前选中法条详情 - 放在 laws-list 内部实现整体滚动 */}
          {activeLaw && (
            <div className="law-detail-panel">
              <h3 className="law-detail-title">{activeLaw.name}</h3>
              <div className="law-detail-content">
                {activeLaw.articles.map((article, index) => (
                  <div className="law-article" key={index}>
                    <span className="article-number">{article.number}</span>
                    <span>{article.content}</span>
              <h3 className="law-detail-title">{activeLaw.lawTitle || '未命名法条'}</h3>
              {/* 添加时效性和制定机关信息 */}
              <div className="law-meta" style={{ marginBottom: '15px' }}>
                {activeLaw.lawValidityName && (
                  <div className="law-meta-item">
                    <i className="fas fa-check-circle" style={{ color: 'var(--success-color)' }}></i>
                    <span>时效性:{activeLaw.lawValidityName}</span>
                  </div>
                ))}
                )}
                {activeLaw.authorityName && (
                  <div className="law-meta-item">
                    <i className="fas fa-landmark"></i>
                    <span>制定机关:{activeLaw.authorityName}</span>
                  </div>
                )}
              </div>
              <div className="law-detail-content">
                {(activeLaw.provisionIndex && activeLaw.provisionText) ? (
                  <div className="law-article">
                    <span className="article-number">{activeLaw.provisionIndex}</span>
                    <span>{activeLaw.provisionText}</span>
                  </div>
                ) : (
                  <p>暂无详细内容</p>
                )}
              </div>
            </div>
          )}