1. CSS样式基础

1.1 样式语法

<template>
  <view class="container">
    <view class="header" :class="{ active: isActive }">
      <text class="title">标题</text>
    </view>
    <view class="content" :style="dynamicStyle">
      <text>内容区域</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isActive: true,
      dynamicStyle: {
        color: '#333',
        fontSize: '16px'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.container {
  padding: 20rpx;
  background-color: #f5f5f5;
  
  .header {
    height: 100rpx;
    background-color: #fff;
    border-radius: 10rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 20rpx;
    
    &.active {
      background-color: #007aff;
      
      .title {
        color: #fff;
      }
    }
    
    .title {
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
    }
  }
  
  .content {
    background-color: #fff;
    padding: 30rpx;
    border-radius: 10rpx;
    min-height: 200rpx;
  }
}
</style>

1.2 尺寸单位

/* UniApp支持的尺寸单位 */
.size-demo {
  /* rpx: 响应式像素,推荐使用 */
  width: 750rpx; /* 等于屏幕宽度 */
  height: 100rpx;
  
  /* px: 物理像素 */
  border: 1px solid #ccc;
  
  /* %: 百分比 */
  margin: 5%;
  
  /* vh/vw: 视口单位 */
  min-height: 50vh;
  
  /* rem/em: 相对单位 */
  font-size: 1.2rem;
  
  /* upx: 微信小程序兼容单位 */
  padding: 20upx;
}

/* 响应式设计 */
@media screen and (max-width: 768rpx) {
  .responsive {
    font-size: 28rpx;
  }
}

@media screen and (min-width: 769rpx) {
  .responsive {
    font-size: 32rpx;
  }
}

1.3 Flex布局

/* Flex容器 */
.flex-container {
  display: flex;
  flex-direction: row; /* row | column | row-reverse | column-reverse */
  justify-content: center; /* flex-start | flex-end | center | space-between | space-around | space-evenly */
  align-items: center; /* flex-start | flex-end | center | baseline | stretch */
  flex-wrap: wrap; /* nowrap | wrap | wrap-reverse */
  gap: 20rpx; /* 项目间距 */
}

/* Flex项目 */
.flex-item {
  flex: 1; /* flex-grow flex-shrink flex-basis */
  flex-grow: 1; /* 放大比例 */
  flex-shrink: 1; /* 缩小比例 */
  flex-basis: auto; /* 基础大小 */
  align-self: auto; /* auto | flex-start | flex-end | center | baseline | stretch */
}

/* 常用布局示例 */
.layout-examples {
  /* 水平居中 */
  .horizontal-center {
    display: flex;
    justify-content: center;
  }
  
  /* 垂直居中 */
  .vertical-center {
    display: flex;
    align-items: center;
  }
  
  /* 完全居中 */
  .full-center {
    display: flex;
    justify-content: center;
    align-items: center;
  }
  
  /* 两端对齐 */
  .space-between {
    display: flex;
    justify-content: space-between;
  }
  
  /* 等分布局 */
  .equal-width {
    display: flex;
    
    .item {
      flex: 1;
    }
  }
  
  /* 固定宽度+自适应 */
  .fixed-adaptive {
    display: flex;
    
    .fixed {
      width: 200rpx;
      flex-shrink: 0;
    }
    
    .adaptive {
      flex: 1;
    }
  }
}

2. SCSS预处理器

2.1 变量和混入

// styles/variables.scss
// 颜色变量
$primary-color: #007aff;
$secondary-color: #5ac8fa;
$success-color: #4cd964;
$warning-color: #ff9500;
$error-color: #ff3b30;
$text-color: #333;
$text-light: #666;
$text-lighter: #999;
$border-color: #e5e5e5;
$background-color: #f5f5f5;

// 尺寸变量
$border-radius: 10rpx;
$border-radius-large: 20rpx;
$spacing-small: 10rpx;
$spacing-medium: 20rpx;
$spacing-large: 30rpx;
$font-size-small: 24rpx;
$font-size-medium: 28rpx;
$font-size-large: 32rpx;
$font-size-xlarge: 36rpx;

// 阴影变量
$shadow-light: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
$shadow-medium: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
$shadow-heavy: 0 8rpx 30rpx rgba(0, 0, 0, 0.2);

// 动画变量
$transition-fast: 0.2s;
$transition-medium: 0.3s;
$transition-slow: 0.5s;

// 混入(Mixins)
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

@mixin flex-between {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

@mixin ellipsis($lines: 1) {
  @if $lines == 1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  } @else {
    display: -webkit-box;
    -webkit-line-clamp: $lines;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

@mixin button-style($bg-color, $text-color: #fff, $border-color: transparent) {
  background-color: $bg-color;
  color: $text-color;
  border: 2rpx solid $border-color;
  border-radius: $border-radius;
  padding: 20rpx 40rpx;
  font-size: $font-size-medium;
  transition: all $transition-fast;
  
  &:active {
    opacity: 0.8;
    transform: scale(0.98);
  }
}

@mixin card-style {
  background-color: #fff;
  border-radius: $border-radius;
  box-shadow: $shadow-light;
  padding: $spacing-large;
  margin-bottom: $spacing-medium;
}

@mixin responsive-font($mobile, $tablet: null, $desktop: null) {
  font-size: $mobile;
  
  @media screen and (min-width: 768rpx) {
    @if $tablet {
      font-size: $tablet;
    }
  }
  
  @media screen and (min-width: 1024rpx) {
    @if $desktop {
      font-size: $desktop;
    }
  }
}

2.2 函数和工具类

// styles/functions.scss
// 颜色函数
@function lighten-color($color, $amount: 10%) {
  @return lighten($color, $amount);
}

@function darken-color($color, $amount: 10%) {
  @return darken($color, $amount);
}

@function alpha-color($color, $alpha: 0.5) {
  @return rgba($color, $alpha);
}

// 尺寸转换函数
@function rpx-to-px($rpx) {
  @return $rpx / 2 * 1px;
}

@function px-to-rpx($px) {
  @return $px * 2 * 1rpx;
}

// styles/utilities.scss
// 工具类
.u-flex {
  display: flex;
  
  &-center {
    @include flex-center;
  }
  
  &-between {
    @include flex-between;
  }
  
  &-column {
    flex-direction: column;
  }
  
  &-wrap {
    flex-wrap: wrap;
  }
  
  &-1 {
    flex: 1;
  }
}

.u-text {
  &-center {
    text-align: center;
  }
  
  &-left {
    text-align: left;
  }
  
  &-right {
    text-align: right;
  }
  
  &-ellipsis {
    @include ellipsis(1);
  }
  
  &-ellipsis-2 {
    @include ellipsis(2);
  }
  
  &-ellipsis-3 {
    @include ellipsis(3);
  }
}

.u-color {
  &-primary {
    color: $primary-color;
  }
  
  &-success {
    color: $success-color;
  }
  
  &-warning {
    color: $warning-color;
  }
  
  &-error {
    color: $error-color;
  }
  
  &-text {
    color: $text-color;
  }
  
  &-light {
    color: $text-light;
  }
  
  &-lighter {
    color: $text-lighter;
  }
}

.u-bg {
  &-primary {
    background-color: $primary-color;
  }
  
  &-white {
    background-color: #fff;
  }
  
  &-gray {
    background-color: $background-color;
  }
}

.u-margin {
  &-small {
    margin: $spacing-small;
  }
  
  &-medium {
    margin: $spacing-medium;
  }
  
  &-large {
    margin: $spacing-large;
  }
  
  &-top {
    &-small {
      margin-top: $spacing-small;
    }
    
    &-medium {
      margin-top: $spacing-medium;
    }
    
    &-large {
      margin-top: $spacing-large;
    }
  }
  
  &-bottom {
    &-small {
      margin-bottom: $spacing-small;
    }
    
    &-medium {
      margin-bottom: $spacing-medium;
    }
    
    &-large {
      margin-bottom: $spacing-large;
    }
  }
}

.u-padding {
  &-small {
    padding: $spacing-small;
  }
  
  &-medium {
    padding: $spacing-medium;
  }
  
  &-large {
    padding: $spacing-large;
  }
}

.u-border {
  &-radius {
    border-radius: $border-radius;
  }
  
  &-radius-large {
    border-radius: $border-radius-large;
  }
  
  &-bottom {
    border-bottom: 1rpx solid $border-color;
  }
}

.u-shadow {
  &-light {
    box-shadow: $shadow-light;
  }
  
  &-medium {
    box-shadow: $shadow-medium;
  }
  
  &-heavy {
    box-shadow: $shadow-heavy;
  }
}

3. 主题系统

3.1 主题配置

// utils/theme.js
class ThemeManager {
  constructor() {
    this.currentTheme = 'light'
    this.themes = {
      light: {
        name: 'light',
        colors: {
          primary: '#007aff',
          secondary: '#5ac8fa',
          success: '#4cd964',
          warning: '#ff9500',
          error: '#ff3b30',
          background: '#ffffff',
          surface: '#f5f5f5',
          text: '#333333',
          textSecondary: '#666666',
          textLight: '#999999',
          border: '#e5e5e5',
          shadow: 'rgba(0, 0, 0, 0.1)'
        },
        statusBar: 'dark-content'
      },
      dark: {
        name: 'dark',
        colors: {
          primary: '#0a84ff',
          secondary: '#64d2ff',
          success: '#30d158',
          warning: '#ff9f0a',
          error: '#ff453a',
          background: '#000000',
          surface: '#1c1c1e',
          text: '#ffffff',
          textSecondary: '#ebebf5',
          textLight: '#8e8e93',
          border: '#38383a',
          shadow: 'rgba(255, 255, 255, 0.1)'
        },
        statusBar: 'light-content'
      },
      blue: {
        name: 'blue',
        colors: {
          primary: '#1890ff',
          secondary: '#40a9ff',
          success: '#52c41a',
          warning: '#faad14',
          error: '#f5222d',
          background: '#f0f8ff',
          surface: '#e6f7ff',
          text: '#001529',
          textSecondary: '#314659',
          textLight: '#8c8c8c',
          border: '#d9d9d9',
          shadow: 'rgba(24, 144, 255, 0.1)'
        },
        statusBar: 'dark-content'
      }
    }
    
    this.init()
  }
  
  // 初始化主题
  init() {
    // 从本地存储获取主题设置
    const savedTheme = uni.getStorageSync('theme')
    if (savedTheme && this.themes[savedTheme]) {
      this.currentTheme = savedTheme
    } else {
      // 根据系统主题自动设置
      this.detectSystemTheme()
    }
    
    this.applyTheme(this.currentTheme)
  }
  
  // 检测系统主题
  detectSystemTheme() {
    try {
      const systemInfo = uni.getSystemInfoSync()
      if (systemInfo.theme === 'dark') {
        this.currentTheme = 'dark'
      }
    } catch (error) {
      console.log('无法检测系统主题:', error)
    }
  }
  
  // 应用主题
  applyTheme(themeName) {
    if (!this.themes[themeName]) {
      console.error('主题不存在:', themeName)
      return
    }
    
    this.currentTheme = themeName
    const theme = this.themes[themeName]
    
    // 设置CSS变量
    this.setCSSVariables(theme.colors)
    
    // 设置状态栏样式
    this.setStatusBarStyle(theme.statusBar)
    
    // 保存到本地存储
    uni.setStorageSync('theme', themeName)
    
    // 触发主题变更事件
    uni.$emit('themeChanged', {
      theme: themeName,
      colors: theme.colors
    })
    
    console.log('主题已切换到:', themeName)
  }
  
  // 设置CSS变量
  setCSSVariables(colors) {
    const root = document.documentElement || document.body
    
    Object.keys(colors).forEach(key => {
      const cssVar = `--theme-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`
      root.style.setProperty(cssVar, colors[key])
    })
  }
  
  // 设置状态栏样式
  setStatusBarStyle(style) {
    // #ifdef APP-PLUS
    plus.navigator.setStatusBarStyle(style)
    // #endif
    
    // #ifdef MP-WEIXIN
    wx.setNavigationBarColor({
      frontColor: style === 'dark-content' ? '#000000' : '#ffffff',
      backgroundColor: this.getThemeColor('background')
    })
    // #endif
  }
  
  // 切换主题
  switchTheme(themeName) {
    if (themeName === this.currentTheme) {
      return
    }
    
    this.applyTheme(themeName)
  }
  
  // 切换到下一个主题
  toggleTheme() {
    const themeNames = Object.keys(this.themes)
    const currentIndex = themeNames.indexOf(this.currentTheme)
    const nextIndex = (currentIndex + 1) % themeNames.length
    const nextTheme = themeNames[nextIndex]
    
    this.switchTheme(nextTheme)
  }
  
  // 获取当前主题
  getCurrentTheme() {
    return this.currentTheme
  }
  
  // 获取主题颜色
  getThemeColor(colorName) {
    const theme = this.themes[this.currentTheme]
    return theme ? theme.colors[colorName] : null
  }
  
  // 获取所有主题
  getAllThemes() {
    return Object.keys(this.themes).map(key => ({
      name: key,
      displayName: this.themes[key].name,
      colors: this.themes[key].colors
    }))
  }
  
  // 注册自定义主题
  registerTheme(name, themeConfig) {
    this.themes[name] = {
      name,
      ...themeConfig
    }
  }
  
  // 监听系统主题变化
  watchSystemTheme() {
    // #ifdef APP-PLUS
    plus.globalEvent.addEventListener('newintent', () => {
      this.detectSystemTheme()
      this.applyTheme(this.currentTheme)
    })
    // #endif
  }
}

// 创建主题管理实例
const themeManager = new ThemeManager()

export default themeManager

3.2 主题样式

// styles/theme.scss
// CSS变量定义
:root {
  // 颜色变量
  --theme-primary: #007aff;
  --theme-secondary: #5ac8fa;
  --theme-success: #4cd964;
  --theme-warning: #ff9500;
  --theme-error: #ff3b30;
  --theme-background: #ffffff;
  --theme-surface: #f5f5f5;
  --theme-text: #333333;
  --theme-text-secondary: #666666;
  --theme-text-light: #999999;
  --theme-border: #e5e5e5;
  --theme-shadow: rgba(0, 0, 0, 0.1);
}

// 主题样式类
.theme-container {
  background-color: var(--theme-background);
  color: var(--theme-text);
  transition: background-color 0.3s, color 0.3s;
}

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

.theme-card {
  background-color: var(--theme-background);
  border: 1rpx solid var(--theme-border);
  box-shadow: 0 4rpx 12rpx var(--theme-shadow);
  border-radius: 12rpx;
  padding: 24rpx;
}

.theme-button {
  &-primary {
    background-color: var(--theme-primary);
    color: #ffffff;
    border: none;
    
    &:active {
      background-color: var(--theme-primary);
      opacity: 0.8;
    }
  }
  
  &-secondary {
    background-color: transparent;
    color: var(--theme-primary);
    border: 2rpx solid var(--theme-primary);
    
    &:active {
      background-color: var(--theme-primary);
      color: #ffffff;
    }
  }
  
  &-ghost {
    background-color: transparent;
    color: var(--theme-text);
    border: 2rpx solid var(--theme-border);
    
    &:active {
      background-color: var(--theme-surface);
    }
  }
}

.theme-text {
  &-primary {
    color: var(--theme-text);
  }
  
  &-secondary {
    color: var(--theme-text-secondary);
  }
  
  &-light {
    color: var(--theme-text-light);
  }
  
  &-success {
    color: var(--theme-success);
  }
  
  &-warning {
    color: var(--theme-warning);
  }
  
  &-error {
    color: var(--theme-error);
  }
}

.theme-border {
  border-color: var(--theme-border);
  
  &-bottom {
    border-bottom: 1rpx solid var(--theme-border);
  }
  
  &-top {
    border-top: 1rpx solid var(--theme-border);
  }
}

.theme-input {
  background-color: var(--theme-background);
  color: var(--theme-text);
  border: 2rpx solid var(--theme-border);
  border-radius: 8rpx;
  padding: 16rpx 20rpx;
  
  &:focus {
    border-color: var(--theme-primary);
  }
  
  &::placeholder {
    color: var(--theme-text-light);
  }
}

// 主题切换动画
.theme-transition {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

// 深色主题特殊处理
@media (prefers-color-scheme: dark) {
  .auto-theme {
    --theme-primary: #0a84ff;
    --theme-background: #000000;
    --theme-surface: #1c1c1e;
    --theme-text: #ffffff;
    --theme-text-secondary: #ebebf5;
    --theme-text-light: #8e8e93;
    --theme-border: #38383a;
    --theme-shadow: rgba(255, 255, 255, 0.1);
  }
}

3.3 主题切换组件

<!-- components/ThemeSwitch.vue -->
<template>
  <view class="theme-switch">
    <view class="theme-switch-header">
      <text class="theme-switch-title">主题设置</text>
    </view>
    
    <view class="theme-list">
      <view 
        v-for="theme in themes" 
        :key="theme.name"
        class="theme-item"
        :class="{ active: currentTheme === theme.name }"
        @click="switchTheme(theme.name)"
      >
        <view class="theme-preview">
          <view 
            class="theme-color"
            v-for="(color, index) in getPreviewColors(theme.colors)"
            :key="index"
            :style="{ backgroundColor: color }"
          ></view>
        </view>
        <text class="theme-name">{{ getThemeDisplayName(theme.name) }}</text>
        <view v-if="currentTheme === theme.name" class="theme-check">
          <text class="iconfont icon-check"></text>
        </view>
      </view>
    </view>
    
    <view class="theme-options">
      <view class="option-item" @click="toggleAutoTheme">
        <text class="option-label">跟随系统</text>
        <switch 
          :checked="autoTheme" 
          @change="onAutoThemeChange"
          color="var(--theme-primary)"
        />
      </view>
    </view>
  </view>
</template>

<script>
import themeManager from '@/utils/theme.js'

export default {
  name: 'ThemeSwitch',
  data() {
    return {
      currentTheme: 'light',
      autoTheme: false,
      themes: []
    }
  },
  
  mounted() {
    this.init()
  },
  
  methods: {
    init() {
      this.currentTheme = themeManager.getCurrentTheme()
      this.themes = themeManager.getAllThemes()
      this.autoTheme = uni.getStorageSync('autoTheme') || false
      
      // 监听主题变化
      uni.$on('themeChanged', this.onThemeChanged)
    },
    
    switchTheme(themeName) {
      if (this.autoTheme) {
        this.autoTheme = false
        uni.setStorageSync('autoTheme', false)
      }
      
      themeManager.switchTheme(themeName)
    },
    
    toggleAutoTheme() {
      this.autoTheme = !this.autoTheme
      this.onAutoThemeChange({ detail: { value: this.autoTheme } })
    },
    
    onAutoThemeChange(e) {
      this.autoTheme = e.detail.value
      uni.setStorageSync('autoTheme', this.autoTheme)
      
      if (this.autoTheme) {
        themeManager.detectSystemTheme()
      }
    },
    
    onThemeChanged(data) {
      this.currentTheme = data.theme
    },
    
    getPreviewColors(colors) {
      return [colors.primary, colors.background, colors.surface, colors.text]
    },
    
    getThemeDisplayName(themeName) {
      const names = {
        light: '浅色',
        dark: '深色',
        blue: '蓝色'
      }
      return names[themeName] || themeName
    }
  },
  
  beforeDestroy() {
    uni.$off('themeChanged', this.onThemeChanged)
  }
}
</script>

<style lang="scss" scoped>
.theme-switch {
  padding: 40rpx;
  
  &-header {
    margin-bottom: 40rpx;
  }
  
  &-title {
    font-size: 36rpx;
    font-weight: bold;
    color: var(--theme-text);
  }
}

.theme-list {
  margin-bottom: 60rpx;
}

.theme-item {
  display: flex;
  align-items: center;
  padding: 30rpx 0;
  border-bottom: 1rpx solid var(--theme-border);
  transition: all 0.3s;
  
  &:last-child {
    border-bottom: none;
  }
  
  &.active {
    .theme-name {
      color: var(--theme-primary);
      font-weight: bold;
    }
  }
  
  &:active {
    background-color: var(--theme-surface);
  }
}

.theme-preview {
  display: flex;
  margin-right: 30rpx;
  border-radius: 12rpx;
  overflow: hidden;
  box-shadow: 0 2rpx 8rpx var(--theme-shadow);
}

.theme-color {
  width: 20rpx;
  height: 40rpx;
}

.theme-name {
  flex: 1;
  font-size: 32rpx;
  color: var(--theme-text);
  transition: all 0.3s;
}

.theme-check {
  color: var(--theme-primary);
  font-size: 32rpx;
}

.theme-options {
  border-top: 1rpx solid var(--theme-border);
  padding-top: 40rpx;
}

.option-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20rpx 0;
}

.option-label {
  font-size: 32rpx;
  color: var(--theme-text);
}
</style>

4. 响应式设计

4.1 屏幕适配

// styles/responsive.scss
// 断点定义
$breakpoints: (
  xs: 0,
  sm: 576rpx,
  md: 768rpx,
  lg: 992rpx,
  xl: 1200rpx,
  xxl: 1600rpx
);

// 媒体查询混入
@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media screen and (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @warn "Unknown breakpoint: #{$breakpoint}";
  }
}

@mixin respond-between($min, $max) {
  @if map-has-key($breakpoints, $min) and map-has-key($breakpoints, $max) {
    @media screen and (min-width: map-get($breakpoints, $min)) and (max-width: map-get($breakpoints, $max) - 1rpx) {
      @content;
    }
  }
}

@mixin respond-below($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media screen and (max-width: map-get($breakpoints, $breakpoint) - 1rpx) {
      @content;
    }
  }
}

// 响应式容器
.container {
  width: 100%;
  padding: 0 30rpx;
  margin: 0 auto;
  
  @include respond-to(sm) {
    max-width: 540rpx;
  }
  
  @include respond-to(md) {
    max-width: 720rpx;
  }
  
  @include respond-to(lg) {
    max-width: 960rpx;
  }
  
  @include respond-to(xl) {
    max-width: 1140rpx;
  }
  
  @include respond-to(xxl) {
    max-width: 1320rpx;
  }
}

// 响应式网格
.row {
  display: flex;
  flex-wrap: wrap;
  margin: 0 -15rpx;
}

.col {
  padding: 0 15rpx;
  
  // 基础列
  &-1 { flex: 0 0 8.333333%; max-width: 8.333333%; }
  &-2 { flex: 0 0 16.666667%; max-width: 16.666667%; }
  &-3 { flex: 0 0 25%; max-width: 25%; }
  &-4 { flex: 0 0 33.333333%; max-width: 33.333333%; }
  &-5 { flex: 0 0 41.666667%; max-width: 41.666667%; }
  &-6 { flex: 0 0 50%; max-width: 50%; }
  &-7 { flex: 0 0 58.333333%; max-width: 58.333333%; }
  &-8 { flex: 0 0 66.666667%; max-width: 66.666667%; }
  &-9 { flex: 0 0 75%; max-width: 75%; }
  &-10 { flex: 0 0 83.333333%; max-width: 83.333333%; }
  &-11 { flex: 0 0 91.666667%; max-width: 91.666667%; }
  &-12 { flex: 0 0 100%; max-width: 100%; }
  
  // 响应式列
  @include respond-to(sm) {
    &-sm-1 { flex: 0 0 8.333333%; max-width: 8.333333%; }
    &-sm-2 { flex: 0 0 16.666667%; max-width: 16.666667%; }
    &-sm-3 { flex: 0 0 25%; max-width: 25%; }
    &-sm-4 { flex: 0 0 33.333333%; max-width: 33.333333%; }
    &-sm-5 { flex: 0 0 41.666667%; max-width: 41.666667%; }
    &-sm-6 { flex: 0 0 50%; max-width: 50%; }
    &-sm-7 { flex: 0 0 58.333333%; max-width: 58.333333%; }
    &-sm-8 { flex: 0 0 66.666667%; max-width: 66.666667%; }
    &-sm-9 { flex: 0 0 75%; max-width: 75%; }
    &-sm-10 { flex: 0 0 83.333333%; max-width: 83.333333%; }
    &-sm-11 { flex: 0 0 91.666667%; max-width: 91.666667%; }
    &-sm-12 { flex: 0 0 100%; max-width: 100%; }
  }
  
  @include respond-to(md) {
    &-md-1 { flex: 0 0 8.333333%; max-width: 8.333333%; }
    &-md-2 { flex: 0 0 16.666667%; max-width: 16.666667%; }
    &-md-3 { flex: 0 0 25%; max-width: 25%; }
    &-md-4 { flex: 0 0 33.333333%; max-width: 33.333333%; }
    &-md-5 { flex: 0 0 41.666667%; max-width: 41.666667%; }
    &-md-6 { flex: 0 0 50%; max-width: 50%; }
    &-md-7 { flex: 0 0 58.333333%; max-width: 58.333333%; }
    &-md-8 { flex: 0 0 66.666667%; max-width: 66.666667%; }
    &-md-9 { flex: 0 0 75%; max-width: 75%; }
    &-md-10 { flex: 0 0 83.333333%; max-width: 83.333333%; }
    &-md-11 { flex: 0 0 91.666667%; max-width: 91.666667%; }
    &-md-12 { flex: 0 0 100%; max-width: 100%; }
  }
  
  @include respond-to(lg) {
    &-lg-1 { flex: 0 0 8.333333%; max-width: 8.333333%; }
    &-lg-2 { flex: 0 0 16.666667%; max-width: 16.666667%; }
    &-lg-3 { flex: 0 0 25%; max-width: 25%; }
    &-lg-4 { flex: 0 0 33.333333%; max-width: 33.333333%; }
    &-lg-5 { flex: 0 0 41.666667%; max-width: 41.666667%; }
    &-lg-6 { flex: 0 0 50%; max-width: 50%; }
    &-lg-7 { flex: 0 0 58.333333%; max-width: 58.333333%; }
    &-lg-8 { flex: 0 0 66.666667%; max-width: 66.666667%; }
    &-lg-9 { flex: 0 0 75%; max-width: 75%; }
    &-lg-10 { flex: 0 0 83.333333%; max-width: 83.333333%; }
    &-lg-11 { flex: 0 0 91.666667%; max-width: 91.666667%; }
    &-lg-12 { flex: 0 0 100%; max-width: 100%; }
  }
}

// 响应式工具类
.d-none {
  display: none;
}

.d-block {
  display: block;
}

.d-flex {
  display: flex;
}

@include respond-to(sm) {
  .d-sm-none { display: none; }
  .d-sm-block { display: block; }
  .d-sm-flex { display: flex; }
}

@include respond-to(md) {
  .d-md-none { display: none; }
  .d-md-block { display: block; }
  .d-md-flex { display: flex; }
}

@include respond-to(lg) {
  .d-lg-none { display: none; }
  .d-lg-block { display: block; }
  .d-lg-flex { display: flex; }
}

// 响应式字体
.responsive-text {
  font-size: 28rpx;
  
  @include respond-to(md) {
    font-size: 32rpx;
  }
  
  @include respond-to(lg) {
    font-size: 36rpx;
  }
}

// 响应式间距
.responsive-padding {
  padding: 20rpx;
  
  @include respond-to(md) {
    padding: 30rpx;
  }
  
  @include respond-to(lg) {
    padding: 40rpx;
  }
}

4.2 设备适配工具

// utils/device.js
class DeviceAdapter {
  constructor() {
    this.systemInfo = null
    this.screenInfo = null
    this.init()
  }
  
  // 初始化设备信息
  init() {
    try {
      this.systemInfo = uni.getSystemInfoSync()
      this.screenInfo = {
        screenWidth: this.systemInfo.screenWidth,
        screenHeight: this.systemInfo.screenHeight,
        windowWidth: this.systemInfo.windowWidth,
        windowHeight: this.systemInfo.windowHeight,
        pixelRatio: this.systemInfo.pixelRatio,
        statusBarHeight: this.systemInfo.statusBarHeight,
        safeArea: this.systemInfo.safeArea,
        safeAreaInsets: this.systemInfo.safeAreaInsets
      }
    } catch (error) {
      console.error('获取设备信息失败:', error)
    }
  }
  
  // 获取设备类型
  getDeviceType() {
    if (!this.systemInfo) return 'unknown'
    
    const { platform, screenWidth } = this.systemInfo
    
    if (platform === 'ios' || platform === 'android') {
      if (screenWidth < 768) {
        return 'mobile'
      } else {
        return 'tablet'
      }
    }
    
    return 'desktop'
  }
  
  // 判断是否为移动设备
  isMobile() {
    return this.getDeviceType() === 'mobile'
  }
  
  // 判断是否为平板
  isTablet() {
    return this.getDeviceType() === 'tablet'
  }
  
  // 判断是否为桌面设备
  isDesktop() {
    return this.getDeviceType() === 'desktop'
  }
  
  // 判断是否为iOS
  isIOS() {
    return this.systemInfo?.platform === 'ios'
  }
  
  // 判断是否为Android
  isAndroid() {
    return this.systemInfo?.platform === 'android'
  }
  
  // 判断是否为微信小程序
  isWeChat() {
    // #ifdef MP-WEIXIN
    return true
    // #endif
    // #ifndef MP-WEIXIN
    return false
    // #endif
  }
  
  // 判断是否为支付宝小程序
  isAlipay() {
    // #ifdef MP-ALIPAY
    return true
    // #endif
    // #ifndef MP-ALIPAY
    return false
    // #endif
  }
  
  // 判断是否为H5
  isH5() {
    // #ifdef H5
    return true
    // #endif
    // #ifndef H5
    return false
    // #endif
  }
  
  // 判断是否为App
  isApp() {
    // #ifdef APP-PLUS
    return true
    // #endif
    // #ifndef APP-PLUS
    return false
    // #endif
  }
  
  // 获取状态栏高度
  getStatusBarHeight() {
    return this.systemInfo?.statusBarHeight || 0
  }
  
  // 获取安全区域
  getSafeArea() {
    return this.screenInfo?.safeArea || {
      left: 0,
      right: this.screenInfo?.screenWidth || 0,
      top: this.getStatusBarHeight(),
      bottom: this.screenInfo?.screenHeight || 0,
      width: this.screenInfo?.screenWidth || 0,
      height: (this.screenInfo?.screenHeight || 0) - this.getStatusBarHeight()
    }
  }
  
  // 获取安全区域内边距
  getSafeAreaInsets() {
    return this.screenInfo?.safeAreaInsets || {
      left: 0,
      right: 0,
      top: this.getStatusBarHeight(),
      bottom: 0
    }
  }
  
  // rpx转px
  rpxToPx(rpx) {
    if (!this.systemInfo) return rpx
    return (rpx * this.systemInfo.screenWidth) / 750
  }
  
  // px转rpx
  pxToRpx(px) {
    if (!this.systemInfo) return px
    return (px * 750) / this.systemInfo.screenWidth
  }
  
  // 获取屏幕尺寸分类
  getScreenSize() {
    if (!this.systemInfo) return 'medium'
    
    const { screenWidth } = this.systemInfo
    
    if (screenWidth < 576) {
      return 'small'
    } else if (screenWidth < 768) {
      return 'medium'
    } else if (screenWidth < 992) {
      return 'large'
    } else {
      return 'xlarge'
    }
  }
  
  // 获取设备信息
  getDeviceInfo() {
    return {
      ...this.systemInfo,
      deviceType: this.getDeviceType(),
      screenSize: this.getScreenSize(),
      safeArea: this.getSafeArea(),
      safeAreaInsets: this.getSafeAreaInsets()
    }
  }
  
  // 设置页面样式
  setPageStyle() {
    const safeAreaInsets = this.getSafeAreaInsets()
    
    // 设置CSS变量
    const root = document.documentElement || document.body
    if (root) {
      root.style.setProperty('--status-bar-height', `${safeAreaInsets.top}px`)
      root.style.setProperty('--safe-area-inset-top', `${safeAreaInsets.top}px`)
      root.style.setProperty('--safe-area-inset-bottom', `${safeAreaInsets.bottom}px`)
      root.style.setProperty('--safe-area-inset-left', `${safeAreaInsets.left}px`)
      root.style.setProperty('--safe-area-inset-right', `${safeAreaInsets.right}px`)
    }
  }
}

// 创建设备适配实例
const deviceAdapter = new DeviceAdapter()

export default deviceAdapter

5. 动画效果

5.1 CSS动画

// styles/animations.scss
// 基础动画
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes slideInUp {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes slideInDown {
  from {
    transform: translateY(-100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes slideInRight {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes scaleIn {
  from {
    transform: scale(0);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes scaleOut {
  from {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(0);
    opacity: 0;
  }
}

@keyframes bounce {
  0%, 20%, 53%, 80%, 100% {
    transform: translate3d(0, 0, 0);
  }
  40%, 43% {
    transform: translate3d(0, -30rpx, 0);
  }
  70% {
    transform: translate3d(0, -15rpx, 0);
  }
  90% {
    transform: translate3d(0, -4rpx, 0);
  }
}

@keyframes shake {
  0%, 100% {
    transform: translateX(0);
  }
  10%, 30%, 50%, 70%, 90% {
    transform: translateX(-10rpx);
  }
  20%, 40%, 60%, 80% {
    transform: translateX(10rpx);
  }
}

@keyframes pulse {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

// 动画类
.animate {
  animation-duration: 0.3s;
  animation-fill-mode: both;
  
  &-fast {
    animation-duration: 0.15s;
  }
  
  &-slow {
    animation-duration: 0.6s;
  }
  
  &-infinite {
    animation-iteration-count: infinite;
  }
  
  &-delay-1 {
    animation-delay: 0.1s;
  }
  
  &-delay-2 {
    animation-delay: 0.2s;
  }
  
  &-delay-3 {
    animation-delay: 0.3s;
  }
}

.fade-in {
  animation-name: fadeIn;
}

.fade-out {
  animation-name: fadeOut;
}

.slide-in-up {
  animation-name: slideInUp;
}

.slide-in-down {
  animation-name: slideInDown;
}

.slide-in-left {
  animation-name: slideInLeft;
}

.slide-in-right {
  animation-name: slideInRight;
}

.scale-in {
  animation-name: scaleIn;
}

.scale-out {
  animation-name: scaleOut;
}

.bounce {
  animation-name: bounce;
}

.shake {
  animation-name: shake;
}

.pulse {
  animation-name: pulse;
}

.rotate {
  animation-name: rotate;
}

// 过渡效果
.transition {
  transition: all 0.3s ease;
  
  &-fast {
    transition-duration: 0.15s;
  }
  
  &-slow {
    transition-duration: 0.6s;
  }
  
  &-ease-in {
    transition-timing-function: ease-in;
  }
  
  &-ease-out {
    transition-timing-function: ease-out;
  }
  
  &-ease-in-out {
    transition-timing-function: ease-in-out;
  }
  
  &-linear {
    transition-timing-function: linear;
  }
}

// 悬停效果
.hover-scale {
  transition: transform 0.3s ease;
  
  &:active {
    transform: scale(0.95);
  }
}

.hover-lift {
  transition: all 0.3s ease;
  
  &:active {
    transform: translateY(-4rpx);
    box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15);
  }
}

.hover-glow {
  transition: box-shadow 0.3s ease;
  
  &:active {
    box-shadow: 0 0 20rpx rgba(0, 122, 255, 0.5);
  }
}

// 加载动画
.loading-spinner {
  width: 40rpx;
  height: 40rpx;
  border: 4rpx solid #f3f3f3;
  border-top: 4rpx solid var(--theme-primary);
  border-radius: 50%;
  animation: rotate 1s linear infinite;
}

.loading-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  
  .dot {
    width: 8rpx;
    height: 8rpx;
    border-radius: 50%;
    background-color: var(--theme-primary);
    margin: 0 4rpx;
    animation: pulse 1.4s ease-in-out infinite both;
    
    &:nth-child(1) {
      animation-delay: -0.32s;
    }
    
    &:nth-child(2) {
      animation-delay: -0.16s;
    }
  }
}

// 页面切换动画
.page-enter-active,
.page-leave-active {
  transition: all 0.3s ease;
}

.page-enter {
  transform: translateX(100%);
  opacity: 0;
}

.page-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}

// 列表项动画
.list-enter-active {
  transition: all 0.3s ease;
}

.list-leave-active {
  transition: all 0.3s ease;
  position: absolute;
  width: 100%;
}

.list-enter {
  transform: translateY(30rpx);
  opacity: 0;
}

.list-leave-to {
  transform: translateY(-30rpx);
  opacity: 0;
}

.list-move {
  transition: transform 0.3s ease;
}

6. 总结

样式与主题是UniApp应用用户体验的重要组成部分:

  1. CSS基础:掌握UniApp支持的CSS特性和尺寸单位
  2. SCSS预处理器:使用变量、混入、函数提高样式开发效率
  3. 主题系统:实现多主题切换和深色模式支持
  4. 响应式设计:适配不同屏幕尺寸和设备类型
  5. 动画效果:丰富的CSS动画和过渡效果
  6. 设备适配:处理不同平台的样式差异
  7. 性能优化:合理使用CSS选择器和动画
  8. 最佳实践:建立统一的样式规范和组件库