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';
|
|
/**
|
* 类案与法条推荐 - 弹窗内容
|
* 按原型 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(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));
|
};
|
|
const handleSelectLaw = (id) => {
|
setActiveLawId(id);
|
};
|
|
// 加载更多案例
|
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">
|
{/* 左侧:相似典型案例TOP{loadedCount} */}
|
<section className="cases-section">
|
<h2 className="similar-section-title">
|
<i className="fas fa-folder-open"></i>
|
相似典型案例TOP{loadedCount}
|
</h2>
|
|
<div className="cases-list">
|
{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={
|
(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>
|
</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>
|
|
<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>
|
)}
|
|
{(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>
|
)}
|
|
{(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>
|
)}
|
|
{(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>
|
)}
|
|
{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 && 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>
|
)}
|
|
{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 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>
|
|
{/* 右侧:相关专业法条 */}
|
<section className="laws-section">
|
<h2 className="similar-section-title">
|
<i className="fas fa-book"></i>
|
相关专业法条
|
</h2>
|
|
<div className="laws-count">
|
与本案相关的法律条文共 <strong>{displayLaws.length}条</strong>
|
</div>
|
|
<div className="laws-list">
|
{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>
|
{/* 法条内容区域 */}
|
{(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.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>
|
)}
|
</div>
|
</section>
|
</div>
|
);
|
};
|
|
export default SimilarCaseContent;
|