顶栏和侧边栏管理

增加用户体验
This commit is contained in:
F。
2025-08-31 07:07:40 +08:00
parent 6a87808612
commit 8809c44443
19 changed files with 2428 additions and 170 deletions

View File

@@ -0,0 +1,326 @@
/*
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, { useEffect, useState, useContext } from 'react';
import { Button, Card, Col, Form, Row, Switch, Typography } from '@douyinfe/semi-ui';
import { API, showError, showSuccess } from '../../../helpers';
import { useTranslation } from 'react-i18next';
import { StatusContext } from '../../../context/Status';
const { Text } = Typography;
export default function SettingsHeaderNavModules(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [statusState, statusDispatch] = useContext(StatusContext);
// 顶栏模块管理状态
const [headerNavModules, setHeaderNavModules] = useState({
home: true,
console: true,
pricing: {
enabled: true,
requireAuth: false // 默认不需要登录鉴权
},
docs: true,
about: true,
});
// 处理顶栏模块配置变更
function handleHeaderNavModuleChange(moduleKey) {
return (checked) => {
const newModules = { ...headerNavModules };
if (moduleKey === 'pricing') {
// 对于pricing模块只更新enabled属性
newModules[moduleKey] = {
...newModules[moduleKey],
enabled: checked
};
} else {
newModules[moduleKey] = checked;
}
setHeaderNavModules(newModules);
};
}
// 处理模型广场权限控制变更
function handlePricingAuthChange(checked) {
const newModules = { ...headerNavModules };
newModules.pricing = {
...newModules.pricing,
requireAuth: checked
};
setHeaderNavModules(newModules);
}
// 重置顶栏模块为默认配置
function resetHeaderNavModules() {
const defaultModules = {
home: true,
console: true,
pricing: {
enabled: true,
requireAuth: false
},
docs: true,
about: true,
};
setHeaderNavModules(defaultModules);
showSuccess(t('已重置为默认配置'));
}
// 保存配置
async function onSubmit() {
setLoading(true);
try {
const res = await API.put('/api/option/', {
key: 'HeaderNavModules',
value: JSON.stringify(headerNavModules),
});
const { success, message } = res.data;
if (success) {
showSuccess(t('保存成功'));
// 立即更新StatusContext中的状态
statusDispatch({
type: 'set',
payload: {
...statusState.status,
HeaderNavModules: JSON.stringify(headerNavModules)
}
});
// 刷新父组件状态
if (props.refresh) {
await props.refresh();
}
} else {
showError(message);
}
} catch (error) {
showError(t('保存失败,请重试'));
} finally {
setLoading(false);
}
}
useEffect(() => {
// 从 props.options 中获取配置
if (props.options && props.options.HeaderNavModules) {
try {
const modules = JSON.parse(props.options.HeaderNavModules);
// 处理向后兼容性如果pricing是boolean转换为对象格式
if (typeof modules.pricing === 'boolean') {
modules.pricing = {
enabled: modules.pricing,
requireAuth: false // 默认不需要登录鉴权
};
}
setHeaderNavModules(modules);
} catch (error) {
// 使用默认配置
const defaultModules = {
home: true,
console: true,
pricing: {
enabled: true,
requireAuth: false
},
docs: true,
about: true,
};
setHeaderNavModules(defaultModules);
}
}
}, [props.options]);
// 模块配置数据
const moduleConfigs = [
{
key: 'home',
title: t('首页'),
description: t('用户主页,展示系统信息')
},
{
key: 'console',
title: t('控制台'),
description: t('用户控制面板,管理账户')
},
{
key: 'pricing',
title: t('模型广场'),
description: t('模型定价,需要登录访问'),
hasSubConfig: true // 标识该模块有子配置
},
{
key: 'docs',
title: t('文档'),
description: t('系统文档和帮助信息')
},
{
key: 'about',
title: t('关于'),
description: t('关于系统的详细信息')
}
];
return (
<Card>
<Form.Section text={t('顶栏管理')} extraText={t('控制顶栏模块显示状态,全局生效')}>
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
{moduleConfigs.map((module) => (
<Col key={module.key} xs={24} sm={12} md={6} lg={6} xl={6}>
<Card
style={{
borderRadius: '8px',
border: '1px solid var(--semi-color-border)',
transition: 'all 0.2s ease',
background: 'var(--semi-color-bg-1)',
minHeight: '80px'
}}
bodyStyle={{ padding: '16px' }}
hoverable
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '100%'
}}>
<div style={{ flex: 1, textAlign: 'left' }}>
<div style={{
fontWeight: '600',
fontSize: '14px',
color: 'var(--semi-color-text-0)',
marginBottom: '4px'
}}>
{module.title}
</div>
<Text
type="secondary"
size="small"
style={{
fontSize: '12px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4',
display: 'block'
}}
>
{module.description}
</Text>
</div>
<div style={{ marginLeft: '16px' }}>
<Switch
checked={module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]}
onChange={handleHeaderNavModuleChange(module.key)}
size="default"
/>
</div>
</div>
{/* 为模型广场添加权限控制子开关 */}
{module.key === 'pricing' && (module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]) && (
<div style={{
borderTop: '1px solid var(--semi-color-border)',
marginTop: '12px',
paddingTop: '12px'
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div style={{ flex: 1, textAlign: 'left' }}>
<div style={{
fontWeight: '500',
fontSize: '12px',
color: 'var(--semi-color-text-1)',
marginBottom: '2px'
}}>
{t('需要登录访问')}
</div>
<Text
type="secondary"
size="small"
style={{
fontSize: '11px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4',
display: 'block'
}}
>
{t('开启后未登录用户无法访问模型广场')}
</Text>
</div>
<div style={{ marginLeft: '16px' }}>
<Switch
checked={headerNavModules.pricing?.requireAuth || false}
onChange={handlePricingAuthChange}
size="small"
/>
</div>
</div>
</div>
)}
</Card>
</Col>
))}
</Row>
<div style={{
display: 'flex',
gap: '12px',
justifyContent: 'flex-start',
alignItems: 'center',
paddingTop: '8px',
borderTop: '1px solid var(--semi-color-border)'
}}>
<Button
size='default'
type='tertiary'
onClick={resetHeaderNavModules}
style={{
borderRadius: '6px',
fontWeight: '500'
}}
>
{t('重置为默认')}
</Button>
<Button
size='default'
type='primary'
onClick={onSubmit}
loading={loading}
style={{
borderRadius: '6px',
fontWeight: '500',
minWidth: '100px'
}}
>
{t('保存设置')}
</Button>
</div>
</Form.Section>
</Card>
);
}

