feat: add admin-controlled toggles for header theme/language display in system settings

This commit is contained in:
xiezhouwei 2026-06-22 11:46:31 +08:00
parent 3e79e956f0
commit 09d6a7305e
8 changed files with 207 additions and 7 deletions

View File

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

View File

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

View File

@ -35,6 +35,8 @@ const ActionButtons = ({
isLoading,
isMobile,
isSelfUseMode,
showThemeToggle = true,
showLanguageSelector = true,
logout,
navigate,
t,
@ -51,12 +53,16 @@ const ActionButtons = ({
/>
)}
{showThemeToggle && (
<ThemeToggle theme={theme} onThemeToggle={onThemeToggle} t={t} />
)}
{showLanguageSelector && (
<LanguageSelector
currentLang={currentLang}
onLanguageChange={onLanguageChange}
/>
)}
<UserArea
userState={userState}

View File

@ -43,6 +43,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
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}

View File

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

View File

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

View File

@ -5550,6 +5550,10 @@
"支持模型": "支持模型",
"速率限制": "速率限制",
"常见问题": "常见问题",
"本页目录": "本页目录"
"本页目录": "本页目录",
"主题切换": "主题切换",
"顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式",
"语言选择器": "语言选择器",
"顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言"
}
}

View File

@ -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) {
</Card>
</Col>
))}
{/* 主题切换开关 */}
<Col xs={24} sm={12} md={6} lg={6} xl={6}>
<Card
className='transition-shadow hover:shadow-md'
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' }}
>
<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',
}}
>
{t('主题切换')}
</div>
<Text
type='secondary'
size='small'
style={{
fontSize: '12px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4',
display: 'block',
}}
>
{t('顶栏显示主题切换按钮,允许用户切换亮色/暗色模式')}
</Text>
</div>
<div style={{ marginLeft: '16px' }}>
<Switch
checked={showThemeToggle}
onChange={handleShowThemeToggleChange}
size='default'
/>
</div>
</div>
</Card>
</Col>
{/* 语言选择器开关 */}
<Col xs={24} sm={12} md={6} lg={6} xl={6}>
<Card
className='transition-shadow hover:shadow-md'
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' }}
>
<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',
}}
>
{t('语言选择器')}
</div>
<Text
type='secondary'
size='small'
style={{
fontSize: '12px',
color: 'var(--semi-color-text-2)',
lineHeight: '1.4',
display: 'block',
}}
>
{t('顶栏显示语言选择器,允许用户切换界面语言')}
</Text>
</div>
<div style={{ marginLeft: '16px' }}>
<Switch
checked={showLanguageSelector}
onChange={handleShowLanguageSelectorChange}
size='default'
/>
</div>
</div>
</Card>
</Col>
</Row>
<div