完善评查详情

This commit is contained in:
2025-04-18 15:41:43 +08:00
parent 119f9197b2
commit 01d93522b8
25 changed files with 1731 additions and 511 deletions
+206 -92
View File
@@ -34,6 +34,7 @@ export interface ReviewPoint {
humanReviewNote?: string;
humanReviewBy?: string;
humanReviewTime?: string;
contentPage?: number[];
position?: {
section: string;
index: number;
@@ -45,6 +46,8 @@ export interface ReviewPoint {
articles?: Array<string | { name?: string; content?: string; [key: string]: unknown }>;
[key: string]: unknown;
};
postAction?: string;
actionContent?: string;
}
// 统计数据类型
@@ -60,8 +63,8 @@ interface ReviewPointsListProps {
reviewPoints: ReviewPoint[];
statistics: Statistics;
activeReviewPointId: string | null;
onReviewPointSelect: (id: string) => void;
onStatusChange: (id: string, status: string) => void;
onReviewPointSelect: (id: string, page?: number) => void;
onStatusChange: (id: string, status: string, message: string) => void;
}
export function ReviewPointsList({
@@ -73,24 +76,35 @@ export function ReviewPointsList({
}: ReviewPointsListProps) {
// 状态管理
const [editingReviewPoint, setEditingReviewPoint] = useState<string | null>(null); // 当前正在编辑的评查点ID
const [userInputText, setUserInputText] = useState(''); // 用户输入的审核意见文本
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>>({}); // 存储每个评查点的建议文本
// 添加重新审核意见的状态/ 用户输入的修改内容 / 用户提前写好的修改内容
const [manualReviewNotes, setManualReviewNotes] = useState<Record<string, string>>({});
// 初始化建议文本
useEffect(() => {
// 将所有评查点的建议文本存储到状态中
const suggestions: Record<string, string> = {};
reviewPoints.forEach(point => {
suggestions[point.id] = point.suggestion || '';
});
setSuggestionTexts(suggestions);
// 使用函数式更新,不再需要外部 manualReviewNotes 变量
setManualReviewNotes(prev => {
const notes = {...prev};
reviewPoints.forEach(point => {
notes[point.id] = point.actionContent || '';
});
return notes;
});
}, [reviewPoints]);
// 处理建议文本变更
// eslint-disable-next-line no-unused-vars
const handleSuggestionChange = (reviewPointId: string, text: string) => {
setSuggestionTexts(prev => ({
...prev,
@@ -98,11 +112,35 @@ export function ReviewPointsList({
}));
};
/**
* 处理评查点审核操作
* @param reviewPointResultId 评查点结果ID
* @param action 操作类型: 'approve' 通过 / 'reject' 不通过
* @param message 用户输入的审核内容
*/
const handleReviewAction = (reviewPointResultId: string, action: 'approve' | 'reject', message: string) => {
// 更新评查点状态
onStatusChange(reviewPointResultId, action === 'approve' ? 'true' : 'false', message);
// 将参数输出到控制台
console.log('评查点审核通过不通过操作', {
id: reviewPointResultId,
action: action,
content: message,
status: action === 'approve' ? 'true' : 'false'
});
// 清除编辑状态
setEditingReviewPoint(null);
alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointResultId},审核内容: ${message}`);
};
/**
* 过滤评查点
* 根据搜索文本和状态过滤条件筛选评查点
*/
const filteredReviewPoints = reviewPoints.filter(point => {
const filteredReviewPoints = reviewPoints.filter(point => {
// 匹配搜索文本
const matchesSearch = searchText === '' ||
point.title.toLowerCase().includes(searchText.toLowerCase()) ||
@@ -146,36 +184,6 @@ export function ReviewPointsList({
// onStatusChange(reviewPointId, 'success');
};
/**
* 处理评查点审核操作
* @param reviewPointId 评查点ID
* @param action 操作类型: 'approve' 通过 / 'reject' 不通过
*/
const handleReviewAction = (reviewPointId: string, action: 'approve' | 'reject') => {
// 更新评查点状态
onStatusChange(reviewPointId, action === 'approve' ? 'success' : 'error');
// 清除编辑状态
setEditingReviewPoint(null);
setUserInputText('');
alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointId}`);
};
/**
* 显示评查点详情编辑界面
* @param reviewPointId 评查点ID
*/
const handleEditReviewPoint = (reviewPointId: string) => {
setEditingReviewPoint(reviewPointId);
// 获取评查点的建议内容作为初始值
const reviewPoint = reviewPoints.find(point => point.id === reviewPointId);
if (reviewPoint) {
setUserInputText(reviewPoint.suggestion || '');
}
};
/**
* 渲染评查统计信息
* 显示总计、通过、警告、错误数量
@@ -215,7 +223,7 @@ export function ReviewPointsList({
{/* 总计数量 */}
<div className="flex items-center">
<button
className={`w-7 h-7 bg-gray-100 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === null && searchText === '' ? 'ring-2 ring-gray-400' : ''}`}
className={`px-3 h-7 bg-gray-100 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === null && searchText === '' ? 'ring-2 ring-gray-400' : ''}`}
onClick={() => {
setStatusFilter(null);
setSearchText('');
@@ -224,47 +232,47 @@ export function ReviewPointsList({
type="button"
>
<span className="text-sm font-semibold text-gray-600">{totalToShow}</span>
<span className="text-xs text-gray-500 ml-1"></span>
</button>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
{/* 通过数量 */}
<div className="flex items-center">
<button
className={`w-7 h-7 bg-green-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'success' ? 'ring-2 ring-success' : ''}`}
className={`px-3 h-7 bg-green-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'success' ? 'ring-2 ring-green-500' : ''}`}
onClick={() => setStatusFilter(statusFilter === 'success' ? null : 'success')}
aria-label={`过滤通过项 ${statusFilter === 'success' ? '(已选中)' : ''}`}
type="button"
>
<span className="text-sm font-semibold text-success">{successToShow}</span>
<span className="text-xs text-gray-500 ml-2"></span>
</button>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
{/* 警告数量 */}
<div className="flex items-center">
<button
className={`w-7 h-7 bg-yellow-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'warning' ? 'ring-2 ring-warning' : ''}`}
className={`px-3 h-7 bg-yellow-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'warning' ? 'ring-2 ring-yellow-500' : ''}`}
onClick={() => setStatusFilter(statusFilter === 'warning' ? null : 'warning')}
aria-label={`过滤警告项 ${statusFilter === 'warning' ? '(已选中)' : ''}`}
type="button"
>
<span className="text-sm font-semibold text-warning">{warningToShow}</span>
<span className="text-xs text-gray-500 ml-2"></span>
</button>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
<div className="h-8 border-r border-gray-200"></div>
{/* 错误数量 */}
<div className="flex items-center">
<button
className={`w-7 h-7 bg-red-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'error' ? 'ring-2 ring-error' : ''}`}
className={`px-3 h-7 bg-red-50 rounded-md flex items-center justify-center cursor-pointer ${statusFilter === 'error' ? 'ring-2 ring-red-500' : ''}`}
onClick={() => setStatusFilter(statusFilter === 'error' ? null : 'error')}
aria-label={`过滤错误项 ${statusFilter === 'error' ? '(已选中)' : ''}`}
type="button"
>
<span className="text-sm font-semibold text-error">{errorToShow}</span>
<span className="text-xs text-gray-500 ml-2"></span>
</button>
<span className="text-xs text-gray-500 ml-1"></span>
</div>
</div>
</div>
@@ -374,9 +382,9 @@ export function ReviewPointsList({
* @returns 人工审核标记组件
*/
const renderHumanReviewBadge = (reviewPoint: ReviewPoint) => {
if (reviewPoint.needsHumanReview) {
if (reviewPoint.postAction === 'manual') {
return (
<span className="status-badge status-waiting ml-2">
<span className="status-badge status-waiting ml-2 mt-1 text-xs">
<i className="ri-user-line mr-1"></i>
</span>
);
@@ -411,6 +419,14 @@ export function ReviewPointsList({
* @returns 评查点内容组件
*/
const renderReviewPointContent = (reviewPoint: ReviewPoint) => {
const handleManualReviewNotesChange = (reviewPointId: string, text: string) => {
setManualReviewNotes(prev => ({
...prev,
[reviewPointId]: text
}));
};
// 如果当前评查点不处于编辑状态,只显示简单信息
if (editingReviewPoint !== reviewPoint.id) {
// 根据result和status决定渲染哪种样式
@@ -423,19 +439,77 @@ export function ReviewPointsList({
<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">{reviewPoint.suggestion}</p>
<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 handleNoteChange = (reviewPointId: string, text: string) => {
setManualReviewNotes(prev => ({
...prev,
[reviewPointId]: text
}));
};
return (
<div className="mt-2">
{reviewPoint.suggestion && (
<div className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
<div className="flex items-start">
<i className="ri-information-line text-blue-500 mr-2 mt-0.5"></i>
<p className="text-xs text-gray-600 select-text">{reviewPoint.suggestion}</p>
</div>
</div>
)}
{/* 额外的文本输入框区域 */}
<div className="mb-3">
<textarea
id={`manual-review-${reviewPoint.id}`}
className="w-full p-2 border rounded bg-white text-xs min-h-[80px] focus:outline-none focus:border-primary"
placeholder="请输入重新审核意见..."
value={manualReviewNotes[reviewPoint.id] || ''}
onChange={(e) => handleNoteChange(reviewPoint.id, e.target.value)}
></textarea>
</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>
</div>
</div>
);
}
return null;
}
// 非通过状态,显示内容和修改建议
const isErrorStatus = reviewPoint.result === false && reviewPoint.status === 'error';
return (
<div className="mt-2">
{reviewPoint.content !== null && (
@@ -443,8 +517,18 @@ export function ReviewPointsList({
(typeof reviewPoint.content === 'object' && Object.keys(reviewPoint.content).length > 0)
) && (
<>
{/* 建议内容显示区域 */}
{reviewPoint.suggestion && (
<div className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
<div className="flex items-start">
<i className="ri-information-line text-blue-500 mr-2 mt-0.5"></i>
<p className="text-xs text-gray-600 select-text">{reviewPoint.suggestion}</p>
</div>
</div>
)}
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
<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 是对象时的渲染方式
@@ -461,7 +545,7 @@ export function ReviewPointsList({
{value ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left">{value || ' '}</p>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
</div>
))}
</div>
@@ -475,7 +559,7 @@ export function ReviewPointsList({
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
</div>
<p className="text-xs text-left">{reviewPoint.content || '(内容为空)'}</p>
<p className="text-xs text-left select-text">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
</div>
@@ -484,22 +568,22 @@ export function ReviewPointsList({
{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)) && (
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3 select-text">
<div className="flex justify-between items-center mb-1">
<span className="text-xs font-medium"></span>
</div>
{reviewPoint.legalBasis.name && (
<p className="text-xs text-left mb-1">{reviewPoint.legalBasis.name}</p>
<p className="text-xs text-left mb-1 select-text">{reviewPoint.legalBasis.name}</p>
)}
{reviewPoint.legalBasis.content && (
<p className="text-xs text-left mb-1"><span className="font-medium"></span>{reviewPoint.legalBasis.content}</p>
<p className="text-xs text-left mb-1 select-text"><span className="font-medium"></span>{reviewPoint.legalBasis.content}</p>
)}
{reviewPoint.legalBasis.articles && Array.isArray(reviewPoint.legalBasis.articles) && reviewPoint.legalBasis.articles.length > 0 && (
<div>
<p className="text-xs text-left font-medium mb-1"></p>
<ul className="list-disc pl-4">
<ul className="list-disc pl-4 select-text">
{reviewPoint.legalBasis.articles.map((item, index) => (
<li key={index} className="text-xs text-left">
<li key={index} className="text-xs text-left select-text">
{typeof item === 'string' ? item :
typeof item === 'object' && item !== null ?
(item.name ? `${item.name}: ${item.content || ''}` :
@@ -515,22 +599,25 @@ export function ReviewPointsList({
)}
{/* 建议修改区域 */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700"></span>
{/* <span className="text-green-500">符合规范</span> */}
{/* {(reviewPoint.postAction !== 'none') && ( */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700 text-[0.8rem]">{reviewPoint.postAction === 'manual' ? "审核意见:" : "建议修改为:"}</span>
{/* <span className="text-green-500">符合规范</span> */}
</div>
<textarea
value={manualReviewNotes[reviewPoint.id] || ''}
placeholder={reviewPoint.postAction === 'manual' ? "请输入审核意见(可选)..." : "请输入建议修改内容..."}
onChange={(e) => handleManualReviewNotesChange(reviewPoint.id, e.target.value)}
className="text-xs w-full p-2 border rounded bg-white min-h-[100px] focus:outline-none focus:border-[#00684a] focus:shadow-[0_0_0_2px_rgba(0,104,74,0.2)]"
/>
</div>
<textarea
value={suggestionTexts[reviewPoint.id] || ''}
onChange={(e) => handleSuggestionChange(reviewPoint.id, e.target.value)}
className="w-full p-2 border rounded bg-white min-h-[100px] focus:outline-none focus:border-[#00684a] focus:shadow-[0_0_0_2px_rgba(0,104,74,0.2)]"
/>
</div>
{/* )} */}
{/* 操作按钮区域 */}
<div className="flex space-x-2 mt-2">
{/* 一键替换按钮 - 只有非人工审核的点或未通过的人工审核点才显示 */}
{(!reviewPoint.needsHumanReview || (!reviewPoint.result && reviewPoint.status !== 'success')) && (
{(reviewPoint.postAction !== 'manual') && (
<button
className="replace-action flex-1 justify-center"
onClick={() => handleReplace(reviewPoint.id)}
@@ -540,13 +627,27 @@ export function ReviewPointsList({
)}
{/* 人工审核按钮 */}
{reviewPoint.needsHumanReview && !reviewPoint.result && reviewPoint.status !== 'success' && (
<button
className="replace-action flex-1 justify-center human-review-request"
onClick={() => handleEditReviewPoint(reviewPoint.id)}
>
<i className="ri-edit-line"></i>
</button>
{reviewPoint.postAction === 'manual' && (
<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);
}}
>
<i className="ri-check-line mr-1"></i>
</button>
<button
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);
}}
>
<i className="ri-close-line mr-1"></i>
</button>
</div>
)}
</div>
</>
@@ -563,7 +664,7 @@ export function ReviewPointsList({
return (
<div className="mt-2">
{/* 内容显示区域 */}
<div className="p-2 bg-white rounded border border-gray-200 text-xs mb-3">
<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 是对象时的渲染方式
@@ -577,7 +678,7 @@ export function ReviewPointsList({
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
</div>
<p className="text-xs text-left">{value || '(内容为空)'}</p>
<p className="text-xs text-left select-text">{value || (value === '' ? <span className="invisible"></span> : '')}</p>
</div>
))}
</div>
@@ -591,7 +692,7 @@ export function ReviewPointsList({
{isErrorStatus ? '不符合规范' : '需优化'}
</span>
</div>
<p className="text-xs">{reviewPoint.content || '(内容为空)'}</p>
<p className="text-xs select-text">{reviewPoint.content || '(内容为空)'}</p>
</>
)}
</div>
@@ -599,8 +700,8 @@ export function ReviewPointsList({
{/* 建议修改区域 */}
<div className="mb-2">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-700"></span>
<span className="text-green-500"></span>
<span className="text-gray-700 text-xs"></span>
{/* <span className="text-green-500 text-xs">符合规范</span> */}
</div>
<textarea
value={suggestionTexts[reviewPoint.id] || ''}
@@ -610,26 +711,26 @@ export function ReviewPointsList({
</div>
{/* 审核意见区域 */}
<div className="bg-gray-50 rounded border border-gray-200 p-2">
<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={userInputText}
onChange={(e) => setUserInputText(e.target.value)}
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')}
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')}
onClick={() => handleReviewAction(reviewPoint.id, 'reject', manualReviewNotes[reviewPoint.id] || '')}
>
<i className="ri-close-line"></i>
</button>
@@ -664,9 +765,22 @@ export function ReviewPointsList({
);
};
// 处理评查点点击事件
const handleReviewPointClick = (id: string) => {
// 找到被点击的评查点
const reviewPoint = reviewPoints.find(point => point.id === id);
// 如果评查点存在并且有contentPage数组,传递第一个页码
if (reviewPoint && reviewPoint.contentPage && reviewPoint.contentPage.length > 0) {
onReviewPointSelect(id, reviewPoint.contentPage[0]);
} else {
onReviewPointSelect(id);
}
};
// 组件主渲染函数
return (
<div className="review-points-panel">
<div className="review-points-panel select-text">
{/* 面板头部 */}
<div className="review-panel-header py-2 px-4 flex items-center">
<i className="ri-file-list-check-line text-primary mr-2"></i>
@@ -686,24 +800,24 @@ export function ReviewPointsList({
<button
key={reviewPoint.id}
className={`review-point-item ${activeReviewPointId === reviewPoint.id ? 'active' : ''}`}
onClick={() => onReviewPointSelect(reviewPoint.id)}
onClick={() => handleReviewPointClick(reviewPoint.id)}
type="button"
style={{ userSelect: 'text' }}
>
{/* 评查点标题和状态 */}
<div className="review-point-header flex justify-between items-start">
<div className="review-point-title flex-1 text-left">{reviewPoint.title}</div>
<div className="flex items-center ml-2 flex-shrink-0">
{/* 评查点所属分组 */}
<div className="review-point-location">
<i className="ri-file-list-line mr-1"></i>
<span>{reviewPoint.groupName}</span>
</div>
<div className="flex flex-col items-center ml-2 flex-shrink-0">
{renderStatusBadge(reviewPoint.status, reviewPoint.result)}
{renderHumanReviewBadge(reviewPoint)}
</div>
</div>
{/* 评查点所属分组 */}
<div className="review-point-location">
<i className="ri-file-list-line mr-1"></i>
<span>{reviewPoint.groupName}</span>
</div>
{/* 人工审核注释 */}
{renderHumanReviewNote(reviewPoint)}