瀏覽代碼

顶栏和侧边栏管理

增加用户体验
F。 6 月之前
父節點
當前提交
8809c44443

+ 4 - 0
controller/misc.go

@@ -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,

+ 181 - 2
controller/user.go

@@ -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)

+ 1 - 0
dto/user_settings.go

@@ -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 (

+ 86 - 0
model/user.go

@@ -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)))
 	}

+ 36 - 4
web/src/App.jsx

@@ -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

+ 4 - 1
web/src/components/layout/HeaderBar/Navigation.jsx

@@ -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}>

+ 4 - 1
web/src/components/layout/HeaderBar/index.jsx

@@ -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

+ 170 - 118
web/src/components/layout/SiderBar.jsx

@@ -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>

+ 16 - 0
web/src/components/settings/OperationSetting.jsx

@@ -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} />

+ 4 - 0
web/src/components/settings/PersonalSetting.jsx

@@ -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>
 

+ 352 - 8
web/src/components/settings/personal/cards/NotificationSettings.jsx

@@ -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>

+ 39 - 1
web/src/hooks/common/useHeaderBar.js

@@ -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,

+ 60 - 34
web/src/hooks/common/useNavigation.js

@@ -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 {

+ 220 - 0
web/src/hooks/common/useSidebar.js

@@ -0,0 +1,220 @@
+/*
+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
+  };
+};

+ 100 - 0
web/src/hooks/common/useUserPermissions.js

@@ -0,0 +1,100 @@
+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;

+ 59 - 1
web/src/i18n/locales/en.json

@@ -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"
 }

+ 326 - 0
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx

@@ -0,0 +1,326 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import React, { useEffect, useState, useContext } from 'react';
+import { Button, Card, Col, Form, Row, Switch, Typography } from '@douyinfe/semi-ui';
+import { API, showError, showSuccess } from '../../../helpers';
+import { useTranslation } from 'react-i18next';
+import { StatusContext } from '../../../context/Status';
+
+const { Text } = Typography;
+
+export default function SettingsHeaderNavModules(props) {
+  const { t } = useTranslation();
+  const [loading, setLoading] = useState(false);
+  const [statusState, statusDispatch] = useContext(StatusContext);
+  
+  // 顶栏模块管理状态
+  const [headerNavModules, setHeaderNavModules] = useState({
+    home: true,
+    console: true,
+    pricing: {
+      enabled: true,
+      requireAuth: false  // 默认不需要登录鉴权
+    },
+    docs: true,
+    about: true,
+  });
+
+  // 处理顶栏模块配置变更
+  function handleHeaderNavModuleChange(moduleKey) {
+    return (checked) => {
+      const newModules = { ...headerNavModules };
+      if (moduleKey === 'pricing') {
+        // 对于pricing模块,只更新enabled属性
+        newModules[moduleKey] = {
+          ...newModules[moduleKey],
+          enabled: checked
+        };
+      } else {
+        newModules[moduleKey] = checked;
+      }
+      setHeaderNavModules(newModules);
+    };
+  }
+
+  // 处理模型广场权限控制变更
+  function handlePricingAuthChange(checked) {
+    const newModules = { ...headerNavModules };
+    newModules.pricing = {
+      ...newModules.pricing,
+      requireAuth: checked
+    };
+    setHeaderNavModules(newModules);
+  }
+
+  // 重置顶栏模块为默认配置
+  function resetHeaderNavModules() {
+    const defaultModules = {
+      home: true,
+      console: true,
+      pricing: {
+        enabled: true,
+        requireAuth: false
+      },
+      docs: true,
+      about: true,
+    };
+    setHeaderNavModules(defaultModules);
+    showSuccess(t('已重置为默认配置'));
+  }
+
+  // 保存配置
+  async function onSubmit() {
+    setLoading(true);
+    try {
+      const res = await API.put('/api/option/', {
+        key: 'HeaderNavModules',
+        value: JSON.stringify(headerNavModules),
+      });
+      const { success, message } = res.data;
+      if (success) {
+        showSuccess(t('保存成功'));
+
+        // 立即更新StatusContext中的状态
+        statusDispatch({
+          type: 'set',
+          payload: {
+            ...statusState.status,
+            HeaderNavModules: JSON.stringify(headerNavModules)
+          }
+        });
+
+        // 刷新父组件状态
+        if (props.refresh) {
+          await props.refresh();
+        }
+      } else {
+        showError(message);
+      }
+    } catch (error) {
+      showError(t('保存失败,请重试'));
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  useEffect(() => {
+    // 从 props.options 中获取配置
+    if (props.options && props.options.HeaderNavModules) {
+      try {
+        const modules = JSON.parse(props.options.HeaderNavModules);
+
+        // 处理向后兼容性:如果pricing是boolean,转换为对象格式
+        if (typeof modules.pricing === 'boolean') {
+          modules.pricing = {
+            enabled: modules.pricing,
+            requireAuth: false  // 默认不需要登录鉴权
+          };
+        }
+
+        setHeaderNavModules(modules);
+      } catch (error) {
+        // 使用默认配置
+        const defaultModules = {
+          home: true,
+          console: true,
+          pricing: {
+            enabled: true,
+            requireAuth: false
+          },
+          docs: true,
+          about: true,
+        };
+        setHeaderNavModules(defaultModules);
+      }
+    }
+  }, [props.options]);
+
+  // 模块配置数据
+  const moduleConfigs = [
+    {
+      key: 'home',
+      title: t('首页'),
+      description: t('用户主页,展示系统信息')
+    },
+    {
+      key: 'console',
+      title: t('控制台'),
+      description: t('用户控制面板,管理账户')
+    },
+    {
+      key: 'pricing',
+      title: t('模型广场'),
+      description: t('模型定价,需要登录访问'),
+      hasSubConfig: true  // 标识该模块有子配置
+    },
+    {
+      key: 'docs',
+      title: t('文档'),
+      description: t('系统文档和帮助信息')
+    },
+    {
+      key: 'about',
+      title: t('关于'),
+      description: t('关于系统的详细信息')
+    }
+  ];
+
+  return (
+    <Card>
+      <Form.Section text={t('顶栏管理')} extraText={t('控制顶栏模块显示状态,全局生效')}>
+
+        <Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
+          {moduleConfigs.map((module) => (
+            <Col key={module.key} xs={24} sm={12} md={6} lg={6} xl={6}>
+              <Card
+                style={{
+                  borderRadius: '8px',
+                  border: '1px solid var(--semi-color-border)',
+                  transition: 'all 0.2s ease',
+                  background: 'var(--semi-color-bg-1)',
+                  minHeight: '80px'
+                }}
+                bodyStyle={{ padding: '16px' }}
+                hoverable
+              >
+                <div style={{
+                  display: 'flex',
+                  justifyContent: 'space-between',
+                  alignItems: 'center',
+                  height: '100%'
+                }}>
+                  <div style={{ flex: 1, textAlign: 'left' }}>
+                    <div style={{
+                      fontWeight: '600',
+                      fontSize: '14px',
+                      color: 'var(--semi-color-text-0)',
+                      marginBottom: '4px'
+                    }}>
+                      {module.title}
+                    </div>
+                    <Text
+                      type="secondary"
+                      size="small"
+                      style={{
+                        fontSize: '12px',
+                        color: 'var(--semi-color-text-2)',
+                        lineHeight: '1.4',
+                        display: 'block'
+                      }}
+                    >
+                      {module.description}
+                    </Text>
+                  </div>
+                  <div style={{ marginLeft: '16px' }}>
+                    <Switch
+                      checked={module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]}
+                      onChange={handleHeaderNavModuleChange(module.key)}
+                      size="default"
+                    />
+                  </div>
+                </div>
+
+                {/* 为模型广场添加权限控制子开关 */}
+                {module.key === 'pricing' && (module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]) && (
+                  <div style={{
+                    borderTop: '1px solid var(--semi-color-border)',
+                    marginTop: '12px',
+                    paddingTop: '12px'
+                  }}>
+                    <div style={{
+                      display: 'flex',
+                      justifyContent: 'space-between',
+                      alignItems: 'center'
+                    }}>
+                      <div style={{ flex: 1, textAlign: 'left' }}>
+                        <div style={{
+                          fontWeight: '500',
+                          fontSize: '12px',
+                          color: 'var(--semi-color-text-1)',
+                          marginBottom: '2px'
+                        }}>
+                          {t('需要登录访问')}
+                        </div>
+                        <Text
+                          type="secondary"
+                          size="small"
+                          style={{
+                            fontSize: '11px',
+                            color: 'var(--semi-color-text-2)',
+                            lineHeight: '1.4',
+                            display: 'block'
+                          }}
+                        >
+                          {t('开启后未登录用户无法访问模型广场')}
+                        </Text>
+                      </div>
+                      <div style={{ marginLeft: '16px' }}>
+                        <Switch
+                          checked={headerNavModules.pricing?.requireAuth || false}
+                          onChange={handlePricingAuthChange}
+                          size="small"
+                        />
+                      </div>
+                    </div>
+                  </div>
+                )}
+              </Card>
+            </Col>
+          ))}
+
+        </Row>
+
+        <div style={{
+          display: 'flex',
+          gap: '12px',
+          justifyContent: 'flex-start',
+          alignItems: 'center',
+          paddingTop: '8px',
+          borderTop: '1px solid var(--semi-color-border)'
+        }}>
+          <Button
+            size='default'
+            type='tertiary'
+            onClick={resetHeaderNavModules}
+            style={{
+              borderRadius: '6px',
+              fontWeight: '500'
+            }}
+          >
+            {t('重置为默认')}
+          </Button>
+          <Button
+            size='default'
+            type='primary'
+            onClick={onSubmit}
+            loading={loading}
+            style={{
+              borderRadius: '6px',
+              fontWeight: '500',
+              minWidth: '100px'
+            }}
+          >
+            {t('保存设置')}
+          </Button>
+        </div>
+      </Form.Section>
+    </Card>
+  );
+}

