mengstack-website/.vitepress/theme/useTheme.js
MengStack Dev a09d805bd9 feat: 三层明暗模式 + 移动端代码块适配
- 新增 useTheme.js 单例主题管理器:用户手动选择 > 系统偏好 > 时间自动切换(8AM/6PM)
- config.ts 添加 head 内联脚本防止主题闪烁
- 移动端导航按钮右对齐(margin-left:auto)
- 首页和文档页代码块移动端溢出修复
2026-10-03 01:42:24 +08:00

126 lines
3.4 KiB
JavaScript

import { onMounted, onUnmounted } from 'vue'
import { useData } from 'vitepress'
const EXPLICIT_KEY = 'mengstack-theme-explicit'
const VP_KEY = 'vitepress-theme-appearance'
const DAY_START = 8
const NIGHT_START = 18
let initialized = false
let _isDark = null
let _handleToggle = null
function isTimeDark() {
const h = new Date().getHours()
return h < DAY_START || h >= NIGHT_START
}
function getSystemPref() {
if (typeof window === 'undefined' || !window.matchMedia) return null
if (window.matchMedia('(prefers-color-scheme: dark)').matches) return true
if (window.matchMedia('(prefers-color-scheme: light)').matches) return false
return null
}
export function initThemeManager() {
if (initialized) return { isDark: _isDark, handleToggle: _handleToggle }
const vp = useData()
const isDark = vp.isDark
initialized = true
_isDark = isDark
let explicitChoice = null
let timer = null
let sysListener = null
let observer = null
let suppressObs = false
function sync(value) {
suppressObs = true
isDark.value = value
try {
localStorage.setItem(VP_KEY, String(value))
document.documentElement.classList.toggle('dark', value)
} catch {}
setTimeout(() => { suppressObs = false }, 150)
}
function doAutoSwitch() {
const shouldBeDark = isTimeDark()
if (isDark.value !== shouldBeDark) {
sync(shouldBeDark)
}
}
function handleToggle() {
explicitChoice = !isDark.value
try { localStorage.setItem(EXPLICIT_KEY, String(explicitChoice)) } catch {}
if (timer) { clearInterval(timer); timer = null }
sync(explicitChoice)
}
_handleToggle = handleToggle
onMounted(() => {
try {
const stored = localStorage.getItem(EXPLICIT_KEY)
if (stored !== null) explicitChoice = stored === 'true'
} catch {}
if (explicitChoice !== null) {
sync(explicitChoice)
} else {
const sysPref = getSystemPref()
if (sysPref !== null) {
sync(sysPref)
} else {
sync(isTimeDark())
}
}
timer = setInterval(() => {
if (explicitChoice !== null) return
if (getSystemPref() !== null) return
doAutoSwitch()
}, 30000)
if (typeof window !== 'undefined' && window.matchMedia) {
const mql = window.matchMedia('(prefers-color-scheme: dark)')
sysListener = (e) => {
if (explicitChoice !== null) return
sync(e.matches)
}
mql.addEventListener('change', sysListener)
}
if (typeof document !== 'undefined') {
observer = new MutationObserver(() => {
if (suppressObs) return
const dark = document.documentElement.classList.contains('dark')
if (explicitChoice === null) {
explicitChoice = dark
try { localStorage.setItem(EXPLICIT_KEY, String(explicitChoice)) } catch {}
}
isDark.value = dark
try { localStorage.setItem(VP_KEY, String(dark)) } catch {}
})
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
}
})
onUnmounted(() => {
if (timer) clearInterval(timer)
if (sysListener && typeof window !== 'undefined' && window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', sysListener)
}
if (observer) observer.disconnect()
initialized = false
})
return { isDark, handleToggle }
}