- 新增 useTheme.js 单例主题管理器:用户手动选择 > 系统偏好 > 时间自动切换(8AM/6PM) - config.ts 添加 head 内联脚本防止主题闪烁 - 移动端导航按钮右对齐(margin-left:auto) - 首页和文档页代码块移动端溢出修复
126 lines
3.4 KiB
JavaScript
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 }
|
|
}
|