Skip to content

样式处理

在uni-app开发中,样式处理是构建用户界面的重要部分。本文将详细介绍uni-app中的样式处理方法、跨平台样式适配以及常用的样式技巧。

uni-app样式基础

支持的样式语言

uni-app支持以下几种样式语言:

  1. CSS:标准的层叠样式表
  2. SCSS/SASS:CSS的预处理器,需要安装相应的依赖
  3. Less:另一种CSS预处理器,同样需要安装相应的依赖
  4. Stylus:第三种CSS预处理器,需要安装相应的依赖

使用预处理器

要使用CSS预处理器,首先需要安装相应的依赖:

bash
# 安装SCSS/SASS
npm install sass sass-loader -D

# 安装Less
npm install less less-loader -D

# 安装Stylus
npm install stylus stylus-loader -D

然后在<style>标签中指定lang属性:

html
<!-- 使用SCSS -->
<style lang="scss">
$primary-color: #007AFF;

.btn {
  background-color: $primary-color;
  &:active {
    opacity: 0.8;
  }
}
</style>

<!-- 使用Less -->
<style lang="less">
@primary-color: #007AFF;

.btn {
  background-color: @primary-color;
  &:active {
    opacity: 0.8;
  }
}
</style>

<!-- 使用Stylus -->
<style lang="stylus">
primary-color = #007AFF

.btn
  background-color primary-color
  &:active
    opacity 0.8
</style>

样式导入

uni-app支持通过@import导入外部样式文件:

css
/* 导入公共样式 */
@import '@/common/styles/common.css';
@import '@/common/styles/variables.scss';

提示

使用预处理器时,可以在全局导入变量和混合(mixins),避免在每个组件中重复导入。

uni-app样式特性

rpx响应式单位

uni-app提供了rpx(responsive pixel)作为响应式单位,用于适配不同屏幕尺寸。在不同设备上,rpx会自动按比例缩放。

  • 规定屏幕宽度为750rpx(即设计稿以750px为标准)
  • 开发者可以直接按照设计稿的尺寸来写样式,不需要进行像素转换
css
.container {
  width: 750rpx;
  height: 100rpx;
  padding: 20rpx;
  font-size: 28rpx;
}

rpx与px的换算关系:

设备rpx换算px比例
iPhone 51rpx = 0.42px
iPhone 6/7/81rpx = 0.5px
iPhone 6/7/8 Plus1rpx = 0.552px
iPhone X/XS1rpx = 0.552px

注意

在较小的屏幕上,如果rpx计算出的px值小于1,会被四舍五入取整,可能导致不够精确。对于需要精确控制的边框等场景,建议使用px作为单位。

样式作用域

uni-app支持两种样式作用域:

  1. 全局样式:在App.vue中定义的样式,作用于整个应用
  2. 局部样式:在页面或组件中定义的样式,仅作用于当前页面或组件
html
<!-- App.vue -->
<style>
/* 全局样式 */
.btn {
  padding: 20rpx;
  border-radius: 8rpx;
}
</style>

<!-- 页面或组件 -->
<style>
/* 局部样式 */
.container {
  padding: 30rpx;
}
</style>

如果要在组件中使用scoped样式(仅作用于当前组件),可以添加scoped属性:

html
<style scoped>
.title {
  font-size: 32rpx;
  color: #333;
}
</style>

条件编译样式

uni-app支持通过条件编译来为不同平台编写特定的样式:

css
/* 所有平台通用样式 */
.btn {
  padding: 20rpx;
}

/* #ifdef H5 */
/* H5平台特有样式 */
.btn {
  cursor: pointer;
}
/* #endif */

/* #ifdef MP-WEIXIN */
/* 微信小程序特有样式 */
.btn {
  line-height: 2;
}
/* #endif */

