【uniapp实践】主题样式配置浅色深色以及自定义

实现效果:

1.需要配置的样式

1.1 设置每个主题对应的颜色, 主题配置映射 $themes:

@/styles/themes/variables.scss

复制代码
// 主题配置映射
$themes: (
  "light": (
    // 基础颜色
    --color-primary: #2979ff,
    --color-success: #19be6b,
    --color-warning: #ff9900,
    --color-error: #fa3534,
    --color-info: #909399,
    
    // 背景色
    --bg-color: #ffffff,
    --bg-color-page: #f2f2f6,
    --bg-color-grey: #f7f7f7,
    --bg-color-card: #ffffff,
    
    // 文字颜色
    --text-color: #303133,
    --text-color-regular: #606266,
    --text-color-secondary: #909399,
    --text-color-placeholder: #c0c4cc,
    
    // 边框颜色
    --border-color: #e4e7ed,
    --border-color-light: #ebeef5,
    
    // 阴影
    --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1),
    
    // uView主题映射
    --u-primary: #2979ff,
    --u-success: #19be6b,
    --u-warning: #ff9900,
    --u-error: #fa3534
  ),
  
  "dark": (
    --color-primary: #2b85e4,
    --color-success: #18b566,
    --color-warning: #f29100,
    --color-error: #d63031,
    --color-info: #6b7280,
    
    --bg-color: #1a1a1a,
    --bg-color-page: #0f0f0f,
    --bg-color-grey: #2d2d2d,
    --bg-color-card: #2d2d2d,
    
    --text-color: #e5e7eb,
    --text-color-regular: #d1d5db,
    --text-color-secondary: #9ca3af,
    --text-color-placeholder: #6b7280,
    
    --border-color: #374151,
    --border-color-light: #4b5563,
    
    --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.6),
    
    --u-primary: #2b85e4,
    --u-success: #18b566,
    --u-warning: #f29100,
    --u-error: #d63031
  ),
  
  "blue": (
    --color-primary: #409eff,
    --color-success: #67c23a,
    --color-warning: #e6a23c,
    --color-error: #f56c6c,
    --color-info: #909399,
    
    --bg-color: #ecf5ff,
    --bg-color-page: #d9ecff,
    --bg-color-grey: #f0f7ff,
    --bg-color-card: #ffffff,
    
    --text-color: #303133,
    --text-color-regular: #606266,
    --text-color-secondary: #909399,
    --text-color-placeholder: #c0c4cc,
    
    --border-color: #b3d8ff,
    --border-color-light: #d9ecff,
    
    --box-shadow: 0 2px 12px 0 rgba(64, 158, 255, 0.1),
    
    --u-primary: #409eff,
    --u-success: #67c23a,
    --u-warning: #e6a23c,
    --u-error: #f56c6c
  ),
  
  "red": (
    --color-primary: #e74c3c,
    --color-success: #27ae60,
    --color-warning: #f39c12,
    --color-error: #c0392b,
    --color-info: #95a5a6,
    
    --bg-color: #fdedec,
    --bg-color-page: #fadbd8,
    --bg-color-grey: #fbefef,
    --bg-color-card: #ffffff,
    
    --text-color: #2c3e50,
    --text-color-regular: #34495e,
    --text-color-secondary: #7f8c8d,
    --text-color-placeholder: #bdc3c7,
    
    --border-color: #f5b7b1,
    --border-color-light: #fadbd8,
    
    --box-shadow: 0 2px 12px 0 rgba(231, 76, 60, 0.1),
    
    --u-primary: #e74c3c,
    --u-success: #27ae60,
    --u-warning: #f39c12,
    --u-error: #c0392b
  )
);

1.2 混入

@styles/mixins.scss

复制代码
@import '@/styles/themes/variables.scss';

// 应用主题变量
@mixin apply-theme($theme-name) {
  $theme: map-get($themes, $theme-name);
  
  @each $key, $value in $theme {
    #{$key}: #{$value};
  }
}

