在前一篇文章我们对uni-app有了一个最基础的认识,在开始一个uni-app项目,HbuilderX工具已经帮我们初始化好了一个工程化项目,所以此时我们开始uni-app项目后,一定是根据UI开始切图,所以此时你必须要以最高的标准还原一个小程序或者h5的切图。

正文开始...

在开始本文之前,主要会从以下几个方面去认识uni-app布局

  • 了解最基础常用内置组件
  • 一些常用的内置组件
  • 糊一个淘宝首页以此来熟悉uni-app中的内置组件

切图

我打算以移动端淘宝首页的静态页面来熟悉小程序中布局以及样式的编写

从结构分析来看,我们把首页主要分为三个模块header,contenttabBar

  • 设置背景颜色

我们发现背景颜色是灰色了,所以在App.vue中设置全局page的样式,相当于设置body的样式

<style>
	/*每个页面公共css */
page{
	background-color: #f4f4f4;
}
</style>
  • header模块
<template>
	<view class="top-bar-header">
		<view class="top-bar-header-logo"></view>
		<view class="top-bar-header-search">
			<text class="text">寻找宝贝店铺</text>
			<button class="btn">搜索</button>
		</view>
		<view class="top-bar-header-sigin"></view>
	</view>
</template>

我们在看下对应css

	$logo: "https://gw.alicdn.com/imgextra/i4/O1CN019XWXNq1aAPOVMYSiu_!!6000000003289-2-tps-167-63.png";
	$singPic: "https://gw.alicdn.com/imgextra/i4/O1CN01ftum4629SHP6bCqTm_!!6000000008066-2-tps-99-99.png";
	.top-bar-header {
		display: flex;
		align-items: center;
		padding: 10rpx 30rpx;
    position: sticky;
		z-index: 1;
		top:0;
		background-color: #f4f4f4;
		&-logo {
			&::before {
				content: "";
				display: block;
				width: 94rpx;
				height: 36rpx;
				background-image: url($logo);
				background-size: cover;
				background-repeat: no-repeat;
			}
		}
		&-search {
			display: flex;
			align-items: center;
			flex:1;
			border: 1px solid #ff5000;
			background-color: #fff;
			height: 60rpx;
			border-radius: 60rpx;
			justify-content: space-between;
			margin: 0 25rpx;
			.text {
				text-indent: 20rpx;
				font-size:20rpx;
				color:#666;
			}
			.btn {
				width:120rpx;
				height: 54rpx;
				line-height: 54rpx;
				border-radius: 27px;
				font-size: 20rpx;
				color: #fff;
				background-color: #FF8D0E;
				margin: 0 2rpx;
			}
		}
		&-sigin {
			&::before {
				content: "";
				display: block;
				width: 56rpx;
				height: 56rpx;
				background-image: url($singPic);
				background-size: 56rpx 56rpx;
				background-repeat: no-repeat;
			}
		}
	}

在以上布局上,我们发现我们的header组件使用的布局标签就是内置组件viewtext,然后我们的样式使用的是scss,你也会发现我们的logo签到使用的是在线图片

为什么我们需要使用在线cdn图片?

其实在小程序中,我们很多的资源图片推荐使用在线图片,因为本地加载的图片会增加主包的体积,我们优先考虑cdn加载图片地址

另外为什么我们使用了伪类去加载图片,个人觉得这种方式比image标签方式性能要好,因为css是可以被缓存的,所以使用css方式去家在图片,对应的资源更容易缓存,而且减少了小程序页面标签结构。

还有我们的头部使用了一个吸顶的效果

.top-bar-header {
		display: flex;
		align-items: center;
		padding: 10rpx 30rpx;
		position: sticky;
		z-index: 1;
		top:0;
		background-color: #f4f4f4;
   ...
}

position: sticky吸顶这个特性非常有用,这也是我们在移动端布局优化交互的一种手段,使用它对用户体验来说比较友好。

最后我们看下布局样式后的结果

在顶部已经布局完了,我们再来看下内容区域部分

  • content

