Merge branch 'alpha' into imageratio-and-audioratio-edit
This commit is contained in:
@@ -130,17 +130,19 @@ export default function GeneralSettings(props) {
|
||||
showClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.Input
|
||||
field={'QuotaPerUnit'}
|
||||
label={t('单位美元额度')}
|
||||
initValue={''}
|
||||
placeholder={t('一单位货币能兑换的额度')}
|
||||
onChange={handleFieldChange('QuotaPerUnit')}
|
||||
showClear
|
||||
onClick={() => setShowQuotaWarning(true)}
|
||||
/>
|
||||
</Col>
|
||||
{inputs.QuotaPerUnit !== '500000' && inputs.QuotaPerUnit !== 500000 && (
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.Input
|
||||
field={'QuotaPerUnit'}
|
||||
label={t('单位美元额度')}
|
||||
initValue={''}
|
||||
placeholder={t('一单位货币能兑换的额度')}
|
||||
onChange={handleFieldChange('QuotaPerUnit')}
|
||||
showClear
|
||||
onClick={() => setShowQuotaWarning(true)}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.Input
|
||||
field={'USDExchangeRate'}
|
||||
@@ -194,7 +196,7 @@ export default function GeneralSettings(props) {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.Switch
|
||||
field={'DemoSiteEnabled'}
|
||||
|
||||
356
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
Normal file
356
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
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='default'
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,8 @@ export default function SettingsMonitoring(props) {
|
||||
AutomaticDisableChannelEnabled: false,
|
||||
AutomaticEnableChannelEnabled: false,
|
||||
AutomaticDisableKeywords: '',
|
||||
'monitor_setting.auto_test_channel_enabled': false,
|
||||
'monitor_setting.auto_test_channel_minutes': 10,
|
||||
});
|
||||
const refForm = useRef();
|
||||
const [inputsRow, setInputsRow] = useState(inputs);
|
||||
@@ -98,6 +100,40 @@ export default function SettingsMonitoring(props) {
|
||||
style={{ marginBottom: 15 }}
|
||||
>
|
||||
<Form.Section text={t('监控设置')}>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.Switch
|
||||
field={'monitor_setting.auto_test_channel_enabled'}
|
||||
label={t('定时测试所有通道')}
|
||||
size='default'
|
||||
checkedText='|'
|
||||
uncheckedText='〇'
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
'monitor_setting.auto_test_channel_enabled': value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.InputNumber
|
||||
label={t('自动测试所有通道间隔时间')}
|
||||
step={1}
|
||||
min={1}
|
||||
suffix={t('分钟')}
|
||||
extraText={t('每隔多少分钟测试一次所有通道')}
|
||||
placeholder={''}
|
||||
field={'monitor_setting.auto_test_channel_minutes'}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
...inputs,
|
||||
'monitor_setting.auto_test_channel_minutes': parseInt(value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
|
||||
<Form.InputNumber
|
||||
|
||||
422
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx
Normal file
422
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -41,6 +41,8 @@ export default function SettingsPaymentGateway(props) {
|
||||
TopupGroupRatio: '',
|
||||
CustomCallbackAddress: '',
|
||||
PayMethods: '',
|
||||
AmountOptions: '',
|
||||
AmountDiscount: '',
|
||||
});
|
||||
const [originInputs, setOriginInputs] = useState({});
|
||||
const formApiRef = useRef(null);
|
||||
@@ -62,7 +64,30 @@ export default function SettingsPaymentGateway(props) {
|
||||
TopupGroupRatio: props.options.TopupGroupRatio || '',
|
||||
CustomCallbackAddress: props.options.CustomCallbackAddress || '',
|
||||
PayMethods: props.options.PayMethods || '',
|
||||
AmountOptions: props.options.AmountOptions || '',
|
||||
AmountDiscount: props.options.AmountDiscount || '',
|
||||
};
|
||||
|
||||
// 美化 JSON 展示
|
||||
try {
|
||||
if (currentInputs.AmountOptions) {
|
||||
currentInputs.AmountOptions = JSON.stringify(
|
||||
JSON.parse(currentInputs.AmountOptions),
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
if (currentInputs.AmountDiscount) {
|
||||
currentInputs.AmountDiscount = JSON.stringify(
|
||||
JSON.parse(currentInputs.AmountDiscount),
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
setInputs(currentInputs);
|
||||
setOriginInputs({ ...currentInputs });
|
||||
formApiRef.current.setValues(currentInputs);
|
||||
@@ -93,6 +118,20 @@ export default function SettingsPaymentGateway(props) {
|
||||
}
|
||||
}
|
||||
|
||||
if (originInputs['AmountOptions'] !== inputs.AmountOptions && inputs.AmountOptions.trim() !== '') {
|
||||
if (!verifyJSON(inputs.AmountOptions)) {
|
||||
showError(t('自定义充值数量选项不是合法的 JSON 数组'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (originInputs['AmountDiscount'] !== inputs.AmountDiscount && inputs.AmountDiscount.trim() !== '') {
|
||||
if (!verifyJSON(inputs.AmountDiscount)) {
|
||||
showError(t('充值金额折扣配置不是合法的 JSON 对象'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const options = [
|
||||
@@ -123,6 +162,12 @@ export default function SettingsPaymentGateway(props) {
|
||||
if (originInputs['PayMethods'] !== inputs.PayMethods) {
|
||||
options.push({ key: 'PayMethods', value: inputs.PayMethods });
|
||||
}
|
||||
if (originInputs['AmountOptions'] !== inputs.AmountOptions) {
|
||||
options.push({ key: 'payment_setting.amount_options', value: inputs.AmountOptions });
|
||||
}
|
||||
if (originInputs['AmountDiscount'] !== inputs.AmountDiscount) {
|
||||
options.push({ key: 'payment_setting.amount_discount', value: inputs.AmountDiscount });
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
const requestQueue = options.map((opt) =>
|
||||
@@ -228,6 +273,37 @@ export default function SettingsPaymentGateway(props) {
|
||||
placeholder={t('为一个 JSON 文本')}
|
||||
autosize
|
||||
/>
|
||||
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Col span={24}>
|
||||
<Form.TextArea
|
||||
field='AmountOptions'
|
||||
label={t('自定义充值数量选项')}
|
||||
placeholder={t('为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]')}
|
||||
autosize
|
||||
extraText={t('设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Col span={24}>
|
||||
<Form.TextArea
|
||||
field='AmountDiscount'
|
||||
label={t('充值金额折扣配置')}
|
||||
placeholder={t('为一个 JSON 对象,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
|
||||
autosize
|
||||
extraText={t('设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Button onClick={submitPayAddress}>{t('更新支付设置')}</Button>
|
||||
</Form.Section>
|
||||
</Form>
|
||||
|
||||
453
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
Normal file
453
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -130,9 +130,7 @@ export default function ModelRatioNotSetEditor(props) {
|
||||
|
||||
// 在 return 语句之前,先处理过滤和分页逻辑
|
||||
const filteredModels = models.filter((model) =>
|
||||
searchText
|
||||
? model.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
: true,
|
||||
searchText ? model.name.includes(searchText) : true,
|
||||
);
|
||||
|
||||
// 然后基于过滤后的数据计算分页数据
|
||||
|
||||
@@ -99,9 +99,7 @@ export default function ModelSettingsVisualEditor(props) {
|
||||
|
||||
// 在 return 语句之前,先处理过滤和分页逻辑
|
||||
const filteredModels = models.filter((model) => {
|
||||
const keywordMatch = searchText
|
||||
? model.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
: true;
|
||||
const keywordMatch = searchText ? model.name.includes(searchText) : true;
|
||||
const conflictMatch = conflictOnly ? model.hasConflict : true;
|
||||
return keywordMatch && conflictMatch;
|
||||
});
|
||||
|
||||
@@ -151,8 +151,17 @@ export default function UpstreamRatioSync(props) {
|
||||
setChannelEndpoints((prev) => {
|
||||
const merged = { ...prev };
|
||||
transferData.forEach((channel) => {
|
||||
if (!merged[channel.key]) {
|
||||
merged[channel.key] = DEFAULT_ENDPOINT;
|
||||
const id = channel.key;
|
||||
const base = channel._originalData?.base_url || '';
|
||||
const name = channel.label || '';
|
||||
const isOfficial =
|
||||
id === -100 ||
|
||||
base === 'https://basellm.github.io' ||
|
||||
name === '官方倍率预设';
|
||||
if (!merged[id]) {
|
||||
merged[id] = isOfficial
|
||||
? '/llm-metadata/api/newapi/ratio_config-v1-base.json'
|
||||
: DEFAULT_ENDPOINT;
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
|
||||
Reference in New Issue
Block a user