fix: align login and home routing with leaudit backend
This commit is contained in:
@@ -664,7 +664,7 @@ export async function getUserRoutesByRole(
|
|||||||
// 注意:Authorization 头会由 axios 拦截器自动添加(从 localStorage 读取)
|
// 注意:Authorization 头会由 axios 拦截器自动添加(从 localStorage 读取)
|
||||||
// 但为了确保使用正确的 token,这里仍然显式传递
|
// 但为了确保使用正确的 token,这里仍然显式传递
|
||||||
const response = await apiRequest<BackendRoutesResponse>(
|
const response = await apiRequest<BackendRoutesResponse>(
|
||||||
'/rbac/user/routes', // endpoint (第一个参数)
|
'/api/rbac/user/routes', // endpoint (第一个参数)
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -678,6 +678,12 @@ export async function getUserRoutesByRole(
|
|||||||
// 检查响应是否成功
|
// 检查响应是否成功
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error('❌ [User Routes] API 请求失败:', 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('令牌已过期') ||
|
const isTokenExpired = response.error.includes('令牌已过期') ||
|
||||||
response.error.includes('令牌') ||
|
response.error.includes('令牌') ||
|
||||||
@@ -702,6 +708,10 @@ export async function getUserRoutesByRole(
|
|||||||
// 检查响应数据
|
// 检查响应数据
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
console.error('❌ [User Routes] 后端未返回数据');
|
console.error('❌ [User Routes] 后端未返回数据');
|
||||||
|
if (response.status === 404) {
|
||||||
|
console.warn('⚠️ [User Routes] 后端路由权限接口未落地,回退到静态菜单');
|
||||||
|
return buildFallbackRoutes(roleKey);
|
||||||
|
}
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
toastService.error("获取路由数据失败");
|
toastService.error("获取路由数据失败");
|
||||||
}
|
}
|
||||||
@@ -998,6 +1008,7 @@ export function mapUserRoleToRoleKey(userRole: string): string {
|
|||||||
const roleMapping: Record<string, string> = {
|
const roleMapping: Record<string, string> = {
|
||||||
'common': 'common',
|
'common': 'common',
|
||||||
'admin': 'admin',
|
'admin': 'admin',
|
||||||
|
'provincial_admin': 'admin',
|
||||||
'deptLeader': 'deptLeader',
|
'deptLeader': 'deptLeader',
|
||||||
'groupLeader': 'groupLeader',
|
'groupLeader': 'groupLeader',
|
||||||
// 添加常见的后端角色映射
|
// 添加常见的后端角色映射
|
||||||
@@ -1009,4 +1020,23 @@ export function mapUserRoleToRoleKey(userRole: string): string {
|
|||||||
|
|
||||||
// 如果找不到映射,返回 userRole 本身(假设后端已经返回了正确的 role_key)
|
// 如果找不到映射,返回 userRole 本身(假设后端已经返回了正确的 role_key)
|
||||||
return roleMapping[userRole] || userRole || 'common';
|
return roleMapping[userRole] || userRole || 'common';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于静态菜单数据构造后备结果。
|
||||||
|
*/
|
||||||
|
function buildFallbackRoutes(roleKey: string): {
|
||||||
|
success: boolean;
|
||||||
|
data: MenuItem[];
|
||||||
|
permissionMap: Record<string, string[]>;
|
||||||
|
} {
|
||||||
|
const mappedRoleKey = mapUserRoleToRoleKey(roleKey);
|
||||||
|
const fallbackMenus = FALLBACK_MENU_DATA[mappedRoleKey] || FALLBACK_MENU_DATA.common;
|
||||||
|
const permissionMap: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: fallbackMenus,
|
||||||
|
permissionMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+23
-104
@@ -1,4 +1,3 @@
|
|||||||
import { postgrestGet, type PostgrestParams } from "../postgrest-client";
|
|
||||||
import { apiRequest } from "../axios-client";
|
import { apiRequest } from "../axios-client";
|
||||||
// import dayjs from 'dayjs';
|
// import dayjs from 'dayjs';
|
||||||
|
|
||||||
@@ -202,7 +201,7 @@ export async function getHomeData(reviewType?: string | null, userId?: string |
|
|||||||
export interface AreaConfig {
|
export interface AreaConfig {
|
||||||
area: string; // 地区名称
|
area: string; // 地区名称
|
||||||
enabled: boolean; // 是否启用
|
enabled: boolean; // 是否启用
|
||||||
sort_order: number; // 排序顺序
|
sortOrder: number; // 排序顺序
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -212,11 +211,13 @@ export interface EntryModule {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
path: string | null;
|
targetPath: string | null;
|
||||||
areas: AreaConfig[]; // 修改为对象数组
|
routePath: string | null;
|
||||||
created_at: string;
|
iconPath: string | null;
|
||||||
updated_at: string;
|
sortOrder: number;
|
||||||
document_types?: Array<{
|
requiresDocumentTypes: boolean;
|
||||||
|
areas: AreaConfig[];
|
||||||
|
documentTypes: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
code: string | null;
|
code: string | null;
|
||||||
@@ -225,119 +226,38 @@ export interface EntryModule {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户可访问的入口模块
|
* 获取用户可访问的入口模块
|
||||||
* @param userArea 用户所属地区
|
|
||||||
* @param token JWT token
|
* @param token JWT token
|
||||||
* @returns 入口模块列表
|
* @returns 入口模块列表
|
||||||
*/
|
*/
|
||||||
export async function getEntryModules(userRole: string | null | undefined, userArea: string | null | undefined, token?: string): Promise<EntryModule[]> {
|
export async function getEntryModules(token?: string): Promise<EntryModule[]> {
|
||||||
try {
|
try {
|
||||||
if (!userRole || !userArea) {
|
if (!token) {
|
||||||
console.warn('⚠️ [getEntryModules] 用户角色或地区为空,返回空模块列表');
|
console.warn('⚠️ [getEntryModules] JWT 为空,返回空模块列表');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('🔍 [getEntryModules] 查询地区:', userArea);
|
const modulesResponse = await apiRequest<EntryModule[]>(
|
||||||
|
'/api/home/entry-modules',
|
||||||
// 查询 entry_modules 表,获取所有模块(在客户端进行过滤)
|
{
|
||||||
const params: PostgrestParams = {
|
method: 'GET',
|
||||||
select: 'id,name,description,path,areas,created_at,updated_at',
|
headers: {
|
||||||
filter: {}
|
'Authorization': `Bearer ${token}`
|
||||||
};
|
}
|
||||||
|
}
|
||||||
const modulesResponse = await postgrestGet('/api/postgrest/proxy/entry_modules', { ...params, token });
|
);
|
||||||
|
|
||||||
if (modulesResponse.error) {
|
if (modulesResponse.error) {
|
||||||
console.error('❌ [getEntryModules] 查询入口模块失败:', modulesResponse.error);
|
console.error('❌ [getEntryModules] 查询入口模块失败:', modulesResponse.error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const allModules = extractApiData<EntryModule[]>(modulesResponse.data);
|
const modules = extractApiData<EntryModule[]>(modulesResponse.data);
|
||||||
if (!allModules || allModules.length === 0) {
|
if (!modules || modules.length === 0) {
|
||||||
console.warn('⚠️ [getEntryModules] 未找到任何入口模块');
|
console.warn('⚠️ [getEntryModules] 未找到任何入口模块');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔑 在客户端过滤:只保留包含用户地区且已启用的模块
|
return modules;
|
||||||
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<Array<{ id: number; name: string; code: string | null }>>(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;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [getEntryModules] 获取入口模块失败:', error instanceof Error ? error.message : String(error));
|
console.error('❌ [getEntryModules] 获取入口模块失败:', error instanceof Error ? error.message : String(error));
|
||||||
return [];
|
return [];
|
||||||
@@ -525,4 +445,3 @@ export async function getTopRiskUsers(
|
|||||||
return { available: false, total: 0, items: [] };
|
return { available: false, total: 0, items: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export interface LoginResponse {
|
|||||||
* @returns 登录响应(包含 JWT token)
|
* @returns 登录响应(包含 JWT token)
|
||||||
*/
|
*/
|
||||||
export async function loginWithOAuth(loginData: LoginRequest): Promise<LoginResponse> {
|
export async function loginWithOAuth(loginData: LoginRequest): Promise<LoginResponse> {
|
||||||
const loginUrl = `${API_BASE_URL}/auth/login`;
|
const loginUrl = `${API_BASE_URL}/api/auth/login`;
|
||||||
|
|
||||||
console.log("📝 [Login Client] 调用后端 OAuth 登录接口:", loginUrl);
|
console.log("📝 [Login Client] 调用后端 OAuth 登录接口:", loginUrl);
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export async function loginWithPassword(
|
|||||||
username: string,
|
username: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<LoginResponse> {
|
): Promise<LoginResponse> {
|
||||||
const loginUrl = `${API_BASE_URL}/auth/login`;
|
const loginUrl = `${API_BASE_URL}/api/auth/login`;
|
||||||
|
|
||||||
console.log("📝 [Login Client] 调用后端密码登录接口:", loginUrl);
|
console.log("📝 [Login Client] 调用后端密码登录接口:", loginUrl);
|
||||||
|
|
||||||
|
|||||||
+506
-506
File diff suppressed because it is too large
Load Diff
+65
-81
@@ -1,11 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate, Form, useLoaderData } from '@remix-run/react';
|
import { useNavigate, Form, useLoaderData } from '@remix-run/react';
|
||||||
import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
|
import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
|
||||||
import styles from "~/styles/pages/home.css?url";
|
import styles from "~/styles/pages/home.css?url";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getUserSession, logout } from "~/api/login/auth.server";
|
import type { EntryModule } from '~/api/home/home';
|
||||||
import { toastService } from '~/components/ui';
|
import { getUserSession, logout } from "~/api/login/auth.server";
|
||||||
import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config';
|
import { toastService } from '~/components/ui';
|
||||||
|
import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config';
|
||||||
|
|
||||||
export const links = () => [
|
export const links = () => [
|
||||||
{ rel: "stylesheet", href: styles }
|
{ rel: "stylesheet", href: styles }
|
||||||
@@ -37,18 +38,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const { userRole, userInfo, frontendJWT } = await getUserSession(request);
|
const { userRole, userInfo, frontendJWT } = await getUserSession(request);
|
||||||
|
|
||||||
// 🔑 获取用户地区并查询入口模块
|
// 🔑 获取用户地区并查询入口模块
|
||||||
const userArea = userInfo?.area || null;
|
let entryModules: EntryModule[] = [];
|
||||||
// console.log('🔍 [Index Loader] 用户地区:', userArea);
|
if (frontendJWT) {
|
||||||
// console.log('🔍 [Index Loader] 用户角色:', userRole);
|
const { getEntryModules } = await import('~/api/home/home');
|
||||||
|
entryModules = await getEntryModules(frontendJWT);
|
||||||
let entryModules = [];
|
console.log(`📦 [Index Loader] 获取到 ${entryModules.length} 个入口模块`);
|
||||||
if (userRole && frontendJWT) {
|
} else {
|
||||||
const { getEntryModules } = await import('~/api/home/home');
|
console.warn('⚠️ [Index Loader] 缺少 JWT,返回空模块列表');
|
||||||
entryModules = await getEntryModules(userRole,userArea, frontendJWT);
|
}
|
||||||
console.log(`📦 [Index Loader] 获取到 ${entryModules.length} 个入口模块`);
|
|
||||||
} else {
|
|
||||||
console.warn('⚠️ [Index Loader] 用户角色为空,返回空模块列表');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔑 检查用户是否有系统设置权限
|
// 🔑 检查用户是否有系统设置权限
|
||||||
let hasSettingsAccess = false;
|
let hasSettingsAccess = false;
|
||||||
@@ -200,18 +197,17 @@ export default function Index() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 处理模块点击
|
// 处理模块点击
|
||||||
const handleModuleClick = (module: typeof loaderData.entryModules[0]) => {
|
const handleModuleClick = (module: EntryModule) => {
|
||||||
// 提取文档类型 IDs
|
// 提取文档类型 IDs
|
||||||
const typeIds = module.document_types?.map(dt => dt.id) || [];
|
const typeIds = module.documentTypes.map((dt) => dt.id) || [];
|
||||||
|
|
||||||
// 🔑 验证文档类型(智慧法务助手除外)
|
if (module.requiresDocumentTypes && typeIds.length === 0) {
|
||||||
if (module.name !== '智慧法务助手' && typeIds.length === 0) {
|
toastService.error('该入口尚未关联文档类型,无法进入');
|
||||||
toastService.error('该入口尚未关联文档类型,无法进入');
|
console.warn('⚠️ [Index] 模块未关联文档类型:', module.name);
|
||||||
console.warn('⚠️ [Index] 模块未关联文档类型:', module.name);
|
return;
|
||||||
return; // 阻止进入
|
}
|
||||||
}
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
// 🔑 清除各页面的筛选条件缓存(切换入口模块时重置)
|
// 🔑 清除各页面的筛选条件缓存(切换入口模块时重置)
|
||||||
sessionStorage.removeItem('rules.searchParams');
|
sessionStorage.removeItem('rules.searchParams');
|
||||||
|
|
||||||
@@ -225,54 +221,36 @@ export default function Index() {
|
|||||||
sessionStorage.removeItem('documentTypeIds');
|
sessionStorage.removeItem('documentTypeIds');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储模块信息
|
// 存储模块信息
|
||||||
sessionStorage.setItem('selectedModuleId', String(module.id));
|
sessionStorage.setItem('selectedModuleId', String(module.id));
|
||||||
sessionStorage.setItem('selectedModuleName', module.name);
|
sessionStorage.setItem('selectedModuleName', module.name);
|
||||||
sessionStorage.setItem('selectedModulePicPath', module.path)
|
sessionStorage.setItem('selectedModulePicPath', module.iconPath || '')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔑 根据模块名称决定跳转路径
|
const targetPath = module.targetPath || '/home';
|
||||||
let targetPath = '/home'; // 默认跳转到首页
|
if (targetPath === '/chat-with-llm/chat' && typeof window !== 'undefined') {
|
||||||
|
sessionStorage.setItem('selectedModulePicPath', module.iconPath || '/images/icon_assistant.png');
|
||||||
if (module.name.includes('合同')) {
|
}
|
||||||
// 合同相关模块 → 跳转到合同模板搜索
|
navigate(targetPath);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理键盘事件
|
// 处理键盘事件
|
||||||
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 === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
handleModuleClick(module);
|
handleModuleClick(module);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取模块图标(根据模块 path 或 id)
|
// 获取模块图标(根据模块 path 或 id)
|
||||||
const getModuleIcon = (module: typeof loaderData.entryModules[0]) => {
|
const getModuleIcon = (module: EntryModule) => {
|
||||||
// 根据 path 判断图标
|
if (module.iconPath){
|
||||||
// if (module.path?.includes('ht')) {
|
if (module.iconPath.startsWith('/images/')) {
|
||||||
// return '/images/icon_hetong.png';
|
return module.iconPath;
|
||||||
// } else if (module.path?.includes('aj')) {
|
}
|
||||||
// return '/images/icon_anjuan.png';
|
return `${DOCUMENT_URL}${module.iconPath}`;
|
||||||
// } else if (module.path?.includes('nw')) {
|
}
|
||||||
// return '/images/icon_assistant.png';
|
return '/images/icon_assistant.png';
|
||||||
// }
|
};
|
||||||
// 默认图标
|
|
||||||
if (module.path){
|
|
||||||
return `${DOCUMENT_URL}${module.path}`
|
|
||||||
}
|
|
||||||
return '/images/icon_assistant.png';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理登出
|
// 处理登出
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
@@ -439,15 +417,21 @@ export default function Index() {
|
|||||||
/* 正常模式:显示所有入口模块 */
|
/* 正常模式:显示所有入口模块 */
|
||||||
loaderData.entryModules && loaderData.entryModules.length > 0 ? (
|
loaderData.entryModules && loaderData.entryModules.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{loaderData.entryModules.map((module) => {
|
{loaderData.entryModules.map((module: EntryModule) => {
|
||||||
const isLLMModule = module.name === '智慧法务助手';
|
const isLLMModule = module.targetPath === '/chat-with-llm/chat';
|
||||||
|
const isCrossCheckingModule = module.targetPath === '/cross-checking';
|
||||||
// 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块
|
|
||||||
if (isLLMModule && !loaderData.hasChatLLMAccess) {
|
// 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块
|
||||||
return null;
|
if (isLLMModule && !loaderData.hasChatLLMAccess) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
return (
|
|
||||||
|
// 交叉评查已在下面独立渲染,避免首页重复出现两张卡片
|
||||||
|
if (isCrossCheckingModule) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={module.id}
|
key={module.id}
|
||||||
className="module-card"
|
className="module-card"
|
||||||
@@ -512,4 +496,4 @@ export default function Index() {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-13
@@ -212,19 +212,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const frontendJWT = loginResponse.data.access_token;
|
const frontendJWT = loginResponse.data.access_token;
|
||||||
const savedUserInfo = loginResponse.data.user_info;
|
const savedUserInfo = loginResponse.data.user_info;
|
||||||
const backExpiresIn = loginResponse.data.expires_in || (60 * 60 * 8)
|
const backExpiresIn = loginResponse.data.expires_in || (60 * 60 * 8)
|
||||||
|
// 直接使用当前服务端时间作为 session 签发时间,避免后端返回的本地时间字符串被 Node 以不同时区解析
|
||||||
// 🔑 提取后端返回的签发时间并转换为时间戳
|
const tokenIssuedAt = Date.now();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新userInfo以包含数据库ID、JWT(user_role 从后端返回)
|
// 更新userInfo以包含数据库ID、JWT(user_role 从后端返回)
|
||||||
const enhancedUserInfo = {
|
const enhancedUserInfo = {
|
||||||
|
|||||||
+6
-16
@@ -103,10 +103,11 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
const response = await loginWithPassword(username.trim(), password.trim());
|
const response = await loginWithPassword(username.trim(), password.trim());
|
||||||
|
|
||||||
if (!response.success || !response.data) {
|
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({
|
return Response.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: response.error || "登录失败,请检查用户名和密码"
|
error: loginError
|
||||||
}, { status: 401 });
|
}, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,25 +134,14 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
// if(!user_info.area){
|
// if(!user_info.area){
|
||||||
// user_info.area = '梅州'
|
// user_info.area = '梅州'
|
||||||
// }
|
// }
|
||||||
|
// 直接使用当前服务端时间作为 session 签发时间,避免后端返回的本地时间字符串被 Node 以不同时区解析
|
||||||
// 🔑 将后端返回的 issued_time 转换为时间戳(毫秒)
|
const tokenIssuedAt = Date.now();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("✅ [Login Action] 登录成功,准备创建 session");
|
console.log("✅ [Login Action] 登录成功,准备创建 session");
|
||||||
// console.log("📦 [Login Action] 后端返回完整数据:", response.data);
|
|
||||||
console.log("👤 [Login Action] 用户角色:", user_info.user_role); // 应该是 "admin"
|
console.log("👤 [Login Action] 用户角色:", user_info.user_role); // 应该是 "admin"
|
||||||
console.log("⏰ [Login Action] Token 有效期:", expires_in, "秒 (", expires_in / 3600, "小时)");
|
console.log("⏰ [Login Action] Token 有效期:", expires_in, "秒 (", expires_in / 3600, "小时)");
|
||||||
|
|
||||||
|
|
||||||
// ✅ 账密登录直接写入 Cookie Session 并跳首页
|
// ✅ 账密登录直接写入 Cookie Session 并跳首页
|
||||||
// localStorage 由 root 中的客户端会话引导逻辑补写,避免 callback 跳转链路卡住
|
// localStorage 由 root 中的客户端会话引导逻辑补写,避免 callback 跳转链路卡住
|
||||||
return createUserSession({
|
return createUserSession({
|
||||||
|
|||||||
Reference in New Issue
Block a user