From d100657dacb25df91013ef25432620e6ae10d1f8 Mon Sep 17 00:00:00 2001
From: shimai <shimai@example.com>
Date: Thu, 09 Apr 2026 16:56:44 +0800
Subject: [PATCH] feat:增加白云案件类案数据
---
web-app/src/components/tools/CaseSearchContent.jsx | 378 +++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 276 insertions(+), 102 deletions(-)
diff --git a/web-app/src/components/tools/CaseSearchContent.jsx b/web-app/src/components/tools/CaseSearchContent.jsx
index 97ca1b3..7d74610 100644
--- a/web-app/src/components/tools/CaseSearchContent.jsx
+++ b/web-app/src/components/tools/CaseSearchContent.jsx
@@ -1,69 +1,236 @@
-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) => {
@@ -101,14 +268,21 @@
查询条件
</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
@@ -118,15 +292,24 @@
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 }}>
查询
@@ -140,31 +323,11 @@
{/* 筛选器区域 */}
<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' : ''}`}
@@ -184,7 +347,7 @@
<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' : ''}`}
@@ -227,37 +390,48 @@
<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>
@@ -265,9 +439,9 @@
<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>
@@ -279,7 +453,7 @@
<div className="case-detail-modal-header-custom">
<h2>
<i className="fas fa-folder-open"></i>
- 典型案例详情
+ {selectedCaseType === 'mediation' ? '典型案例详情' : '判决文书详情'}
</h2>
</div>
}
@@ -293,7 +467,7 @@
className="case-detail-antd-modal"
closeIcon={<span className="case-detail-modal-close-custom">×</span>}
>
- <TypicalCaseDetailContent caseData={selectedCase} />
+ <TypicalCaseDetailContent caseData={selectedCase} caseType={selectedCaseType} />
</Modal>
</div>
);
--
Gitblit v1.8.0