| | |
| | | import React, { useState } from 'react'; |
| | | import { Form, Input, Select, Button, Card, List, Tag, Typography } from 'antd'; |
| | | import { SearchOutlined } from '@ant-design/icons'; |
| | | import { Input, DatePicker, Button, Spin, Pagination, Select, Modal } from 'antd'; |
| | | import { SearchOutlined, RedoOutlined } from '@ant-design/icons'; |
| | | import { mockCaseList } from '../../mocks/caseMocks'; |
| | | import TypicalCaseDetailContent from './TypicalCaseDetailContent'; |
| | | import './TypicalCaseSearch.css'; |
| | | |
| | | 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: '原告王某在被告公司车间工作时因设备故障受伤,经鉴定为九级伤残。双方就工伤赔偿金额产生争议,原告诉至法院...', |
| | | }, |
| | | ]; |
| | | const { RangePicker } = DatePicker; |
| | | |
| | | /** |
| | | * 案例搜索内容组件(用于弹窗内显示) |
| | | * 典型案例查询内容组件 - 与原型 case_search.html 保持一致 |
| | | */ |
| | | const CaseSearchContent = () => { |
| | | const [loading, setLoading] = useState(false); |
| | | const [list, setList] = useState(mockCaseList); |
| | | const [list, setList] = useState(mockCaseList.list); |
| | | const [keyword, setKeyword] = useState(''); |
| | | const [disputeType, setDisputeType] = useState(undefined); |
| | | const [detailVisible, setDetailVisible] = useState(false); |
| | | const [selectedCase, setSelectedCase] = useState(null); |
| | | const [currentPage, setCurrentPage] = useState(1); |
| | | |
| | | const handleSearch = (values) => { |
| | | // 模拟筛选器状态 |
| | | const [caseTypeFilters, setCaseTypeFilters] = useState([ |
| | | { label: '判决文书', count: 1221120, checked: true }, |
| | | { label: '调解案例', count: 332526, checked: false }, |
| | | ]); |
| | | |
| | | 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([ |
| | | { 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 handleSearch = () => { |
| | | setLoading(true); |
| | | setTimeout(() => { |
| | | setList(mockCaseList); |
| | | setList(mockCaseList.list); |
| | | setLoading(false); |
| | | }, 300); |
| | | }; |
| | | |
| | | const caseTypeColorMap = { |
| | | 判决: 'blue', |
| | | 调解: 'green', |
| | | 仲裁: 'orange', |
| | | 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 }))); |
| | | }; |
| | | |
| | | const handleCaseClick = (item) => { |
| | | setSelectedCase(item); |
| | | setDetailVisible(true); |
| | | }; |
| | | |
| | | 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> |
| | | <Input |
| | | placeholder="请填写" |
| | | value={keyword} |
| | | onChange={e => setKeyword(e.target.value)} |
| | | /> |
| | | </div> |
| | | <div className="case-search-form-group"> |
| | | <label className="case-search-form-label">纠纷类型</label> |
| | | <Select |
| | | style={{ width: '100%' }} |
| | | placeholder="请选择" |
| | | value={disputeType} |
| | | onChange={setDisputeType} |
| | | allowClear |
| | | > |
| | | <Select.Option value="邻里纠纷">邻里纠纷</Select.Option> |
| | | <Select.Option value="劳动争议">劳动争议</Select.Option> |
| | | <Select.Option value="合同纠纷">合同纠纷</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> |
| | | <RangePicker style={{ width: '100%' }} /> |
| | | </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"> |
| | | {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> |
| | | <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"> |
| | | {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.judgmentDate}</span> |
| | | <span>地区:{item.region}</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"> |
| | | {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={{ background: '#f5f7fb', padding: 12, borderRadius: 6 }}> |
| | | <Text strong style={{ display: 'block', marginBottom: 8 }}>案例摘要:</Text> |
| | | <Paragraph ellipsis={{ rows: 3, expandable: true, symbol: '展开' }}> |
| | | {item.caseSummary} |
| | | </Paragraph> |
| | | {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">记录总数:{mockCaseList.pageInfo.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> |
| | | </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> |
| | | </div> |
| | | </Card> |
| | | </List.Item> |
| | | )} |
| | | /> |
| | | </Card> |
| | | <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} |
| | | showSizeChanger={false} |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | {/* 案例详情弹窗 */} |
| | | <Modal |
| | | title={ |
| | | <div className="case-detail-modal-header-custom"> |
| | | <h2> |
| | | <i className="fas fa-folder-open"></i> |
| | | 典型案例详情 |
| | | </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} /> |
| | | </Modal> |
| | | </div> |
| | | ); |
| | | }; |