在内容区域我们发现最顶部是一个可滑动区域的模块,也是一个导航模块,我们暂且叫做分类模块

在这块布局我主要是考虑这样的结构

看下结构

<template>
	<view class="content-wrap">
		<!--分类_start-->
		<scroll-view  class="classify" :scroll-x="true">
			<view class="classify-item" v-for="(item, index) in classifyData" :key="index">
				<view class="classify-item-sub" v-for="(sub, sindex) in item" :key="sindex">
					<view>
						<image :src="sub.url" mode="aspectFill"></image>
					</view>
					<view><text>{{sub.text}}</text></view>
				</view>
			</view>
		</scroll-view>
		<!--分类_end-->
	</view>
</template>

注意我们使用了一个内置scroll-view内置组件,通常在滑动区域我们就要考虑使用这个组件,我么设置scroll-x=true水平方向可以滚动,默认是xy轴方向不会滚动

写了结构后,我们看下scss

.content-wrap {
		width: 100%;
		.classify {
			margin: 30rpx 0 10rpx;
			padding: 10rpx 0;
			width: 94%;
			height: 320rpx;
			overflow: hidden;
			background-color: #fff;
			border-radius: 30rpx;
			transform: translate(3%,0);
		
			/deep/ .uni-scroll-view {
				-webkit-overflow-scrolling: touch;
				&::-webkit-scrollbar {
					display: none;
				}
			}
			&-item {
					display: flex;
					width:100%;
				&-sub {
					display: flex;
					flex-direction: column;
          align-items: center;
					width: 150rpx;
					font-size:26rpx;
					margin-left: 7rpx;
					image {
						width:134rpx;
						height:104rpx;
					}
				}
			}
		}
	}
	

我们发现这个分类左右有间隙,所以宽度需要设置一个自适应的值,这个宽度你不能写100%,否则右边的间距,你始终无法空出来,所以,我们这里采用取巧的办法,宽度设置94%,然后设置transform:translate(3%, 0)移动自身x轴线的距离

另外,我们发现每一个item-sub都有设置固定的宽度,这是需要设置的,为了一整屏显示恰好是5个,所以需要设置一个合适的宽度即可。并且我们有设置margin-left: 7rpx

我们看下绑定的数据部分

	export default {
		data() {
			return {
				classifyData: [
					[
						{
							text: "天猫新品",
							url: "//gw.alicdn.com/imgextra/i4/O1CN01XCiY1B1px9ivHkDGm_!!6000000005426-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "今日爆款",
							url: "//gw.alicdn.com/imgextra/i3/O1CN01FgQFp81spmBXqQMtA_!!6000000005816-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "天猫国际",
							url: "//gw.alicdn.com/imgextra/i1/O1CN01tsk5OY1q0MUo5PJga_!!6000000005433-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "飞猪旅行",
							url: "//gw.alicdn.com/imgextra/i2/O1CN01yK3Cxn1sTnAx1fOjq_!!6000000005768-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "天猫超市",
							url: "//gw.alicdn.com/imgextra/i1/O1CN01iZIGkz1URSOUdRHqS_!!6000000002514-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "冬奥公益",
							url: "//gw.alicdn.com/imgextra/i4/O1CN01VuRfwH1muSbsJFxoM_!!6000000005014-2-tps-183-144.png_q90.jpg_.webp"
						},{
							text: "淘宝票",
							url: "//gw.alicdn.com/imgextra/i2/O1CN01qrrUAN1wRjrhtfk6Q_!!6000000006305-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "天猫好房",
							url: "//gw.alicdn.com/imgextra/i3/O1CN01MiunNU1oU0uEKRvja_!!6000000005227-2-tps-183-144.png_q90.jpg_.webp"
						},
					],
					[
						{
							text: "淘宝吃货",
							url: "//gw.alicdn.com/imgextra/i1/O1CN018Ilnep1Qt9ryh1Vue_!!6000000002033-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "省钱卡",
							url: "//gw.alicdn.com/imgextra/i2/O1CN01gUIFrA1OuPj70aTIW_!!6000000001765-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "领淘金币",
							url: "//gw.alicdn.com/imgextra/i3/O1CN013WcsZW1jfr4AXnmVl_!!6000000004576-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "阿里拍卖",
							url: "//gw.alicdn.com/imgextra/i2/O1CN01ZOR1cv1yjGFORGh1V_!!6000000006614-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "分类",
							url: "//gw.alicdn.com/tfs/TB1WgOmesieb18jSZFvXXaI3FXa-183-144.png_q90.jpg_.webp?getAvatar=1"
						},
						{
							text: "咸鱼",
							url: "//gw.alicdn.com/imgextra/i1/O1CN01rIm4u11F81KogrKz4_!!6000000000441-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "土货鲜食",
							url: "//gw.alicdn.com/imgextra/i1/O1CN019LbO921EcsOuQqWTM_!!6000000000373-2-tps-183-144.png_q90.jpg_.webp"
						},
						{
							text: "天猫汽车",
							url: "//gw.alicdn.com/imgextra/i4/O1CN01cxU47W1RnUaWavX3f_!!6000000002156-2-tps-183-144.png_q90.jpg_.webp"
						}
					]
				]
			}
		}
	}

