添加评查意见的悬浮按钮

This commit is contained in:
2025-07-17 01:16:43 +08:00
parent 290936a339
commit 348128bbe0
4 changed files with 161 additions and 109 deletions
+11 -5
View File
@@ -32,6 +32,7 @@ export interface CrossCheckingTask {
status: CrossCheckingTaskStatus;
score: number;
operation: string;
documentIds: number[];
}
// API响应格式
@@ -79,7 +80,8 @@ const mockTasks: CrossCheckingTask[] = [
progress: 0,
status: CrossCheckingTaskStatus.PENDING,
score: 0,
operation: '去评查'
operation: '去评查',
documentIds: [1, 2, 3, 4, 5]
},
{
id: 2,
@@ -92,7 +94,8 @@ const mockTasks: CrossCheckingTask[] = [
progress: 72,
status: CrossCheckingTaskStatus.IN_PROGRESS,
score: 0,
operation: '进行中'
operation: '进行中',
documentIds: [1, 2, 3, 4, 5]
},
{
id: 3,
@@ -105,7 +108,8 @@ const mockTasks: CrossCheckingTask[] = [
progress: 100,
status: CrossCheckingTaskStatus.COMPLETED,
score: 95,
operation: '查看结果'
operation: '查看结果',
documentIds: [1, 2, 3, 4, 5]
},
{
id: 4,
@@ -118,7 +122,8 @@ const mockTasks: CrossCheckingTask[] = [
progress: 100,
status: CrossCheckingTaskStatus.COMPLETED,
score: 85,
operation: '查看结果'
operation: '查看结果',
documentIds: [1, 2, 3, 4, 5]
},
{
id: 5,
@@ -131,7 +136,8 @@ const mockTasks: CrossCheckingTask[] = [
progress: 100,
status: CrossCheckingTaskStatus.COMPLETED,
score: 92,
operation: '查看结果'
operation: '查看结果',
documentIds: [1, 2, 3, 4, 5]
}
];
+5 -1
View File
@@ -315,7 +315,7 @@ export async function getReviewPoints(fileId: string) {
'document_id': `eq.${fileId}`
}
};
const scoringProposalsResponse = await postgrestGet('scoring_proposals', scoringProposalsParams);
const scoringProposalsResponse = await postgrestGet('cross_scoring_proposals', scoringProposalsParams);
if (scoringProposalsResponse.error) {
return { error: scoringProposalsResponse.error, status: scoringProposalsResponse.status };
@@ -404,8 +404,12 @@ export async function getReviewPoints(fileId: string) {
// suggestion: '只是给建议的修改内容',
result: result.evaluated_results?.result, // 记录评查结果,用于统计
// score是评查点设置的满分的分数
score: point.score || 0,
// finalScore是评查点对应评查结果最终所得的分数 用户交叉评查时使用
finalScore: result.final_score || 0,
postAction: point.post_action || '',
// postAction: 'manual',
+137 -100
View File
@@ -416,12 +416,17 @@ export function ReviewPointsList({
// 状态管理
const [searchText, setSearchText] = useState(''); // 搜索文本
const [statusFilter, setStatusFilter] = useState<string | null>(null); // 状态过滤
const [evaluationResultIds, setEvaluationResultIds] = useState<number[]>([]); // 评分提案的evaluation_result_id
// 在组件中使用scoringProposals(这里只是简单使用以避免linter警告)
// 将来可以用于显示相关的评分提案信息
useEffect(() => {
if (scoringProposals && scoringProposals.length > 0) {
console.log('收到评分提案数据:', scoringProposals.length, '个提案');
// 获取提案的evaluation_result_id
const evaluationResultIds = scoringProposals.map(proposal => Number(proposal.evaluation_result_id));
setEvaluationResultIds(evaluationResultIds);
// console.log('提案的evaluation_result_id:', evaluationResultIds);
}
}, [scoringProposals]);
@@ -447,6 +452,11 @@ export function ReviewPointsList({
* 打开提出意见模态框
*/
const handleOpenOpinionModal = (reviewPoint: ReviewPoint) => {
// 如果评分提案的evaluation_result_id包含当前评查点的id,则不打开模态框
if (evaluationResultIds.includes(Number(reviewPoint.id))) {
toastService.error('当前评查点已有意见提出项,可前往意见列表查看');
return;
}
setSelectedReviewPoint(reviewPoint);
setOpinionForm({
auditPoint: reviewPoint.pointName,
@@ -2098,8 +2108,34 @@ export function ReviewPointsList({
// 组件主渲染函数
return (
<>
<div className="review-points-panel select-text">
<TooltipPortal />
<div className="relative">
{/* 悬浮的意见数量显示 - 固定在左侧 */}
<div className="absolute left-[-35px] top-16 z-10 group cursor-pointer">
{/* 默认状态:竖向排列,窄宽度 */}
<div className="flex flex-col items-center bg-blue-50 px-2 py-2 rounded-lg border border-blue-200 shadow-md transition-all duration-300 group-hover:scale-0 group-hover:opacity-0 origin-top-right">
<i className="ri-chat-1-line text-blue-600 text-base"></i>
<span className="text-base text-blue-600 font-bold leading-tight whitespace-nowrap">{scoringProposals.length}</span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
</div>
{/* 悬浮状态:横向排列,显示图标,数字放大 */}
<div className="absolute top-0 right-0 opacity-0 scale-0 group-hover:opacity-100 group-hover:scale-100 flex items-center bg-blue-50 px-3 py-2 rounded-lg border border-blue-200 shadow-lg transition-all duration-300 origin-top-right">
<button className="flex items-center" aria-label="点击查看详情">
<div className="flex flex-col">
<i className="ri-chat-1-line text-blue-600 text-base"></i>
<span className="text-xl text-blue-600 font-bold">{scoringProposals.length}</span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
<span className="text-xs text-blue-500 leading-tight whitespace-wrap"></span>
</div>
</button>
</div>
</div>
<div className="review-points-panel select-text">
<TooltipPortal />
{/* 面板头部 */}
<div className="review-panel-header py-2 px-4 flex items-center">
<i className="ri-file-list-check-line text-primary mr-2"></i>
@@ -2126,7 +2162,7 @@ export function ReviewPointsList({
tabIndex={0}
style={{ userSelect: 'text' }}
onClick={() => {
// console.log('reviewPoint', reviewPoint);
console.log('reviewPoint', reviewPoint);
handleReviewPointClick(reviewPoint.id);
}}
onKeyDown={(e) => {
@@ -2182,104 +2218,105 @@ export function ReviewPointsList({
renderEmptyState()
)}
</div>
</div>
{/* 提出意见模态框 */}
<Modal
isOpen={isOpinionModalOpen}
onClose={handleCloseOpinionModal}
title="提出意见"
size="medium"
footer={
<div className="flex justify-end space-x-3">
<button
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={handleCloseOpinionModal}
disabled={isSubmittingOpinion}
>
</button>
<button
className="px-4 py-2 bg-green-700 border border-transparent rounded-md text-sm font-medium text-white hover:bg-green-600 disabled:opacity-50"
onClick={handleSubmitOpinion}
disabled={isSubmittingOpinion}
>
{isSubmittingOpinion ? '提交中...' : '发起投票'}
</button>
</div>
}
>
<div className="space-y-4">
{/* 审查点 */}
<div>
<label htmlFor="audit-point" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="audit-point"
type="text"
value={opinionForm.auditPoint}
readOnly
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm focus:outline-none"
/>
</div>
{/* 发现问题 */}
<div>
<label htmlFor="found-issue" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="found-issue"
type="text"
value={opinionForm.foundIssue}
readOnly
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm focus:outline-none"
/>
</div>
{/* 审查意见 */}
<div>
<label htmlFor="audit-opinion" className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<textarea
id="audit-opinion"
value={opinionForm.auditOpinion}
onChange={(e) => handleOpinionFormChange('auditOpinion', e.target.value)}
placeholder="请输入审查意见..."
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-green-700 focus:border-green-700 focus:outline-none"
/>
</div>
{/* 扣分 */}
<div>
<label htmlFor="deduction-score" className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<input
id="deduction-score"
type="number"
value={opinionForm.deductionScore}
onChange={(e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value >= 0) {
// 限制到1位小数
const roundedValue = Math.round(value * 10) / 10;
handleOpinionFormChange('deductionScore', roundedValue);
}
}}
step="0.1"
min="0"
max="100"
placeholder="0.0"
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-green-700 focus:border-green-700 focus:outline-none"
/>
<p className="mt-1 text-xs text-gray-500">1</p>
</div>
</div>
</Modal>
{/* 提出意见模态框 */}
<Modal
isOpen={isOpinionModalOpen}
onClose={handleCloseOpinionModal}
title="提出意见"
size="medium"
footer={
<div className="flex justify-end space-x-3">
<button
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={handleCloseOpinionModal}
disabled={isSubmittingOpinion}
>
</button>
<button
className="px-4 py-2 bg-green-700 border border-transparent rounded-md text-sm font-medium text-white hover:bg-green-600 disabled:opacity-50"
onClick={handleSubmitOpinion}
disabled={isSubmittingOpinion}
>
{isSubmittingOpinion ? '提交中...' : '发起投票'}
</button>
</div>
}
>
<div className="space-y-4">
{/* 审查点 */}
<div>
<label htmlFor="audit-point" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="audit-point"
type="text"
value={opinionForm.auditPoint}
readOnly
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm focus:outline-none"
/>
</div>
{/* 发现问题 */}
<div>
<label htmlFor="found-issue" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="found-issue"
type="text"
value={opinionForm.foundIssue}
readOnly
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-sm focus:outline-none"
/>
</div>
{/* 审查意见 */}
<div>
<label htmlFor="audit-opinion" className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<textarea
id="audit-opinion"
value={opinionForm.auditOpinion}
onChange={(e) => handleOpinionFormChange('auditOpinion', e.target.value)}
placeholder="请输入审查意见..."
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-green-700 focus:border-green-700 focus:outline-none"
/>
</div>
{/* 扣分 */}
<div>
<label htmlFor="deduction-score" className="block text-sm font-medium text-gray-700 mb-2">
<span className="text-red-500">*</span>
</label>
<input
id="deduction-score"
type="number"
value={opinionForm.deductionScore}
onChange={(e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value >= 0) {
// 限制到1位小数
const roundedValue = Math.round(value * 10) / 10;
handleOpinionFormChange('deductionScore', roundedValue);
}
}}
step="0.1"
min="0"
max="100"
placeholder="0.0"
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-green-700 focus:border-green-700 focus:outline-none"
/>
<p className="mt-1 text-xs text-gray-500">1</p>
</div>
</div>
</Modal>
</div>
</>
);
}
+8 -3
View File
@@ -176,6 +176,11 @@ export default function CrossCheckingIndex() {
return 'high';
};
// 处理查看结果
// const handleViewResult = (taskId: number, docIds: number[]) => {
// // 根据taskId获取关联的
// };
// 渲染进度条
const renderProgress = (progress: number) => (
<div className="flex items-center space-x-2">
@@ -198,7 +203,7 @@ export default function CrossCheckingIndex() {
type="primary"
size="small"
className="operation-btn primary"
onClick={() => navigate(`/cross-checking/${task.id}`)}
onClick={() => handleViewResult(task.id, task.documentIds)}
>
<i className="ri-play-line"></i>
@@ -210,7 +215,7 @@ export default function CrossCheckingIndex() {
type="default"
size="small"
className="operation-btn secondary"
onClick={() => navigate(`/cross-checking/${task.id}`)}
onClick={() => handleViewResult(task.id, task.documentIds)}
>
<i className="ri-eye-line"></i>
@@ -222,7 +227,7 @@ export default function CrossCheckingIndex() {
type="default"
size="small"
className="operation-btn secondary"
onClick={() => navigate(`/cross-checking/result?id=1355&previousRoute=cross-checking`)}
onClick={() => handleViewResult(task.id, task.documentIds)}
>
<i className="ri-file-text-line"></i>