View File

@@ -0,0 +1,362 @@
/*
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 { useTranslation } from 'react-i18next';
import { Card, Form, Button, Switch, Row, Col, Typography } from '@douyinfe/semi-ui';
import { API, showSuccess, showError } from '../../../helpers';
import { StatusContext } from '../../../context/Status';
const { Text } = Typography;
export default function SettingsSidebarModulesAdmin(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [statusState, statusDispatch] = useContext(StatusContext);
// 左侧边栏模块管理状态(管理员全局控制)
const [sidebarModulesAdmin, setSidebarModulesAdmin] = 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
}
});
// 处理区域级别开关变更
function handleSectionChange(sectionKey) {
return (checked) => {
const newModules = {
...sidebarModulesAdmin,
[sectionKey]: {
...sidebarModulesAdmin[sectionKey],
enabled: checked
}
};
setSidebarModulesAdmin(newModules);
};
}
// 处理功能级别开关变更
function handleModuleChange(sectionKey, moduleKey) {
return (checked) => {
const newModules = {
...sidebarModulesAdmin,
[sectionKey]: {
...sidebarModulesAdmin[sectionKey],
[moduleKey]: checked
}
};
setSidebarModulesAdmin(newModules);
};
}
// 重置为默认配置
function resetSidebarModules() {
const defaultModules = {
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
}
};
setSidebarModulesAdmin(defaultModules);
showSuccess(t('已重置为默认配置'));
}
// 保存配置
async function onSubmit() {
setLoading(true);
try {
const res = await API.put('/api/option/', {
key: 'SidebarModulesAdmin',
value: JSON.stringify(sidebarModulesAdmin),
});
const { success, message } = res.data;
if (success) {
showSuccess(t('保存成功'));
// 立即更新StatusContext中的状态
statusDispatch({
type: 'set',
payload: {
...statusState.status,
SidebarModulesAdmin: JSON.stringify(sidebarModulesAdmin)
}
});
// 刷新父组件状态
if (props.refresh) {
await props.refresh();
}
} else {
showError(message);
}
} catch (error) {
showError(t('保存失败,请重试'));
} finally {
setLoading(false);
}
}
useEffect(() => {
// 从 props.options 中获取配置
if (props.options && props.options.SidebarModulesAdmin) {
try {
const modules = JSON.parse(props.options.SidebarModulesAdmin);
setSidebarModulesAdmin(modules);
} catch (error) {
// 使用默认配置
const defaultModules = {
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 }
};
setSidebarModulesAdmin(defaultModules);
}
}
}, [props.options]);
// 区域配置数据
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('系统参数配置') }
]
}
];
return (
<Card>
<Form.Section text={t('侧边栏管理(全局控制)')} extraText={t('全局控制侧边栏区域和功能显示,管理员隐藏的功能用户无法启用')}>
{sectionConfigs.map((section) => (
<div key={section.key} style={{ marginBottom: '32px' }}>
{/* 区域标题和总开关 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
padding: '12px 16px',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: '8px',
border: '1px solid var(--semi-color-border)'
}}>
<div>
<div style={{
fontWeight: '600',
fontSize: '16px',
color: 'var(--semi-color-text-0)',
marginBottom: '4px'
}}>
{section.title}
</div>
<Text
type="secondary"
size="small"
style={{
fontSize: '12px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4'
}}
>
{section.description}
</Text>
</div>
<Switch
checked={sidebarModulesAdmin[section.key]?.enabled}
onChange={handleSectionChange(section.key)}
size="default"
/>
</div>
{/* 功能模块网格 */}
<Row gutter={[16, 16]}>
{section.modules.map((module) => (
<Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
<Card
bodyStyle={{ padding: '16px' }}
hoverable
style={{
opacity: sidebarModulesAdmin[section.key]?.enabled ? 1 : 0.5,
transition: 'opacity 0.2s'
}}
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '100%'
}}>
<div style={{ flex: 1, textAlign: 'left' }}>
<div style={{
fontWeight: '600',
fontSize: '14px',
color: 'var(--semi-color-text-0)',
marginBottom: '4px'
}}>
{module.title}
</div>
<Text
type="secondary"
size="small"
style={{
fontSize: '12px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4',
display: 'block'
}}
>
{module.description}
</Text>
</div>
<div style={{ marginLeft: '16px' }}>
<Switch
checked={sidebarModulesAdmin[section.key]?.[module.key]}
onChange={handleModuleChange(section.key, module.key)}
size="default"
disabled={!sidebarModulesAdmin[section.key]?.enabled}
/>
</div>
</div>
</Card>
</Col>
))}
</Row>
</div>
))}
<div style={{
display: 'flex',
gap: '12px',
justifyContent: 'flex-start',
alignItems: 'center',
paddingTop: '8px',
borderTop: '1px solid var(--semi-color-border)'
}}>
<Button
size='default'
type='tertiary'
onClick={resetSidebarModules}
style={{
borderRadius: '6px',
fontWeight: '500'
}}
>
{t('重置为默认')}
</Button>
<Button
size='default'
type='primary'
onClick={onSubmit}
loading={loading}
style={{
borderRadius: '6px',
fontWeight: '500',
minWidth: '100px'
}}
>
{t('保存设置')}
</Button>
</div>
</Form.Section>
</Card>
);
}