// 主题混入 - 用于在scoped样式中使用主题变量
@mixin theme-property($property, $key, $important: false) {
  @each $theme-name, $theme-map in $themes {
    $value: map-get($theme-map, $key);
    
    [data-theme="#{$theme-name}"] & {
      @if $important {
        #{$property}: #{$value} !important;
      } @else {
        #{$property}: #{$value};
      }
    }
  }
}

// 快捷混入
@mixin background-color($color, $important: false) {
  @include theme-property(background-color, $color, $important);
}

@mixin color($color, $important: false) {
  @include theme-property(color, $color, $important);
}

@mixin border-color($color, $important: false) {
  @include theme-property(border-color, $color, $important);
}

@mixin fill($color, $important: false) {
  @include theme-property(fill, $color, $important);
}

1.2.1 应用主题变量混入:apply-theme($theme-name)

通过主题名称来应用主题的所有变量。

复制代码
@mixin apply-theme($theme-name) {
  $theme: map-get($themes, $theme-name); //根据主题名获取对应主题的变量映射
  
  @each $key, $value in $theme { //循环遍历当前主题的所有变量
    #{$key}: #{$value};// 输出 "变量名: 值"(如 --color-primary: #072046)
  }
}

如何使用:

复制代码
// 全局样式文件(非 scoped)
@import '@/styles/mixins.scss';

// 给根元素注入 light 主题的所有变量
.page-container[data-theme="light"] {
  @include apply-theme(light); // 输出所有 light 主题的变量(--color-primary: #072046 等)
}

// 注入 dark 主题的所有变量
.page-container[data-theme="dark"] {
  @include apply-theme(dark); // 输出所有 dark 主题的变量
}

1.2.2 主题属性混入:theme-property($property, $key, $important: false)

复制代码
// 主题混入 - 用于在scoped样式中使用主题变量
@mixin theme-property($property, $key, $important: false) {
  @each $theme-name, $theme-map in $themes {//循环所有主题(light/dark/custom...)
    $value: map-get($theme-map, $key);//获取当前主题下 $key 对应的变量值(如 $key 为 --color-primary 时,light 主题值为 #072046)
    [data-theme="#{$theme-name}"] & {//生成 "主题标识 + 目标选择器" 的样式规则
      @if $important {
        #{$property}: #{$value} !important;
      } @else {
        #{$property}: #{$value};
      }
    }
  }
}

使用:

javascript 复制代码
.card {
  // 给 .card 的 background-color 适配所有主题
  @include theme-property(background-color, --bg-card); 
  // 等同于自动生成:
  // [data-theme="light"] .card { background-color: #ffffff; }
  // [data-theme="dark"] .card { background-color: #1e1e1e; }
  // [data-theme="custom"] .card { background-color: #fff8e1; }
}

.title {
  // 给 .title 的 color 适配所有主题,且加 !important
  @include theme-property(color, --color-text, true);
}

1.2.3 快捷混入(语法糖)

javascript 复制代码
// 背景色快捷混入
@mixin background-color($color, $important: false) {
  @include theme-property(background-color, $color, $important);
}

// 文字色快捷混入
@mixin color($color, $important: false) {
  @include theme-property(color, $color, $important);
}

// 边框色快捷混入
@mixin border-color($color, $important: false) {
  @include theme-property(border-color, $color, $important);
}

// 填充色(如 SVG fill)快捷混入
@mixin fill($color, $important: false) {
  @include theme-property(fill, $color, $important);
}

简化 theme-property 的使用,避免重复写 theme-property 和 CSS 属性名,提高开发效率。

使用:

javascript 复制代码
.card {
  // 等价于 @include theme-property(background-color, --bg-card);
  @include background-color(--bg-card); 
}

.title {
  // 等价于 @include theme-property(color, --color-text, true);
  @include color(--color-text, true);
}

.theme-option {
  // 等价于 @include theme-property(border-color, --border-color);
  @include border-color(--border-color);
}

1.3 主题样式入口文件,实现多样式作用于根文件。

javascript 复制代码
@import '@/styles/themes/variables.scss';
@import './mixins.scss';

// 为每个主题生成CSS变量
@each $theme-name, $theme-map in $themes {
  [data-theme="#{$theme-name}"] {
    @include apply-theme($theme-name);
    
    // 主题特定的全局样式
    background-color: var(--bg-color-page);
    color: var(--text-color);
    
    // 平滑过渡效果
    transition: background-color 0.3s ease, color 0.3s ease;
  }
}

