# 法律条文查询API对接数据展示 ## 变更概述 将法律条文查询功能从Mock数据迁移到真实API对接,实现完整的数据加载、查询过滤、分页展示和详情查看流程。 ## 背景与动机 当前LawSearchContent组件使用静态Mock数据,无法满足实际业务需求。需要对接后端API实现: 1. 页面加载时自动获取热门法律法规 2. 动态加载分类统计数据用于筛选器 3. 支持多条件组合查询和分页 4. 点击展开时动态加载法条内容 5. 弹出详情窗口展示完整法律原文 ## 核心目标 1. **默认数据加载**:页面打开时调用getHotLaws获取热门法律列表 2. **分类统计加载**:调用getCategoryStatistics获取筛选器数据(法律性质、制定机关、时效性) 3. **查询功能**:支持关键词、日期范围、多维度筛选的组合查询 4. **展开加载法条**:点击卡片展开时调用getLawProvisions获取条文内容 5. **详情弹窗**:点击展开卡片时弹出详情窗,调用getLawOriginalDetail获取原文并解析章节导航 ## 设计决策 ### 1. API调用时机 | 场景 | API | 触发时机 | 参数 | |------|-----|---------|------| | 页面初始化 | getHotLaws | useEffect首次加载 | limit=10 | | 页面初始化 | getCategoryStatistics | useEffect首次加载 | 无 | | 点击查询按钮 | searchLaws | 用户触发 | keyword, publishStart, publishEnd, lawNatures, authorities, validities, page, size | | 点击卡片展开 | getLawProvisions | 首次展开时 | law_info_id | | 点击展开卡片 | getLawOriginalDetail | 弹窗打开时 | law_original_info_id | ### 2. 数据映射 #### 列表数据映射(从API返回到UI展示) ```javascript // API返回: lawDataList.data.data { law_info_id: "xxx", // 法律信息ID(用于getLawProvisions) law_original_info_id: "yyy", // 法律原文ID(用于getLawOriginalDetail) title: "中华人民共和国劳动法", // → 法律标题 validity_name: "有效", // → 时效性 law_nature_name: "法律", // → 法律效力位阶 authority_name: "全国人民代表大会", // → 制定机关 publish_time: "2020-05-28", // → 公布日期 implementation_time: "2021-01-01" // → 实施日期 } ``` #### 分类统计数据映射 ```javascript // API返回: categoryResponse.data [ { code: 101, name: "全国人民代表大会", value: "1", count: 256 }, { code: 102, name: "法律", value: "1", count: 300 }, { code: 103, name: "有效", value: "1", count: 500 } ] // 分组逻辑: // - code=101 → 制定机关筛选器 // - code=102 → 法律性质筛选器 // - code=103 → 时效性筛选器 // 每组按count降序排列,默认勾选第一项 ``` ### 3. 查询参数构建 ```javascript // 筛选器选中项转换为API参数 const buildSearchParams = () => { const params = { page: currentPage, size: pageSize }; // 关键词(非空才传) if (keyword.trim()) { params.keyword = keyword.trim(); } // 公布日期(两个都有值才传) if (publishStart && publishEnd) { params.publishStart = publishStart; // YYYY-MM-DD格式 params.publishEnd = publishEnd; } // 法律性质(多个用逗号拼接) const selectedNatures = filters.lawNature .filter(item => item.checked) .map(item => item.value) .join(','); if (selectedNatures) { params.lawNatures = selectedNatures; } // 制定机关(多个用逗号拼接) const selectedAuthorities = filters.org .filter(item => item.checked) .map(item => item.value) .join(','); if (selectedAuthorities) { params.authorities = selectedAuthorities; } // 时效性(多个用逗号拼接) const selectedValidities = filters.validity .filter(item => item.checked) .map(item => item.value) .join(','); if (selectedValidities) { params.validities = selectedValidities; } return params; }; ``` ### 4. 章节导航提取逻辑 从 `provision_text` (纯文本) 提取章节列表: ```javascript const extractChapters = (provisionText) => { // 1. 定位目录区域 const startMarker = "\n\n目  录\n"; const endMarker = "\n\n第一章 总则\n\n"; const startIndex = provisionText.indexOf(startMarker); const endIndex = provisionText.indexOf(endMarker); if (startIndex === -1 || endIndex === -1) { return []; // 未找到目录标记,返回空数组 } // 2. 提取目录内容 const tocContent = provisionText.substring( startIndex + startMarker.length, endIndex ); // 3. 按行分割并过滤空行 const lines = tocContent.split('\n').filter(line => line.trim()); // 4. 转换为章节对象 return lines.map((line, index) => ({ id: `chapter-${index + 1}`, title: line.trim() })); }; ``` ### 5. 状态管理策略 ```javascript const [loading, setLoading] = useState(false); // 加载中状态 const [list, setList] = useState([]); // 法律列表 const [filters, setFilters] = useState({ // 筛选器数据 lawNature: [], // 从API加载 org: [], // 从API加载 validity: [] // 从API加载 }); const [activeId, setActiveId] = useState(null); // 当前展开的卡片ID const [expandedProvisions, setExpandedProvisions] = useState({}); // 已加载的法条内容 {law_info_id: articles} const [currentPage, setCurrentPage] = useState(1); // 当前页码 const [total, setTotal] = useState(0); // 总记录数 const [pageSize, setPageSize] = useState(10); // 每页数量 ``` ### 6. 错误处理与降级 - API调用失败时显示Toast提示,不降级到Mock数据 - 筛选器加载失败时禁用查询按钮 - 法条内容加载失败时展示"加载失败,请重试" - 详情弹窗加载失败时显示错误提示 ## 技术实现要点 ### 涉及文件 1. `web-app/src/components/tools/LawSearchContent.jsx` - 主要修改 2. `web-app/src/services/LawAPIService.js` - 确认API方法签名 3. `web-app/src/components/tools/LawDetailContent.jsx` - 详情组件修改 ### 关键修改点 #### 1. 初始化数据加载 ```javascript useEffect(() => { const loadInitialData = async () => { setLoading(true); try { const [hotLawsRes, categoryRes] = await Promise.all([ LawAPIService.getHotLaws(10), LawAPIService.getCategoryStatistics() ]); // 处理法律列表 setList(hotLawsRes.data?.data || []); setTotal(hotLawsRes.data?.total || 0); setPageSize(hotLawsRes.data?.size || 10); // 处理分类统计 const categories = categoryRes.data || []; setFilters({ lawNature: processCategory(categories, 102), org: processCategory(categories, 101), validity: processCategory(categories, 103) }); } catch (error) { message.error('数据加载失败,请刷新重试'); } finally { setLoading(false); } }; loadInitialData(); }, []); ``` #### 2. 查询功能 ```javascript const handleSearch = async () => { setLoading(true); try { const params = buildSearchParams(); const response = await LawAPIService.searchLaws(params); setList(response.data?.data || []); setTotal(response.data?.total || 0); setPageSize(response.data?.size || 10); setCurrentPage(params.page); // 清空已展开的法条缓存 setExpandedProvisions({}); setActiveId(null); } catch (error) { message.error('查询失败,请重试'); } finally { setLoading(false); } }; ``` #### 3. 展开加载法条 ```javascript const handleLawItemClick = async (law) => { const lawInfoId = law.law_info_id; if (activeId === lawInfoId) { // 已展开状态,弹出详情 setSelectedLawId(law.law_original_info_id); setDetailVisible(true); } else { // 首次展开,加载法条内容 setActiveId(lawInfoId); if (!expandedProvisions[lawInfoId]) { try { const response = await LawAPIService.getLawProvisions(lawInfoId); setExpandedProvisions(prev => ({ ...prev, [lawInfoId]: response.data || [] })); } catch (error) { message.error('法条内容加载失败'); } } } }; ``` #### 4. 详情弹窗 ```javascript // LawDetailContent.jsx修改 useEffect(() => { const loadDetail = async () => { if (!lawId) return; setLoading(true); try { const response = await LawAPIService.getLawOriginalDetail(lawId); const detailData = response.data; // 从provision_text提取章节 const chapters = extractChapters(detailData.provision_text || ''); setLawDetail({ ...detailData, chapters }); } catch (error) { message.error('详情加载失败'); } finally { setLoading(false); } }; loadDetail(); }, [lawId]); ``` ## 验收标准 ### 功能验收 - [ ] 页面打开时自动显示热门法律列表(10条) - [ ] 筛选器显示从API加载的分类数据,默认勾选第一项 - [ ] 点击查询按钮能正确传递所有筛选条件 - [ ] 分页组件根据API返回的total和size正确显示 - [ ] 点击卡片首次展开时加载法条内容 - [ ] 点击已展开卡片能弹出详情窗口 - [ ] 详情窗口显示完整法律信息和章节导航 - [ ] 章节导航点击能锚点跳转到对应内容 ### 数据验收 - [ ] 列表展示的字段与API返回字段正确映射 - [ ] 筛选器按code分组且按count降序排列 - [ ] 查询参数正确处理空值(空值不传) - [ ] 多选筛选器的value用逗号拼接 - [ ] 分页切换时保持查询条件 ### 异常处理 - [ ] API调用失败时显示友好提示 - [ ] 加载中状态正确显示Spin遮罩 - [ ] 无数据时显示"暂无数据"提示 - [ ] 网络错误能捕获并提示用户 ## 风险与注意事项 1. **API返回数据结构**:需要与后端确认实际返回的数据结构是否与文档一致 2. **章节提取逻辑**:provision_text格式可能存在变化,需要容错处理 3. **性能考虑**:展开法条时的API调用需要加载状态反馈 4. **缓存策略**:已展开的法条内容应缓存,避免重复请求 5. **分页保持**:切换页码时需要保持当前的查询条件和筛选状态 ## 参考资料 - 原型文件:`document/原型/law_search.html` - 详情原型:`document/原型/law_search_detail.html` - API Service:`web-app/src/services/LawAPIService.js` - 现有组件:`web-app/src/components/tools/LawSearchContent.jsx`