Skip to content

主题定制

概述

RuoYi-Plus-UniApp 移动端提供了完整的主题定制系统,支持全局主题配置、深色模式、CSS 变量定制等功能。主题系统基于 WD UI 组件库的 ConfigProvider 组件实现,提供了超过 2000 个主题变量,覆盖所有组件的样式定制需求。

核心特性

  • 响应式主题管理 - 通过 Vue 3 Composition API 实现响应式主题配置
  • 三层优先级机制 - 支持默认主题、全局覆盖、局部覆盖三层优先级
  • CSS 变量系统 - 完整的 --wot-* CSS 变量体系,支持运行时动态切换
  • 深色模式支持 - 内置深色主题变量,支持自动和手动切换
  • SCSS 变量回退 - 所有 SCSS 变量支持 CSS 变量回退机制
  • 持久化存储 - 使用 UniApp 存储 API 实现主题配置持久化
  • UnoCSS 集成 - 原子化 CSS 框架集成,支持主题色引用
  • TypeScript 支持 - 完整的类型定义,提供开发时类型检查

架构设计

主题系统架构

┌─────────────────────────────────────────────────────────────┐
│                      应用层 (App.vue)                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              ConfigProvider 组件                      │   │
│  │  ┌─────────────┬─────────────┬─────────────────┐    │   │
│  │  │ theme       │ themeVars   │ CSS Variables   │    │   │
│  │  │ 'light'     │ 主题变量对象 │ --wot-*        │    │   │
│  │  │ 'dark'      │             │                 │    │   │
│  │  └─────────────┴─────────────┴─────────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│                            ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              useTheme Composable                      │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │ 默认主题 < 全局覆盖 < 局部覆盖               │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│                            ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              SCSS/CSS 变量系统                        │   │
│  │  variable.scss → CSS Variables → 组件样式           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

核心文件结构

plus-uniapp/src/
├── composables/
│   └── useTheme.ts              # 主题管理 Composable
├── wd/
│   ├── components/
│   │   ├── wd-config-provider/  # 全局配置组件
│   │   │   └── wd-config-provider.vue
│   │   └── common/
│   │       └── abstracts/
│   │           └── variable.scss # SCSS 变量定义
│   └── index.ts                 # 类型导出
├── utils/
│   └── cache.ts                 # 缓存工具(主题存储)
├── App.vue                      # 全局样式入口
└── uno.config.ts                # UnoCSS 主题配置

ConfigProvider 全局配置

基础用法

ConfigProvider 是 WD UI 组件库的全局配置提供者,用于配置主题、深色模式等全局属性。

vue
<template>
  <wd-config-provider :theme="theme" :theme-vars="themeVars">
    <!-- 应用内容 -->
    <router-view />
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import type { ConfigProviderThemeVars } from '@/wd'

const theme = ref<'light' | 'dark'>('light')

const themeVars = ref<ConfigProviderThemeVars>({
  colorTheme: '#409EFF',
  colorSuccess: '#52C41A',
  colorWarning: '#FFBA00',
  colorDanger: '#F56C6C',
})
</script>

Props 属性

属性说明类型默认值
theme主题风格,设置为 dark 开启深色模式'light' | 'dark''light'
themeVars自定义主题变量对象ConfigProviderThemeVars{}
customStyle自定义根节点样式string''
customClass自定义根节点样式类string''

深色模式

通过设置 theme 属性为 'dark' 开启深色模式:

vue
<template>
  <wd-config-provider :theme="isDark ? 'dark' : 'light'">
    <view class="app">
      <wd-button @click="toggleTheme">切换主题</wd-button>
    </view>
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const isDark = ref(false)

const toggleTheme = () => {
  isDark.value = !isDark.value
}
</script>

主题变量定制

通过 themeVars 属性自定义组件样式:

vue
<template>
  <wd-config-provider :theme-vars="themeVars">
    <wd-button type="primary">主题按钮</wd-button>
    <wd-cell title="单元格" value="内容" />
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import type { ConfigProviderThemeVars } from '@/wd'

