From 694f07234d49a1d53b96a5d6a3f4996b31cb7df4 Mon Sep 17 00:00:00 2001 From: steve <1050403040@qq.com> Date: Thu, 4 Sep 2025 15:53:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E6=B7=BB=E5=8A=A0=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20ThemeToggle=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E5=9C=A8=E7=95=8C=E9=9D=A2=E4=B8=8A=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=88=87=E6=8D=A2=E6=8C=89=E9=92=AE=20-=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20useTheme=E9=92=A9=E5=AD=90=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E4=B8=BB=E9=A2=98=E7=AE=A1=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91-=20=E6=B7=BB=E5=8A=A0=E6=9A=97=E9=BB=91=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E5=92=8C=E4=BA=AE=E8=89=B2=E6=A8=A1=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=94=AF=E6=8C=81=20-=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E5=AD=98=E5=82=A8=E5=92=8C=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E7=9A=84=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=20-=20=E6=B7=BB=E5=8A=A0=E4=B8=BB=E9=A2=98=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E4=BA=8B=E4=BB=B6=E5=B9=BF=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ThemeToggle.vue | 33 ++++++++ src/composables/useTheme.js | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 src/components/ThemeToggle.vue create mode 100644 src/composables/useTheme.js diff --git a/src/components/ThemeToggle.vue b/src/components/ThemeToggle.vue new file mode 100644 index 0000000..2daad6f --- /dev/null +++ b/src/components/ThemeToggle.vue @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/src/composables/useTheme.js b/src/composables/useTheme.js new file mode 100644 index 0000000..111b355 --- /dev/null +++ b/src/composables/useTheme.js @@ -0,0 +1,141 @@ +import { ref, onMounted, onUnmounted } from 'vue' + +// 全局响应式主题状态 +const isDark = ref(false) + +// 检查当前主题状态 +const checkCurrentTheme = () => { + return document.documentElement.classList.contains('dark') || + document.documentElement.getAttribute('data-theme') === 'dark' +} + +// 更新响应式状态 +const updateReactiveState = () => { + isDark.value = checkCurrentTheme() +} + +// 主题管理逻辑 +export function useTheme() { + let mutationObserver = null + + // 初始化主题 + const initTheme = () => { + const savedTheme = localStorage.getItem('theme') + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + + if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { + setDarkTheme() + } else { + setLightTheme() + } + + // 立即更新响应式状态 + updateReactiveState() + } + + // 设置深色主题 + const setDarkTheme = () => { + document.documentElement.classList.add('dark') + document.documentElement.setAttribute('data-theme', 'dark') + document.body.classList.add('dark') + document.body.setAttribute('data-theme', 'dark') + localStorage.setItem('theme', 'dark') + + // 立即更新响应式状态 + isDark.value = true + + // 触发主题更新事件 + window.dispatchEvent(new CustomEvent('theme-change', { detail: { isDark: true } })) + } + + // 设置浅色主题 + const setLightTheme = () => { + document.documentElement.classList.remove('dark') + document.documentElement.removeAttribute('data-theme') + document.body.classList.remove('dark') + document.body.removeAttribute('data-theme') + localStorage.setItem('theme', 'light') + + // 立即更新响应式状态 + isDark.value = false + + // 触发主题更新事件 + window.dispatchEvent(new CustomEvent('theme-change', { detail: { isDark: false } })) + } + + // 切换主题 + const toggleTheme = () => { + if (isDark.value) { + setLightTheme() + } else { + setDarkTheme() + } + } + + // 监听系统主题变化 + const setupSystemThemeListener = () => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + mediaQuery.addListener(() => { + const savedTheme = localStorage.getItem('theme') + // 只有在用户没有手动设置主题时才跟随系统 + if (!savedTheme) { + initTheme() + } + }) + } + + // 设置DOM变化监听器(确保响应式状态同步) + const setupDOMObserver = () => { + if (typeof window !== 'undefined') { + mutationObserver = new MutationObserver(() => { + updateReactiveState() + }) + + // 监听documentElement和body的变化 + mutationObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'data-theme'] + }) + + mutationObserver.observe(document.body, { + attributes: true, + attributeFilter: ['class', 'data-theme'] + }) + } + } + + // 清理监听器 + const cleanup = () => { + if (mutationObserver) { + mutationObserver.disconnect() + mutationObserver = null + } + } + + // 获取当前主题 + const getCurrentTheme = () => { + return isDark.value ? 'dark' : 'light' + } + + // 组件挂载时初始化 + onMounted(() => { + setupDOMObserver() + updateReactiveState() + }) + + // 组件卸载时清理 + onUnmounted(() => { + cleanup() + }) + + return { + isDark, + initTheme, + toggleTheme, + setDarkTheme, + setLightTheme, + setupSystemThemeListener, + getCurrentTheme, + updateReactiveState + } +} \ No newline at end of file