tony.cheng
2026-01-27 6a77991808ba41650e646afd247b17fa3903c6e4
Optimize typical case search and detail UI to match prototype 100%
5 files added
4 files modified
1492 ■■■■■ changed files
web-app/src/components/tools/CaseSearchContent.jsx 371 ●●●● patch | view | raw | blame | history
web-app/src/components/tools/LawDetailContent.css 176 ●●●●● patch | view | raw | blame | history
web-app/src/components/tools/LawDetailContent.jsx 107 ●●●●● patch | view | raw | blame | history
web-app/src/components/tools/LawSearchContent.jsx 56 ●●●● patch | view | raw | blame | history
web-app/src/components/tools/TypicalCaseDetailContent.css 153 ●●●●● patch | view | raw | blame | history
web-app/src/components/tools/TypicalCaseDetailContent.jsx 160 ●●●●● patch | view | raw | blame | history
web-app/src/components/tools/TypicalCaseSearch.css 370 ●●●●● patch | view | raw | blame | history
web-app/src/mocks/caseMocks.js 51 ●●●● patch | view | raw | blame | history
web-app/src/mocks/lawMocks.js 48 ●●●●● patch | view | raw | blame | history
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>
  );
};
web-app/src/components/tools/LawDetailContent.css
New file
@@ -0,0 +1,176 @@
/* 法律条文详情样式 - 与原型 law_search_detail.html 保持一致 */
.law-detail-modal-body {
  height: 100%;
  overflow-y: auto;
  padding: 20px;
  background-color: #f5f7fb;
}
/* 章节导航 */
.law-detail-chapter-nav {
  background: white;
  border-radius: var(--border-radius, 6px);
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  margin-bottom: 20px;
}
.law-detail-chapter-nav-title {
  font-size: 1.1rem;
  color: var(--dark-color, #212529);
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
.law-detail-chapter-nav-title i {
  color: var(--primary-color, #1a6fb8);
}
.law-detail-chapter-list {
  max-height: 200px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.law-detail-chapter-link {
  display: block;
  padding: 8px 12px;
  color: var(--gray-color, #6c757d);
  text-decoration: none;
  border-left: 3px solid transparent;
  transition: all 0.2s ease;
  font-size: 0.95rem;
}
.law-detail-chapter-link:hover {
  background: #f8f9fa;
  color: var(--primary-color, #1a6fb8);
  border-left-color: var(--primary-color, #1a6fb8);
}
.law-detail-chapter-link.active {
  background: #e3f2fd;
  color: var(--primary-color, #1a6fb8);
  border-left-color: var(--primary-color, #1a6fb8);
  font-weight: 600;
}
/* 法律详情容器 */
.law-detail-main-container {
  background: white;
  border-radius: var(--border-radius, 6px);
  padding: 30px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.law-detail-header {
  margin-bottom: 30px;
  border-bottom: 2px solid var(--primary-color, #1a6fb8);
  padding-bottom: 20px;
}
.law-detail-title {
  font-size: 1.6rem;
  color: var(--primary-color, #1a6fb8);
  margin-bottom: 20px;
  text-align: center;
  font-weight: 700;
}
.law-detail-meta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 15px;
}
.law-detail-meta-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background: #f8f9fa;
  border-radius: var(--border-radius, 6px);
}
.law-detail-meta-label {
  font-weight: 600;
  color: var(--dark-color, #212529);
  min-width: 100px;
  font-size: 0.9rem;
}
.law-detail-meta-value {
  color: var(--gray-color, #6c757d);
  font-size: 0.9rem;
}
.law-detail-meta-value.status-effective {
  color: var(--success-color, #2a9d8f);
  font-weight: 600;
}
/* 法律内容 */
.law-detail-content-area {
  line-height: 1.8;
}
.law-detail-chapter-section {
  margin-bottom: 30px;
}
.law-detail-chapter-title {
  font-size: 1.3rem;
  color: var(--secondary-color, #0d4a8a);
  margin-bottom: 20px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-color, #dee2e6);
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
.law-detail-chapter-title i {
  color: var(--primary-color, #1a6fb8);
}
.law-detail-articles-container {
  margin-left: 20px;
}
.law-detail-article-item {
  margin-bottom: 25px;
  padding: 15px;
  border-left: 3px solid #e9ecef;
  transition: all 0.2s ease;
}
.law-detail-article-item:hover {
  border-left-color: var(--primary-color, #1a6fb8);
  background: #f8f9fa;
}
.law-detail-article-number {
  font-weight: 700;
  color: var(--primary-color, #1a6fb8);
  margin-bottom: 8px;
  font-size: 1.05rem;
}
.law-detail-article-content {
  font-size: 0.95rem;
  color: var(--dark-color, #212529);
  text-align: justify;
}
/* 响应式调整 */
@media (max-width: 768px) {
  .law-detail-articles-container {
    margin-left: 0;
  }
}
web-app/src/components/tools/LawDetailContent.jsx
New file
@@ -0,0 +1,107 @@
import React, { useState, useEffect } from 'react';
import { mockLawDetail } from '../../mocks/lawMocks';
import './LawDetailContent.css';
const LawDetailContent = ({ lawId }) => {
  const [activeChapter, setActiveChapter] = useState('chapter1');
  const [lawDetail, setLawDetail] = useState(null);
  useEffect(() => {
    // 模拟根据 lawId 获取详情
    setLawDetail(mockLawDetail);
  }, [lawId]);
  if (!lawDetail) return <div className="law-detail-loading">加载中...</div>;
  const handleChapterClick = (chapterId) => {
    setActiveChapter(chapterId);
    const element = document.getElementById(chapterId);
    if (element) {
      element.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  };
  return (
    <div className="law-detail-modal-body">
      {/* 章节导航 */}
      <div className="law-detail-chapter-nav">
        <h3 className="law-detail-chapter-nav-title">
          <i className="fas fa-list-ol"></i>
          章节导航
        </h3>
        <div className="law-detail-chapter-list">
          {lawDetail.chapters.map((chapter) => (
            <a
              key={chapter.id}
              href={`#${chapter.id}`}
              className={`law-detail-chapter-link ${activeChapter === chapter.id ? 'active' : ''}`}
              onClick={(e) => {
                e.preventDefault();
                handleChapterClick(chapter.id);
              }}
            >
              {chapter.title}
            </a>
          ))}
        </div>
      </div>
      {/* 法律详情容器 */}
      <div className="law-detail-main-container">
        <div className="law-detail-header">
          <h2 className="law-detail-title">{lawDetail.lawName}</h2>
          <div className="law-detail-meta-grid">
            <div className="law-detail-meta-item">
              <span className="law-detail-meta-label">时效性:</span>
              <span className="law-detail-meta-value status-effective">{lawDetail.status}</span>
            </div>
            <div className="law-detail-meta-item">
              <span className="law-detail-meta-label">法律效力位阶:</span>
              <span className="law-detail-meta-value">{lawDetail.effectLevel}</span>
            </div>
            <div className="law-detail-meta-item">
              <span className="law-detail-meta-label">制定机关:</span>
              <span className="law-detail-meta-value">{lawDetail.org}</span>
            </div>
            <div className="law-detail-meta-item">
              <span className="law-detail-meta-label">公布日期:</span>
              <span className="law-detail-meta-value">{lawDetail.publishDate}</span>
            </div>
            <div className="law-detail-meta-item">
              <span className="law-detail-meta-label">实施日期:</span>
              <span className="law-detail-meta-value">{lawDetail.effectiveDate}</span>
            </div>
          </div>
        </div>
        <div className="law-detail-content-area">
          {lawDetail.chapters.map((chapter) => (
            <div className="law-detail-chapter-section" id={chapter.id} key={chapter.id}>
              <h3 className="law-detail-chapter-title">
                <i className="fas fa-bookmark"></i>
                {chapter.title}
              </h3>
              <div className="law-detail-articles-container">
                {chapter.articles.map((article, index) => (
                  <div className="law-detail-article-item" key={index}>
                    <div className="law-detail-article-number">{article.number}</div>
                    <div className="law-detail-article-content">
                      {article.content.split('\n').map((line, i) => (
                        <React.Fragment key={i}>
                          {line}
                          {i < article.content.split('\n').length - 1 && <br />}
                        </React.Fragment>
                      ))}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};
export default LawDetailContent;
web-app/src/components/tools/LawSearchContent.jsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Input, DatePicker, Button, Pagination, Spin } from 'antd';
import { Input, DatePicker, Button, Pagination, Spin, Modal } from 'antd';
import { SearchOutlined, RedoOutlined } from '@ant-design/icons';
import LawDetailContent from './LawDetailContent';
import './LawSearchContent.css';
// Mock数据 - 按原型格式
@@ -100,6 +101,8 @@
  const [activeId, setActiveId] = useState('law-001');
  const [filters, setFilters] = useState(filterConfig);
  const [currentPage, setCurrentPage] = useState(1);
  const [detailVisible, setDetailVisible] = useState(false);
  const [selectedLawId, setSelectedLawId] = useState(null);
  const pageSize = 10;
  const total = 256;
@@ -117,7 +120,7 @@
  };
  const toggleFilter = (category, index) => {
    setFilters(prev => {
    setFilters((prev) => {
      const newFilters = { ...prev };
      newFilters[category] = [...prev[category]];
      newFilters[category][index] = {
@@ -126,6 +129,16 @@
      };
      return newFilters;
    });
  };
  const handleLawItemClick = (lawId) => {
    if (activeId === lawId) {
      // 如果已经是激活状态,再次点击弹出详情
      setSelectedLawId(lawId);
      setDetailVisible(true);
    } else {
      setActiveId(lawId);
    }
  };
  return (
@@ -182,7 +195,9 @@
                  <div className={`law-search-filter-checkbox ${item.checked ? 'checked' : ''}`}>
                    {item.checked && <i className="fas fa-check"></i>}
                  </div>
                  <span>{item.label} ({item.count})</span>
                  <span>
                    {item.label} ({item.count})
                  </span>
                </div>
              ))}
            </div>
@@ -193,15 +208,13 @@
            <h3 className="law-search-filter-title">制定机关</h3>
            <div className="law-search-filter-list">
              {filters.org.map((item, index) => (
                <div
                  key={index}
                  className="law-search-filter-item"
                  onClick={() => toggleFilter('org', index)}
                >
                <div key={index} className="law-search-filter-item" onClick={() => toggleFilter('org', index)}>
                  <div className={`law-search-filter-checkbox ${item.checked ? 'checked' : ''}`}>
                    {item.checked && <i className="fas fa-check"></i>}
                  </div>
                  <span>{item.label} ({item.count})</span>
                  <span>
                    {item.label} ({item.count})
                  </span>
                </div>
              ))}
            </div>
@@ -220,7 +233,9 @@
                  <div className={`law-search-filter-checkbox ${item.checked ? 'checked' : ''}`}>
                    {item.checked && <i className="fas fa-check"></i>}
                  </div>
                  <span>{item.label} ({item.count})</span>
                  <span>
                    {item.label} ({item.count})
                  </span>
                </div>
              ))}
            </div>
@@ -241,7 +256,7 @@
              <div
                key={law.id}
                className={`law-search-law-item ${activeId === law.id ? 'active' : ''}`}
                onClick={() => setActiveId(law.id)}
                onClick={() => handleLawItemClick(law.id)}
              >
                <h3 className="law-search-law-title">{law.lawName}</h3>
                <div className="law-search-law-meta">
@@ -295,6 +310,25 @@
          />
        </div>
      </div>
      {/* 详情弹窗 */}
      <Modal
        title={
          <div style={{ fontSize: '1.2rem', fontWeight: 600 }}>
            <i className="fas fa-book" style={{ marginRight: 10, color: '#1a6fb8' }}></i>
            法律条文详情
          </div>
        }
        visible={detailVisible}
        onCancel={() => setDetailVisible(false)}
        footer={null}
        width={1000}
        bodyStyle={{ padding: 0, height: '80vh', overflow: 'hidden' }}
        centered
        destroyOnClose
      >
        <LawDetailContent lawId={selectedLawId} />
      </Modal>
    </div>
  );
};
web-app/src/components/tools/TypicalCaseDetailContent.css
New file
@@ -0,0 +1,153 @@
/* 典型案例详情样式 - 与原型 case_search_detail.html 保持一致 */
.case-detail-container {
  max-width: 1000px;
  margin: 0 auto;
  background-color: white;
  border-radius: 10px;
  overflow: hidden;
  height: 100%;
}
.case-detail-info-section {
  padding: 25px 30px;
  border-bottom: 1px solid #eaeaea;
}
.case-detail-info-title {
  color: #1A6FB8 !important;
  font-size: 20px !important;
  margin-bottom: 20px !important;
  padding-bottom: 10px !important;
  border-bottom: 2px solid #f0f0f0 !important;
  font-weight: 600 !important;
}
.case-detail-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}
.case-detail-info-item {
  margin-bottom: 5px;
  display: block;
}
.case-detail-info-label {
  font-weight: 600;
  color: #555;
  margin-right: 8px;
}
.case-detail-info-value {
  color: #222;
}
.case-detail-body {
  padding: 25px 30px;
}
.case-detail-section {
  margin-bottom: 30px;
}
.case-detail-section-title {
  color: #1A6FB8 !important;
  font-size: 18px !important;
  margin-bottom: 15px !important;
  padding-left: 10px !important;
  border-left: 4px solid #1A6FB8 !important;
  font-weight: 600 !important;
}
.case-detail-section-content {
  background-color: #f9fafc;
  padding: 20px;
  border-radius: 8px;
  border-left: 3px solid #d0e3f0;
  line-height: 1.6;
  color: #333;
}
.case-detail-plaintiff-demand,
.case-detail-court-decision,
.case-detail-mediation-result {
  background-color: #f0f8ff;
  border-left: 3px solid #1A6FB8;
  padding-left: 0;
}
.case-detail-inner-content {
  padding-left: 20px;
}
.case-detail-mediation-process {
  background-color: #f9fff0;
  border-left: 3px solid #7cb342;
}
.case-detail-legal-articles {
  background-color: #fff8f0;
  border-left: 3px solid #ff9800;
}
.case-detail-mediation-scheme {
  background-color: #f5f0ff;
  border-left: 3px solid #8a63d2;
}
.case-detail-section-content p {
  margin-bottom: 15px;
}
.case-detail-section-content p:last-child {
  margin-bottom: 0;
}
.case-detail-section-content ol,
.case-detail-section-content ul {
  padding-left: 20px;
}
.case-detail-section-content li {
  margin-bottom: 8px;
}
.case-detail-article {
  margin-bottom: 15px;
  padding-bottom: 15px;
  border-bottom: 1px dashed #e0e0e0;
}
.case-detail-article:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}
.case-detail-article-title {
  font-weight: 600;
  color: #d45a00;
  margin-bottom: 5px;
}
.case-detail-note {
  font-style: italic;
  color: #666;
  margin-top: 15px;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 5px;
  border-left: 3px solid #aaa;
}
@media (max-width: 768px) {
  .case-detail-header, .case-detail-info-section, .case-detail-body {
    padding: 20px;
  }
  .case-detail-info-grid {
    grid-template-columns: 1fr;
  }
}
web-app/src/components/tools/TypicalCaseDetailContent.jsx
New file
@@ -0,0 +1,160 @@
import React from 'react';
import './TypicalCaseDetailContent.css';
/**
 * 典型案例详情内容组件 - 与原型 case_search_detail.html 保持一致
 */
const TypicalCaseDetailContent = ({ caseData }) => {
  if (!caseData) return <div className="case-detail-loading">加载中...</div>;
  const { caseTitle, caseType, disputeType, caseNumber, court, judgmentDate, region, disputeTime, content } = caseData;
  return (
    <div className="case-detail-container">
      {/* 案件基本信息 */}
      <div className="case-detail-info-section">
        <h2 className="case-detail-info-title">案件基本信息</h2>
        <div className="case-detail-info-grid">
          <div className="case-detail-info-item">
            <span className="case-detail-info-label">纠纷发生时间:</span>
            <span className="case-detail-info-value">{disputeTime || '2024-4-12 12:00'}</span>
          </div>
          <div className="case-detail-info-item">
            <span className="case-detail-info-label">发生地点:</span>
            <span className="case-detail-info-value">{region || '广东省/广州市'}</span>
          </div>
          <div className="case-detail-info-item">
            <span className="case-detail-info-label">纠纷类型:</span>
            <span className="case-detail-info-value">{disputeType}</span>
          </div>
          <div className="case-detail-info-item">
            <span className="case-detail-info-label">调解组织:</span>
            <span className="case-detail-info-value">{court || '白云区新市街综治中心'}</span>
          </div>
        </div>
      </div>
      {/* 案件内容 */}
      <div className="case-detail-body">
        {content?.overview && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">案例概述</h3>
            <div className="case-detail-section-content">
              {content.overview.split('\n').map((para, i) => (
                <p key={i}>{para}</p>
              ))}
            </div>
          </div>
        )}
        {content?.plaintiffDemand && content.plaintiffDemand.length > 0 && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">原告诉讼请求</h3>
            <div className="case-detail-section-content case-detail-plaintiff-demand">
              <div className="case-detail-inner-content">
                <p>原告{caseData.plaintiff || '黄某'}起诉请求:</p>
                <ol>
                  {content.plaintiffDemand.map((item, i) => (
                    <li key={i}>{item}</li>
                  ))}
                </ol>
              </div>
            </div>
          </div>
        )}
        {content?.courtDecision && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">法院审理与判决</h3>
            <div className="case-detail-section-content case-detail-court-decision">
              <div className="case-detail-inner-content">
                {content.courtDecision.split('\n').map((para, i) => (
                  <p key={i}>{para}</p>
                ))}
              </div>
            </div>
          </div>
        )}
        {content?.mediationBackground && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">调解背景</h3>
            <div className="case-detail-section-content">
              <p>{content.mediationBackground}</p>
            </div>
          </div>
        )}
        {content?.partiesPosition && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">双方立场</h3>
            <div className="case-detail-section-content">
              <p><strong>{caseData.plaintiff || '黄某'}表示:</strong>{content.partiesPosition.plaintiff}</p>
              <p><strong>{caseData.defendant || '郭某'}认为:</strong>{content.partiesPosition.defendant}</p>
            </div>
          </div>
        )}
        {content?.mediationProcess && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">调解过程</h3>
            <div className="case-detail-section-content case-detail-mediation-process">
              <p>{content.mediationProcess}</p>
            </div>
          </div>
        )}
        {content?.mediationScheme && content.mediationScheme.length > 0 && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">调解方案</h3>
            <div className="case-detail-section-content case-detail-mediation-scheme">
              <p>法官提出的调解方案:</p>
              <ul>
                {content.mediationScheme.map((item, i) => (
                  <li key={i}>{item}</li>
                ))}
              </ul>
            </div>
          </div>
        )}
        {content?.mediationResult && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">调解结果</h3>
            <div className="case-detail-section-content case-detail-mediation-result">
              <div className="case-detail-inner-content">
                <p>经过进一步的协商,双方最终接受了法官提出的调解方案。</p>
                <ol>
                  {content.mediationResult.items.map((item, i) => (
                    <li key={i}>{item}</li>
                  ))}
                </ol>
                {content.mediationResult.note && (
                  <div className="case-detail-note">
                    <p>{content.mediationResult.note}</p>
                  </div>
                )}
              </div>
            </div>
          </div>
        )}
        {content?.legalArticles && content.legalArticles.length > 0 && (
          <div className="case-detail-section">
            <h3 className="case-detail-section-title">案例相关法律条文</h3>
            <div className="case-detail-section-content case-detail-legal-articles">
              {content.legalArticles.map((article, i) => (
                <div className="case-detail-article" key={i}>
                  <div className="case-detail-article-title">{article.title}</div>
                  <p>{article.content}</p>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
};
export default TypicalCaseDetailContent;
web-app/src/components/tools/TypicalCaseSearch.css
New file
@@ -0,0 +1,370 @@
/* 典型案例查询样式 - 与原型 case_search.html 保持一致 */
.case-search-container {
  padding: 0;
}
/* 查询条件区域 */
.case-search-query-conditions {
  background: white;
  border-radius: var(--border-radius, 8px);
  padding: 25px;
  margin-bottom: 25px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  border: 1px solid var(--border-color, #dee2e6);
}
.case-search-query-title {
  font-size: 1.2rem;
  color: var(--dark-color, #212529);
  margin-bottom: 20px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
.case-search-query-title i {
  color: var(--primary-color, #1a6fb8);
}
.case-search-query-form {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
.case-search-form-group {
  margin-bottom: 5px;
}
.case-search-form-label {
  display: block;
  margin-bottom: 8px;
  font-weight: 600;
  color: var(--dark-color, #212529);
  font-size: 1rem;
}
.case-search-date-input-group {
  display: flex;
  align-items: center;
  gap: 10px;
}
.case-search-date-separator {
  color: var(--gray-color, #6c757d);
  font-weight: 600;
}
.case-search-button-group {
  display: flex;
  gap: 15px;
  align-items: flex-end;
}
/* 筛选器区域 */
.case-search-filters-section {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 25px;
  margin-bottom: 30px;
}
.case-search-filter-category {
  background: white;
  border-radius: var(--border-radius, 8px);
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  border: 1px solid var(--border-color, #dee2e6);
}
.case-search-filter-title {
  font-size: 1.1rem;
  color: var(--dark-color, #212529);
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-color, #dee2e6);
  font-weight: 600;
}
.case-search-filter-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.case-search-filter-item {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  padding: 8px 5px;
  border-radius: 4px;
  transition: all 0.2s ease;
  font-size: 1.05rem;
}
.case-search-filter-item:hover {
  background-color: #f8f9fa;
  color: var(--primary-color, #1a6fb8);
}
.case-search-filter-item.active {
  background-color: #e3f2fd;
  color: var(--primary-color, #1a6fb8);
  font-weight: 600;
}
.case-search-filter-checkbox {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border-color, #dee2e6);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
}
.case-search-filter-checkbox.checked {
  background-color: var(--primary-color, #1a6fb8);
  border-color: var(--primary-color, #1a6fb8);
  color: white;
}
.case-search-filter-checkbox.checked i {
  font-size: 0.9rem;
}
.case-search-filter-count {
  margin-left: auto;
  font-size: 0.9rem;
  color: var(--gray-color, #6c757d);
  background: #f8f9fa;
  padding: 3px 10px;
  border-radius: 20px;
  min-width: 60px;
  text-align: center;
}
/* 子筛选器 */
.case-search-sub-filter-list {
  margin-left: 20px;
  margin-top: 8px;
  border-left: 2px solid #e9ecef;
  padding-left: 15px;
}
.case-search-sub-filter-item {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  padding: 6px 5px;
  font-size: 0.95rem;
  transition: all 0.2s ease;
}
.case-search-sub-filter-item:hover {
  color: var(--primary-color, #1a6fb8);
}
/* 查询结果区域 */
.case-search-results-section {
  background: white;
  border-radius: var(--border-radius, 8px);
  padding: 25px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  border: 1px solid var(--border-color, #dee2e6);
}
.case-search-results-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 25px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--border-color, #dee2e6);
}
.case-search-results-title {
  font-size: 1.3rem;
  color: var(--dark-color, #212529);
  font-weight: 600;
  margin: 0;
}
.case-search-total-count {
  font-size: 1rem;
  color: var(--gray-color, #6c757d);
  background: #f8f9fa;
  padding: 8px 15px;
  border-radius: 20px;
  font-weight: 600;
}
.case-search-cases-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.case-search-case-item {
  padding: 20px;
  border: 1px solid var(--border-color, #dee2e6);
  border-radius: var(--border-radius, 8px);
  transition: all 0.2s ease;
  cursor: pointer;
  background: white;
}
.case-search-case-item:hover {
  border-color: var(--primary-color, #1a6fb8);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transform: translateY(-3px);
}
.case-search-case-title {
  font-size: 1.15rem;
  color: var(--primary-color, #1a6fb8);
  margin-bottom: 15px;
  font-weight: 600;
  line-height: 1.4;
}
.case-search-case-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  font-size: 0.95rem;
  color: var(--gray-color, #6c757d);
  margin-bottom: 10px;
}
.case-search-case-meta-item {
  display: flex;
  align-items: center;
  gap: 8px;
}
.case-search-case-meta-item i {
  font-size: 0.9rem;
  color: var(--primary-color, #1a6fb8);
}
.case-search-case-type-badge {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 0.85rem;
  font-weight: 600;
  margin-top: 5px;
}
.case-search-case-type-badge.judgment {
  background-color: #e3f2fd;
  color: var(--primary-color, #1a6fb8);
}
.case-search-case-type-badge.mediation {
  background-color: #e8f5e9;
  color: var(--success-color, #2a9d8f);
}
/* 分页 */
.case-search-pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 15px;
  margin-top: 35px;
  padding-top: 25px;
  border-top: 1px solid var(--border-color, #dee2e6);
}
/* 自定义 Modal 样式,模拟原型效果 */
.case-detail-antd-modal .ant-modal-content {
  border-radius: 12px !important;
  overflow: hidden !important;
  box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3) !important;
}
.case-detail-antd-modal .ant-modal-header {
  padding: 0 !important;
  border-bottom: none !important;
}
.case-detail-modal-header-custom {
  background: linear-gradient(135deg, #1e5799 0%, #207cca 100%);
  color: white;
  padding: 15px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.case-detail-modal-header-custom h2 {
  margin: 0;
  font-size: 1.5rem;
  color: white;
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
.case-detail-modal-close-custom {
  color: white;
  font-size: 28px;
  font-weight: bold;
  cursor: pointer;
  transition: transform 0.2s;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}
.case-detail-modal-close-custom:hover {
  transform: scale(1.1);
  background-color: rgba(255, 255, 255, 0.1);
}
.case-detail-antd-modal .ant-modal-close {
  top: 10px;
  right: 10px;
  color: white !important;
}
.case-detail-antd-modal .ant-modal-close-x {
  display: none; /* 使用自定义的 closeIcon */
}
/* 响应式 */
@media (max-width: 1200px) {
  .case-search-query-form {
    grid-template-columns: repeat(2, 1fr);
  }
  .case-search-filters-section {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 768px) {
  .case-search-query-form {
    grid-template-columns: 1fr;
  }
  .case-search-filters-section {
    grid-template-columns: 1fr;
  }
  .case-search-date-input-group {
    flex-direction: column;
    align-items: stretch;
  }
  .case-search-date-separator {
    display: none;
  }
}
web-app/src/mocks/caseMocks.js
@@ -5,16 +5,47 @@
  list: [
    {
      id: 'case-001',
      caseTitle: '张某诉某科技公司欠薪纠纷案',
      caseType: '判决',
      disputeType: '欠薪',
      caseNumber: '(2024)京0108民初12345号',
      court: '北京市海淀区人民法院',
      judgmentDate: '2024-03-15',
      region: '北京市海淀区',
      parties: '原告:张某;被告:北京某科技有限公司',
      caseSummary:
        '原告张某于2022年1月入职被告公司担任软件工程师,双方签订了为期三年的劳动合同。2023年6月起,被告公司未按时足额支付原告工资,累计欠薪3个月,共计人民币45000元。原告多次催讨未果,遂诉至法院。法院经审理认为,被告公司应按劳动合同约定及时足额支付劳动报酬,判决被告公司支付原告欠薪45000元及相应利息。',
      caseTitle: '黄某诉郭某隐私权邻里纠纷调解典型案例',
      caseType: '调解',
      disputeType: '劳动争议', // 这里按原型写劳动争议
      caseNumber: '(2020)沪0118民初15600号',
      court: '白云区新市街综治中心',
      judgmentDate: '2024-04-12',
      region: '广东省/广州市',
      parties: '原告:黄某;被告:郭某',
      disputeTime: '2024-4-12 12:00',
      caseSummary: '本案是一起涉及邻里之间隐私权纠纷的案件。在调解过程中,法官通过实地查看、耐心解释法律规定和情理劝导等方式,促使双方换位思考,理解对方的立场和需求。',
      content: {
        overview: '本案是一起涉及邻里之间隐私权纠纷的案件。在调解过程中,法官通过实地查看、耐心解释法律规定和情理劝导等方式,促使双方换位思考,理解对方的立场和需求。调解方案既考虑了黄某对隐私权保护的合理诉求,又兼顾了邓某对家庭安全的关注,在维护法律尊严的同时,最大限度地修复了邻里关系。\n\n这一案例体现了调解在解决邻里纠纷中的独特优势,能够以更加灵活、和谐的方式化解矛盾,实现法律效果和社会效果的有机统一。',
        plaintiffDemand: [
          '判令被告邓某立即停止侵权行为并赔偿损失人民币3000元;',
          '判令被告邓某支付原告为制止侵权行为支出的律师费3000元。',
        ],
        courtDecision: '上海市青浦区人民法院于2020年12月28日作出(2020)沪0118民初15600号民事判决,判令:\n1. 被告邓某于判决生效之日起十日内拆除其安装于上海市青浦区赵巷镇嘉松中路XXX室入户门的可视门铃;\n2. 驳回原告黄某的其他诉讼请求。\n\n一审宣判后,双方当事人均未提出上诉,判决已发生法律效力。',
        mediationBackground: '在本案审理过程中,主审法官意识到邻里纠纷如果能够通过调解解决,将更有助于修复邻里关系,促进社区和谐。于是,在开庭前,法官尝试组织双方进行调解。',
        partiesPosition: {
          plaintiff: '自从邓某安装了可视门铃后,自己总感觉处于被监视的状态,日常生活受到严重干扰,精神高度紧张。自己多次与邓某沟通要求拆除,但邓某拒不配合,无奈之下才诉至法院。',
          defendant: '安装可视门铃是为了方便家庭安全考虑,方便监测住宅周边情况。而且可视门铃感应距离只有3米,拍摄到的黄某家画面模糊不清,根本无法看清具体情况,不存在侵犯隐私的问题。',
        },
        mediationProcess: '法官在听取双方陈述后,带领双方来到争议的可视门铃安装现场进行实地查看。通过现场查看,法官向邓某指出,虽然可视门铃可能在技术参数上显示感应距离有限,但实际安装位置正对着黄某家多住户的卧室和阳台,即使面面模糊,仍然可能获取到一些关于住户日常生活的信息。',
        mediationScheme: [
          '邓某将可视门铃调整至一个既能满足其对自家门口一定范围监测需求,又不会直接拍摄到黄某等住户卧室和阳台的位置;',
          '邓某向黄某口头赔礼道歉,以缓解双方之间的紧张关系;',
          '对于黄某提出的赔偿费用,考虑到邓某的行为并未造成实际的物质损失,且双方均为邻里关系,希望黄某能够适当降低赔偿要求,邓某则给予一定的经济补偿。',
        ],
        mediationResult: {
          items: [
            '邓某当场向黄某口头赔礼道歉,表示自己之前没有充分考虑到邻居的感受,今后会注意行为方式。',
            '邓某承诺会尽快将可视门铃调整至合适位置。',
            '黄某也同意接受邓某500元的经济补偿,不再要求其他赔偿。',
          ],
          note: '本案通过调解方式成功解决了邻里纠纷,既保护了原告的隐私权,又兼顾了被告的安全需求,同时修复了邻里关系,实现了法律效果与社会效果的统一。',
        },
        legalArticles: [
          { title: '《中华人民共和国劳动法》第十六条', content: '劳动合同是劳动者与用人单位确立劳动关系、明确双方权利和义务的协议。建立劳动关系应当订立劳动合同。' },
          { title: '《中华人民共和国劳动法》第十七条', content: '订立和变更劳动合同,应当遵循平等自愿、协商一致的原则,不得违反法律、行政法规的规定。劳动合同依法订立即具有法律约束力,当事人必须履行劳动合同规定的义务。' },
        ],
      },
    },
    {
      id: 'case-002',
web-app/src/mocks/lawMocks.js
@@ -44,3 +44,51 @@
    total: 2,
  },
};
export const mockLawDetail = {
  id: 'law-001',
  lawName: '中华人民共和国劳动法',
  effectLevel: '法律',
  status: '有效',
  org: '全国人民代表大会',
  publishDate: '2020-05-28',
  effectiveDate: '2021-01-01',
  chapters: [
    {
      id: 'chapter1',
      title: '第一章 总则',
      articles: [
        {
          number: '第一条',
          content: '为了保护劳动者的合法权益,调整劳动关系,建立和维护适应社会主义市场经济的劳动制度,促进经济发展和社会进步,根据宪法,制定本法。',
        },
        {
          number: '第二条',
          content: '在中华人民共和国境内的企业、个体经济组织(以下简称用人单位)和与之形成劳动关系的劳动者,适用本法。\n\n国家机关、事业组织、社会团体和与之建立劳动合同关系的劳动者,依照本法执行。',
        },
        {
          number: '第三条',
          content: '劳动者享有平等就业和选择职业的权利、取得劳动报酬的权利、休息休假的权利、获得劳动安全卫生保护的权利、接受职业技能培训的权利、享受社会保险和福利的权利、提请劳动争议处理的权利以及法律规定的其他劳动权利。\n\n劳动者应当完成劳动任务,提高职业技能,执行劳动安全卫生规程,遵守劳动纪律和职业道德。',
        },
      ],
    },
    {
      id: 'chapter2',
      title: '第二章 促进就业',
      articles: [
        {
          number: '第十条',
          content: '国家通过促进经济和社会发展,创造就业条件,扩大就业机会。\n\n国家鼓励企业、事业单位、社会团体在法律法规规定的范围内兴办产业或者拓展经营,增加就业。\n\n国家支持劳动者自愿组织起来就业和从事个体经营实现就业。',
        },
        {
          number: '第十一条',
          content: '地方各级人民政府应当采取措施,发展多种类型的职业介绍机构,提供就业服务。',
        },
        {
          number: '第十二条',
          content: '劳动者就业,不因民族、种族、性别、宗教信仰不同而受歧视。',
        },
      ],
    },
  ],
};