TuringToken/web/src/hooks/common/useSidebar.js

339 lines
9.9 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 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 { useState, useEffect, useMemo, useContext, useRef } from 'react';
import { StatusContext } from '../../context/Status';
import { UserContext } from '../../context/User';
import { API, mergeSelfResponseIntoLocalUser } from '../../helpers';
// 创建一个全局事件系统来同步所有useSidebar实例
const sidebarEventTarget = new EventTarget();
const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh';
// 默认配置(当没有任何角色配置或用户配置时的兜底)
export const DEFAULT_SIDEBAR_CONFIG = {
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
benchmarks: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
'supplierApply': true,
},
personal: {
enabled: true,
topup: true,
personal: true,
supplier: true,
distributor_center: true,
'supplier-apply': true,
'supplier-channel': true,
'supplier-pricing-settings': true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
'model-heat': true,
redemption: true,
user: true,
'operation-log': true,
subscription: true,
setting: true,
distributor: true,
'supplier-management': true,
'supplier-application-approval': true,
'supplier-dashboard': true,
},
};
const deepClone = (value) => JSON.parse(JSON.stringify(value));
// 将savedConfig合并到DEFAULT_SIDEBAR_CONFIG确保新增模块有默认值
export const mergeWithDefaults = (savedConfig) => {
const merged = deepClone(DEFAULT_SIDEBAR_CONFIG);
if (!savedConfig || typeof savedConfig !== 'object') return merged;
for (const [sectionKey, sectionConfig] of Object.entries(savedConfig)) {
if (!sectionConfig || typeof sectionConfig !== 'object') continue;
if (!merged[sectionKey]) {
merged[sectionKey] = { ...sectionConfig };
continue;
}
merged[sectionKey] = { ...merged[sectionKey], ...sectionConfig };
}
return merged;
};
export const useSidebar = () => {
const [statusState] = useContext(StatusContext);
const [userState, userDispatch] = useContext(UserContext);
const [userConfig, setUserConfig] = useState(null);
const [loading, setLoading] = useState(true);
const instanceIdRef = useRef(null);
const hasLoadedOnceRef = useRef(false);
if (!instanceIdRef.current) {
const randomPart = Math.random().toString(16).slice(2);
instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`;
}
// 获取角色级模块配置
const roleModulesConfig = useMemo(() => {
if (statusState?.status?.SidebarModulesByRole) {
try {
return JSON.parse(statusState.status.SidebarModulesByRole);
} catch (error) {
return {};
}
}
return {};
}, [statusState?.status?.SidebarModulesByRole]);
// 获取当前用户角色
const userRole = userState?.user?.role ?? null;
// 基于角色获取基础配置替代原来的adminConfig
const roleBaseConfig = useMemo(() => {
if (userRole != null && roleModulesConfig) {
const roleStr = String(userRole);
const config = roleModulesConfig[roleStr];
if (config && typeof config === 'object') {
return mergeWithDefaults(config);
}
}
return mergeWithDefaults(null);
}, [userRole, roleModulesConfig]);
// 加载用户配置的通用方法
const loadUserConfig = async ({ withLoading } = {}) => {
const shouldShowLoader =
typeof withLoading === 'boolean'
? withLoading
: !hasLoadedOnceRef.current;
try {
if (shouldShowLoader) {
setLoading(true);
}
const res = await API.get('/api/user/self');
if (res.data.success && res.data.data) {
const payload = res.data.data;
if (payload.id != null) {
mergeSelfResponseIntoLocalUser(payload, userDispatch);
}
if (payload.sidebar_modules) {
let config;
if (typeof payload.sidebar_modules === 'string') {
config = JSON.parse(payload.sidebar_modules);
} else {
config = payload.sidebar_modules;
}
setUserConfig(config);
} else {
// 当用户没有配置时,基于角色配置生成默认值
const defaultUserConfig = {};
Object.keys(roleBaseConfig).forEach((sectionKey) => {
if (roleBaseConfig[sectionKey]?.enabled) {
defaultUserConfig[sectionKey] = { enabled: true };
Object.keys(roleBaseConfig[sectionKey]).forEach((moduleKey) => {
if (
moduleKey !== 'enabled' &&
roleBaseConfig[sectionKey][moduleKey]
) {
defaultUserConfig[sectionKey][moduleKey] = true;
}
});
}
});
setUserConfig(defaultUserConfig);
}
} else {
const defaultUserConfig = generateDefaultFromRoleConfig(roleBaseConfig);
setUserConfig(defaultUserConfig);
}
} catch (error) {
const defaultUserConfig = generateDefaultFromRoleConfig(roleBaseConfig);
setUserConfig(defaultUserConfig);
} finally {
if (shouldShowLoader) {
setLoading(false);
}
hasLoadedOnceRef.current = true;
}
};
// 刷新用户配置的方法(供外部调用)
const refreshUserConfig = async () => {
if (Object.keys(roleBaseConfig).length > 0) {
await loadUserConfig({ withLoading: false });
}
sidebarEventTarget.dispatchEvent(
new CustomEvent(SIDEBAR_REFRESH_EVENT, {
detail: { sourceId: instanceIdRef.current, skipLoader: true },
}),
);
};
// 加载用户配置
useEffect(() => {
if (Object.keys(roleBaseConfig).length > 0) {
loadUserConfig();
}
}, [roleBaseConfig]);
// 监听全局刷新事件
useEffect(() => {
const handleRefresh = (event) => {
if (event?.detail?.sourceId === instanceIdRef.current) {
return;
}
if (Object.keys(roleBaseConfig).length > 0) {
loadUserConfig({
withLoading: event?.detail?.skipLoader ? false : undefined,
});
}
};
sidebarEventTarget.addEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
return () => {
sidebarEventTarget.removeEventListener(
SIDEBAR_REFRESH_EVENT,
handleRefresh,
);
};
}, [roleBaseConfig]);
// 计算最终的显示配置:角色配置(基础) + 用户个人配置(覆盖)
const finalConfig = useMemo(() => {
const result = {};
if (!roleBaseConfig || Object.keys(roleBaseConfig).length === 0) {
return result;
}
if (!userConfig) {
return result;
}
// 遍历所有区域
Object.keys(roleBaseConfig).forEach((sectionKey) => {
const roleSection = roleBaseConfig[sectionKey];
const userSection = userConfig[sectionKey];
// 如果角色配置禁用了整个区域,则该区域不显示
if (!roleSection?.enabled) {
result[sectionKey] = { enabled: false };
return;
}
// 区域级别:用户可以选择隐藏角色允许的区域
const sectionEnabled = userSection ? userSection.enabled !== false : true;
result[sectionKey] = { enabled: sectionEnabled };
// 功能级别:只有角色和用户都允许的功能才显示
Object.keys(roleSection).forEach((moduleKey) => {
if (moduleKey === 'enabled') return;
const roleAllowed = roleSection[moduleKey];
const userAllowed = userSection
? userSection[moduleKey] !== false
: true;
result[sectionKey][moduleKey] =
roleAllowed && userAllowed && sectionEnabled;
});
});
return result;
}, [roleBaseConfig, userConfig]);
// 检查特定功能是否应该显示
const isModuleVisible = (sectionKey, moduleKey = null) => {
if (moduleKey) {
return finalConfig[sectionKey]?.[moduleKey] === true;
} else {
return finalConfig[sectionKey]?.enabled === true;
}
};
// 检查区域是否有任何可见的功能
const hasSectionVisibleModules = (sectionKey) => {
const section = finalConfig[sectionKey];
if (!section?.enabled) return false;
return Object.keys(section).some(
(key) => key !== 'enabled' && section[key] === true,
);
};
// 获取区域的可见功能列表
const getVisibleModules = (sectionKey) => {
const section = finalConfig[sectionKey];
if (!section?.enabled) return [];
return Object.keys(section).filter(
(key) => key !== 'enabled' && section[key] === true,
);
};
return {
loading,
roleBaseConfig,
userConfig,
finalConfig,
isModuleVisible,
hasSectionVisibleModules,
getVisibleModules,
refreshUserConfig,
};
};
// 辅助函数:基于角色配置生成默认用户配置
function generateDefaultFromRoleConfig(roleConfig) {
const defaultUserConfig = {};
Object.keys(roleConfig).forEach((sectionKey) => {
if (roleConfig[sectionKey]?.enabled) {
defaultUserConfig[sectionKey] = { enabled: true };
Object.keys(roleConfig[sectionKey]).forEach((moduleKey) => {
if (moduleKey !== 'enabled' && roleConfig[sectionKey][moduleKey]) {
defaultUserConfig[sectionKey][moduleKey] = true;
}
});
}
});
return defaultUserConfig;
}