const themeVars = ref<ConfigProviderThemeVars>({
  // 基础色彩
  colorTheme: '#1890ff',
  colorSuccess: '#52c41a',
  colorWarning: '#faad14',
  colorDanger: '#ff4d4f',

  // 按钮样式
  buttonPrimaryBgColor: '#1890ff',
  buttonPrimaryColor: '#ffffff',
  buttonMediumHeight: '80rpx',
  buttonMediumFontSize: '28rpx',

  // 单元格样式
  cellPadding: '24rpx 32rpx',
  cellTitleFontSize: '28rpx',
  cellValueFontSize: '26rpx',

  // 导航栏
  navbarTitleFontSize: '32rpx',
  navbarTitleFontWeight: '500',
})
</script>

useTheme Composable

概述

useTheme 是一个用于管理主题配置的 Composable,提供了全局主题状态管理和便捷的主题操作方法。

基础用法

typescript
import { useTheme } from '@/composables/useTheme'

const { themeVars, setGlobalTheme, resetGlobalTheme, getCurrentTheme } = useTheme()

// 获取当前主题配置
const currentTheme = getCurrentTheme()

// 设置全局主题覆盖
setGlobalTheme({
  colorTheme: '#ff6b6b',
  buttonPrimaryBgColor: '#ff6b6b',
})

// 重置全局主题覆盖
resetGlobalTheme()

API 说明

typescript
interface UseThemeReturn {
  /**
   * 计算属性,返回合并后的完整主题配置
   * 优先级: 默认主题 < 全局覆盖 < 局部覆盖
   */
  themeVars: ComputedRef<ConfigProviderThemeVars>

  /**
   * 设置全局主题覆盖
   * @param overrides - 要覆盖的主题变量
   */
  setGlobalTheme: (overrides: Partial<ConfigProviderThemeVars>) => void

  /**
   * 重置全局主题覆盖
   */
  resetGlobalTheme: () => void

  /**
   * 获取当前生效的主题配置
   */
  getCurrentTheme: () => ConfigProviderThemeVars
}

优先级机制

主题系统采用三层优先级机制:

typescript
// 优先级: 默认主题 < 全局覆盖 < 局部覆盖

// 1. 默认主题 (最低优先级)
const DEFAULT_THEME: ConfigProviderThemeVars = {
  colorTheme: '#409EFF',
  colorSuccess: '#52C41A',
  // ...
}

// 2. 全局覆盖 (中等优先级)
setGlobalTheme({
  colorTheme: '#1890ff',  // 会覆盖默认值
})

// 3. 局部覆盖 (最高优先级)
const localTheme = {
  colorTheme: '#ff6b6b',  // 会覆盖全局和默认值
}

完整示例

vue
<template>
  <view class="theme-demo">
    <view class="section">
      <text class="title">当前主题色: {{ currentColor }}</text>
      <view class="color-picker">
        <view
          v-for="color in colors"
          :key="color"
          class="color-item"
          :style="{ backgroundColor: color }"
          @click="changeTheme(color)"
        />
      </view>
    </view>

    <view class="section">
      <wd-button type="primary" block>主题按钮</wd-button>
    </view>

    <view class="section">
      <wd-button @click="resetTheme">重置主题</wd-button>
    </view>
  </view>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useTheme } from '@/composables/useTheme'

const { themeVars, setGlobalTheme, resetGlobalTheme, getCurrentTheme } = useTheme()

const colors = ['#409EFF', '#1890ff', '#ff6b6b', '#52c41a', '#722ed1']

const currentColor = computed(() => getCurrentTheme().colorTheme)

const changeTheme = (color: string) => {
  setGlobalTheme({
    colorTheme: color,
    buttonPrimaryBgColor: color,
  })
}

const resetTheme = () => {
  resetGlobalTheme()
}
</script>

CSS 变量系统

变量命名规范

WD UI 组件库的所有 CSS 变量都以 --wot- 前缀开头,变量名采用短横线分隔(kebab-case)命名:

JavaScript 属性名         →    CSS 变量名
colorTheme               →    --wot-color-theme
buttonPrimaryBgColor     →    --wot-button-primary-bg-color
navbarTitleFontSize      →    --wot-navbar-title-font-size

基础色彩变量

scss
/* 主题颜色 */
--wot-color-theme: #409EFF;         /* 主题色 */
--wot-color-white: rgb(255, 255, 255);
--wot-color-black: rgb(0, 0, 0);