有人好奇,为什么我这是一个二维数组,正常情况下,后端给的动态接口数据肯定是一维的,怎么现在你渲染就变成二维的了?那是不是要跑去跟后端同学说,大兄弟,你的数据结构不符合我前端渲染要求,你能不能换成我这样的二维数组。

no,只要是我们前端能解决的,我们尽量不要麻烦后端,无论是一维还是多维,数据在手,天下我有,拿到数据,我们就可以改在成我任意数据结构。

var data = [
  {
    text: '天猫新品',
    url: ''
  },
  {
    text: '今日爆款',
    url: ''
  },
  {
    text: '天猫国际',
    url: ''
  }
  ...
]
var proxyTarnsdata = (data = [], limit = 2, row=2) => {
    let len = Math.ceil(data.length / row);
    var arr = new Array(len).fill(0);
    return arr.map((v, index) => data.slice(index*limit, limit* (index + 1)))
}
console.log(proxyTarnsdata([1,2,3,4,5,6,7,8,9,10,11]))
/*
[
  [1,2],
  [3,4],
  [5,6],
  [7,8],
  [9, 10],
  [11]
]
*/

我们通过以上一个proxyTarnsdata方法就可以将数组分割成我们想要的数据

最后我们看下最终布局效果

第一页默认数据 滑动到第二页数据

在样式中,你会发现css中有这样的一段代码

/deep/ .uni-scroll-view {
    -webkit-overflow-scrolling: touch;
    &::-webkit-scrollbar {
      display: none;
    }
}

这段代码的处理在业务中实际上是很常见,首先我么有用/deep/这样的一个深度选择器,通常来说是我们要修改第三方默认UI样式,除了这个我们有隐藏原生的滚动条,但是这并不会影响页面的横向滚动,在移动端可以这么做,要么设置一个好看的样式滚动条,但实际上原生滚动条样式在移动端确实有点丑了,所以这算是页面样式优化的一个点。

overflow-scrolling:touch这个可以让页面滑动更流畅

  • 商品区域

这部分比较简单,主要是固定四个模块

<!--货物分类_start-->
		<veiw class="goods">
			<view class="item" v-for="(item, index) in goodsList" :key="index">
				<view class="item-wrap" v-for="(subItem, sindex) in item" :key="sindex">
					<view class="item-header">
						<text class="title">{{subItem.text}}</text>
						<text :class="['tag', subItem.tagClass]">{{subItem.tag}}</text>
					</view>
					<view class="item-content">
						<view class="item-lf">
							<image :src="subItem.lf" mode="aspectFill"></image>
						</view>
						<view class="item-rt">
							<image :src="subItem.rl" mode="aspectFill"></image>
						</view>
					</view>
				</view>
			</view>
		</veiw>
		<!--货物分类_end-->

我们再看下对应绑定的数据

