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:
Sakurasan
2026-08-30 01:35:10 +08:00
parent ac0a6808ec
commit 1e07c5903f
26 changed files with 2189 additions and 2323 deletions
+60
View File
@@ -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 }
}