Files
new-api/web/src/components/auth/ModuleRoute.jsx
2025-09-19 16:24:31 +08:00

201 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useState, useEffect, useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { StatusContext } from '../../context/Status';
import Loading from '../common/ui/Loading';
import { API } from '../../helpers';
/**
* ModuleRoute - 基于功能模块权限的路由保护组件
*
* @param {Object} props
* @param {React.ReactNode} props.children - 要保护的子组件
* @param {string} props.modulePath - 模块权限路径,如 "admin.channel", "console.token"
* @param {React.ReactNode} props.fallback - 无权限时显示的组件,默认跳转到 /forbidden
* @returns {React.ReactNode}
*/
const ModuleRoute = ({ children, modulePath, fallback = <Navigate to="/forbidden" replace /> }) => {
const [hasPermission, setHasPermission] = useState(null);
const [statusState] = useContext(StatusContext);
useEffect(() => {
checkModulePermission();
}, [modulePath, statusState?.status]); // 只在status数据变化时重新检查
const checkModulePermission = async () => {
try {
// 检查用户是否已登录
const user = localStorage.getItem('user');
if (!user) {
setHasPermission(false);
return;
}
const userData = JSON.parse(user);
const userRole = userData.role;
// 超级管理员始终有权限
if (userRole >= 100) {
setHasPermission(true);
return;
}
// 检查模块权限
const permission = await checkModulePermissionAPI(modulePath);
// 如果返回null表示status数据还未加载完成保持loading状态
if (permission === null) {
setHasPermission(null);
return;
}
setHasPermission(permission);
} catch (error) {
console.error('检查模块权限失败:', error);
// 出错时采用安全优先策略,拒绝访问
setHasPermission(false);
}
};
const checkModulePermissionAPI = async (modulePath) => {
try {
// 数据看板始终允许访问,不受控制台区域开关影响
if (modulePath === 'console.detail') {
return true;
}
// 从StatusContext中获取配置信息
// 如果status数据还未加载完成返回null表示需要等待
if (!statusState?.status) {
return null;
}
const user = JSON.parse(localStorage.getItem('user'));
const userRole = user.role;
// 解析模块路径
const pathParts = modulePath.split('.');
if (pathParts.length < 2) {
return false;
}
// 普通用户权限检查
if (userRole < 10) {
return await isUserModuleAllowed(modulePath);
}
// 超级管理员权限检查 - 不受系统配置限制
if (userRole >= 100) {
return true;
}
// 管理员权限检查 - 受系统配置限制
if (userRole >= 10 && userRole < 100) {
// 从/api/user/self获取系统权限配置
try {
const userRes = await API.get('/api/user/self');
if (userRes.data.success && userRes.data.data.sidebar_config) {
const sidebarConfigData = userRes.data.data.sidebar_config;
// 管理员权限检查基于系统配置,不受用户偏好影响
const systemConfig = sidebarConfigData.system || sidebarConfigData;
return checkModulePermissionInConfig(systemConfig, modulePath);
} else {
// 没有配置时,除了系统设置外都允许访问
return modulePath !== 'admin.setting';
}
} catch (error) {
console.error('获取侧边栏配置失败:', error);
return false;
}
}
return false;
} catch (error) {
console.error('API权限检查失败:', error);
return false;
}
};
const isUserModuleAllowed = async (modulePath) => {
// 数据看板始终允许访问,不受控制台区域开关影响
if (modulePath === 'console.detail') {
return true;
}
// 普通用户的权限基于最终计算的配置
try {
const userRes = await API.get('/api/user/self');
if (userRes.data.success && userRes.data.data.sidebar_config) {
const sidebarConfigData = userRes.data.data.sidebar_config;
// 使用最终计算的配置进行权限检查
const finalConfig = sidebarConfigData.final || sidebarConfigData;
return checkModulePermissionInConfig(finalConfig, modulePath);
}
return false;
} catch (error) {
console.error('获取用户权限配置失败:', error);
return false;
}
};
// 检查新的sidebar_config结构中的模块权限
const checkModulePermissionInConfig = (sidebarConfig, modulePath) => {
const parts = modulePath.split('.');
if (parts.length !== 2) {
return false;
}
const [sectionKey, moduleKey] = parts;
const section = sidebarConfig[sectionKey];
// 检查区域是否存在且启用
if (!section || !section.enabled) {
return false;
}
// 检查模块是否启用
const moduleValue = section[moduleKey];
// 处理布尔值和嵌套对象两种情况
if (typeof moduleValue === 'boolean') {
return moduleValue === true;
} else if (typeof moduleValue === 'object' && moduleValue !== null) {
// 对于嵌套对象检查其enabled状态
return moduleValue.enabled === true;
}
return false;
};
// 权限检查中
if (hasPermission === null) {
return <Loading />;
}
// 无权限
if (!hasPermission) {
return fallback;
}
// 有权限,渲染子组件
return children;
};
export default ModuleRoute;