export default: {
  data() {
    return {
    goodsList: [
					[
						{
							text: '聚划算',
							tag: "品牌折扣",
							lf: '//gw.alicdn.com/tps/O1CN01y2pvJj1QCZ62MzJpO_!!6000000001940-0-yinhe.jpg_140x140q90.jpg_.webp',
							rl: '//gw.alicdn.com/tps/O1CN01YYixvu1hlgwUB7cJE_!!6000000004318-0-yinhe.jpg_180x180q90.jpg_.webp',
							tagClass: "jhs"
						},
						{
							text: '有好货',
							tag: "好口碑",
							lf: '//gw.alicdn.com/tps/O1CN01t8hkxx1l7AOFWyMym_!!6000000004771-0-yinhe.jpg_140x140q90.jpg_.webp',
							rl: '//gw.alicdn.com/tps/O1CN01IhbCD928CsXHRBzOP_!!6000000007897-0-yinhe.jpg_180x180q90.jpg_.webp',
							tagClass: "hkb"
						},
					],
					[
						{
							text: "天天特卖",
							tag: "1元秒杀",
							lf: "//gw.alicdn.com/tps/O1CN01sXaXqO1l5KpDBiueI_!!6000000004767-0-yinhe.jpg_140x140q90.jpg_.webp",
							rl: "//gw.alicdn.com/tps/TB1hevqDYj1gK0jSZFOSuw7GpXa.jpg_180x180q90.jpg_.webp",
							tagClass: "tttm"
						},
						{
							text: "每日好店",
							tag: "头条",
							lf: "//gw.alicdn.com/tps/O1CN01UDLq5p1OdTBxWkrJk_!!6000000001728-0-yinhe.jpg_140x140q90.jpg_.webp",
							rl: "//gw.alicdn.com/tps/O1CN018R31ZP1CbNjUG4pee_!!99-0-lubanu.jpg_180x180q90.jpg_.webp",
							tagClass: "mehd"
						},
					]
				]
    }
  }
}

我们发现goodslist依旧是一个二维数组,其实我们布局这样的结构主要有以下几种方案

  • 一维数据结构,采用换行,每一项item设置宽度50%,然后换行

  • 二维数组,采用左右结构,然后子元素flex:1处理等分父元素

这里我才用的是第二种方案,数据结构不同,采取的布局方案会稍有不同。

