Skip to content

Latest commit

 

History

History
802 lines (672 loc) · 17.8 KB

File metadata and controls

802 lines (672 loc) · 17.8 KB

Theme System 主题系统文档

概述

主题系统提供了完整的视觉主题管理功能,支持多主题切换、自定义主题、动态主题生成等,为用户提供个性化的视觉体验。

文件位置: src/styles/theme.css

主题架构

1. CSS变量系统

/* 主题变量定义 */
:root {
  /* 主色调 */
  --theme-primary: #409eff;
  --theme-primary-light: #66b1ff;
  --theme-primary-lighter: #a0cfff;
  --theme-primary-dark: #3a8ee6;
  --theme-primary-darker: #337ecc;
  
  /* 辅助色 */
  --theme-success: #67c23a;
  --theme-warning: #e6a23c;
  --theme-danger: #f56c6c;
  --theme-info: #909399;
  
  /* 中性色 */
  --theme-text-primary: #303133;
  --theme-text-regular: #606266;
  --theme-text-secondary: #909399;
  --theme-text-placeholder: #c0c4cc;
  
  /* 边框色 */
  --theme-border-base: #dcdfe6;
  --theme-border-light: #e4e7ed;
  --theme-border-lighter: #ebeef5;
  --theme-border-extra-light: #f2f6fc;
  
  /* 背景色 */
  --theme-bg-primary: #ffffff;
  --theme-bg-secondary: #f5f7fa;
  --theme-bg-tertiary: #fafafa;
  --theme-bg-quaternary: #f0f2f5;
  
  /* 阴影 */
  --theme-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
  --theme-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  --theme-shadow-dark: 0 4px 12px rgba(0, 0, 0, 0.15);
  
  /* 圆角 */
  --theme-border-radius-base: 4px;
  --theme-border-radius-small: 2px;
  --theme-border-radius-large: 8px;
  --theme-border-radius-round: 20px;
  --theme-border-radius-circle: 50%;
  
  /* 字体 */
  --theme-font-size-extra-small: 12px;
  --theme-font-size-small: 13px;
  --theme-font-size-base: 14px;
  --theme-font-size-medium: 16px;
  --theme-font-size-large: 18px;
  --theme-font-size-extra-large: 20px;
  
  /* 间距 */
  --theme-spacing-xs: 4px;
  --theme-spacing-sm: 8px;
  --theme-spacing-md: 16px;
  --theme-spacing-lg: 24px;
  --theme-spacing-xl: 32px;
  --theme-spacing-xxl: 48px;
  
  /* 动画 */
  --theme-transition-base: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  --theme-transition-fade: opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
  --theme-transition-md-fade: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}

2. 预设主题

默认主题 (Default)

[data-theme="default"] {
  --theme-primary: #409eff;
  --theme-primary-light: #66b1ff;
  --theme-primary-lighter: #a0cfff;
  --theme-primary-dark: #3a8ee6;
  --theme-primary-darker: #337ecc;
  
  --theme-success: #67c23a;
  --theme-warning: #e6a23c;
  --theme-danger: #f56c6c;
  --theme-info: #909399;
  
  --theme-text-primary: #303133;
  --theme-text-regular: #606266;
  --theme-text-secondary: #909399;
  --theme-text-placeholder: #c0c4cc;
  
  --theme-bg-primary: #ffffff;
  --theme-bg-secondary: #f5f7fa;
  --theme-bg-tertiary: #fafafa;
  --theme-bg-quaternary: #f0f2f5;
  
  --theme-border-base: #dcdfe6;
  --theme-border-light: #e4e7ed;
  --theme-border-lighter: #ebeef5;
  --theme-border-extra-light: #f2f6fc;
}

暗色主题 (Dark)

