diff --git a/src/@core/components/LocaleSwitcher.vue b/src/@core/components/LocaleSwitcher.vue deleted file mode 100644 index 48346469..00000000 --- a/src/@core/components/LocaleSwitcher.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - diff --git a/src/@core/components/ThemeSwitcher.vue b/src/@core/components/ThemeSwitcher.vue deleted file mode 100644 index 3484e090..00000000 --- a/src/@core/components/ThemeSwitcher.vue +++ /dev/null @@ -1,184 +0,0 @@ - - - diff --git a/src/layouts/components/DefaultLayout.vue b/src/layouts/components/DefaultLayout.vue index 5542a21d..d55c26bb 100644 --- a/src/layouts/components/DefaultLayout.vue +++ b/src/layouts/components/DefaultLayout.vue @@ -3,7 +3,6 @@ import VerticalNavSectionTitle from '@/@layouts/components/VerticalNavSectionTit import VerticalNavLayout from '@layouts/components/VerticalNavLayout.vue' import VerticalNavLink from '@layouts/components/VerticalNavLink.vue' import Footer from '@/layouts/components/Footer.vue' -import NavbarActions from '@/layouts/components/NavbarActions.vue' import UserNofification from '@/layouts/components/UserNotification.vue' import SearchBar from '@/layouts/components/SearchBar.vue' import ShortcutBar from '@/layouts/components/ShortcutBar.vue' @@ -82,8 +81,6 @@ onMounted(() => { - - diff --git a/src/layouts/components/NavbarActions.vue b/src/layouts/components/NavbarActions.vue deleted file mode 100644 index ad7c8277..00000000 --- a/src/layouts/components/NavbarActions.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/src/layouts/components/UserProfile.vue b/src/layouts/components/UserProfile.vue index be53d2c8..4ba2420f 100644 --- a/src/layouts/components/UserProfile.vue +++ b/src/layouts/components/UserProfile.vue @@ -8,6 +8,12 @@ import ProgressDialog from '@/components/dialog/ProgressDialog.vue' import UserAuthDialog from '@/components/dialog/UserAuthDialog.vue' import { useAuthStore, useUserStore } from '@/stores' import { useI18n } from 'vue-i18n' +import { useDisplay, useTheme } from 'vuetify' +import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n' +import { checkPrefersColorSchemeIsDark } from '@/@core/utils' +import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n' +import { saveLocalTheme } from '@/@core/utils/theme' +import type { ThemeSwitcherTheme } from '@layouts/types' // 认证 Store const authStore = useAuthStore() @@ -15,6 +21,8 @@ const authStore = useAuthStore() const userStore = useUserStore() // 国际化 const { t } = useI18n() +// 显示器 +const display = useDisplay() // 确认框 const createConfirm = useConfirm() @@ -31,6 +39,18 @@ const siteAuthDialog = ref(false) // 重启确认对话框 const restartDialog = ref(false) +// 自定义CSS弹窗 +const cssDialog = ref(false) + +// 主题菜单是否显示 +const showThemeMenu = ref(false) + +// 语言菜单是否显示 +const showLanguageMenu = ref(false) + +// 自定义CSS +const customCSS = ref('') + // 执行注销操作 function logout() { // 清除登录状态信息 @@ -82,13 +102,172 @@ const superUser = computed(() => userStore.superUser) const userName = computed(() => userStore.userName) const avatar = computed(() => userStore.avatar || avatar1) const userLevel = computed(() => userStore.level) + +// 主题相关功能 +const { name: themeName, global: globalTheme } = useTheme() +const savedTheme = ref(localStorage.getItem('theme') ?? themeName) +const currentThemeName = ref(savedTheme.value) + +const themes: ThemeSwitcherTheme[] = [ + { + name: 'auto', + title: t('theme.auto'), + icon: 'mdi-laptop', + }, + { + name: 'light', + title: t('theme.light'), + icon: 'mdi-weather-sunny', + }, + { + name: 'dark', + title: t('theme.dark'), + icon: 'mdi-weather-night', + }, + { + name: 'purple', + title: t('theme.purple'), + icon: 'mdi-brightness-4', + }, + { + name: 'transparent', + title: t('theme.transparent'), + icon: 'mdi-gradient-horizontal', + }, +] + +// 编辑器主题 +const editorTheme = computed(() => (currentThemeName.value === 'light' ? 'github' : 'monokai')) + +// 更新主题 +function updateTheme() { + const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light' + const theme = currentThemeName.value === 'auto' ? autoTheme : currentThemeName.value + globalTheme.name.value = theme + // 保存原始主题设置,而不是计算后的值 + savedTheme.value = currentThemeName.value + // 保存主题到本地 + saveLocalTheme(currentThemeName.value, globalTheme) + // 刷新页面 + location.reload() +} + +// 切换主题 +function changeTheme(theme: string) { + currentThemeName.value = theme + showThemeMenu.value = false + // 保存主题到服务端 + try { + api.post('/user/config/Layout', { + theme, + }) + } catch (e) { + console.error(e) + } +} + +// 获取自定义 CSS +async function getCustomCSS() { + try { + const result: { [key: string]: any } = await api.get('system/setting/UserCustomCSS') + if (result && result.success && result.data?.value) { + customCSS.value = result.data?.value ?? '' + if (customCSS.value) { + const style = document.createElement('style') + style.innerHTML = result.data?.value ?? '' + document.head.appendChild(style) + } + } + } catch (error) { + console.error(error) + } +} + +// 保存自定义 CSS +async function saveCustomCSS() { + cssDialog.value = false + try { + const result: { [key: string]: any } = await api.post('system/setting/UserCustomCSS', customCSS.value, { + headers: { + 'Content-Type': 'text/plain', + }, + }) + + if (result.success) $toast.success(t('theme.customCssSaveSuccess')) + } catch (e) { + console.error(t('theme.customCssSaveFailed')) + } +} + +// 监听主题变化 +watch( + () => currentThemeName.value, + () => updateTheme(), +) + +// 监听系统主题变化 +try { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme) +} catch (e) { + console.error(t('theme.deviceNotSupport')) +} + +// 语言相关功能 +const currentLocale = ref(getCurrentLocale()) + +// 支持的语言列表 +const locales = computed(() => { + return Object.entries(SUPPORTED_LOCALES).map(([key, locale]) => ({ + value: key as SupportedLocale, + title: locale.title, + flag: locale.flag, + icon: `flag-${key.split('-')[0]}`, + })) +}) + +// 切换语言 +async function changeLocale(locale: SupportedLocale) { + showLanguageMenu.value = false + try { + await setI18nLanguage(locale) + currentLocale.value = locale + // 刷新页面 + window.location.reload() + } catch (error) { + console.error(error) + } +} + +// 获取当前语言图标 +const getCurrentIcon = computed(() => { + const locale = locales.value.find(l => l.value === currentLocale.value) + return locale?.flag || '🌐' +}) + +// 获取当前主题图标 +const getThemeIcon = computed(() => { + const theme = themes.find(t => t.name === currentThemeName.value) + return theme?.icon || 'mdi-laptop' +}) + +onMounted(() => { + getCustomCSS() +})