import React, { useState, useEffect } from 'react';
|
import { Input, Button, Spin, Pagination, Select, Modal, Radio, message } from 'antd';
|
import { SearchOutlined, RedoOutlined } from '@ant-design/icons';
|
import CaseAPIService from '../../services/CaseAPIService';
|
import TypicalCaseDetailContent from './TypicalCaseDetailContent';
|
import './TypicalCaseSearch.css';
|
|
/**
|
* 典型案例查询内容组件 - 与原型 case_search.html 保持一致
|
*/
|
const CaseSearchContent = () => {
|
const [loading, setLoading] = useState(false);
|
const [list, setList] = useState([]);
|
const [total, setTotal] = useState(0);
|
const [keyword, setKeyword] = useState('');
|
const [caseType, setCaseType] = useState('judgment'); // 'judgment' | 'mediation'
|
const [disputeType, setDisputeType] = useState(undefined);
|
const [disputeTypeOptions, setDisputeTypeOptions] = useState([]);
|
const [detailVisible, setDetailVisible] = useState(false);
|
const [selectedCase, setSelectedCase] = useState(null);
|
const [selectedCaseType, setSelectedCaseType] = useState('judgment'); // 用于详情弹窗
|
const [currentPage, setCurrentPage] = useState(1);
|
const [pageSize] = useState(10);
|
|
// 筛选器状态 - 发生时间
|
const [yearFilters, setYearFilters] = useState([]);
|
|
// 筛选器状态 - 纠纷发生地
|
const [regionFilters, setRegionFilters] = useState([]);
|
|
// 日期格式化函数
|
const formatDate = (dateStr) => {
|
if (!dateStr) return '';
|
const date = new Date(dateStr);
|
if (isNaN(date.getTime())) return dateStr;
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
return `${year}年${month}月${day}日`;
|
};
|
|
// 加载纠纷类型下拉框数据
|
const loadDisputeTypes = async (caseSource) => {
|
try {
|
const res = await CaseAPIService.getDisputeTypes(caseSource);
|
if (res && res.data && Array.isArray(res.data)) {
|
const options = res.data.map(item => ({
|
label: item.dispute_type,
|
value: item.dispute_type
|
}));
|
setDisputeTypeOptions(options);
|
} else {
|
setDisputeTypeOptions([]);
|
}
|
} catch (error) {
|
console.error('加载纠纷类型失败:', error);
|
setDisputeTypeOptions([]);
|
}
|
};
|
|
// 加载发生时间统计数据
|
const loadYearStatistics = async () => {
|
try {
|
const api = caseType === 'judgment'
|
? CaseAPIService.getYearStatistics
|
: CaseAPIService.getMediationYearStatistics;
|
const res = await api();
|
if (res && res.data && Array.isArray(res.data)) {
|
const filters = res.data.map(item => ({
|
label: `${item.name}年`,
|
count: item.count || 0,
|
checked: false,
|
value: item.name
|
}));
|
setYearFilters(filters);
|
} else {
|
setYearFilters([]);
|
}
|
} catch (error) {
|
console.error('加载年份统计失败:', error);
|
setYearFilters([]);
|
}
|
};
|
|
// 加载纠纷发生地统计数据
|
const loadAreaStatistics = async (caseSource) => {
|
try {
|
const res = await CaseAPIService.getAreaStatistics(caseSource);
|
if (res && res.data && Array.isArray(res.data)) {
|
const filters = res.data.map((item, index) => ({
|
label: item.name || item.region,
|
count: item.count || 0,
|
checked: index === 0,
|
value: item.name || item.region,
|
isSub: item.level > 1,
|
children: item.children && Array.isArray(item.children) ? item.children.map(child => ({
|
label: child.name || child.region,
|
count: child.count || 0,
|
checked: false,
|
value: child.name || child.region
|
})) : undefined
|
}));
|
setRegionFilters(filters);
|
} else {
|
setRegionFilters([]);
|
}
|
} catch (error) {
|
console.error('加载地区统计失败:', error);
|
setRegionFilters([]);
|
}
|
};
|
|
// 加载案例列表数据
|
const loadCaseList = async (page = currentPage) => {
|
setLoading(true);
|
try {
|
// 获取选中的年份
|
const selectedYears = Array.isArray(yearFilters)
|
? yearFilters.filter(f => f.checked).map(f => f.value).join(',')
|
: '';
|
|
// 获取选中的地区
|
const selectedRegions = [];
|
if (Array.isArray(regionFilters)) {
|
regionFilters.forEach(region => {
|
if (region.checked && region.label !== '全部') {
|
selectedRegions.push(region.value);
|
}
|
if (region.children && Array.isArray(region.children)) {
|
region.children.forEach(child => {
|
if (child.checked) {
|
selectedRegions.push(child.value);
|
}
|
});
|
}
|
});
|
}
|
|
const params = {
|
page,
|
size: pageSize,
|
keyword: keyword || undefined,
|
caseTypeFirst: disputeType || undefined,
|
occurrenceYears: selectedYears || undefined,
|
regionList: selectedRegions.join(',') || undefined
|
};
|
|
const api = caseType === 'mediation'
|
? CaseAPIService.getMediationCases
|
: CaseAPIService.getCourtCases;
|
|
const res = await api(params);
|
|
if (res && res.data) {
|
// API返回结构: { code, message, data: { total, page, size, data: [...] } }
|
const listData = res.data.data || res.data;
|
const totalCount = res.data.total || 0;
|
|
if (Array.isArray(listData)) {
|
setList(listData);
|
setTotal(totalCount);
|
} else {
|
setList([]);
|
setTotal(0);
|
}
|
} else {
|
setList([]);
|
setTotal(0);
|
}
|
} catch (error) {
|
console.error('加载案例列表失败:', error);
|
message.error('加载案例列表失败');
|
setList([]);
|
setTotal(0);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 案例类型切换时重新加载关联数据
|
useEffect(() => {
|
const caseSource = caseType === 'judgment' ? 'judgment' : 'mediation';
|
loadDisputeTypes(caseSource);
|
loadYearStatistics();
|
loadAreaStatistics(caseSource);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [caseType]);
|
|
// 首次加载
|
useEffect(() => {
|
loadCaseList(1);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, []);
|
|
const handleSearch = () => {
|
setCurrentPage(1);
|
loadCaseList(1);
|
};
|
|
const handleReset = () => {
|
setKeyword('');
|
setDisputeType(undefined);
|
if (Array.isArray(yearFilters)) {
|
setYearFilters(yearFilters.map(f => ({ ...f, checked: false })));
|
}
|
if (Array.isArray(regionFilters)) {
|
setRegionFilters(regionFilters.map((f, i) => ({ ...f, checked: i === 0 })));
|
}
|
setCurrentPage(1);
|
};
|
|
const handleCaseClick = async (item) => {
|
try {
|
const id = caseType === 'mediation' ? item.id : item.cpws_case_info_id;
|
const api = caseType === 'mediation'
|
? CaseAPIService.getMediationCaseDetail
|
: CaseAPIService.getCourtCaseDetail;
|
|
const res = await api(id);
|
if (res && res.data) {
|
setSelectedCase(res.data);
|
setSelectedCaseType(caseType);
|
setDetailVisible(true);
|
}
|
} catch (error) {
|
console.error('加载案例详情失败:', error);
|
message.error('加载案例详情失败');
|
}
|
};
|
|
const handlePageChange = (page) => {
|
setCurrentPage(page);
|
loadCaseList(page);
|
};
|
|
const toggleFilter = (filters, setFilters, index, isChild = false, parentIndex = null) => {
|
const newFilters = [...filters];
|
if (isChild) {
|
newFilters[parentIndex].children = [...newFilters[parentIndex].children];
|
newFilters[parentIndex].children[index] = {
|
...newFilters[parentIndex].children[index],
|
checked: !newFilters[parentIndex].children[index].checked
|
};
|
} else {
|
newFilters[index] = { ...newFilters[index], checked: !newFilters[index].checked };
|
// 如果选中"全部",取消其他
|
if (newFilters[index].label === '全部' && newFilters[index].checked) {
|
newFilters.forEach((f, i) => { if (i !== index) f.checked = false; });
|
} else if (newFilters[index].checked) {
|
const allIdx = newFilters.findIndex(f => f.label === '全部');
|
if (allIdx !== -1) newFilters[allIdx].checked = false;
|
}
|
}
|
setFilters(newFilters);
|
};
|
|
return (
|
<div className="case-search-container">
|
<h1 className="case-search-content-title">
|
<i className="fas fa-folder-open"></i>
|
典型案例
|
</h1>
|
|
{/* 查询条件区域 */}
|
<div className="case-search-query-conditions">
|
<h2 className="case-search-query-title">
|
<i className="fas fa-search"></i>
|
查询条件
|
</h2>
|
<div className="case-search-query-form">
|
{/* 第一行:案例类型 + 纠纷类型 */}
|
<div className="case-search-form-group">
|
<label className="case-search-form-label">案例类型</label>
|
<Radio.Group
|
value={caseType}
|
onChange={e => {
|
setCaseType(e.target.value);
|
setDisputeType(undefined); // 切换类型时重置纠纷类型
|
}}
|
>
|
<Radio value="judgment">判决文书</Radio>
|
<Radio value="mediation">调解案例</Radio>
|
</Radio.Group>
|
</div>
|
|
<div className="case-search-form-group">
|
<label className="case-search-form-label">纠纷类型</label>
|
<Select
|
style={{ width: '100%' }}
|
placeholder="请选择"
|
value={disputeType}
|
onChange={setDisputeType}
|
allowClear
|
>
|
{disputeTypeOptions.map(option => (
|
<Select.Option key={option.value} value={option.value}>
|
{option.label}
|
</Select.Option>
|
))}
|
</Select>
|
</div>
|
|
{/* 第二行:关键词 + 按钮 */}
|
<div className="case-search-form-group">
|
<label className="case-search-form-label">关键词</label>
|
<Input
|
placeholder="请填写"
|
value={keyword}
|
onChange={e => setKeyword(e.target.value)}
|
/>
|
</div>
|
|
<div className="case-search-form-group case-search-button-group">
|
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch} loading={loading} style={{ height: 46 }}>
|
查询
|
</Button>
|
<Button icon={<RedoOutlined />} onClick={handleReset} style={{ height: 46 }}>
|
重置条件
|
</Button>
|
</div>
|
</div>
|
</div>
|
|
{/* 筛选器区域 */}
|
<div className="case-search-filters-section">
|
{/* 发生时间 */}
|
<div className="case-search-filter-category">
|
<h3 className="case-search-filter-title">发生时间</h3>
|
<div className="case-search-filter-list">
|
{Array.isArray(yearFilters) && yearFilters.map((item, index) => (
|
<div
|
key={index}
|
className={`case-search-filter-item ${item.checked ? 'active' : ''}`}
|
onClick={() => toggleFilter(yearFilters, setYearFilters, index)}
|
>
|
<div className={`case-search-filter-checkbox ${item.checked ? 'checked' : ''}`}>
|
{item.checked && <i className="fas fa-check"></i>}
|
</div>
|
<span>{item.label}</span>
|
<span className="case-search-filter-count">{item.count.toLocaleString()}</span>
|
</div>
|
))}
|
</div>
|
</div>
|
|
{/* 纠纷发生地 */}
|
<div className="case-search-filter-category">
|
<h3 className="case-search-filter-title">纠纷发生地</h3>
|
<div className="case-search-filter-list">
|
{Array.isArray(regionFilters) && regionFilters.map((item, index) => (
|
<React.Fragment key={index}>
|
<div
|
className={`${item.isSub ? 'case-search-sub-filter-item' : 'case-search-filter-item'} ${item.checked ? 'active' : ''}`}
|
onClick={() => toggleFilter(regionFilters, setRegionFilters, index)}
|
style={item.isSub ? { marginLeft: 20 } : {}}
|
>
|
<div className={`case-search-filter-checkbox ${item.checked ? 'checked' : ''}`}>
|
{item.checked && <i className="fas fa-check"></i>}
|
</div>
|
<span>{item.label}</span>
|
<span className="case-search-filter-count">{item.count.toLocaleString()}</span>
|
</div>
|
{item.children && (
|
<div className="case-search-sub-filter-list" style={{ marginLeft: 40 }}>
|
{item.children.map((child, cIdx) => (
|
<div
|
key={cIdx}
|
className={`case-search-sub-filter-item ${child.checked ? 'active' : ''}`}
|
onClick={(e) => {
|
e.stopPropagation();
|
toggleFilter(regionFilters, setRegionFilters, cIdx, true, index);
|
}}
|
>
|
<div className={`case-search-filter-checkbox ${child.checked ? 'checked' : ''}`}>
|
{child.checked && <i className="fas fa-check"></i>}
|
</div>
|
<span>{child.label}</span>
|
<span className="case-search-filter-count">{child.count.toLocaleString()}</span>
|
</div>
|
))}
|
</div>
|
)}
|
</React.Fragment>
|
))}
|
</div>
|
</div>
|
</div>
|
|
{/* 查询结果区域 */}
|
<div className="case-search-results-section">
|
<div className="case-search-results-header">
|
<h2 className="case-search-results-title">查询结果</h2>
|
<div className="case-search-total-count">记录总数:{total}条</div>
|
</div>
|
|
<Spin spinning={loading}>
|
<div className="case-search-cases-list">
|
{Array.isArray(list) && list.map((item) => {
|
// 根据案例类型映射字段
|
const title = caseType === 'mediation' ? item.case_title : item.case_name;
|
const time = caseType === 'mediation' ? item.occur_time : item.judgment_date;
|
const location = caseType === 'mediation'
|
? `${item.que_prov_name || ''}/${item.que_city_name || ''}`
|
: item.court;
|
const type = caseType === 'mediation' ? item.case_type_first_name : item.case_reason;
|
const caseId = caseType === 'mediation' ? item.id : item.cpws_case_info_id;
|
|
return (
|
<div
|
key={caseId}
|
className="case-search-case-item"
|
onClick={() => handleCaseClick(item)}
|
>
|
<h3 className="case-search-case-title">{title}</h3>
|
<div className="case-search-case-meta">
|
<div className="case-search-case-meta-item">
|
<i className="far fa-calendar-alt"></i>
|
<span>发生时间:{formatDate(time)}</span>
|
</div>
|
<div className="case-search-case-meta-item">
|
<i className="fas fa-map-marker-alt"></i>
|
<span>发生地点:{location}</span>
|
</div>
|
<div className="case-search-case-meta-item">
|
<i className="fas fa-balance-scale"></i>
|
<span>纠纷类型:{type}</span>
|
</div>
|
</div>
|
<div className={`case-search-case-type-badge ${caseType === 'mediation' ? 'mediation' : 'judgment'}`}>
|
{caseType === 'mediation' ? '调解案例' : '判决文书'}
|
</div>
|
</div>
|
);
|
})}
|
</div>
|
</Spin>
|
|
{/* 分页 */}
|
<div className="case-search-pagination">
|
<Pagination
|
current={currentPage}
|
total={total}
|
pageSize={pageSize}
|
onChange={handlePageChange}
|
showSizeChanger={false}
|
/>
|
</div>
|
</div>
|
|
{/* 案例详情弹窗 */}
|
<Modal
|
title={
|
<div className="case-detail-modal-header-custom">
|
<h2>
|
<i className="fas fa-folder-open"></i>
|
{selectedCaseType === 'mediation' ? '典型案例详情' : '判决文书详情'}
|
</h2>
|
</div>
|
}
|
visible={detailVisible}
|
onCancel={() => setDetailVisible(false)}
|
footer={null}
|
width={1000}
|
bodyStyle={{ padding: 0, height: '85vh', overflowY: 'auto', backgroundColor: '#f5f7fa' }}
|
centered
|
destroyOnClose
|
className="case-detail-antd-modal"
|
closeIcon={<span className="case-detail-modal-close-custom">×</span>}
|
>
|
<TypicalCaseDetailContent caseData={selectedCase} caseType={selectedCaseType} />
|
</Modal>
|
</div>
|
);
|
};
|
|
export default CaseSearchContent;
|