/** * 评查点列表组件 * * 功能概述: * - 展示评查结果统计信息(总计、通过、警告、错误数量) * - 提供评查点过滤功能(按状态和搜索文本) * - 显示评查点详细信息(标题、状态、内容、建议修改等) * - 支持评查点操作(一键替换、人工审核等) * * 组件结构: * - 统计区域: 显示评查点数量统计 * - 搜索区域: 提供文本搜索功能 * - 评查点列表: 展示所有评查点 * - 评查点卡片: 展示单个评查点详情 * - 评查点头部: 显示标题和状态 * - 评查点内容: 显示当前内容和问题 * - 建议修改区域: 显示建议的修改内容 */ import { useState, useEffect, useRef } from 'react'; import { toastService } from '../ui/Toast'; import { createPortal } from 'react-dom'; // 导入React Portal API,用于将组件渲染到DOM树的不同位置 import { Tooltip } from '../ui/Tooltip'; import { Modal } from '../ui/Modal'; import { Table } from '../ui/Table'; import { Pagination } from '../ui/Pagination'; import { Button } from '../ui/Button'; import { LoadingIndicator } from '../ui/SkeletonScreen'; import { performOpinionAction, type CrossCheckingOpinion, type OpinionActionType } from '../../api/cross-checking/cross-file-result'; import { useFetcher, useNavigate } from '@remix-run/react'; import { API_BASE_URL } from '~/config/api-config'; // import '../../styles/components/TooltipStyles.css'; /** * 比较方法映射 * 将后端返回的比较方法英文值映射为友好的中文显示 */ const compareMethodMap: Record = { 'exact': '精确匹配', 'contains': '包含关系', 'semantic': '大模型语义匹配', // 可以根据需要添加更多映射 }; /** * 获取比较方法的中文显示 * @param method 比较方法的原始值 * @returns 映射后的中文显示文本 */ const getCompareMethodText = (method?: string): string => { if (!method) return '相等'; const text = compareMethodMap[method] || method; // 确保返回的是字符串类型 return typeof text === 'string' ? text : String(text); }; /** * 规则类型映射 * 将后端返回的规则类型英文值映射为友好的中文显示 */ const ruleTypeMap: Record = { 'exists': '有无判断', 'format': '格式判断', 'logic': '逻辑判断', 'regex': '正则表达式', // 可以根据需要添加更多映射 }; /** * 获取规则类型的中文显示 * @param type 规则类型的原始值 * @returns 映射后的中文显示文本 */ const getRuleTypeText = (type?: string): string => { if (!type) return ''; return ruleTypeMap[type] || type; }; /** * 评查点类型定义 * 用于展示单个评查结果 */ export interface ReviewPoint { id: string; documentId?: string; pointId?: string; evaluationPointId?: string | number; // 新增,允许兜底 editAuditStatusId?: string | number; editAuditStatus: number; editAuditStatusMessage?: string; // 添加审核意见字段 pointName: string; title: string; groupName: string; status: string; content: Record; suggestion: string; needsHumanReview?: boolean; humanReviewNote?: string; humanReviewBy?: string; humanReviewTime?: string; contentPage?: Record; position?: { section: string; index: number; }; result?: boolean; legalBasis?: { name?: string; content?: string; articles?: Array; [key: string]: unknown; }; postAction?: string; actionContent?: string; failMessage?: string; passMessage?: string; score?: number; // 评查点满分 finalScore?: number | null; // 评查点最终获得分数 machineScore?: number; // 评查点机器获得分数 evaluationConfig?: { rules?: Array<{ type: string; config?: { fields?: string[]; pairs?: Array<{ sourceField?: string; targetField?: string }>; logic?: string; }; }>; }; evaluatedPointResultsLog?: { rules: Array<{ id: string; type: string; res?: boolean; config: Record; }>; }; } // 统计数据类型 interface Statistics { total: number; success: number; warning: number; error: number; score: number; } // 定义评分提案数据接口 interface ScoringProposal { id: string | number; evaluation_result_id: string | number; proposer_id: string | number; proposed_score: number; reason: string; status: string; created_at: string; updated_at: string; document_id: string | number; } interface UserInfo { id: number; [key: string]: unknown; } interface ReviewPointsListProps { reviewPoints: ReviewPoint[]; statistics: Statistics; activeReviewPointResultId: string | null; onReviewPointSelect: (id: string, page?: number) => void; onStatusChange?: (id: string, editAuditStatusId: string | number, status: string, message: string) => void; scoringProposals?: ScoringProposal[]; jwtToken?: string; // 添加JWT token参数 userInfo?: UserInfo; // 添加用户信息参数 onOpinionSubmitted?: (newProposal: ScoringProposal) => void; // 新增:意见提交成功后的回调 } /** * 全局状态对象,存储当前活动的提示框信息 * 这种方式避免了复杂的状态提升或Context API的使用 */ let activeTooltip = { show: false, // 控制提示框是否显示 content: null as React.ReactNode, // 提示框内容(React节点) position: { top: 0, left: 0 }, // 提示框在屏幕上的位置 ready: false // 新增:控制是否已准备好显示 }; /** * 提示框Portal组件 * * 使用React Portal将提示框渲染到document.body下, * 这样可以确保提示框不受任何父元素overflow或z-index限制 */ function TooltipPortal() { // 使用本地状态保存提示框信息的副本 const [tooltip, setTooltip] = useState(activeTooltip); useEffect(() => { // 通过自定义事件机制监听全局tooltip状态更新 const updateTooltip = () => { // 使用扩展运算符创建对象副本,确保状态更新被React检测到 setTooltip({...activeTooltip}); }; // 添加事件监听器 window.addEventListener('tooltip-update', updateTooltip); // 组件卸载时清理事件监听器 return () => { window.removeEventListener('tooltip-update', updateTooltip); }; }, []); // 如果不显示或没有内容,则不渲染任何东西 if (!tooltip.show || !tooltip.content) return null; // 使用createPortal将提示框内容渲染到document.body return createPortal(
{tooltip.content} {/* 添加小三角形指向提示框指向的元素 */}
, document.body // 将内容挂载到body元素,完全脱离原组件DOM结构 ); } /** * 显示提示框的辅助函数 * @param content 要显示的React节点内容 * @param position 显示位置坐标 */ function showTooltip(content: React.ReactNode, position: { top: number; left: number }): void { // 先设置内容和位置,但不立即显示 activeTooltip = { show: true, content, position, ready: false // 初始设为未准备好 }; // 触发事件,让TooltipPortal渲染tooltip(但不可见) window.dispatchEvent(new Event('tooltip-update')); // 使用RAF确保tooltip已渲染到DOM后再计算最终位置 requestAnimationFrame(() => { // 查找刚创建的tooltip元素 const tooltipElement = document.querySelector('.fixed.bg-white.shadow-lg.rounded-md') as HTMLElement; if (tooltipElement) { // 获取tooltip的实际尺寸 const tooltipRect = tooltipElement.getBoundingClientRect(); // 重新计算位置,确保tooltip不会超出视口 let adjustedTop = position.top; let adjustedLeft = position.left; // 检查是否超出右边界 if (adjustedLeft - tooltipRect.width < 0) { adjustedLeft = tooltipRect.width + 10; // 留一些边距 } // 检查是否超出上边界 if (adjustedTop - tooltipRect.height / 2 < 0) { adjustedTop = tooltipRect.height / 2 + 10; } // 检查是否超出下边界 if (adjustedTop + tooltipRect.height / 2 > window.innerHeight) { adjustedTop = window.innerHeight - tooltipRect.height / 2 - 10; } // 更新位置并设为准备好显示 activeTooltip.position = { top: adjustedTop, left: adjustedLeft }; activeTooltip.ready = true; // 再次触发事件更新显示状态 window.dispatchEvent(new Event('tooltip-update')); } else { // 如果找不到tooltip元素,直接显示 activeTooltip.ready = true; window.dispatchEvent(new Event('tooltip-update')); } }); } /** * 隐藏提示框的辅助函数 */ function hideTooltip(): void { // 设置为不显示状态并重置ready状态 activeTooltip.show = false; activeTooltip.ready = false; // 触发自定义事件,通知TooltipPortal组件更新状态 window.dispatchEvent(new Event('tooltip-update')); } /** * React组件表格Tooltip * 将文本数据解析为表格并使用React组件渲染 * 条件性Tooltip组件 * 只有当内容超过2行时才显示tooltip */ const ReactTableTooltip = ({ content }: { content: string }) => { const [showTooltip, setShowTooltip] = useState(false); const [renderedContent, setRenderedContent] = useState(null); const textRef = useRef(null); const isTableLike = content.includes('\t') && content.includes('\n'); useEffect(() => { const checkTextOverflow = () => { const element = textRef.current; if (element) { // 如果是表格格式,总是显示tooltip;否则只在文本溢出时显示 setShowTooltip(isTableLike || element.scrollHeight > element.clientHeight); } }; // 预渲染内容并缓存 if (isTableLike) { setRenderedContent(renderReactTable(content)); } else { setRenderedContent(content); } requestAnimationFrame(checkTextOverflow); window.addEventListener('resize', checkTextOverflow); return () => { window.removeEventListener('resize', checkTextOverflow); }; }, [content, isTableLike]); // 解析表格数据 const parseTableData = (text: string) => { const rows = text.split('\n').map(row => row.split('\t')); return rows; }; // 渲染React表格 const renderReactTable = (text: string) => { try { const tableData = parseTableData(text); const hasHeader = tableData.length > 0; return (
{hasHeader && ( {tableData[0].map((cell, cellIndex) => ( ))} )} {tableData.slice(1).map((row, rowIndex) => ( {row.map((cell, cellIndex) => ( ))} ))}
{cell || ' '}
{cell || ' '}
); } catch (error) { console.error('表格渲染错误:', error); return
表格渲染错误
; } }; return (
{showTooltip ? (
{content}
) : (
{content}
)}
); }; export function ReviewPointsList({ reviewPoints, statistics, activeReviewPointResultId, onReviewPointSelect, scoringProposals = [], jwtToken, userInfo, onOpinionSubmitted }: ReviewPointsListProps) { // 状态管理 const [searchText, setSearchText] = useState(''); // 搜索文本 const [statusFilter, setStatusFilter] = useState(null); // 状态过滤 const [evaluationResultIds, setEvaluationResultIds] = useState([]); // 评分提案的evaluation_result_id const [localScoringProposals, setLocalScoringProposals] = useState(scoringProposals); // 本地状态管理scoringProposals const fetcher = useFetcher(); // 归一化 reviewPoints,确保每个点都有 id 字段 // const [normalizedReviewPoints, setNormalizedReviewPoints] = useState([]); // console.log('normalizedReviewPoints', normalizedReviewPoints); // useEffect(() => { // const norm = reviewPoints.map(point => ({ // ...point, // id: String(point.id || point.evaluationPointId || point.pointId || '') // 保证 id 为字符串且不为 undefined // })); // setNormalizedReviewPoints(norm); // }, [reviewPoints]); // 同步外部scoringProposals到本地状态 useEffect(() => { setLocalScoringProposals(scoringProposals); }, [scoringProposals]); // 在组件中使用localScoringProposals useEffect(() => { if (localScoringProposals && localScoringProposals.length > 0) { // console.log('收到评分提案数据:', localScoringProposals.length, '个提案'); // 获取提案的evaluation_result_id const evaluationResultIds = localScoringProposals.map(proposal => Number(proposal.evaluation_result_id)); setEvaluationResultIds(evaluationResultIds); // console.log('提案的evaluation_result_id:', evaluationResultIds); } }, [localScoringProposals]); // 提出意见模态框相关状态 const [isOpinionModalOpen, setIsOpinionModalOpen] = useState(false); const [selectedReviewPoint, setSelectedReviewPoint] = useState(null); const [opinionForm, setOpinionForm] = useState({ // 评查点名称 auditPoint: '', // 发现问题 foundIssue: '', // 审查意见 auditOpinion: '', // 扣分 deductionScore: 0 }); const [isSubmittingOpinion, setIsSubmittingOpinion] = useState(false); // 意见列表模态框相关状态 const [isOpinionListModalOpen, setIsOpinionListModalOpen] = useState(false); const [opinionListData, setOpinionListData] = useState([]); const [opinionListLoading, setOpinionListLoading] = useState(false); const [opinionListTotal, setOpinionListTotal] = useState(0); const [opinionListCurrentPage, setOpinionListCurrentPage] = useState(1); const [opinionListPageSize, setOpinionListPageSize] = useState(10); const [performingAction, setPerformingAction] = useState(null); // 监听fetcher状态变化 - 获取意见列表数据 useEffect(() => { if (fetcher.data && fetcher.state === 'idle' && opinionListLoading) { const data = fetcher.data as { success?: boolean; data?: { opinions: CrossCheckingOpinion[]; total: number; pagination?: { page: number; page_size: number; total: number; total_pages: number; }; }; error?: string; }; if (data.success && data.data) { console.log('data.data', data.data); setOpinionListData(data.data.opinions || []); setOpinionListTotal(data.data.total || 0); if (data.data.pagination) { setOpinionListCurrentPage(data.data.pagination.page); setOpinionListPageSize(data.data.pagination.page_size); } } else { toastService.error(data.error || '加载意见列表失败'); } setOpinionListLoading(false); } }, [fetcher.data, fetcher.state, opinionListLoading]); // 监听fetcher状态变化 - 提交意见 useEffect(() => { if (fetcher.data && fetcher.state === 'idle' && isSubmittingOpinion) { const data = fetcher.data as { success?: boolean; error?: string; }; if (data.success) { toastService.success('意见提交成功'); handleCloseOpinionModal(); } else { console.error('提交意见失败:', data.error); toastService.error(data.error || '提交意见失败'); } setIsSubmittingOpinion(false); } }, [fetcher.data, fetcher.state, isSubmittingOpinion]); // 存放评查点ID与有效页码的映射 const [effectivePages, setEffectivePages] = useState>({}); /** * 打开提出意见模态框 */ const handleOpenOpinionModal = (reviewPoint: ReviewPoint) => { // 如果评分提案的evaluation_result_id包含当前评查点的id,则不打开模态框 if (evaluationResultIds.includes(Number(reviewPoint.id))) { toastService.error('当前评查点已有意见提出项,可前往意见列表查看'); return; } setSelectedReviewPoint(reviewPoint); setOpinionForm({ auditPoint: reviewPoint.pointName, foundIssue: reviewPoint.result ? (reviewPoint.passMessage || '') : (reviewPoint.failMessage || ''), auditOpinion: '', deductionScore: 0 }); setIsOpinionModalOpen(true); }; /** * 关闭提出意见模态框 */ const handleCloseOpinionModal = () => { setIsOpinionModalOpen(false); setSelectedReviewPoint(null); setOpinionForm({ auditPoint: '', foundIssue: '', auditOpinion: '', deductionScore: 0 }); }; /** * 处理意见表单输入 */ const handleOpinionFormChange = (field: string, value: string | number) => { setOpinionForm(prev => ({ ...prev, [field]: value })); }; /** * 加载意见列表数据 */ const loadOpinionListData = async (page: number = 1, pageSize: number = 10, documentId?: string | number) => { // 使用传入的documentId或者从selectedReviewPoint获取 const targetDocumentId = documentId || selectedReviewPoint?.documentId; if (!targetDocumentId) return; setOpinionListLoading(true); try { // 使用 fetcher 调用路由的 action const formData = new FormData(); formData.append("intent", "getCrossCheckingOpinions"); formData.append("documentId", targetDocumentId.toString()); formData.append("page", page.toString()); formData.append("pageSize", pageSize.toString()); fetcher.submit(formData, { method: "POST" }); } catch (error) { console.error('加载意见列表失败:', error); toastService.error('加载意见列表失败'); setOpinionListLoading(false); } }; /** * 打开意见列表模态框 */ const handleOpenOpinionListModal = (reviewPoint: ReviewPoint) => { // console.log('查看reviewPoint', reviewPoint); if (localScoringProposals.length === 0) { toastService.warning('当前文件尚未有人提出过意见'); return; } setSelectedReviewPoint(reviewPoint); setIsOpinionListModalOpen(true); // console.log('打开意见列表模态框'); // 直接传递reviewPoint的documentId,避免依赖状态更新 loadOpinionListData(1, 10, reviewPoint.documentId); }; /** * 关闭意见列表模态框 */ const handleCloseOpinionListModal = () => { setIsOpinionListModalOpen(false); setOpinionListData([]); setOpinionListTotal(0); setOpinionListCurrentPage(1); setOpinionListPageSize(10); }; /** * 处理意见操作(赞同、反对、撤销投票、撤销意见) */ const handleOpinionAction = async (opinionId: string | number, action: OpinionActionType) => { const actionKey = `${opinionId}-${action}`; setPerformingAction(actionKey); try { const response = await performOpinionAction({ opinionId, action }, jwtToken, userInfo as { user_id: number } | undefined); if (response.error) { toastService.error(response.error); return; } toastService.success(response.data?.message || '操作成功'); // console.log('即将重新加载数据'); // 重新加载数据 await loadOpinionListData(opinionListCurrentPage, opinionListPageSize); } catch (error) { console.error('操作失败:', error); toastService.error(error instanceof Error ? error.message : '操作失败,请稍后重试'); } finally { setPerformingAction(null); } }; /** * 处理意见列表分页变化 */ const handleOpinionListPageChange = (page: number) => { setOpinionListCurrentPage(page); loadOpinionListData(page, opinionListPageSize); }; /** * 处理意见列表每页大小变化 */ const handleOpinionListPageSizeChange = (size: number) => { setOpinionListPageSize(size); loadOpinionListData(1, size); }; /** * 刷新意见列表 */ const handleRefreshOpinionList = () => { loadOpinionListData(opinionListCurrentPage, opinionListPageSize); }; /** * 提交意见 */ const handleSubmitOpinion = async () => { // 校验表单 if (!opinionForm.auditOpinion.trim()) { toastService.error('请填写审查意见'); return; } if (opinionForm.deductionScore > 100) { toastService.error('扣分不能大于100分'); return; } if (!selectedReviewPoint) { toastService.error('未选择评查点'); return; } // 新增:详细打印每个校验条件 // console.log('校验前 selectedReviewPoint:', selectedReviewPoint); // console.log('校验前 opinionForm:', opinionForm); // console.log('校验前 userInfo:', userInfo); // console.log('documentId:', selectedReviewPoint.documentId, 'isNaN:', isNaN(Number(selectedReviewPoint.documentId)), 'typeof:', typeof selectedReviewPoint.documentId); // console.log('pointId:', selectedReviewPoint.pointId, 'isNaN:', isNaN(Number(selectedReviewPoint.pointId)), 'typeof:', typeof selectedReviewPoint.pointId); // console.log('deductionScore:', opinionForm.deductionScore, 'typeof:', typeof opinionForm.deductionScore, 'isNaN:', isNaN(Number(opinionForm.deductionScore))); // console.log('auditOpinion:', opinionForm.auditOpinion, 'trim:', String(opinionForm.auditOpinion).trim(), 'typeof:', typeof opinionForm.auditOpinion); // console.log('user_id:', userInfo?.user_id, 'typeof:', typeof userInfo?.user_id); // 更严谨的校验逻辑 if ( selectedReviewPoint.documentId === undefined || selectedReviewPoint.pointId === undefined || opinionForm.deductionScore === undefined || opinionForm.auditOpinion === undefined || userInfo?.user_id === undefined || isNaN(Number(selectedReviewPoint.documentId)) || isNaN(Number(selectedReviewPoint.pointId)) || isNaN(Number(opinionForm.deductionScore)) || !String(opinionForm.auditOpinion).trim() ) { toastService.error('请完整填写所有必填项'); setIsSubmittingOpinion(false); return; } // 打印所有关键数据 // console.log('selectedReviewPoint:', selectedReviewPoint); // console.log('opinionForm:', opinionForm); // console.log('userInfo:', userInfo); // 组装后端要求的字段名和内容 const data = { document_id: Number(selectedReviewPoint.documentId), evaluation_point_id: Number(selectedReviewPoint.pointId), proposed_score: Number(opinionForm.deductionScore), reason: opinionForm.auditOpinion, proposer_id: userInfo.user_id, problem_message: opinionForm.foundIssue, evaluation_result_id: Number(selectedReviewPoint.id), }; if (selectedReviewPoint.evaluationPointId) { data.evaluation_result_id = Number(selectedReviewPoint.evaluationPointId); } // 打印最终请求体 // console.log('最终请求体:', data); // 用原生 fetch + application/json 提交 try { const response = await fetch(`${API_BASE_URL.replace(/\/$/, '')}/admin/cross_review/proposals`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${userInfo.frontend_jwt}`, }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { toastService.success('意见提交成功'); // 创建新的提案对象 const newProposal: ScoringProposal = { id: result.id || Date.now(), // 使用返回的ID或时间戳作为临时ID evaluation_result_id: data.evaluation_result_id, proposer_id: data.proposer_id as number, proposed_score: data.proposed_score, reason: data.reason, status: 'pending', // 默认状态 created_at: new Date().toISOString(), updated_at: new Date().toISOString(), document_id: data.document_id }; // 更新本地状态 setLocalScoringProposals(prev => [...prev, newProposal]); // 调用父组件回调(如果提供) if (onOpinionSubmitted) { onOpinionSubmitted(newProposal); } handleCloseOpinionModal(); } else { toastService.error(result.detail || '提交意见失败'); } } catch (error) { console.error('提交意见失败:', error); toastService.error('提交意见失败,请稍后重试'); } setIsSubmittingOpinion(false); }; /** * 过滤评查点 * 根据搜索文本和状态过滤条件筛选评查点 */ const filteredReviewPoints = reviewPoints.filter(point => { // 匹配搜索文本 const matchesSearch = searchText === '' || point.pointName.toLowerCase().includes(searchText.toLowerCase()) || point.title.toLowerCase().includes(searchText.toLowerCase()) || // point.groupName.toLowerCase().includes(searchText.toLowerCase()) || JSON.stringify(point.content).toLowerCase().includes(searchText.toLowerCase()) // 处理状态过滤 let matchesStatus = false; if (statusFilter === null) { // 未选择过滤条件时显示所有 matchesStatus = true; } else if (statusFilter === 'success') { // 过滤"通过"状态 matchesStatus = point.result === true; } else if (statusFilter === 'warning') { // 过滤"警告"状态 matchesStatus = point.result === false && (point.status === 'warning' || point.status === 'info'); } else if (statusFilter === 'error') { // 过滤"错误"状态 matchesStatus = point.result === false && point.status === 'error'; } // console.log('筛选point', point); return matchesSearch && matchesStatus; }); // console.log('筛选filteredReviewPoints', filteredReviewPoints); /** * 渲染评查统计信息 * 显示总计、通过、警告、错误数量 */ const renderStatistics = () => { // 确保传入的statistics存在,否则使用计算值 const statsToUse = statistics || { total: reviewPoints.length, success: 0, warning: 0, error: 0, score: 0 }; // 计算各个状态的评查点数量 const successCount = reviewPoints.filter( point => point.result === true || (point.result === undefined && point.status === 'success') ).length; const warningCount = reviewPoints.filter( point => point.result === false && (point.status === 'warning' || point.status === 'info') ).length; const errorCount = reviewPoints.filter( point => point.result === false && point.status === 'error' ).length; // 如果没有计算值,则使用传入的统计值 const totalToShow = statsToUse.total === 0 ? reviewPoints.length : statsToUse.total; const successToShow = successCount || statsToUse.success; const warningToShow = warningCount || statsToUse.warning; const errorToShow = errorCount || statsToUse.error; return (
{/* 总计数量 */}
{/* 通过数量 */}
{/* 警告数量 */}
{/* 错误数量 */}
); }; /** * 渲染搜索框 * 用于按文本搜索评查点 */ const renderSearchBar = () => { return (
setSearchText(e.target.value)} /> {searchText && ( )}
); }; /** * 渲染评查点状态标签 * @param status 状态文本 * @param result 评查结果 * @param title 标签提示内容 * @returns 状态标签组件 */ const renderStatusBadge = (status: string, result?: boolean, title?: string) => { // 优先根据result判断是否通过 if (result === true) { return ( {title &&
{title}
} } placement="top" theme="light" trigger="hover" showArrow={true} className="tooltip-custom-offset tooltip-top" fixedPlacement={true} > 通过
); } // 当result为false时,根据status决定显示警告还是错误 if (result === false) { if (status === 'warning' || status === 'info') { return ( {title &&
{title}
} } placement="top" theme="light" trigger="hover" showArrow={true} className="tooltip-custom-offset tooltip-top" fixedPlacement={true} > 警告
); } else if (status === 'error') { return ( {title &&
{title}
} } placement="top" theme="light" trigger="hover" showArrow={true} className="tooltip-custom-offset tooltip-top" fixedPlacement={true} > 不通过
); } } }; /** * 渲染评查点主要内容 * @param reviewPoint 评查点 * @returns 评查点主要内容组件 */ const renderContent = (reviewPoint: ReviewPoint, otherRules: Array>) => { return ( <> {/* 渲染其他规则分组 */} {otherRules.map((rule, index) => { return
{renderOtherRule(rule, reviewPoint)}
; })} {/*
*/} {/* 渲染各个一致性的规则分组 */} {reviewPoint.evaluatedPointResultsLog?.rules?.map((rule, index) => { // console.log('rule-------', rule); if (rule.type === 'consistency') { // if (rule.res === true && reviewPoint.result === true) { return
{otherRules.length > 0 &&
} {renderConsistencyRule(rule, reviewPoint)}
; // }else { // return null; // } } if (rule.type === 'ai') { return
{otherRules.length > 0 &&
} {renderModelRule(rule, reviewPoint)}
; } })} ); }; /** * 渲染评查点一致性的规则的样式 * @param singleReviewPoint 一个评查点的一致性规则对象 * @param reviewPoint 评查点 * @returns 评查点一致性的规则的样式 */ const renderConsistencyRule = (singleReviewPoint: Record,reviewPoint: ReviewPoint) => { // 如果评查点结果为false,则判断单个规则是否通过,如果一致,则渲染 if (reviewPoint.result !== singleReviewPoint.res) { return null; } if (!singleReviewPoint || Object.keys(singleReviewPoint).length === 0) { return null; } // console.log('singleReviewPoint-------', singleReviewPoint); // 检查是否存在配置和pairs数组 const config = singleReviewPoint.config as { logic?: string; pairs?: Array<{ sourceField: Record; targetField: Record; res: boolean; compareMethod?: string; }>; selectedFields?: string[] } | undefined; if (!config || !config.pairs || !Array.isArray(config.pairs) || config.pairs.length === 0) { return null; } // 处理配对数据 const pairs = config.pairs; // 获取第一个有效页码 if (reviewPoint.id && !effectivePages[reviewPoint.id]) { for (const pair of pairs) { // 检查sourceField中是否有有效页码 const sourceFieldKey = Object.keys(pair.sourceField)[0]; if (sourceFieldKey && pair.sourceField[sourceFieldKey].page && Number(pair.sourceField[sourceFieldKey].page) > 0) { // 保存页码 setEffectivePages(prev => ({ ...prev, [reviewPoint.id || '']: Number(pair.sourceField[sourceFieldKey].page) })); break; } // 如果sourceField没有有效页码,检查targetField const targetFieldKey = Object.keys(pair.targetField)[0]; if (targetFieldKey && pair.targetField[targetFieldKey].page && Number(pair.targetField[targetFieldKey].page) > 0) { // 保存页码 setEffectivePages(prev => ({ ...prev, [reviewPoint.id || '']: Number(pair.targetField[targetFieldKey].page) })); break; } } } // 查找链条关系 const findChains = () => { type ChainItem = { field: string; data: { key: string; page: number; value: string }; res: boolean; compareMethod?: string; }; const chains: Array> = []; const visited = new Set(); // 构建字段映射关系 const fieldMap = new Map>(); pairs.forEach(pair => { // 提取源字段和目标字段的名称 const sourceFieldKey = Object.keys(pair.sourceField)[0]; const targetFieldKey = Object.keys(pair.targetField)[0]; if (!fieldMap.has(sourceFieldKey)) { fieldMap.set(sourceFieldKey, []); } fieldMap.get(sourceFieldKey)?.push({ targetField: targetFieldKey, data: { source: { key: sourceFieldKey, ...pair.sourceField[sourceFieldKey] }, target: { key: targetFieldKey, ...pair.targetField[targetFieldKey] } }, res: pair.res, compareMethod: pair.compareMethod }); }); // console.log('fieldMap-------', fieldMap); // 查找链条的起始点(只作为源不作为目标的字段) const startPoints = new Set(); for (const [key] of fieldMap.entries()) { let isTarget = false; for (const pair of pairs) { const targetFieldKey = Object.keys(pair.targetField)[0]; if (targetFieldKey === key) { isTarget = true; break; } } if (!isTarget) { startPoints.add(key); } } // console.log('startPoints-------', startPoints); // 从每个起始点开始构建链条 for (const startPoint of startPoints) { if (visited.has(startPoint)) continue; const tempChain: Array = []; let currentField = startPoint; // 向后构建链条 while (fieldMap.has(currentField)) { const targets = fieldMap.get(currentField); if (!targets || targets.length === 0) break; // 找到第一个未访问的目标 let nextTarget = null; for (const target of targets) { if (!visited.has(target.targetField)) { nextTarget = target; break; } } if (!nextTarget) break; // 添加源字段到链条 if (tempChain.length === 0) { tempChain.push({ field: currentField, data: nextTarget.data.source, res: nextTarget.res, compareMethod: nextTarget.compareMethod }); } // 添加目标字段到链条 tempChain.push({ field: nextTarget.targetField, data: nextTarget.data.target, res: nextTarget.res, compareMethod: nextTarget.compareMethod }); // 标记为已访问 visited.add(currentField); visited.add(nextTarget.targetField); // 移动到下一个字段 currentField = nextTarget.targetField; } // console.log('tempChain-------', tempChain); // 检查是否有链条,并处理链条断点 if (tempChain.length > 0) { // 如果链条长度大于1 if (tempChain.length > 1) { // 存储所有拆分后的链条 const splittedChains: Array> = []; // 从后往前遍历,检查每个相邻元素之间的连接 let endIndex = tempChain.length - 1; // 从倒数第一个元素开始往前遍历 for (let i = tempChain.length - 1; i > 0; i--) { // 检查当前元素与前一个元素的连接是否为false // 当前元素为tempChain[i],前一个元素为tempChain[i-1] // 连接结果存储在当前元素(tempChain[i])的result中 const connectionResult = tempChain[i].res; // 如果连接为false,或已到达起始位置,拆分链条 if (!connectionResult) { // 从当前断点到结束索引构建一个新链条 const newChain = tempChain.slice(i, endIndex + 1); if (newChain.length > 1) { splittedChains.push(newChain); // 将当前断点的前一个元素和后一个元素组成一个新链条 const newChain_before = tempChain.slice(i-1, i+1); // console.log('newChain_before-------', newChain_before); splittedChains.push(newChain_before); } // 更新结束索引为当前位置的前一个 endIndex = i - 1; } // 当到达第一个元素前一个位置时,需要处理剩余的链条 if (i === 1) { // 处理剩余部分 (0 到 endIndex) const remainingChain = tempChain.slice(0, endIndex + 1); if (remainingChain.length > 1) { splittedChains.push(remainingChain); } } } // 如果没有任何断点,添加整个链条 if (splittedChains.length === 0) { splittedChains.push([...tempChain]); } // 将拆分的链条添加到结果中 splittedChains.reverse().forEach(chain => { chains.push(chain); }); } else { // 如果链条长度为1,直接添加 chains.push([...tempChain]); } } } // console.log('chains-------', chains); // 处理没有找到的孤立对(这种情况只要规则配置是没问题的,就一定不会存在孤立的情况) for (const pair of pairs) { const sourceFieldKey = Object.keys(pair.sourceField)[0]; const targetFieldKey = Object.keys(pair.targetField)[0]; if (!visited.has(sourceFieldKey) || !visited.has(targetFieldKey)) { const isolatedPair: Array = [ { field: sourceFieldKey, data: { key: sourceFieldKey, ...pair.sourceField[sourceFieldKey] }, res: pair.res }, { field: targetFieldKey, data: { key: targetFieldKey, ...pair.targetField[targetFieldKey] }, res: pair.res } ]; chains.push(isolatedPair); visited.add(sourceFieldKey); visited.add(targetFieldKey); } } return chains; }; const chains = findChains(); return (
{chains.map((chain, chainIndex) => { const isLongChain = chain.length > 2; const res = chain[1].res; // 获取compareMethod // const compareMethod = chain[1].compareMethod || ''; // 转换为友好的显示文本 // const compareMethodText = getCompareMethodText(compareMethod); // 确定样式类名 const itemClassName = res ? "comparison-item match" : "comparison-item mismatch"; // console.log('currentchain-------', chain); // 如果是长链(3个或以上元素) if (isLongChain) { // console.log('currentlongchain-------', chain); return (
{ e.stopPropagation(); // 遍历chain找到第一个有效的page let hasPage = false; for (const item of chain) { if (item.data.page && typeof onReviewPointSelect === 'function') { hasPage = true; onReviewPointSelect(reviewPoint.id, Number(item.data.page)); break; } } if (!hasPage) { // toastService.error('没有找到有效的页码'); } }} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); // 遍历chain找到第一个有效的page for (const item of chain) { if (item.data.page && typeof onReviewPointSelect === 'function') { onReviewPointSelect(reviewPoint.id, Number(item.data.page)); break; } } } }} role="button" tabIndex={0} >
{/* 展示链条 */}
{chain.map((item, idx) => ( {item.field} {idx < chain.length - 1 && ( {typeof chain[idx+1].compareMethod === 'object' ? '' : getCompareMethodText(chain[idx+1].compareMethod)} )} ))}
{/* 展示链条的每个元素的内容 */}
{chain.map((item, idx) => ( ))}
{res ? ( ) : ( )} {/* 使用鼠标事件处理悬停提示 */}
{ // 获取元素位置信息 const rect = e.currentTarget.getBoundingClientRect(); // 创建提示框内容 const content = (
{chain.map((item, idx) => idx >= 1 ? (
{typeof item.compareMethod === 'object' ? '' : `${getCompareMethodText(item.compareMethod)}:`}
{res ? '通过' : '不通过'}
) : null )}
); // 显示提示框 showTooltip(content, { top: rect.top + rect.height/2, left: rect.left }); }} onMouseLeave={hideTooltip} />
); } // 如果是标准的成对比较(2个元素) return (
{chain[0].field.split('-').pop()}
{res ? ( ) : ( )} {/* 使用鼠标事件处理悬停提示 */}
{ // 获取元素位置信息 const rect = e.currentTarget.getBoundingClientRect(); // 创建提示框内容 const content = (
{typeof chain[1].compareMethod === 'object' ? '' : `${getCompareMethodText(chain[1].compareMethod)}:`}
{res ? '通过' : '不通过'}
); // 显示提示框,稍微向下偏移,便于鼠标移动到tooltip上 showTooltip(content, { top: rect.top + rect.height/2, left: rect.left }); }} onMouseLeave={hideTooltip} />
); })}
); }; /** * 渲染评查点有无判断,格式判断,逻辑判断,正则表达式的规则的样式 * @param otherRule 评查点规则数据 * @param reviewPoint 关联的评查点 * @returns 评查点有无判断,格式判断,逻辑判断,正则表达式的规则的样式 */ const renderOtherRule = (otherRule: Record, reviewPoint: ReviewPoint) => { const fieldKey = otherRule.fieldKey as string; const fieldValue = otherRule.fieldValue as { type: Record; }; // 获取res的综合结果 // 如果存在res=false,则整体结果为false,否则为true const hasFailure = Object.values(fieldValue?.type || {}).some(item => item.res === false); const overallResult = !hasFailure; // 找到res为false的条目,用于主要显示 const failedTypeEntry = Object.entries(fieldValue?.type || {}).find(([, item]) => item.res === false); // 如果没有失败的条目,则使用第一个条目 const mainTypeEntry = failedTypeEntry || Object.entries(fieldValue?.type || {})[0]; // 如果没有任何条目,则返回空 if (!mainTypeEntry) return null; const [, mainTypeValue] = mainTypeEntry; /** * 创建提示框内容 * 这个函数返回一个React节点,用于在提示框中显示 * 它将为每种规则类型(exists/format/logic/regex)创建一个带有状态标识的项目 */ const createTooltipContent = () => { return (
{Object.entries(fieldValue?.type || {}).map(([typeKey, typeValue]) => (
{getRuleTypeText(typeKey)}:
{typeValue.res ? '通过' : '不通过'}
))}
); }; /** * 处理鼠标悬停事件 * 当鼠标悬停在状态指示器上时,计算提示框应该显示的位置并显示提示框 * @param e 鼠标事件对象 */ const handleMouseEnter = (e: React.MouseEvent): void => { // 获取触发元素的位置信息 const rect = e.currentTarget.getBoundingClientRect(); // 调用全局函数显示提示框,传递内容和位置信息 showTooltip( createTooltipContent(), { top: rect.top + rect.height/2, left: rect.left } ); }; return ( ); }; /** * 渲染评查点大模型判断的规则的样式 * * 该函数处理AI模型评估的结果展示,包括: * 1. 从规则配置中提取字段和评估结果 * 2. 为每个字段创建可点击的UI元素,显示内容和评估状态 * 3. 展示模型的评估消息 * 4. 处理字段点击导航到相应页面的逻辑 * * @param aiRule 评查点大模型判断的规则对象 * @param reviewPoint 关联的评查点对象 * @returns React组件,用于显示AI模型评估结果 */ const renderModelRule = (aiRule: Record, reviewPoint: ReviewPoint) => { // 从aiRule中提取配置信息 const config = aiRule.config as { model?: string; fields?: Record; message?: string; res?: boolean; } | undefined; // 如果评查点评查结果和规则的结果不一致,则不渲染,跳过 if(config?.res !== reviewPoint.result){ return null; } // 如果配置不存在,不渲染任何内容 if (!config) return null; // 获取第一个有效页码 if (reviewPoint.id && !effectivePages[reviewPoint.id] && config.fields) { for (const field of Object.values(config.fields || {})) { if (field.page && Number(field.page) > 0) { setEffectivePages(prev => ({ ...prev, [reviewPoint.id || '']: Number(field.page) })); break; } } } // 创建一个数组来存储需要渲染的JSX元素 const fieldElements: JSX.Element[] = []; // 遍历fields,获取每个字段的值并生成对应的JSX元素 if (config.fields) { Object.entries(config.fields).forEach(([key, value], index) => { const res = value.value.trim() !== ''; fieldElements.push( ); }); } // 渲染AI模型返回的评估消息 if (config.message) { // 检查message是否为对象,如果是则转换为字符串 const messageContent = typeof config.message === 'object' ? JSON.stringify(config.message) : String(config.message); // 添加模型评估消息区域,使用蓝色背景突出显示 fieldElements.push(

{messageContent}

); } // 返回包含所有元素的React片段 return <>{fieldElements}; }; /** * 过滤评查点中的规则,把type是exists、format、logic、regex的规则中重复的进行去重和合并 * * 该函数的主要作用: * 1. 从评查点的evaluatedPointResultsLog中提取特定类型的规则 * 2. 将相同字段(fieldKey)的不同规则类型结果合并到一起 * 3. 为UI渲染准备统一结构的数据 * * 支持的规则类型: * - exists: 有无判断规则 * - format: 格式判断规则 * - logic: 逻辑判断规则 * - regex: 正则表达式规则 * * @param reviewPoint 评查点对象 * @returns 合并后的规则数组,每个元素包含字段名和各类规则的评估结果 */ const filterOtherRule = (reviewPoint: ReviewPoint) => { // 定义接口描述规则字段值的结构 interface RuleFieldValue { page?: number | string; value?: string; type: Record; } const allRule: Array<{ fieldKey: string; fieldValue: RuleFieldValue; }> = []; for (const rule of reviewPoint.evaluatedPointResultsLog?.rules || []) { // 如果评查点评查结果和规则的结果不一致,则不渲染,跳过 if(rule.config.res !== reviewPoint.result){ continue; } // 处理"有无判断"类型的规则 if (rule.type === 'exists') { // 使用类型断言获取config对象的具体结构 const config = rule.config as { res: boolean; fields: Record; logic?: string; }; // 如果res为true,则遍历fields,提取不为空的字段 if (config.res) { // 遍历fields对象的每个属性 Object.entries(config.fields).forEach(([key, fieldValue]) => { // 只处理值不为空的字段 if (fieldValue.value && fieldValue.value.trim() !== '') { // 创建新对象并添加type标记 const newItem = { fieldKey: key, fieldValue: { ...fieldValue, type: { exists: true } } }; allRule.push(newItem); } }); } else { // 如果res为false,则遍历fields,提取所有字段 Object.entries(config.fields).forEach(([key, fieldValue]) => { // 根据值是否为空添加不同的type标记 const isValueEmpty = !fieldValue.value || fieldValue.value.trim() === ''; // 创建新对象并添加type标记 const newItem = { fieldKey: key, fieldValue: { ...fieldValue, type: { exists: isValueEmpty ? false : true } } }; allRule.push(newItem); }); } } // 处理"格式判断"类型的规则 if (rule.type === 'format') { // 使用类型断言获取config对象的具体结构 const config = rule.config as { res: boolean; field: Record; formatType?: string; parameters?: string; }; // 从config中获取field对象 // 注意:根据示例,format类型中是field而不是fields if (config.field) { // 获取field中唯一的键值对 const entries = Object.entries(config.field); if (entries.length > 0) { const [key, fieldValue] = entries[0]; // 创建新对象并添加type标记 const newItem = { fieldKey: key, fieldValue: { ...fieldValue, type: { format: config.res } // 标记为format类型,结果为config.res } }; allRule.push(newItem); } } } // 处理"逻辑判断"类型的规则 if (rule.type === 'logic') { // 使用类型断言获取config对象的具体结构 const config = rule.config as { logic: string; res: boolean; conditions: Array<{ field: Record; value: string; operator: string; res: boolean; }>; }; // 遍历conditions数组 if (config.conditions && Array.isArray(config.conditions)) { config.conditions.forEach(condition => { // 从condition中获取field对象 const entries = Object.entries(condition.field); if (entries.length > 0) { const [key, fieldValue] = entries[0]; // 创建新对象并添加type标记 const newItem = { fieldKey: key, fieldValue: { ...fieldValue, type: { logic: condition.res } } }; allRule.push(newItem); } }); } } // 处理"正则表达式"类型的规则 if (rule.type === 'regex') { // 使用类型断言获取config对象的具体结构 const config = rule.config as { res: boolean; field: Record; pattern?: string; matchType?: string; selectedFields?: string[]; }; if (config.field) { const entries = Object.entries(config.field); if (entries.length > 0) { const [key, fieldValue] = entries[0]; // 创建新对象并添加type标记 const newItem = { fieldKey: key, fieldValue: { ...fieldValue, type: { regex: config.res } } }; allRule.push(newItem); } } } } // console.log('allRule-------', allRule); // 对allRule进行去重和合并 const mergedRules: Array<{ fieldKey: string; fieldValue: { type: Record; }; }> = []; // 使用对象存储相同fieldKey的项,便于快速查找和合并 const fieldKeyMap: Record; }; }> = {}; // 第一步:按fieldKey分组并合并不同类型的规则结果 allRule.forEach(item => { const fieldKey = item.fieldKey; const fieldValue = item.fieldValue; const typeKey = Object.keys(fieldValue.type)[0]; // 获取类型名称(exists/logic/regex/format) const typeValue = fieldValue.type[typeKey]; // 获取类型值(true/false) // 提取页码和值 const page = fieldValue.page; const value = fieldValue.value; // 如果是第一次遇到这个fieldKey,创建新条目 if (!fieldKeyMap[fieldKey]) { // 创建新的结构 fieldKeyMap[fieldKey] = { fieldKey, fieldValue: { type: {} } }; } // 将类型信息添加到type对象中,允许一个字段有多种规则类型的结果 fieldKeyMap[fieldKey].fieldValue.type[typeKey] = { res: typeValue, page, value }; }); // 将合并后的对象转换为数组 for (const key in fieldKeyMap) { mergedRules.push(fieldKeyMap[key]); } // 获取第一个有效页码 if (reviewPoint.id && !effectivePages[reviewPoint.id]) { // 遍历合并后的规则数组,查找第一个有效页码 for (const rule of mergedRules) { // 遍历字段类型对象 const typeEntries = Object.entries(rule.fieldValue.type); // 遍历每种类型规则 for (const [, typeValue] of typeEntries) { // 检查是否有有效页码 if (typeValue.page && Number(typeValue.page) > 0) { // 找到有效页码,设置状态并跳出循环 setEffectivePages(prev => ({ ...prev, [reviewPoint.id || '']: Number(typeValue.page) })); // 使用break跳出当前循环 break; } } // 如果已经找到有效页码,跳出外层循环 if (reviewPoint.id && effectivePages[reviewPoint.id]) { break; } } } // 返回合并后的规则数组 return mergedRules; }; /** * 渲染评查点内容与建议 * @param reviewPoint 评查点 * @returns 评查点内容与建议组件 */ const renderReviewPointContent = (reviewPoint: ReviewPoint) => { const mergedRules = filterOtherRule(reviewPoint); // console.log('mergedRules1-------', mergedRules); // 根据result和status决定渲染哪种样式 if (reviewPoint.result === true) { // 已通过的评查点只显示基本信息和人工审核注释 return ( <> {checkContentPage(reviewPoint).pageIndex === 0 && (

