diff --git a/qiming/src/pages/OpenApp/BaseTemplate/openAppTheme.ts b/qiming/src/constants/openAppTheme.constants.ts similarity index 70% rename from qiming/src/pages/OpenApp/BaseTemplate/openAppTheme.ts rename to qiming/src/constants/openAppTheme.constants.ts index d68d917c..40859c60 100644 --- a/qiming/src/pages/OpenApp/BaseTemplate/openAppTheme.ts +++ b/qiming/src/constants/openAppTheme.constants.ts @@ -1,4 +1,5 @@ export const OPEN_APP_THEME_STORAGE_KEY = 'qiming-open-app-theme'; +export const OPEN_APP_THEME_CHANGED_EVENT = 'qiming-open-app-theme-changed'; export const openAppThemes = [ { @@ -36,3 +37,16 @@ export const getInitialOpenAppTheme = (): OpenAppTheme => { ? storedTheme : DEFAULT_OPEN_APP_THEME; }; + +export const setOpenAppThemePreference = (theme: OpenAppTheme) => { + if (typeof window === 'undefined') { + return; + } + + window.localStorage.setItem(OPEN_APP_THEME_STORAGE_KEY, theme); + window.dispatchEvent( + new CustomEvent(OPEN_APP_THEME_CHANGED_EVENT, { + detail: theme, + }), + ); +}; diff --git a/qiming/src/layouts/Setting/ThemeSwitchPanel.less b/qiming/src/layouts/Setting/ThemeSwitchPanel.less index dd5e98ee..41ad70a1 100644 --- a/qiming/src/layouts/Setting/ThemeSwitchPanel.less +++ b/qiming/src/layouts/Setting/ThemeSwitchPanel.less @@ -34,3 +34,133 @@ } } } + +.panelTitle { + margin: @padding 0; + font-size: @fontSize; + font-weight: @fontWeightStrong; + color: @colorText; +} + +.openAppThemePanel { + h4 { + margin: 0 0 @marginSm 0; + font-size: @fontSizeSm; + color: @colorTextSecondary; + font-weight: normal; + } +} + +.openAppThemeOptions { + display: grid; + grid-template-columns: repeat(3, minmax(120px, 172px)); + gap: @paddingSm; +} + +.openAppThemeOption { + display: flex; + flex-direction: column; + align-items: center; + gap: @marginXxs; + min-width: 0; + padding: @paddingXs; + border: 2px solid transparent; + border-radius: @borderRadiusLg; + background: transparent; + color: @colorTextSecondary; + cursor: pointer; + transition: border-color 0.3s @motionEaseOut, color 0.3s @motionEaseOut; + + &:hover, + &.active { + border-color: @colorPrimary; + color: @colorText; + } +} + +.openAppThemePreview { + display: flex; + width: 116px; + height: 64px; + overflow: hidden; + border: 1px solid rgba(0, 101, 105, 18%); + border-radius: @borderRadiusSm; + background: #fff; +} + +.previewRail { + width: 28px; + background: #006569; +} + +.previewBody { + flex: 1; + padding: @paddingXs; + background: #fbfffd; + + span { + display: block; + width: 46px; + height: 7px; + margin-bottom: 6px; + border-radius: 999px; + background: rgba(0, 101, 105, 22%); + + &:first-child { + width: 60px; + background: rgba(0, 101, 105, 42%); + } + } +} + +.dispatch { + .openAppThemePreview { + border-color: rgba(126, 232, 196, 36%); + background: #003f42; + } + + .previewRail { + background: #002d30; + } + + .previewBody { + background: linear-gradient(180deg, rgba(0, 166, 122, 22%), #003f42); + + span { + background: rgba(242, 255, 252, 28%); + + &:first-child { + background: #00a67a; + } + } + } +} + +.light { +} + +.portal { + .openAppThemePreview { + border-top: 4px solid #006569; + background: #f4fbf8; + } + + .previewRail { + background: #ffffff; + border-right: 1px solid rgba(0, 101, 105, 18%); + } + + .previewBody { + background: #f4fbf8; + } +} + +.openAppThemeLabel { + max-width: 132px; + overflow: hidden; + font-size: @fontSizeSm; + line-height: @lineHeight; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/qiming/src/layouts/Setting/ThemeSwitchPanel.tsx b/qiming/src/layouts/Setting/ThemeSwitchPanel.tsx index dfbd7c20..05a37122 100644 --- a/qiming/src/layouts/Setting/ThemeSwitchPanel.tsx +++ b/qiming/src/layouts/Setting/ThemeSwitchPanel.tsx @@ -12,6 +12,12 @@ import BackgroundImagePanel from '@/components/business-component/ThemeConfig/BackgroundImagePanel'; import NavigationStylePanel from '@/components/business-component/ThemeConfig/NavigationStylePanel'; import ThemeColorPanel from '@/components/business-component/ThemeConfig/ThemeColorPanel'; +import { + OpenAppTheme, + getInitialOpenAppTheme, + openAppThemes, + setOpenAppThemePreference, +} from '@/constants/openAppTheme.constants'; import { backgroundConfigs, STORAGE_KEYS } from '@/constants/theme.constants'; import { useUnifiedTheme } from '@/hooks/useUnifiedTheme'; import { dict } from '@/services/i18nRuntime'; @@ -21,7 +27,7 @@ import { ThemeLayoutColorStyle } from '@/types/enums/theme'; import { TenantThemeConfig } from '@/types/tenant'; import { message } from 'antd'; import classNames from 'classnames'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styles from './ThemeSwitchPanel.less'; const cx = classNames.bind(styles); @@ -56,6 +62,9 @@ const ThemeSwitchPanel: React.FC = ({ updateNavigationStyle, toggleNavigationTheme, } = useUnifiedTheme(); + const [openAppTheme, setOpenAppTheme] = useState( + getInitialOpenAppTheme, + ); // 转换背景配置为组件需要的格式 const backgroundImages: BackgroundImage[] = useMemo(() => { @@ -183,6 +192,12 @@ const ThemeSwitchPanel: React.FC = ({ } }; + const handleOpenAppThemeChange = (theme: OpenAppTheme) => { + setOpenAppTheme(theme); + setOpenAppThemePreference(theme); + setHasUserSwitchThemeFlag(); + }; + return (
@@ -209,6 +224,40 @@ const ThemeSwitchPanel: React.FC = ({ />
+
+
+

启明应用外观

+

国网绿风格

+
+ {openAppThemes.map((theme) => ( + + ))} +
+
+
+
{ }, [agentId]); useEffect(() => { - localStorage.setItem(OPEN_APP_THEME_STORAGE_KEY, openAppTheme); - }, [openAppTheme]); + const handleOpenAppThemeChange = (event: Event) => { + const nextTheme = (event as CustomEvent).detail; + setOpenAppTheme(nextTheme); + }; + + window.addEventListener( + OPEN_APP_THEME_CHANGED_EVENT, + handleOpenAppThemeChange, + ); + + return () => { + window.removeEventListener( + OPEN_APP_THEME_CHANGED_EVENT, + handleOpenAppThemeChange, + ); + }; + }, []); // 已发布的智能体详情接口 const { run: runDetail } = useRequest(apiPublishedAgentInfo, { @@ -522,7 +538,10 @@ const BaseTemplate: React.FC = () => { })} title={theme.title} aria-pressed={openAppTheme === theme.key} - onClick={() => setOpenAppTheme(theme.key)} + onClick={() => { + setOpenAppTheme(theme.key); + setOpenAppThemePreference(theme.key); + }} > {theme.label} diff --git a/qiming/tests/openAppTheme.test.ts b/qiming/tests/openAppTheme.test.ts index 0fe0eeb1..77ad1028 100644 --- a/qiming/tests/openAppTheme.test.ts +++ b/qiming/tests/openAppTheme.test.ts @@ -3,7 +3,8 @@ import { getInitialOpenAppTheme, isOpenAppTheme, openAppThemes, -} from '@/pages/OpenApp/BaseTemplate/openAppTheme'; + setOpenAppThemePreference, +} from '@/constants/openAppTheme.constants'; describe('openAppTheme', () => { beforeEach(() => { @@ -42,4 +43,11 @@ describe('openAppTheme', () => { localStorage.setItem(OPEN_APP_THEME_STORAGE_KEY, 'unknown'); expect(getInitialOpenAppTheme()).toBe('dispatch'); }); + + test('stores theme preference through the shared setter', () => { + setOpenAppThemePreference('light'); + + expect(localStorage.getItem(OPEN_APP_THEME_STORAGE_KEY)).toBe('light'); + expect(getInitialOpenAppTheme()).toBe('light'); + }); });