fix: align login and home routing with leaudit backend
This commit is contained in:
+65
-81
@@ -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<HTMLDivElement>) => {
|
||||
const handleKeyDown = (module: EntryModule, e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
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 (
|
||||
<div
|
||||
key={module.id}
|
||||
className="module-card"
|
||||
@@ -512,4 +496,4 @@ export default function Index() {
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-13
@@ -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 = {
|
||||
|
||||
+6
-16
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user