@mixin tagStyle($bgColor) {
		color: #fff;
		border-radius: 5rpx;
		padding: 0 5rpx;
		background-color: $bgColor;
	}
	@mixin set_width_height($w, $h) {
		width: $w;
		height: $h
	}
	.content-wrap {
		width: 100%;
		.classify {
			margin: 30rpx 0 10rpx;
			padding: 10rpx 0;
			width: 94%;
			height: 320rpx;
			overflow: hidden;
			background-color: #fff;
			border-radius: 30rpx;
			transform: translate(3%,0);
		
			/deep/ .uni-scroll-view {
				-webkit-overflow-scrolling: touch;
				&::-webkit-scrollbar {
					display: none;
				}
			}
			&-item {
					display: flex;
					width:100%;
				&-sub {
					display: flex;
					align-items: center;
					flex-direction: column;
					width: 150rpx;
					font-size:26rpx;
					margin-left: 7rpx;
					image {
						width:134rpx;
						height:104rpx;
					}
				}
			}
		}
		.goods {
			display: flex;
			overflow: hidden;
			background-color: #fff;
			border-radius: 30rpx;
			margin: 20rpx;
			.item {
				flex:1;
				display: flex;
				flex-direction: column;
        padding: 10rpx 0;
				&:first-child, &:last-child {
					.item-wrap {
						&:last-child {
							border-bottom: none;
						}
					}
				}
			
				&:last-child {
					.item-wrap {
						&:first-child {
							border-right: none;
						}
						&:last-child {
							border-right: none;
						}
					}
				}
				.item-wrap {
					display: flex;
					flex-direction: column;
					border-right: 1px solid #e5e5e5;
					border-bottom: 1px solid #e5e5e5;
					
					.item-header {
						.title {
							font-size: 35rpx;
							color: #111;
							font-weight: bold;
							padding: 25rpx 15rpx;
						}
						.tag {
							font-size: 20rpx;
							&.jhs, &.tttm {
								@include tagStyle(#ff4200);
							}
							&.hkb {
								@include tagStyle(#0090ea);
							}
							&.mehd {
								@include tagStyle(#ff9711);
							}
						}
					}
					.item-content {
						display: flex;
						align-items: center;
						justify-content: space-between;
						.item-lf {
							image {
								@include set_width_height(126rpx, 126rpx);
								margin-left: 20rpx;
							}
						}
						.item-rt {
							align-self: flex-end;
							image {
								@include set_width_height(160rpx, 160rpx);
							}
						}
					}
				}
				
			}
		}
	}

这里讲解下关键的两个函数tagStyleset_width_height

@mixin tagStyle($bgColor) {
		color: #fff;
		border-radius: 5rpx;
		padding: 0 5rpx;
		background-color: $bgColor;
	}
	@mixin set_width_height($w, $h) {
		width: $w;
		height: $h
	}

由于我们页面有这样类似的标签样式代码,所以我们利用了一个scssmixin极大的减少了重复代码,别忘记了scss内置了mixin这样好用方案,真的让你少写很多重复的代码

最后看下页面完成布局后的效果

因为这个组件官方有多大数据渲染的长列表优化,所以肯定比我们普通的获取数据,并直接渲染性能更高些

不过在用这个组件前,我们需要到官方插件市场去安装

直接导入HbuilderX中安装即可全局使用

当我们安装导入到我们自己项目时,我们的uni_modules中就有一个uni-list组件

在之前一篇文章中有写到,像这样导入的插件是自动注册的,所以在我们的页面组件中可以使用

<!--商品列表_start-->
		<uni-list class="shop-list" :border="false">
			<uni-list-item v-for="(item, index) in shopData" :key="index">
				<template slot="body">
					<view class="card-item-body">
						<image class="img" :src="item.url" mode="aspectFill"></image>
						<view class="card-item-body-desc">
							<text>{{item.desc}}</text>
						</view>
						<view>
							<text class="prise">¥{{item.prices}}</text>
							<text class="buys">{{item.buys}}+人已购买</text>
						</view>
					</view>
				</template>
			</uni-list-item>
		</uni-list>
		<!--商品列表_end-->

uni-list就是官方的一个插件,当我们在官方市场导入项目时,我们就可以像上面一样使用了

我们看下对应的css

.shop-list {
			background-color: #fff;
			display: flex;
			justify-content: space-between;
			flex-direction: row;
			flex-wrap: wrap;
			box-sizing: border-box;
			padding: 0 20rpx;
			/deep/ {
				.uni-list-item__container {
					padding: 0;	
				}
			}
			.card-item-body {
				width:340rpx;
				border-radius: 20rpx;
				padding-bottom: 10rpx;
				&-desc {
					overflow: hidden;
					height: 75rpx;
					padding: 20rpx 10rpx;
				}
				.prise {
					color: #ff5500;
					font-size: 28rpx;
				}
				.buys {
					color: #999;
					padding-left: 10rpx;
					font-size:22rpx;
					
				}
				.img {
					width:100%;
					height: 342rpx;
				}
			}
		}
.footer {
			background-color: #4e525e;
			.footer-wrap {
				display: flex;
				justify-content: space-around;
				padding: 20rpx 0;
				color: #c3c6ca;
				.btn {
					border: 1px solid #42454d;
					background-color: #555965;
					border-radius: 12rpx;
					padding: 10rpx 34rpx;
					font-size: 20rpx;
				}
			}
			&-timer {
				font-size: 20rpx;
				text-align: center;
				color:#c3c6ca;
				padding-bottom: 20rpx;
			}
		}

另外还有一个底部页脚

	<!--底部_start-->
		<view class="footer">
			<view class="footer-wrap">
				<view v-for="(item, index) in footerData" :key="index" class="btn">
					{{item.text}}
				</view>
			</view>
			<view class="footer-timer">
				@2022-Web技术学苑
			</view>
			
		</view>
		<!--底部_end-->

我们看下最终的布局效果

  • tabBar底部切换

关于tabBar其实在第一篇文章就已有讲过,实际上是在pages.json中配置

为什么不自己布局底部的tabBar呢?

其实官方采用这种方案肯定是有自己的考量,就是要磨平不同端的差异,所以不需要用户自己开发一个底部的tab切换,而是通过统一配置就行

tabBaropen in new window中我们可以看到

"tabBar": {
		"color": "#7A7E83",
		"selectedColor": "#3cc51f",
		"borderStyle": "black",
		"backgroundColor": "#ffffff",
		"height": "60px",
		"list": [{
			"pagePath": "pages/index/index",
			"iconPath": "https://img.alicdn.com/imgextra/i2/O1CN01OStgh21aWOP69RsTZ_!!6000000003337-2-tps-63-63.png",
			"selectedIconPath": "https://img.alicdn.com/imgextra/i1/O1CN01oAxBQ81qF1EZtn4Rq_!!6000000005465-2-tps-108-108.png",
			"text": ""
		}, {
			"pagePath": "pages/shop/shop",
			"iconPath": "https://img.alicdn.com/imgextra/i3/O1CN0151qDFg2AN4Vma3SdE_!!6000000008190-2-tps-63-63.png",
			"selectedIconPath": "",
			"text": "购物车"
		},
		{
			"pagePath": "pages/my/my",
			"iconPath": "https://img.alicdn.com/imgextra/i3/O1CN01nOAkIZ1ou7XqRzXEZ_!!6000000005284-2-tps-63-63.png",
			"selectedIconPath": "https://img.alicdn.com/imgextra/i1/O1CN01gJdKCx1sB10EydwbE_!!6000000005727-2-tps-63-63.png",
			"text": "我的淘宝"
		}
		]
	}

我们在首页有使用一张透明图,此时你需要修改tabBar样式,正常情况,你需要在App.vue文件中修改

App.vue

.pages-index-index {
	uni-tabbar {
		.uni-tabbar {
			.uni-tabbar__item {
				&:nth-of-type(2) {
					.uni-tabbar__bd {
						background-color: #FF8D0E;
						border-radius: 50%;
						width: 100rpx;
						height: 100rpx !important;
						img {
							width: 100%;
							height: auto;
							transform: scale(1.5);
						}
					}
				}
			}
		}
	}
}

最后我们看下最终结果

这样我们用uni-app切了一张移动端淘宝的首页,还算完美,估计细节间距还要磨改,因为设计师的眼睛很毒,你这还原度太低了,这里要改,那里要改...,一个卑微的切图工具人,太惨了...

拿我大刀来,再说还原度不行,改个颜色一次,请自觉扫描五块钱...

总结

  • 就是用uni-app的一些内置组件还原了淘宝首页

  • 我们布局时候发现,所有的标签都是view与几个text,还有我们所用到的,image,uni-listuni-list-item,布局好像没用太多的内置组件,写样式还是一样,没有任何区别

  • 不知道你注意到没,我们的单位是用rpx,这在小程序中是一种响应式单位,其实在移动端自适应中,我们推崇rpxuni-app支持了h5小程序同时能用rpx,所以在uni-app 中单位就用rpx,1px = 2rpx,当你量得设计稿尺寸是100px时,换成rpx就是200rpx,uni-app会在最后的页面中帮你计算出来

  • 分享了scss中关于mixin的用法,可以大大提高代码的复用度和可维护性

  • 本文示例code exampleopen in new window

扫二维码,关注公众号
专注前端技术,分享web技术
加作者微信
扫二维码 备注 【加群】
教你聊天恋爱,助力脱单
微信扫小程序二维码,助你寻他/她
专注前端技术,分享Web技术
微信扫小程序二维码,一起学习,一起进步
前端面试大全
海量前端面试经典题,助力前端面试