[data-theme="dark"] {
  --theme-primary: #409eff;
  --theme-primary-light: #66b1ff;
  --theme-primary-lighter: #a0cfff;
  --theme-primary-dark: #3a8ee6;
  --theme-primary-darker: #337ecc;
  
  --theme-success: #67c23a;
  --theme-warning: #e6a23c;
  --theme-danger: #f56c6c;
  --theme-info: #909399;
  
  --theme-text-primary: #e4e7ed;
  --theme-text-regular: #cfcfcf;
  --theme-text-secondary: #a3a6ad;
  --theme-text-placeholder: #6c6e72;
  
  --theme-bg-primary: #1d1e1f;
  --theme-bg-secondary: #25262b;
  --theme-bg-tertiary: #2c2e33;
  --theme-bg-quaternary: #34373c;
  
  --theme-border-base: #4c4d4f;
  --theme-border-light: #414243;
  --theme-border-lighter: #363637;
  --theme-border-extra-light: #2b2b2c;
  
  --theme-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.24), 0 0 6px rgba(0, 0, 0, 0.08);
  --theme-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
  --theme-shadow-dark: 0 4px 12px rgba(0, 0, 0, 0.3);
}

蓝色主题 (Blue)

[data-theme="blue"] {
  --theme-primary: #1890ff;
  --theme-primary-light: #40a9ff;
  --theme-primary-lighter: #69c0ff;
  --theme-primary-dark: #096dd9;
  --theme-primary-darker: #0050b3;
  
  --theme-success: #52c41a;
  --theme-warning: #faad14;
  --theme-danger: #ff4d4f;
  --theme-info: #13c2c2;
  
  --theme-bg-primary: #f0f8ff;
  --theme-bg-secondary: #e6f7ff;
  --theme-bg-tertiary: #bae7ff;
  --theme-bg-quaternary: #91d5ff;
}

绿色主题 (Green)

[data-theme="green"] {
  --theme-primary: #52c41a;
  --theme-primary-light: #73d13d;
  --theme-primary-lighter: #95de64;
  --theme-primary-dark: #389e0d;
  --theme-primary-darker: #237804;
  
  --theme-success: #52c41a;
  --theme-warning: #faad14;
  --theme-danger: #ff4d4f;
  --theme-info: #13c2c2;
  
  --theme-bg-primary: #f6ffed;
  --theme-bg-secondary: #d9f7be;
  --theme-bg-tertiary: #b7eb8f;
  --theme-bg-quaternary: #95de64;
}

紫色主题 (Purple)

[data-theme="purple"] {
  --theme-primary: #722ed1;
  --theme-primary-light: #9254de;
  --theme-primary-lighter: #b37feb;
  --theme-primary-dark: #531dab;
  --theme-primary-darker: #391085;
  
  --theme-success: #52c41a;
  --theme-warning: #faad14;
  --theme-danger: #ff4d4f;
  --theme-info: #13c2c2;
  
  --theme-bg-primary: #f9f0ff;
  --theme-bg-secondary: #efdbff;
  --theme-bg-tertiary: #d3adf7;
  --theme-bg-quaternary: #b37feb;
}

3. 组件主题样式

按钮主题

.btn {
  background-color: var(--theme-primary);
  border-color: var(--theme-primary);
  color: var(--theme-bg-primary);
  border-radius: var(--theme-border-radius-base);
  transition: var(--theme-transition-base);
}

.btn:hover {
  background-color: var(--theme-primary-light);
  border-color: var(--theme-primary-light);
}

.btn:active {
  background-color: var(--theme-primary-dark);
  border-color: var(--theme-primary-dark);
}

.btn-secondary {
  background-color: var(--theme-bg-secondary);
  border-color: var(--theme-border-base);
  color: var(--theme-text-primary);
}

.btn-success {
  background-color: var(--theme-success);
  border-color: var(--theme-success);
  color: var(--theme-bg-primary);
}

.btn-warning {
  background-color: var(--theme-warning);
  border-color: var(--theme-warning);
  color: var(--theme-bg-primary);
}

.btn-danger {
  background-color: var(--theme-danger);
  border-color: var(--theme-danger);
  color: var(--theme-bg-primary);
}

卡片主题

