Files
FileShare/web/src/theme/index.ts
T
SKYMirror 7f060dd0e4 26.9(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务:

- 安全审计全部修复(docs/security-audit-2026-09-05.md):
  bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、
  全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、
  管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、
  会话默认 7 天、限流缓存故障降级、robots.txt 端点等
- 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固
- Redis 支持库号(FCB_REDIS_DB / redis://…/db URL)
- 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、
  提码 5 位起、chunk 32MiB 上限、admin 审计动作等)

验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
2026-09-05 04:22:41 +08:00

76 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 主题系统(需求②):
* - 三态:light / dark / system(跟随 prefers-color-scheme
* - 默认跟随系统;localStorage 记忆(键 fcb_theme_mode
* - 实际生效主题写入 <html data-theme="light|dark">CSS 变量按此切换
* - Naive UI darkTheme 由 App.vue 读取 useTheme().resolved 响应式传递
*/
import { ref, watch } from 'vue'
export type ThemeMode = 'light' | 'dark' | 'system'
export type ResolvedTheme = 'light' | 'dark'
export const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system']
const STORAGE_KEY = 'fcb_theme_mode'
function readStoredMode(): ThemeMode | null {
try {
const v = localStorage.getItem(STORAGE_KEY)
return v === 'light' || v === 'dark' || v === 'system' ? v : null
} catch {
return null
}
}
function persist(mode: ThemeMode): void {
try {
localStorage.setItem(STORAGE_KEY, mode)
} catch {
/* ignore */
}
}
function systemPrefersDark(): boolean {
return typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches
}
/** 当前模式(未初始化时按 localStorage → 默认 system */
const mode = ref<ThemeMode>(readStoredMode() ?? 'system')
/** 实际生效主题(响应式,供 Naive UI 与 <html data-theme> 使用) */
const resolved = ref<ResolvedTheme>(mode.value === 'system' ? (systemPrefersDark() ? 'dark' : 'light') : mode.value)
let started = false
/** 初始化系统主题监听(App.vue 挂载时调用一次) */
function start(): void {
if (started || typeof matchMedia !== 'function') return
started = true
const mq = matchMedia('(prefers-color-scheme: dark)')
mq.addEventListener?.('change', () => {
if (mode.value === 'system') resolved.value = mq.matches ? 'dark' : 'light'
})
}
function apply(): void {
start()
resolved.value = mode.value === 'system' ? (systemPrefersDark() ? 'dark' : 'light') : mode.value
document.documentElement.dataset.theme = resolved.value
}
watch(mode, apply, { immediate: true })
/** 切换模式并记忆(需求②:导航栏三态切换 + localStorage */
export function setThemeMode(next: ThemeMode): void {
mode.value = next
persist(next)
}
export function useTheme() {
return {
mode,
resolved,
setMode: setThemeMode,
}
}