// 全局主题类
.theme-primary {
  color: var(--color-primary);
}

.theme-bg {
  background-color: var(--bg-color);
}

.theme-card {
  background-color: var(--bg-color-card);
  border-color: var(--border-color-light);
}

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

.theme-text-regular {
  color: var(--text-color-regular);
}

.theme-border {
  border-color: var(--border-color);
}

1.4 pina实现统一管理主题配置、切换逻辑、持久化存储、以及 TabBar 样式同步

javascript 复制代码
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'

export const useThemeStore = defineStore('theme', () => {
  // 当前主题
  const currentTheme = ref('light')
  
  // 主题列表
  const themeList = ref([
    { 
      name: 'light', 
      label: '浅色主题', 
      icon: '🌞',
      tabBar: {
        backgroundColor: '#ffffff',
        borderStyle: 'black',
        color: '#999999',
        selectedColor: '#2979ff'
      }
    },
    { 
      name: 'dark', 
      label: '深色主题', 
      icon: '🌙',
      tabBar: {
        backgroundColor: '#1a1a1a',
        borderStyle: 'white',
        color: '#666666',
        selectedColor: '#2b85e4'
      }
    },
    { 
      name: 'blue', 
      label: '蓝色主题', 
      icon: '💙',
      tabBar: {
        backgroundColor: '#ecf5ff',
        borderStyle: 'black',
        color: '#909399',
        selectedColor: '#409eff'
      }
    },
    { 
      name: 'red', 
      label: '红色主题', 
      icon: '❤️',
      tabBar: {
        backgroundColor: '#fdedec',
        borderStyle: 'black',
        color: '#909399',
        selectedColor: '#e74c3c'
      }
    }
  ])
  
  // 获取当前主题的 TabBar 配置
  const currentTabBarConfig = computed(() => {
	  // 从主题列表中找到当前主题对应的配置
    const theme = themeList.value.find(t => t.name === currentTheme.value)
	// 找不到则返回默认主题(第一个)的 TabBar 配置(容错处理)
    return theme ? theme.tabBar : themeList.value[0].tabBar
  })
  
  // 切换主题
  const switchTheme = async (themeName) => {
    if (themeList.value.some(theme => theme.name === themeName)) {
		//更新当前主题状态(响应式更新,触发组件重新渲染)
      currentTheme.value = themeName
	  // 应用主题到 DOM(设置 data-theme 属性)
      await applyThemeToDOM(themeName)
	  // 更新 TabBar 样式(同步底部导航栏颜色)
      await updateTabBarStyle()
	  // 保存主题到本地存储(持久化,下次打开APP生效)
      saveThemeToStorage(themeName)
    }
  }
  
  // 应用主题到 DOM
  const applyThemeToDOM = (themeName) => {
    return new Promise((resolve) => {
		 // 给根元素(html)添加 data-theme 属性,值为当前主题名
      document.documentElement.setAttribute('data-theme', themeName)
      resolve()
    })
  }
  
  // 更新 TabBar 样式
  const updateTabBarStyle = () => {
    return new Promise((resolve, reject) => {
		// 获取当前主题的 TabBar 配置
      const config = currentTabBarConfig.value
      
      // 方法1: 使用 uni.setTabBarStyle (小程序官方API)
      if (typeof uni.setTabBarStyle === 'function') {
        uni.setTabBarStyle({
          backgroundColor: config.backgroundColor,
          borderStyle: config.borderStyle,
          color: config.color,
          selectedColor: config.selectedColor,
          success: () => {
            console.log('TabBar 样式更新成功')
            resolve()
          },
          fail: (error) => {
            console.error('TabBar 样式更新失败:', error)
            reject(error)
          }
        })
      } else {
        // 方法2: 自定义 TabBar 方案
        updateCustomTabBar()
        resolve()
      }
    })
  }
  
  // 更新自定义 TabBar
  const updateCustomTabBar = () => {
    // 触发自定义 TabBar 更新
    if (typeof getApp === 'function') {
      const app = getApp()
      if (app && app.globalData) {
        app.globalData.tabBarTheme = currentTabBarConfig.value
      }
    }
    
    // 发布主题更新事件
    uni.$emit('themeChanged', {
      theme: currentTheme.value,
      tabBarConfig: currentTabBarConfig.value
    })
  }
  
  // 保存到本地存储
  const saveThemeToStorage = (themeName) => {
    try {
      uni.setStorageSync('app-theme', themeName)
    } catch (error) {
      console.warn('主题存储失败:', error)
    }
  }
  
  // 初始化主题
  const initTheme = () => {
    try {
		// 读取本地存储的主题(优先使用用户之前选择的主题)
      const savedTheme = uni.getStorageSync('app-theme')
      if (savedTheme) {
        switchTheme(savedTheme)
        return
      }
      // 本地无存储时,适配系统主题(如手机开启深色模式则用 dark 主题)
      const systemInfo = uni.getSystemInfoSync()
      const systemTheme = systemInfo.theme === 'dark' ? 'dark' : 'light'
      switchTheme(systemTheme)
    } catch (error) {
		// 异常容错(读取失败/无系统主题时,默认用 light 主题)
      console.warn('主题初始化失败:', error)
      switchTheme('light')
    }
  }
  
  return {
    currentTheme,
    themeList,
    currentTabBarConfig,
    switchTheme,
    initTheme
  }
})