.card {
  background-color: var(--theme-bg-primary);
  border: 1px solid var(--theme-border-light);
  border-radius: var(--theme-border-radius-large);
  box-shadow: var(--theme-shadow-base);
  transition: var(--theme-transition-base);
}

.card:hover {
  box-shadow: var(--theme-shadow-light);
}

.card-header {
  background-color: var(--theme-bg-secondary);
  border-bottom: 1px solid var(--theme-border-light);
  color: var(--theme-text-primary);
  font-size: var(--theme-font-size-medium);
  font-weight: 600;
}

.card-body {
  color: var(--theme-text-regular);
  font-size: var(--theme-font-size-base);
}

.card-footer {
  background-color: var(--theme-bg-tertiary);
  border-top: 1px solid var(--theme-border-light);
  color: var(--theme-text-secondary);
  font-size: var(--theme-font-size-small);
}

表单主题

.form-control {
  background-color: var(--theme-bg-primary);
  border: 1px solid var(--theme-border-base);
  border-radius: var(--theme-border-radius-base);
  color: var(--theme-text-primary);
  font-size: var(--theme-font-size-base);
  transition: var(--theme-transition-base);
}

.form-control:focus {
  border-color: var(--theme-primary);
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
  outline: none;
}

.form-control::placeholder {
  color: var(--theme-text-placeholder);
}

.form-label {
  color: var(--theme-text-primary);
  font-size: var(--theme-font-size-base);
  font-weight: 500;
}

.form-help {
  color: var(--theme-text-secondary);
  font-size: var(--theme-font-size-small);
}

.form-error {
  color: var(--theme-danger);
  font-size: var(--theme-font-size-small);
}

导航主题

.navbar {
  background-color: var(--theme-bg-primary);
  border-bottom: 1px solid var(--theme-border-light);
  box-shadow: var(--theme-shadow-base);
}

.navbar-brand {
  color: var(--theme-primary);
  font-size: var(--theme-font-size-large);
  font-weight: 700;
}

.navbar-nav .nav-link {
  color: var(--theme-text-regular);
  font-size: var(--theme-font-size-base);
  transition: var(--theme-transition-base);
}

.navbar-nav .nav-link:hover {
  color: var(--theme-primary);
}

.navbar-nav .nav-link.active {
  color: var(--theme-primary);
  font-weight: 600;
}

.sidebar {
  background-color: var(--theme-bg-secondary);
  border-right: 1px solid var(--theme-border-light);
}

.sidebar-menu .menu-item {
  color: var(--theme-text-regular);
  transition: var(--theme-transition-base);
}

.sidebar-menu .menu-item:hover {
  background-color: var(--theme-bg-tertiary);
  color: var(--theme-primary);
}

.sidebar-menu .menu-item.active {
  background-color: var(--theme-primary);
  color: var(--theme-bg-primary);
}

4. 主题切换动画

/* 主题切换过渡动画 */
* {
  transition: 
    background-color var(--theme-transition-base),
    border-color var(--theme-transition-base),
    color var(--theme-transition-base),
    box-shadow var(--theme-transition-base);
}

/* 主题切换时的淡入淡出效果 */
.theme-transition-enter-active,
.theme-transition-leave-active {
  transition: opacity 0.3s ease;
}

.theme-transition-enter-from,
.theme-transition-leave-to {
  opacity: 0;
}

