feat: 1. 将交叉评查转移在入口页。

2. 交叉评查渲染的pdf预览组件复用评查点详情的,同时在评查结果中的数据也添加坐标信息。
This commit is contained in:
2025-11-26 10:49:15 +08:00
parent d1f764028c
commit fe75b4fabd
8 changed files with 181 additions and 93 deletions
@@ -80,6 +80,16 @@ const getRuleTypeText = (type?: string): string => {
return ruleTypeMap[type] || type; return ruleTypeMap[type] || type;
}; };
/**
* 字符位置类型定义
* 用于定位文档中具体的文字位置
*/
export interface CharPosition {
box: number[][]; // 字符边界框坐标
char: string; // 字符内容
score: number; // OCR识别置信度
}
/** /**
* 评查点类型定义 * 评查点类型定义
* 用于展示单个评查结果 * 用于展示单个评查结果
@@ -172,7 +182,7 @@ interface ReviewPointsListProps {
reviewPoints: ReviewPoint[]; reviewPoints: ReviewPoint[];
statistics: Statistics; statistics: Statistics;
activeReviewPointResultId: string | null; activeReviewPointResultId: string | null;
onReviewPointSelect: (id: string, page?: number) => void; onReviewPointSelect: (id: string, page?: number, charPositions?: CharPosition[]) => void;
onStatusChange?: (id: string, editAuditStatusId: string | number, status: string, message: string) => void; onStatusChange?: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
scoringProposals?: ScoringProposal[]; scoringProposals?: ScoringProposal[];
jwtToken?: string; // 添加JWT token参数 jwtToken?: string; // 添加JWT token参数
@@ -1110,15 +1120,15 @@ export function ReviewPointsList({
// console.log('singleReviewPoint-------', singleReviewPoint); // console.log('singleReviewPoint-------', singleReviewPoint);
// 检查是否存在配置和pairs数组 // 检查是否存在配置和pairs数组
const config = singleReviewPoint.config as { const config = singleReviewPoint.config as {
logic?: string; logic?: string;
pairs?: Array<{ pairs?: Array<{
sourceField: Record<string, { page: number; value: string }>; sourceField: Record<string, { page: number; value: string; char_positions?: CharPosition[] }>;
targetField: Record<string, { page: number; value: string }>; targetField: Record<string, { page: number; value: string; char_positions?: CharPosition[] }>;
res: boolean; res: boolean;
compareMethod?: string; compareMethod?: string;
}>; }>;
selectedFields?: string[] selectedFields?: string[]
} | undefined; } | undefined;
if (!config || !config.pairs || !Array.isArray(config.pairs) || config.pairs.length === 0) { if (!config || !config.pairs || !Array.isArray(config.pairs) || config.pairs.length === 0) {
@@ -1159,27 +1169,28 @@ export function ReviewPointsList({
// 查找链条关系 // 查找链条关系
const findChains = () => { const findChains = () => {
type ChainItem = { type ChainItem = {
field: string; field: string;
data: { data: {
key: string; key: string;
page: number; page: number;
value: string value: string;
}; char_positions?: CharPosition[];
};
res: boolean; res: boolean;
compareMethod?: string; compareMethod?: string;
}; };
const chains: Array<Array<ChainItem>> = []; const chains: Array<Array<ChainItem>> = [];
const visited = new Set<string>(); const visited = new Set<string>();
// 构建字段映射关系 // 构建字段映射关系
const fieldMap = new Map<string, Array<{ const fieldMap = new Map<string, Array<{
targetField: string; targetField: string;
data: { data: {
source: { key: string; page: number; value: string }; source: { key: string; page: number; value: string; char_positions?: CharPosition[] };
target: { key: string; page: number; value: string }; target: { key: string; page: number; value: string; char_positions?: CharPosition[] };
}; };
res: boolean; res: boolean;
compareMethod?: string; compareMethod?: string;
}>>(); }>>();
@@ -1396,7 +1407,7 @@ export function ReviewPointsList({
for (const item of chain) { for (const item of chain) {
if (item.data.page && typeof onReviewPointSelect === 'function') { if (item.data.page && typeof onReviewPointSelect === 'function') {
hasPage = true; hasPage = true;
onReviewPointSelect(reviewPoint.id, Number(item.data.page)); onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions);
break; break;
} }
} }
@@ -1410,7 +1421,7 @@ export function ReviewPointsList({
// 遍历chain找到第一个有效的page // 遍历chain找到第一个有效的page
for (const item of chain) { for (const item of chain) {
if (item.data.page && typeof onReviewPointSelect === 'function') { if (item.data.page && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPoint.id, Number(item.data.page)); onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions);
break; break;
} }
} }
@@ -1450,7 +1461,7 @@ export function ReviewPointsList({
// 假设onReviewPointSelect在作用域内可用 // 假设onReviewPointSelect在作用域内可用
const reviewPointId = reviewPoint.id as string; const reviewPointId = reviewPoint.id as string;
if (reviewPointId && typeof onReviewPointSelect === 'function') { if (reviewPointId && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPointId, Number(item.data.page)); onReviewPointSelect(reviewPointId, Number(item.data.page), item.data.char_positions);
} }
} }
else if(reviewPoint.contentPage && reviewPoint.contentPage[item.field]){ else if(reviewPoint.contentPage && reviewPoint.contentPage[item.field]){
@@ -1533,7 +1544,7 @@ export function ReviewPointsList({
if (chain[0].data.page) { if (chain[0].data.page) {
const reviewPointId = reviewPoint.id as string; const reviewPointId = reviewPoint.id as string;
if (reviewPointId && typeof onReviewPointSelect === 'function') { if (reviewPointId && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPointId, chain[0].data.page); onReviewPointSelect(reviewPointId, chain[0].data.page, chain[0].data.char_positions);
} }
} }
else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[0].field]){ else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[0].field]){
@@ -1559,7 +1570,7 @@ export function ReviewPointsList({
if (chain[1].data.page) { if (chain[1].data.page) {
const reviewPointId = reviewPoint.id as string; const reviewPointId = reviewPoint.id as string;
if (reviewPointId && typeof onReviewPointSelect === 'function') { if (reviewPointId && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPointId, chain[1].data.page); onReviewPointSelect(reviewPointId, chain[1].data.page, chain[1].data.char_positions);
} }
} }
else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[1].field]){ else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[1].field]){
@@ -1632,12 +1643,13 @@ export function ReviewPointsList({
*/ */
const renderOtherRule = (otherRule: Record<string, unknown>, reviewPoint: ReviewPoint) => { const renderOtherRule = (otherRule: Record<string, unknown>, reviewPoint: ReviewPoint) => {
const fieldKey = otherRule.fieldKey as string; const fieldKey = otherRule.fieldKey as string;
const fieldValue = otherRule.fieldValue as { const fieldValue = otherRule.fieldValue as {
type: Record<string, { type: Record<string, {
res: boolean; res: boolean;
page?: number | string; page?: number | string;
value?: string; value?: string;
}>; char_positions?: CharPosition[];
}>;
}; };
// 获取res的综合结果 // 获取res的综合结果
@@ -1698,7 +1710,7 @@ export function ReviewPointsList({
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (mainTypeValue.page && typeof onReviewPointSelect === 'function') { if (mainTypeValue.page && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page)); onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions);
}else if(reviewPoint.contentPage && reviewPoint.contentPage[fieldKey]){ }else if(reviewPoint.contentPage && reviewPoint.contentPage[fieldKey]){
onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey])); onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey]));
}else{ }else{
@@ -1709,7 +1721,7 @@ export function ReviewPointsList({
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); e.preventDefault();
if (mainTypeValue.page && typeof onReviewPointSelect === 'function') { if (mainTypeValue.page && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page)); onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions);
}else{ }else{
toastService.error(`没有找到${fieldKey}对应的索引内容`); toastService.error(`没有找到${fieldKey}对应的索引内容`);
} }
@@ -1772,12 +1784,13 @@ export function ReviewPointsList({
const renderModelRule = (aiRule: Record<string, unknown>, reviewPoint: ReviewPoint) => { const renderModelRule = (aiRule: Record<string, unknown>, reviewPoint: ReviewPoint) => {
// 从aiRule中提取配置信息 // 从aiRule中提取配置信息
const config = aiRule.config as { const config = aiRule.config as {
model?: string; model?: string;
fields?: Record<string, { fields?: Record<string, {
page: number | string; page: number | string;
value: string; value: string;
}>; char_positions?: CharPosition[];
}>;
message?: string; message?: string;
res?: boolean; res?: boolean;
} | undefined; } | undefined;
@@ -1819,7 +1832,7 @@ export function ReviewPointsList({
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (value.page && typeof onReviewPointSelect === 'function') { if (value.page && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPoint.id, Number(value.page)); onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions);
}else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){ }else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){
onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key])); onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key]));
}else{ }else{
@@ -1831,7 +1844,7 @@ export function ReviewPointsList({
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); e.preventDefault();
if (value.page && typeof onReviewPointSelect === 'function') { if (value.page && typeof onReviewPointSelect === 'function') {
onReviewPointSelect(reviewPoint.id, Number(value.page)); onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions);
}else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){ }else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){
onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key])); onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key]));
}else{ }else{
@@ -1946,6 +1959,7 @@ export function ReviewPointsList({
interface RuleFieldValue { interface RuleFieldValue {
page?: number | string; page?: number | string;
value?: string; value?: string;
char_positions?: CharPosition[];
type: Record<string, boolean>; type: Record<string, boolean>;
} }
@@ -1964,7 +1978,7 @@ export function ReviewPointsList({
// 使用类型断言获取config对象的具体结构 // 使用类型断言获取config对象的具体结构
const config = rule.config as { const config = rule.config as {
res: boolean; res: boolean;
fields: Record<string, { page: number; value: string }>; fields: Record<string, { page: number; value: string; char_positions?: CharPosition[] }>;
logic?: string; logic?: string;
}; };
@@ -2011,7 +2025,7 @@ export function ReviewPointsList({
// 使用类型断言获取config对象的具体结构 // 使用类型断言获取config对象的具体结构
const config = rule.config as { const config = rule.config as {
res: boolean; res: boolean;
field: Record<string, { page: string | number; value: string }>; field: Record<string, { page: string | number; value: string; char_positions?: CharPosition[]}>;
formatType?: string; formatType?: string;
parameters?: string; parameters?: string;
}; };
@@ -2045,7 +2059,7 @@ export function ReviewPointsList({
logic: string; logic: string;
res: boolean; res: boolean;
conditions: Array<{ conditions: Array<{
field: Record<string, { page: number | string; value: string }>; field: Record<string, { page: number | string; value: string; char_positions?: CharPosition[] }>;
value: string; value: string;
operator: string; operator: string;
res: boolean; res: boolean;
@@ -2080,7 +2094,7 @@ export function ReviewPointsList({
// 使用类型断言获取config对象的具体结构 // 使用类型断言获取config对象的具体结构
const config = rule.config as { const config = rule.config as {
res: boolean; res: boolean;
field: Record<string, { page: number | string; value: string }>; field: Record<string, { page: number | string; value: string; char_positions?: CharPosition[] }>;
pattern?: string; pattern?: string;
matchType?: string; matchType?: string;
selectedFields?: string[]; selectedFields?: string[];
@@ -2115,6 +2129,7 @@ export function ReviewPointsList({
res: boolean; res: boolean;
page?: number | string; page?: number | string;
value?: string; value?: string;
char_positions?: CharPosition[]
}>; }>;
}; };
}> = []; }> = [];
@@ -2127,6 +2142,7 @@ export function ReviewPointsList({
res: boolean; res: boolean;
page?: number | string; page?: number | string;
value?: string; value?: string;
char_positions?: CharPosition[]
}>; }>;
}; };
}> = {}; }> = {};
@@ -2138,9 +2154,10 @@ export function ReviewPointsList({
const typeKey = Object.keys(fieldValue.type)[0]; // 获取类型名称(exists/logic/regex/format const typeKey = Object.keys(fieldValue.type)[0]; // 获取类型名称(exists/logic/regex/format
const typeValue = fieldValue.type[typeKey]; // 获取类型值(true/false const typeValue = fieldValue.type[typeKey]; // 获取类型值(true/false
// 提取页码和值 // 提取页码和值和字符位置
const page = fieldValue.page; const page = fieldValue.page;
const value = fieldValue.value; const value = fieldValue.value;
const char_positions = fieldValue.char_positions
// 如果是第一次遇到这个fieldKey,创建新条目 // 如果是第一次遇到这个fieldKey,创建新条目
if (!fieldKeyMap[fieldKey]) { if (!fieldKeyMap[fieldKey]) {
@@ -2157,7 +2174,8 @@ export function ReviewPointsList({
fieldKeyMap[fieldKey].fieldValue.type[typeKey] = { fieldKeyMap[fieldKey].fieldValue.type[typeKey] = {
res: typeValue, res: typeValue,
page, page,
value value,
char_positions
}; };
}); });
+1 -1
View File
@@ -3,7 +3,7 @@
*/ */
export { FileInfo } from './FileInfo'; export { FileInfo } from './FileInfo';
export { FilePreview } from './FilePreview'; export { FilePreview } from '../reviews/FilePreview';
export { ReviewPointsList } from './ReviewPointsList'; export { ReviewPointsList } from './ReviewPointsList';
export type { ReviewPoint } from './ReviewPointsList'; export type { ReviewPoint } from './ReviewPointsList';
export { DocumentListModal } from './DocumentListModal'; export { DocumentListModal } from './DocumentListModal';
+25 -3
View File
@@ -90,16 +90,18 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
fetchUserRoutes(); fetchUserRoutes();
}, [userRole, frontendJWT, navigate]); }, [userRole, frontendJWT, navigate]);
// 🔑 检查是否处于系统设置模式 // 🔑 检查是否处于系统设置模式或交叉评查模式
const [isSettingsMode, setIsSettingsMode] = useState<boolean>(false); const [isSettingsMode, setIsSettingsMode] = useState<boolean>(false);
const [isCrossCheckingMode, setIsCrossCheckingMode] = useState<boolean>(false);
// 从 sessionStorage 读取当前选中的模块名称和图片路径,以及系统设置模式标志 // 从 sessionStorage 读取当前选中的模块名称和图片路径,以及各种模式标志
useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
try { try {
const moduleName = sessionStorage.getItem('selectedModuleName'); const moduleName = sessionStorage.getItem('selectedModuleName');
const modulePicPath = sessionStorage.getItem('selectedModulePicPath'); const modulePicPath = sessionStorage.getItem('selectedModulePicPath');
const settingsMode = sessionStorage.getItem('settingsMode'); const settingsMode = sessionStorage.getItem('settingsMode');
const crossCheckingMode = sessionStorage.getItem('crossCheckingMode');
if (moduleName) { if (moduleName) {
setSelectedModuleName(moduleName); setSelectedModuleName(moduleName);
@@ -114,9 +116,19 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
// 🔑 检查是否处于系统设置模式 // 🔑 检查是否处于系统设置模式
if (settingsMode === 'true') { if (settingsMode === 'true') {
setIsSettingsMode(true); setIsSettingsMode(true);
setIsCrossCheckingMode(false); // 互斥
console.log('⚙️ [Sidebar] 进入系统设置模式'); console.log('⚙️ [Sidebar] 进入系统设置模式');
} else { }
// 🔑 检查是否处于交叉评查模式
else if (crossCheckingMode === 'true') {
setIsCrossCheckingMode(true);
setIsSettingsMode(false); // 互斥
console.log('🔀 [Sidebar] 进入交叉评查模式');
}
// 普通模式
else {
setIsSettingsMode(false); setIsSettingsMode(false);
setIsCrossCheckingMode(false);
} }
} catch (error) { } catch (error) {
console.error('❌ [Sidebar] 读取 sessionStorage 失败:', error); console.error('❌ [Sidebar] 读取 sessionStorage 失败:', error);
@@ -177,11 +189,21 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
return item.path === '/settings' || item.path?.startsWith('/settings/'); return item.path === '/settings' || item.path?.startsWith('/settings/');
} }
// 🔑 优先检查:如果处于交叉评查模式,只显示 /cross-checking 及其子路由
if (isCrossCheckingMode) {
return item.path === '/cross-checking' || item.path?.startsWith('/cross-checking/');
}
// 🔑 重要:非系统设置模式下,隐藏所有 /settings 相关菜单 // 🔑 重要:非系统设置模式下,隐藏所有 /settings 相关菜单
if (item.path === '/settings' || item.path?.startsWith('/settings/')) { if (item.path === '/settings' || item.path?.startsWith('/settings/')) {
return false; return false;
} }
// 🔑 重要:非交叉评查模式下,隐藏所有 /cross-checking 相关菜单
if (item.path === '/cross-checking' || item.path?.startsWith('/cross-checking/')) {
return false;
}
// 如果是省局访问 // 如果是省局访问
// if(isPort51707){ // if(isPort51707){
// if (selectedModuleName === '智慧法务大模型'){ // if (selectedModuleName === '智慧法务大模型'){
+9 -9
View File
@@ -181,18 +181,18 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
}, [targetPage, numPages, fileContent, activeReviewPointResultId, isStructuredView, isPdf]); }, [targetPage, numPages, fileContent, activeReviewPointResultId, isStructuredView, isPdf]);
// 调试日志 // 调试日志
console.log('[FilePreview] 组件渲染', { // console.log('[FilePreview] 组件渲染', {
real_path, // real_path,
fileExtension, // fileExtension,
isDocx, // isDocx,
isPdf, // isPdf,
hasPath: !!fileContent.path, // hasPath: !!fileContent.path,
hasTemplatePath: !!fileContent.template_contract_path // hasTemplatePath: !!fileContent.template_contract_path
}); // });
// 如果是PDF文件,直接使用PdfPreview组件 // 如果是PDF文件,直接使用PdfPreview组件
if (isPdf && real_path) { if (isPdf && real_path) {
console.log('[FilePreview] 渲染PDF预览', { real_path, targetPage, charPositions }); // console.log('[FilePreview] 渲染PDF预览', { real_path, targetPage, charPositions });
const pageOffset = fileContent.ocrResult?.__meta?.page_offset || 0; const pageOffset = fileContent.ocrResult?.__meta?.page_offset || 0;
return ( return (
<PdfPreview <PdfPreview
@@ -56,14 +56,14 @@ export function PdfPreview({
onZoomChange onZoomChange
}: PdfPreviewProps) { }: PdfPreviewProps) {
// 调试日志 // 调试日志
console.log('[PdfPreview] 组件渲染', { // console.log('[PdfPreview] 组件渲染', {
filePath, // filePath,
targetPage, // targetPage,
charPositions, // charPositions,
isStructuredView, // isStructuredView,
activeReviewPointResultId, // activeReviewPointResultId,
pageOffset // pageOffset
}); // });
// ============ 状态管理 ============ // ============ 状态管理 ============
const [numPages, setNumPages] = useState<number | null>(null); const [numPages, setNumPages] = useState<number | null>(null);
+55 -4
View File
@@ -51,6 +51,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 🔑 检查用户是否有系统设置权限 // 🔑 检查用户是否有系统设置权限
let hasSettingsAccess = false; let hasSettingsAccess = false;
let hasCrossCheckingAccess = false;
if (userRole && frontendJWT) { if (userRole && frontendJWT) {
const { getUserRoutesByRole } = await import('~/api/auth/user-routes'); const { getUserRoutesByRole } = await import('~/api/auth/user-routes');
const routesResult = await getUserRoutesByRole(userRole, frontendJWT, true); // includeHidden=true const routesResult = await getUserRoutesByRole(userRole, frontendJWT, true); // includeHidden=true
@@ -58,12 +60,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
if (routesResult.success && routesResult.data) { if (routesResult.success && routesResult.data) {
// 检查是否存在顶级路由 '/settings' // 检查是否存在顶级路由 '/settings'
hasSettingsAccess = routesResult.data.some(route => route.path === '/settings'); hasSettingsAccess = routesResult.data.some(route => route.path === '/settings');
// 检查是否存在顶级路由 '/cross-checking'
hasCrossCheckingAccess = routesResult.data.some(route => route.path === '/cross-checking');
// console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`); // console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`);
// console.log(`🔑 [Index Loader] 用户${hasCrossCheckingAccess ? '有' : '没有'}交叉评查权限`);
} }
} }
// 返回用户信息、入口模块和系统设置权限给客户端 // 返回用户信息、入口模块和权限给客户端
return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess }); return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess, hasCrossCheckingAccess });
} }
export default function Index() { export default function Index() {
@@ -108,13 +113,20 @@ export default function Index() {
// console.log('👤 [Index] 当前用户信息:', userInfo); // console.log('👤 [Index] 当前用户信息:', userInfo);
}, [userRole, userInfo]); }, [userRole, userInfo]);
// 🔑 清除系统设置模式标志(当用户返回首页时) // 🔑 清除系统设置模式和交叉评查模式标志(当用户返回首页时)
useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const settingsMode = sessionStorage.getItem('settingsMode'); const settingsMode = sessionStorage.getItem('settingsMode');
const crossCheckingMode = sessionStorage.getItem('crossCheckingMode');
if (settingsMode === 'true') { if (settingsMode === 'true') {
sessionStorage.removeItem('settingsMode'); sessionStorage.removeItem('settingsMode');
console.log('🔄 [Index] 清除系统设置模式标志'); // console.log('🔄 [Index] 清除系统设置模式标志');
}
if (crossCheckingMode === 'true') {
sessionStorage.removeItem('crossCheckingMode');
// console.log('🔄 [Index] 清除交叉评查模式标志');
} }
} }
}, []); // 只在组件挂载时执行一次 }, []); // 只在组件挂载时执行一次
@@ -233,12 +245,31 @@ export default function Index() {
sessionStorage.removeItem('selectedModuleId'); sessionStorage.removeItem('selectedModuleId');
sessionStorage.removeItem('selectedModuleName'); sessionStorage.removeItem('selectedModuleName');
sessionStorage.removeItem('selectedModulePicPath'); sessionStorage.removeItem('selectedModulePicPath');
// 清除交叉评查模式标志
sessionStorage.removeItem('crossCheckingMode');
} }
// 跳转到系统设置的默认页面 // 跳转到系统设置的默认页面
navigate('/rule-groups'); navigate('/rule-groups');
}; };
// 处理进入交叉评查
const handleEnterCrossChecking = () => {
if (typeof window !== 'undefined') {
// 🔑 设置标志:表示用户通过交叉评查入口进入
sessionStorage.setItem('crossCheckingMode', 'true');
// 清除模块相关的标志(因为不是从入口模块进入)
sessionStorage.removeItem('selectedModuleId');
sessionStorage.removeItem('selectedModuleName');
sessionStorage.removeItem('selectedModulePicPath');
// 清除系统设置模式标志
sessionStorage.removeItem('settingsMode');
}
// 跳转到交叉评查的默认页面
navigate('/cross-checking');
};
return ( return (
<div className="home-page"> <div className="home-page">
{/* 登出表单 - 隐藏 */} {/* 登出表单 - 隐藏 */}
@@ -309,6 +340,25 @@ export default function Index() {
</div> </div>
))} ))}
{/* 🔑 交叉评查入口 - 只有有权限的用户才能看到 */}
{loaderData.hasCrossCheckingAccess && (
<div
className="module-card"
onClick={handleEnterCrossChecking}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleEnterCrossChecking();
}
}}
role="button"
tabIndex={0}
aria-label="交叉评查"
>
<i className="ri-shuffle-line text-5xl text-primary"></i>
<span className="module-name"></span>
</div>
)}
{/* 🔑 系统设置入口 - 只有有权限的用户才能看到 */} {/* 🔑 系统设置入口 - 只有有权限的用户才能看到 */}
{loaderData.hasSettingsAccess && ( {loaderData.hasSettingsAccess && (
<div <div
@@ -327,6 +377,7 @@ export default function Index() {
<span className="module-name"></span> <span className="module-name"></span>
</div> </div>
)} )}
</> </>
) : ( ) : (
<div className="text-center text-gray-500 py-8"> <div className="text-center text-gray-500 py-8">
+18 -13
View File
@@ -37,8 +37,8 @@ import {
ReviewPointsList ReviewPointsList
} from "~/components/cross-checking"; } from "~/components/cross-checking";
// 从ReviewPointsList组件中导入ReviewPoint类型 // 从ReviewPointsList组件中导入ReviewPoint类型和CharPosition类型
import { type ReviewPoint } from '~/components/cross-checking'; import { type ReviewPoint, type CharPosition } from '~/components/cross-checking';
import { messageService } from "~/components/ui/MessageModal"; import { messageService } from "~/components/ui/MessageModal";
import { loadingBarService } from "~/components/ui/LoadingBar"; import { loadingBarService } from "~/components/ui/LoadingBar";
import { Breadcrumb } from "~/components/layout/Breadcrumb"; import { Breadcrumb } from "~/components/layout/Breadcrumb";
@@ -297,7 +297,7 @@ export async function action({ request }: ActionFunctionArgs) {
} }
export default function CrossCheckingResult() { export default function CrossCheckingResult() {
console.log('[组件] CrossCheckingResult 渲染'); // console.log('[组件] CrossCheckingResult 渲染');
const navigate = useNavigate(); const navigate = useNavigate();
const loaderData = useLoaderData<typeof loader>(); const loaderData = useLoaderData<typeof loader>();
@@ -306,18 +306,19 @@ export default function CrossCheckingResult() {
const [reviewData, setReviewData] = useState<ReviewData | null>(null); const [reviewData, setReviewData] = useState<ReviewData | null>(null);
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null); const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
const [targetPage, setTargetPage] = useState<number | undefined>(undefined); const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
const [charPositions, setCharPositions] = useState<CharPosition[] | undefined>(undefined);
const [localScoringProposals, setLocalScoringProposals] = useState<ScoringProposal[]>(scoring_proposals || []); // 本地状态管理scoringProposals const [localScoringProposals, setLocalScoringProposals] = useState<ScoringProposal[]>(scoring_proposals || []); // 本地状态管理scoringProposals
// 使用ref来跟踪loading状态,避免不必要的重新渲染 // 使用ref来跟踪loading状态,避免不必要的重新渲染
const isProcessingRef = useRef(false); const isProcessingRef = useRef(false);
// 添加组件挂载/卸载日志 // 添加组件挂载/卸载日志
useEffect(() => { // useEffect(() => {
console.log('[组件] CrossCheckingResult 挂载'); // console.log('[组件] CrossCheckingResult 挂载');
return () => { // return () => {
console.log('[组件] CrossCheckingResult 卸载'); // console.log('[组件] CrossCheckingResult 卸载');
}; // };
}, []); // }, []);
// 同步外部scoring_proposals到本地状态 // 同步外部scoring_proposals到本地状态
useEffect(() => { useEffect(() => {
@@ -387,19 +388,22 @@ export default function CrossCheckingResult() {
}, [document, reviewPoints, statistics, reviewInfo]); }, [document, reviewPoints, statistics, reviewInfo]);
const handleReviewPointSelect = useCallback((reviewPointId: string, page?: number) => { const handleReviewPointSelect = useCallback((reviewPointId: string, page?: number, charPositions?: CharPosition[]) => {
// 如果点击的是相同的评查点,但有page参数,先重置targetPage以确保useEffect能够触发 // 如果点击的是相同的评查点,但有page参数,先重置targetPage以确保useEffect能够触发
if (reviewPointId === activeReviewPointResultId && page) { if (reviewPointId === activeReviewPointResultId && page) {
setTargetPage(undefined); setTargetPage(undefined);
// 使用setTimeout确保状态更新后再设置新的targetPage setCharPositions(undefined);
// 使用setTimeout确保状态更新后再设置新的targetPage和charPositions
setTimeout(() => { setTimeout(() => {
setActiveReviewPointResultId(reviewPointId); setActiveReviewPointResultId(reviewPointId);
setTargetPage(page); setTargetPage(page);
setCharPositions(charPositions);
}, 0); }, 0);
} else { } else {
// 正常设置activeReviewPointIdtargetPage // 正常设置activeReviewPointIdtargetPage和charPositions
setActiveReviewPointResultId(reviewPointId); setActiveReviewPointResultId(reviewPointId);
setTargetPage(page); setTargetPage(page);
setCharPositions(charPositions);
} }
}, [activeReviewPointResultId]); }, [activeReviewPointResultId]);
@@ -724,11 +728,12 @@ export default function CrossCheckingResult() {
<div className="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:space-x-4 lg:justify-between"> <div className="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:space-x-4 lg:justify-between">
{/* 左侧:文件预览 */} {/* 左侧:文件预览 */}
<div className="w-full lg:w-[62%]"> <div className="w-full lg:w-[62%]">
<FilePreview <FilePreview
fileContent={document} fileContent={document}
reviewPoints={reviewData.reviewPoints} reviewPoints={reviewData.reviewPoints}
activeReviewPointResultId={activeReviewPointResultId} activeReviewPointResultId={activeReviewPointResultId}
targetPage={targetPage} targetPage={targetPage}
charPositions={charPositions}
/> />
</div> </div>
+2 -10
View File
@@ -234,7 +234,7 @@ export async function action({ request }: ActionFunctionArgs) {
const result = formData.get("result") as string; const result = formData.get("result") as string;
const message = formData.get("message") as string; const message = formData.get("message") as string;
console.log('更新评查结果参数:', { reviewPointResultId, editAuditStatusId, result, message }); // console.log('更新评查结果参数:', { reviewPointResultId, editAuditStatusId, result, message });
try { try {
const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, result, message, request); const response = await updateReviewResult(reviewPointResultId, editAuditStatusId, result, message, request);
@@ -257,7 +257,7 @@ export async function action({ request }: ActionFunctionArgs) {
if (intent === "confirmReviewResults") { if (intent === "confirmReviewResults") {
const documentId = formData.get("documentId") as string; const documentId = formData.get("documentId") as string;
console.log('确认评查结果参数:', { documentId }); // console.log('确认评查结果参数:', { documentId });
try { try {
const response = await confirmReviewResults(documentId, request); const response = await confirmReviewResults(documentId, request);
@@ -293,17 +293,9 @@ export default function ReviewDetails() {
const navigate = useNavigate(); const navigate = useNavigate();
const loaderData = useLoaderData<typeof loader>(); const loaderData = useLoaderData<typeof loader>();
// 调试:查看loaderData内容 - 强制刷新
console.log('[Reviews Component] loaderData keys:', Object.keys(loaderData));
console.log('[Reviews Component] loaderData:', loaderData);
const fetcher = useFetcher(); const fetcher = useFetcher();
const { document, reviewPoints, statistics, reviewInfo, comparison_document, frontendJWT } = loaderData; const { document, reviewPoints, statistics, reviewInfo, comparison_document, frontendJWT } = loaderData;
// 调试:查看解构后的数据
console.log('[Reviews Component] 解构后的document:', document);
console.log('[Reviews Component] 解构后的reviewPoints length:', reviewPoints?.length);
const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态 const [isLoading, setIsLoading] = useState(false); // 已经通过loader加载了数据,不需要再显示加载状态
const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo' const [activeTab, setActiveTab] = useState<string>('preview'); // 'preview', 'analysis', 'fileinfo'
const [reviewData, setReviewData] = useState<ReviewData | null>(null); const [reviewData, setReviewData] = useState<ReviewData | null>(null);