1.5 页面使用

javascript 复制代码
<template>
	<view class="page-container">
		<!-- 顶部栏 -->
		<view class="header theme-card">
			<text class="title theme-text">主题切换演示</text>
		</view>

		<view class="content">
			<!-- 主题切换 -->
			<view class="card theme-card">
				<text class="card-title theme-text">切换主题</text>
				<view class="theme-selector">
					<view v-for="theme in themeList" :key="theme.name" class="theme-option"
						:class="{ active: selectedThemeName === theme.name }" @click="switchThemeClick(theme)">
						<text class="theme-icon">{{ theme.icon }}</text>
						<text class="theme-label">{{ theme.label }}</text>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script setup>
	import {
		onMounted,
		ref
	} from 'vue'
	import {
		useThemeStore
	} from '@/store/theme.js'
	const {
		themeList,
		switchTheme,
		initTheme,
		currentTheme
	} = useThemeStore()
	const selectedThemeName = ref(currentTheme?.name || '')
	onMounted(() => {
		initTheme()
	})

	const switchThemeClick = (theme) => {
		switchTheme(theme.name)
		selectedThemeName.value = theme.name
	}
</script>

<style lang="scss" scoped>
	.page-container {
		min-height: 100vh;
		padding: 20rpx;

	}

	.header {
		padding: 40rpx 30rpx;
		border-radius: 20rpx;
		margin-bottom: 30rpx;
		text-align: center;

		.title {
			display: block;
			font-size: 36rpx;
			font-weight: bold;
			margin-bottom: 10rpx;
		}

		.subtitle {
			display: block;
			font-size: 28rpx;
		}
	}

	.content {
		display: flex;
		flex-direction: column;
		gap: 30rpx;
	}

	.card {
		padding: 30rpx;
		border-radius: 20rpx;
		border: 1rpx solid;

		.card-title {
			font-size: 32rpx;
			font-weight: bold;
			margin-bottom: 30rpx;
			display: block;
		}
	}

	.theme-selector {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 20rpx;
		margin-bottom: 30rpx;

		.theme-option {
			padding: 30rpx 20rpx;
			border-radius: 16rpx;
			border: 2rpx solid var(--border-color);
			display: flex;
			flex-direction: column;
			align-items: center;
			transition: all 0.3s;

			&.active {
				border-color: var(--color-primary);
				background-color: var(--bg-color);
				color: var(--text-color);
			}

			.theme-icon {
				font-size: 48rpx;
				margin-bottom: 10rpx;
			}

			.theme-label {
				font-size: 26rpx;
				transition: color 0.3s;
			}
		}
	}

	.cycle-btn {
		width: 100%;
	}
</style>

1.6 扩展

