diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 690915e..2703334 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -219,10 +219,12 @@ function getFileExtension(filename: string): string { function mapProcessingStatusToFileStatus(status?: string | null): string { const normalized = (status || '').toLowerCase(); - if (normalized === 'completed') return 'Processed'; + if (normalized === 'completed' || normalized === 'processed') return 'Processed'; if (normalized === 'failed') return 'Failed'; - if (normalized === 'running' || normalized === 'queued' || normalized === 'dispatch') return 'Evaluationing'; - if (normalized === 'waiting' || normalized === 'pending') return 'Waiting'; + if (normalized === 'cutting') return 'Cutting'; + if (normalized === 'extractioning') return 'Extractioning'; + if (normalized === 'evaluationing' || normalized === 'running' || normalized === 'dispatch') return 'Evaluationing'; + if (normalized === 'waiting' || normalized === 'pending' || normalized === 'queued') return 'Waiting'; return 'Waiting'; } @@ -827,13 +829,15 @@ export async function getDocumentsListFromAPI(searchParams: { if (name) params.keyword = name; if (fileStatus) { const normalizedFileStatus = fileStatus.toLowerCase(); - if (normalizedFileStatus === 'processed') { - params.processingStatus = 'completed'; - } else if (normalizedFileStatus === 'failed') { - params.processingStatus = 'failed'; - } else { - params.processingStatus = 'running'; - } + const processingStatusMap: Record = { + waiting: 'waiting', + cutting: 'Cutting', + extractioning: 'Extractioning', + evaluationing: 'Evaluationing', + processed: 'completed', + failed: 'failed', + }; + params.processingStatus = processingStatusMap[normalizedFileStatus] || fileStatus; } if (documentTypeIds && documentTypeIds.length > 0) { diff --git a/app/components/layout/Layout.tsx b/app/components/layout/Layout.tsx index e8f4b5e..73cd979 100644 --- a/app/components/layout/Layout.tsx +++ b/app/components/layout/Layout.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import type { MenuItem } from '~/api/auth/user-routes'; import { Sidebar } from './Sidebar'; // import { Header } from './Header'; import { Breadcrumb } from './Breadcrumb'; @@ -10,6 +11,7 @@ interface LayoutProps { userRole?: UserRole; frontendJWT?: string; isMobile?: boolean; // 是否为移动端设备(服务端通过 User-Agent 检测) + menuItems?: MenuItem[]; } // 添加一个接口表示路由handle可能包含的属性 @@ -37,7 +39,7 @@ type RulesTestDetailData = { }; }; -export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '', isMobile = false }: LayoutProps) { +export function Layout({ children, userRole = 'developer' as UserRole, frontendJWT = '', isMobile = false, menuItems = [] }: LayoutProps) { const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [effectiveUserRole, setEffectiveUserRole] = useState(userRole); const [effectiveFrontendJWT, setEffectiveFrontendJWT] = useState(frontendJWT); @@ -153,6 +155,7 @@ export function Layout({ children, userRole = 'developer' as UserRole, frontendJ onToggle={toggleSidebar} userRole={effectiveUserRole} frontendJWT={effectiveFrontendJWT} + menuItems={menuItems} /> {/* 规则详情页顶部栏 */} diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index 0e09f82..1753794 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -10,13 +10,14 @@ interface SidebarProps { collapsed: boolean; userRole: UserRole; frontendJWT?: string; + menuItems?: MenuItem[]; } -export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: SidebarProps) { +export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '', menuItems: initialMenuItems = [] }: SidebarProps) { const location = useLocation(); const [expandedMenus, setExpandedMenus] = useState>({}); - const [menuItems, setMenuItems] = useState([]); // 动态菜单项 - const [isLoadingRoutes, setIsLoadingRoutes] = useState(true); // 路由加载状态 + const [menuItems, setMenuItems] = useState(initialMenuItems); // 动态菜单项 + const [isLoadingRoutes, setIsLoadingRoutes] = useState(initialMenuItems.length === 0); // 路由加载状态 const [isMobile, setIsMobile] = useState(false); // 移动端检测 const [selectedModuleName, setSelectedModuleName] = useState(''); // 当前选中的模块名称 const [selectedModulePicPath, setSelectedModulePicPath] = useState(''); // 当前选中的模块图片路径 @@ -39,12 +40,15 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid // 获取用户路由权限 useEffect(() => { - // console.log('🔍 [Sidebar] useEffect 触发,开始获取路由权限'); + if (initialMenuItems.length > 0) { + setMenuItems(initialMenuItems); + setIsLoadingRoutes(false); + return; + } const fetchUserRoutes = async () => { setIsLoadingRoutes(true); try { - // 优先使用传入的 frontendJWT,否则从 localStorage 读取 let jwt = frontendJWT; if (!jwt && typeof window !== 'undefined') { @@ -59,29 +63,20 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid return; } - // console.log('🔍 [Sidebar] 当前用户角色:', userRole, 'JWT前20字符:', jwt.substring(0, 20)); - // console.log('🔍 [Sidebar] 映射后的角色key:', roleKey); const result = await getUserRoutesByRole(userRole, jwt); if (result.success && result.data) { setMenuItems(result.data); - // console.log('✅ [Sidebar] 用户路由权限加载成功:', result.data); } else { console.error('❌ [Sidebar] 获取用户路由权限失败:', result.error); - - // 如果需要重定向到首页 if (result.shouldRedirectToHome) { - // console.log('🔄 [Sidebar] 重定向到首页'); navigate('/'); return; } - - // 其他错误情况,使用空数组 setMenuItems([]); } } catch (error) { console.error('❌ [Sidebar] 获取用户路由权限时发生错误:', error); - // 发生异常时也重定向到首页 navigate('/'); return; } finally { @@ -90,7 +85,7 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid }; fetchUserRoutes(); - }, [userRole, frontendJWT, navigate]); + }, [userRole, frontendJWT, navigate, initialMenuItems]); // 🔑 检查是否处于系统设置模式或交叉评查模式 const [isSettingsMode, setIsSettingsMode] = useState(false); diff --git a/app/components/reviews/ReviewTabs.tsx b/app/components/reviews/ReviewTabs.tsx index 5029436..5c71dea 100644 --- a/app/components/reviews/ReviewTabs.tsx +++ b/app/components/reviews/ReviewTabs.tsx @@ -27,12 +27,13 @@ interface ReviewTabsProps { comparisonId?: number; }; onConfirmResults: () => void; + onExportReport?: () => void; jwtToken?: string | null; /** 下载前保存文档的回调,返回 true 表示保存成功可以继续下载 */ onSaveBeforeDownload?: () => Promise; } -export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults, jwtToken, onSaveBeforeDownload }: ReviewTabsProps) { +export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults, onExportReport, jwtToken, onSaveBeforeDownload }: ReviewTabsProps) { const [isNavigating, setIsNavigating] = useState(false); const [isReuploadModalOpen, setIsReuploadModalOpen] = useState(false); const [selectedTemplateFiles, setSelectedTemplateFiles] = useState([]); @@ -58,14 +59,21 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi : previousRoute === 'filesUpload' ? "/files/upload" : "/rules-files"; - // 立即导航返回 - navigate(returnTo); - // 触发上级页面数据重新加载 + navigate(returnTo); + setTimeout(() => { revalidator.revalidate(); + setIsNavigating(false); + loadingBarService.hide(); + }, 0); }; // 下载原文件 const handleDownloadFile = async () => { + if (!fileInfo.path) { + toastService.warning('当前文档暂无可下载原文件'); + return; + } + try { // 如果有保存回调,先执行保存(仅对 DOCX 文件有效) if (onSaveBeforeDownload) { @@ -311,12 +319,15 @@ export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfi )} - {/* */} + {onExportReport && ( + + )} - - @@ -1305,7 +1345,7 @@ export default function RulesTestDetail() {
{item.name || item.id} - {item.type} / {item.id} + {item.type} {requiredFromLabel(String(item.required))}{item.signatureTypes && item.signatureTypes.length > 0 ? ` · ${item.signatureTypes.join('、')}` : ''}
@@ -1421,9 +1461,101 @@ export default function RulesTestDetail() { ) : ( - -
当前链接没有匹配到评查点,请返回列表重新进入。
-
+ <> + +
+ 当前子类型还没有正式评查点。你可以先新增评查点,或先维护抽取字段 / 子文档 / 视觉要素后再回到这里补规则。 +
+
+ + +
+ 这里维护当前子类型可复用的抽取字段;新增评查点后再在“依赖字段”中引用。 + +
+ {fields.length > 0 ? ( +
+ {fields.map((field) => ( +
+
+ {field.name} + {field.group || '未分组'} / {fieldTypeLabel(field.type)} + + {requiredFromLabel(field.requiredFrom || '-')} + {field.type === 'enum' && field.allowed && field.allowed.length > 0 ? ` · ${field.allowed.join('、')}` : ''} + {field.description ? ` · ${field.description}` : ''} + +
+
+ + +
+
+ ))} +
+ ) : ( +
当前还没有配置抽取字段。
+ )} +
+ + +
+ 案卷场景可先把常用文书与内部字段配好,后续评查点直接引用。 + +
+ {subDocuments.length > 0 ? ( +
+ {subDocuments.map((document) => ( +
+
+ {document.name} + {document.id} / {requiredFromLabel(document.required || '-')} + {document.fields.length} 个字段{document.groups.length > 0 ? ` · ${document.groups.join('、')}` : ''} +
+
+ + +
+
+ ))} +
+ ) : ( +
当前还没有配置子文档。
+ )} +
+ + +
+ 这里维护签章、签名、骑缝章等可复用视觉要素,新增评查点后再按需引用。 + +
+ {visualElements.length > 0 ? ( +
+ {visualElements.map((item) => ( +
+
+ {item.name || item.id} + {item.type} + {requiredFromLabel(String(item.required))}{item.signatureTypes && item.signatureTypes.length > 0 ? ` · ${item.signatureTypes.join('、')}` : ''} +
+
+ + +
+
+ ))} +
+ ) : ( +
当前还没有配置视觉要素。
+ )} +
+ )}
@@ -1603,7 +1735,7 @@ export default function RulesTestDetail() {
@@ -1614,6 +1746,16 @@ export default function RulesTestDetail() {
+ {fieldDraft.type === 'enum' && ( + + )}