/* 主题切换加载动画 */
.theme-loading {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(255, 255, 255, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

.theme-loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid var(--theme-border-light);
  border-top: 4px solid var(--theme-primary);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

5. 响应式主题适配

/* 移动端主题适配 */
@media (max-width: 767px) {
  :root {
    --theme-font-size-base: 13px;
    --theme-font-size-medium: 15px;
    --theme-font-size-large: 17px;
    --theme-spacing-md: 12px;
    --theme-spacing-lg: 20px;
    --theme-border-radius-base: 6px;
    --theme-border-radius-large: 10px;
  }
  
  .card {
    border-radius: var(--theme-border-radius-base);
    box-shadow: var(--theme-shadow-light);
  }
  
  .btn {
    padding: var(--theme-spacing-sm) var(--theme-spacing-md);
    font-size: var(--theme-font-size-base);
  }
}

/* 平板端主题适配 */
@media (min-width: 768px) and (max-width: 1023px) {
  :root {
    --theme-font-size-base: 14px;
    --theme-font-size-medium: 16px;
    --theme-font-size-large: 18px;
    --theme-spacing-md: 16px;
    --theme-spacing-lg: 24px;
  }
}

/* 桌面端主题适配 */
@media (min-width: 1024px) {
  :root {
    --theme-font-size-base: 14px;
    --theme-font-size-medium: 16px;
    --theme-font-size-large: 18px;
    --theme-spacing-md: 16px;
    --theme-spacing-lg: 24px;
    --theme-spacing-xl: 32px;
  }
}

6. 高对比度主题

/* 高对比度主题 */
[data-theme="high-contrast"] {
  --theme-primary: #0066cc;
  --theme-primary-light: #3385d6;
  --theme-primary-dark: #004499;
  
  --theme-success: #008000;
  --theme-warning: #ff8c00;
  --theme-danger: #cc0000;
  --theme-info: #0080c7;
  
  --theme-text-primary: #000000;
  --theme-text-regular: #333333;
  --theme-text-secondary: #666666;
  --theme-text-placeholder: #999999;
  
  --theme-bg-primary: #ffffff;
  --theme-bg-secondary: #f0f0f0;
  --theme-bg-tertiary: #e0e0e0;
  --theme-bg-quaternary: #d0d0d0;
  
  --theme-border-base: #000000;
  --theme-border-light: #333333;
  --theme-border-lighter: #666666;
  --theme-border-extra-light: #999999;
  
  --theme-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.3);
  --theme-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
  --theme-shadow-dark: 0 4px 12px rgba(0, 0, 0, 0.4);
}

/* 高对比度模式下的特殊样式 */
@media (prefers-contrast: high) {
  :root {
    --theme-primary: #0066cc;
    --theme-text-primary: #000000;
    --theme-bg-primary: #ffffff;
    --theme-border-base: #000000;
  }
  
  .btn {
    border-width: 2px;
  }
  
  .form-control {
    border-width: 2px;
  }
  
  .card {
    border-width: 2px;
  }
}

7. 打印主题

/* 打印主题 */
@media print {
  :root {
    --theme-primary: #000000;
    --theme-text-primary: #000000;
    --theme-text-regular: #000000;
    --theme-text-secondary: #666666;
    --theme-bg-primary: #ffffff;
    --theme-bg-secondary: #ffffff;
    --theme-border-base: #000000;
    --theme-shadow-base: none;
    --theme-shadow-light: none;
    --theme-shadow-dark: none;
  }
  
  .card {
    box-shadow: none;
    border: 1px solid #000000;
  }
  
  .btn {
    background-color: transparent;
    color: #000000;
    border: 1px solid #000000;
  }
  
  .navbar,
  .sidebar,
  .footer {
    display: none;
  }
}

JavaScript主题管理

1. 主题管理器

/**
 * 主题管理器
 */
export class ThemeManager {
  constructor() {
    this.currentTheme = 'default'
    this.themes = ['default', 'dark', 'blue', 'green', 'purple']
    this.storageKey = 'app_theme'
    this.init()
  }
  
  init() {
    this.loadTheme()
    this.applyTheme(this.currentTheme)
    this.observeSystemTheme()
  }
  
  setTheme(theme) {
    if (!this.themes.includes(theme)) {
      console.warn(`Unknown theme: ${theme}`)
      return false
    }
    
    this.currentTheme = theme
    this.applyTheme(theme)
    this.saveTheme()
    return true
  }
  
  applyTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme)
    
    // 触发主题变更事件
    window.dispatchEvent(new CustomEvent('themechange', {
      detail: { theme }
    }))
  }
  
  loadTheme() {
    try {
      const saved = localStorage.getItem(this.storageKey)
      if (saved && this.themes.includes(saved)) {
        this.currentTheme = saved
      }
    } catch (error) {
      console.warn('Failed to load theme:', error)
    }
  }
  
  saveTheme() {
    try {
      localStorage.setItem(this.storageKey, this.currentTheme)
    } catch (error) {
      console.warn('Failed to save theme:', error)
    }
  }
  
  observeSystemTheme() {
    if (window.matchMedia) {
      const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
      darkModeQuery.addEventListener('change', (e) => {
        if (this.currentTheme === 'auto') {
          this.applyTheme(e.matches ? 'dark' : 'default')
        }
      })
    }
  }
  
  getCurrentTheme() {
    return this.currentTheme
  }
  
  getAvailableThemes() {
    return [...this.themes]
  }
  
  toggleTheme() {
    const currentIndex = this.themes.indexOf(this.currentTheme)
    const nextIndex = (currentIndex + 1) % this.themes.length
    this.setTheme(this.themes[nextIndex])
  }
}