/* 功能色 */
--wot-color-success: #34d19d;       /* 成功色 */
--wot-color-warning: #f0883a;       /* 警告色 */
--wot-color-danger: #fa4350;        /* 危险色 */
--wot-color-purple: #8268de;        /* 紫色 */
--wot-color-yellow: #f0cd1d;        /* 黄色 */
--wot-color-blue: #2bb3ed;          /* 蓝色 */

/* 灰色系统 - 8个等级 */
--wot-color-gray-1: #f9f9f9;        /* 最浅灰 */
--wot-color-gray-2: #f2f3f5;
--wot-color-gray-3: #e5e6eb;
--wot-color-gray-4: #c9cdd4;
--wot-color-gray-5: #a9aeb8;
--wot-color-gray-6: #86909c;
--wot-color-gray-7: #6b7785;
--wot-color-gray-8: #4e5969;        /* 最深灰 */

文字颜色变量

scss
/* 字体灰色(透明度级别) */
--wot-font-gray-1: rgba(0, 0, 0, 0.9);   /* 主要文字 */
--wot-font-gray-2: rgba(0, 0, 0, 0.6);   /* 次要文字 */
--wot-font-gray-3: rgba(0, 0, 0, 0.4);   /* 辅助文字 */
--wot-font-gray-4: rgba(0, 0, 0, 0.26);  /* 禁用文字 */

/* 白色字体 */
--wot-font-white-1: rgba(255, 255, 255, 1);     /* 主要白字 */
--wot-font-white-2: rgba(255, 255, 255, 0.55);  /* 次要白字 */

/* 语义化文字颜色 */
--wot-color-title: #000000;         /* 标题颜色 */
--wot-color-content: #262626;       /* 内容颜色 */
--wot-color-secondary: #595959;     /* 次要内容 */
--wot-color-aid: #8c8c8c;           /* 辅助内容 */
--wot-color-tip: #bfbfbf;           /* 提示内容 */

边框和背景变量

scss
/* 边框颜色 */
--wot-color-border: #d9d9d9;        /* 默认边框 */
--wot-color-border-light: #e8e8e8;  /* 浅色边框 */

/* 背景颜色 */
--wot-color-bg: #f5f5f5;            /* 页面背景 */

字体尺寸变量

scss
/* 字体大小 */
--wot-fs-big: 48rpx;                /* 大型标题 */
--wot-fs-important: 38rpx;          /* 重要标题 */
--wot-fs-title: 32rpx;              /* 常规标题 */
--wot-fs-content: 28rpx;            /* 正文内容 */
--wot-fs-secondary: 24rpx;          /* 次要内容 */
--wot-fs-aid: 20rpx;                /* 辅助文字 */

/* 字重 */
--wot-fw-medium: 500;               /* 中等粗细 */
--wot-fw-semibold: 600;             /* 半粗体 */

间距变量

scss
/* 基础间距 */
--wot-size-side-padding: 30rpx;     /* 页面边距 */

深色模式变量

scss
/* 深色背景 */
--wot-dark-background: #131313;     /* 主背景 */
--wot-dark-background2: #1b1b1b;    /* 次级背景 */
--wot-dark-background3: #232323;    /* 卡片背景 */
--wot-dark-background4: #2b2b2b;    /* 悬浮背景 */
--wot-dark-background5: #333333;    /* 输入框背景 */
--wot-dark-background6: #3b3b3b;    /* 禁用背景 */
--wot-dark-background7: #434343;    /* 分割线 */

/* 深色文字 */
--wot-dark-color: rgba(255, 255, 255, 0.9);      /* 主要文字 */
--wot-dark-color2: rgba(255, 255, 255, 0.7);     /* 次要文字 */
--wot-dark-color3: rgba(255, 255, 255, 0.5);     /* 辅助文字 */
--wot-dark-color4: rgba(255, 255, 255, 0.35);    /* 禁用文字 */

组件主题变量

Button 按钮

scss
/* 按钮主题变量 */
--wot-button-primary-bg-color: var(--wot-color-theme);
--wot-button-primary-color: #ffffff;
--wot-button-success-bg-color: var(--wot-color-success);
--wot-button-warning-bg-color: var(--wot-color-warning);
--wot-button-danger-bg-color: var(--wot-color-danger);

/* 按钮尺寸 */
--wot-button-small-height: 56rpx;
--wot-button-small-padding: 0 20rpx;
--wot-button-small-font-size: 24rpx;

