| | |
| | | import React, { useState } from 'react'; |
| | | import { Input, DatePicker, Button, Spin, Pagination, Select, Modal } from 'antd'; |
| | | 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 { mockCaseList } from '../../mocks/caseMocks'; |
| | | import CaseAPIService from '../../services/CaseAPIService'; |
| | | import TypicalCaseDetailContent from './TypicalCaseDetailContent'; |
| | | import './TypicalCaseSearch.css'; |
| | | |
| | | const { RangePicker } = DatePicker; |
| | | |
| | | /** |
| | | * 典型案例查询内容组件 - 与原型 case_search.html 保持一致 |
| | | */ |
| | | const CaseSearchContent = () => { |
| | | const [loading, setLoading] = useState(false); |
| | | const [list, setList] = useState(mockCaseList.list); |
| | | 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 [caseTypeFilters, setCaseTypeFilters] = useState([ |
| | | { label: '判决文书', count: 1221120, checked: true }, |
| | | { label: '调解案例', count: 332526, checked: false }, |
| | | ]); |
| | | // 筛选器状态 - 发生时间 |
| | | const [yearFilters, setYearFilters] = useState([]); |
| | | |
| | | const [yearFilters, setYearFilters] = useState([ |
| | | { label: '2021年', count: 1221120, checked: false }, |
| | | { label: '2022年', count: 332526, checked: false }, |
| | | { label: '2023年', count: 62221, checked: true }, |
| | | { label: '2024年', count: 32212, checked: false }, |
| | | ]); |
| | | // 筛选器状态 - 纠纷发生地 |
| | | const [regionFilters, setRegionFilters] = useState([]); |
| | | |
| | | const [regionFilters, setRegionFilters] = useState([ |
| | | { label: '全部', count: 462100, checked: true }, |
| | | { label: '广东省', count: 62201, checked: false, isSub: true, children: [ |
| | | { label: '广州市', count: 10221, checked: false }, |
| | | { label: '深圳市', count: 20001, checked: false }, |
| | | { label: '中山市', count: 9632, checked: false }, |
| | | ]}, |
| | | { label: '广西省', count: 44552, checked: false, isSub: true }, |
| | | { label: '湖南省', count: 83001, checked: false, isSub: true }, |
| | | { label: '湖北省', count: 98745, checked: false, isSub: true }, |
| | | { label: '浙江省', count: 30021, checked: false, isSub: true }, |
| | | ]); |
| | | // 日期格式化函数 |
| | | 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 = () => { |
| | | setLoading(true); |
| | | setTimeout(() => { |
| | | setList(mockCaseList.list); |
| | | setLoading(false); |
| | | }, 300); |
| | | setCurrentPage(1); |
| | | loadCaseList(1); |
| | | }; |
| | | |
| | | const handleReset = () => { |
| | | setKeyword(''); |
| | | setDisputeType(undefined); |
| | | setCaseTypeFilters(caseTypeFilters.map((f, i) => ({ ...f, checked: i === 0 }))); |
| | | setYearFilters(yearFilters.map((f, i) => ({ ...f, checked: i === 2 }))); |
| | | setRegionFilters(regionFilters.map((f, i) => ({ ...f, checked: i === 0 }))); |
| | | 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 = (item) => { |
| | | setSelectedCase(item); |
| | | setDetailVisible(true); |
| | | 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) => { |
| | |
| | | 查询条件 |
| | | </h2> |
| | | <div className="case-search-query-form"> |
| | | {/* 第一行:案例类型 + 纠纷类型 */} |
| | | <div className="case-search-form-group"> |
| | | <label className="case-search-form-label">关键词</label> |
| | | <Input |
| | | placeholder="请填写" |
| | | value={keyword} |
| | | onChange={e => setKeyword(e.target.value)} |
| | | /> |
| | | <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 |
| | |
| | | onChange={setDisputeType} |
| | | allowClear |
| | | > |
| | | <Select.Option value="邻里纠纷">邻里纠纷</Select.Option> |
| | | <Select.Option value="劳动争议">劳动争议</Select.Option> |
| | | <Select.Option value="合同纠纷">合同纠纷</Select.Option> |
| | | {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> |
| | | <RangePicker style={{ width: '100%' }} /> |
| | | <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 }}> |
| | | 查询 |
| | |
| | | |
| | | {/* 筛选器区域 */} |
| | | <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"> |
| | | {caseTypeFilters.map((item, index) => ( |
| | | <div |
| | | key={index} |
| | | className={`case-search-filter-item ${item.checked ? 'active' : ''}`} |
| | | onClick={() => toggleFilter(caseTypeFilters, setCaseTypeFilters, 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"> |
| | | {yearFilters.map((item, index) => ( |
| | | {Array.isArray(yearFilters) && yearFilters.map((item, index) => ( |
| | | <div |
| | | key={index} |
| | | className={`case-search-filter-item ${item.checked ? 'active' : ''}`} |
| | |
| | | <div className="case-search-filter-category"> |
| | | <h3 className="case-search-filter-title">纠纷发生地</h3> |
| | | <div className="case-search-filter-list"> |
| | | {regionFilters.map((item, index) => ( |
| | | {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' : ''}`} |
| | |
| | | <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">记录总数:{mockCaseList.pageInfo.total}条</div> |
| | | <div className="case-search-total-count">记录总数:{total}条</div> |
| | | </div> |
| | | |
| | | <Spin spinning={loading}> |
| | | <div className="case-search-cases-list"> |
| | | {list.map((item) => ( |
| | | <div |
| | | key={item.id} |
| | | className="case-search-case-item" |
| | | onClick={() => handleCaseClick(item)} |
| | | > |
| | | <h3 className="case-search-case-title">{item.caseTitle}</h3> |
| | | <div className="case-search-case-meta"> |
| | | <div className="case-search-case-meta-item"> |
| | | <i className="far fa-calendar-alt"></i> |
| | | <span>发生时间:{item.judgmentDate}</span> |
| | | {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-meta-item"> |
| | | <i className="fas fa-map-marker-alt"></i> |
| | | <span>发生地点:{item.region}</span> |
| | | </div> |
| | | <div className="case-search-case-meta-item"> |
| | | <i className="fas fa-balance-scale"></i> |
| | | <span>纠纷类型:{item.disputeType}</span> |
| | | <div className={`case-search-case-type-badge ${caseType === 'mediation' ? 'mediation' : 'judgment'}`}> |
| | | {caseType === 'mediation' ? '调解案例' : '判决文书'} |
| | | </div> |
| | | </div> |
| | | <div className={`case-search-case-type-badge ${item.caseType === '调解' ? 'mediation' : 'judgment'}`}> |
| | | {item.caseType === '调解' ? '调解案例' : '判决文书'} |
| | | </div> |
| | | </div> |
| | | ))} |
| | | ); |
| | | })} |
| | | </div> |
| | | </Spin> |
| | | |
| | |
| | | <div className="case-search-pagination"> |
| | | <Pagination |
| | | current={currentPage} |
| | | total={mockCaseList.pageInfo.total} |
| | | pageSize={10} |
| | | onChange={setCurrentPage} |
| | | total={total} |
| | | pageSize={pageSize} |
| | | onChange={handlePageChange} |
| | | showSizeChanger={false} |
| | | /> |
| | | </div> |
| | |
| | | <div className="case-detail-modal-header-custom"> |
| | | <h2> |
| | | <i className="fas fa-folder-open"></i> |
| | | 典型案例详情 |
| | | {selectedCaseType === 'mediation' ? '典型案例详情' : '判决文书详情'} |
| | | </h2> |
| | | </div> |
| | | } |
| | |
| | | className="case-detail-antd-modal" |
| | | closeIcon={<span className="case-detail-modal-close-custom">×</span>} |
| | | > |
| | | <TypicalCaseDetailContent caseData={selectedCase} /> |
| | | <TypicalCaseDetailContent caseData={selectedCase} caseType={selectedCaseType} /> |
| | | </Modal> |
| | | </div> |
| | | ); |