/* #ifdef APP-PLUS */
/* App平台特有样式 */
.btn {
  box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
/* #endif */

条件编译支持的平台包括:

  • H5:H5平台
  • MP-WEIXIN:微信小程序
  • MP-ALIPAY:支付宝小程序
  • MP-BAIDU:百度小程序
  • MP-TOUTIAO:头条小程序
  • MP-QQ:QQ小程序
  • APP-PLUS:App平台
  • APP-NVUE:App平台的nvue页面

常用样式技巧

弹性布局(Flex)

Flex布局是uni-app中最常用的布局方式,适用于各种复杂的布局需求:

css
/* 基本的Flex容器 */
.container {
  display: flex;
  flex-direction: row; /* 水平排列,可选值:row、column */
  justify-content: space-between; /* 主轴对齐方式 */
  align-items: center; /* 交叉轴对齐方式 */
  flex-wrap: wrap; /* 是否换行 */
}

/* Flex子项 */
.item {
  flex: 1; /* 平均分配空间 */
}

.item-fixed {
  flex: 0 0 200rpx; /* 固定宽度 */
}

常见的Flex布局场景:

  1. 水平居中
css
.center-horizontal {
  display: flex;
  justify-content: center;
}
  1. 垂直居中
css
.center-vertical {
  display: flex;
  align-items: center;
}
  1. 水平垂直居中
css
.center {
  display: flex;
  justify-content: center;
  align-items: center;
}
  1. 两端对齐
css
.space-between {
  display: flex;
  justify-content: space-between;
}
  1. 均匀分布
css
.space-around {
  display: flex;
  justify-content: space-around;
}
  1. 底部对齐
css
.align-bottom {
  display: flex;
  align-items: flex-end;
}

网格布局(Grid)

对于更复杂的二维布局,可以使用Grid布局:

css
.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3列等宽 */
  grid-gap: 20rpx; /* 间距 */
}

.grid-item {
  background-color: #f1f1f1;
  padding: 20rpx;
}

/* 跨列或跨行的项目 */
.grid-item-large {
  grid-column: span 2; /* 跨2列 */
  grid-row: span 2; /* 跨2行 */
}

注意

Grid布局在某些低版本小程序中可能不被完全支持,使用前请确认目标平台的兼容性。

定位

uni-app支持CSS的定位属性:

css
/* 相对定位 */
.relative {
  position: relative;
  top: 20rpx;
  left: 30rpx;
}

/* 绝对定位 */
.absolute {
  position: absolute;
  top: 50rpx;
  right: 50rpx;
}

/* 固定定位 */
.fixed {
  position: fixed;
  bottom: 30rpx;
  right: 30rpx;
}

注意

在小程序中,fixed定位的元素会相对于页面进行定位,而不是视口。

文本样式

常用的文本样式设置:

css
.text {
  font-size: 28rpx;
  color: #333;
  line-height: 1.5;
  text-align: center;
  font-weight: bold;
  text-decoration: underline;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

多行文本溢出显示省略号:

css
.multi-ellipsis {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; /* 显示行数 */
  overflow: hidden;
}

阴影效果

添加阴影效果:

css
.card {
  background-color: #fff;
  border-radius: 8rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

/* 更强烈的阴影 */
.card-raised {
  box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.15);
}

渐变背景

创建渐变背景:

css
.gradient-bg {
  background: linear-gradient(to right, #4facfe, #00f2fe);
}

/* 对角线渐变 */
.gradient-diagonal {
  background: linear-gradient(135deg, #667eea, #764ba2);
}

/* 径向渐变 */
.gradient-radial {
  background: radial-gradient(circle, #667eea, #764ba2);
}

动画效果

使用CSS动画:

css
/* 定义动画 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 应用动画 */
.fade-in {
  animation: fadeIn 0.5s ease-in-out;
}

/* 过渡效果 */
.btn {
  background-color: #007AFF;
  transition: all 0.3s ease;
}

.btn:active {
  background-color: #0056b3;
  transform: scale(0.98);
}

样式组织与管理

样式模块化

为了更好地组织样式,可以将样式按功能拆分为多个文件:

styles/
  ├── variables.scss     # 变量定义
  ├── mixins.scss        # 混合器定义
  ├── reset.scss         # 重置样式
  ├── typography.scss    # 文字排版样式
  ├── buttons.scss       # 按钮样式
  ├── forms.scss         # 表单样式
  ├── layout.scss        # 布局样式
  └── utilities.scss     # 工具类样式

App.vue中导入这些样式:

html
<style lang="scss">
@import '@/styles/variables.scss';
@import '@/styles/mixins.scss';
@import '@/styles/reset.scss';
@import '@/styles/typography.scss';
@import '@/styles/buttons.scss';
@import '@/styles/forms.scss';
@import '@/styles/layout.scss';
@import '@/styles/utilities.scss';
</style>

使用SCSS变量和混合器

variables.scss中定义变量:

scss
// 颜色
$primary-color: #007AFF;
$secondary-color: #6c757d;
$success-color: #28a745;
$danger-color: #dc3545;
$warning-color: #ffc107;
$info-color: #17a2b8;
$light-color: #f8f9fa;
$dark-color: #343a40;

// 字体
$font-family-base: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
$font-size-base: 28rpx;
$font-size-large: 32rpx;
$font-size-small: 24rpx;

// 间距
$spacing-base: 20rpx;
$spacing-large: 30rpx;
$spacing-small: 10rpx;

// 边框
$border-radius: 8rpx;
$border-color: #eee;
$border-width: 1rpx;

// 阴影
$box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);

mixins.scss中定义混合器:

scss
// 清除浮动
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

// 文本溢出显示省略号
@mixin text-ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

// 多行文本溢出显示省略号
@mixin multi-ellipsis($lines: 2) {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: $lines;
  overflow: hidden;
}

// 绝对居中
@mixin absolute-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

// 响应式断点
@mixin respond-to($breakpoint) {
  @if $breakpoint == 'small' {
    @media screen and (max-width: 375px) {
      @content;
    }
  } @else if $breakpoint == 'medium' {
    @media screen and (min-width: 376px) and (max-width: 768px) {
      @content;
    }
  } @else if $breakpoint == 'large' {
    @media screen and (min-width: 769px) {
      @content;
    }
  }
}

在组件中使用变量和混合器:

html
<style lang="scss">
@import '@/styles/variables.scss';
@import '@/styles/mixins.scss';

.card {
  background-color: #fff;
  border-radius: $border-radius;
  box-shadow: $box-shadow;
  padding: $spacing-base;
  margin-bottom: $spacing-base;
  
  .title {
    font-size: $font-size-large;
    color: $dark-color;
    @include text-ellipsis;
  }
  
  .content {
    font-size: $font-size-base;
    color: $secondary-color;
    margin-top: $spacing-small;
    @include multi-ellipsis(3);
  }
  
  .footer {
    margin-top: $spacing-base;
    @include clearfix;
    
    .btn {
      float: right;
      background-color: $primary-color;
      color: #fff;
      padding: $spacing-small $spacing-base;
      border-radius: $border-radius;
    }
  }
  
  @include respond-to('small') {
    padding: $spacing-small;
  }
}
</style>

工具类样式

utilities.scss中定义常用的工具类:

scss
// 间距
.m-0 { margin: 0; }
.m-1 { margin: $spacing-small; }
.m-2 { margin: $spacing-base; }
.m-3 { margin: $spacing-large; }

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: $spacing-small; }
.mt-2 { margin-top: $spacing-base; }
.mt-3 { margin-top: $spacing-large; }

// 类似地定义其他方向的margin和padding...

// 文本对齐
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }

// 文本颜色
.text-primary { color: $primary-color; }
.text-secondary { color: $secondary-color; }
.text-success { color: $success-color; }
.text-danger { color: $danger-color; }
.text-warning { color: $warning-color; }
.text-info { color: $info-color; }
.text-light { color: $light-color; }
.text-dark { color: $dark-color; }

// 背景颜色
.bg-primary { background-color: $primary-color; }
.bg-secondary { background-color: $secondary-color; }
.bg-success { background-color: $success-color; }
.bg-danger { background-color: $danger-color; }
.bg-warning { background-color: $warning-color; }
.bg-info { background-color: $info-color; }
.bg-light { background-color: $light-color; }
.bg-dark { background-color: $dark-color; }

// 显示
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.d-inline { display: inline; }
.d-inline-block { display: inline-block; }

// Flex工具类
.flex-row { flex-direction: row; }
.flex-column { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-nowrap { flex-wrap: nowrap; }
.justify-content-start { justify-content: flex-start; }
.justify-content-end { justify-content: flex-end; }
.justify-content-center { justify-content: center; }
.justify-content-between { justify-content: space-between; }
.justify-content-around { justify-content: space-around; }
.align-items-start { align-items: flex-start; }
.align-items-end { align-items: flex-end; }
.align-items-center { align-items: center; }
.align-items-baseline { align-items: baseline; }
.align-items-stretch { align-items: stretch; }

// 其他工具类
.w-100 { width: 100%; }
.h-100 { height: 100%; }
.rounded { border-radius: $border-radius; }
.border { border: $border-width solid $border-color; }
.shadow { box-shadow: $box-shadow; }
.overflow-hidden { overflow: hidden; }
.position-relative { position: relative; }
.position-absolute { position: absolute; }
.position-fixed { position: fixed; }

在页面中使用工具类:

html
<template>
  <view class="container">
    <view class="card m-2 shadow">
      <view class="d-flex justify-content-between align-items-center">
        <text class="text-dark">标题</text>
        <text class="text-secondary">副标题</text>
      </view>
      <view class="mt-2 text-primary">内容</view>
      <view class="d-flex justify-content-end mt-2">
        <button class="bg-primary text-light">按钮</button>
      </view>
    </view>
  </view>
</template>

跨平台样式适配

样式兼容性问题

不同平台对CSS的支持程度不同,可能导致样式在不同平台上表现不一致。常见的兼容性问题包括:

  1. Flex布局:某些旧版本小程序对Flex布局的支持不完善
  2. CSS变量:低版本平台可能不支持CSS变量
  3. 动画效果:某些复杂动画在不同平台上表现不一致
  4. 字体:不同平台默认字体不同,可能导致文本显示差异

平台差异化处理

条件编译

使用条件编译为不同平台提供特定样式:

html
<style>
/* 通用样式 */
.btn {
  padding: 20rpx;
}

/* #ifdef H5 */
/* H5特有样式 */
.btn {
  cursor: pointer;
}
/* #endif */

/* #ifdef MP-WEIXIN */
/* 微信小程序特有样式 */
.btn {
  line-height: 2;
}
/* #endif */
</style>

平台特有样式类

为不同平台添加特定的类名:

js
// 在页面或组件的onLoad中添加平台标识类
onLoad() {
  // #ifdef H5
  this.platformClass = 'platform-h5'
  // #endif
  
  // #ifdef MP-WEIXIN
  this.platformClass = 'platform-mp-weixin'
  // #endif
  
  // #ifdef APP-PLUS
  this.platformClass = 'platform-app'
  // #endif
}

在模板中使用:

html
<template>
  <view :class="['container', platformClass]">
    <!-- 内容 -->
  </view>
</template>

在样式中针对不同平台编写特定样式:

css
/* 通用样式 */
.container {
  padding: 20rpx;
}

/* H5平台特有样式 */
.platform-h5 .btn {
  cursor: pointer;
}

/* 微信小程序特有样式 */
.platform-mp-weixin .btn {
  line-height: 2;
}

/* App平台特有样式 */
.platform-app .btn {
  box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}

自适应布局

为了适应不同屏幕尺寸,可以采用以下策略:

  1. 使用rpx单位:自动适应不同屏幕尺寸
  2. 使用百分比:根据父容器大小自动调整
  3. 使用Flex布局:灵活分配空间
  4. 媒体查询:针对不同屏幕尺寸提供不同样式
scss
.container {
  padding: 20rpx;
  
  @include respond-to('small') {
    padding: 10rpx;
  }
  
  @include respond-to('large') {
    padding: 30rpx;
    max-width: 750rpx;
    margin: 0 auto;
  }
}

常见样式案例

卡片组件

html
<template>
  <view class="card">
    <view class="card-header">
      <text class="card-title">卡片标题</text>
      <text class="card-subtitle">副标题</text>
    </view>
    <view class="card-body">
      <text class="card-text">卡片内容区域,可以放置各种内容。</text>
    </view>
    <view class="card-footer">
      <button class="btn btn-primary">按钮</button>
    </view>
  </view>
</template>

<style lang="scss">
.card {
  background-color: #fff;
  border-radius: 12rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  margin-bottom: 20rpx;
  overflow: hidden;
  
  &-header {
    padding: 20rpx;
    border-bottom: 1rpx solid #eee;
  }
  
  &-title {
    font-size: 32rpx;
    font-weight: bold;
    color: #333;
    display: block;
  }
  
  &-subtitle {
    font-size: 24rpx;
    color: #666;
    margin-top: 6rpx;
    display: block;
  }
  
  &-body {
    padding: 20rpx;
  }
  
  &-text {
    font-size: 28rpx;
    color: #333;
    line-height: 1.5;
  }
  
  &-footer {
    padding: 20rpx;
    border-top: 1rpx solid #eee;
    display: flex;
    justify-content: flex-end;
  }
}

.btn {
  padding: 15rpx 30rpx;
  border-radius: 8rpx;
  font-size: 28rpx;
  
  &-primary {
    background-color: #007AFF;
    color: #fff;
  }
}
</style>

列表组件

html
<template>
  <view class="list">
    <view class="list-item" v-for="(item, index) in items" :key="index">
      <image class="list-item-image" :src="item.image" mode="aspectFill"></image>
      <view class="list-item-content">
        <text class="list-item-title">{{ item.title }}</text>
        <text class="list-item-desc">{{ item.description }}</text>
      </view>
      <view class="list-item-action">
        <text class="list-item-price">{{ item.price }}</text>
        <button class="list-item-btn">查看</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      items: [
        {
          title: '商品标题1',
          description: '商品描述信息,这是一段比较长的文字内容...',
          image: '/static/images/item1.jpg',
          price: '¥99.00'
        },
        {
          title: '商品标题2',
          description: '商品描述信息,这是一段比较长的文字内容...',
          image: '/static/images/item2.jpg',
          price: '¥199.00'
        },
        {
          title: '商品标题3',
          description: '商品描述信息,这是一段比较长的文字内容...',
          image: '/static/images/item3.jpg',
          price: '¥299.00'
        }
      ]
    }
  }
}
</script>