该评查点无法找到索引内容,无法自动定位到对应页面。

)} {/* 评查点内容显示区域 */} {reviewPoint.content && Object.entries(reviewPoint.content).length > 0 && (
{/* 修改评查结果的结构之后,显示新的结构 */} {renderContent(reviewPoint, mergedRules)}
)} ); } return (
{/* 没有索引内容提示 */} {checkContentPage(reviewPoint).pageIndex === 0 && (

该评查点无法找到索引内容,无法自动定位到对应页面。

)} {/* 建议内容显示区域 */} {reviewPoint.suggestion && (

{reviewPoint.suggestion}

)} {/* 法律依据内容 */} {reviewPoint.legalBasis && (typeof reviewPoint.legalBasis === 'object') && ( (reviewPoint.legalBasis.name || reviewPoint.legalBasis.content || (reviewPoint.legalBasis.articles && Array.isArray(reviewPoint.legalBasis.articles) && reviewPoint.legalBasis.articles.length > 0)) && (
法律依据
{reviewPoint.legalBasis.name && (

{reviewPoint.legalBasis.name}

)} {reviewPoint.legalBasis.content && (

条款内容:{reviewPoint.legalBasis.content}

)} {reviewPoint.legalBasis.articles && Array.isArray(reviewPoint.legalBasis.articles) && reviewPoint.legalBasis.articles.length > 0 && (

相关条款:

    {reviewPoint.legalBasis.articles.map((item, index) => (
  • {typeof item === 'string' ? item : typeof item === 'object' && item !== null ? (item.name ? `${item.name}: ${item.content || ''}` : item.content || JSON.stringify(item)) : String(item)}
  • ))}
)}
) )} {reviewPoint.content !== null && Object.keys(reviewPoint.content).length > 0 && ( <> {/* 内容显示区域 */}
{/* 修改评查结果的结构之后,显示新的结构 */} {renderContent(reviewPoint, mergedRules)}
)}
); }; /** * 渲染无匹配结果提示 * 当过滤后没有评查点时显示 */ const renderEmptyState = () => { return (

没有找到匹配的评查点

请尝试不同的搜索词或清除筛选条件

{(searchText || statusFilter) && ( )}
); }; // 处理评查点点击事件 const handleReviewPointClick = (id: string) => { // 找到被点击的评查点 const reviewPoint = reviewPoints.find(result => result.id === id); // 如果评查点存在 if (reviewPoint) { // 如果effectivePages有值,使用它 if (reviewPoint.id && effectivePages[reviewPoint.id]) { // console.log('effectivePages', effectivePages[reviewPoint.id]); onReviewPointSelect(id, effectivePages[reviewPoint.id]); // return; } else { // 没有有效页码,只传递ID onReviewPointSelect(id); } } else { // 没有找到评查点,只传递ID onReviewPointSelect(id); } }; // 检查评查点的contentPage,如果contentPage内也没有page,则返回默认值 const checkContentPage = (reviewPoint: ReviewPoint): { pageIndex: number, key?: string, id: string } => { // 返回对象初始化 const result = { pageIndex: 0, id: reviewPoint.id }; // 如果contentPage不存在或是空对象,返回默认值 if (!reviewPoint.contentPage || Object.keys(reviewPoint.contentPage).length === 0) { return result; } // 遍历contentPage中的每个key for (const key of Object.keys(reviewPoint.contentPage)) { if (reviewPoint.contentPage[key] && parseInt(reviewPoint.contentPage[key] as string) > 0) { // 返回第一个找到的有效页码,以及对应的key return { pageIndex: parseInt(reviewPoint.contentPage[key] as string), key, id: reviewPoint.id }; } } // 如果遍历完所有key都没找到有效页码,返回默认值 return result; }; // 在ReviewPointsList组件内部 useEffect(() => { if (isOpinionListModalOpen && selectedReviewPoint?.documentId) { loadOpinionListData(1, opinionListPageSize); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpinionListModalOpen, selectedReviewPoint?.documentId]); // 组件主渲染函数 return ( <>
{/* 悬浮的意见数量显示 - 固定在左侧 */}
{/* 面板头部 */}
评查结果
{/* 评查统计 */} {renderStatistics()} {/* 搜索框 */} {renderSearchBar()} {/* 评查点列表 */}
{filteredReviewPoints.length > 0 ? ( filteredReviewPoints.map(reviewPoint => (
{ console.log('reviewPoint', reviewPoint); handleReviewPointClick(reviewPoint.id); }} onKeyDown={(e) => { if (e.key === 'Enter') { handleReviewPointClick(reviewPoint.id); } }} > {/* 评查点标题和状态 */} {/* 评查点名称 pointName*/}
{/*
*/}
{reviewPoint.pointName}
{/*
{reviewPoint.title}
//评查点分组显示
{reviewPoint.groupName}
*/} {/*
*/}
{/* 提出意见按钮 */}
{renderStatusBadge(reviewPoint.status, reviewPoint.result,reviewPoint.title)}
{/* 评查点内容和操作 */} {renderReviewPointContent(reviewPoint)}
)) ) : ( renderEmptyState() )}
{/* 提出意见模态框 */}
} >
{/* 审查点 */}
{/* 发现问题 */}
{/* 审查意见 */}