feat(theme): 添加主题切换功能- 新增 ThemeToggle 组件,用于在界面上显示主题切换按钮
- 实现 useTheme钩子,提供主题管理逻辑- 添加暗黑模式和亮色模式的切换支持 - 实现本地存储和系统主题的自动检测 - 添加主题切换时的事件广播
This commit is contained in:
33
src/components/ThemeToggle.vue
Normal file
33
src/components/ThemeToggle.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<n-button
|
||||
circle
|
||||
size="medium"
|
||||
class="theme-toggle"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon v-if="isDark">
|
||||
<Sunny />
|
||||
</n-icon>
|
||||
<n-icon v-else>
|
||||
<Moon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Moon, Sunny } from '@vicons/ionicons5'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
|
||||
const { isDark, toggleTheme } = useTheme()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.theme-toggle {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}</style>
|
||||
141
src/composables/useTheme.js
Normal file
141
src/composables/useTheme.js
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user