From 823cf3819f2f91adeada3707435d40b3dac8f7b4 Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Fri, 06 Feb 2026 18:15:05 +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">&times;</span>}
       >
-        <TypicalCaseDetailContent caseData={selectedCase} />
+        <TypicalCaseDetailContent caseData={selectedCase} caseType={selectedCaseType} />
       </Modal>
     </div>
   );

--
Gitblit v1.8.0