Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d2dcd8be
authored
Aug 31, 2025
by
creamlike1024
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'alpha' of github.com:x-Ai/new-api into x-Ai-alpha
parents
ed71c9fc
d0d6168e
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
2428 additions
and
170 deletions
+2428
-170
controller/misc.go
+4
-0
controller/user.go
+181
-2
dto/user_settings.go
+1
-0
model/user.go
+86
-0
web/src/App.jsx
+36
-4
web/src/components/layout/HeaderBar/Navigation.jsx
+4
-1
web/src/components/layout/HeaderBar/index.jsx
+4
-1
web/src/components/layout/SiderBar.jsx
+170
-118
web/src/components/settings/OperationSetting.jsx
+16
-0
web/src/components/settings/PersonalSetting.jsx
+4
-0
web/src/components/settings/personal/cards/NotificationSettings.jsx
+352
-8
web/src/hooks/common/useHeaderBar.js
+39
-1
web/src/hooks/common/useNavigation.js
+60
-34
web/src/hooks/common/useSidebar.js
+220
-0
web/src/hooks/common/useUserPermissions.js
+100
-0
web/src/i18n/locales/en.json
+59
-1
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
+326
-0
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx
+362
-0
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
+404
-0
No files found.
controller/misc.go
View file @
d2dcd8be
...
...
@@ -89,6 +89,10 @@ func GetStatus(c *gin.Context) {
"announcements_enabled"
:
cs
.
AnnouncementsEnabled
,
"faq_enabled"
:
cs
.
FAQEnabled
,
// 模块管理配置
"HeaderNavModules"
:
common
.
OptionMap
[
"HeaderNavModules"
],
"SidebarModulesAdmin"
:
common
.
OptionMap
[
"SidebarModulesAdmin"
],
"oidc_enabled"
:
system_setting
.
GetOIDCSettings
()
.
Enabled
,
"oidc_client_id"
:
system_setting
.
GetOIDCSettings
()
.
ClientId
,
"oidc_authorization_endpoint"
:
system_setting
.
GetOIDCSettings
()
.
AuthorizationEndpoint
,
...
...
controller/user.go
View file @
d2dcd8be
...
...
@@ -210,6 +210,7 @@ func Register(c *gin.Context) {
Password
:
user
.
Password
,
DisplayName
:
user
.
Username
,
InviterId
:
inviterId
,
Role
:
common
.
RoleCommonUser
,
// 明确设置角色为普通用户
}
if
common
.
EmailVerificationEnabled
{
cleanUser
.
Email
=
user
.
Email
...
...
@@ -426,6 +427,7 @@ func GetAffCode(c *gin.Context) {
func
GetSelf
(
c
*
gin
.
Context
)
{
id
:=
c
.
GetInt
(
"id"
)
userRole
:=
c
.
GetInt
(
"role"
)
user
,
err
:=
model
.
GetUserById
(
id
,
false
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
...
...
@@ -434,14 +436,136 @@ func GetSelf(c *gin.Context) {
// Hide admin remarks: set to empty to trigger omitempty tag, ensuring the remark field is not included in JSON returned to regular users
user
.
Remark
=
""
// 计算用户权限信息
permissions
:=
calculateUserPermissions
(
userRole
)
// 获取用户设置并提取sidebar_modules
userSetting
:=
user
.
GetSetting
()
// 构建响应数据,包含用户信息和权限
responseData
:=
map
[
string
]
interface
{}{
"id"
:
user
.
Id
,
"username"
:
user
.
Username
,
"display_name"
:
user
.
DisplayName
,
"role"
:
user
.
Role
,
"status"
:
user
.
Status
,
"email"
:
user
.
Email
,
"group"
:
user
.
Group
,
"quota"
:
user
.
Quota
,
"used_quota"
:
user
.
UsedQuota
,
"request_count"
:
user
.
RequestCount
,
"aff_code"
:
user
.
AffCode
,
"aff_count"
:
user
.
AffCount
,
"aff_quota"
:
user
.
AffQuota
,
"aff_history_quota"
:
user
.
AffHistoryQuota
,
"inviter_id"
:
user
.
InviterId
,
"linux_do_id"
:
user
.
LinuxDOId
,
"setting"
:
user
.
Setting
,
"stripe_customer"
:
user
.
StripeCustomer
,
"sidebar_modules"
:
userSetting
.
SidebarModules
,
// 正确提取sidebar_modules字段
"permissions"
:
permissions
,
// 新增权限字段
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
user
,
"data"
:
responseData
,
})
return
}
// 计算用户权限的辅助函数
func
calculateUserPermissions
(
userRole
int
)
map
[
string
]
interface
{}
{
permissions
:=
map
[
string
]
interface
{}{}
// 根据用户角色计算权限
if
userRole
==
common
.
RoleRootUser
{
// 超级管理员不需要边栏设置功能
permissions
[
"sidebar_settings"
]
=
false
permissions
[
"sidebar_modules"
]
=
map
[
string
]
interface
{}{}
}
else
if
userRole
==
common
.
RoleAdminUser
{
// 管理员可以设置边栏,但不包含系统设置功能
permissions
[
"sidebar_settings"
]
=
true
permissions
[
"sidebar_modules"
]
=
map
[
string
]
interface
{}{
"admin"
:
map
[
string
]
interface
{}{
"setting"
:
false
,
// 管理员不能访问系统设置
},
}
}
else
{
// 普通用户只能设置个人功能,不包含管理员区域
permissions
[
"sidebar_settings"
]
=
true
permissions
[
"sidebar_modules"
]
=
map
[
string
]
interface
{}{
"admin"
:
false
,
// 普通用户不能访问管理员区域
}
}
return
permissions
}
// 根据用户角色生成默认的边栏配置
func
generateDefaultSidebarConfig
(
userRole
int
)
string
{
defaultConfig
:=
map
[
string
]
interface
{}{}
// 聊天区域 - 所有用户都可以访问
defaultConfig
[
"chat"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"playground"
:
true
,
"chat"
:
true
,
}
// 控制台区域 - 所有用户都可以访问
defaultConfig
[
"console"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"detail"
:
true
,
"token"
:
true
,
"log"
:
true
,
"midjourney"
:
true
,
"task"
:
true
,
}
// 个人中心区域 - 所有用户都可以访问
defaultConfig
[
"personal"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"topup"
:
true
,
"personal"
:
true
,
}
// 管理员区域 - 根据角色决定
if
userRole
==
common
.
RoleAdminUser
{
// 管理员可以访问管理员区域,但不能访问系统设置
defaultConfig
[
"admin"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"channel"
:
true
,
"models"
:
true
,
"redemption"
:
true
,
"user"
:
true
,
"setting"
:
false
,
// 管理员不能访问系统设置
}
}
else
if
userRole
==
common
.
RoleRootUser
{
// 超级管理员可以访问所有功能
defaultConfig
[
"admin"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"channel"
:
true
,
"models"
:
true
,
"redemption"
:
true
,
"user"
:
true
,
"setting"
:
true
,
}
}
// 普通用户不包含admin区域
// 转换为JSON字符串
configBytes
,
err
:=
json
.
Marshal
(
defaultConfig
)
if
err
!=
nil
{
common
.
SysLog
(
"生成默认边栏配置失败: "
+
err
.
Error
())
return
""
}
return
string
(
configBytes
)
}
func
GetUserModels
(
c
*
gin
.
Context
)
{
id
,
err
:=
strconv
.
Atoi
(
c
.
Param
(
"id"
))
if
err
!=
nil
{
...
...
@@ -528,8 +652,61 @@ func UpdateUser(c *gin.Context) {
}
func
UpdateSelf
(
c
*
gin
.
Context
)
{
var
requestData
map
[
string
]
interface
{}
err
:=
json
.
NewDecoder
(
c
.
Request
.
Body
)
.
Decode
(
&
requestData
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"无效的参数"
,
})
return
}
// 检查是否是sidebar_modules更新请求
if
sidebarModules
,
exists
:=
requestData
[
"sidebar_modules"
];
exists
{
userId
:=
c
.
GetInt
(
"id"
)
user
,
err
:=
model
.
GetUserById
(
userId
,
false
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
// 获取当前用户设置
currentSetting
:=
user
.
GetSetting
()
// 更新sidebar_modules字段
if
sidebarModulesStr
,
ok
:=
sidebarModules
.
(
string
);
ok
{
currentSetting
.
SidebarModules
=
sidebarModulesStr
}
// 保存更新后的设置
user
.
SetSetting
(
currentSetting
)
if
err
:=
user
.
Update
(
false
);
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"更新设置失败: "
+
err
.
Error
(),
})
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"设置更新成功"
,
})
return
}
// 原有的用户信息更新逻辑
var
user
model
.
User
err
:=
json
.
NewDecoder
(
c
.
Request
.
Body
)
.
Decode
(
&
user
)
requestDataBytes
,
err
:=
json
.
Marshal
(
requestData
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"无效的参数"
,
})
return
}
err
=
json
.
Unmarshal
(
requestDataBytes
,
&
user
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
...
...
@@ -537,6 +714,7 @@ func UpdateSelf(c *gin.Context) {
})
return
}
if
user
.
Password
==
""
{
user
.
Password
=
"$I_LOVE_U"
// make Validator happy :)
}
...
...
@@ -679,6 +857,7 @@ func CreateUser(c *gin.Context) {
Username
:
user
.
Username
,
Password
:
user
.
Password
,
DisplayName
:
user
.
DisplayName
,
Role
:
user
.
Role
,
// 保持管理员设置的角色
}
if
err
:=
cleanUser
.
Insert
(
0
);
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
...
...
dto/user_settings.go
View file @
d2dcd8be
...
...
@@ -8,6 +8,7 @@ type UserSetting struct {
NotificationEmail
string
`json:"notification_email,omitempty"`
// NotificationEmail 通知邮箱地址
AcceptUnsetRatioModel
bool
`json:"accept_unset_model_ratio_model,omitempty"`
// AcceptUnsetRatioModel 是否接受未设置价格的模型
RecordIpLog
bool
`json:"record_ip_log,omitempty"`
// 是否记录请求和错误日志IP
SidebarModules
string
`json:"sidebar_modules,omitempty"`
// SidebarModules 左侧边栏模块配置
}
var
(
...
...
model/user.go
View file @
d2dcd8be
...
...
@@ -91,6 +91,68 @@ func (user *User) SetSetting(setting dto.UserSetting) {
user
.
Setting
=
string
(
settingBytes
)
}
// 根据用户角色生成默认的边栏配置
func
generateDefaultSidebarConfigForRole
(
userRole
int
)
string
{
defaultConfig
:=
map
[
string
]
interface
{}{}
// 聊天区域 - 所有用户都可以访问
defaultConfig
[
"chat"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"playground"
:
true
,
"chat"
:
true
,
}
// 控制台区域 - 所有用户都可以访问
defaultConfig
[
"console"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"detail"
:
true
,
"token"
:
true
,
"log"
:
true
,
"midjourney"
:
true
,
"task"
:
true
,
}
// 个人中心区域 - 所有用户都可以访问
defaultConfig
[
"personal"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"topup"
:
true
,
"personal"
:
true
,
}
// 管理员区域 - 根据角色决定
if
userRole
==
common
.
RoleAdminUser
{
// 管理员可以访问管理员区域,但不能访问系统设置
defaultConfig
[
"admin"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"channel"
:
true
,
"models"
:
true
,
"redemption"
:
true
,
"user"
:
true
,
"setting"
:
false
,
// 管理员不能访问系统设置
}
}
else
if
userRole
==
common
.
RoleRootUser
{
// 超级管理员可以访问所有功能
defaultConfig
[
"admin"
]
=
map
[
string
]
interface
{}{
"enabled"
:
true
,
"channel"
:
true
,
"models"
:
true
,
"redemption"
:
true
,
"user"
:
true
,
"setting"
:
true
,
}
}
// 普通用户不包含admin区域
// 转换为JSON字符串
configBytes
,
err
:=
json
.
Marshal
(
defaultConfig
)
if
err
!=
nil
{
common
.
SysLog
(
"生成默认边栏配置失败: "
+
err
.
Error
())
return
""
}
return
string
(
configBytes
)
}
// CheckUserExistOrDeleted check if user exist or deleted, if not exist, return false, nil, if deleted or exist, return true, nil
func
CheckUserExistOrDeleted
(
username
string
,
email
string
)
(
bool
,
error
)
{
var
user
User
...
...
@@ -320,10 +382,34 @@ func (user *User) Insert(inviterId int) error {
user
.
Quota
=
common
.
QuotaForNewUser
//user.SetAccessToken(common.GetUUID())
user
.
AffCode
=
common
.
GetRandomString
(
4
)
// 初始化用户设置,包括默认的边栏配置
if
user
.
Setting
==
""
{
defaultSetting
:=
dto
.
UserSetting
{}
// 这里暂时不设置SidebarModules,因为需要在用户创建后根据角色设置
user
.
SetSetting
(
defaultSetting
)
}
result
:=
DB
.
Create
(
user
)
if
result
.
Error
!=
nil
{
return
result
.
Error
}
// 用户创建成功后,根据角色初始化边栏配置
// 需要重新获取用户以确保有正确的ID和Role
var
createdUser
User
if
err
:=
DB
.
Where
(
"username = ?"
,
user
.
Username
)
.
First
(
&
createdUser
)
.
Error
;
err
==
nil
{
// 生成基于角色的默认边栏配置
defaultSidebarConfig
:=
generateDefaultSidebarConfigForRole
(
createdUser
.
Role
)
if
defaultSidebarConfig
!=
""
{
currentSetting
:=
createdUser
.
GetSetting
()
currentSetting
.
SidebarModules
=
defaultSidebarConfig
createdUser
.
SetSetting
(
currentSetting
)
createdUser
.
Update
(
false
)
common
.
SysLog
(
fmt
.
Sprintf
(
"为新用户 %s (角色: %d) 初始化边栏配置"
,
createdUser
.
Username
,
createdUser
.
Role
))
}
}
if
common
.
QuotaForNewUser
>
0
{
RecordLog
(
user
.
Id
,
LogTypeSystem
,
fmt
.
Sprintf
(
"新用户注册赠送 %s"
,
logger
.
LogQuota
(
common
.
QuotaForNewUser
)))
}
...
...
web/src/App.jsx
View file @
d2dcd8be
...
...
@@ -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
,
{
lazy
,
Suspense
}
from
'react'
;
import
React
,
{
lazy
,
Suspense
,
useContext
,
useMemo
}
from
'react'
;
import
{
Route
,
Routes
,
useLocation
}
from
'react-router-dom'
;
import
Loading
from
'./components/common/ui/Loading'
;
import
User
from
'./pages/User'
;
...
...
@@ -27,6 +27,7 @@ import LoginForm from './components/auth/LoginForm';
import
NotFound
from
'./pages/NotFound'
;
import
Forbidden
from
'./pages/Forbidden'
;
import
Setting
from
'./pages/Setting'
;
import
{
StatusContext
}
from
'./context/Status'
;
import
PasswordResetForm
from
'./components/auth/PasswordResetForm'
;
import
PasswordResetConfirm
from
'./components/auth/PasswordResetConfirm'
;
...
...
@@ -53,6 +54,29 @@ const About = lazy(() => import('./pages/About'));
function
App
()
{
const
location
=
useLocation
();
const
[
statusState
]
=
useContext
(
StatusContext
);
// 获取模型广场权限配置
const
pricingRequireAuth
=
useMemo
(()
=>
{
const
headerNavModulesConfig
=
statusState
?.
status
?.
HeaderNavModules
;
if
(
headerNavModulesConfig
)
{
try
{
const
modules
=
JSON
.
parse
(
headerNavModulesConfig
);
// 处理向后兼容性:如果pricing是boolean,默认不需要登录
if
(
typeof
modules
.
pricing
===
'boolean'
)
{
return
false
;
// 默认不需要登录鉴权
}
// 如果是对象格式,使用requireAuth配置
return
modules
.
pricing
?.
requireAuth
===
true
;
}
catch
(
error
)
{
console
.
error
(
'解析顶栏模块配置失败:'
,
error
);
return
false
;
// 默认不需要登录
}
}
return
false
;
// 默认不需要登录
},
[
statusState
?.
status
?.
HeaderNavModules
]);
return
(
<
SetupCheck
>
...
...
@@ -253,9 +277,17 @@ function App() {
<
Route
path=
'/pricing'
element=
{
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
key=
{
location
.
pathname
}
>
<
Pricing
/>
</
Suspense
>
pricingRequireAuth
?
(
<
PrivateRoute
>
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
key=
{
location
.
pathname
}
>
<
Pricing
/>
</
Suspense
>
</
PrivateRoute
>
)
:
(
<
Suspense
fallback=
{
<
Loading
></
Loading
>
}
key=
{
location
.
pathname
}
>
<
Pricing
/>
</
Suspense
>
)
}
/>
<
Route
...
...
web/src/components/layout/HeaderBar/Navigation.jsx
View file @
d2dcd8be
...
...
@@ -21,7 +21,7 @@ import React from 'react';
import
{
Link
}
from
'react-router-dom'
;
import
SkeletonWrapper
from
'./SkeletonWrapper'
;
const
Navigation
=
({
mainNavLinks
,
isMobile
,
isLoading
,
userState
})
=>
{
const
Navigation
=
({
mainNavLinks
,
isMobile
,
isLoading
,
userState
,
pricingRequireAuth
})
=>
{
const
renderNavLinks
=
()
=>
{
const
baseClasses
=
'flex-shrink-0 flex items-center gap-1 font-semibold rounded-md transition-all duration-200 ease-in-out'
;
...
...
@@ -51,6 +51,9 @@ const Navigation = ({ mainNavLinks, isMobile, isLoading, userState }) => {
if
(
link
.
itemKey
===
'console'
&&
!
userState
.
user
)
{
targetPath
=
'/login'
;
}
if
(
link
.
itemKey
===
'pricing'
&&
pricingRequireAuth
&&
!
userState
.
user
)
{
targetPath
=
'/login'
;
}
return
(
<
Link
key=
{
link
.
itemKey
}
to=
{
targetPath
}
className=
{
commonLinkClasses
}
>
...
...
web/src/components/layout/HeaderBar/index.jsx
View file @
d2dcd8be
...
...
@@ -44,6 +44,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
isDemoSiteMode
,
isConsoleRoute
,
theme
,
headerNavModules
,
pricingRequireAuth
,
logout
,
handleLanguageChange
,
handleThemeToggle
,
...
...
@@ -60,7 +62,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
getUnreadKeys
,
}
=
useNotifications
(
statusState
);
const
{
mainNavLinks
}
=
useNavigation
(
t
,
docsLink
);
const
{
mainNavLinks
}
=
useNavigation
(
t
,
docsLink
,
headerNavModules
);
return
(
<
header
className=
'text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg'
>
...
...
@@ -102,6 +104,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
isMobile=
{
isMobile
}
isLoading=
{
isLoading
}
userState=
{
userState
}
pricingRequireAuth=
{
pricingRequireAuth
}
/>
<
ActionButtons
...
...
web/src/components/layout/SiderBar.jsx
View file @
d2dcd8be
...
...
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
import
{
getLucideIcon
}
from
'../../helpers/render'
;
import
{
ChevronLeft
}
from
'lucide-react'
;
import
{
useSidebarCollapsed
}
from
'../../hooks/common/useSidebarCollapsed'
;
import
{
useSidebar
}
from
'../../hooks/common/useSidebar'
;
import
{
isAdmin
,
isRoot
,
showError
}
from
'../../helpers'
;
import
{
Nav
,
Divider
,
Button
}
from
'@douyinfe/semi-ui'
;
...
...
@@ -49,6 +50,7 @@ const routerMap = {
const
SiderBar
=
({
onNavigate
=
()
=>
{}
})
=>
{
const
{
t
}
=
useTranslation
();
const
[
collapsed
,
toggleCollapsed
]
=
useSidebarCollapsed
();
const
{
isModuleVisible
,
hasSectionVisibleModules
,
loading
:
sidebarLoading
}
=
useSidebar
();
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([
'home'
]);
const
[
chatItems
,
setChatItems
]
=
useState
([]);
...
...
@@ -57,117 +59,158 @@ const SiderBar = ({ onNavigate = () => {} }) => {
const
[
routerMapState
,
setRouterMapState
]
=
useState
(
routerMap
);
const
workspaceItems
=
useMemo
(
()
=>
[
{
text
:
t
(
'数据看板'
),
itemKey
:
'detail'
,
to
:
'/detail'
,
className
:
localStorage
.
getItem
(
'enable_data_export'
)
===
'true'
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'令牌管理'
),
itemKey
:
'token'
,
to
:
'/token'
,
},
{
text
:
t
(
'使用日志'
),
itemKey
:
'log'
,
to
:
'/log'
,
},
{
text
:
t
(
'绘图日志'
),
itemKey
:
'midjourney'
,
to
:
'/midjourney'
,
className
:
localStorage
.
getItem
(
'enable_drawing'
)
===
'true'
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'任务日志'
),
itemKey
:
'task'
,
to
:
'/task'
,
className
:
localStorage
.
getItem
(
'enable_task'
)
===
'true'
?
''
:
'tableHiddle'
,
},
],
()
=>
{
const
items
=
[
{
text
:
t
(
'数据看板'
),
itemKey
:
'detail'
,
to
:
'/detail'
,
className
:
localStorage
.
getItem
(
'enable_data_export'
)
===
'true'
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'令牌管理'
),
itemKey
:
'token'
,
to
:
'/token'
,
},
{
text
:
t
(
'使用日志'
),
itemKey
:
'log'
,
to
:
'/log'
,
},
{
text
:
t
(
'绘图日志'
),
itemKey
:
'midjourney'
,
to
:
'/midjourney'
,
className
:
localStorage
.
getItem
(
'enable_drawing'
)
===
'true'
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'任务日志'
),
itemKey
:
'task'
,
to
:
'/task'
,
className
:
localStorage
.
getItem
(
'enable_task'
)
===
'true'
?
''
:
'tableHiddle'
,
},
];
// 根据配置过滤项目
const
filteredItems
=
items
.
filter
(
item
=>
{
const
configVisible
=
isModuleVisible
(
'console'
,
item
.
itemKey
);
return
configVisible
;
});
return
filteredItems
;
},
[
localStorage
.
getItem
(
'enable_data_export'
),
localStorage
.
getItem
(
'enable_drawing'
),
localStorage
.
getItem
(
'enable_task'
),
t
,
isModuleVisible
,
],
);
const
financeItems
=
useMemo
(
()
=>
[
{
text
:
t
(
'钱包管理'
),
itemKey
:
'topup'
,
to
:
'/topup'
,
},
{
text
:
t
(
'个人设置'
),
itemKey
:
'personal'
,
to
:
'/personal'
,
},
],
[
t
],
()
=>
{
const
items
=
[
{
text
:
t
(
'钱包管理'
),
itemKey
:
'topup'
,
to
:
'/topup'
,
},
{
text
:
t
(
'个人设置'
),
itemKey
:
'personal'
,
to
:
'/personal'
,
},
];
// 根据配置过滤项目
const
filteredItems
=
items
.
filter
(
item
=>
{
const
configVisible
=
isModuleVisible
(
'personal'
,
item
.
itemKey
);
return
configVisible
;
});
return
filteredItems
;
},
[
t
,
isModuleVisible
],
);
const
adminItems
=
useMemo
(
()
=>
[
{
text
:
t
(
'渠道管理'
),
itemKey
:
'channel'
,
to
:
'/channel'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'模型管理'
),
itemKey
:
'models'
,
to
:
'/console/models'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'兑换码管理'
),
itemKey
:
'redemption'
,
to
:
'/redemption'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'用户管理'
),
itemKey
:
'user'
,
to
:
'/user'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'系统设置'
),
itemKey
:
'setting'
,
to
:
'/setting'
,
className
:
isRoot
()
?
''
:
'tableHiddle'
,
},
],
[
isAdmin
(),
isRoot
(),
t
],
()
=>
{
const
items
=
[
{
text
:
t
(
'渠道管理'
),
itemKey
:
'channel'
,
to
:
'/channel'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'模型管理'
),
itemKey
:
'models'
,
to
:
'/console/models'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'兑换码管理'
),
itemKey
:
'redemption'
,
to
:
'/redemption'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'用户管理'
),
itemKey
:
'user'
,
to
:
'/user'
,
className
:
isAdmin
()
?
''
:
'tableHiddle'
,
},
{
text
:
t
(
'系统设置'
),
itemKey
:
'setting'
,
to
:
'/setting'
,
className
:
isRoot
()
?
''
:
'tableHiddle'
,
},
];
// 根据配置过滤项目
const
filteredItems
=
items
.
filter
(
item
=>
{
const
configVisible
=
isModuleVisible
(
'admin'
,
item
.
itemKey
);
return
configVisible
;
});
return
filteredItems
;
},
[
isAdmin
(),
isRoot
(),
t
,
isModuleVisible
],
);
const
chatMenuItems
=
useMemo
(
()
=>
[
{
text
:
t
(
'操练场'
),
itemKey
:
'playground'
,
to
:
'/playground'
,
},
{
text
:
t
(
'聊天'
),
itemKey
:
'chat'
,
items
:
chatItems
,
},
],
[
chatItems
,
t
],
()
=>
{
const
items
=
[
{
text
:
t
(
'操练场'
),
itemKey
:
'playground'
,
to
:
'/playground'
,
},
{
text
:
t
(
'聊天'
),
itemKey
:
'chat'
,
items
:
chatItems
,
},
];
// 根据配置过滤项目
const
filteredItems
=
items
.
filter
(
item
=>
{
const
configVisible
=
isModuleVisible
(
'chat'
,
item
.
itemKey
);
return
configVisible
;
});
return
filteredItems
;
},
[
chatItems
,
t
,
isModuleVisible
],
);
// 更新路由映射,添加聊天路由
...
...
@@ -213,7 +256,6 @@ const SiderBar = ({ onNavigate = () => {} }) => {
updateRouterMapWithChats
(
chats
);
}
}
catch
(
e
)
{
console
.
error
(
e
);
showError
(
'聊天数据解析失败'
);
}
}
...
...
@@ -382,31 +424,41 @@ const SiderBar = ({ onNavigate = () => {} }) => {
}
}
>
{
/* 聊天区域 */
}
<
div
className=
'sidebar-section'
>
{
!
collapsed
&&
<
div
className=
'sidebar-group-label'
>
{
t
(
'聊天'
)
}
</
div
>
}
{
chatMenuItems
.
map
((
item
)
=>
renderSubItem
(
item
))
}
</
div
>
{
hasSectionVisibleModules
(
'chat'
)
&&
(
<
div
className=
'sidebar-section'
>
{
!
collapsed
&&
<
div
className=
'sidebar-group-label'
>
{
t
(
'聊天'
)
}
</
div
>
}
{
chatMenuItems
.
map
((
item
)
=>
renderSubItem
(
item
))
}
</
div
>
)
}
{
/* 控制台区域 */
}
<
Divider
className=
'sidebar-divider'
/>
<
div
>
{
!
collapsed
&&
(
<
div
className=
'sidebar-group-label'
>
{
t
(
'控制台'
)
}
</
div
>
)
}
{
workspaceItems
.
map
((
item
)
=>
renderNavItem
(
item
))
}
</
div
>
{
hasSectionVisibleModules
(
'console'
)
&&
(
<>
<
Divider
className=
'sidebar-divider'
/>
<
div
>
{
!
collapsed
&&
(
<
div
className=
'sidebar-group-label'
>
{
t
(
'控制台'
)
}
</
div
>
)
}
{
workspaceItems
.
map
((
item
)
=>
renderNavItem
(
item
))
}
</
div
>
</>
)
}
{
/* 个人中心区域 */
}
<
Divider
className=
'sidebar-divider'
/>
<
div
>
{
!
collapsed
&&
(
<
div
className=
'sidebar-group-label'
>
{
t
(
'个人中心'
)
}
</
div
>
)
}
{
financeItems
.
map
((
item
)
=>
renderNavItem
(
item
))
}
</
div
>
{
/* 管理员区域 - 只在管理员时显示 */
}
{
isAdmin
()
&&
(
{
hasSectionVisibleModules
(
'personal'
)
&&
(
<>
<
Divider
className=
'sidebar-divider'
/>
<
div
>
{
!
collapsed
&&
(
<
div
className=
'sidebar-group-label'
>
{
t
(
'个人中心'
)
}
</
div
>
)
}
{
financeItems
.
map
((
item
)
=>
renderNavItem
(
item
))
}
</
div
>
</>
)
}
{
/* 管理员区域 - 只在管理员时显示且配置允许时显示 */
}
{
isAdmin
()
&&
hasSectionVisibleModules
(
'admin'
)
&&
(
<>
<
Divider
className=
'sidebar-divider'
/>
<
div
>
...
...
web/src/components/settings/OperationSetting.jsx
View file @
d2dcd8be
...
...
@@ -20,6 +20,8 @@ For commercial licensing, please contact support@quantumnous.com
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Card
,
Spin
}
from
'@douyinfe/semi-ui'
;
import
SettingsGeneral
from
'../../pages/Setting/Operation/SettingsGeneral'
;
import
SettingsHeaderNavModules
from
'../../pages/Setting/Operation/SettingsHeaderNavModules'
;
import
SettingsSidebarModulesAdmin
from
'../../pages/Setting/Operation/SettingsSidebarModulesAdmin'
;
import
SettingsSensitiveWords
from
'../../pages/Setting/Operation/SettingsSensitiveWords'
;
import
SettingsLog
from
'../../pages/Setting/Operation/SettingsLog'
;
import
SettingsMonitoring
from
'../../pages/Setting/Operation/SettingsMonitoring'
;
...
...
@@ -46,6 +48,12 @@ const OperationSetting = () => {
DemoSiteEnabled
:
false
,
SelfUseModeEnabled
:
false
,
/* 顶栏模块管理 */
HeaderNavModules
:
''
,
/* 左侧边栏模块管理(管理员) */
SidebarModulesAdmin
:
''
,
/* 敏感词设置 */
CheckSensitiveEnabled
:
false
,
CheckSensitiveOnPromptEnabled
:
false
,
...
...
@@ -108,6 +116,14 @@ const OperationSetting = () => {
<
Card
style=
{
{
marginTop
:
'10px'
}
}
>
<
SettingsGeneral
options=
{
inputs
}
refresh=
{
onRefresh
}
/>
</
Card
>
{
/* 顶栏模块管理 */
}
<
div
style=
{
{
marginTop
:
'10px'
}
}
>
<
SettingsHeaderNavModules
options=
{
inputs
}
refresh=
{
onRefresh
}
/>
</
div
>
{
/* 左侧边栏模块管理(管理员) */
}
<
div
style=
{
{
marginTop
:
'10px'
}
}
>
<
SettingsSidebarModulesAdmin
options=
{
inputs
}
refresh=
{
onRefresh
}
/>
</
div
>
{
/* 屏蔽词过滤设置 */
}
<
Card
style=
{
{
marginTop
:
'10px'
}
}
>
<
SettingsSensitiveWords
options=
{
inputs
}
refresh=
{
onRefresh
}
/>
...
...
web/src/components/settings/PersonalSetting.jsx
View file @
d2dcd8be
...
...
@@ -38,6 +38,8 @@ const PersonalSetting = () => {
let
navigate
=
useNavigate
();
const
{
t
}
=
useTranslation
();
const
[
inputs
,
setInputs
]
=
useState
({
wechat_verification_code
:
''
,
email_verification_code
:
''
,
...
...
@@ -330,6 +332,8 @@ const PersonalSetting = () => {
saveNotificationSettings=
{
saveNotificationSettings
}
/>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/settings/personal/cards/NotificationSettings.jsx
View file @
d2dcd8be
...
...
@@ -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
>
...
...
web/src/hooks/common/useHeaderBar.js
View file @
d2dcd8be
...
...
@@ -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
{
useState
,
useEffect
,
useContext
,
useCallback
}
from
'react'
;
import
{
useState
,
useEffect
,
useContext
,
useCallback
,
useMemo
}
from
'react'
;
import
{
useNavigate
,
useLocation
}
from
'react-router-dom'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
UserContext
}
from
'../../context/User'
;
...
...
@@ -51,6 +51,42 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
const
docsLink
=
statusState
?.
status
?.
docs_link
||
''
;
const
isDemoSiteMode
=
statusState
?.
status
?.
demo_site_enabled
||
false
;
// 获取顶栏模块配置
const
headerNavModulesConfig
=
statusState
?.
status
?.
HeaderNavModules
;
// 使用useMemo确保headerNavModules正确响应statusState变化
const
headerNavModules
=
useMemo
(()
=>
{
if
(
headerNavModulesConfig
)
{
try
{
const
modules
=
JSON
.
parse
(
headerNavModulesConfig
);
// 处理向后兼容性:如果pricing是boolean,转换为对象格式
if
(
typeof
modules
.
pricing
===
'boolean'
)
{
modules
.
pricing
=
{
enabled
:
modules
.
pricing
,
requireAuth
:
false
// 默认不需要登录鉴权
};
}
return
modules
;
}
catch
(
error
)
{
console
.
error
(
'解析顶栏模块配置失败:'
,
error
);
return
null
;
}
}
return
null
;
},
[
headerNavModulesConfig
]);
// 获取模型广场权限配置
const
pricingRequireAuth
=
useMemo
(()
=>
{
if
(
headerNavModules
?.
pricing
)
{
return
typeof
headerNavModules
.
pricing
===
'object'
?
headerNavModules
.
pricing
.
requireAuth
:
false
;
// 默认不需要登录
}
return
false
;
// 默认不需要登录
},
[
headerNavModules
]);
const
isConsoleRoute
=
location
.
pathname
.
startsWith
(
'/console'
);
const
theme
=
useTheme
();
...
...
@@ -156,6 +192,8 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
isConsoleRoute
,
theme
,
drawerOpen
,
headerNavModules
,
pricingRequireAuth
,
// Actions
logout
,
...
...
web/src/hooks/common/useNavigation.js
View file @
d2dcd8be
...
...
@@ -19,41 +19,67 @@ For commercial licensing, please contact support@quantumnous.com
import
{
useMemo
}
from
'react'
;
export
const
useNavigation
=
(
t
,
docsLink
)
=>
{
export
const
useNavigation
=
(
t
,
docsLink
,
headerNavModules
)
=>
{
const
mainNavLinks
=
useMemo
(
()
=>
[
{
text
:
t
(
'首页'
),
itemKey
:
'home'
,
to
:
'/'
,
},
{
text
:
t
(
'控制台'
),
itemKey
:
'console'
,
to
:
'/console'
,
},
{
text
:
t
(
'模型广场'
),
itemKey
:
'pricing'
,
to
:
'/pricing'
,
},
...(
docsLink
?
[
{
text
:
t
(
'文档'
),
itemKey
:
'docs'
,
isExternal
:
true
,
externalLink
:
docsLink
,
},
]
:
[]),
{
text
:
t
(
'关于'
),
itemKey
:
'about'
,
to
:
'/about'
,
},
],
[
t
,
docsLink
],
()
=>
{
// 默认配置,如果没有传入配置则显示所有模块
const
defaultModules
=
{
home
:
true
,
console
:
true
,
pricing
:
true
,
docs
:
true
,
about
:
true
,
};
// 使用传入的配置或默认配置
const
modules
=
headerNavModules
||
defaultModules
;
const
allLinks
=
[
{
text
:
t
(
'首页'
),
itemKey
:
'home'
,
to
:
'/'
,
},
{
text
:
t
(
'控制台'
),
itemKey
:
'console'
,
to
:
'/console'
,
},
{
text
:
t
(
'模型广场'
),
itemKey
:
'pricing'
,
to
:
'/pricing'
,
},
...(
docsLink
?
[
{
text
:
t
(
'文档'
),
itemKey
:
'docs'
,
isExternal
:
true
,
externalLink
:
docsLink
,
},
]
:
[]),
{
text
:
t
(
'关于'
),
itemKey
:
'about'
,
to
:
'/about'
,
},
];
// 根据配置过滤导航链接
return
allLinks
.
filter
(
link
=>
{
if
(
link
.
itemKey
===
'docs'
)
{
return
docsLink
&&
modules
.
docs
;
}
if
(
link
.
itemKey
===
'pricing'
)
{
// 支持新的pricing配置格式
return
typeof
modules
.
pricing
===
'object'
?
modules
.
pricing
.
enabled
:
modules
.
pricing
;
}
return
modules
[
link
.
itemKey
]
===
true
;
});
},
[
t
,
docsLink
,
headerNavModules
],
);
return
{
...
...
web/src/hooks/common/useSidebar.js
0 → 100644
View file @
d2dcd8be
/*
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
,
useMemo
,
useContext
}
from
'react'
;
import
{
StatusContext
}
from
'../../context/Status'
;
import
{
API
}
from
'../../helpers'
;
export
const
useSidebar
=
()
=>
{
const
[
statusState
]
=
useContext
(
StatusContext
);
const
[
userConfig
,
setUserConfig
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
// 默认配置
const
defaultAdminConfig
=
{
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
=
useMemo
(()
=>
{
if
(
statusState
?.
status
?.
SidebarModulesAdmin
)
{
try
{
const
config
=
JSON
.
parse
(
statusState
.
status
.
SidebarModulesAdmin
);
return
config
;
}
catch
(
error
)
{
return
defaultAdminConfig
;
}
}
return
defaultAdminConfig
;
},
[
statusState
?.
status
?.
SidebarModulesAdmin
]);
// 加载用户配置的通用方法
const
loadUserConfig
=
async
()
=>
{
try
{
setLoading
(
true
);
const
res
=
await
API
.
get
(
'/api/user/self'
);
if
(
res
.
data
.
success
&&
res
.
data
.
data
.
sidebar_modules
)
{
let
config
;
// 检查sidebar_modules是字符串还是对象
if
(
typeof
res
.
data
.
data
.
sidebar_modules
===
'string'
)
{
config
=
JSON
.
parse
(
res
.
data
.
data
.
sidebar_modules
);
}
else
{
config
=
res
.
data
.
data
.
sidebar_modules
;
}
setUserConfig
(
config
);
}
else
{
// 当用户没有配置时,生成一个基于管理员配置的默认用户配置
// 这样可以确保权限控制正确生效
const
defaultUserConfig
=
{};
Object
.
keys
(
adminConfig
).
forEach
(
sectionKey
=>
{
if
(
adminConfig
[
sectionKey
]?.
enabled
)
{
defaultUserConfig
[
sectionKey
]
=
{
enabled
:
true
};
// 为每个管理员允许的模块设置默认值为true
Object
.
keys
(
adminConfig
[
sectionKey
]).
forEach
(
moduleKey
=>
{
if
(
moduleKey
!==
'enabled'
&&
adminConfig
[
sectionKey
][
moduleKey
])
{
defaultUserConfig
[
sectionKey
][
moduleKey
]
=
true
;
}
});
}
});
setUserConfig
(
defaultUserConfig
);
}
}
catch
(
error
)
{
// 出错时也生成默认配置,而不是设置为空对象
const
defaultUserConfig
=
{};
Object
.
keys
(
adminConfig
).
forEach
(
sectionKey
=>
{
if
(
adminConfig
[
sectionKey
]?.
enabled
)
{
defaultUserConfig
[
sectionKey
]
=
{
enabled
:
true
};
Object
.
keys
(
adminConfig
[
sectionKey
]).
forEach
(
moduleKey
=>
{
if
(
moduleKey
!==
'enabled'
&&
adminConfig
[
sectionKey
][
moduleKey
])
{
defaultUserConfig
[
sectionKey
][
moduleKey
]
=
true
;
}
});
}
});
setUserConfig
(
defaultUserConfig
);
}
finally
{
setLoading
(
false
);
}
};
// 刷新用户配置的方法(供外部调用)
const
refreshUserConfig
=
async
()
=>
{
if
(
Object
.
keys
(
adminConfig
).
length
>
0
)
{
await
loadUserConfig
();
}
};
// 加载用户配置
useEffect
(()
=>
{
// 只有当管理员配置加载完成后才加载用户配置
if
(
Object
.
keys
(
adminConfig
).
length
>
0
)
{
loadUserConfig
();
}
},
[
adminConfig
]);
// 计算最终的显示配置
const
finalConfig
=
useMemo
(()
=>
{
const
result
=
{};
// 确保adminConfig已加载
if
(
!
adminConfig
||
Object
.
keys
(
adminConfig
).
length
===
0
)
{
return
result
;
}
// 如果userConfig未加载,等待加载完成
if
(
!
userConfig
)
{
return
result
;
}
// 遍历所有区域
Object
.
keys
(
adminConfig
).
forEach
(
sectionKey
=>
{
const
adminSection
=
adminConfig
[
sectionKey
];
const
userSection
=
userConfig
[
sectionKey
];
// 如果管理员禁用了整个区域,则该区域不显示
if
(
!
adminSection
?.
enabled
)
{
result
[
sectionKey
]
=
{
enabled
:
false
};
return
;
}
// 区域级别:用户可以选择隐藏管理员允许的区域
// 当userSection存在时检查enabled状态,否则默认为true
const
sectionEnabled
=
userSection
?
(
userSection
.
enabled
!==
false
)
:
true
;
result
[
sectionKey
]
=
{
enabled
:
sectionEnabled
};
// 功能级别:只有管理员和用户都允许的功能才显示
Object
.
keys
(
adminSection
).
forEach
(
moduleKey
=>
{
if
(
moduleKey
===
'enabled'
)
return
;
const
adminAllowed
=
adminSection
[
moduleKey
];
// 当userSection存在时检查模块状态,否则默认为true
const
userAllowed
=
userSection
?
(
userSection
[
moduleKey
]
!==
false
)
:
true
;
result
[
sectionKey
][
moduleKey
]
=
adminAllowed
&&
userAllowed
&&
sectionEnabled
;
});
});
return
result
;
},
[
adminConfig
,
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
,
adminConfig
,
userConfig
,
finalConfig
,
isModuleVisible
,
hasSectionVisibleModules
,
getVisibleModules
,
refreshUserConfig
};
};
web/src/hooks/common/useUserPermissions.js
0 → 100644
View file @
d2dcd8be
import
{
useState
,
useEffect
}
from
'react'
;
import
{
API
}
from
'../../helpers'
;
/**
* 用户权限钩子 - 从后端获取用户权限,替代前端角色判断
* 确保权限控制的安全性,防止前端绕过
*/
export
const
useUserPermissions
=
()
=>
{
const
[
permissions
,
setPermissions
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
null
);
// 加载用户权限(从用户信息接口获取)
const
loadPermissions
=
async
()
=>
{
try
{
setLoading
(
true
);
setError
(
null
);
const
res
=
await
API
.
get
(
'/api/user/self'
);
if
(
res
.
data
.
success
)
{
const
userPermissions
=
res
.
data
.
data
.
permissions
;
setPermissions
(
userPermissions
);
console
.
log
(
'用户权限加载成功:'
,
userPermissions
);
}
else
{
setError
(
res
.
data
.
message
||
'获取权限失败'
);
console
.
error
(
'获取权限失败:'
,
res
.
data
.
message
);
}
}
catch
(
error
)
{
setError
(
'网络错误,请重试'
);
console
.
error
(
'加载用户权限异常:'
,
error
);
}
finally
{
setLoading
(
false
);
}
};
useEffect
(()
=>
{
loadPermissions
();
},
[]);
// 检查是否有边栏设置权限
const
hasSidebarSettingsPermission
=
()
=>
{
return
permissions
?.
sidebar_settings
===
true
;
};
// 检查是否允许访问特定的边栏区域
const
isSidebarSectionAllowed
=
(
sectionKey
)
=>
{
if
(
!
permissions
?.
sidebar_modules
)
return
true
;
const
sectionPerms
=
permissions
.
sidebar_modules
[
sectionKey
];
return
sectionPerms
!==
false
;
};
// 检查是否允许访问特定的边栏模块
const
isSidebarModuleAllowed
=
(
sectionKey
,
moduleKey
)
=>
{
if
(
!
permissions
?.
sidebar_modules
)
return
true
;
const
sectionPerms
=
permissions
.
sidebar_modules
[
sectionKey
];
// 如果整个区域被禁用
if
(
sectionPerms
===
false
)
return
false
;
// 如果区域存在但模块被禁用
if
(
sectionPerms
&&
sectionPerms
[
moduleKey
]
===
false
)
return
false
;
return
true
;
};
// 获取允许的边栏区域列表
const
getAllowedSidebarSections
=
()
=>
{
if
(
!
permissions
?.
sidebar_modules
)
return
[];
return
Object
.
keys
(
permissions
.
sidebar_modules
).
filter
(
sectionKey
=>
isSidebarSectionAllowed
(
sectionKey
)
);
};
// 获取特定区域允许的模块列表
const
getAllowedSidebarModules
=
(
sectionKey
)
=>
{
if
(
!
permissions
?.
sidebar_modules
)
return
[];
const
sectionPerms
=
permissions
.
sidebar_modules
[
sectionKey
];
if
(
sectionPerms
===
false
)
return
[];
if
(
!
sectionPerms
||
typeof
sectionPerms
!==
'object'
)
return
[];
return
Object
.
keys
(
sectionPerms
).
filter
(
moduleKey
=>
moduleKey
!==
'enabled'
&&
sectionPerms
[
moduleKey
]
===
true
);
};
return
{
permissions
,
loading
,
error
,
loadPermissions
,
hasSidebarSettingsPermission
,
isSidebarSectionAllowed
,
isSidebarModuleAllowed
,
getAllowedSidebarSections
,
getAllowedSidebarModules
,
};
};
export
default
useUserPermissions
;
web/src/i18n/locales/en.json
View file @
d2dcd8be
...
...
@@ -2017,5 +2017,63 @@
"查看密钥"
:
"View key"
,
"查看渠道密钥"
:
"View channel key"
,
"渠道密钥信息"
:
"Channel key information"
,
"密钥获取成功"
:
"Key acquisition successful"
"密钥获取成功"
:
"Key acquisition successful"
,
"顶栏管理"
:
"Header Management"
,
"控制顶栏模块显示状态,全局生效"
:
"Control header module display status, global effect"
,
"用户主页,展示系统信息"
:
"User homepage, displaying system information"
,
"用户控制面板,管理账户"
:
"User control panel for account management"
,
"模型广场"
:
"Model Marketplace"
,
"模型定价,需要登录访问"
:
"Model pricing, requires login to access"
,
"文档"
:
"Documentation"
,
"系统文档和帮助信息"
:
"System documentation and help information"
,
"关于系统的详细信息"
:
"Detailed information about the system"
,
"重置为默认"
:
"Reset to Default"
,
"保存设置"
:
"Save Settings"
,
"已重置为默认配置"
:
"Reset to default configuration"
,
"保存成功"
:
"Saved successfully"
,
"保存失败,请重试"
:
"Save failed, please try again"
,
"侧边栏管理(全局控制)"
:
"Sidebar Management (Global Control)"
,
"全局控制侧边栏区域和功能显示,管理员隐藏的功能用户无法启用"
:
"Global control of sidebar areas and functions, users cannot enable functions hidden by administrators"
,
"聊天区域"
:
"Chat Area"
,
"操练场和聊天功能"
:
"Playground and chat functions"
,
"操练场"
:
"Playground"
,
"AI模型测试环境"
:
"AI model testing environment"
,
"聊天"
:
"Chat"
,
"聊天会话管理"
:
"Chat session management"
,
"控制台区域"
:
"Console Area"
,
"数据管理和日志查看"
:
"Data management and log viewing"
,
"数据看板"
:
"Dashboard"
,
"系统数据统计"
:
"System data statistics"
,
"令牌管理"
:
"Token Management"
,
"API令牌管理"
:
"API token management"
,
"使用日志"
:
"Usage Logs"
,
"API使用记录"
:
"API usage records"
,
"绘图日志"
:
"Drawing Logs"
,
"绘图任务记录"
:
"Drawing task records"
,
"任务日志"
:
"Task Logs"
,
"系统任务记录"
:
"System task records"
,
"个人中心区域"
:
"Personal Center Area"
,
"用户个人功能"
:
"User personal functions"
,
"钱包管理"
:
"Wallet Management"
,
"余额充值管理"
:
"Balance recharge management"
,
"个人设置"
:
"Personal Settings"
,
"个人信息设置"
:
"Personal information settings"
,
"管理员区域"
:
"Administrator Area"
,
"系统管理功能"
:
"System management functions"
,
"渠道管理"
:
"Channel Management"
,
"API渠道配置"
:
"API channel configuration"
,
"模型管理"
:
"Model Management"
,
"AI模型配置"
:
"AI model configuration"
,
"兑换码管理"
:
"Redemption Code Management"
,
"兑换码生成管理"
:
"Redemption code generation management"
,
"用户管理"
:
"User Management"
,
"用户账户管理"
:
"User account management"
,
"系统设置"
:
"System Settings"
,
"系统参数配置"
:
"System parameter configuration"
,
"边栏设置"
:
"Sidebar Settings"
,
"您可以个性化设置侧边栏的要显示功能"
:
"You can customize the sidebar functions to display"
,
"保存边栏设置"
:
"Save Sidebar Settings"
,
"侧边栏设置保存成功"
:
"Sidebar settings saved successfully"
,
"需要登录访问"
:
"Require Login"
,
"开启后未登录用户无法访问模型广场"
:
"When enabled, unauthenticated users cannot access the model marketplace"
}
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
0 → 100644
View file @
d2dcd8be
/*
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
>
);
}
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx
0 → 100644
View file @
d2dcd8be
/*
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
>
);
}
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
0 → 100644
View file @
d2dcd8be
/*
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
>
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment