feat: 1. 添加交叉评查中的相关页面的按钮与权限的绑定控制。 2. 完善权限校验的hook函数,添加指定的交叉评查的相关的权限。

fix: 1. 修复交叉评查中无法高亮文档的问题。
This commit is contained in:
2025-12-12 16:10:05 +08:00
parent d4000cd292
commit 847f7b2b5a
9 changed files with 214 additions and 140 deletions
+1 -1
View File
@@ -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
};
+1 -1
View File
@@ -987,7 +987,7 @@ export const CollaboraViewer = forwardRef<CollaboraViewerHandle, CollaboraViewer
{/* 搜索替换测试面板 - 移动到左上角并添加关闭按钮 */}
{showSearchReplacePanel && (
<div className="absolute top-2 left-2 z-50 bg-white bg-opacity-10 px-3 py-2 rounded shadow-lg border border-gray-200">
<div className="absolute top-2 left-2 z-50 bg-white bg-opacity-70 px-3 py-2 rounded shadow-lg border border-gray-200">
{/* 标题栏和关闭按钮 */}
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-gray-700 font-medium"></span>
@@ -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)) && (
<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>
<div className="law-reference mb-3 select-text">
{reviewPoint.legalBasis.name && (
<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 select-text"><span className="font-medium"></span>{reviewPoint.legalBasis.content}</p>
<div className="law-reference-title">{reviewPoint.legalBasis.name}</div>
)}
{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 select-text">
{reviewPoint.legalBasis.articles.map((item, index) => (
<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 || ''}` :
item.content || JSON.stringify(item)) :
String(item)}
</li>
))}
</ul>
<div className="law-reference-articles">
{reviewPoint.legalBasis.articles.map((item, index) => (
<span key={index} className="law-article">
{typeof item === 'string' ? item :
typeof item === 'object' && item !== null ?
(item.name || item.content || JSON.stringify(item)) :
String(item)}
</span>
))}
</div>
)}
{reviewPoint.legalBasis.content && (
<div className="law-reference-content">{reviewPoint.legalBasis.content}</div>
)}
</div>
)
)}
@@ -2539,30 +2542,32 @@ export function ReviewPointsList({
return (
<>
<div className="relative">
{/* 悬浮的意见数量显示 - 固定在左侧 */}
<button
className="absolute left-[-35px] top-16 z-10 cursor-pointer"
onClick={() => handleOpenOpinionListModal(reviewPoints[0])}
type="button"
aria-label="查看意见列表"
>
<div className={`relative flex flex-col items-center bg-gradient-to-br from-blue-50 to-blue-100 px-2 py-2 rounded-lg border border-blue-300 shadow-md transition-all duration-200 ease-out hover:scale-110 hover:shadow-xl active:scale-95 ${scoringProposals.length === 0 ? 'opacity-50' : 'opacity-100'}`}>
{/* 脉动提示点 - 仅当有意见时显示 */}
{scoringProposals.length > 0 && (
<span className="absolute -top-1 -right-1 flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
</span>
)}
<i className="ri-chat-1-line text-blue-600 text-lg mb-0.5"></i>
<span className="text-lg text-blue-700 font-bold leading-tight">{scoringProposals.length}</span>
<div className="flex flex-col items-center text-[10px] text-blue-600 leading-tight mt-0.5">
<span></span>
<span></span>
<span></span>
{/* 悬浮的意见数量显示 - 固定在左侧,需要 canReadProposal 权限 */}
{canReadProposal && (
<button
className="absolute left-[-35px] top-16 z-10 cursor-pointer"
onClick={() => handleOpenOpinionListModal(reviewPoints[0])}
type="button"
aria-label="查看意见列表"
>
<div className={`relative flex flex-col items-center bg-gradient-to-br from-blue-50 to-blue-100 px-2 py-2 rounded-lg border border-blue-300 shadow-md transition-all duration-200 ease-out hover:scale-110 hover:shadow-xl active:scale-95 ${scoringProposals.length === 0 ? 'opacity-50' : 'opacity-100'}`}>
{/* 脉动提示点 - 仅当有意见时显示 */}
{scoringProposals.length > 0 && (
<span className="absolute -top-1 -right-1 flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
</span>
)}
<i className="ri-chat-1-line text-blue-600 text-lg mb-0.5"></i>
<span className="text-lg text-blue-700 font-bold leading-tight">{scoringProposals.length}</span>
<div className="flex flex-col items-center text-[10px] text-blue-600 leading-tight mt-0.5">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</button>
</button>
)}
<div className="review-points-panel select-text">
<TooltipPortal />
@@ -2623,17 +2628,19 @@ export function ReviewPointsList({
</div> */}
{/* </div> */}
<div className="flex ml-2 flex-shrink-0 min-w-[15%]">
{/* 提出意见按钮 */}
<div className="flex items-center">
<button className="bg-green-700 hover:bg-green-600 text-white px-2 py-1 rounded-md text-xs"
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击
handleOpenOpinionModal(reviewPoint);
}}
>
<i className="ri-chat-1-line mr-1"></i>
</button>
</div>
{/* 提出意见按钮 - 需要 canCreateProposal 权限 */}
{canCreateProposal && (
<div className="flex items-center">
<button className="bg-green-700 hover:bg-green-600 text-white px-2 py-1 rounded-md text-xs"
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击
handleOpenOpinionModal(reviewPoint);
}}
>
<i className="ri-chat-1-line mr-1"></i>
</button>
</div>
)}
{renderStatusBadge(reviewPoint.status, reviewPoint.result,reviewPoint.title)}
</div>
</div>
@@ -2944,7 +2951,14 @@ export function ReviewPointsList({
render: (_: unknown, record: CrossCheckingOpinion) => {
const isPerforming = (action: string) => performingAction === `${record.proposal_id}-${action}`;
return (
<OpinionActions record={record} isPerforming={isPerforming} handleOpinionAction={handleOpinionAction} userInfo={userInfo as { user_id: number } | undefined} />
<OpinionActions
record={record}
isPerforming={isPerforming}
handleOpinionAction={handleOpinionAction}
userInfo={userInfo as { user_id: number } | undefined}
canVoteProposal={canVoteProposal}
canDeleteProposal={canDeleteProposal}
/>
);
}
}
@@ -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 | OpinionActionType>(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 (
<div className="flex gap-3">
{/* 仅当can_vote为true时显示赞同/反对按钮 */}
{record.can_vote && (
{/* 仅当can_vote为true且有canVoteProposal权限时显示赞同/反对按钮 */}
{record.can_vote && canVoteProposal && (
<>
<Button
type="default"
@@ -3055,8 +3071,8 @@ function OpinionActions({ record, isPerforming, handleOpinionAction, userInfo }:
</Button>
</>
)}
{/* 仅当can_vote为false时显示撤销投票按钮 */}
{!record.can_vote && !isProposer && (
{/* 仅当can_vote为false且有canVoteProposal权限时显示撤销投票按钮 */}
{!record.can_vote && !isProposer && canVoteProposal && (
<Button
type="default"
className="bg-yellow-600 hover:bg-yellow-700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
@@ -3066,8 +3082,8 @@ function OpinionActions({ record, isPerforming, handleOpinionAction, userInfo }:
{isPerforming('withdraw_vote') ? '处理中...' : '撤销投票'}
</Button>
)}
{/* 仅当是发起人才显示撤销意见按钮 */}
{isProposer && (
{/* 仅当是发起人且有canDeleteProposal权限才显示撤销意见按钮 */}
{isProposer && canDeleteProposal && (
<Button
type="default"
className="bg-yellow-600 hover:bg-red-700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
+1 -1
View File
@@ -5,5 +5,5 @@
export { FileInfo } from './FileInfo';
export { FilePreview } from '../reviews/FilePreview';
export { ReviewPointsList } from './ReviewPointsList';
export type { ReviewPoint } from './ReviewPointsList';
export type { ReviewPoint, CharPosition } from './ReviewPointsList';
export { DocumentListModal } from './DocumentListModal';
+14 -23
View File
@@ -2192,34 +2192,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)) && (
<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>
<div className="law-reference mb-3 select-text">
{reviewPoint.legalBasis.name && (
<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 select-text"><span className="font-medium"></span>{reviewPoint.legalBasis.content}</p>
<div className="law-reference-title">{reviewPoint.legalBasis.name}</div>
)}
{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 select-text">
{reviewPoint.legalBasis.articles.map((item, index) => (
<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 || ''}` :
item.content || JSON.stringify(item)) :
String(item)}
</li>
))}
</ul>
<div className="law-reference-articles">
{reviewPoint.legalBasis.articles.map((item, index) => (
<span key={index} className="law-article">
{typeof item === 'string' ? item :
typeof item === 'object' && item !== null ?
(item.name || item.content || JSON.stringify(item)) :
String(item)}
</span>
))}
</div>
)}
{reviewPoint.legalBasis.content && (
<div className="law-reference-content">{reviewPoint.legalBasis.content}</div>
)}
</div>
)
)}
+50 -1
View File
@@ -35,6 +35,34 @@ interface RootLoaderData {
};
}
/**
* 交叉评查模块默认权限配置
* 当 permissionMap 中没有配置时,使用此默认配置
*/
const CROSS_CHECKING_DEFAULT_PERMISSIONS: Record<string, string[]> = {
'/cross-checking': [
'cross_review:task:read',
// 'cross_review:task:create',
// 'cross_review:document:complete',
'cross_review:progress:view',
'cross_review:proposal:create',
'cross_review:proposal:delete',
'cross_review:proposal:read',
// 'cross_review:proposal:vote'
],
'/cross-checking/upload': [
// 'cross_review:task:create'
],
'/cross-checking/result': [
// 'cross_review:document:complete',
'cross_review:progress:view',
'cross_review:proposal:create',
'cross_review:proposal:delete',
'cross_review:proposal:read',
// 'cross_review:proposal:vote'
]
};
export function usePermission() {
const rootData = useRouteLoaderData("root") as RootLoaderData;
const location = useLocation();
@@ -46,7 +74,28 @@ export function usePermission() {
// 🔑 根据当前路由获取权限列表
const currentPath = location.pathname;
// console.log('currentPath', currentPath)
const currentPermissions = permissionMap[currentPath] || [];
// 获取当前路由的权限:优先使用 permissionMap,否则使用交叉评查默认配置
const getCrossCheckingPermissions = (): string[] => {
// 检查是否是交叉评查相关路由
if (currentPath.startsWith('/cross-checking')) {
// 精确匹配
if (CROSS_CHECKING_DEFAULT_PERMISSIONS[currentPath]) {
return CROSS_CHECKING_DEFAULT_PERMISSIONS[currentPath];
}
// 处理带参数的路由,如 /cross-checking/result?id=xxx
const basePath = currentPath.split('?')[0];
if (CROSS_CHECKING_DEFAULT_PERMISSIONS[basePath]) {
return CROSS_CHECKING_DEFAULT_PERMISSIONS[basePath];
}
}
return [];
};
// 优先使用 permissionMap 中的权限,如果没有则使用交叉评查默认权限
const currentPermissions = permissionMap[currentPath]?.length > 0
? permissionMap[currentPath]
: getCrossCheckingPermissions();
// 向后兼容:如果存在旧的permissions数组,也要支持
const legacyPermissions = rootData?.permissions || [];
+3 -3
View File
@@ -251,9 +251,9 @@ export default function CrossCheckingIndex() {
const fetcher = useFetcher();
// 权限控制
const { canCreate, canView } = usePermission();
const canCreateTask = canCreate('cross_review');
const canViewTask = canView('cross_review');
const { hasPermission } = usePermission();
const canCreateTask = hasPermission('cross_review:task:create');
const canViewTask = hasPermission('cross_review:task:read');
// 状态管理
const [isDeleting, setIsDeleting] = useState(false);
+23 -5
View File
@@ -29,6 +29,7 @@ import crossCheckingStyles from "~/styles/cross-checking-result.css?url";
import { getReviewPoints, updateReviewResult, getReviewPoints_fromApi} from "~/api/evaluation_points/reviews";
import { toastService } from "~/components/ui/Toast";
import { confirmReviewResults, checkProposalVotes, findIsProposer } from "~/api/cross-checking/cross-file-result";
import { usePermission } from "~/hooks/usePermission";
// 导入交叉评查详情页面组件
import {
@@ -332,10 +333,19 @@ export default function CrossCheckingResult() {
const loaderData = useLoaderData<typeof loader>();
const { document, reviewPoints, statistics, reviewInfo, scoring_proposals, jwtToken, userInfo, isProposer, taskId, taskName } = loaderData;
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
// 权限控制
const { hasPermission } = usePermission();
const canCompleteDocument = hasPermission('cross_review:document:complete'); // 完成评查按钮
const canReadProposal = hasPermission('cross_review:proposal:read'); // 查看意见列表
const canCreateProposal = hasPermission('cross_review:proposal:create'); // 提出建议按钮
const canDeleteProposal = hasPermission('cross_review:proposal:delete'); // 撤销意见按钮
const canVoteProposal = hasPermission('cross_review:proposal:vote'); // 赞同/反对按钮
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
const [charPositions, setCharPositions] = useState<CharPosition[] | undefined>(undefined);
const [highlightValue, setHighlightValue] = useState<string | undefined>(undefined);
const [localScoringProposals, setLocalScoringProposals] = useState<ScoringProposal[]>(scoring_proposals || []); // 本地状态管理scoringProposals
const [aiSuggestionReplace, setAiSuggestionReplace] = useState<{
searchText: string;
@@ -419,22 +429,25 @@ export default function CrossCheckingResult() {
}, [document, reviewPoints, statistics, reviewInfo]);
const handleReviewPointSelect = useCallback((reviewPointId: string, page?: number, charPositions?: CharPosition[]) => {
const handleReviewPointSelect = useCallback((reviewPointId: string, page?: number, charPositions?: CharPosition[], value?: string) => {
// 如果点击的是相同的评查点,但有page参数,先重置targetPage以确保useEffect能够触发
if (reviewPointId === activeReviewPointResultId && page) {
setTargetPage(undefined);
setCharPositions(undefined);
// 使用setTimeout确保状态更新后再设置新的targetPage和charPositions
setHighlightValue(undefined);
// 使用setTimeout确保状态更新后再设置新的targetPage、charPositions和highlightValue
setTimeout(() => {
setActiveReviewPointResultId(reviewPointId);
setTargetPage(page);
setCharPositions(charPositions);
setHighlightValue(value);
}, 0);
} else {
// 正常设置activeReviewPointId、targetPagecharPositions
// 正常设置activeReviewPointId、targetPagecharPositions和highlightValue
setActiveReviewPointResultId(reviewPointId);
setTargetPage(page);
setCharPositions(charPositions);
setHighlightValue(value);
}
}, [activeReviewPointResultId]);
@@ -730,8 +743,8 @@ export default function CrossCheckingResult() {
</div>
</div>
{/* 完成评查按钮 */}
{isProposer && (
{/* 完成评查按钮 - 需要 isProposer 且拥有 cross_review:document:complete 权限 */}
{isProposer && canCompleteDocument && (
<button
type="button"
onClick={(event) => {
@@ -778,6 +791,7 @@ export default function CrossCheckingResult() {
activeReviewPointResultId={activeReviewPointResultId}
targetPage={targetPage}
charPositions={charPositions}
highlightValue={highlightValue}
aiSuggestionReplace={aiSuggestionReplace}
/>
</div>
@@ -796,6 +810,10 @@ export default function CrossCheckingResult() {
onOpinionSubmitted={handleOpinionSubmitted}
fileFormat={reviewData.fileInfo.fileFormat}
onAiSuggestionReplace={handleAiSuggestionReplace}
canReadProposal={canReadProposal}
canCreateProposal={canCreateProposal}
canDeleteProposal={canDeleteProposal}
canVoteProposal={canVoteProposal}
/>
</div>
</div>
+7 -7
View File
@@ -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(()=>{