a6cc190751
P1b Task 9: apps/admin based on art-design-pro v3.0.2 (tag, commit d224dd4; source recorded in apps/admin/SOURCE.md), trimmed to layout/login/i18n/request/table-form core. Login wired to real POST /v1/auth/password (single-token session), Bearer header on all requests, 401 interceptor logout+redirect, request-driven sliding renewal (refresh at ~80% TTL, single-flight), best-effort POST /v1/auth/logout on user-menu logout. One protected stub page (相册管理). Dev port 5176 strictPort + /v1 proxy to 8080. Admin keeps its own eslint9 flat config; root eslint ignores apps/admin/**. vitest unit tests for renewal pacing + Playwright e2e (guard redirect, localized wrong-password error, login lands on stub, token persists across reload, logout clears session). Co-Authored-By: Claude Code <noreply@anthropic.com>
175 lines
4.9 KiB
TypeScript
175 lines
4.9 KiB
TypeScript
/**
|
|
* useTheme - 系统主题管理
|
|
*
|
|
* 提供完整的主题切换和管理功能,支持亮色、暗色和自动模式。
|
|
* 自动处理主题切换时的过渡效果,确保切换流畅无闪烁。
|
|
*
|
|
* ## 主要功能
|
|
*
|
|
* 1. 主题切换 - 支持亮色、暗色、自动三种主题模式
|
|
* 2. 自动模式 - 根据系统偏好自动切换主题
|
|
* 3. 颜色适配 - 自动调整主题色的明暗变体(9 个层级)
|
|
* 4. 过渡优化 - 切换时临时禁用过渡效果,避免闪烁
|
|
* 5. 状态持久化 - 主题设置自动保存到 store
|
|
*
|
|
* ## 使用示例
|
|
*
|
|
* ```typescript
|
|
* const { switchThemeStyles } = useTheme()
|
|
*
|
|
* // 切换到暗色主题
|
|
* switchThemeStyles(SystemThemeEnum.DARK)
|
|
*
|
|
* // 切换到亮色主题
|
|
* switchThemeStyles(SystemThemeEnum.LIGHT)
|
|
*
|
|
* // 切换到自动模式(跟随系统)
|
|
* switchThemeStyles(SystemThemeEnum.AUTO)
|
|
* ```
|
|
*
|
|
* @module useTheme
|
|
* @author Art Design Pro Team
|
|
*/
|
|
|
|
import { useSettingStore } from '@/store/modules/setting'
|
|
import { SystemThemeEnum } from '@/enums/appEnum'
|
|
import AppConfig from '@/config'
|
|
import { SystemThemeTypes } from '@/types/store'
|
|
import { getDarkColor, getLightColor, setElementThemeColor } from '@/utils/ui'
|
|
import { usePreferredDark } from '@vueuse/core'
|
|
import { watch } from 'vue'
|
|
|
|
export function useTheme() {
|
|
const settingStore = useSettingStore()
|
|
|
|
// 禁用过渡效果
|
|
const disableTransitions = () => {
|
|
const style = document.createElement('style')
|
|
style.setAttribute('id', 'disable-transitions')
|
|
style.textContent = '* { transition: none !important; }'
|
|
document.head.appendChild(style)
|
|
}
|
|
|
|
// 启用过渡效果
|
|
const enableTransitions = () => {
|
|
const style = document.getElementById('disable-transitions')
|
|
if (style) {
|
|
style.remove()
|
|
}
|
|
}
|
|
|
|
// 设置系统主题
|
|
const setSystemTheme = (theme: SystemThemeEnum, themeMode?: SystemThemeEnum) => {
|
|
// 临时禁用过渡效果
|
|
disableTransitions()
|
|
|
|
const el = document.getElementsByTagName('html')[0]
|
|
const isDark = theme === SystemThemeEnum.DARK
|
|
|
|
if (!themeMode) {
|
|
themeMode = theme
|
|
}
|
|
|
|
const currentTheme = AppConfig.systemThemeStyles[theme as keyof SystemThemeTypes]
|
|
|
|
if (currentTheme) {
|
|
el.setAttribute('class', currentTheme.className)
|
|
}
|
|
|
|
// 设置按钮颜色加深或变浅
|
|
const primary = settingStore.systemThemeColor
|
|
|
|
for (let i = 1; i <= 9; i++) {
|
|
document.documentElement.style.setProperty(
|
|
`--el-color-primary-light-${i}`,
|
|
isDark ? `${getDarkColor(primary, i / 10)}` : `${getLightColor(primary, i / 10)}`
|
|
)
|
|
}
|
|
|
|
// 更新store中的主题设置
|
|
settingStore.setGlopTheme(theme, themeMode)
|
|
|
|
// 使用 requestAnimationFrame 确保在下一帧恢复过渡效果
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
enableTransitions()
|
|
})
|
|
})
|
|
}
|
|
|
|
// 使用 VueUse 的 usePreferredDark 检测系统主题偏好
|
|
const prefersDark = usePreferredDark()
|
|
|
|
// 自动设置系统主题
|
|
const setSystemAutoTheme = () => {
|
|
const theme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT
|
|
setSystemTheme(theme, SystemThemeEnum.AUTO)
|
|
}
|
|
|
|
// 切换主题
|
|
const switchThemeStyles = (theme: SystemThemeEnum) => {
|
|
if (theme === SystemThemeEnum.AUTO) {
|
|
setSystemAutoTheme()
|
|
} else {
|
|
setSystemTheme(theme)
|
|
}
|
|
}
|
|
|
|
return {
|
|
setSystemTheme,
|
|
setSystemAutoTheme,
|
|
switchThemeStyles,
|
|
prefersDark
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 初始化主题系统
|
|
*/
|
|
export function initializeTheme() {
|
|
const settingStore = useSettingStore()
|
|
const prefersDark = usePreferredDark()
|
|
|
|
// 根据系统偏好应用主题
|
|
const applyThemeByMode = () => {
|
|
const el = document.getElementsByTagName('html')[0]
|
|
let actualTheme = settingStore.systemThemeType
|
|
|
|
// 如果是 AUTO 模式,检测系统偏好
|
|
if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) {
|
|
actualTheme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT
|
|
// 更新实际应用的主题类型
|
|
settingStore.systemThemeType = actualTheme
|
|
}
|
|
|
|
// 设置主题 class
|
|
const currentTheme = AppConfig.systemThemeStyles[actualTheme as keyof SystemThemeTypes]
|
|
if (currentTheme) {
|
|
el.setAttribute('class', currentTheme.className)
|
|
}
|
|
|
|
// 设置主题颜色
|
|
setElementThemeColor(settingStore.systemThemeColor)
|
|
|
|
// 设置圆角
|
|
document.documentElement.style.setProperty('--custom-radius', `${settingStore.customRadius}rem`)
|
|
}
|
|
|
|
// 应用主题
|
|
applyThemeByMode()
|
|
|
|
// 如果是 AUTO 模式,监听系统主题变化(使用 VueUse 的响应式特性)
|
|
if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) {
|
|
watch(
|
|
prefersDark,
|
|
() => {
|
|
// 只有在 AUTO 模式下才响应系统主题变化
|
|
if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) {
|
|
applyThemeByMode()
|
|
}
|
|
},
|
|
{ immediate: false }
|
|
)
|
|
}
|
|
}
|