diff --git a/app/api/auth/user-routes.ts b/app/api/auth/user-routes.ts index 9d9a9ac..f701a9f 100644 --- a/app/api/auth/user-routes.ts +++ b/app/api/auth/user-routes.ts @@ -664,7 +664,7 @@ export async function getUserRoutesByRole( // 注意:Authorization 头会由 axios 拦截器自动添加(从 localStorage 读取) // 但为了确保使用正确的 token,这里仍然显式传递 const response = await apiRequest( - '/rbac/user/routes', // endpoint (第一个参数) + '/api/rbac/user/routes', // endpoint (第一个参数) { method: 'GET', headers: { @@ -678,6 +678,12 @@ export async function getUserRoutesByRole( // 检查响应是否成功 if (response.error) { console.error('❌ [User Routes] API 请求失败:', response.error); + + if (response.status === 404) { + console.warn('⚠️ [User Routes] 后端路由权限接口未落地,回退到静态菜单'); + return buildFallbackRoutes(roleKey); + } + // 🔑 如果是令牌过期错误,标记需要重定向到登录页 const isTokenExpired = response.error.includes('令牌已过期') || response.error.includes('令牌') || @@ -702,6 +708,10 @@ export async function getUserRoutesByRole( // 检查响应数据 if (!response.data) { console.error('❌ [User Routes] 后端未返回数据'); + if (response.status === 404) { + console.warn('⚠️ [User Routes] 后端路由权限接口未落地,回退到静态菜单'); + return buildFallbackRoutes(roleKey); + } if (typeof window !== 'undefined') { toastService.error("获取路由数据失败"); } @@ -998,6 +1008,7 @@ export function mapUserRoleToRoleKey(userRole: string): string { const roleMapping: Record = { 'common': 'common', 'admin': 'admin', + 'provincial_admin': 'admin', 'deptLeader': 'deptLeader', 'groupLeader': 'groupLeader', // 添加常见的后端角色映射 @@ -1009,4 +1020,23 @@ export function mapUserRoleToRoleKey(userRole: string): string { // 如果找不到映射,返回 userRole 本身(假设后端已经返回了正确的 role_key) return roleMapping[userRole] || userRole || 'common'; -} +} + +/** + * 基于静态菜单数据构造后备结果。 + */ +function buildFallbackRoutes(roleKey: string): { + success: boolean; + data: MenuItem[]; + permissionMap: Record; +} { + const mappedRoleKey = mapUserRoleToRoleKey(roleKey); + const fallbackMenus = FALLBACK_MENU_DATA[mappedRoleKey] || FALLBACK_MENU_DATA.common; + const permissionMap: Record = {}; + + return { + success: true, + data: fallbackMenus, + permissionMap, + }; +} diff --git a/app/api/home/home.ts b/app/api/home/home.ts index cf46220..a95c4fe 100644 --- a/app/api/home/home.ts +++ b/app/api/home/home.ts @@ -1,4 +1,3 @@ -import { postgrestGet, type PostgrestParams } from "../postgrest-client"; import { apiRequest } from "../axios-client"; // import dayjs from 'dayjs'; @@ -202,7 +201,7 @@ export async function getHomeData(reviewType?: string | null, userId?: string | export interface AreaConfig { area: string; // 地区名称 enabled: boolean; // 是否启用 - sort_order: number; // 排序顺序 + sortOrder: number; // 排序顺序 } /** @@ -212,11 +211,13 @@ export interface EntryModule { id: number; name: string; description: string | null; - path: string | null; - areas: AreaConfig[]; // 修改为对象数组 - created_at: string; - updated_at: string; - document_types?: Array<{ + targetPath: string | null; + routePath: string | null; + iconPath: string | null; + sortOrder: number; + requiresDocumentTypes: boolean; + areas: AreaConfig[]; + documentTypes: Array<{ id: number; name: string; code: string | null; @@ -225,119 +226,38 @@ export interface EntryModule { /** * 获取用户可访问的入口模块 - * @param userArea 用户所属地区 * @param token JWT token * @returns 入口模块列表 */ -export async function getEntryModules(userRole: string | null | undefined, userArea: string | null | undefined, token?: string): Promise { +export async function getEntryModules(token?: string): Promise { try { - if (!userRole || !userArea) { - console.warn('⚠️ [getEntryModules] 用户角色或地区为空,返回空模块列表'); + if (!token) { + console.warn('⚠️ [getEntryModules] JWT 为空,返回空模块列表'); return []; } - // console.log('🔍 [getEntryModules] 查询地区:', userArea); - - // 查询 entry_modules 表,获取所有模块(在客户端进行过滤) - const params: PostgrestParams = { - select: 'id,name,description,path,areas,created_at,updated_at', - filter: {} - }; - - const modulesResponse = await postgrestGet('/api/postgrest/proxy/entry_modules', { ...params, token }); + const modulesResponse = await apiRequest( + '/api/home/entry-modules', + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}` + } + } + ); if (modulesResponse.error) { console.error('❌ [getEntryModules] 查询入口模块失败:', modulesResponse.error); return []; } - const allModules = extractApiData(modulesResponse.data); - if (!allModules || allModules.length === 0) { + const modules = extractApiData(modulesResponse.data); + if (!modules || modules.length === 0) { console.warn('⚠️ [getEntryModules] 未找到任何入口模块'); return []; } - // 🔑 在客户端过滤:只保留包含用户地区且已启用的模块 - const modules = allModules.filter(module => { - // 省级管理员可以看到所有模块 - if (userRole === 'provincial_admin') { - return true; - } - - // 检查 areas 数组中是否存在匹配的地区配置 - if (!module.areas || !Array.isArray(module.areas)) { - return false; - } - - // 查找用户地区的配置 - const areaConfig = module.areas.find(config => - config.area === userArea && config.enabled === true - ); - - return !!areaConfig; // 找到且启用才返回 true - }); - - if (modules.length === 0) { - console.warn('⚠️ [getEntryModules] 未找到已启用的入口模块'); - return []; - } - - // console.log(`✅ [getEntryModules] 找到 ${modules.length} 个已启用的入口模块`); - - // 为每个模块查询关联的 document_types - const modulesWithTypes = await Promise.all( - modules.map(async (module) => { - try { - const typesParams: PostgrestParams = { - select: 'id,name,code', - filter: { - entry_module_id: `eq.${module.id}` - } - }; - - const typesResponse = await postgrestGet('/api/postgrest/proxy/document_types', { ...typesParams, token }); - - if (typesResponse.error) { - console.error(`❌ [getEntryModules] 查询模块 ${module.id} 的文档类型失败:`, typesResponse.error); - return { ...module, document_types: [] }; - } - - const documentTypes = extractApiData>(typesResponse.data); - - return { - ...module, - document_types: documentTypes || [] - }; - } catch (error) { - console.error(`❌ [getEntryModules] 处理模块 ${module.id} 时出错:`, error); - return { ...module, document_types: [] }; - } - }) - ); - - // console.log('✅ [getEntryModules] 入口模块数据(含文档类型):', JSON.stringify(modulesWithTypes)); - - // 默认会多加一个 智慧法务助手 入口 默认所有人都可以用,看到 - modulesWithTypes.push({ - "id": 0, - "name": "智慧法务助手", - "description": "智慧法务助手", - "path": "entryModule/assistant", - "areas": [], - "created_at": "2025-11-18T21:33:33.857417+08:00", - "updated_at": "2025-11-18T22:28:51.819722+08:00", - "document_types": [ - { - "id": 0, - "name": "空", - "code": "空" - } - ] - } - ) - - - return modulesWithTypes; + return modules; } catch (error) { console.error('❌ [getEntryModules] 获取入口模块失败:', error instanceof Error ? error.message : String(error)); return []; @@ -525,4 +445,3 @@ export async function getTopRiskUsers( return { available: false, total: 0, items: [] }; } } - diff --git a/app/api/login/login-client.ts b/app/api/login/login-client.ts index 3f84d11..11075e9 100644 --- a/app/api/login/login-client.ts +++ b/app/api/login/login-client.ts @@ -64,7 +64,7 @@ export interface LoginResponse { * @returns 登录响应(包含 JWT token) */ export async function loginWithOAuth(loginData: LoginRequest): Promise { - const loginUrl = `${API_BASE_URL}/auth/login`; + const loginUrl = `${API_BASE_URL}/api/auth/login`; console.log("📝 [Login Client] 调用后端 OAuth 登录接口:", loginUrl); @@ -116,7 +116,7 @@ export async function loginWithPassword( username: string, password: string ): Promise { - const loginUrl = `${API_BASE_URL}/auth/login`; + const loginUrl = `${API_BASE_URL}/api/auth/login`; console.log("📝 [Login Client] 调用后端密码登录接口:", loginUrl); diff --git a/app/config/api-config.ts b/app/config/api-config.ts index a8c091b..ec0520c 100644 --- a/app/config/api-config.ts +++ b/app/config/api-config.ts @@ -1,511 +1,511 @@ -/** - * API配置文件 - * 统一管理所有API地址,方便部署时修改 - * 支持环境变量覆盖配置 - */ -// 环境配置类型 -interface ApiConfig { - // 主API基础URL(FastAPI后端地址,包含Dify代理) - baseUrl: string; - // 文档服务URL - documentUrl: string; - // 文档上传API URL - uploadUrl: string; - // Collabora Online 服务器地址 - collaboraUrl: string; - // 应用基础URL(用于 WOPI 回调) - appUrl: string; - // OAuth2.0配置 - oauth: { - // IDaaS服务器地址 - serverUrl?: string; - // OAuth2应用Client ID - clientId?: string; - // OAuth2应用Client Secret - clientSecret?: string; - // 回调地址(内网Web) - redirectUri?: string; - // 钉钉Web回调地址(互联网地址) - dingtalkRedirectUri?: string; - // 应用ID(用于登出) - appId?: string; - }; - // Dify 知识库检索配置 - dify: { - // Reranking 模型提供商 - rerankingProviderName: string; - // Reranking 模型名称 - rerankingModelName: string; - }; -} - -// 端口特定配置映射 -// 根据不同端口提供不同的API配置 -export const portConfigs: Record> = { - - // 主要 - // 梅州 +/** + * API配置文件 + * 统一管理所有API地址,方便部署时修改 + * 支持环境变量覆盖配置 + */ +// 环境配置类型 +interface ApiConfig { + // 主API基础URL(FastAPI后端地址,包含Dify代理) + baseUrl: string; + // 文档服务URL + documentUrl: string; + // 文档上传API URL + uploadUrl: string; + // Collabora Online 服务器地址 + collaboraUrl: string; + // 应用基础URL(用于 WOPI 回调) + appUrl: string; + // OAuth2.0配置 + oauth: { + // IDaaS服务器地址 + serverUrl?: string; + // OAuth2应用Client ID + clientId?: string; + // OAuth2应用Client Secret + clientSecret?: string; + // 回调地址(内网Web) + redirectUri?: string; + // 钉钉Web回调地址(互联网地址) + dingtalkRedirectUri?: string; + // 应用ID(用于登出) + appId?: string; + }; + // Dify 知识库检索配置 + dify: { + // Reranking 模型提供商 + rerankingProviderName: string; + // Reranking 模型名称 + rerankingModelName: string; + }; +} + +// 端口特定配置映射 +// 根据不同端口提供不同的API配置 +export const portConfigs: Record> = { + + // 主要 + // 梅州 '51703': { - - // baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) - // documentUrl: 'http://172.16.0.59:8096/docauditai/', - // uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', - // baseUrl: 'http://nas.7bm.co:8096', - // documentUrl: 'http://nas.7bm.co:8096/docauditai/', - // uploadUrl: 'http://nas.7bm.co:8096/api/v2/documents', + + // baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) + // documentUrl: 'http://172.16.0.59:8096/docauditai/', + // uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', + // baseUrl: 'http://nas.7bm.co:8096', + // documentUrl: 'http://nas.7bm.co:8096/docauditai/', + // uploadUrl: 'http://nas.7bm.co:8096/api/v2/documents', // leaudit-platform 联调地址 - baseUrl: 'http://172.15.0.59:8096', - documentUrl: 'http://172.15.0.59:8096/docauditai/', - uploadUrl: 'http://172.15.0.59:8096/api/upload', - collaboraUrl: 'http://172.16.0.58:9980', - appUrl: 'http://172.16.0.34:51703', - - - // baseUrl: 'http://172.16.0.56:8073', - // documentUrl: 'http://172.16.0.56:8073/docauditai/', - // uploadUrl: 'http://172.16.0.56:8073/api/v2/documents', - // collaboraUrl: 'http://172.16.0.81:9980', - // appUrl: 'http://172.16.0.34:51703', - - // baseUrl: 'http://10.79.97.17:8000', - // documentUrl: 'http://10.79.97.17:8000/docauditai/', - // uploadUrl: 'http://10.79.97.17:8000/api/v2/documents', - // collaboraUrl: 'http://10.79.97.17:9980', - // appUrl: 'http://10.79.97.17:51703', - - oauth: { - redirectUri: 'http://10.79.97.17:51703/callback', - // 钉钉Web回调地址(互联网地址)- 需要根据实际部署修改 - dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51703 || 'https://10-79-97-1751703-b2oaixksdrrisox0t3.ztna-dingtalk.com/callback' - } - }, - - - // 云浮 - '51704': { - // baseUrl: 'http://172.16.0.55:8001', - // documentUrl: 'http://172.16.0.55:8001/docauditai/', - // uploadUrl: 'http://172.16.0.55:8001/api/v2/documents', - // collaboraUrl: 'http://172.16.0.81:9980', - // appUrl: 'http://172.16.0.34:51704', - - baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) - documentUrl: 'http://172.16.0.59:8096/docauditai/', - uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', - collaboraUrl: 'http://172.16.0.58:9980', - appUrl: 'http://172.16.0.34:51703', - - // baseUrl: 'http://10.79.97.17:8001', - // documentUrl: 'http://10.79.97.17:8001/docauditai/', - // uploadUrl: 'http://10.79.97.17:8001/api/v2/documents', - // collaboraUrl: 'http://10.79.97.17:9980', - // appUrl: 'http://10.79.97.17:51704', - oauth: { - redirectUri: 'http://10.79.97.17:51704/callback', - dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51704 || 'https://10-79-97-1751704-xxxxxxxxx.ztna-dingtalk.com/callback' - } - }, - - // 揭阳 - '51705': { - baseUrl: 'http://10.79.97.17:8002', - documentUrl: 'http://10.79.97.17:8002/docauditai/', - uploadUrl: 'http://10.79.97.17:8002/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://10.79.97.17:51705', - oauth: { - redirectUri: 'http://10.79.97.17:51705/callback', - dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51705 || 'https://10-79-97-1751705-xxxxxxxxx.ztna-dingtalk.com/callback' - } - }, - - // 潮州 - '51706': { - baseUrl: 'http://10.79.97.17:8003', - documentUrl: 'http://10.79.97.17:8003/docauditai/', - uploadUrl: 'http://10.79.97.17:8003/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://10.79.97.17:51706', - oauth: { - redirectUri: 'http://10.79.97.17:51706/callback', - dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51706 || 'https://10-79-97-1751706-xxxxxxxxx.ztna-dingtalk.com/callback' - } - }, - - - // 省局 - '51707': { - // baseUrl: 'http://172.16.0.55:8866', - // documentUrl: 'http://172.16.0.55:8866/docauditai/', - // uploadUrl: 'http://172.16.0.55:8866/api/v2/documents', - // collaboraUrl: 'http://172.16.0.81:9980', - // appUrl: 'http://172.16.0.34:51707', - - // 正式环境 - baseUrl: 'http://10.79.97.17:8866', - documentUrl: 'http://10.79.97.17:8866/docauditai/', - uploadUrl: 'http://10.79.97.17:8866/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://10.79.97.17:51707', - - oauth: { - redirectUri: 'http://10.79.97.17:51707/callback', - dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51707 || 'https://10-79-97-1751707-xxxxxxxxx.ztna-dingtalk.com/callback' - } - }, - - //test - '51708': { - baseUrl: 'http://10.79.97.17:8005', - documentUrl: 'http://10.79.97.17:8005/docauditai/', - uploadUrl: 'http://10.79.97.17:8005/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://10.79.97.17:51708' - }, -}; - -// 不同环境的默认配置 -// 由于合同模板的上传,后续的的uploadUrl都不需要/upload,直接写/api/v2/documents,由程序自动添加/upload或/upload_contract_template -const configs: Record = { - // 开发环境 + baseUrl: 'http://172.16.0.59:8096', + documentUrl: 'http://172.16.0.59:8096/docauditai/', + uploadUrl: 'http://172.16.0.59:8096/api/upload', + collaboraUrl: 'http://172.16.0.58:9980', + appUrl: 'http://172.16.0.34:51703', + + + // baseUrl: 'http://172.16.0.56:8073', + // documentUrl: 'http://172.16.0.56:8073/docauditai/', + // uploadUrl: 'http://172.16.0.56:8073/api/v2/documents', + // collaboraUrl: 'http://172.16.0.81:9980', + // appUrl: 'http://172.16.0.34:51703', + + // baseUrl: 'http://10.79.97.17:8000', + // documentUrl: 'http://10.79.97.17:8000/docauditai/', + // uploadUrl: 'http://10.79.97.17:8000/api/v2/documents', + // collaboraUrl: 'http://10.79.97.17:9980', + // appUrl: 'http://10.79.97.17:51703', + + oauth: { + redirectUri: 'http://10.79.97.17:51703/callback', + // 钉钉Web回调地址(互联网地址)- 需要根据实际部署修改 + dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51703 || 'https://10-79-97-1751703-b2oaixksdrrisox0t3.ztna-dingtalk.com/callback' + } + }, + + + // 云浮 + '51704': { + // baseUrl: 'http://172.16.0.55:8001', + // documentUrl: 'http://172.16.0.55:8001/docauditai/', + // uploadUrl: 'http://172.16.0.55:8001/api/v2/documents', + // collaboraUrl: 'http://172.16.0.81:9980', + // appUrl: 'http://172.16.0.34:51704', + + baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) + documentUrl: 'http://172.16.0.59:8096/docauditai/', + uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', + collaboraUrl: 'http://172.16.0.58:9980', + appUrl: 'http://172.16.0.34:51703', + + // baseUrl: 'http://10.79.97.17:8001', + // documentUrl: 'http://10.79.97.17:8001/docauditai/', + // uploadUrl: 'http://10.79.97.17:8001/api/v2/documents', + // collaboraUrl: 'http://10.79.97.17:9980', + // appUrl: 'http://10.79.97.17:51704', + oauth: { + redirectUri: 'http://10.79.97.17:51704/callback', + dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51704 || 'https://10-79-97-1751704-xxxxxxxxx.ztna-dingtalk.com/callback' + } + }, + + // 揭阳 + '51705': { + baseUrl: 'http://10.79.97.17:8002', + documentUrl: 'http://10.79.97.17:8002/docauditai/', + uploadUrl: 'http://10.79.97.17:8002/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51705', + oauth: { + redirectUri: 'http://10.79.97.17:51705/callback', + dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51705 || 'https://10-79-97-1751705-xxxxxxxxx.ztna-dingtalk.com/callback' + } + }, + + // 潮州 + '51706': { + baseUrl: 'http://10.79.97.17:8003', + documentUrl: 'http://10.79.97.17:8003/docauditai/', + uploadUrl: 'http://10.79.97.17:8003/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51706', + oauth: { + redirectUri: 'http://10.79.97.17:51706/callback', + dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51706 || 'https://10-79-97-1751706-xxxxxxxxx.ztna-dingtalk.com/callback' + } + }, + + + // 省局 + '51707': { + // baseUrl: 'http://172.16.0.55:8866', + // documentUrl: 'http://172.16.0.55:8866/docauditai/', + // uploadUrl: 'http://172.16.0.55:8866/api/v2/documents', + // collaboraUrl: 'http://172.16.0.81:9980', + // appUrl: 'http://172.16.0.34:51707', + + // 正式环境 + baseUrl: 'http://10.79.97.17:8866', + documentUrl: 'http://10.79.97.17:8866/docauditai/', + uploadUrl: 'http://10.79.97.17:8866/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51707', + + oauth: { + redirectUri: 'http://10.79.97.17:51707/callback', + dingtalkRedirectUri: process.env.DINGTALK_REDIRECT_URI_51707 || 'https://10-79-97-1751707-xxxxxxxxx.ztna-dingtalk.com/callback' + } + }, + + //test + '51708': { + baseUrl: 'http://10.79.97.17:8005', + documentUrl: 'http://10.79.97.17:8005/docauditai/', + uploadUrl: 'http://10.79.97.17:8005/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51708' + }, +}; + +// 不同环境的默认配置 +// 由于合同模板的上传,后续的的uploadUrl都不需要/upload,直接写/api/v2/documents,由程序自动添加/upload或/upload_contract_template +const configs: Record = { + // 开发环境 development: { - // baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) - // documentUrl: 'http://172.16.0.59:8096/docauditai/', - // uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', + // baseUrl: 'http://172.16.0.59:8096', // FastAPI后端(包含/dify代理) + // documentUrl: 'http://172.16.0.59:8096/docauditai/', + // uploadUrl: 'http://172.16.0.59:8096/api/v2/documents', // leaudit-platform 联调地址 - baseUrl: 'http://172.15.0.59:8096', - documentUrl: 'http://172.15.0.59:8096/docauditai/', - uploadUrl: 'http://172.15.0.59:8096/api/upload', - // baseUrl: 'http://172.16.0.84:8073', // FastAPI后端(包含/dify代理) - // documentUrl: 'http://172.16.0.84:8073/docauditai/', - // uploadUrl: 'http://172.16.0.84:8073/api/v2/documents', - - collaboraUrl: 'http://172.16.0.58:9980', - // collaboraUrl: 'http://nas.7bm.co:9980', - appUrl: 'http://172.16.0.34:51703', - - oauth: { - serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 - clientId: 'none', - clientSecret: 'none', // 需要替换为实际的Client Secret - redirectUri: 'http://10.79.97.17/', // 回调地址 - appId: 'idaasoauth2' // 应用ID,用于登出 - }, - dify: { - rerankingProviderName: 'langgenius/tongyi/tongyi', - rerankingModelName: 'gte-rerank' - } - }, - - // 测试环境 - testing: { - baseUrl: 'http://172.16.0.55:8073', // FastAPI后端(包含/dify代理) - documentUrl: 'http://172.16.0.55:8073/docauditai/', - uploadUrl: 'http://172.16.0.55:8073/api/v2/documents', - collaboraUrl: 'http://172.16.0.81:9980', - // appUrl: 'http://10.79.97.17:51703', - appUrl: 'http://172.16.0.34:5183', - oauth: { - serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 - clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO', - clientSecret: 'placeholder', // 需要替换为实际的Client Secret - redirectUri: 'http://10.79.97.17/', // 回调地址 - appId: 'idaasoauth2' // 应用ID,用于登出 - }, - dify: { - rerankingProviderName: 'langgenius/tongyi/tongyi', - rerankingModelName: 'gte-rerank' - } - }, - - // 生产环境 - production: { - baseUrl: 'http://10.79.97.17:8000', // FastAPI后端(包含/dify代理) - // minio - documentUrl: 'http://10.76.244.156:9000/docauditai/', - // 文件上传 - uploadUrl: 'http://10.79.97.17:8000/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://10.79.97.17:51703', - oauth: { - clientId: '224266374b56ee6254ed3d339014b033kaZy92exUmy', - // serverUrl: 'http://10.79.112.85', // IDaaS服务器地址(测试) - serverUrl: 'http://10.79.97.252', // IDaaS服务器地址(生产) - // ⚠️ 安全警告:clientSecret 不应该硬编码在代码中 - // 请在生产环境使用环境变量 OAUTH_CLIENT_SECRET - clientSecret: 'placeholder', // 占位符,实际值从环境变量获取 - redirectUri: 'http://10.79.97.17/', // 回调地址 - appId: 'idaasoauth2' // 应用ID,用于登出 - }, - dify: { - // rerankingProviderName: 'langgenius/tongyi/tongyi', - rerankingProviderName: 'langgenius/xinference/xinference', - // rerankingModelName: 'gte-rerank' - rerankingModelName: 'bge-reranker-v2-m3' - } - }, - - // 备用配置 (可以根据需要添加更多环境) - staging: { - baseUrl: 'http://172.16.0.119:9000/admin', // FastAPI后端(包含/dify代理) - documentUrl: 'http://nas.7bm.co:9000/docauditai/', - uploadUrl: 'http://172.16.0.119:8000/api/v2/documents', - collaboraUrl: 'http://10.79.97.17:9980', - appUrl: 'http://172.16.0.119:3000', - oauth: { - serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 - clientId: 'none', // 需要替换为实际的Client ID - clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret - redirectUri: 'http://172.16.0.119:3000/callback', // 回调地址 - appId: 'idaasoauth2' // 应用ID,用于登出 - }, - dify: { - rerankingProviderName: 'langgenius/tongyi/tongyi', - rerankingModelName: 'gte-rerank' - } - } -}; - -// 获取当前环境,默认为development -const getCurrentEnvironment = (): string => { - // 在服务器端,优先使用PM2设置的环境变量 - if (typeof window === 'undefined') { - // 服务器端:直接使用process.env.NODE_ENV - const nodeEnv = process.env.NODE_ENV; - // console.log('🔧 服务器端环境检测:', { - // NODE_ENV: nodeEnv, - // result: nodeEnv || 'development' - // }); - return nodeEnv || 'development'; - } - - // 客户端:优先使用NEXT_PUBLIC_前缀的环境变量 - const nextPublicNodeEnv = process.env.NEXT_PUBLIC_NODE_ENV; - const nodeEnv = process.env.NODE_ENV; - const result = nextPublicNodeEnv || nodeEnv || 'development'; - - // console.log('🔧 客户端环境检测:', { - // NEXT_PUBLIC_NODE_ENV: nextPublicNodeEnv, - // NODE_ENV: nodeEnv, - // result: result - // }); - - return result; -}; - -// 从环境变量获取配置,如果环境变量不存在则使用默认配置 -const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => { - return { - baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || defaultConfig.baseUrl, - documentUrl: process.env.NEXT_PUBLIC_DOCUMENT_URL || defaultConfig.documentUrl, - uploadUrl: process.env.NEXT_PUBLIC_UPLOAD_URL || defaultConfig.uploadUrl, - - collaboraUrl: defaultConfig.collaboraUrl || '', - appUrl: defaultConfig.appUrl || '', - - oauth: { - serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || defaultConfig.oauth.serverUrl, - clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || defaultConfig.oauth.clientId, - // ⚠️ 注意:clientSecret 不应该使用 NEXT_PUBLIC_ 前缀 - // 应该只在服务器端通过 process.env.OAUTH_CLIENT_SECRET 访问 - clientSecret: process.env.OAUTH_CLIENT_SECRET || defaultConfig.oauth.clientSecret, - redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || defaultConfig.oauth.redirectUri, - appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || defaultConfig.oauth.appId - }, - dify: defaultConfig.dify - }; -}; - -/** - * 获取当前端口号 - * 优先从浏览器location获取,然后从环境变量获取 - */ -const getCurrentPort = (): string => { - // 在客户端,优先从浏览器location获取端口 - let windowPort = ''; - if (typeof window !== 'undefined') { - windowPort = window.location.port || ''; - } - - // 在服务器端,优先使用运行时端口检测 - if (typeof window === 'undefined') { - const runtimePort = getRuntimePort(); - if (runtimePort) { - // console.log('🔧 服务器端运行时端口检测:', runtimePort); - return runtimePort; - } - } - - // 优先使用环境变量中的端口配置 - const nextPublicApiPortConfig = process.env.NEXT_PUBLIC_API_PORT_CONFIG; - const nextPublicPort = process.env.NEXT_PUBLIC_PORT; - const apiPortConfig = process.env.API_PORT_CONFIG; - const portEnv = process.env.PORT; - - // 优先级:windowPort > NEXT_PUBLIC_API_PORT_CONFIG > NEXT_PUBLIC_PORT > API_PORT_CONFIG > PORT环境变量 - const result = windowPort || nextPublicApiPortConfig || nextPublicPort || apiPortConfig || portEnv || ''; - - // console.log('🔧 端口检测:', { - // windowPort: windowPort, - // NEXT_PUBLIC_API_PORT_CONFIG: nextPublicApiPortConfig, - // NEXT_PUBLIC_PORT: nextPublicPort, - // API_PORT_CONFIG: apiPortConfig, - // PORT: portEnv, - // result: result - // }); - - return result; -}; - -/** - * 运行时端口检测 - 从服务器启动参数或环境变量获取实际端口 - * 这个方法只在服务器端运行,用于动态获取实际运行端口 - */ -const getRuntimePort = (): string => { - if (typeof window !== 'undefined') { - return ''; // 客户端不执行此逻辑 - } - - // 尝试从进程参数中获取端口 - const args = process.argv; - for (let i = 0; i < args.length; i++) { - if (args[i] === '--port' && i + 1 < args.length) { - return args[i + 1]; - } - if (args[i].startsWith('--port=')) { - return args[i].split('=')[1]; - } - } - - // 从环境变量获取 - return process.env.PORT || ''; -}; - -/** - * 获取当前配置 - * 支持根据端口动态切换API配置 - */ -const getCurrentConfig = (): ApiConfig => { - const env = getCurrentEnvironment(); - const port = getCurrentPort(); - - // console.log('🔧 配置调试信息:', { - // environment: env, - // port: port, - // hasPortConfig: !!(port && portConfigs[port]), - // portConfig: port ? portConfigs[port] : null - // }); - - // 获取基础配置 - let defaultConfig = configs[env] || configs.development; - - // 如果有端口特定配置,则合并配置 - if (port && portConfigs[port]) { - console.log(`🔧 使用端口特定配置: ${port}`); - const portConfig = portConfigs[port]; - defaultConfig = { - ...defaultConfig, - ...portConfig, - // 如果端口配置中有oauth,需要深度合并oauth配置 - oauth: portConfig.oauth - ? { ...defaultConfig.oauth, ...portConfig.oauth } - : defaultConfig.oauth - }; - // console.log(`🔧 使用端口特定配置---深度合并后: ${JSON.stringify(defaultConfig.oauth)}`) - } else { - // console.log(`🔧 使用环境配置: ${env}`, defaultConfig); - } - - // 只有在明确设置了环境变量的情况下才覆盖配置 - const hasEnvOverrides = process.env.NEXT_PUBLIC_API_BASE_URL || - process.env.NEXT_PUBLIC_DOCUMENT_URL || - process.env.NEXT_PUBLIC_UPLOAD_URL; - - if (hasEnvOverrides) { - // console.log('🔧 检测到环境变量覆盖,使用环境变量配置'); - return getConfigFromEnv(defaultConfig); - } - - console.log('🔧 最终配置:', defaultConfig); - return defaultConfig; -}; - -// 导出当前环境的配置 -export const apiConfig = getCurrentConfig(); - -// 导出具体的配置项,方便使用 -export const { - baseUrl: API_BASE_URL, - documentUrl: DOCUMENT_URL, - uploadUrl: UPLOAD_URL, - collaboraUrl: COLLABORA_URL, - appUrl: APP_URL, - oauth: OAUTH_CONFIG, - dify: DIFY_CONFIG -} = apiConfig; - -/** - * 🔓 客户端安全的 OAuth 配置(不包含 clientSecret) - * 可以安全地在客户端代码中使用 - */ -export const CLIENT_OAUTH_CONFIG = { - serverUrl: OAUTH_CONFIG.serverUrl as string, - clientId: OAUTH_CONFIG.clientId as string, - redirectUri: OAUTH_CONFIG.redirectUri as string, - appId: OAUTH_CONFIG.appId as string, - // 客户端不需要 clientSecret -}; - -// 导出所有配置,供调试使用 -export { configs }; - -// 工具函数:设置环境(主要用于测试) -export const setEnvironment = (env: string): ApiConfig => { - return configs[env] || configs.development; -}; - -/** - * 工具函数:获取当前端口配置信息(用于调试) - */ -export const getCurrentPortConfig = () => { - const port = getCurrentPort(); - const env = getCurrentEnvironment(); - return { - currentPort: port, - currentEnvironment: env, - hasPortConfig: !!(port && portConfigs[port]), - portConfig: port ? portConfigs[port] : null - }; -}; - -// 调试信息(仅在开发环境显示) -if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'testing') { - console.log('📦 API配置信息:', { - environment: getCurrentEnvironment(), - currentEnv: process.env.NODE_ENV, - port: getCurrentPort(), - config: { - ...apiConfig, - oauth: { - ...apiConfig.oauth, - clientSecret: '***' // 隐藏敏感信息 - } - }, - }); -} - -/** - * 交叉评查专属模式配置 - * 当为 true 且端口为 51707 时,首页只显示交叉评查入口 - */ -export const CROSS_CHECKING_ONLY_MODE = process.env.CROSS_CHECKING_ONLY_MODE === 'true'; - -/** - * 交叉评查专属模式的目标端口 - */ -export const CROSS_CHECKING_ONLY_PORT = '51707'; - -/** - * 获取当前端口(服务端使用) - */ + baseUrl: 'http://172.16.0.59:8096', + documentUrl: 'http://172.16.0.59:8096/docauditai/', + uploadUrl: 'http://172.16.0.59:8096/api/upload', + // baseUrl: 'http://172.16.0.84:8073', // FastAPI后端(包含/dify代理) + // documentUrl: 'http://172.16.0.84:8073/docauditai/', + // uploadUrl: 'http://172.16.0.84:8073/api/v2/documents', + + collaboraUrl: 'http://172.16.0.58:9980', + // collaboraUrl: 'http://nas.7bm.co:9980', + appUrl: 'http://172.16.0.34:51703', + + oauth: { + serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 + clientId: 'none', + clientSecret: 'none', // 需要替换为实际的Client Secret + redirectUri: 'http://10.79.97.17/', // 回调地址 + appId: 'idaasoauth2' // 应用ID,用于登出 + }, + dify: { + rerankingProviderName: 'langgenius/tongyi/tongyi', + rerankingModelName: 'gte-rerank' + } + }, + + // 测试环境 + testing: { + baseUrl: 'http://172.16.0.55:8073', // FastAPI后端(包含/dify代理) + documentUrl: 'http://172.16.0.55:8073/docauditai/', + uploadUrl: 'http://172.16.0.55:8073/api/v2/documents', + collaboraUrl: 'http://172.16.0.81:9980', + // appUrl: 'http://10.79.97.17:51703', + appUrl: 'http://172.16.0.34:5183', + oauth: { + serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 + clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO', + clientSecret: 'placeholder', // 需要替换为实际的Client Secret + redirectUri: 'http://10.79.97.17/', // 回调地址 + appId: 'idaasoauth2' // 应用ID,用于登出 + }, + dify: { + rerankingProviderName: 'langgenius/tongyi/tongyi', + rerankingModelName: 'gte-rerank' + } + }, + + // 生产环境 + production: { + baseUrl: 'http://10.79.97.17:8000', // FastAPI后端(包含/dify代理) + // minio + documentUrl: 'http://10.76.244.156:9000/docauditai/', + // 文件上传 + uploadUrl: 'http://10.79.97.17:8000/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51703', + oauth: { + clientId: '224266374b56ee6254ed3d339014b033kaZy92exUmy', + // serverUrl: 'http://10.79.112.85', // IDaaS服务器地址(测试) + serverUrl: 'http://10.79.97.252', // IDaaS服务器地址(生产) + // ⚠️ 安全警告:clientSecret 不应该硬编码在代码中 + // 请在生产环境使用环境变量 OAUTH_CLIENT_SECRET + clientSecret: 'placeholder', // 占位符,实际值从环境变量获取 + redirectUri: 'http://10.79.97.17/', // 回调地址 + appId: 'idaasoauth2' // 应用ID,用于登出 + }, + dify: { + // rerankingProviderName: 'langgenius/tongyi/tongyi', + rerankingProviderName: 'langgenius/xinference/xinference', + // rerankingModelName: 'gte-rerank' + rerankingModelName: 'bge-reranker-v2-m3' + } + }, + + // 备用配置 (可以根据需要添加更多环境) + staging: { + baseUrl: 'http://172.16.0.119:9000/admin', // FastAPI后端(包含/dify代理) + documentUrl: 'http://nas.7bm.co:9000/docauditai/', + uploadUrl: 'http://172.16.0.119:8000/api/v2/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://172.16.0.119:3000', + oauth: { + serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 + clientId: 'none', // 需要替换为实际的Client ID + clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret + redirectUri: 'http://172.16.0.119:3000/callback', // 回调地址 + appId: 'idaasoauth2' // 应用ID,用于登出 + }, + dify: { + rerankingProviderName: 'langgenius/tongyi/tongyi', + rerankingModelName: 'gte-rerank' + } + } +}; + +// 获取当前环境,默认为development +const getCurrentEnvironment = (): string => { + // 在服务器端,优先使用PM2设置的环境变量 + if (typeof window === 'undefined') { + // 服务器端:直接使用process.env.NODE_ENV + const nodeEnv = process.env.NODE_ENV; + // console.log('🔧 服务器端环境检测:', { + // NODE_ENV: nodeEnv, + // result: nodeEnv || 'development' + // }); + return nodeEnv || 'development'; + } + + // 客户端:优先使用NEXT_PUBLIC_前缀的环境变量 + const nextPublicNodeEnv = process.env.NEXT_PUBLIC_NODE_ENV; + const nodeEnv = process.env.NODE_ENV; + const result = nextPublicNodeEnv || nodeEnv || 'development'; + + // console.log('🔧 客户端环境检测:', { + // NEXT_PUBLIC_NODE_ENV: nextPublicNodeEnv, + // NODE_ENV: nodeEnv, + // result: result + // }); + + return result; +}; + +// 从环境变量获取配置,如果环境变量不存在则使用默认配置 +const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => { + return { + baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || defaultConfig.baseUrl, + documentUrl: process.env.NEXT_PUBLIC_DOCUMENT_URL || defaultConfig.documentUrl, + uploadUrl: process.env.NEXT_PUBLIC_UPLOAD_URL || defaultConfig.uploadUrl, + + collaboraUrl: defaultConfig.collaboraUrl || '', + appUrl: defaultConfig.appUrl || '', + + oauth: { + serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || defaultConfig.oauth.serverUrl, + clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || defaultConfig.oauth.clientId, + // ⚠️ 注意:clientSecret 不应该使用 NEXT_PUBLIC_ 前缀 + // 应该只在服务器端通过 process.env.OAUTH_CLIENT_SECRET 访问 + clientSecret: process.env.OAUTH_CLIENT_SECRET || defaultConfig.oauth.clientSecret, + redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || defaultConfig.oauth.redirectUri, + appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || defaultConfig.oauth.appId + }, + dify: defaultConfig.dify + }; +}; + +/** + * 获取当前端口号 + * 优先从浏览器location获取,然后从环境变量获取 + */ +const getCurrentPort = (): string => { + // 在客户端,优先从浏览器location获取端口 + let windowPort = ''; + if (typeof window !== 'undefined') { + windowPort = window.location.port || ''; + } + + // 在服务器端,优先使用运行时端口检测 + if (typeof window === 'undefined') { + const runtimePort = getRuntimePort(); + if (runtimePort) { + // console.log('🔧 服务器端运行时端口检测:', runtimePort); + return runtimePort; + } + } + + // 优先使用环境变量中的端口配置 + const nextPublicApiPortConfig = process.env.NEXT_PUBLIC_API_PORT_CONFIG; + const nextPublicPort = process.env.NEXT_PUBLIC_PORT; + const apiPortConfig = process.env.API_PORT_CONFIG; + const portEnv = process.env.PORT; + + // 优先级:windowPort > NEXT_PUBLIC_API_PORT_CONFIG > NEXT_PUBLIC_PORT > API_PORT_CONFIG > PORT环境变量 + const result = windowPort || nextPublicApiPortConfig || nextPublicPort || apiPortConfig || portEnv || ''; + + // console.log('🔧 端口检测:', { + // windowPort: windowPort, + // NEXT_PUBLIC_API_PORT_CONFIG: nextPublicApiPortConfig, + // NEXT_PUBLIC_PORT: nextPublicPort, + // API_PORT_CONFIG: apiPortConfig, + // PORT: portEnv, + // result: result + // }); + + return result; +}; + +/** + * 运行时端口检测 - 从服务器启动参数或环境变量获取实际端口 + * 这个方法只在服务器端运行,用于动态获取实际运行端口 + */ +const getRuntimePort = (): string => { + if (typeof window !== 'undefined') { + return ''; // 客户端不执行此逻辑 + } + + // 尝试从进程参数中获取端口 + const args = process.argv; + for (let i = 0; i < args.length; i++) { + if (args[i] === '--port' && i + 1 < args.length) { + return args[i + 1]; + } + if (args[i].startsWith('--port=')) { + return args[i].split('=')[1]; + } + } + + // 从环境变量获取 + return process.env.PORT || ''; +}; + +/** + * 获取当前配置 + * 支持根据端口动态切换API配置 + */ +const getCurrentConfig = (): ApiConfig => { + const env = getCurrentEnvironment(); + const port = getCurrentPort(); + + // console.log('🔧 配置调试信息:', { + // environment: env, + // port: port, + // hasPortConfig: !!(port && portConfigs[port]), + // portConfig: port ? portConfigs[port] : null + // }); + + // 获取基础配置 + let defaultConfig = configs[env] || configs.development; + + // 如果有端口特定配置,则合并配置 + if (port && portConfigs[port]) { + console.log(`🔧 使用端口特定配置: ${port}`); + const portConfig = portConfigs[port]; + defaultConfig = { + ...defaultConfig, + ...portConfig, + // 如果端口配置中有oauth,需要深度合并oauth配置 + oauth: portConfig.oauth + ? { ...defaultConfig.oauth, ...portConfig.oauth } + : defaultConfig.oauth + }; + // console.log(`🔧 使用端口特定配置---深度合并后: ${JSON.stringify(defaultConfig.oauth)}`) + } else { + // console.log(`🔧 使用环境配置: ${env}`, defaultConfig); + } + + // 只有在明确设置了环境变量的情况下才覆盖配置 + const hasEnvOverrides = process.env.NEXT_PUBLIC_API_BASE_URL || + process.env.NEXT_PUBLIC_DOCUMENT_URL || + process.env.NEXT_PUBLIC_UPLOAD_URL; + + if (hasEnvOverrides) { + // console.log('🔧 检测到环境变量覆盖,使用环境变量配置'); + return getConfigFromEnv(defaultConfig); + } + + console.log('🔧 最终配置:', defaultConfig); + return defaultConfig; +}; + +// 导出当前环境的配置 +export const apiConfig = getCurrentConfig(); + +// 导出具体的配置项,方便使用 +export const { + baseUrl: API_BASE_URL, + documentUrl: DOCUMENT_URL, + uploadUrl: UPLOAD_URL, + collaboraUrl: COLLABORA_URL, + appUrl: APP_URL, + oauth: OAUTH_CONFIG, + dify: DIFY_CONFIG +} = apiConfig; + +/** + * 🔓 客户端安全的 OAuth 配置(不包含 clientSecret) + * 可以安全地在客户端代码中使用 + */ +export const CLIENT_OAUTH_CONFIG = { + serverUrl: OAUTH_CONFIG.serverUrl as string, + clientId: OAUTH_CONFIG.clientId as string, + redirectUri: OAUTH_CONFIG.redirectUri as string, + appId: OAUTH_CONFIG.appId as string, + // 客户端不需要 clientSecret +}; + +// 导出所有配置,供调试使用 +export { configs }; + +// 工具函数:设置环境(主要用于测试) +export const setEnvironment = (env: string): ApiConfig => { + return configs[env] || configs.development; +}; + +/** + * 工具函数:获取当前端口配置信息(用于调试) + */ +export const getCurrentPortConfig = () => { + const port = getCurrentPort(); + const env = getCurrentEnvironment(); + return { + currentPort: port, + currentEnvironment: env, + hasPortConfig: !!(port && portConfigs[port]), + portConfig: port ? portConfigs[port] : null + }; +}; + +// 调试信息(仅在开发环境显示) +if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'testing') { + console.log('📦 API配置信息:', { + environment: getCurrentEnvironment(), + currentEnv: process.env.NODE_ENV, + port: getCurrentPort(), + config: { + ...apiConfig, + oauth: { + ...apiConfig.oauth, + clientSecret: '***' // 隐藏敏感信息 + } + }, + }); +} + +/** + * 交叉评查专属模式配置 + * 当为 true 且端口为 51707 时,首页只显示交叉评查入口 + */ +export const CROSS_CHECKING_ONLY_MODE = process.env.CROSS_CHECKING_ONLY_MODE === 'true'; + +/** + * 交叉评查专属模式的目标端口 + */ +export const CROSS_CHECKING_ONLY_PORT = '51707'; + +/** + * 获取当前端口(服务端使用) + */ export { getCurrentPort }; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index c109476..2ba89f5 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react'; -import { useNavigate, Form, useLoaderData } from '@remix-run/react'; -import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; -import styles from "~/styles/pages/home.css?url"; -import dayjs from 'dayjs'; -import { getUserSession, logout } from "~/api/login/auth.server"; -import { toastService } from '~/components/ui'; -import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config'; +import { useNavigate, Form, useLoaderData } from '@remix-run/react'; +import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; +import styles from "~/styles/pages/home.css?url"; +import dayjs from 'dayjs'; +import type { EntryModule } from '~/api/home/home'; +import { getUserSession, logout } from "~/api/login/auth.server"; +import { toastService } from '~/components/ui'; +import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config'; export const links = () => [ { rel: "stylesheet", href: styles } @@ -37,18 +38,14 @@ export async function loader({ request }: LoaderFunctionArgs) { const { userRole, userInfo, frontendJWT } = await getUserSession(request); // 🔑 获取用户地区并查询入口模块 - const userArea = userInfo?.area || null; - // console.log('🔍 [Index Loader] 用户地区:', userArea); - // console.log('🔍 [Index Loader] 用户角色:', userRole); - - let entryModules = []; - if (userRole && frontendJWT) { - const { getEntryModules } = await import('~/api/home/home'); - entryModules = await getEntryModules(userRole,userArea, frontendJWT); - console.log(`📦 [Index Loader] 获取到 ${entryModules.length} 个入口模块`); - } else { - console.warn('⚠️ [Index Loader] 用户角色为空,返回空模块列表'); - } + let entryModules: EntryModule[] = []; + if (frontendJWT) { + const { getEntryModules } = await import('~/api/home/home'); + entryModules = await getEntryModules(frontendJWT); + console.log(`📦 [Index Loader] 获取到 ${entryModules.length} 个入口模块`); + } else { + console.warn('⚠️ [Index Loader] 缺少 JWT,返回空模块列表'); + } // 🔑 检查用户是否有系统设置权限 let hasSettingsAccess = false; @@ -200,18 +197,17 @@ export default function Index() { }, []); // 处理模块点击 - const handleModuleClick = (module: typeof loaderData.entryModules[0]) => { - // 提取文档类型 IDs - const typeIds = module.document_types?.map(dt => dt.id) || []; - - // 🔑 验证文档类型(智慧法务助手除外) - if (module.name !== '智慧法务助手' && typeIds.length === 0) { - toastService.error('该入口尚未关联文档类型,无法进入'); - console.warn('⚠️ [Index] 模块未关联文档类型:', module.name); - return; // 阻止进入 - } - - if (typeof window !== 'undefined') { + const handleModuleClick = (module: EntryModule) => { + // 提取文档类型 IDs + const typeIds = module.documentTypes.map((dt) => dt.id) || []; + + if (module.requiresDocumentTypes && typeIds.length === 0) { + toastService.error('该入口尚未关联文档类型,无法进入'); + console.warn('⚠️ [Index] 模块未关联文档类型:', module.name); + return; + } + + if (typeof window !== 'undefined') { // 🔑 清除各页面的筛选条件缓存(切换入口模块时重置) sessionStorage.removeItem('rules.searchParams'); @@ -225,54 +221,36 @@ export default function Index() { sessionStorage.removeItem('documentTypeIds'); } - // 存储模块信息 - sessionStorage.setItem('selectedModuleId', String(module.id)); - sessionStorage.setItem('selectedModuleName', module.name); - sessionStorage.setItem('selectedModulePicPath', module.path) - } - - // 🔑 根据模块名称决定跳转路径 - let targetPath = '/home'; // 默认跳转到首页 - - if (module.name.includes('合同')) { - // 合同相关模块 → 跳转到合同模板搜索 - targetPath = '/contract-template/search'; - // console.log('📌 [Index] 合同模块,跳转到:', targetPath); - } else if (module.name === '智慧法务助手') { - // 智慧法务助手 → 跳转到 AI 对话 - targetPath = '/chat-with-llm/chat'; - sessionStorage.setItem('selectedModulePicPath', '/images/icon_assistant.png') - // console.log('📌 [Index] 智慧法务助手,跳转到:', targetPath); - } else { - // console.log('📌 [Index] 其他模块,跳转到:', targetPath); - } - - navigate(targetPath); - }; + // 存储模块信息 + sessionStorage.setItem('selectedModuleId', String(module.id)); + sessionStorage.setItem('selectedModuleName', module.name); + sessionStorage.setItem('selectedModulePicPath', module.iconPath || '') + } + + const targetPath = module.targetPath || '/home'; + if (targetPath === '/chat-with-llm/chat' && typeof window !== 'undefined') { + sessionStorage.setItem('selectedModulePicPath', module.iconPath || '/images/icon_assistant.png'); + } + navigate(targetPath); + }; // 处理键盘事件 - const handleKeyDown = (module: typeof loaderData.entryModules[0], e: React.KeyboardEvent) => { + const handleKeyDown = (module: EntryModule, e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { handleModuleClick(module); } }; // 获取模块图标(根据模块 path 或 id) - const getModuleIcon = (module: typeof loaderData.entryModules[0]) => { - // 根据 path 判断图标 - // if (module.path?.includes('ht')) { - // return '/images/icon_hetong.png'; - // } else if (module.path?.includes('aj')) { - // return '/images/icon_anjuan.png'; - // } else if (module.path?.includes('nw')) { - // return '/images/icon_assistant.png'; - // } - // 默认图标 - if (module.path){ - return `${DOCUMENT_URL}${module.path}` - } - return '/images/icon_assistant.png'; - }; + const getModuleIcon = (module: EntryModule) => { + if (module.iconPath){ + if (module.iconPath.startsWith('/images/')) { + return module.iconPath; + } + return `${DOCUMENT_URL}${module.iconPath}`; + } + return '/images/icon_assistant.png'; + }; // 处理登出 const handleLogout = () => { @@ -439,15 +417,21 @@ export default function Index() { /* 正常模式:显示所有入口模块 */ loaderData.entryModules && loaderData.entryModules.length > 0 ? ( <> - {loaderData.entryModules.map((module) => { - const isLLMModule = module.name === '智慧法务助手'; - - // 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块 - if (isLLMModule && !loaderData.hasChatLLMAccess) { - return null; - } - - return ( + {loaderData.entryModules.map((module: EntryModule) => { + const isLLMModule = module.targetPath === '/chat-with-llm/chat'; + const isCrossCheckingModule = module.targetPath === '/cross-checking'; + + // 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块 + if (isLLMModule && !loaderData.hasChatLLMAccess) { + return null; + } + + // 交叉评查已在下面独立渲染,避免首页重复出现两张卡片 + if (isCrossCheckingModule) { + return null; + } + + return (
); -} \ No newline at end of file +} diff --git a/app/routes/callback.tsx b/app/routes/callback.tsx index 7aabaf8..aed00bf 100644 --- a/app/routes/callback.tsx +++ b/app/routes/callback.tsx @@ -212,19 +212,8 @@ export async function loader({ request }: LoaderFunctionArgs) { const frontendJWT = loginResponse.data.access_token; const savedUserInfo = loginResponse.data.user_info; const backExpiresIn = loginResponse.data.expires_in || (60 * 60 * 8) - - // 🔑 提取后端返回的签发时间并转换为时间戳 - let tokenIssuedAt = Date.now(); // 默认使用当前时间 - if (loginResponse.data.issued_time) { - try { - // 后端返回格式:"2025-11-18 17:41:06" - // 转换为时间戳(毫秒) - tokenIssuedAt = new Date(loginResponse.data.issued_time.replace(' ', 'T')).getTime(); - console.log("📅 [Callback] 使用后端返回的签发时间:", loginResponse.data.issued_time, "→", tokenIssuedAt); - } catch (error) { - console.warn("⚠️ [Callback] 无法解析 issued_time,使用当前时间:", error); - } - } + // 直接使用当前服务端时间作为 session 签发时间,避免后端返回的本地时间字符串被 Node 以不同时区解析 + const tokenIssuedAt = Date.now(); // 更新userInfo以包含数据库ID、JWT(user_role 从后端返回) const enhancedUserInfo = { diff --git a/app/routes/login.tsx b/app/routes/login.tsx index 3decc6d..d525db8 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -103,10 +103,11 @@ export async function action({ request }: ActionFunctionArgs) { const response = await loginWithPassword(username.trim(), password.trim()); if (!response.success || !response.data) { - console.error("❌ [Login Action] 登录失败:", response.error); + const loginError = response.error || response.message || "登录失败,请检查用户名和密码"; + console.error("❌ [Login Action] 登录失败:", loginError); return Response.json({ success: false, - error: response.error || "登录失败,请检查用户名和密码" + error: loginError }, { status: 401 }); } @@ -133,25 +134,14 @@ export async function action({ request }: ActionFunctionArgs) { // if(!user_info.area){ // user_info.area = '梅州' // } - - // 🔑 将后端返回的 issued_time 转换为时间戳(毫秒) - let tokenIssuedAt = Date.now(); // 默认使用当前时间 - if (issued_time) { - try { - // 后端返回格式:"2025-11-18 17:41:06" - // 转换为时间戳(毫秒) - tokenIssuedAt = new Date(issued_time.replace(' ', 'T')).getTime(); - console.log("📅 [Login Action] 使用后端返回的签发时间:", issued_time, "→", tokenIssuedAt); - } catch (error) { - console.warn("⚠️ [Login Action] 无法解析 issued_time,使用当前时间:", error); - } - } + // 直接使用当前服务端时间作为 session 签发时间,避免后端返回的本地时间字符串被 Node 以不同时区解析 + const tokenIssuedAt = Date.now(); console.log("✅ [Login Action] 登录成功,准备创建 session"); - // console.log("📦 [Login Action] 后端返回完整数据:", response.data); console.log("👤 [Login Action] 用户角色:", user_info.user_role); // 应该是 "admin" console.log("⏰ [Login Action] Token 有效期:", expires_in, "秒 (", expires_in / 3600, "小时)"); + // ✅ 账密登录直接写入 Cookie Session 并跳首页 // localStorage 由 root 中的客户端会话引导逻辑补写,避免 callback 跳转链路卡住 return createUserSession({