View File

@@ -0,0 +1,404 @@
/*
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 { useState, useEffect, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Button, Switch, Typography, Row, Col, Avatar } from '@douyinfe/semi-ui';
import { API, showSuccess, showError } from '../../../helpers';
import { StatusContext } from '../../../context/Status';
import { UserContext } from '../../../context/User';
import { useUserPermissions } from '../../../hooks/common/useUserPermissions';
import { useSidebar } from '../../../hooks/common/useSidebar';
import { Settings } from 'lucide-react';
const { Text } = Typography;
export default function SettingsSidebarModulesUser() {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [statusState] = useContext(StatusContext);
// 使用后端权限验证替代前端角色判断
const {
permissions,
loading: permissionsLoading,
hasSidebarSettingsPermission,
isSidebarSectionAllowed,
isSidebarModuleAllowed,
} = useUserPermissions();
// 使用useSidebar钩子获取刷新方法
const { refreshUserConfig } = useSidebar();
// 如果没有边栏设置权限,不显示此组件
if (!permissionsLoading && !hasSidebarSettingsPermission()) {
return null;
}
// 权限加载中,显示加载状态
if (permissionsLoading) {
return null;
}
// 根据用户权限生成默认配置
const generateDefaultConfig = () => {
const defaultConfig = {};
// 聊天区域 - 所有用户都可以访问
if (isSidebarSectionAllowed('chat')) {
defaultConfig.chat = {
enabled: true,
playground: isSidebarModuleAllowed('chat', 'playground'),
chat: isSidebarModuleAllowed('chat', 'chat')
};
}
// 控制台区域 - 所有用户都可以访问
if (isSidebarSectionAllowed('console')) {
defaultConfig.console = {
enabled: true,
detail: isSidebarModuleAllowed('console', 'detail'),
token: isSidebarModuleAllowed('console', 'token'),
log: isSidebarModuleAllowed('console', 'log'),
midjourney: isSidebarModuleAllowed('console', 'midjourney'),
task: isSidebarModuleAllowed('console', 'task')
};
}
// 个人中心区域 - 所有用户都可以访问
if (isSidebarSectionAllowed('personal')) {
defaultConfig.personal = {
enabled: true,
topup: isSidebarModuleAllowed('personal', 'topup'),
personal: isSidebarModuleAllowed('personal', 'personal')
};
}
// 管理员区域 - 只有管理员可以访问
if (isSidebarSectionAllowed('admin')) {
defaultConfig.admin = {
enabled: true,
channel: isSidebarModuleAllowed('admin', 'channel'),
models: isSidebarModuleAllowed('admin', 'models'),
redemption: isSidebarModuleAllowed('admin', 'redemption'),
user: isSidebarModuleAllowed('admin', 'user'),
setting: isSidebarModuleAllowed('admin', 'setting')
};
}
return defaultConfig;
};
// 用户个人左侧边栏模块设置
const [sidebarModulesUser, setSidebarModulesUser] = useState({});
// 管理员全局配置
const [adminConfig, setAdminConfig] = useState(null);
// 处理区域级别开关变更
function handleSectionChange(sectionKey) {
return (checked) => {
const newModules = {
...sidebarModulesUser,
[sectionKey]: {
...sidebarModulesUser[sectionKey],
enabled: checked
}
};
setSidebarModulesUser(newModules);
console.log('用户边栏区域配置变更:', sectionKey, checked, newModules);
};
}
// 处理功能级别开关变更
function handleModuleChange(sectionKey, moduleKey) {
return (checked) => {
const newModules = {
...sidebarModulesUser,
[sectionKey]: {
...sidebarModulesUser[sectionKey],
[moduleKey]: checked
}
};
setSidebarModulesUser(newModules);
console.log('用户边栏功能配置变更:', sectionKey, moduleKey, checked, newModules);
};
}
// 重置为默认配置(基于权限过滤)
function resetSidebarModules() {
const defaultConfig = generateDefaultConfig();
setSidebarModulesUser(defaultConfig);
showSuccess(t('已重置为默认配置'));
console.log('用户边栏配置重置为默认:', defaultConfig);
}
// 保存配置
async function onSubmit() {
setLoading(true);
try {
console.log('保存用户边栏配置:', sidebarModulesUser);
const res = await API.put('/api/user/self', {
sidebar_modules: JSON.stringify(sidebarModulesUser),
});
const { success, message } = res.data;
if (success) {
showSuccess(t('保存成功'));
console.log('用户边栏配置保存成功');
// 刷新useSidebar钩子中的用户配置实现实时更新
await refreshUserConfig();
console.log('用户边栏配置已刷新,边栏将立即更新');
} else {
showError(message);
console.error('用户边栏配置保存失败:', message);
}
} catch (error) {
showError(t('保存失败,请重试'));
console.error('用户边栏配置保存异常:', error);
} finally {
setLoading(false);
}
}
// 统一的配置加载逻辑
useEffect(() => {
const loadConfigs = async () => {
try {
// 获取管理员全局配置
if (statusState?.status?.SidebarModulesAdmin) {
const adminConf = JSON.parse(statusState.status.SidebarModulesAdmin);
setAdminConfig(adminConf);
console.log('加载管理员边栏配置:', adminConf);
}
// 获取用户个人配置
const userRes = await API.get('/api/user/self');
if (userRes.data.success && userRes.data.data.sidebar_modules) {
let userConf;
// 检查sidebar_modules是字符串还是对象
if (typeof userRes.data.data.sidebar_modules === 'string') {
userConf = JSON.parse(userRes.data.data.sidebar_modules);
} else {
userConf = userRes.data.data.sidebar_modules;
}
console.log('从API加载的用户配置:', userConf);
// 确保用户配置也经过权限过滤
const filteredUserConf = {};
Object.keys(userConf).forEach(sectionKey => {
if (isSidebarSectionAllowed(sectionKey)) {
filteredUserConf[sectionKey] = { ...userConf[sectionKey] };
// 过滤不允许的模块
Object.keys(userConf[sectionKey]).forEach(moduleKey => {
if (moduleKey !== 'enabled' && !isSidebarModuleAllowed(sectionKey, moduleKey)) {
delete filteredUserConf[sectionKey][moduleKey];
}
});
}
});
setSidebarModulesUser(filteredUserConf);
console.log('权限过滤后的用户配置:', filteredUserConf);
} else {
// 如果用户没有配置,使用权限过滤后的默认配置
const defaultConfig = generateDefaultConfig();
setSidebarModulesUser(defaultConfig);
console.log('用户无配置,使用默认配置:', defaultConfig);
}
} catch (error) {
console.error('加载边栏配置失败:', error);
// 出错时也使用默认配置
const defaultConfig = generateDefaultConfig();
setSidebarModulesUser(defaultConfig);
}
};
// 只有权限加载完成且有边栏设置权限时才加载配置
if (!permissionsLoading && hasSidebarSettingsPermission()) {
loadConfigs();
}
}, [statusState, permissionsLoading, hasSidebarSettingsPermission, isSidebarSectionAllowed, isSidebarModuleAllowed]);
// 检查功能是否被管理员允许
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)
);
return (
<Card className='!rounded-2xl shadow-sm border-0'>
{/* 卡片头部 */}
<div className='flex items-center mb-4'>
<Avatar size='small' color='purple' className='mr-3 shadow-md'>
<Settings size={16} />
</Avatar>
<div>
<Typography.Text className='text-lg font-medium'>
{t('左侧边栏个人设置')}
</Typography.Text>
<div className='text-xs text-gray-600'>
{t('个性化设置左侧边栏的显示内容')}
</div>
</div>
</div>
<div className='mb-4'>
<Text type="secondary" className='text-sm text-gray-600'>
{t('您可以个性化设置侧边栏的要显示功能')}
</Text>
</div>
{sectionConfigs.map((section) => (
<div key={section.key} className='mb-6'>
{/* 区域标题和总开关 */}
<div className='flex justify-between items-center mb-4 p-4 bg-gray-50 rounded-xl border border-gray-200'>
<div>
<div className='font-semibold text-base text-gray-900 mb-1'>
{section.title}
</div>
<Text className='text-xs text-gray-600'>
{section.description}
</Text>
</div>
<Switch
checked={sidebarModulesUser[section.key]?.enabled}
onChange={handleSectionChange(section.key)}
size="default"
/>
</div>
{/* 功能模块网格 */}
<Row gutter={[12, 12]}>
{section.modules.map((module) => (
<Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
<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>
<Text className='text-xs text-gray-600 leading-relaxed block'>
{module.description}
</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 className='flex justify-end gap-3 mt-6 pt-4 border-t border-gray-200'>
<Button
type='tertiary'
onClick={resetSidebarModules}
className='!rounded-lg'
>
{t('重置为默认')}
</Button>
<Button
type='primary'
onClick={onSubmit}
loading={loading}
className='!rounded-lg'
>
{t('保存设置')}
</Button>
</div>
</Card>
);
}