feat: add admin-controlled toggles for header theme/language display in system settings
This commit is contained in:
parent
3e79e956f0
commit
09d6a7305e
|
|
@ -117,6 +117,8 @@ func GetStatus(c *gin.Context) {
|
||||||
|
|
||||||
// 模块管理配置
|
// 模块管理配置
|
||||||
"HeaderNavModules": common.OptionMap["HeaderNavModules"],
|
"HeaderNavModules": common.OptionMap["HeaderNavModules"],
|
||||||
|
"HeaderShowThemeToggle": common.OptionMap["HeaderShowThemeToggle"] != "false",
|
||||||
|
"HeaderShowLanguageSelector": common.OptionMap["HeaderShowLanguageSelector"] != "false",
|
||||||
"SidebarModulesByRole": common.OptionMap["SidebarModulesByRole"],
|
"SidebarModulesByRole": common.OptionMap["SidebarModulesByRole"],
|
||||||
|
|
||||||
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
|
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,9 @@ func InitOptionMap() {
|
||||||
common.OptionMap[k] = v
|
common.OptionMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
common.OptionMap["HeaderShowThemeToggle"] = strconv.FormatBool(true)
|
||||||
|
common.OptionMap["HeaderShowLanguageSelector"] = strconv.FormatBool(true)
|
||||||
|
|
||||||
common.OptionMapRWMutex.Unlock()
|
common.OptionMapRWMutex.Unlock()
|
||||||
loadOptionsFromDatabase()
|
loadOptionsFromDatabase()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ const ActionButtons = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
isMobile,
|
isMobile,
|
||||||
isSelfUseMode,
|
isSelfUseMode,
|
||||||
|
showThemeToggle = true,
|
||||||
|
showLanguageSelector = true,
|
||||||
logout,
|
logout,
|
||||||
navigate,
|
navigate,
|
||||||
t,
|
t,
|
||||||
|
|
@ -51,12 +53,16 @@ const ActionButtons = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ThemeToggle theme={theme} onThemeToggle={onThemeToggle} t={t} />
|
{showThemeToggle && (
|
||||||
|
<ThemeToggle theme={theme} onThemeToggle={onThemeToggle} t={t} />
|
||||||
|
)}
|
||||||
|
|
||||||
<LanguageSelector
|
{showLanguageSelector && (
|
||||||
currentLang={currentLang}
|
<LanguageSelector
|
||||||
onLanguageChange={onLanguageChange}
|
currentLang={currentLang}
|
||||||
/>
|
onLanguageChange={onLanguageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<UserArea
|
<UserArea
|
||||||
userState={userState}
|
userState={userState}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||||
docsNav,
|
docsNav,
|
||||||
isDemoSiteMode,
|
isDemoSiteMode,
|
||||||
isConsoleRoute,
|
isConsoleRoute,
|
||||||
|
showThemeToggle,
|
||||||
|
showLanguageSelector,
|
||||||
theme,
|
theme,
|
||||||
headerNavModules,
|
headerNavModules,
|
||||||
pricingRequireAuth,
|
pricingRequireAuth,
|
||||||
|
|
@ -134,6 +136,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
isSelfUseMode={isSelfUseMode}
|
isSelfUseMode={isSelfUseMode}
|
||||||
|
showThemeToggle={showThemeToggle}
|
||||||
|
showLanguageSelector={showLanguageSelector}
|
||||||
logout={logout}
|
logout={logout}
|
||||||
navigate={navigate}
|
navigate={navigate}
|
||||||
t={t}
|
t={t}
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,10 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||||
|
|
||||||
const isConsoleRoute = location.pathname.startsWith('/console');
|
const isConsoleRoute = location.pathname.startsWith('/console');
|
||||||
|
|
||||||
|
// 读取顶栏主题切换和语言选择器开关配置
|
||||||
|
const showThemeToggle = statusState?.status?.HeaderShowThemeToggle !== false;
|
||||||
|
const showLanguageSelector = statusState?.status?.HeaderShowLanguageSelector !== false;
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const actualTheme = useActualTheme();
|
const actualTheme = useActualTheme();
|
||||||
const setTheme = useSetTheme();
|
const setTheme = useSetTheme();
|
||||||
|
|
@ -251,6 +255,8 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||||
docsNav,
|
docsNav,
|
||||||
isDemoSiteMode,
|
isDemoSiteMode,
|
||||||
isConsoleRoute,
|
isConsoleRoute,
|
||||||
|
showThemeToggle,
|
||||||
|
showLanguageSelector,
|
||||||
theme,
|
theme,
|
||||||
drawerOpen,
|
drawerOpen,
|
||||||
headerNavModules,
|
headerNavModules,
|
||||||
|
|
|
||||||
|
|
@ -5691,6 +5691,10 @@
|
||||||
"支持模型": "Supported Models",
|
"支持模型": "Supported Models",
|
||||||
"速率限制": "Rate Limits",
|
"速率限制": "Rate Limits",
|
||||||
"常见问题": "FAQ",
|
"常见问题": "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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5550,6 +5550,10 @@
|
||||||
"支持模型": "支持模型",
|
"支持模型": "支持模型",
|
||||||
"速率限制": "速率限制",
|
"速率限制": "速率限制",
|
||||||
"常见问题": "常见问题",
|
"常见问题": "常见问题",
|
||||||
"本页目录": "本页目录"
|
"本页目录": "本页目录",
|
||||||
|
"主题切换": "主题切换",
|
||||||
|
"顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式",
|
||||||
|
"语言选择器": "语言选择器",
|
||||||
|
"顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +54,8 @@ export default function SettingsHeaderNavModules(props) {
|
||||||
benchmark: true,
|
benchmark: true,
|
||||||
docs: true,
|
docs: true,
|
||||||
});
|
});
|
||||||
|
const [showThemeToggle, setShowThemeToggle] = useState(true);
|
||||||
|
const [showLanguageSelector, setShowLanguageSelector] = useState(true);
|
||||||
|
|
||||||
// 处理顶栏模块配置变更
|
// 处理顶栏模块配置变更
|
||||||
function handleHeaderNavModuleChange(moduleKey) {
|
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) {
|
function handleBlurPricingChange(moduleKey) {
|
||||||
return async (checked) => {
|
return async (checked) => {
|
||||||
|
|
@ -224,6 +272,17 @@ export default function SettingsHeaderNavModules(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
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 中获取配置
|
// 从 props.options 中获取配置
|
||||||
if (props.options && props.options.HeaderNavModules) {
|
if (props.options && props.options.HeaderNavModules) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -547,6 +606,118 @@ export default function SettingsHeaderNavModules(props) {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</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>
|
</Row>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue