From 847f7b2b5a3996f9386350551f94c5a450a166db Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Fri, 12 Dec 2025 16:10:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=201.=20=E6=B7=BB=E5=8A=A0=E4=BA=A4?= =?UTF-8?q?=E5=8F=89=E8=AF=84=E6=9F=A5=E4=B8=AD=E7=9A=84=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=8C=89=E9=92=AE=E4=B8=8E=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=9A=84=E7=BB=91=E5=AE=9A=E6=8E=A7=E5=88=B6=E3=80=82?= =?UTF-8?q?=20=20=20=202.=20=E5=AE=8C=E5=96=84=E6=9D=83=E9=99=90=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=9A=84hook=E5=87=BD=E6=95=B0=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8C=87=E5=AE=9A=E7=9A=84=E4=BA=A4=E5=8F=89=E8=AF=84?= =?UTF-8?q?=E6=9F=A5=E7=9A=84=E7=9B=B8=E5=85=B3=E7=9A=84=E6=9D=83=E9=99=90?= =?UTF-8?q?=E3=80=82=20fix:=201.=20=E4=BF=AE=E5=A4=8D=E4=BA=A4=E5=8F=89?= =?UTF-8?q?=E8=AF=84=E6=9F=A5=E4=B8=AD=E6=97=A0=E6=B3=95=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cross-checking/cross-file-result.ts | 2 +- app/components/collabora/CollaboraViewer.tsx | 2 +- .../cross-checking/ReviewPointsList.tsx | 212 ++++++++++-------- app/components/cross-checking/index.ts | 2 +- app/components/reviews/ReviewPointsList.tsx | 37 ++- app/hooks/usePermission.tsx | 51 ++++- app/routes/cross-checking._index.tsx | 6 +- app/routes/cross-checking.result.tsx | 28 ++- app/routes/reviews.tsx | 14 +- 9 files changed, 214 insertions(+), 140 deletions(-) diff --git a/app/api/cross-checking/cross-file-result.ts b/app/api/cross-checking/cross-file-result.ts index b9c43d3..d355740 100644 --- a/app/api/cross-checking/cross-file-result.ts +++ b/app/api/cross-checking/cross-file-result.ts @@ -151,7 +151,7 @@ export async function submitCrossCheckingOpinion( evaluation_point_id: Number(opinionData.evaluationPointId), // 强制转数字 proposed_score: opinionData.deductionScore, reason: opinionData.auditOpinion, - proposer_id: userInfo?.user_id || 1, + proposer_id: userInfo?.user_id, evaluation_result_id: opinionData.reviewPointResultId }; diff --git a/app/components/collabora/CollaboraViewer.tsx b/app/components/collabora/CollaboraViewer.tsx index 2856881..4ad0047 100644 --- a/app/components/collabora/CollaboraViewer.tsx +++ b/app/components/collabora/CollaboraViewer.tsx @@ -987,7 +987,7 @@ export const CollaboraViewer = forwardRef +
{/* 标题栏和关闭按钮 */}
搜索替换 diff --git a/app/components/cross-checking/ReviewPointsList.tsx b/app/components/cross-checking/ReviewPointsList.tsx index a14143e..4f050b8 100644 --- a/app/components/cross-checking/ReviewPointsList.tsx +++ b/app/components/cross-checking/ReviewPointsList.tsx @@ -25,14 +25,14 @@ import { Table } from '../ui/Table'; import { Pagination } from '../ui/Pagination'; import { Button } from '../ui/Button'; import { LoadingIndicator } from '../ui/SkeletonScreen'; -import { +import { performOpinionAction, + submitCrossCheckingOpinion, type CrossCheckingOpinion, - type OpinionActionType + type OpinionActionType, + type SubmitOpinionRequest } from '../../api/cross-checking/cross-file-result'; import { useFetcher, useNavigate } from '@remix-run/react'; -import { API_BASE_URL } from '~/config/api-config'; -import axios from 'axios'; // import '../../styles/components/TooltipStyles.css'; /** @@ -190,6 +190,11 @@ interface ReviewPointsListProps { onOpinionSubmitted?: (newProposal: ScoringProposal) => void; // 新增:意见提交成功后的回调 fileFormat?: string; // 文件格式(用于判断是否为PDF) onAiSuggestionReplace?: (searchText: string, replaceText: string, pageNumber: number) => void; // AI建议替换回调 + // 权限控制 + canReadProposal?: boolean; // 查看意见列表权限 + canCreateProposal?: boolean; // 提出建议权限 + canDeleteProposal?: boolean; // 撤销意见权限 + canVoteProposal?: boolean; // 赞同/反对权限 } /** @@ -451,7 +456,12 @@ export function ReviewPointsList({ userInfo, onOpinionSubmitted, fileFormat, - onAiSuggestionReplace + onAiSuggestionReplace, + // 权限控制 - 默认为 true 保持向后兼容 + canReadProposal = true, + canCreateProposal = true, + canDeleteProposal = true, + canVoteProposal = true }: ReviewPointsListProps) { // 状态管理 const [searchText, setSearchText] = useState(''); // 搜索文本 @@ -784,58 +794,58 @@ export function ReviewPointsList({ // 打印最终请求体 // console.log('最终请求体:', data); // console.log('jwtToken:', jwtToken); - // 用 axios + application/json 提交 + + // 组装 submitCrossCheckingOpinion 需要的参数 + const opinionData: SubmitOpinionRequest = { + reviewPointResultId: data.evaluation_result_id, + documentId: data.document_id, + evaluationPointId: data.evaluation_point_id, + auditOpinion: data.reason, + deductionScore: data.proposed_score + }; + try { - const response = await axios.post(`${API_BASE_URL.replace(/\/$/, '')}/admin/cross_review/proposals`, data, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${jwtToken}`, - } - }); - const result = response.data; - if (result.code === 200 || result.code === 0) { + const response = await submitCrossCheckingOpinion( + opinionData, + jwtToken, + { user_id: Number(userInfo.user_id) } + ); + + if (response.error) { + throw new Error(response.error); + } + + if (response.data?.success) { toastService.success('意见提交成功'); - + // 创建新的提案对象 const newProposal: ScoringProposal = { - id: result.id || Date.now(), // 使用返回的ID或时间戳作为临时ID + id: response.data.data?.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(), + created_at: response.data.data?.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 { - throw new Error(result.msg || '提交意见失败') - // toastService.error(result.msg || '提交意见失败'); + throw new Error('提交意见失败'); } } catch (error) { console.error('提交意见失败:', error); - - // 正确处理 axios 错误响应 - let errorMessage = '提交意见失败,请稍后重试'; - - if (axios.isAxiosError(error) && error.response?.data) { - // 从 axios 错误响应中提取 msg 字段 - errorMessage = error.response.data.msg || errorMessage; - } else if (error instanceof Error) { - // 处理普通 Error 对象 - errorMessage = error.message || errorMessage; - } - + const errorMessage = error instanceof Error ? error.message : '提交意见失败,请稍后重试'; toastService.error(errorMessage); } setIsSubmittingOpinion(false); @@ -2406,32 +2416,25 @@ 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)) && ( -
-
- 法律依据 -
+
{reviewPoint.legalBasis.name && ( -

{reviewPoint.legalBasis.name}

- )} - {reviewPoint.legalBasis.content && ( -

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

+
{reviewPoint.legalBasis.name}
)} {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.legalBasis.articles.map((item, index) => ( + + {typeof item === 'string' ? item : + typeof item === 'object' && item !== null ? + (item.name || item.content || JSON.stringify(item)) : + String(item)} + + ))}
)} + {reviewPoint.legalBasis.content && ( +
{reviewPoint.legalBasis.content}
+ )}
) )} @@ -2539,30 +2542,32 @@ export function ReviewPointsList({ return ( <>
- {/* 悬浮的意见数量显示 - 固定在左侧 */} -
- + + )}
@@ -2623,17 +2628,19 @@ export function ReviewPointsList({
*/} {/*
*/}
- {/* 提出意见按钮 */} -
- -
+ {/* 提出意见按钮 - 需要 canCreateProposal 权限 */} + {canCreateProposal && ( +
+ +
+ )} {renderStatusBadge(reviewPoint.status, reviewPoint.result,reviewPoint.title)}
@@ -2944,7 +2951,14 @@ export function ReviewPointsList({ render: (_: unknown, record: CrossCheckingOpinion) => { const isPerforming = (action: string) => performingAction === `${record.proposal_id}-${action}`; return ( - + ); } } @@ -2980,11 +2994,13 @@ export function ReviewPointsList({ } // 操作按钮区美化+弹窗确认组件 -function OpinionActions({ record, isPerforming, handleOpinionAction, userInfo }: { +function OpinionActions({ record, isPerforming, handleOpinionAction, userInfo, canVoteProposal = true, canDeleteProposal = true }: { record: CrossCheckingOpinion; isPerforming: (action: string) => boolean; handleOpinionAction: (id: string | number, action: OpinionActionType) => void; userInfo?: { user_id: number }; + canVoteProposal?: boolean; // 赞同/反对/撤销投票权限 + canDeleteProposal?: boolean; // 撤销意见权限 }) { const [showModal, setShowModal] = useState(null); const [countdown, setCountdown] = useState(3); @@ -3030,12 +3046,12 @@ function OpinionActions({ record, isPerforming, handleOpinionAction, userInfo }: }; // 判断是否是发起人 - const isProposer = userInfo && record.proposer_id === userInfo.user_id; + const isProposer = userInfo && record.proposer_id === Number(userInfo.user_id); return (
- {/* 仅当can_vote为true时显示赞同/反对按钮 */} - {record.can_vote && ( + {/* 仅当can_vote为true且有canVoteProposal权限时显示赞同/反对按钮 */} + {record.can_vote && canVoteProposal && ( <>
@@ -796,6 +810,10 @@ export default function CrossCheckingResult() { onOpinionSubmitted={handleOpinionSubmitted} fileFormat={reviewData.fileInfo.fileFormat} onAiSuggestionReplace={handleAiSuggestionReplace} + canReadProposal={canReadProposal} + canCreateProposal={canCreateProposal} + canDeleteProposal={canDeleteProposal} + canVoteProposal={canVoteProposal} />
diff --git a/app/routes/reviews.tsx b/app/routes/reviews.tsx index 05cd0f0..87cfa42 100644 --- a/app/routes/reviews.tsx +++ b/app/routes/reviews.tsx @@ -322,11 +322,11 @@ export default function ReviewDetails() { } | undefined>(undefined); // 🐛 调试:打印 loader 返回的完整数据到浏览器控制台 - useEffect(() => { - if (typeof window !== 'undefined') { - console.group('📦 [Reviews] Loader 数据'); + // useEffect(() => { + // if (typeof window !== 'undefined') { + // console.group('📦 [Reviews] Loader 数据'); // console.log('完整数据:', loaderData); - console.log('文档信息:', document); + // console.log('文档信息:', document); // console.log('评查点数量:', reviewPoints?.length); // console.log('评查点数量:', reviewPoints); // console.log('统计信息:', statistics); @@ -334,9 +334,9 @@ export default function ReviewDetails() { // console.log('比对文档:', comparison_document); // console.log('用户信息:', loaderData.userInfo); // console.log('JWT Token (前20位):', frontendJWT?.substring(0, 20) + '...'); - console.groupEnd(); - } - }, [loaderData, document, reviewPoints, statistics, reviewInfo, comparison_document, frontendJWT]); + // console.groupEnd(); + // } + // }, [loaderData, document, reviewPoints, statistics, reviewInfo, comparison_document, frontendJWT]); // loader 数据加载出错 useEffect(()=>{