diff --git a/controller/misc.go b/controller/misc.go index 5f2987f..067b4a4 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -117,6 +117,8 @@ func GetStatus(c *gin.Context) { // 模块管理配置 "HeaderNavModules": common.OptionMap["HeaderNavModules"], + "HeaderShowThemeToggle": common.OptionMap["HeaderShowThemeToggle"] != "false", + "HeaderShowLanguageSelector": common.OptionMap["HeaderShowLanguageSelector"] != "false", "SidebarModulesByRole": common.OptionMap["SidebarModulesByRole"], "oidc_enabled": system_setting.GetOIDCSettings().Enabled, diff --git a/model/option.go b/model/option.go index a0ec28a..d1be35f 100644 --- a/model/option.go +++ b/model/option.go @@ -290,6 +290,9 @@ func InitOptionMap() { common.OptionMap[k] = v } + common.OptionMap["HeaderShowThemeToggle"] = strconv.FormatBool(true) + common.OptionMap["HeaderShowLanguageSelector"] = strconv.FormatBool(true) + common.OptionMapRWMutex.Unlock() loadOptionsFromDatabase() } diff --git a/web/src/components/layout/headerbar/ActionButtons.jsx b/web/src/components/layout/headerbar/ActionButtons.jsx index 5e32f28..84becd7 100644 --- a/web/src/components/layout/headerbar/ActionButtons.jsx +++ b/web/src/components/layout/headerbar/ActionButtons.jsx @@ -35,6 +35,8 @@ const ActionButtons = ({ isLoading, isMobile, isSelfUseMode, + showThemeToggle = true, + showLanguageSelector = true, logout, navigate, t, @@ -51,12 +53,16 @@ const ActionButtons = ({ /> )} - + {showThemeToggle && ( + + )} - + {showLanguageSelector && ( + + )} { docsNav, isDemoSiteMode, isConsoleRoute, + showThemeToggle, + showLanguageSelector, theme, headerNavModules, pricingRequireAuth, @@ -134,6 +136,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { isLoading={isLoading} isMobile={isMobile} isSelfUseMode={isSelfUseMode} + showThemeToggle={showThemeToggle} + showLanguageSelector={showLanguageSelector} logout={logout} navigate={navigate} t={t} diff --git a/web/src/hooks/common/useHeaderBar.js b/web/src/hooks/common/useHeaderBar.js index f0b31da..2ff669f 100644 --- a/web/src/hooks/common/useHeaderBar.js +++ b/web/src/hooks/common/useHeaderBar.js @@ -107,6 +107,10 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { const isConsoleRoute = location.pathname.startsWith('/console'); + // 读取顶栏主题切换和语言选择器开关配置 + const showThemeToggle = statusState?.status?.HeaderShowThemeToggle !== false; + const showLanguageSelector = statusState?.status?.HeaderShowLanguageSelector !== false; + const theme = useTheme(); const actualTheme = useActualTheme(); const setTheme = useSetTheme(); @@ -251,6 +255,8 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { docsNav, isDemoSiteMode, isConsoleRoute, + showThemeToggle, + showLanguageSelector, theme, drawerOpen, headerNavModules, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index df2217d..057d9c3 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5691,6 +5691,10 @@ "支持模型": "Supported Models", "速率限制": "Rate Limits", "常见问题": "FAQ", - "本页目录": "On this page" + "本页目录": "On this page", + "主题切换": "Theme Toggle", + "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "Show theme toggle in header bar, allowing users to switch between light/dark mode", + "语言选择器": "Language Selector", + "顶栏显示语言选择器,允许用户切换界面语言": "Show language selector in header bar, allowing users to switch interface language" } } \ No newline at end of file diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 0250766..ae8ed1d 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -5550,6 +5550,10 @@ "支持模型": "支持模型", "速率限制": "速率限制", "常见问题": "常见问题", - "本页目录": "本页目录" + "本页目录": "本页目录", + "主题切换": "主题切换", + "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式", + "语言选择器": "语言选择器", + "顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言" } } \ No newline at end of file diff --git a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx index 8d0e742..9967336 100644 --- a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx +++ b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx @@ -54,6 +54,8 @@ export default function SettingsHeaderNavModules(props) { benchmark: true, docs: true, }); + const [showThemeToggle, setShowThemeToggle] = useState(true); + const [showLanguageSelector, setShowLanguageSelector] = useState(true); // 处理顶栏模块配置变更 function handleHeaderNavModuleChange(moduleKey) { @@ -127,6 +129,52 @@ export default function SettingsHeaderNavModules(props) { })(); } + // 处理主题切换开关变更(即时保存) + async function handleShowThemeToggleChange(checked) { + setShowThemeToggle(checked); + const key = 'HeaderShowThemeToggle'; + const value = String(checked); + try { + const res = await API.put('/api/option/', { key, value }); + if (res.data.success) { + showSuccess(t('保存成功')); + statusDispatch({ + type: 'set', + payload: { ...statusState.status, [key]: value }, + }); + } else { + showError(res.data.message); + setShowThemeToggle(!checked); + } + } catch { + showError(t('保存失败,请重试')); + setShowThemeToggle(!checked); + } + } + + // 处理语言选择器开关变更(即时保存) + async function handleShowLanguageSelectorChange(checked) { + setShowLanguageSelector(checked); + const key = 'HeaderShowLanguageSelector'; + const value = String(checked); + try { + const res = await API.put('/api/option/', { key, value }); + if (res.data.success) { + showSuccess(t('保存成功')); + statusDispatch({ + type: 'set', + payload: { ...statusState.status, [key]: value }, + }); + } else { + showError(res.data.message); + setShowLanguageSelector(!checked); + } + } catch { + showError(t('保存失败,请重试')); + setShowLanguageSelector(!checked); + } + } + // 处理模糊价格开关变更(即时保存) function handleBlurPricingChange(moduleKey) { return async (checked) => { @@ -224,6 +272,17 @@ export default function SettingsHeaderNavModules(props) { } useEffect(() => { + // 从 props.options 中获取主题切换和语言选择器设置 + if (props.options) { + const themeVal = props.options.HeaderShowThemeToggle; + if (themeVal !== undefined && themeVal !== null) { + setShowThemeToggle(themeVal === 'true' || themeVal === true); + } + const langVal = props.options.HeaderShowLanguageSelector; + if (langVal !== undefined && langVal !== null) { + setShowLanguageSelector(langVal === 'true' || langVal === true); + } + } // 从 props.options 中获取配置 if (props.options && props.options.HeaderNavModules) { try { @@ -547,6 +606,118 @@ export default function SettingsHeaderNavModules(props) { ))} + + {/* 主题切换开关 */} + + +
+
+
+ {t('主题切换')} +
+ + {t('顶栏显示主题切换按钮,允许用户切换亮色/暗色模式')} + +
+
+ +
+
+
+ + + {/* 语言选择器开关 */} + + +
+
+
+ {t('语言选择器')} +
+ + {t('顶栏显示语言选择器,允许用户切换界面语言')} + +
+
+ +
+
+
+