在前一篇文章我们对uni-app
有了一个最基础的认识,在开始一个uni-app
项目,HbuilderX
工具已经帮我们初始化好了一个工程化项目,所以此时我们开始uni-app
项目后,一定是根据UI开始切图,所以此时你必须要以最高的标准还原一个小程序或者h5的切图。
正文开始...
在开始本文之前,主要会从以下几个方面去认识uni-app
布局
- 了解最基础常用内置组件
- 一些常用的内置组件
- 糊一个淘宝首页以此来熟悉
uni-app
中的内置组件
切图
我打算以移动端淘宝首页的静态页面来熟悉小程序中布局以及样式的编写
从结构分析来看,我们把首页主要分为三个模块header
,content
,tabBar
- 设置背景颜色
我们发现背景颜色是灰色了,所以在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
组件使用的布局标签就是内置组件view
,text
,然后我们的样式使用的是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);
}
}
}
}
}
}
}
这里讲解下关键的两个函数tagStyle
与set_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
}
由于我们页面有这样类似的标签样式代码,所以我们利用了一个scss
的mixin
极大的减少了重复代码,别忘记了scss
内置了mixin
这样好用方案,真的让你少写很多重复的代码
最后看下页面完成布局后的效果
- 商品列表 商品列表我们这里采用的官方推荐的组件uni-list
因为这个组件官方有多大数据渲染的长列表优化,所以肯定比我们普通的获取数据,并直接渲染性能更高些
不过在用这个组件前,我们需要到官方插件市场去安装
直接导入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
切换,而是通过统一配置就行
在tabBar中我们可以看到
"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-list
、uni-list-item
,布局好像没用太多的内置组件,写样式还是一样,没有任何区别不知道你注意到没,我们的单位是用
rpx
,这在小程序中是一种响应式单位,其实在移动端自适应中,我们推崇rpx
,uni-app
支持了h5
与小程序
同时能用rpx
,所以在uni-app
中单位就用rpx
,1px = 2rpx
,当你量得设计稿尺寸是100px时,换成rpx
就是200rpx
,uni-app
会在最后的页面中帮你计算出来分享了
scss
中关于mixin
的用法,可以大大提高代码的复用度和可维护性本文示例code example