--wot-button-medium-height: 72rpx;
--wot-button-medium-padding: 0 30rpx;
--wot-button-medium-font-size: 28rpx;

--wot-button-large-height: 96rpx;
--wot-button-large-padding: 0 40rpx;
--wot-button-large-font-size: 32rpx;

/* 按钮圆角 */
--wot-button-border-radius: 8rpx;
--wot-button-round-border-radius: 999rpx;

Cell 单元格

scss
/* 单元格主题变量 */
--wot-cell-padding: 26rpx 30rpx;
--wot-cell-wrapper-padding: 0 30rpx;
--wot-cell-bg-color: #ffffff;
--wot-cell-title-font-size: 28rpx;
--wot-cell-title-color: var(--wot-color-title);
--wot-cell-value-font-size: 28rpx;
--wot-cell-value-color: var(--wot-color-content);
--wot-cell-label-font-size: 24rpx;
--wot-cell-label-color: var(--wot-color-tip);
--wot-cell-required-color: var(--wot-color-danger);
--wot-cell-border-color: var(--wot-color-border-light);

Input 输入框

scss
/* 输入框主题变量 */
--wot-input-padding: 18rpx;
--wot-input-font-size: 28rpx;
--wot-input-color: var(--wot-color-title);
--wot-input-placeholder-color: var(--wot-color-tip);
--wot-input-disabled-color: var(--wot-color-tip);
--wot-input-bg-color: #ffffff;
--wot-input-border-color: var(--wot-color-border);
--wot-input-focus-border-color: var(--wot-color-theme);
--wot-input-error-border-color: var(--wot-color-danger);
--wot-input-clear-color: var(--wot-color-tip);
scss
/* 导航栏主题变量 */
--wot-navbar-height: 88rpx;
--wot-navbar-bg-color: #ffffff;
--wot-navbar-title-font-size: 32rpx;
--wot-navbar-title-font-weight: normal;
--wot-navbar-title-color: var(--wot-color-title);
--wot-navbar-icon-color: var(--wot-color-title);
--wot-navbar-icon-size: 40rpx;
--wot-navbar-capsule-bg-color: rgba(0, 0, 0, 0.04);
--wot-navbar-capsule-border-color: rgba(0, 0, 0, 0.08);

Toast 轻提示

scss
/* Toast 主题变量 */
--wot-toast-padding: 24rpx 30rpx;
--wot-toast-font-size: 28rpx;
--wot-toast-color: #ffffff;
--wot-toast-bg-color: rgba(0, 0, 0, 0.8);
--wot-toast-border-radius: 16rpx;
--wot-toast-icon-size: 72rpx;
--wot-toast-loading-size: 60rpx;
scss
/* 模态框主题变量 */
--wot-modal-width: 560rpx;
--wot-modal-padding: 48rpx 40rpx 32rpx;
--wot-modal-bg-color: #ffffff;
--wot-modal-border-radius: 24rpx;
--wot-modal-title-font-size: 34rpx;
--wot-modal-title-color: var(--wot-color-title);
--wot-modal-content-font-size: 28rpx;
--wot-modal-content-color: var(--wot-color-content);
--wot-modal-btn-font-size: 32rpx;
--wot-modal-confirm-btn-color: var(--wot-color-theme);
--wot-modal-cancel-btn-color: var(--wot-color-content);

SCSS 变量系统

变量回退机制

WD UI 的 SCSS 变量系统支持 CSS 变量回退,确保在不支持 CSS 变量的环境下仍能正常显示:

scss
// variable.scss 中的定义
$default-theme: #0957DE;  // 默认主题色

