chengmw
9 days ago 96f4f1ea3a088d315a60c65bae50b5074441bf4c
web-app/src/components/tools/CaseSearchContent.jsx
@@ -1,127 +1,474 @@
import React, { useState } from 'react';
import { Form, Input, Select, Button, Card, List, Tag, Typography } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
const { Text, Paragraph } = Typography;
// Mock数据
const mockCaseList = [
  {
    id: 'case-001',
    caseTitle: '张某诉某科技公司欠薪纠纷案',
    caseType: '判决',
    disputeType: '欠薪',
    caseNumber: '(2024)京0108民初12345号',
    court: '北京市海淀区人民法院',
    judgmentDate: '2024-03-15',
    region: '北京市海淀区',
    caseSummary: '原告张某于2022年1月入职被告公司担任软件工程师,双方签订了为期三年的劳动合同。2023年6月起,被告公司未按时足额支付原告工资,累计欠薪3个月,共计人民币45000元...',
  },
  {
    id: 'case-002',
    caseTitle: '李某与某餐饮公司劳动争议调解案',
    caseType: '调解',
    disputeType: '解除合同',
    caseNumber: '劳调字[2024]第0089号',
    court: '广州市劳动争议调解委员会',
    judgmentDate: '2024-02-20',
    region: '广东省广州市',
    caseSummary: '申请人李某于2021年3月入职被申请人公司担任厨师长,双方因解除劳动合同经济补偿问题发生争议。经调解,双方达成一致协议...',
  },
  {
    id: 'case-003',
    caseTitle: '王某诉某制造公司工伤赔偿纠纷案',
    caseType: '判决',
    disputeType: '工伤赔偿',
    caseNumber: '(2024)粤0304民初5678号',
    court: '深圳市福田区人民法院',
    judgmentDate: '2024-01-10',
    region: '广东省深圳市',
    caseSummary: '原告王某在被告公司车间工作时因设备故障受伤,经鉴定为九级伤残。双方就工伤赔偿金额产生争议,原告诉至法院...',
  },
];
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(mockCaseList);
  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 handleSearch = (values) => {
    setLoading(true);
    setTimeout(() => {
      setList(mockCaseList);
      setLoading(false);
    }, 300);
  // 筛选器状态 - 发生时间
  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 caseTypeColorMap = {
    判决: 'blue',
    调解: 'green',
    仲裁: 'orange',
  // 加载纠纷类型下拉框数据
  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>
      <Card title="查询条件" style={{ marginBottom: 16 }}>
        <Form layout="inline" onFinish={handleSearch}>
          <Form.Item name="keyword" label="关键字">
            <Input placeholder="请输入案例关键字" style={{ width: 200 }} />
          </Form.Item>
          <Form.Item name="disputeType" label="纠纷类型">
            <Select placeholder="请选择" style={{ width: 120 }} allowClear>
              <Select.Option value="欠薪">欠薪</Select.Option>
              <Select.Option value="解除合同">解除合同</Select.Option>
              <Select.Option value="工伤赔偿">工伤赔偿</Select.Option>
    <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>
          </Form.Item>
          <Form.Item name="caseType" label="案件类型">
            <Select placeholder="请选择" style={{ width: 100 }} allowClear>
              <Select.Option value="判决">判决</Select.Option>
              <Select.Option value="调解">调解</Select.Option>
              <Select.Option value="仲裁">仲裁</Select.Option>
            </Select>
          </Form.Item>
          <Form.Item>
            <Button type="primary" htmlType="submit" icon={<SearchOutlined />} loading={loading}>
          </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>
          </Form.Item>
        </Form>
      </Card>
            <Button icon={<RedoOutlined />} onClick={handleReset} style={{ height: 46 }}>
              重置条件
            </Button>
          </div>
        </div>
      </div>
      <Card title={`查询结果(共 ${list.length} 条)`}>
        <List
          loading={loading}
          dataSource={list}
          renderItem={(item) => (
            <List.Item>
              <Card style={{ width: '100%' }}>
                <div style={{ marginBottom: 12 }}>
                  <Text strong style={{ fontSize: 16, marginRight: 8 }}>{item.caseTitle}</Text>
                  <Tag color={caseTypeColorMap[item.caseType] || 'default'}>{item.caseType}</Tag>
                  <Tag>{item.disputeType}</Tag>
      {/* 筛选器区域 */}
      <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>
                <div style={{ fontSize: '0.85rem', color: '#666', marginBottom: 12 }}>
                  <span style={{ marginRight: 16 }}>案号:{item.caseNumber}</span>
                  <span style={{ marginRight: 16 }}>审理机构:{item.court}</span>
                <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>
                <div style={{ fontSize: '0.85rem', color: '#666', marginBottom: 12 }}>
                  <span style={{ marginRight: 16 }}>裁判日期:{item.judgmentDate}</span>
                  <span>地区:{item.region}</span>
                {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 style={{ background: '#f5f7fb', padding: 12, borderRadius: 6 }}>
                  <Text strong style={{ display: 'block', marginBottom: 8 }}>案例摘要:</Text>
                  <Paragraph ellipsis={{ rows: 3, expandable: true, symbol: '展开' }}>
                    {item.caseSummary}
                  </Paragraph>
                </div>
              </Card>
            </List.Item>
          )}
        />
      </Card>
              );
            })}
          </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">&times;</span>}
      >
        <TypicalCaseDetailContent caseData={selectedCase} caseType={selectedCaseType} />
      </Modal>
    </div>
  );
};