Merge branch 'PingChuan' into shiy-login

# Conflicts:
#	app/config/api-config.ts
fix: 1. 修复无法加载数据的问题:没有从入口页中进来会缺少数据。
2. 加强后端接口关于token的校验错误和权限校验错误的管理。

feat: 1. 对接后端的数据看板的接口。
2. 将系统设置单独抽出来作为管理员的固定一个入口。
This commit is contained in:
2025-11-22 15:57:22 +08:00
27 changed files with 1972 additions and 643 deletions
+85 -25
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useNavigate, Form, useLoaderData } from '@remix-run/react';
import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs, redirect } from "@remix-run/node";
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";
@@ -49,8 +49,21 @@ export async function loader({ request }: LoaderFunctionArgs) {
console.warn('⚠️ [Index Loader] 用户角色为空,返回空模块列表');
}
// 返回用户信息和入口模块给客户端
return Response.json({ userRole, userInfo, entryModules });
// 🔑 检查用户是否有系统设置权限
let hasSettingsAccess = false;
if (userRole && frontendJWT) {
const { getUserRoutesByRole } = await import('~/api/auth/user-routes');
const routesResult = await getUserRoutesByRole(userRole, frontendJWT, true); // includeHidden=true
if (routesResult.success && routesResult.data) {
// 检查是否存在顶级路由 '/settings'
hasSettingsAccess = routesResult.data.some(route => route.path === '/settings');
// console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`);
}
}
// 返回用户信息、入口模块和系统设置权限给客户端
return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess });
}
export default function Index() {
@@ -62,7 +75,7 @@ export default function Index() {
});
// 检查是否通过51707端口访问
const [isPort51707, setIsPort51707] = useState(false);
// const [isPort51707, setIsPort51707] = useState(false);
// 用户信息:优先使用服务端返回的,否则从 localStorage 读取
const [userInfo, setUserInfo] = useState(loaderData.userInfo);
@@ -70,7 +83,7 @@ export default function Index() {
useEffect(() => {
if (typeof window !== 'undefined') {
setIsPort51707(window.location.port === '51707');
// setIsPort51707(window.location.port === '51707');
// 如果服务端没有返回用户信息,从 localStorage 读取
if (!loaderData.userInfo || !loaderData.userRole) {
@@ -91,10 +104,21 @@ export default function Index() {
// 打印用户角色
useEffect(() => {
console.log('📋 [Index] 当前用户角色:', userRole);
console.log('👤 [Index] 当前用户信息:', userInfo);
// console.log('📋 [Index] 当前用户角色:', userRole);
// console.log('👤 [Index] 当前用户信息:', userInfo);
}, [userRole, userInfo]);
// 🔑 清除系统设置模式标志(当用户返回首页时)
useEffect(() => {
if (typeof window !== 'undefined') {
const settingsMode = sessionStorage.getItem('settingsMode');
if (settingsMode === 'true') {
sessionStorage.removeItem('settingsMode');
console.log('🔄 [Index] 清除系统设置模式标志');
}
}
}, []); // 只在组件挂载时执行一次
// 更新日期时间
useEffect(() => {
const updateDateTime = () => {
@@ -200,6 +224,21 @@ export default function Index() {
}
};
// 处理进入系统设置
const handleEnterSettings = () => {
if (typeof window !== 'undefined') {
// 🔑 设置标志:表示用户通过系统设置入口进入
sessionStorage.setItem('settingsMode', 'true');
// 清除模块相关的标志(因为不是从入口模块进入)
sessionStorage.removeItem('selectedModuleId');
sessionStorage.removeItem('selectedModuleName');
sessionStorage.removeItem('selectedModulePicPath');
}
// 跳转到系统设置的默认页面
navigate('/rule-groups');
};
return (
<div className="home-page">
{/* 登出表单 - 隐藏 */}
@@ -250,24 +289,45 @@ export default function Index() {
<div className="modules-container">
{/* 动态渲染入口模块 */}
{loaderData.entryModules && loaderData.entryModules.length > 0 ? (
loaderData.entryModules.map((module) => (
<div
key={module.id}
className="module-card"
onClick={() => handleModuleClick(module)}
onKeyDown={(e) => handleKeyDown(module, e)}
role="button"
tabIndex={0}
aria-label={module.name}
>
<img
src={getModuleIcon(module)}
alt={module.name}
className="w-12 h-12 mx-1"
/>
<span className="module-name">{module.name}</span>
</div>
))
<>
{loaderData.entryModules.map((module) => (
<div
key={module.id}
className="module-card"
onClick={() => handleModuleClick(module)}
onKeyDown={(e) => handleKeyDown(module, e)}
role="button"
tabIndex={0}
aria-label={module.name}
>
<img
src={getModuleIcon(module)}
alt={module.name}
className="w-12 h-12 mx-1"
/>
<span className="module-name">{module.name}</span>
</div>
))}
{/* 🔑 系统设置入口 - 只有有权限的用户才能看到 */}
{loaderData.hasSettingsAccess && (
<div
className="module-card"
onClick={handleEnterSettings}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleEnterSettings();
}
}}
role="button"
tabIndex={0}
aria-label="系统设置"
>
<i className="ri-settings-4-line text-5xl text-primary"></i>
<span className="module-name"></span>
</div>
)}
</>
) : (
<div className="text-center text-gray-500 py-8">