// SCSS 变量定义,支持 CSS 变量回退
$-color-theme: var(--wot-color-theme, $default-theme) !default;
$-color-success: var(--wot-color-success, #34d19d) !default;
$-color-warning: var(--wot-color-warning, #f0883a) !default;
$-color-danger: var(--wot-color-danger, #fa4350) !default;

在组件中使用

scss
// 组件样式文件
@import '../common/abstracts/variable';

.my-component {
  // 使用 SCSS 变量
  color: $-color-theme;
  background-color: $-color-bg;
  font-size: $-fs-content;
  padding: $-size-side-padding;

  &__title {
    color: $-color-title;
    font-size: $-fs-title;
    font-weight: $-fw-medium;
  }

  &__content {
    color: $-color-content;
    font-size: $-fs-content;
  }

  &__tip {
    color: $-color-tip;
    font-size: $-fs-aid;
  }
}

变量覆盖

在组件或页面中覆盖 SCSS 变量:

scss
// 方式1: 直接覆盖 CSS 变量
:root {
  --wot-color-theme: #1890ff;
  --wot-button-primary-bg-color: #1890ff;
}

// 方式2: 在组件内覆盖
.my-page {
  --wot-color-theme: #ff6b6b;

  // 组件会使用覆盖后的变量值
  .wd-button--primary {
    // 按钮背景色会变成 #ff6b6b
  }
}

UnoCSS 主题集成

配置说明

项目使用 UnoCSS 作为原子化 CSS 框架,在 uno.config.ts 中配置了与主题系统的集成:

typescript
// uno.config.ts
import { defineConfig } from 'unocss'
import presetUni from '@dcloudio/uni-preset-uno'
import { presetIcons, presetAttributify } from 'unocss'

export default defineConfig({
  presets: [
    presetUni({
      attributify: {
        prefixedOnly: true,
      },
    }),
    presetIcons({
      scale: 1.2,
      warn: true,
      extraProperties: {
        display: 'inline-block',
        'vertical-align': 'middle',
      },
    }),
    presetAttributify(),
  ],

  // 主题配置
  theme: {
    colors: {
      // 引用 CSS 变量,实现主题联动
      primary: 'var(--wot-color-theme, #0957DE)',
      success: 'var(--wot-color-success, #34d19d)',
      warning: 'var(--wot-color-warning, #f0883a)',
      danger: 'var(--wot-color-danger, #fa4350)',
    },
    fontSize: {
      '2xs': ['20rpx', '28rpx'],
      '3xs': ['18rpx', '26rpx'],
    },
  },
})

使用主题色

vue
<template>
  <view class="demo">
    <!-- 使用主题色 -->
    <text class="text-primary">主题色文字</text>
    <text class="text-success">成功色文字</text>
    <text class="text-warning">警告色文字</text>
    <text class="text-danger">危险色文字</text>

    <!-- 背景色 -->
    <view class="bg-primary p-4 text-white">主题色背景</view>

    <!-- 边框色 -->
    <view class="border border-primary p-4">主题色边框</view>
  </view>
</template>

主题持久化

使用缓存工具

通过 cache.ts 工具实现主题配置的持久化存储:

typescript
import { cache } from '@/utils/cache'
import type { ConfigProviderThemeVars } from '@/wd'

// 存储主题模式
cache.set('themeMode', 'dark')

// 存储主题配置对象
cache.set('themeConfig', {
  colorTheme: '#409EFF',
  colorSuccess: '#52C41A',
})

// 读取主题模式
const themeMode = cache.get<string>('themeMode')

// 读取主题配置
const themeConfig = cache.get<ConfigProviderThemeVars>('themeConfig')

// 设置过期时间(7天)
cache.set('themeMode', 'dark', 7 * 24 * 3600)

完整的主题持久化方案

typescript
// composables/useThemePersist.ts
import { ref, watch, onMounted } from 'vue'
import { cache } from '@/utils/cache'
import type { ConfigProviderThemeVars } from '@/wd'

const THEME_MODE_KEY = 'app_theme_mode'
const THEME_CONFIG_KEY = 'app_theme_config'

export function useThemePersist() {
  const themeMode = ref<'light' | 'dark'>('light')
  const themeConfig = ref<ConfigProviderThemeVars>({})

  // 初始化时从缓存读取
  onMounted(() => {
    const savedMode = cache.get<'light' | 'dark'>(THEME_MODE_KEY)
    const savedConfig = cache.get<ConfigProviderThemeVars>(THEME_CONFIG_KEY)

    if (savedMode) {
      themeMode.value = savedMode
    }

    if (savedConfig) {
      themeConfig.value = savedConfig
    }
  })

  // 监听变化并自动保存
  watch(themeMode, (newMode) => {
    cache.set(THEME_MODE_KEY, newMode)
  })

  watch(themeConfig, (newConfig) => {
    cache.set(THEME_CONFIG_KEY, newConfig)
  }, { deep: true })

  // 切换主题模式
  const toggleThemeMode = () => {
    themeMode.value = themeMode.value === 'light' ? 'dark' : 'light'
  }

  // 设置主题配置
  const setThemeConfig = (config: Partial<ConfigProviderThemeVars>) => {
    themeConfig.value = { ...themeConfig.value, ...config }
  }

  // 重置主题
  const resetTheme = () => {
    themeMode.value = 'light'
    themeConfig.value = {}
    cache.remove(THEME_MODE_KEY)
    cache.remove(THEME_CONFIG_KEY)
  }

  return {
    themeMode,
    themeConfig,
    toggleThemeMode,
    setThemeConfig,
    resetTheme,
  }
}

在 App.vue 中使用

vue
<template>
  <wd-config-provider :theme="themeMode" :theme-vars="themeConfig">
    <slot />
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { useThemePersist } from '@/composables/useThemePersist'

const { themeMode, themeConfig } = useThemePersist()
</script>

跟随系统主题

监听系统主题变化

typescript
// composables/useSystemTheme.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useSystemTheme() {
  const systemTheme = ref<'light' | 'dark'>('light')

  // 获取系统主题
  const getSystemTheme = (): 'light' | 'dark' => {
    const res = uni.getSystemInfoSync()
    return res.theme || 'light'
  }

  // 监听系统主题变化
  const handleThemeChange = (res: { theme: 'light' | 'dark' }) => {
    systemTheme.value = res.theme
  }

  onMounted(() => {
    // 初始化系统主题
    systemTheme.value = getSystemTheme()

    // 监听系统主题变化
    // #ifdef MP-WEIXIN
    uni.onThemeChange(handleThemeChange)
    // #endif
  })

  onUnmounted(() => {
    // #ifdef MP-WEIXIN
    uni.offThemeChange(handleThemeChange)
    // #endif
  })

  return {
    systemTheme,
    getSystemTheme,
  }
}

自动跟随系统主题

vue
<template>
  <wd-config-provider :theme="currentTheme">
    <view class="app">
      <wd-cell title="跟随系统" center>
        <template #value>
          <wd-switch v-model="followSystem" />
        </template>
      </wd-cell>

      <wd-cell v-if="!followSystem" title="深色模式" center>
        <template #value>
          <wd-switch v-model="isDark" />
        </template>
      </wd-cell>
    </view>
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { useSystemTheme } from '@/composables/useSystemTheme'
import { cache } from '@/utils/cache'

const { systemTheme } = useSystemTheme()

const followSystem = ref(cache.get<boolean>('followSystem') ?? true)
const isDark = ref(cache.get<boolean>('isDark') ?? false)

const currentTheme = computed(() => {
  if (followSystem.value) {
    return systemTheme.value
  }
  return isDark.value ? 'dark' : 'light'
})

// 保存设置
watch(followSystem, (val) => cache.set('followSystem', val))
watch(isDark, (val) => cache.set('isDark', val))
</script>

主题切换动画

基础过渡动画

vue
<template>
  <wd-config-provider :theme="theme" :theme-vars="themeVars">
    <view class="app" :class="{ 'theme-transitioning': isTransitioning }">
      <slot />
    </view>
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const theme = ref<'light' | 'dark'>('light')
const isTransitioning = ref(false)

const toggleTheme = () => {
  isTransitioning.value = true

  // 延迟切换主题,让过渡动画生效
  setTimeout(() => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }, 50)

  // 动画结束后移除过渡类
  setTimeout(() => {
    isTransitioning.value = false
  }, 350)
}
</script>

<style lang="scss">
.app {
  transition: none;

  &.theme-transitioning {
    transition: background-color 0.3s ease, color 0.3s ease;

    * {
      transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
    }
  }
}
</style>

主题配置示例

品牌主题配置

typescript
// themes/brand.ts
import type { ConfigProviderThemeVars } from '@/wd'

// 默认品牌主题
export const defaultBrandTheme: ConfigProviderThemeVars = {
  colorTheme: '#409EFF',
  colorSuccess: '#52C41A',
  colorWarning: '#FFBA00',
  colorDanger: '#F56C6C',
}

// 蓝色主题
export const blueTheme: ConfigProviderThemeVars = {
  colorTheme: '#1890ff',
  buttonPrimaryBgColor: '#1890ff',
  navbarBgColor: '#1890ff',
}

// 绿色主题
export const greenTheme: ConfigProviderThemeVars = {
  colorTheme: '#52c41a',
  buttonPrimaryBgColor: '#52c41a',
  navbarBgColor: '#52c41a',
}

// 紫色主题
export const purpleTheme: ConfigProviderThemeVars = {
  colorTheme: '#722ed1',
  buttonPrimaryBgColor: '#722ed1',
  navbarBgColor: '#722ed1',
}

// 红色主题
export const redTheme: ConfigProviderThemeVars = {
  colorTheme: '#f5222d',
  buttonPrimaryBgColor: '#f5222d',
  navbarBgColor: '#f5222d',
}

主题选择器组件

vue
<template>
  <view class="theme-selector">
    <view class="theme-list">
      <view
        v-for="(item, index) in themes"
        :key="index"
        class="theme-item"
        :class="{ active: currentIndex === index }"
        @click="selectTheme(index)"
      >
        <view
          class="theme-preview"
          :style="{ backgroundColor: item.config.colorTheme }"
        />
        <text class="theme-name">{{ item.name }}</text>
      </view>
    </view>
  </view>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { useTheme } from '@/composables/useTheme'
import {
  defaultBrandTheme,
  blueTheme,
  greenTheme,
  purpleTheme,
  redTheme,
} from '@/themes/brand'

const { setGlobalTheme } = useTheme()

const themes = [
  { name: '默认', config: defaultBrandTheme },
  { name: '蓝色', config: blueTheme },
  { name: '绿色', config: greenTheme },
  { name: '紫色', config: purpleTheme },
  { name: '红色', config: redTheme },
]

const currentIndex = ref(0)

const selectTheme = (index: number) => {
  currentIndex.value = index
  setGlobalTheme(themes[index].config)
}
</script>

类型定义

ConfigProviderThemeVars

typescript
/**
 * 主题变量类型定义
 */
export interface ConfigProviderThemeVars {
  // 基础色彩
  colorTheme?: string
  colorWhite?: string
  colorBlack?: string
  colorSuccess?: string
  colorWarning?: string
  colorDanger?: string
  colorPurple?: string
  colorYellow?: string
  colorBlue?: string

  // 灰色系统
  colorGray1?: string
  colorGray2?: string
  colorGray3?: string
  colorGray4?: string
  colorGray5?: string
  colorGray6?: string
  colorGray7?: string
  colorGray8?: string

  // 字体颜色
  fontGray1?: string
  fontGray2?: string
  fontGray3?: string
  fontGray4?: string
  fontWhite1?: string
  fontWhite2?: string

  // 语义化颜色
  colorTitle?: string
  colorContent?: string
  colorSecondary?: string
  colorAid?: string
  colorTip?: string
  colorBorder?: string
  colorBorderLight?: string
  colorBg?: string

  // 深色模式
  darkBackground?: string
  darkBackground2?: string
  darkBackground3?: string
  darkBackground4?: string
  darkBackground5?: string
  darkBackground6?: string
  darkBackground7?: string
  darkColor?: string
  darkColor2?: string
  darkColor3?: string
  darkColor4?: string

  // 字体尺寸
  fsBig?: string
  fsImportant?: string
  fsTitle?: string
  fsContent?: string
  fsSecondary?: string
  fsAid?: string

  // 字重
  fwMedium?: number | string
  fwSemibold?: number | string

  // 间距
  sizeSidePadding?: string

  // 组件特定变量
  // Button
  buttonPrimaryBgColor?: string
  buttonPrimaryColor?: string
  buttonSuccessBgColor?: string
  buttonWarningBgColor?: string
  buttonDangerBgColor?: string
  buttonSmallHeight?: string
  buttonMediumHeight?: string
  buttonLargeHeight?: string
  buttonBorderRadius?: string

  // Cell
  cellPadding?: string
  cellBgColor?: string
  cellTitleFontSize?: string
  cellTitleColor?: string
  cellValueFontSize?: string
  cellValueColor?: string

  // Input
  inputPadding?: string
  inputFontSize?: string
  inputColor?: string
  inputPlaceholderColor?: string
  inputBorderColor?: string
  inputFocusBorderColor?: string

  // Navbar
  navbarHeight?: string
  navbarBgColor?: string
  navbarTitleFontSize?: string
  navbarTitleFontWeight?: string | number
  navbarTitleColor?: string

  // Toast
  toastPadding?: string
  toastFontSize?: string
  toastColor?: string
  toastBgColor?: string
  toastBorderRadius?: string

  // Modal
  modalWidth?: string
  modalPadding?: string
  modalBgColor?: string
  modalBorderRadius?: string
  modalTitleFontSize?: string
  modalTitleColor?: string
  modalContentFontSize?: string
  modalContentColor?: string

  // Loading
  loadingSize?: string

  // MessageBox
  messageBoxTitleColor?: string
  messageBoxContentColor?: string

  // Notify
  notifyPadding?: string
  notifyFontSize?: string

  // ... 更多组件变量
  [key: string]: string | number | undefined
}

最佳实践

1. 统一主题入口

建议在 App.vue 中统一配置 ConfigProvider,避免在多处重复配置:

vue
<!-- App.vue -->
<template>
  <wd-config-provider :theme="theme" :theme-vars="themeVars">
    <router-view />
  </wd-config-provider>
</template>

<script lang="ts" setup>
import { useThemePersist } from '@/composables/useThemePersist'

const { themeMode: theme, themeConfig: themeVars } = useThemePersist()
</script>

2. 按需定制变量

只覆盖需要修改的变量,保持默认值的一致性:

typescript
// 推荐: 只覆盖需要的变量
const themeVars = {
  colorTheme: '#1890ff',
  buttonPrimaryBgColor: '#1890ff',
}

// 不推荐: 覆盖所有变量
const themeVars = {
  colorTheme: '#1890ff',
  colorSuccess: '#52c41a',
  colorWarning: '#faad14',
  colorDanger: '#ff4d4f',
  // ... 大量不需要修改的变量
}

3. 使用语义化命名

为自定义主题使用语义化的命名:

typescript
// 推荐: 语义化命名
const themes = {
  brand: { colorTheme: '#1890ff' },
  dark: { colorTheme: '#177ddc' },
  festival: { colorTheme: '#f5222d' },  // 节日主题
}

// 不推荐: 无意义命名
const themes = {
  theme1: { colorTheme: '#1890ff' },
  theme2: { colorTheme: '#177ddc' },
}

4. 深色模式适配

确保自定义样式同时适配深色模式:

scss
.my-component {
  // 亮色模式
  background-color: var(--wot-color-bg);
  color: var(--wot-color-content);

  // 深色模式自动适配
  // ConfigProvider 会自动应用深色变量
}

常见问题

1. 主题变量不生效

问题: 设置了 themeVars 但组件样式没有变化

解决方案:

  • 确保 ConfigProvider 包裹了目标组件
  • 检查变量名是否正确(使用驼峰命名)
  • 确认组件支持该主题变量
vue
<!-- 正确用法 -->
<wd-config-provider :theme-vars="{ colorTheme: '#1890ff' }">
  <wd-button type="primary">按钮</wd-button>
</wd-config-provider>

<!-- 错误: ConfigProvider 没有包裹组件 -->
<wd-config-provider :theme-vars="{ colorTheme: '#1890ff' }" />
<wd-button type="primary">按钮</wd-button>

2. 深色模式闪烁

问题: 页面加载时深色模式出现闪烁

解决方案:

  • 在 App.vue 中同步读取缓存的主题设置
  • 使用 CSS 变量提前设置默认值
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { cache } from '@/utils/cache'

// 同步读取,避免闪烁
const theme = ref(cache.get<'light' | 'dark'>('theme') || 'light')
</script>

3. 主题变量类型错误

问题: TypeScript 报类型错误

解决方案:

  • 使用 ConfigProviderThemeVars 类型
  • 确保值类型正确(字符串/数字)
typescript
import type { ConfigProviderThemeVars } from '@/wd'

const themeVars: ConfigProviderThemeVars = {
  colorTheme: '#1890ff',        // 正确: 字符串
  fwMedium: 500,                // 正确: 数字
  buttonMediumHeight: '80rpx',  // 正确: 字符串(带单位)
}

4. UnoCSS 主题色不同步

问题: UnoCSS 的主题色与 WD UI 不一致

解决方案: 确保 UnoCSS 配置引用了 CSS 变量:

typescript
// uno.config.ts
theme: {
  colors: {
    primary: 'var(--wot-color-theme, #0957DE)',
  },
}