mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-20 03:18:37 +00:00
顶栏和侧边栏管理
增加用户体验
This commit is contained in:
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import React, { useRef, useEffect, useState, useContext } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
@@ -28,11 +28,17 @@ import {
|
||||
Toast,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Switch,
|
||||
Row,
|
||||
Col,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconMail, IconKey, IconBell, IconLink } from '@douyinfe/semi-icons';
|
||||
import { ShieldCheck, Bell, DollarSign } from 'lucide-react';
|
||||
import { renderQuotaWithPrompt } from '../../../../helpers';
|
||||
import { ShieldCheck, Bell, DollarSign, Settings } from 'lucide-react';
|
||||
import { renderQuotaWithPrompt, API, showSuccess, showError } from '../../../../helpers';
|
||||
import CodeViewer from '../../../playground/CodeViewer';
|
||||
import { StatusContext } from '../../../../context/Status';
|
||||
import { UserContext } from '../../../../context/User';
|
||||
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
|
||||
|
||||
const NotificationSettings = ({
|
||||
t,
|
||||
@@ -41,6 +47,128 @@ const NotificationSettings = ({
|
||||
saveNotificationSettings,
|
||||
}) => {
|
||||
const formApiRef = useRef(null);
|
||||
const [statusState] = useContext(StatusContext);
|
||||
const [userState] = useContext(UserContext);
|
||||
|
||||
// 左侧边栏设置相关状态
|
||||
const [sidebarLoading, setSidebarLoading] = useState(false);
|
||||
const [activeTabKey, setActiveTabKey] = useState('notification');
|
||||
const [sidebarModulesUser, setSidebarModulesUser] = useState({
|
||||
chat: {
|
||||
enabled: true,
|
||||
playground: true,
|
||||
chat: true
|
||||
},
|
||||
console: {
|
||||
enabled: true,
|
||||
detail: true,
|
||||
token: true,
|
||||
log: true,
|
||||
midjourney: true,
|
||||
task: true
|
||||
},
|
||||
personal: {
|
||||
enabled: true,
|
||||
topup: true,
|
||||
personal: true
|
||||
},
|
||||
admin: {
|
||||
enabled: true,
|
||||
channel: true,
|
||||
models: true,
|
||||
redemption: true,
|
||||
user: true,
|
||||
setting: true
|
||||
}
|
||||
});
|
||||
const [adminConfig, setAdminConfig] = useState(null);
|
||||
|
||||
// 使用后端权限验证替代前端角色判断
|
||||
const {
|
||||
permissions,
|
||||
loading: permissionsLoading,
|
||||
hasSidebarSettingsPermission,
|
||||
isSidebarSectionAllowed,
|
||||
isSidebarModuleAllowed,
|
||||
} = useUserPermissions();
|
||||
|
||||
// 左侧边栏设置处理函数
|
||||
const handleSectionChange = (sectionKey) => {
|
||||
return (checked) => {
|
||||
const newModules = {
|
||||
...sidebarModulesUser,
|
||||
[sectionKey]: {
|
||||
...sidebarModulesUser[sectionKey],
|
||||
enabled: checked
|
||||
}
|
||||
};
|
||||
setSidebarModulesUser(newModules);
|
||||
};
|
||||
};
|
||||
|
||||
const handleModuleChange = (sectionKey, moduleKey) => {
|
||||
return (checked) => {
|
||||
const newModules = {
|
||||
...sidebarModulesUser,
|
||||
[sectionKey]: {
|
||||
...sidebarModulesUser[sectionKey],
|
||||
[moduleKey]: checked
|
||||
}
|
||||
};
|
||||
setSidebarModulesUser(newModules);
|
||||
};
|
||||
};
|
||||
|
||||
const saveSidebarSettings = async () => {
|
||||
setSidebarLoading(true);
|
||||
try {
|
||||
const res = await API.put('/api/user/self', {
|
||||
sidebar_modules: JSON.stringify(sidebarModulesUser)
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('侧边栏设置保存成功'));
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('保存失败'));
|
||||
}
|
||||
setSidebarLoading(false);
|
||||
};
|
||||
|
||||
const resetSidebarModules = () => {
|
||||
const defaultConfig = {
|
||||
chat: { enabled: true, playground: true, chat: true },
|
||||
console: { enabled: true, detail: true, token: true, log: true, midjourney: true, task: true },
|
||||
personal: { enabled: true, topup: true, personal: true },
|
||||
admin: { enabled: true, channel: true, models: true, redemption: true, user: true, setting: true }
|
||||
};
|
||||
setSidebarModulesUser(defaultConfig);
|
||||
};
|
||||
|
||||
// 加载左侧边栏配置
|
||||
useEffect(() => {
|
||||
const loadSidebarConfigs = async () => {
|
||||
try {
|
||||
// 获取管理员全局配置
|
||||
if (statusState?.status?.SidebarModulesAdmin) {
|
||||
const adminConf = JSON.parse(statusState.status.SidebarModulesAdmin);
|
||||
setAdminConfig(adminConf);
|
||||
}
|
||||
|
||||
// 获取用户个人配置
|
||||
const userRes = await API.get('/api/user/self');
|
||||
if (userRes.data.success && userRes.data.data.sidebar_modules) {
|
||||
const userConf = JSON.parse(userRes.data.data.sidebar_modules);
|
||||
setSidebarModulesUser(userConf);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载边栏配置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadSidebarConfigs();
|
||||
}, [statusState]);
|
||||
|
||||
// 初始化表单值
|
||||
useEffect(() => {
|
||||
@@ -54,6 +182,75 @@ const NotificationSettings = ({
|
||||
handleNotificationSettingChange(field, value);
|
||||
};
|
||||
|
||||
// 检查功能是否被管理员允许
|
||||
const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
|
||||
if (!adminConfig) return true;
|
||||
|
||||
if (moduleKey) {
|
||||
return adminConfig[sectionKey]?.enabled && adminConfig[sectionKey]?.[moduleKey];
|
||||
} else {
|
||||
return adminConfig[sectionKey]?.enabled;
|
||||
}
|
||||
};
|
||||
|
||||
// 区域配置数据(根据权限过滤)
|
||||
const sectionConfigs = [
|
||||
{
|
||||
key: 'chat',
|
||||
title: t('聊天区域'),
|
||||
description: t('操练场和聊天功能'),
|
||||
modules: [
|
||||
{ key: 'playground', title: t('操练场'), description: t('AI模型测试环境') },
|
||||
{ key: 'chat', title: t('聊天'), description: t('聊天会话管理') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: t('控制台区域'),
|
||||
description: t('数据管理和日志查看'),
|
||||
modules: [
|
||||
{ key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
|
||||
{ key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
|
||||
{ key: 'log', title: t('使用日志'), description: t('API使用记录') },
|
||||
{ key: 'midjourney', title: t('绘图日志'), description: t('绘图任务记录') },
|
||||
{ key: 'task', title: t('任务日志'), description: t('系统任务记录') }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'personal',
|
||||
title: t('个人中心区域'),
|
||||
description: t('用户个人功能'),
|
||||
modules: [
|
||||
{ key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
|
||||
{ key: 'personal', title: t('个人设置'), description: t('个人信息设置') }
|
||||
]
|
||||
},
|
||||
// 管理员区域:根据后端权限控制显示
|
||||
{
|
||||
key: 'admin',
|
||||
title: t('管理员区域'),
|
||||
description: t('系统管理功能'),
|
||||
modules: [
|
||||
{ key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
|
||||
{ key: 'models', title: t('模型管理'), description: t('AI模型配置') },
|
||||
{ key: 'redemption', title: t('兑换码管理'), description: t('兑换码生成管理') },
|
||||
{ key: 'user', title: t('用户管理'), description: t('用户账户管理') },
|
||||
{ key: 'setting', title: t('系统设置'), description: t('系统参数配置') }
|
||||
]
|
||||
}
|
||||
].filter(section => {
|
||||
// 使用后端权限验证替代前端角色判断
|
||||
return isSidebarSectionAllowed(section.key);
|
||||
}).map(section => ({
|
||||
...section,
|
||||
modules: section.modules.filter(module =>
|
||||
isSidebarModuleAllowed(section.key, module.key)
|
||||
)
|
||||
})).filter(section =>
|
||||
// 过滤掉没有可用模块的区域
|
||||
section.modules.length > 0 && isAllowedByAdmin(section.key)
|
||||
);
|
||||
|
||||
// 表单提交
|
||||
const handleSubmit = () => {
|
||||
if (formApiRef.current) {
|
||||
@@ -75,10 +272,32 @@ const NotificationSettings = ({
|
||||
<Card
|
||||
className='!rounded-2xl shadow-sm border-0'
|
||||
footer={
|
||||
<div className='flex justify-end'>
|
||||
<Button type='primary' onClick={handleSubmit}>
|
||||
{t('保存设置')}
|
||||
</Button>
|
||||
<div className='flex justify-end gap-3'>
|
||||
{activeTabKey === 'sidebar' ? (
|
||||
// 边栏设置标签页的按钮
|
||||
<>
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={resetSidebarModules}
|
||||
className='!rounded-lg'
|
||||
>
|
||||
{t('重置为默认')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={saveSidebarSettings}
|
||||
loading={sidebarLoading}
|
||||
className='!rounded-lg'
|
||||
>
|
||||
{t('保存边栏设置')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
// 其他标签页的通用保存按钮
|
||||
<Button type='primary' onClick={handleSubmit}>
|
||||
{t('保存设置')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -103,7 +322,11 @@ const NotificationSettings = ({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{() => (
|
||||
<Tabs type='card' defaultActiveKey='notification'>
|
||||
<Tabs
|
||||
type='card'
|
||||
defaultActiveKey='notification'
|
||||
onChange={(key) => setActiveTabKey(key)}
|
||||
>
|
||||
{/* 通知配置 Tab */}
|
||||
<TabPane
|
||||
tab={
|
||||
@@ -312,6 +535,127 @@ const NotificationSettings = ({
|
||||
/>
|
||||
</div>
|
||||
</TabPane>
|
||||
|
||||
{/* 左侧边栏设置 Tab - 根据后端权限控制显示 */}
|
||||
{hasSidebarSettingsPermission() && (
|
||||
<TabPane
|
||||
tab={
|
||||
<div className='flex items-center'>
|
||||
<Settings size={16} className='mr-2' />
|
||||
{t('边栏设置')}
|
||||
</div>
|
||||
}
|
||||
itemKey='sidebar'
|
||||
>
|
||||
<div className='py-4'>
|
||||
<div className='mb-4'>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.5',
|
||||
color: 'var(--semi-color-text-2)'
|
||||
}}
|
||||
>
|
||||
{t('您可以个性化设置侧边栏的要显示功能')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{/* 边栏设置功能区域容器 */}
|
||||
<div
|
||||
className='border rounded-xl p-4'
|
||||
style={{
|
||||
borderColor: 'var(--semi-color-border)',
|
||||
backgroundColor: 'var(--semi-color-bg-1)'
|
||||
}}
|
||||
>
|
||||
|
||||
{sectionConfigs.map((section) => (
|
||||
<div key={section.key} className='mb-6'>
|
||||
{/* 区域标题和总开关 */}
|
||||
<div
|
||||
className='flex justify-between items-center mb-4 p-4 rounded-lg'
|
||||
style={{
|
||||
backgroundColor: 'var(--semi-color-fill-0)',
|
||||
border: '1px solid var(--semi-color-border-light)',
|
||||
borderColor: 'var(--semi-color-fill-1)'
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className='font-semibold text-base text-gray-900 mb-1'>
|
||||
{section.title}
|
||||
</div>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.5',
|
||||
color: 'var(--semi-color-text-2)'
|
||||
}}
|
||||
>
|
||||
{section.description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Switch
|
||||
checked={sidebarModulesUser[section.key]?.enabled}
|
||||
onChange={handleSectionChange(section.key)}
|
||||
size="default"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 功能模块网格 */}
|
||||
<Row gutter={[12, 12]}>
|
||||
{section.modules
|
||||
.filter(module => isAllowedByAdmin(section.key, module.key))
|
||||
.map((module) => (
|
||||
<Col key={module.key} xs={24} sm={24} md={12} lg={8} xl={8}>
|
||||
<Card
|
||||
className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${
|
||||
sidebarModulesUser[section.key]?.enabled ? '' : 'opacity-50'
|
||||
}`}
|
||||
bodyStyle={{ padding: '16px' }}
|
||||
hoverable
|
||||
>
|
||||
<div className='flex justify-between items-center h-full'>
|
||||
<div className='flex-1 text-left'>
|
||||
<div className='font-semibold text-sm text-gray-900 mb-1'>
|
||||
{module.title}
|
||||
</div>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
className='block'
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.5',
|
||||
color: 'var(--semi-color-text-2)',
|
||||
marginTop: '4px'
|
||||
}}
|
||||
>
|
||||
{module.description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className='ml-4'>
|
||||
<Switch
|
||||
checked={sidebarModulesUser[section.key]?.[module.key]}
|
||||
onChange={handleModuleChange(section.key, module.key)}
|
||||
size="default"
|
||||
disabled={!sidebarModulesUser[section.key]?.enabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
</div> {/* 关闭边栏设置功能区域容器 */}
|
||||
</div>
|
||||
</TabPane>
|
||||
)}
|
||||
</Tabs>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user