关于Sass:

  1. 变量复用 可通过$定义变量,存储颜色、尺寸、字体等常用值,便于全局统一管理和修改。比如定义主题色变量后,项目中所有用到该颜色的地方只需引用变量,修改时仅改一处即可。

    scss

    复制代码
    $primary-color: #2979ff;
    .button {
      background-color: $primary-color;
    }
  2. 选择器与属性嵌套 支持选择器嵌套,让样式层级和 HTML 结构保持一致,减少代码冗余;还支持属性嵌套,对于 font、margin 等同命名空间的属性,可简化书写。

    scss

    复制代码
    // 选择器嵌套
    .header {
      .nav {
        color: #333;
        &:hover { // & 指代父选择器.nav
          color: $primary-color;
        }
      }
    }
    // 属性嵌套
    .text {
      font: {
        size: 16px;
        weight: bold;
      }
    }
  3. 混合器(Mixin) 类似 JS 函数,可用@mixin定义可复用的样式块,还支持传参,能轻松处理兼容性样式、重复样式等场景,通过@include调用。

    scss

    复制代码
    @mixin border-radius($radius: 5px) {
      border-radius: $radius;
      -webkit-border-radius: $radius;
    }
    .card {
      @include border-radius(8px);
    }
  4. 样式继承@extend指令实现选择器间的样式继承,减少重复代码。若不想保留基础选择器,可改用占位符选择器%定义基础样式。

    scss

    复制代码
    %base-btn {
      padding: 10px 20px;
      border: none;
    }
    .btn-primary {
      @extend %base-btn;
      background-color: $primary-color;
    }
  5. 数学运算 支持对尺寸、百分比等数值进行加减乘除运算,方便动态计算样式值,适配不同布局需求。

    scss

    复制代码
    $base-width: 1000px;
    .box {
      width: $base-width / 2;
      margin: 10px + 5px;
    }
  6. 逻辑控制与循环 提供@if条件判断、@for循环、@each遍历等指令,可动态生成批量样式,比如栅格系统的列样式。

    scss

    复制代码
    // @for循环生成多列样式
    @for $i from 1 to 6 {
      .col-#{$i} {
        width: 100% / $i;
      }
    }
  7. 模块化导入@import导入其他 SCSS 文件,可将变量、混合器、组件样式等拆分到不同文件,实现样式的模块化管理,让项目结构更清晰。

    scss

    复制代码
    @import '@/styles/variables.scss';
    @import './mixins.scss';

官网:

Sass 官方网站https://sass-lang.com/

Sass 中文网https://www.sass.hk/

相关推荐
小胖学前端5 小时前
解决 uniapp H5 与原生应用通信的坑:一个经过实战验证的解决方案
前端·uni-app
2501_9160074711 小时前
iOS性能调试工具终极指南,从系统底层到多端协同的全方位优化实践(2025版)
android·ios·小程序·https·uni-app·iphone·webview
2501_9159214311 小时前
iOS崩溃日志深度分析与工具组合实战,从符号化到自动化诊断的完整体系
android·ios·小程序·uni-app·自动化·cocoa·iphone
2501_9160088918 小时前
没有源码如何加密 IPA 实战流程与多工具组合落地指南
android·ios·小程序·https·uni-app·iphone·webview
LXA080920 小时前
UniApp 小程序中使用地图组件
小程序·uni-app·notepad++
QuantumLeap丶1 天前
《uni-app跨平台开发完全指南》- 07 - 数据绑定与事件处理
vue.js·ios·uni-app
2501_915909061 天前
Flutter 应用怎么加固,多工具组合的工程化实战(Flutter 加固/Dart 混淆/IPA 成品加固/Ipa Guard + CI)
android·flutter·ios·ci/cd·小程序·uni-app·iphone
xiaaaa.z1 天前
macos HbuildX 使用cli脚本创建uniapp 运行时报错“cli项目运行依赖本地的Nodejs环境,请先安装并配置到系统环境变量后重试。”
macos·uni-app
2501_915909061 天前
深入理解HTTPS和HTTP的区别、工作原理及安全重要性
安全·http·ios·小程序·https·uni-app·iphone