样式处理
在uni-app开发中,样式处理是构建用户界面的重要部分。本文将详细介绍uni-app中的样式处理方法、跨平台样式适配以及常用的样式技巧。
uni-app样式基础
支持的样式语言
uni-app支持以下几种样式语言:
- CSS:标准的层叠样式表
- SCSS/SASS:CSS的预处理器,需要安装相应的依赖
- Less:另一种CSS预处理器,同样需要安装相应的依赖
- Stylus:第三种CSS预处理器,需要安装相应的依赖
使用预处理器
要使用CSS预处理器,首先需要安装相应的依赖:
# 安装SCSS/SASS
npm install sass sass-loader -D
# 安装Less
npm install less less-loader -D
# 安装Stylus
npm install stylus stylus-loader -D
然后在<style>
标签中指定lang属性:
<!-- 使用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
导入外部样式文件:
/* 导入公共样式 */
@import '@/common/styles/common.css';
@import '@/common/styles/variables.scss';
提示
使用预处理器时,可以在全局导入变量和混合(mixins),避免在每个组件中重复导入。
uni-app样式特性
rpx响应式单位
uni-app提供了rpx
(responsive pixel)作为响应式单位,用于适配不同屏幕尺寸。在不同设备上,rpx会自动按比例缩放。
- 规定屏幕宽度为750rpx(即设计稿以750px为标准)
- 开发者可以直接按照设计稿的尺寸来写样式,不需要进行像素转换
.container {
width: 750rpx;
height: 100rpx;
padding: 20rpx;
font-size: 28rpx;
}
rpx与px的换算关系:
设备 | rpx换算px比例 |
---|---|
iPhone 5 | 1rpx = 0.42px |
iPhone 6/7/8 | 1rpx = 0.5px |
iPhone 6/7/8 Plus | 1rpx = 0.552px |
iPhone X/XS | 1rpx = 0.552px |
注意
在较小的屏幕上,如果rpx计算出的px值小于1,会被四舍五入取整,可能导致不够精确。对于需要精确控制的边框等场景,建议使用px作为单位。
样式作用域
uni-app支持两种样式作用域:
- 全局样式:在App.vue中定义的样式,作用于整个应用
- 局部样式:在页面或组件中定义的样式,仅作用于当前页面或组件
<!-- App.vue -->
<style>
/* 全局样式 */
.btn {
padding: 20rpx;
border-radius: 8rpx;
}
</style>
<!-- 页面或组件 -->
<style>
/* 局部样式 */
.container {
padding: 30rpx;
}
</style>
如果要在组件中使用scoped样式(仅作用于当前组件),可以添加scoped
属性:
<style scoped>
.title {
font-size: 32rpx;
color: #333;
}
</style>
条件编译样式
uni-app支持通过条件编译来为不同平台编写特定的样式:
/* 所有平台通用样式 */
.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中最常用的布局方式,适用于各种复杂的布局需求:
/* 基本的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布局场景:
- 水平居中
.center-horizontal {
display: flex;
justify-content: center;
}
- 垂直居中
.center-vertical {
display: flex;
align-items: center;
}
- 水平垂直居中
.center {
display: flex;
justify-content: center;
align-items: center;
}
- 两端对齐
.space-between {
display: flex;
justify-content: space-between;
}
- 均匀分布
.space-around {
display: flex;
justify-content: space-around;
}
- 底部对齐
.align-bottom {
display: flex;
align-items: flex-end;
}
网格布局(Grid)
对于更复杂的二维布局,可以使用Grid布局:
.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的定位属性:
/* 相对定位 */
.relative {
position: relative;
top: 20rpx;
left: 30rpx;
}
/* 绝对定位 */
.absolute {
position: absolute;
top: 50rpx;
right: 50rpx;
}
/* 固定定位 */
.fixed {
position: fixed;
bottom: 30rpx;
right: 30rpx;
}
注意
在小程序中,fixed
定位的元素会相对于页面进行定位,而不是视口。
文本样式
常用的文本样式设置:
.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;
}
多行文本溢出显示省略号:
.multi-ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 显示行数 */
overflow: hidden;
}
阴影效果
添加阴影效果:
.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);
}
渐变背景
创建渐变背景:
.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动画:
/* 定义动画 */
@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
中导入这些样式:
<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
中定义变量:
// 颜色
$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
中定义混合器:
// 清除浮动
@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;
}
}
}
在组件中使用变量和混合器:
<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
中定义常用的工具类:
// 间距
.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; }
在页面中使用工具类:
<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的支持程度不同,可能导致样式在不同平台上表现不一致。常见的兼容性问题包括:
- Flex布局:某些旧版本小程序对Flex布局的支持不完善
- CSS变量:低版本平台可能不支持CSS变量
- 动画效果:某些复杂动画在不同平台上表现不一致
- 字体:不同平台默认字体不同,可能导致文本显示差异
平台差异化处理
条件编译
使用条件编译为不同平台提供特定样式:
<style>
/* 通用样式 */
.btn {
padding: 20rpx;
}
/* #ifdef H5 */
/* H5特有样式 */
.btn {
cursor: pointer;
}
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序特有样式 */
.btn {
line-height: 2;
}
/* #endif */
</style>
平台特有样式类
为不同平台添加特定的类名:
// 在页面或组件的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
}
在模板中使用:
<template>
<view :class="['container', platformClass]">
<!-- 内容 -->
</view>
</template>
在样式中针对不同平台编写特定样式:
/* 通用样式 */
.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);
}
自适应布局
为了适应不同屏幕尺寸,可以采用以下策略:
- 使用rpx单位:自动适应不同屏幕尺寸
- 使用百分比:根据父容器大小自动调整
- 使用Flex布局:灵活分配空间
- 媒体查询:针对不同屏幕尺寸提供不同样式
.container {
padding: 20rpx;
@include respond-to('small') {
padding: 10rpx;
}
@include respond-to('large') {
padding: 30rpx;
max-width: 750rpx;
margin: 0 auto;
}
}
常见样式案例
卡片组件
<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>
列表组件
<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>
表单样式
<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>
导航栏组件
<template>
<view class="navbar">
<view class="navbar-left" @tap="onBack" v-if="showBack">
<text class="navbar-icon"></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>
标签页组件
<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样式的基础知识、特性和常用技巧,包括:
- 样式语言支持:CSS、SCSS/SASS、Less、Stylus
- uni-app样式特性:rpx响应式单位、样式作用域、条件编译样式
- 常用样式技巧:弹性布局、网格布局、定位、文本样式、阴影效果、渐变背景、动画效果
- 样式组织与管理:样式模块化、使用变量和混合器、工具类样式
- 跨平台样式适配:处理样式兼容性问题、平台差异化处理、自适应布局
- 常见样式案例:卡片组件、列表组件、表单样式、导航栏组件、标签页组件
通过掌握这些样式处理方法和技巧,开发者可以更高效地构建uni-app应用的用户界面,提供更好的用户体验。