<style lang="scss">
.list {
  background-color: #f5f5f5;
  padding: 10rpx;
  
  &-item {
    display: flex;
    background-color: #fff;
    border-radius: 8rpx;
    margin-bottom: 20rpx;
    padding: 20rpx;
    
    &-image {
      width: 160rpx;
      height: 160rpx;
      border-radius: 8rpx;
      flex-shrink: 0;
    }
    
    &-content {
      flex: 1;
      padding: 0 20rpx;
      overflow: hidden;
    }
    
    &-title {
      font-size: 32rpx;
      color: #333;
      font-weight: bold;
      margin-bottom: 10rpx;
      display: block;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    
    &-desc {
      font-size: 26rpx;
      color: #666;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
    }
    
    &-action {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      align-items: flex-end;
      flex-shrink: 0;
      width: 140rpx;
    }
    
    &-price {
      font-size: 32rpx;
      color: #ff6700;
      font-weight: bold;
    }
    
    &-btn {
      font-size: 24rpx;
      padding: 8rpx 20rpx;
      background-color: #007AFF;
      color: #fff;
      border-radius: 30rpx;
      line-height: 1.5;
    }
  }
}
</style>

表单样式

html
<template>
  <view class="form-container">
    <view class="form-group">
      <text class="form-label">用户名</text>
      <input class="form-input" placeholder="请输入用户名" v-model="username" />
    </view>
    
    <view class="form-group">
      <text class="form-label">密码</text>
      <input class="form-input" type="password" placeholder="请输入密码" v-model="password" />
    </view>
    
    <view class="form-group">
      <text class="form-label">性别</text>
      <view class="form-radio-group">
        <label class="form-radio">
          <radio value="male" :checked="gender === 'male'" color="#007AFF" @tap="gender = 'male'" />
          <text>男</text>
        </label>
        <label class="form-radio">
          <radio value="female" :checked="gender === 'female'" color="#007AFF" @tap="gender = 'female'" />
          <text>女</text>
        </label>
      </view>
    </view>
    
    <view class="form-group">
      <text class="form-label">兴趣爱好</text>
      <view class="form-checkbox-group">
        <label class="form-checkbox" v-for="(item, index) in hobbies" :key="index">
          <checkbox :value="item.value" :checked="item.checked" color="#007AFF" @tap="toggleHobby(index)" />
          <text>{{ item.label }}</text>
        </label>
      </view>
    </view>
    
    <view class="form-group">
      <text class="form-label">所在地区</text>
      <picker mode="region" @change="onRegionChange" :value="region" class="form-picker">
        <view class="form-picker-value">
          {{ region[0] + ' ' + region[1] + ' ' + region[2] || '请选择地区' }}
        </view>
      </picker>
    </view>
    
    <view class="form-group">
      <text class="form-label">个人简介</text>
      <textarea class="form-textarea" placeholder="请输入个人简介" v-model="bio" />
    </view>
    
    <view class="form-group form-actions">
      <button class="form-button form-button-secondary" @tap="resetForm">重置</button>
      <button class="form-button form-button-primary" @tap="submitForm">提交</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: '',
      gender: 'male',
      hobbies: [
        { label: '阅读', value: 'reading', checked: false },
        { label: '音乐', value: 'music', checked: false },
        { label: '运动', value: 'sports', checked: false },
        { label: '旅行', value: 'travel', checked: false }
      ],
      region: ['广东省', '深圳市', '南山区'],
      bio: ''
    }
  },
  methods: {
    toggleHobby(index) {
      this.hobbies[index].checked = !this.hobbies[index].checked
    },
    onRegionChange(e) {
      this.region = e.detail.value
    },
    resetForm() {
      this.username = ''
      this.password = ''
      this.gender = 'male'
      this.hobbies.forEach(item => {
        item.checked = false
      })
      this.region = ['广东省', '深圳市', '南山区']
      this.bio = ''
    },
    submitForm() {
      // 表单验证
      if (!this.username) {
        uni.showToast({
          title: '请输入用户名',
          icon: 'none'
        })
        return
      }
      if (!this.password) {
        uni.showToast({
          title: '请输入密码',
          icon: 'none'
        })
        return
      }
      
      // 提交表单
      uni.showLoading({
        title: '提交中...'
      })
      
      // 模拟提交
      setTimeout(() => {
        uni.hideLoading()
        uni.showToast({
          title: '提交成功',
          icon: 'success'
        })
      }, 1500)
    }
  }
}
</script>

