redesign: site-wide UI/UX overhaul per web-interface-guidelines
- unified design system: focus-visible rings, reduced-motion, color-scheme, theme persistence, tightened emerald radius tokens (checkbox circle bug) - redesigned all pages: Home/Login/Signup/404, dashboard shell, Overview hero (time-based gradient), list pages with empty states, form modals, detail pages - role-scoped navigation: console menu vs admin console with cross-switch entries, Chinese route titles and breadcrumbs - theme switcher (light/dark/auto) via composables/theme, home + dashboard - state-driven dropdowns (touch-safe logout), menu divider pointer-events fix - btn-primary unified to black-on-white / white-on-black per theme - update REFACTOR_PLAN.md (increments 2-6)
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 主题偏好:浅色 / 深色 / 跟随系统;浅色映射为品牌 emerald 主题
|
||||
export type ThemePreference = 'light' | 'dark' | 'auto'
|
||||
export type ResolvedTheme = 'light' | 'dark'
|
||||
|
||||
const STORAGE_KEY = 'theme'
|
||||
const MEDIA = '(prefers-color-scheme: dark)'
|
||||
|
||||
function readStoredPreference(): ThemePreference {
|
||||
try {
|
||||
const v = localStorage.getItem(STORAGE_KEY)
|
||||
if (v === 'light' || v === 'dark' || v === 'auto') return v
|
||||
} catch { /* localStorage 不可用 */ }
|
||||
return 'auto'
|
||||
}
|
||||
|
||||
function resolveTheme(p: ThemePreference): ResolvedTheme {
|
||||
if (p !== 'auto') return p
|
||||
try {
|
||||
return window.matchMedia(MEDIA).matches ? 'dark' : 'light'
|
||||
} catch {
|
||||
return 'light'
|
||||
}
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
resolvedTheme.value = resolveTheme(preference.value)
|
||||
document.documentElement.setAttribute('data-theme', resolvedTheme.value === 'dark' ? 'dark' : 'emerald')
|
||||
}
|
||||
|
||||
// 系统深浅色切换时,auto 模式实时跟随(模块级监听,避免重复注册)
|
||||
let mediaListenerBound = false
|
||||
function ensureMediaListener() {
|
||||
if (mediaListenerBound || typeof window === 'undefined') return
|
||||
mediaListenerBound = true
|
||||
try {
|
||||
window.matchMedia(MEDIA).addEventListener('change', () => {
|
||||
if (preference.value === 'auto') applyTheme()
|
||||
})
|
||||
} catch { /* 旧浏览器无 addEventListener */ }
|
||||
}
|
||||
|
||||
const preference = ref<ThemePreference>(readStoredPreference())
|
||||
const resolvedTheme = ref<ResolvedTheme>(resolveTheme(preference.value))
|
||||
|
||||
applyTheme()
|
||||
ensureMediaListener()
|
||||
|
||||
function setPreference(p: ThemePreference) {
|
||||
preference.value = p
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, p)
|
||||
} catch { /* ignore */ }
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return { preference, resolvedTheme, setPreference }
|
||||
}
|
||||
Reference in New Issue
Block a user