fix:1. 优化角色权限管理的加载中样式。

2. root添加限制51707端口的路由访问,只允许访问交叉评查。
3. 开启省局端口限制的配置。
This commit is contained in:
2025-12-15 22:25:05 +08:00
parent 0aa75c6ffb
commit d2346aad70
3 changed files with 84 additions and 64 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ JWT_SECRET=gdyc-super-secrets-jjwtt-key-change-this-in-production-20250721-from-
# 交叉评查专属模式 # 交叉评查专属模式
# 设置为 true 时,端口51707只显示交叉评查入口,隐藏其他模块 # 设置为 true 时,端口51707只显示交叉评查入口,隐藏其他模块
# 设置为 false 时,保持正常模式显示所有模块 # 设置为 false 时,保持正常模式显示所有模块
CROSS_CHECKING_ONLY_MODE=false CROSS_CHECKING_ONLY_MODE=true
+22 -15
View File
@@ -34,12 +34,19 @@ import RouteChangeLoader from "~/components/ui/RouteChangeLoader";
// 导入认证相关的服务器端功能(仅在服务器端使用) // 导入认证相关的服务器端功能(仅在服务器端使用)
import { import {
// getUserSession, // getUserSession,
logout, logout,
type UserRole type UserRole
} from "~/api/login/auth.server"; } from "~/api/login/auth.server";
// 导入交叉评查专属模式配置
import {
CROSS_CHECKING_ONLY_MODE,
CROSS_CHECKING_ONLY_PORT,
getCurrentPort
} from "~/config/api-config";
// 定义需要高级权限的路径 // 定义需要高级权限的路径
// export const developerOnlyPaths = [ // export const developerOnlyPaths = [
// '/settings', // '/settings',
@@ -289,20 +296,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 如果执行到这里,说明已通过认证或是公共路径 // 如果执行到这里,说明已通过认证或是公共路径
} }
// 检查51707端口访问控制 // 检查交叉评查专属模式访问控制
const currentPort = process.env.PORT || process.env.API_PORT_CONFIG; // 当 CROSS_CHECKING_ONLY_MODE=true 且端口为指定端口时,只允许访问 /cross-checking 相关路由
const runtimePort = url.port || currentPort; const currentPort = getCurrentPort();
const isPort51707 = currentPort === '51707' || runtimePort === '51707'; const isCrossCheckingOnlyMode = CROSS_CHECKING_ONLY_MODE && currentPort === CROSS_CHECKING_ONLY_PORT;
if (isPort51707 && !isPublicPath) { if (isCrossCheckingOnlyMode && !isPublicPath) {
// 51707端口(省局)只允许访问交叉评查相关路径和首页 // 交叉评查专属模式:只允许访问首页和交叉评查相关路径
const allowedPaths = ['/', '/cross-checking','/chat-with-llm']; const crossCheckingAllowedPaths = ['/', '/cross-checking'];
const isAllowedPath = allowedPaths.some(path => pathname === path) || const isCrossCheckingAllowedPath = crossCheckingAllowedPaths.some(path => pathname === path) ||
pathname.startsWith('/cross-checking/') || pathname.startsWith('/cross-checking/');
pathname.startsWith('/chat-with-llm/');
if (!isAllowedPath) { if (!isCrossCheckingAllowedPath) {
return redirect("/cross-checking"); console.warn(`⚠️ [Root Loader] 交叉评查专属模式:拒绝访问 ${pathname}`);
throw new Response("交叉评查专属模式下无权访问此页面", { status: 403 });
} }
} }
+61 -48
View File
@@ -868,6 +868,9 @@ export default function RolePermissions() {
// v3.8: 加载角色权限的 loading 状态 // v3.8: 加载角色权限的 loading 状态
const [loadingPermissions, setLoadingPermissions] = useState(false); const [loadingPermissions, setLoadingPermissions] = useState(false);
// 加载用户列表的 loading 状态
const [loadingUsers, setLoadingUsers] = useState(false);
// v3.8: 路由ID到路由信息的映射(用于显示通用权限关联的路由名称) // v3.8: 路由ID到路由信息的映射(用于显示通用权限关联的路由名称)
const [routeIdToInfoMap, setRouteIdToInfoMap] = useState<Map<number, { title: string; path: string }>>(new Map()); const [routeIdToInfoMap, setRouteIdToInfoMap] = useState<Map<number, { title: string; path: string }>>(new Map());
@@ -981,6 +984,7 @@ export default function RolePermissions() {
const handleSelectRole = async (role: RoleInfo) => { const handleSelectRole = async (role: RoleInfo) => {
setSelectedRole(role); setSelectedRole(role);
setLoadingPermissions(true); // v3.8: 开始加载权限 setLoadingPermissions(true); // v3.8: 开始加载权限
setLoadingUsers(true); // 开始加载用户列表
try { try {
// 动态导入权限映射工具 // 动态导入权限映射工具
@@ -1069,6 +1073,7 @@ export default function RolePermissions() {
toastService.error('加载角色权限失败'); toastService.error('加载角色权限失败');
} finally { } finally {
setLoadingPermissions(false); // v3.8: 结束加载权限 setLoadingPermissions(false); // v3.8: 结束加载权限
setLoadingUsers(false); // 结束加载用户列表
} }
}; };
@@ -1933,59 +1938,67 @@ export default function RolePermissions() {
</Button> </Button>
</div> </div>
<div className="users-list"> {/* 加载状态显示 */}
{roleUsers.length > 0 ? ( {loadingUsers ? (
roleUsers.map(user => ( <div className="loading-container" style={{ minHeight: '300px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '12px' }}>
<div key={user.id} className="user-card"> <i className="ri-loader-4-line spin" style={{ fontSize: '32px', color: '#00684a' }}></i>
<div className="user-avatar"> <span style={{ color: '#666' }}>...</span>
<i className="ri-user-line"></i> </div>
</div> ) : (
<div className="user-info"> <div className="users-list">
<div className="user-name"> {roleUsers.length > 0 ? (
{user.nick_name} roleUsers.map(user => (
{user.is_leader && ( <div key={user.id} className="user-card">
<span className="leader-badge"></span> <div className="user-avatar">
)} <i className="ri-user-line"></i>
</div> </div>
<div className="user-username">@{user.username}</div> <div className="user-info">
<div className="user-org"> <div className="user-name">
{/* {JSON.stringify(user)} */} {user.nick_name}
{user.ou_name} {user.is_leader && (
{user.area && <span style={{ marginLeft: '8px', color: '#666' }}> {user.area}</span>} <span className="leader-badge"></span>
)}
</div>
<div className="user-username">@{user.username}</div>
<div className="user-org">
{/* {JSON.stringify(user)} */}
{user.ou_name}
{user.area && <span style={{ marginLeft: '8px', color: '#666' }}> {user.area}</span>}
</div>
<div className="user-contact">
{user.phone_number && (
<span>
<i className="ri-phone-line"></i>
{user.phone_number}
</span>
)}
{user.email && (
<span>
<i className="ri-mail-line"></i>
{user.email}
</span>
)}
</div>
</div> </div>
<div className="user-contact"> <div className="user-actions">
{user.phone_number && ( <button
<span> className="btn-icon text-error"
<i className="ri-phone-line"></i> onClick={() => handleRemoveUserRole(user)}
{user.phone_number} title="移除角色"
</span> >
)} <i className="ri-user-unfollow-line"></i>
{user.email && ( </button>
<span>
<i className="ri-mail-line"></i>
{user.email}
</span>
)}
</div> </div>
</div> </div>
<div className="user-actions"> ))
<button ) : (
className="btn-icon text-error" <div className="empty-state">
onClick={() => handleRemoveUserRole(user)} <i className="ri-user-line"></i>
title="移除角色" <p></p>
>
<i className="ri-user-unfollow-line"></i>
</button>
</div>
</div> </div>
)) )}
) : ( </div>
<div className="empty-state"> )}
<i className="ri-user-line"></i>
<p></p>
</div>
)}
</div>
</div> </div>
)} )}
</div> </div>