<style lang="scss">
.form-container {
  padding: 30rpx;
  background-color: #fff;
}

.form-group {
  margin-bottom: 30rpx;
}

.form-label {
  display: block;
  font-size: 28rpx;
  color: #333;
  margin-bottom: 10rpx;
}

.form-input {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  padding: 0 20rpx;
  font-size: 28rpx;
  box-sizing: border-box;
}

.form-radio-group,
.form-checkbox-group {
  display: flex;
  flex-wrap: wrap;
}

.form-radio,
.form-checkbox {
  display: flex;
  align-items: center;
  margin-right: 30rpx;
  margin-bottom: 10rpx;
  font-size: 28rpx;
}

.form-picker {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  padding: 0 20rpx;
  box-sizing: border-box;
}

.form-picker-value {
  height: 80rpx;
  line-height: 80rpx;
  font-size: 28rpx;
  color: #333;
}

.form-textarea {
  width: 100%;
  height: 200rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  padding: 20rpx;
  font-size: 28rpx;
  box-sizing: border-box;
}

.form-actions {
  display: flex;
  justify-content: space-between;
  margin-top: 50rpx;
}

.form-button {
  width: 48%;
  height: 80rpx;
  border-radius: 8rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28rpx;
  
  &-primary {
    background-color: #007AFF;
    color: #fff;
  }
  
  &-secondary {
    background-color: #f5f5f5;
    color: #333;
  }
}
</style>

