完善文档预览的效果修改

This commit is contained in:
2025-04-21 23:02:29 +08:00
parent 5c2c367856
commit cd2f060d87
15 changed files with 718 additions and 565 deletions
+12 -4
View File
@@ -1,4 +1,4 @@
import { useNavigate } from "@remix-run/react";
interface FileInfoProps {
fileInfo: {
fileName: string;
@@ -15,6 +15,7 @@ interface FileInfoProps {
}
export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
const navigate = useNavigate();
const handleDownloadFile = async () => {
try {
const urlBefore = 'http://172.18.0.100:9000/docauditai/';
@@ -79,20 +80,27 @@ export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
)}
</div>
<div className="flex space-x-3">
{/* 返回上一级 */}
<button
className="ant-btn ant-btn-default flex items-center"
onClick={() => navigate(-1)}
>
<i className="ri-arrow-left-line mr-1"></i>
</button>
<button
className="ant-btn ant-btn-default flex items-center"
onClick={handleDownloadFile}
>
<i className="ri-file-download-line mr-1"></i>
</button>
<button
{/* <button
className="ant-btn ant-btn-default flex items-center"
onClick={handleExportReport}
>
<i className="ri-file-copy-line mr-1"></i> 导出评查报告
</button>
</button> */}
<button
className="ant-btn ant-btn-primary flex items-center"
className={`ant-btn ant-btn-primary flex items-center ${fileInfo.auditStatus === 1 ? 'hidden' : ''}`}
onClick={onConfirmResults}
>
<i className="ri-check-double-line mr-1"></i>
+80 -10
View File
@@ -2,7 +2,7 @@
* 文件预览组件
* 显示文档内容和评查点高亮
*/
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, ChangeEvent } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
// 设置worker路径为public目录下的worker文件
@@ -11,6 +11,7 @@ pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
// 导入统一的ReviewPoint类型
import { type ReviewPoint } from './';
import { toastService } from '../ui/Toast';
/**
* 自定义样式
@@ -72,10 +73,11 @@ interface FilePreviewProps {
export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, targetPage }: FilePreviewProps) {
const [zoomLevel, setZoomLevel] = useState(100);
const [highlightsVisible, setHighlightsVisible] = useState(true);
// const [highlightsVisible, setHighlightsVisible] = useState(true);
const contentRef = useRef<HTMLDivElement>(null);
const [numPages, setNumPages] = useState<number | null>(null);
const [loadError, setLoadError] = useState<string | null>(null);
const [pageInputValue, setPageInputValue] = useState<string>('');
// 放大文档
const handleZoomIn = () => {
@@ -92,9 +94,9 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
};
// 切换高亮显示
const toggleHighlights = () => {
setHighlightsVisible(!highlightsVisible);
};
// const toggleHighlights = () => {
// setHighlightsVisible(!highlightsVisible);
// };
// 当选中的评查点变化时,滚动到对应位置
// useEffect(() => {
@@ -132,7 +134,7 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
const pageElement = document.getElementById(`page-${newTargetPage}`);
if (pageElement) {
console.log(`跳转到第${newTargetPage}页,对应评查点ID: ${activeReviewPointId}`);
console.log(`跳转到第${newTargetPage}页,对应评查点结果ID: ${activeReviewPointId}`);
pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
@@ -152,6 +154,40 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
}
};
// 处理页码输入变化
const handlePageInputChange = (e: ChangeEvent<HTMLInputElement>) => {
// 只允许输入数字
const value = e.target.value.replace(/\D/g, '');
setPageInputValue(value);
};
// 处理页码跳转
const handlePageJump = () => {
if (!pageInputValue || !numPages) return;
const targetPageNum = parseInt(pageInputValue, 10);
// 验证页码是否在有效范围内
if (targetPageNum > 0 && targetPageNum <= numPages) {
// 找到目标页面元素并滚动到该位置
const pageElement = document.getElementById(`page-${targetPageNum}`);
if (pageElement) {
pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
// 页码超出范围,显示错误信息或重置输入
toastService.warning(`请输入有效页码 (1-${numPages})`);
setPageInputValue('');
}
};
// 处理回车键跳转
const handlePageInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handlePageJump();
}
};
// PDF文档加载成功回调函数
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
@@ -166,6 +202,13 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
const additionalMargin = Math.max(0, (zoomFactor - 1) * 800); // 800是估计的页面高度
return baseMargin + additionalMargin;
};
// 滚动到顶部
const handleScrollToTop = () => {
if (contentRef.current) {
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
}
};
/**
* 渲染PDF文档的所有页面
@@ -224,7 +267,7 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
/>
{/* 渲染评查点高亮区域 */}
{highlightsVisible && pageReviewPoints.map(point => {
{/* {highlightsVisible && pageReviewPoints.map(point => {
// 判断当前评查点是否为激活状态(被选中)
const isActive = point.id === activeReviewPointId;
@@ -249,7 +292,7 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
}}
/>
);
})}
})} */}
</div>
</div>
);
@@ -288,7 +331,14 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
<i className="ri-file-text-line text-primary mr-2"></i>
<span className="font-medium text-primary"></span>
</div>
<div className="file-preview-actions">
<div className="file-preview-actions flex items-center">
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5 "
onClick={handleScrollToTop}
>
<i className="ri-arrow-up-double-line "></i>
<span className="ml-1"></span>
</button>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5"
onClick={handleZoomIn}
@@ -301,13 +351,33 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta
>
<i className="ri-zoom-out-line"></i>
</button>
{/* 页码跳转控件 */}
<div className="inline-flex items-center ml-2">
<input
type="text"
className="ant-input ant-input-sm py-0 px-1 text-xs h-5 leading-5 w-10 text-center
focus:outline-none focus:ring-1 focus:ring-green-900"
placeholder="页码"
value={pageInputValue}
onChange={handlePageInputChange}
onKeyDown={handlePageInputKeyDown}
/>
<button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5 ml-1"
onClick={handlePageJump}
disabled={!numPages}
>
<i className="ri-arrow-right-line"></i>
</button>
{numPages && <span className="ml-1 text-xs text-gray-500">/ {numPages}</span>}
</div>
{/* <button
className="ant-btn ant-btn-sm ant-btn-default py-0 px-1 text-xs h-5 leading-5"
onClick={toggleHighlights}
>
<i className="ri-mark-pen-line"></i> {highlightsVisible ? '隐藏问题' : '显示问题'}
</button> */}
<span className="ml-2 text-xs text-gray-500">{"比例:"+zoomLevel+"%"}</span>
<span className="ml-2 text-xs text-gray-500 inline-block">{"比例:"+zoomLevel+"%"}</span>
</div>
</div>
<div
+171 -273
View File
@@ -18,6 +18,7 @@
* - 操作按钮: 提供一键替换和人工审核功能
*/
import { useState, useEffect } from 'react';
// import { toastService } from '../ui/Toast';
/**
* 评查点类型定义
@@ -25,11 +26,15 @@ import { useState, useEffect } from 'react';
*/
export interface ReviewPoint {
id: string;
documentId?: string;
pointId?: string;
editAuditStatusId?: string | number;
editAuditStatus: number;
pointName: string;
title: string;
groupName: string;
status: string;
content: string | Record<string, string>;
content: Record<string, string>;
suggestion: string;
needsHumanReview?: boolean;
humanReviewNote?: string;
@@ -65,7 +70,7 @@ interface ReviewPointsListProps {
statistics: Statistics;
activeReviewPointId: string | null;
onReviewPointSelect: (id: string, page?: number) => void;
onStatusChange: (id: string, status: string, message: string) => void;
onStatusChange: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
}
export function ReviewPointsList({
@@ -79,7 +84,7 @@ export function ReviewPointsList({
const [editingReviewPoint, setEditingReviewPoint] = useState<string | null>(null); // 当前正在编辑的评查点ID
const [searchText, setSearchText] = useState(''); // 搜索文本
const [statusFilter, setStatusFilter] = useState<string | null>(null); // 状态过滤
// eslint-disable-next-line no-unused-vars
const [suggestionTexts, setSuggestionTexts] = useState<Record<string, string>>({}); // 存储每个评查点的建议文本
// 添加重新审核意见的状态/ 用户输入的修改内容 / 用户提前写好的修改内容
@@ -116,25 +121,34 @@ export function ReviewPointsList({
/**
* 处理评查点审核操作
* @param reviewPointResultId 评查点结果ID
* @param action 操作类型: 'approve' 通过 / 'reject' 不通过
* @param editAuditStatusId 审核状态记录ID
* @param action 操作类型: 'approve' 通过 / 'reject' 不通过 / 'review' 重新审核
* @param message 用户输入的审核内容
*/
const handleReviewAction = (reviewPointResultId: string, action: 'approve' | 'reject', message: string) => {
const handleReviewAction = (reviewPointResultId: string, editAuditStatusId: string | number | undefined, action: 'approve' | 'reject' | 'review', message: string) => {
// 更新评查点状态
onStatusChange(reviewPointResultId, action === 'approve' ? 'true' : 'false', message);
// console.log('handleReviewAction-------', reviewPointResultId, editAuditStatusId, action, message);
if (action === 'review') {
// 重新审核时,不更新结果状态,只更新审核意见和审核状态
// console.log('重新审核-------', reviewPointResultId, editAuditStatusId || '', 'review', message);
onStatusChange(reviewPointResultId, editAuditStatusId || '', 'review', message);
} else {
// 通过/不通过时,更新结果状态和审核意见
// console.log('通过/不通过-------', reviewPointResultId, editAuditStatusId || '', action === 'approve' ? 'true' : 'false', message);
onStatusChange(reviewPointResultId, editAuditStatusId || '', action === 'approve' ? 'true' : 'false', message);
}
// 将参数输出到控制台
console.log('评查点审核通过不通过操作', {
console.log('评查点审核操作', {
id: reviewPointResultId,
editAuditStatusId: editAuditStatusId,
action: action,
content: message,
status: action === 'approve' ? 'true' : 'false'
status: action === 'approve' ? 'true' : (action === 'reject' ? 'false' : 'review')
});
// 清除编辑状态
setEditingReviewPoint(null);
// alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointResultId},审核内容: ${message}`);
};
/**
@@ -147,7 +161,6 @@ export function ReviewPointsList({
point.pointName.toLowerCase().includes(searchText.toLowerCase()) ||
point.title.toLowerCase().includes(searchText.toLowerCase()) ||
point.groupName.toLowerCase().includes(searchText.toLowerCase()) ||
(typeof point.content === 'string' && point.content.toLowerCase().includes(searchText.toLowerCase())) ||
(typeof point.content === 'object' && point.content !== null &&
Object.values(point.content).some(value =>
typeof value === 'string' && value.toLowerCase().includes(searchText.toLowerCase())
@@ -291,7 +304,8 @@ export function ReviewPointsList({
<div className="relative">
<input
type="text"
className="w-full border border-gray-200 rounded-md pl-8 pr-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-primary"
className="w-full border border-gray-200 rounded-md pl-8 pr-2 py-1 text-xs
focus:outline-none focus:ring-1 focus:ring-green-800"
placeholder="搜索评查点..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
@@ -434,35 +448,24 @@ export function ReviewPointsList({
// 根据result和status决定渲染哪种样式
if (reviewPoint.result === true ){
// 已通过的评查点只显示基本信息和人工审核注释 delete
if (reviewPoint.needsHumanReview && reviewPoint.humanReviewNote) {
return (
<div className="mt-2">
<div className="p-2 bg-green-50 rounded border border-green-200 text-xs">
<p className="text-xs text-success"><i className="ri-check-line mr-1"></i></p>
{reviewPoint.suggestion && (
<div className="border-t border-green-200 mt-1 pt-1">
<p className="text-xs text-gray-600 select-text">{reviewPoint.suggestion}</p>
</div>
)}
</div>
</div>
);
}
// if (reviewPoint.needsHumanReview && reviewPoint.humanReviewNote) {
// return (
// <div className="mt-2">
// <div className="p-2 bg-green-50 rounded border border-green-200 text-xs">
// <p className="text-xs text-success"><i className="ri-check-line mr-1"></i>已处理</p>
// {reviewPoint.suggestion && (
// <div className="border-t border-green-200 mt-1 pt-1">
// <p className="text-xs text-gray-600 select-text">{reviewPoint.suggestion}</p>
// </div>
// )}
// </div>
// </div>
// );
// }
// 处理 result=true 且 postAction=manual 的情况
if (reviewPoint.postAction === 'manual') {
// 处理重新审核意见的提交
const handleReReview = (reviewPointId: string, status: string) => {
const note = manualReviewNotes[reviewPointId] || '';
if (!note.trim()) {
alert('请输入审核意见');
return;
}
// 在实际应用中,这里应该调用API提交审核意见
onStatusChange(reviewPointId, status, note);
alert(`提交重新审核意见: ${note}`);
// 可以添加提交成功后的状态更新等操作
};
const note = manualReviewNotes[reviewPoint.id] || '';
// 处理重新审核意见的输入
const handleNoteChange = (reviewPointId: string, text: string) => {
@@ -483,7 +486,6 @@ export function ReviewPointsList({
</div>
)}
{/* 额外的文本输入框区域 */}
<div className="mb-3">
<textarea
@@ -496,12 +498,29 @@ export function ReviewPointsList({
</div>
<div className="flex justify-end">
<button
className="bg-purple-600 hover:bg-purple-700 text-xs text-white py-1 px-2 rounded-md flex items-center justify-center"
onClick={() => handleReReview(reviewPoint.id, 'false')}
>
<i className="ri-refresh-line mr-1"></i>
</button>
{reviewPoint.editAuditStatus === 0 ? (
<div className="w-full flex justify-end gap-2">
<button
className="bg-[#1890ff] hover:bg-blue-600 text-xs text-white py-1 px-2 rounded-md flex items-center justify-center"
onClick={() => handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'approve', note)}
>
<i className="ri-check-line mr-1"></i>
</button>
<button
className="bg-[#f5222d] hover:bg-red-600 text-xs text-white py-1 px-2 rounded-md flex items-center justify-center"
onClick={() => handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'reject', note)}
>
<i className="ri-close-line mr-1"></i>
</button>
</div>
) : (
<button
className="bg-purple-600 hover:bg-purple-700 text-xs text-white py-1 px-2 rounded-md flex items-center justify-center"
onClick={() => handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'review', note)}
>
<i className="ri-refresh-line mr-1"></i>
</button>
)}
</div>
</div>
);
@@ -514,60 +533,51 @@ export function ReviewPointsList({
<p className="text-xs text-red-500 select-text text-left"></p>
)}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
{typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? (
// 当 content 是对象时的渲染方式
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1"
onClick={(e) => {
// 阻止事件冒泡,防止触发父元素的点击事件
e.stopPropagation();
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1"
onClick={(e) => {
// 阻止事件冒泡,防止触发父元素的点击事件
e.stopPropagation();
console.log(`通过:单独点击${key}----`,reviewPoint);
// 检查评查点是否有 contentPage 以及当前 key 对应的页码数组
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
// 获取当前 key 对应的第一个页码并跳转
console.log(`通过:单独点击${key}----页码---`,reviewPoint.contentPage[key][0]);
console.log(`非通过:单独点击${key}----`,reviewPoint);
// 检查评查点是否有 contentPage 以及当前 key 对应的页码数组
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
console.log(`通过:单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
// 获取当前 key 对应的第一个页码并跳转
console.log(`非通过:单独点击${key}----页码---`,reviewPoint.contentPage[key][0]);
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// 如果没有对应页码,弹出提示
// alert(`无法找到"${key}"对应的内容页面`);
console.log(`通过:单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// alert(`无法找到"${key}"对应的内容页面`);
}
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
>
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className={`text-xs ${value ? 'text-error' : 'text-warning'}`}>
{value ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
>
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className={`text-xs ${value ? 'text-error' : 'text-warning'}`}>
{value ? '' : '缺失'}
</span>
</div>
))}
</div>
) : (
// 当 content 是字符串时的渲染方式
<>
</>
)}
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
</div>
))}
</div>
</div>
</>
);
@@ -634,76 +644,58 @@ export function ReviewPointsList({
)}
{reviewPoint.content !== null && (
(typeof reviewPoint.content === 'string' && reviewPoint.content !== '') ||
(typeof reviewPoint.content === 'object' && Object.keys(reviewPoint.content).length > 0)
) && (
{reviewPoint.content !== null && Object.keys(reviewPoint.content).length > 0 && (
<>
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
{typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? (
// 当 content 是对象时的渲染方式
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1"
onClick={(e) => {
// 阻止事件冒泡,防止触发父元素的点击事件
e.stopPropagation();
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1"
onClick={(e) => {
// 阻止事件冒泡,防止触发父元素的点击事件
e.stopPropagation();
console.log(`非通过:单独点击${key}----`,reviewPoint);
// 检查评查点是否有 contentPage 以及当前 key 对应的页码数组
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
// 获取当前 key 对应的第一个页码并跳转
console.log(`非通过:单独点击${key}----页码---`,reviewPoint.contentPage[key][0]);
console.log(`非通过:单独点击${key}----`,reviewPoint);
// 检查评查点是否有 contentPage 以及当前 key 对应的页码数组
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// 如果没有对应页码,弹出提示
// alert(`无法找到"${key}"对应的内容页面`);
console.log(`非通过:单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
// 获取当前 key 对应的第一个页码并跳转
console.log(`非通过:单独点击${key}----页码---`,reviewPoint.contentPage[key][0]);
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// 如果没有对应页码,弹出提示
// alert(`无法找到"${key}"对应的内容页面`);
console.log(`非通过:单独点击${key}--------没有对应页码`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// alert(`无法找到"${key}"对应的内容页面`);
}
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
>
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{value ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
>
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{value ? '' : '缺失'}
</span>
</div>
))}
</div>
) : (
// 当 content 是字符串时的渲染方式
<>
{/* 为字符串内容也添加标题和状态 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs"></span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
</div>
<p className="text-xs text-left select-text">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
))}
</div>
</div>
</>
)}
@@ -740,13 +732,13 @@ export function ReviewPointsList({
)}
{/* 人工审核按钮 */}
{reviewPoint.postAction === 'manual' && (
{reviewPoint.editAuditStatus === 0 ? (
<div className="w-full flex justify-end gap-2">
<button
className="bg-[#1890ff] hover:bg-blue-600 text-white py-1 px-2 rounded-md text-sm"
onClick={() => {
const note = manualReviewNotes[reviewPoint.id] || '';
handleReviewAction(reviewPoint.id, 'approve', note);
handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'approve', note);
}}
>
<i className="ri-check-line mr-1"></i>
@@ -755,12 +747,24 @@ export function ReviewPointsList({
className="bg-[#f5222d] hover:bg-red-600 text-white py-1 px-2 rounded-md text-sm"
onClick={() => {
const note = manualReviewNotes[reviewPoint.id] || '';
handleReviewAction(reviewPoint.id, 'reject', note);
handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'reject', note);
}}
>
<i className="ri-close-line mr-1"></i>
</button>
</div>
) : (
<div className="w-full flex justify-end">
<button
className="bg-purple-600 hover:bg-purple-700 text-white py-1 px-2 rounded-md text-sm"
onClick={() => {
const note = manualReviewNotes[reviewPoint.id] || '';
handleReviewAction(reviewPoint.id, reviewPoint.editAuditStatusId, 'review', note);
}}
>
<i className="ri-refresh-line mr-1"></i>
</button>
</div>
)}
</div>
)}
@@ -768,120 +772,6 @@ export function ReviewPointsList({
);
}
// 处于编辑状态时显示编辑界面
// 根据result和status决定显示不同的标记
const isErrorStatus = reviewPoint.result === false && reviewPoint.status === 'error';
return (
<div className="mt-2">
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
{/* 隐藏顶部的"当前值"标题,在每个内容项中显示 */}
{typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? (
// 当 content 是对象时的渲染方式
<div>
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded p-1"
onClick={(e) => {
// 阻止事件冒泡,防止触发父元素的点击事件
e.stopPropagation();
console.log(`非通过:单独点击${key}----`,reviewPoint);
// 检查评查点是否有 contentPage 以及当前 key 对应的页码数组
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
// 获取当前 key 对应的第一个页码并跳转
console.log(`非通过:单独点击${key}----页码---`,reviewPoint.contentPage[key][0]);
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
// 如果没有对应页码,弹出提示
alert(`无法找到"${key}"对应的内容页面`);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (reviewPoint.contentPage && reviewPoint.contentPage[key] && reviewPoint.contentPage[key].length > 0) {
onReviewPointSelect(reviewPoint.id, reviewPoint.contentPage[key][0]);
} else {
alert(`无法找到"${key}"对应的内容页面`);
}
}
}}
role="button"
tabIndex={0}
aria-label={`查看${key}内容详情`}
>
{/* 使用flex布局使key和状态标签左右对齐 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs">{key}</span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{/* {isErrorStatus ? '不符合规范' : '需优化'} */}
{value ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
</div>
))}
</div>
) : (
// 当 content 是字符串时的渲染方式
<>
{/* 为字符串内容也添加标题和状态 */}
<div className="flex justify-between items-center mb-1">
<span className="text-xs"></span>
<span className={`text-xs ${isErrorStatus ? 'text-error' : 'text-warning'}`}>
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
</div>
<p className="text-xs select-text">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
</div>
{/* 建议修改区域 */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700 text-xs"></span>
{/* <span className="text-green-500 text-xs">符合规范</span> */}
</div>
<textarea
value={suggestionTexts[reviewPoint.id] || ''}
onChange={(e) => handleSuggestionChange(reviewPoint.id, e.target.value)}
className="w-full p-2 border rounded bg-gray-50 min-h-[100px] focus:outline-none focus:border-[#00684a] focus:shadow-[0_0_0_2px_rgba(0,104,74,0.2)]"
/>
</div>
{/* 审核意见区域 */}
<div className="bg-blue-50 rounded border border-blue-200 p-2">
<label htmlFor="reviewNote" className="block text-xs text-gray-700 mb-1"></label>
<textarea
id="reviewNote"
className="w-full border border-gray-200 rounded-md text-xs p-2 mb-2"
placeholder="请输入审核意见(可选)..."
rows={2}
value={manualReviewNotes[reviewPoint.id] || ''}
onChange={(e) => handleManualReviewNotesChange(reviewPoint.id, e.target.value)}
></textarea>
<div className="flex justify-end mt-2 space-x-2">
<button
className="replace-action"
onClick={() => handleReviewAction(reviewPoint.id, 'approve', manualReviewNotes[reviewPoint.id] || '')}
>
<i className="ri-check-line"></i>
</button>
<button
className="replace-action status-error"
onClick={() => handleReviewAction(reviewPoint.id, 'reject', manualReviewNotes[reviewPoint.id] || '')}
>
<i className="ri-close-line"></i>
</button>
</div>
</div>
</div>
);
};
/**
@@ -912,7 +802,7 @@ export function ReviewPointsList({
// 处理评查点点击事件
const handleReviewPointClick = (id: string) => {
// 找到被点击的评查点
const reviewPoint = reviewPoints.find(point => point.id === id);
const reviewPoint = reviewPoints.find(result => result.id === id);
// 如果评查点存在
if (reviewPoint) {
@@ -928,9 +818,11 @@ export function ReviewPointsList({
// 没有有效页码,只传递ID
onReviewPointSelect(id);
console.log(`没有有效页码---评查点ID${reviewPoint.pointId},评查点结果ID${id}`);
} else {
// 没有找到评查点,只传递ID
onReviewPointSelect(id);
console.log(`没有找到评查点---评查点结果ID${id}`);
}
};
@@ -981,11 +873,17 @@ export function ReviewPointsList({
<div className="review-points-list">
{filteredReviewPoints.length > 0 ? (
filteredReviewPoints.map(reviewPoint => (
<button
<div
key={reviewPoint.id}
className={`review-point-item ${activeReviewPointId === reviewPoint.id ? 'active' : ''}`}
onClick={() => handleReviewPointClick(reviewPoint.id)}
type="button"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleReviewPointClick(reviewPoint.id);
}
}}
role="button"
tabIndex={0}
style={{ userSelect: 'text' }}
>
{/* 评查点标题和状态 */}
@@ -1009,7 +907,7 @@ export function ReviewPointsList({
{/* 评查点内容和操作 */}
{renderReviewPointContent(reviewPoint)}
</button>
</div>
))
) : (
renderEmptyState()
+1 -1
View File
@@ -18,7 +18,7 @@ export function ActionButtons({ onSave, onSaveDraft, isEditMode }: ActionButtons
</button>
<button
type="button"
className="ant-btn ant-btn-default min-w-[120px]"
className="ant-btn ant-btn-default min-w-[120px] !hidden"
onClick={onSaveDraft}
>
<i className="ri-draft-line mr-1"></i> {isEditMode ? '另存为草稿' : '保存草稿'}