From 6a77991808ba41650e646afd247b17fa3903c6e4 Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Tue, 27 Jan 2026 15:28:57 +0800
Subject: [PATCH] Optimize typical case search and detail UI to match prototype 100%

---
 web-app/src/components/tools/CaseSearchContent.jsx |  371 ++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 272 insertions(+), 99 deletions(-)

diff --git a/web-app/src/components/tools/CaseSearchContent.jsx b/web-app/src/components/tools/CaseSearchContent.jsx
index 5cdced0..97ca1b3 100644
--- a/web-app/src/components/tools/CaseSearchContent.jsx
+++ b/web-app/src/components/tools/CaseSearchContent.jsx
@@ -1,127 +1,300 @@
 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">&times;</span>}
+      >
+        <TypicalCaseDetailContent caseData={selectedCase} />
+      </Modal>
     </div>
   );
 };

--
Gitblit v1.8.0