导航栏组件

html
<template>
  <view class="navbar">
    <view class="navbar-left" @tap="onBack" v-if="showBack">
      <text class="navbar-icon">&#xe60e;</text>
    </view>
    <view class="navbar-center">
      <text class="navbar-title">{{ title }}</text>
    </view>
    <view class="navbar-right">
      <slot name="right"></slot>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: '标题'
    },
    showBack: {
      type: Boolean,
      default: true
    }
  },
  methods: {
    onBack() {
      uni.navigateBack({
        delta: 1
      })
    }
  }
}
</script>

<style lang="scss">
.navbar {
  display: flex;
  align-items: center;
  height: 90rpx;
  background-color: #fff;
  border-bottom: 1rpx solid #eee;
  padding: 0 30rpx;
  
  &-left {
    width: 80rpx;
    display: flex;
    align-items: center;
  }
  
  &-center {
    flex: 1;
    text-align: center;
  }
  
  &-right {
    width: 80rpx;
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }
  
  &-title {
    font-size: 32rpx;
    font-weight: bold;
    color: #333;
  }
  
  &-icon {
    font-family: "iconfont";
    font-size: 36rpx;
    color: #333;
  }
}

/* #ifdef APP-PLUS || H5 */
.navbar {
  padding-top: var(--status-bar-height);
  height: calc(90rpx + var(--status-bar-height));
}
/* #endif */
</style>