+ 362 - 0
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx

@@ -0,0 +1,362 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import React, { useState, useEffect, useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Card, Form, Button, Switch, Row, Col, Typography } from '@douyinfe/semi-ui';
+import { API, showSuccess, showError } from '../../../helpers';
+import { StatusContext } from '../../../context/Status';
+
+const { Text } = Typography;
+
+export default function SettingsSidebarModulesAdmin(props) {
+  const { t } = useTranslation();
+  const [loading, setLoading] = useState(false);
+  const [statusState, statusDispatch] = useContext(StatusContext);
+  
+  // 左侧边栏模块管理状态(管理员全局控制)
+  const [sidebarModulesAdmin, setSidebarModulesAdmin] = useState({
+    chat: {
+      enabled: true,
+      playground: true,
+      chat: true
+    },
+    console: {
+      enabled: true,
+      detail: true,
+      token: true,
+      log: true,
+      midjourney: true,
+      task: true
+    },
+    personal: {
+      enabled: true,
+      topup: true,
+      personal: true
+    },
+    admin: {
+      enabled: true,
+      channel: true,
+      models: true,
+      redemption: true,
+      user: true,
+      setting: true
+    }
+  });
+
+  // 处理区域级别开关变更
+  function handleSectionChange(sectionKey) {
+    return (checked) => {
+      const newModules = { 
+        ...sidebarModulesAdmin, 
+        [sectionKey]: { 
+          ...sidebarModulesAdmin[sectionKey], 
+          enabled: checked 
+        } 
+      };
+      setSidebarModulesAdmin(newModules);
+    };
+  }
+
+  // 处理功能级别开关变更
+  function handleModuleChange(sectionKey, moduleKey) {
+    return (checked) => {
+      const newModules = { 
+        ...sidebarModulesAdmin, 
+        [sectionKey]: { 
+          ...sidebarModulesAdmin[sectionKey], 
+          [moduleKey]: checked 
+        } 
+      };
+      setSidebarModulesAdmin(newModules);
+    };
+  }
+
+  // 重置为默认配置
+  function resetSidebarModules() {
+    const defaultModules = {
+      chat: {
+        enabled: true,
+        playground: true,
+        chat: true
+      },
+      console: {
+        enabled: true,
+        detail: true,
+        token: true,
+        log: true,
+        midjourney: true,
+        task: true
+      },
+      personal: {
+        enabled: true,
+        topup: true,
+        personal: true
+      },
+      admin: {
+        enabled: true,
+        channel: true,
+        models: true,
+        redemption: true,
+        user: true,
+        setting: true
+      }
+    };
+    setSidebarModulesAdmin(defaultModules);
+    showSuccess(t('已重置为默认配置'));
+  }
+
+  // 保存配置
+  async function onSubmit() {
+    setLoading(true);
+    try {
+      const res = await API.put('/api/option/', {
+        key: 'SidebarModulesAdmin',
+        value: JSON.stringify(sidebarModulesAdmin),
+      });
+      const { success, message } = res.data;
+      if (success) {
+        showSuccess(t('保存成功'));
+
+        // 立即更新StatusContext中的状态
+        statusDispatch({
+          type: 'set',
+          payload: {
+            ...statusState.status,
+            SidebarModulesAdmin: JSON.stringify(sidebarModulesAdmin)
+          }
+        });
+
+        // 刷新父组件状态
+        if (props.refresh) {
+          await props.refresh();
+        }
+      } else {
+        showError(message);
+      }
+    } catch (error) {
+      showError(t('保存失败,请重试'));
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  useEffect(() => {
+    // 从 props.options 中获取配置
+    if (props.options && props.options.SidebarModulesAdmin) {
+      try {
+        const modules = JSON.parse(props.options.SidebarModulesAdmin);
+        setSidebarModulesAdmin(modules);
+      } catch (error) {
+        // 使用默认配置
+        const defaultModules = {
+          chat: { enabled: true, playground: true, chat: true },
+          console: { enabled: true, detail: true, token: true, log: true, midjourney: true, task: true },
+          personal: { enabled: true, topup: true, personal: true },
+          admin: { enabled: true, channel: true, models: true, redemption: true, user: true, setting: true }
+        };
+        setSidebarModulesAdmin(defaultModules);
+      }
+    }
+  }, [props.options]);
+
+  // 区域配置数据
+  const sectionConfigs = [
+    {
+      key: 'chat',
+      title: t('聊天区域'),
+      description: t('操练场和聊天功能'),
+      modules: [
+        { key: 'playground', title: t('操练场'), description: t('AI模型测试环境') },
+        { key: 'chat', title: t('聊天'), description: t('聊天会话管理') }
+      ]
+    },
+    {
+      key: 'console',
+      title: t('控制台区域'),
+      description: t('数据管理和日志查看'),
+      modules: [
+        { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
+        { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
+        { key: 'log', title: t('使用日志'), description: t('API使用记录') },
+        { key: 'midjourney', title: t('绘图日志'), description: t('绘图任务记录') },
+        { key: 'task', title: t('任务日志'), description: t('系统任务记录') }
+      ]
+    },
+    {
+      key: 'personal',
+      title: t('个人中心区域'),
+      description: t('用户个人功能'),
+      modules: [
+        { key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
+        { key: 'personal', title: t('个人设置'), description: t('个人信息设置') }
+      ]
+    },
+    {
+      key: 'admin',
+      title: t('管理员区域'),
+      description: t('系统管理功能'),
+      modules: [
+        { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
+        { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
+        { key: 'redemption', title: t('兑换码管理'), description: t('兑换码生成管理') },
+        { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
+        { key: 'setting', title: t('系统设置'), description: t('系统参数配置') }
+      ]
+    }
+  ];
+
+  return (
+    <Card>
+      <Form.Section text={t('侧边栏管理(全局控制)')} extraText={t('全局控制侧边栏区域和功能显示,管理员隐藏的功能用户无法启用')}>
+
+        {sectionConfigs.map((section) => (
+          <div key={section.key} style={{ marginBottom: '32px' }}>
+            {/* 区域标题和总开关 */}
+            <div style={{
+              display: 'flex',
+              justifyContent: 'space-between',
+              alignItems: 'center',
+              marginBottom: '16px',
+              padding: '12px 16px',
+              backgroundColor: 'var(--semi-color-fill-0)',
+              borderRadius: '8px',
+              border: '1px solid var(--semi-color-border)'
+            }}>
+              <div>
+                <div style={{
+                  fontWeight: '600',
+                  fontSize: '16px',
+                  color: 'var(--semi-color-text-0)',
+                  marginBottom: '4px'
+                }}>
+                  {section.title}
+                </div>
+                <Text
+                  type="secondary"
+                  size="small"
+                  style={{
+                    fontSize: '12px',
+                    color: 'var(--semi-color-text-2)',
+                    lineHeight: '1.4'
+                  }}
+                >
+                  {section.description}
+                </Text>
+              </div>
+              <Switch
+                checked={sidebarModulesAdmin[section.key]?.enabled}
+                onChange={handleSectionChange(section.key)}
+                size="default"
+              />
+            </div>
+
+            {/* 功能模块网格 */}
+            <Row gutter={[16, 16]}>
+              {section.modules.map((module) => (
+                <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
+                  <Card
+                    bodyStyle={{ padding: '16px' }}
+                    hoverable
+                    style={{
+                      opacity: sidebarModulesAdmin[section.key]?.enabled ? 1 : 0.5,
+                      transition: 'opacity 0.2s'
+                    }}
+                  >
+                    <div style={{
+                      display: 'flex',
+                      justifyContent: 'space-between',
+                      alignItems: 'center',
+                      height: '100%'
+                    }}>
+                      <div style={{ flex: 1, textAlign: 'left' }}>
+                        <div style={{
+                          fontWeight: '600',
+                          fontSize: '14px',
+                          color: 'var(--semi-color-text-0)',
+                          marginBottom: '4px'
+                        }}>
+                          {module.title}
+                        </div>
+                        <Text
+                          type="secondary"
+                          size="small"
+                          style={{
+                            fontSize: '12px',
+                            color: 'var(--semi-color-text-2)',
+                            lineHeight: '1.4',
+                            display: 'block'
+                          }}
+                        >
+                          {module.description}
+                        </Text>
+                      </div>
+                      <div style={{ marginLeft: '16px' }}>
+                        <Switch
+                          checked={sidebarModulesAdmin[section.key]?.[module.key]}
+                          onChange={handleModuleChange(section.key, module.key)}
+                          size="default"
+                          disabled={!sidebarModulesAdmin[section.key]?.enabled}
+                        />
+                      </div>
+                    </div>
+                  </Card>
+                </Col>
+              ))}
+            </Row>
+          </div>
+        ))}
+
+        <div style={{
+          display: 'flex',
+          gap: '12px',
+          justifyContent: 'flex-start',
+          alignItems: 'center',
+          paddingTop: '8px',
+          borderTop: '1px solid var(--semi-color-border)'
+        }}>
+          <Button
+            size='default'
+            type='tertiary'
+            onClick={resetSidebarModules}
+            style={{
+              borderRadius: '6px',
+              fontWeight: '500'
+            }}
+          >
+            {t('重置为默认')}
+          </Button>
+          <Button
+            size='default'
+            type='primary'
+            onClick={onSubmit}
+            loading={loading}
+            style={{
+              borderRadius: '6px',
+              fontWeight: '500',
+              minWidth: '100px'
+            }}
+          >
+            {t('保存设置')}
+          </Button>
+        </div>
+      </Form.Section>
+    </Card>
+  );
+}

+ 404 - 0
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx

@@ -0,0 +1,404 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import { useState, useEffect, useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Card, Button, Switch, Typography, Row, Col, Avatar } from '@douyinfe/semi-ui';
+import { API, showSuccess, showError } from '../../../helpers';
+import { StatusContext } from '../../../context/Status';
+import { UserContext } from '../../../context/User';
+import { useUserPermissions } from '../../../hooks/common/useUserPermissions';
+import { useSidebar } from '../../../hooks/common/useSidebar';
+import { Settings } from 'lucide-react';
+
+const { Text } = Typography;
+
+export default function SettingsSidebarModulesUser() {
+  const { t } = useTranslation();
+  const [loading, setLoading] = useState(false);
+  const [statusState] = useContext(StatusContext);
+
+  // 使用后端权限验证替代前端角色判断
+  const {
+    permissions,
+    loading: permissionsLoading,
+    hasSidebarSettingsPermission,
+    isSidebarSectionAllowed,
+    isSidebarModuleAllowed,
+  } = useUserPermissions();
+
+  // 使用useSidebar钩子获取刷新方法
+  const { refreshUserConfig } = useSidebar();
+
+  // 如果没有边栏设置权限,不显示此组件
+  if (!permissionsLoading && !hasSidebarSettingsPermission()) {
+    return null;
+  }
+
+  // 权限加载中,显示加载状态
+  if (permissionsLoading) {
+    return null;
+  }
+  
+  // 根据用户权限生成默认配置
+  const generateDefaultConfig = () => {
+    const defaultConfig = {};
+
+    // 聊天区域 - 所有用户都可以访问
+    if (isSidebarSectionAllowed('chat')) {
+      defaultConfig.chat = {
+        enabled: true,
+        playground: isSidebarModuleAllowed('chat', 'playground'),
+        chat: isSidebarModuleAllowed('chat', 'chat')
+      };
+    }
+
+    // 控制台区域 - 所有用户都可以访问
+    if (isSidebarSectionAllowed('console')) {
+      defaultConfig.console = {
+        enabled: true,
+        detail: isSidebarModuleAllowed('console', 'detail'),
+        token: isSidebarModuleAllowed('console', 'token'),
+        log: isSidebarModuleAllowed('console', 'log'),
+        midjourney: isSidebarModuleAllowed('console', 'midjourney'),
+        task: isSidebarModuleAllowed('console', 'task')
+      };
+    }
+
+    // 个人中心区域 - 所有用户都可以访问
+    if (isSidebarSectionAllowed('personal')) {
+      defaultConfig.personal = {
+        enabled: true,
+        topup: isSidebarModuleAllowed('personal', 'topup'),
+        personal: isSidebarModuleAllowed('personal', 'personal')
+      };
+    }
+
+    // 管理员区域 - 只有管理员可以访问
+    if (isSidebarSectionAllowed('admin')) {
+      defaultConfig.admin = {
+        enabled: true,
+        channel: isSidebarModuleAllowed('admin', 'channel'),
+        models: isSidebarModuleAllowed('admin', 'models'),
+        redemption: isSidebarModuleAllowed('admin', 'redemption'),
+        user: isSidebarModuleAllowed('admin', 'user'),
+        setting: isSidebarModuleAllowed('admin', 'setting')
+      };
+    }
+
+    return defaultConfig;
+  };
+
+  // 用户个人左侧边栏模块设置
+  const [sidebarModulesUser, setSidebarModulesUser] = useState({});
+
+  // 管理员全局配置
+  const [adminConfig, setAdminConfig] = useState(null);
+
+  // 处理区域级别开关变更
+  function handleSectionChange(sectionKey) {
+    return (checked) => {
+      const newModules = { 
+        ...sidebarModulesUser, 
+        [sectionKey]: { 
+          ...sidebarModulesUser[sectionKey], 
+          enabled: checked 
+        } 
+      };
+      setSidebarModulesUser(newModules);
+      console.log('用户边栏区域配置变更:', sectionKey, checked, newModules);
+    };
+  }
+
+  // 处理功能级别开关变更
+  function handleModuleChange(sectionKey, moduleKey) {
+    return (checked) => {
+      const newModules = { 
+        ...sidebarModulesUser, 
+        [sectionKey]: { 
+          ...sidebarModulesUser[sectionKey], 
+          [moduleKey]: checked 
+        } 
+      };
+      setSidebarModulesUser(newModules);
+      console.log('用户边栏功能配置变更:', sectionKey, moduleKey, checked, newModules);
+    };
+  }
+
+  // 重置为默认配置(基于权限过滤)
+  function resetSidebarModules() {
+    const defaultConfig = generateDefaultConfig();
+    setSidebarModulesUser(defaultConfig);
+    showSuccess(t('已重置为默认配置'));
+    console.log('用户边栏配置重置为默认:', defaultConfig);
+  }
+
+  // 保存配置
+  async function onSubmit() {
+    setLoading(true);
+    try {
+      console.log('保存用户边栏配置:', sidebarModulesUser);
+      const res = await API.put('/api/user/self', {
+        sidebar_modules: JSON.stringify(sidebarModulesUser),
+      });
+      const { success, message } = res.data;
+      if (success) {
+        showSuccess(t('保存成功'));
+        console.log('用户边栏配置保存成功');
+
+        // 刷新useSidebar钩子中的用户配置,实现实时更新
+        await refreshUserConfig();
+        console.log('用户边栏配置已刷新,边栏将立即更新');
+      } else {
+        showError(message);
+        console.error('用户边栏配置保存失败:', message);
+      }
+    } catch (error) {
+      showError(t('保存失败,请重试'));
+      console.error('用户边栏配置保存异常:', error);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  // 统一的配置加载逻辑
+  useEffect(() => {
+    const loadConfigs = async () => {
+      try {
+        // 获取管理员全局配置
+        if (statusState?.status?.SidebarModulesAdmin) {
+          const adminConf = JSON.parse(statusState.status.SidebarModulesAdmin);
+          setAdminConfig(adminConf);
+          console.log('加载管理员边栏配置:', adminConf);
+        }
+
+        // 获取用户个人配置
+        const userRes = await API.get('/api/user/self');
+        if (userRes.data.success && userRes.data.data.sidebar_modules) {
+          let userConf;
+          // 检查sidebar_modules是字符串还是对象
+          if (typeof userRes.data.data.sidebar_modules === 'string') {
+            userConf = JSON.parse(userRes.data.data.sidebar_modules);
+          } else {
+            userConf = userRes.data.data.sidebar_modules;
+          }
+          console.log('从API加载的用户配置:', userConf);
+
+          // 确保用户配置也经过权限过滤
+          const filteredUserConf = {};
+          Object.keys(userConf).forEach(sectionKey => {
+            if (isSidebarSectionAllowed(sectionKey)) {
+              filteredUserConf[sectionKey] = { ...userConf[sectionKey] };
+              // 过滤不允许的模块
+              Object.keys(userConf[sectionKey]).forEach(moduleKey => {
+                if (moduleKey !== 'enabled' && !isSidebarModuleAllowed(sectionKey, moduleKey)) {
+                  delete filteredUserConf[sectionKey][moduleKey];
+                }
+              });
+            }
+          });
+          setSidebarModulesUser(filteredUserConf);
+          console.log('权限过滤后的用户配置:', filteredUserConf);
+        } else {
+          // 如果用户没有配置,使用权限过滤后的默认配置
+          const defaultConfig = generateDefaultConfig();
+          setSidebarModulesUser(defaultConfig);
+          console.log('用户无配置,使用默认配置:', defaultConfig);
+        }
+      } catch (error) {
+        console.error('加载边栏配置失败:', error);
+        // 出错时也使用默认配置
+        const defaultConfig = generateDefaultConfig();
+        setSidebarModulesUser(defaultConfig);
+      }
+    };
+
+    // 只有权限加载完成且有边栏设置权限时才加载配置
+    if (!permissionsLoading && hasSidebarSettingsPermission()) {
+      loadConfigs();
+    }
+  }, [statusState, permissionsLoading, hasSidebarSettingsPermission, isSidebarSectionAllowed, isSidebarModuleAllowed]);
+
+  // 检查功能是否被管理员允许
+  const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
+    if (!adminConfig) return true;
+    
+    if (moduleKey) {
+      return adminConfig[sectionKey]?.enabled && adminConfig[sectionKey]?.[moduleKey];
+    } else {
+      return adminConfig[sectionKey]?.enabled;
+    }
+  };
+
+  // 区域配置数据(根据后端权限过滤)
+  const sectionConfigs = [
+    {
+      key: 'chat',
+      title: t('聊天区域'),
+      description: t('操练场和聊天功能'),
+      modules: [
+        { key: 'playground', title: t('操练场'), description: t('AI模型测试环境') },
+        { key: 'chat', title: t('聊天'), description: t('聊天会话管理') }
+      ]
+    },
+    {
+      key: 'console',
+      title: t('控制台区域'),
+      description: t('数据管理和日志查看'),
+      modules: [
+        { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
+        { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
+        { key: 'log', title: t('使用日志'), description: t('API使用记录') },
+        { key: 'midjourney', title: t('绘图日志'), description: t('绘图任务记录') },
+        { key: 'task', title: t('任务日志'), description: t('系统任务记录') }
+      ]
+    },
+    {
+      key: 'personal',
+      title: t('个人中心区域'),
+      description: t('用户个人功能'),
+      modules: [
+        { key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
+        { key: 'personal', title: t('个人设置'), description: t('个人信息设置') }
+      ]
+    },
+    {
+      key: 'admin',
+      title: t('管理员区域'),
+      description: t('系统管理功能'),
+      modules: [
+        { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
+        { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
+        { key: 'redemption', title: t('兑换码管理'), description: t('兑换码生成管理') },
+        { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
+        { key: 'setting', title: t('系统设置'), description: t('系统参数配置') }
+      ]
+    }
+  ].filter(section => {
+    // 使用后端权限验证替代前端角色判断
+    return isSidebarSectionAllowed(section.key);
+  }).map(section => ({
+    ...section,
+    modules: section.modules.filter(module =>
+      isSidebarModuleAllowed(section.key, module.key)
+    )
+  })).filter(section =>
+    // 过滤掉没有可用模块的区域
+    section.modules.length > 0 && isAllowedByAdmin(section.key)
+  );
+
+  return (
+    <Card className='!rounded-2xl shadow-sm border-0'>
+      {/* 卡片头部 */}
+      <div className='flex items-center mb-4'>
+        <Avatar size='small' color='purple' className='mr-3 shadow-md'>
+          <Settings size={16} />
+        </Avatar>
+        <div>
+          <Typography.Text className='text-lg font-medium'>
+            {t('左侧边栏个人设置')}
+          </Typography.Text>
+          <div className='text-xs text-gray-600'>
+            {t('个性化设置左侧边栏的显示内容')}
+          </div>
+        </div>
+      </div>
+
+      <div className='mb-4'>
+        <Text type="secondary" className='text-sm text-gray-600'>
+          {t('您可以个性化设置侧边栏的要显示功能')}
+        </Text>
+      </div>
+
+        {sectionConfigs.map((section) => (
+          <div key={section.key} className='mb-6'>
+            {/* 区域标题和总开关 */}
+            <div className='flex justify-between items-center mb-4 p-4 bg-gray-50 rounded-xl border border-gray-200'>
+              <div>
+                <div className='font-semibold text-base text-gray-900 mb-1'>
+                  {section.title}
+                </div>
+                <Text className='text-xs text-gray-600'>
+                  {section.description}
+                </Text>
+              </div>
+              <Switch
+                checked={sidebarModulesUser[section.key]?.enabled}
+                onChange={handleSectionChange(section.key)}
+                size="default"
+              />
+            </div>
+
+            {/* 功能模块网格 */}
+            <Row gutter={[12, 12]}>
+              {section.modules.map((module) => (
+                <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
+                  <Card
+                    className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${
+                      sidebarModulesUser[section.key]?.enabled ? '' : 'opacity-50'
+                    }`}
+                    bodyStyle={{ padding: '16px' }}
+                    hoverable
+                  >
+                    <div className='flex justify-between items-center h-full'>
+                      <div className='flex-1 text-left'>
+                        <div className='font-semibold text-sm text-gray-900 mb-1'>
+                          {module.title}
+                        </div>
+                        <Text className='text-xs text-gray-600 leading-relaxed block'>
+                          {module.description}
+                        </Text>
+                      </div>
+                      <div className='ml-4'>
+                        <Switch
+                          checked={sidebarModulesUser[section.key]?.[module.key]}
+                          onChange={handleModuleChange(section.key, module.key)}
+                          size="default"
+                          disabled={!sidebarModulesUser[section.key]?.enabled}
+                        />
+                      </div>
+                    </div>
+                  </Card>
+                </Col>
+              ))}
+            </Row>
+          </div>
+        ))}
+
+      {/* 底部按钮 */}
+      <div className='flex justify-end gap-3 mt-6 pt-4 border-t border-gray-200'>
+        <Button
+          type='tertiary'
+          onClick={resetSidebarModules}
+          className='!rounded-lg'
+        >
+          {t('重置为默认')}
+        </Button>
+        <Button
+          type='primary'
+          onClick={onSubmit}
+          loading={loading}
+          className='!rounded-lg'
+        >
+          {t('保存设置')}
+        </Button>
+      </div>
+    </Card>
+  );
+}