2. Vue 3 主题组合式API

import { ref, onMounted } from 'vue'

export function useTheme() {
  const themeManager = new ThemeManager()
  const currentTheme = ref(themeManager.getCurrentTheme())
  const availableThemes = ref(themeManager.getAvailableThemes())
  
  const setTheme = (theme) => {
    if (themeManager.setTheme(theme)) {
      currentTheme.value = theme
    }
  }
  
  const toggleTheme = () => {
    themeManager.toggleTheme()
    currentTheme.value = themeManager.getCurrentTheme()
  }
  
  onMounted(() => {
    window.addEventListener('themechange', (e) => {
      currentTheme.value = e.detail.theme
    })
  })
  
  return {
    currentTheme: readonly(currentTheme),
    availableThemes: readonly(availableThemes),
    setTheme,
    toggleTheme
  }
}

使用示例

1. 基础主题切换

<template>
  <div class="theme-switcher">
    <el-select v-model="currentTheme" @change="handleThemeChange">
      <el-option
        v-for="theme in availableThemes"
        :key="theme"
        :label="getThemeLabel(theme)"
        :value="theme"
      />
    </el-select>
  </div>
</template>

<script setup>
import { useTheme } from '@/utils/theme'

const { currentTheme, availableThemes, setTheme } = useTheme()

const handleThemeChange = (theme) => {
  setTheme(theme)
}

const getThemeLabel = (theme) => {
  const labels = {
    default: '默认',
    dark: '暗色',
    blue: '蓝色',
    green: '绿色',
    purple: '紫色'
  }
  return labels[theme] || theme
}
</script>

2. 自定义主题样式

<template>
  <div class="custom-component">
    <h1 class="title">标题</h1>
    <p class="content">内容文本</p>
    <button class="btn btn-primary">按钮</button>
  </div>
</template>

<style scoped>
.custom-component {
  background-color: var(--theme-bg-primary);
  border: 1px solid var(--theme-border-light);
  border-radius: var(--theme-border-radius-large);
  padding: var(--theme-spacing-lg);
}

.title {
  color: var(--theme-text-primary);
  font-size: var(--theme-font-size-large);
  margin-bottom: var(--theme-spacing-md);
}

.content {
  color: var(--theme-text-regular);
  font-size: var(--theme-font-size-base);
  line-height: 1.6;
}

.btn {
  background-color: var(--theme-primary);
  color: var(--theme-bg-primary);
  border: none;
  border-radius: var(--theme-border-radius-base);
  padding: var(--theme-spacing-sm) var(--theme-spacing-md);
  transition: var(--theme-transition-base);
}

.btn:hover {
  background-color: var(--theme-primary-light);
}
</style>

注意事项

  1. 性能优化: 避免过度使用CSS变量,合理组织主题结构
  2. 兼容性: 确保在不支持CSS变量的浏览器中有降级方案
  3. 可访问性: 确保所有主题都符合对比度要求
  4. 一致性: 保持主题间的视觉一致性和用户体验
  5. 测试覆盖: 在所有主题下充分测试组件和页面

相关文档


最后更新时间:2025-09-19