标签页组件

html
<template>
  <view class="tabs">
    <view class="tabs-header">
      <view 
        class="tabs-item" 
        v-for="(item, index) in tabs" 
        :key="index"
        :class="{ active: currentTab === index }"
        @tap="changeTab(index)"
      >
        <text class="tabs-text">{{ item.title }}</text>
      </view>
      <view class="tabs-line" :style="lineStyle"></view>
    </view>
    <view class="tabs-content">
      <swiper 
        :current="currentTab" 
        @change="swiperChange" 
        :style="{ height: contentHeight }"
      >
        <swiper-item v-for="(item, index) in tabs" :key="index">
          <slot :name="'tab-' + index"></slot>
        </swiper-item>
      </swiper>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    tabs: {
      type: Array,
      default: () => []
    },
    defaultTab: {
      type: Number,
      default: 0
    },
    contentHeight: {
      type: String,
      default: '500rpx'
    }
  },
  data() {
    return {
      currentTab: this.defaultTab,
      tabWidth: 0
    }
  },
  computed: {
    lineStyle() {
      return {
        transform: `translateX(${this.currentTab * this.tabWidth}px)`,
        width: this.tabWidth + 'px'
      }
    }
  },
  mounted() {
    this.getTabWidth()
  },
  methods: {
    getTabWidth() {
      const query = uni.createSelectorQuery().in(this)
      query.select('.tabs-item').boundingClientRect(data => {
        if (data) {
          this.tabWidth = data.width
        }
      }).exec()
    },
    changeTab(index) {
      if (this.currentTab === index) return
      this.currentTab = index
      this.$emit('change', index)
    },
    swiperChange(e) {
      const index = e.detail.current
      this.currentTab = index
      this.$emit('change', index)
    }
  }
}
</script>

<style lang="scss">
.tabs {
  width: 100%;
  
  &-header {
    display: flex;
    position: relative;
    height: 80rpx;
    background-color: #fff;
    border-bottom: 1rpx solid #eee;
  }
  
  &-item {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
  }
  
  &-text {
    font-size: 28rpx;
    color: #666;
    transition: color 0.3s;
  }
  
  &-item.active {
    .tabs-text {
      color: #007AFF;
      font-weight: bold;
    }
  }
  
  &-line {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 4rpx;
    background-color: #007AFF;
    transition: transform 0.3s;
  }
  
  &-content {
    width: 100%;
  }
}
</style>

总结

在uni-app开发中,样式处理是构建用户界面的重要部分。通过合理使用uni-app提供的样式特性和跨平台适配技术,可以创建出美观、一致且高效的用户界面。

本文介绍了uni-app样式的基础知识、特性和常用技巧,包括:

  1. 样式语言支持:CSS、SCSS/SASS、Less、Stylus
  2. uni-app样式特性:rpx响应式单位、样式作用域、条件编译样式
  3. 常用样式技巧:弹性布局、网格布局、定位、文本样式、阴影效果、渐变背景、动画效果
  4. 样式组织与管理:样式模块化、使用变量和混合器、工具类样式
  5. 跨平台样式适配:处理样式兼容性问题、平台差异化处理、自适应布局
  6. 常见样式案例:卡片组件、列表组件、表单样式、导航栏组件、标签页组件

通过掌握这些样式处理方法和技巧,开发者可以更高效地构建uni-app应用的用户界面,提供更好的用户体验。

一次开发,多端部署 - 让跨平台开发更简单