经过一段时间对微信小程序的学习,做了一个微信小程序项目“智学商城”,我们先来看看整个项目的效果,再对其讲解,项目效果如下:
先讲解首页,首页主要有这么几个部分、底部选项栏、顶部滑动分类列表、轮播、热销商品、商品列表等
底部选项栏是怎么实现的呢?通过在app.json文件配置来实现,只需配置即可实现,配置代码如下:
{
"pages": [//位置
"pages/shopping/shopping",
"pages/shoppingTrolley/shopping",
"pages/index/index",
"pages/my/my",
"pages/search/search",
"pages/classify/classify"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#DC143C",
"navigationBarTitleText": "智学商城",
"navigationBarTextStyle": "white"
},
"tabBar": {
"selectedColor": "#DC143C",
"list": [
{
"pagePath": "pages/index/index",//路径
"text": "首页",//标题
"iconPath": "img/nav/home-off.png",//默认图标
"selectedIconPath": "img/nav/home-on.png"//选中时的图标
},
{
"pagePath": "pages/classify/classify",
"text": "分类",
"iconPath": "img/nav/search-off.png",
"selectedIconPath": "img/nav/search-on.png"
},
{
"pagePath": "pages/shopping/shopping",
"text": "购物车",
"iconPath": "img/nav/cart-off.png",
"selectedIconPath": "img/nav/cart-on.png"
},
{
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "img/nav/my-off.png",
"selectedIconPath": "img/nav/my-on.png"
}
]
}
}
以上代tabBar为底部选项栏配置,注意在app.json文件里是不能写注释的,否则会报错,上面写了是为了方便大家理解。
接下来讲解首页其它部分的实现思路,如下
顶部滑动分类列表与热销商品实现思路,通过微信组件scroll-view 实现。
轮播与公告轮播实现思路,通过微信轮播组件 swiper 实现。
商品列表实现思路,使用弹性盒子模型进行布局实现。
整个页面实现的wxml代码如下:
<!--index.wxml-->
<view>
<!-- 顶部商品分类滑动列表 -->
<scroll-view scroll-x scroll-with-animotion class='scroll-view scoll-view-fixed'>
<!-- {{activeIndex==index?'active':''}}为动态添加类 -->
<view bindtap='setActiveIndex' class="scroll-view-item {{activeIndex==index?'active':''}}" wx:for='{{goodscategory}}' wx:key='*this' data-index='{{index}}'>{{item}}</view>
</scroll-view>
</view>
<view style='height:33px;'></view>
<!-- 轮播 -->
<!--autoplay是否自动轮播 indicator-dots是否显示指示点 interval间隔时间 circular是否衔接滑动-->
<swiper indicator-dots autoplay circular interval='2000'>
<block wx:for="{{imgurls}}" wx:key="*this">
<swiper-item>
<image src="{{item}}" class='slide-image' />
</swiper-item>
</block>
</swiper>
<!-- 公告 -->
<view class='view-adv'>
<view style="float: left;">公告:</view>
<!--autoplay是否自动轮播 vertical滑动方向是否为纵向 circular是否衔接滑动-->
<swiper class='swiper-adv' autoplay vertical circular>
<swiper-item wx:for="{{notice}}" wx:key="*this">
<text>{{item}}</text>
</swiper-item>
</swiper>
</view>
<!--分割线-->
<view style='height:10rpx;background-color:#eee;'></view>
<!-- 热销商品 -->
<scroll-view class='hot-prod' scroll-x bindscroll="hotProdScroll">
<view class="one-row">
<view class="row-item" wx:for='{{6}}'>
<image src='{{hotprods[index]}}'></image>
<view class="placeholder">热销{{index+1}}</view>
</view>
</view>
<view class="two-row">
<view class="row-item" wx:for='{{5}}'>
<image src='{{hotprods[6+index]}}'></image>
<view class="placeholder">热销{{index+1+6}}</view>
</view>
</view>
</scroll-view>
<!-- 显示进度 -->
<view class='prod-progress'>
<view class='progress-outer'>
<view class='progress-inner' style='margin-left:{{hotProdMarginLeft}}px'></view>
</view>
</view>
<view style='height:10rpx;background-color:#eee;'></view>
<!-- 商品列表 -->
<!-- 外层容器 -->
<view class='goods-container'>
<!-- 单品容器 -->
<view class='goods-item' wx:for='{{goods}}'>
<!-- 图片容器 -->
<view class='goods-image'>
<image src='{{item.pic}}'></image>
</view>
<!-- 商品名 -->
<view class='goods-title'>{{item.name}}</view>
<!-- 商品单价 -->
<view class='goods-price'>¥{{item.price}}</view>
</view>
</view>
wxss样式代码如下:
/*顶部商品分类*/
.scroll-view {
white-space: nowrap;/*不换行*/
}
.scoll-view-fixed {
position: fixed; /*固定定位*/
top: 0px;
background-color: white;
z-index: 9999;
}
.scroll-view .scroll-view-item {
display: inline-block; /*行内块级元素*/
line-height: 33px; /*行高*/
margin: 0 24rpx; /*左右边距*/
}
.scroll-view-item.active {
border-bottom: 2px solid #e02e24; /*激活元素的显示*/
}
/*轮播*/
.slide-image {
width: 100%;
max-height: 150px;
}
/*公告*/
.view-adv{
padding-top: 10px; /*顶部填充*/
padding-left: 20rpx;
}
.swiper-adv {
color: #e02e24;
height: 25px;/*设置高度 */
width: 200px; /*设置宽度*/
display: inline-block; /*行内块级*/
}
/* 热销商品 */
.hot-prod {
white-space: nowrap;/*不换行*/
}
.hot-prod .row-item{
display: inline-block;
padding:10rpx 35rpx;
text-align: center;
}
.hot-prod .row-item image {
display: block; /*要设置宽高,就必须是块级元素*/
margin: 10rpx auto; /*图片水平对齐,上下边距10rpx*/
width: 80rpx;
height: 80rpx;
}
/*进度条跟随*/
.prod-progress{
padding: 15px;
}
.progress-outer,.progress-inner{
width: 35%;
margin: auto;
background-color: #ddd;
height: 4px;
border-radius: 5px;
}
.progress-inner{
background-color:#e02e24;
width: 50%;
}
/*商品列表*/
.goods-container {/*外层容器*/
padding: 15rpx;
background-color: rgb(248, 248, 248);
display: flex; /*弹性盒子模型*/
justify-content: space-between;/*排列方式 均匀排列每个元素,首个元素放置于起点,末尾元素放置于终点。*/
flex-wrap: wrap; /*换行*/
}
/*商品信息*/
.goods-item {
width: 350rpx;
height: 472rpx;
background-color: #fff;
margin-bottom: 24rpx;
box-sizing: border-box;
overflow: hidden;
}
/* 商品图片 */
.goods-image{
width:350rpx;
height:350rpx;
overflow: hidden;
}
.goods-image image{
width:350rpx;
height:350rpx;
}
/*商品名称*/
.goods-title{
width: 320rpx;
/*white-space规定段落中的文本是否进行换行:nowrap文本不会换行,文本会在在同一行上显示,直到遇到 <br> 标签为止。*/
white-space: nowrap;
overflow: hidden;/*溢出的内容隐藏*/
/*text-overflow文本溢出处理:ellipsis省略号 溢出的文本用省略号...表示 注意:文本溢出处理需把溢出的内容隐藏否则省略号是不会显示的*/
text-overflow: ellipsis;
padding:15rpx 10rpx;
}
/*价格*/
.goods-price{
color: #e02e24;
padding:0rpx 10rpx;
}
js代码如下:
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
activeIndex: 0, //顶部分类 默认选中的项
hotProdMarginLeft: 0, //热销 进度条的左边距
goodscategory: [ //商品分类
'热销', '集成灶', '蒸箱烤箱', '水槽', '夏季焕新', '生鲜食品', '美妆服饰', '家居家电',
'新鲜水果', '品质家纺', '酒水饮料', '美容护肤', '家用电器', '内衣鞋袜', '休闲零食'
],
imgurls: [ //轮播图片
'/img/banner/1.jpg',
'/img/banner/2.jpg',
'/img/banner/3.jpg',
'/img/banner/4.jpg',
'/img/banner/5.jpg',
],
notice: [ //公告内容
"今年的深圳雨水比较多",
"今年夏天冰火两重天",
"今年的深圳有山竹",
],
hotprods: [ //热销图片
'/img/hotprod/1.png',
'/img/hotprod/2.png',
'/img/hotprod/3.png',
'/img/hotprod/4.png',
'/img/hotprod/5.png',
'/img/hotprod/6.png',
'/img/hotprod/7.png',
'/img/hotprod/8.png',
'/img/hotprod/9.png',
'/img/hotprod/10.png',
'/img/hotprod/11.png',
],
goods: [{
name: '御泥坊男士黑茶清爽控油矿物泥浆面膜去黑头祛痘收缩毛孔补水新品',
price: 2323,
pic: '/img/goods/1.jpg'
},
{
name: '台湾欣兰黑里透白冻膜225g竹炭清洁收缩毛孔温和去黑白头面膜',
price: 169,
pic: '/img/goods/2.jpg'
},
{
name: 'SHERO CHING',
price: 178,
pic: '/img/goods/3.jpg'
},
{
name: 'JS-sdsosodsd',
price: 545,
pic: '/img/goods/4.jpg'
},
{
name: 'asdsaasddsads',
price: 20,
pic: '/img/goods/5.jpg'
},
{
name: '3223434',
price: 199,
pic: '/img/goods/6.jpg'
},
{
name: '【两瓶420】【欣欣】佑珍提拉面膜紧致改善细纹抬头法令纹去黄',
price: 240,
pic: '/img/goods/7.jpg'
},
{
name: '韩国A. by Bom超能婴儿叶子面膜冰凝叶清凉舒缓补水保湿10片包邮',
price: 99,
pic: '/img/goods/8.jpg'
},
{
name: '手串手串手串',
price: 200,
pic: '/img/goods/9.jpg'
},
{
name: 'Bershka 女士 2017秋冬新款粉色飞行员夹克短外套 01232551622',
price: 2323,
pic: '/img/goods/10.jpg'
},
{
name: '御泥坊清爽平衡泥浆面膜清洁 男女泥膜收缩毛孔 去黑头竹炭火山泥',
price: 454,
pic: '/img/goods/11.jpg'
},
{
name: '御泥坊美白嫩肤蚕丝面膜20片男女蚕丝玻尿酸面膜补水美白淡斑保湿',
price: 444,
pic: '/img/goods/12.jpg'
},
{
name: '御泥坊蜂蜜矿物睡眠面膜免洗滋润补水保湿护肤化妆正品男女包邮',
price: 455,
pic: '/img/goods/13.jpg'
},
{
name: '御泥坊清润莹亮黑面膜控油收缩毛孔深清洁玻尿酸男女补水保湿面膜',
price: 454,
pic: '/img/goods/14.jpg'
},
{
name: '御泥坊美白嫩肤黑面膜贴女士蚕丝玻尿酸祛斑面膜补水美白淡斑保湿',
price: 3434,
pic: '/img/goods/15.jpg'
},
{
name: '御泥坊人参矿物蚕丝面膜7片*3盒 紧致抗皱去皱淡化细纹补水正品女',
price: 5454,
pic: '/img/goods/16.jpg'
},
{
name: '御泥坊玫瑰滋养睡眠面膜免洗补水保湿护肤品夜间男女学生收缩毛孔',
price: 344,
pic: '/img/goods/17.jpg'
},
{
name: '御泥坊红豆薏米面膜100g提亮肤色改善暗黄红润调理泥膜正品女包邮',
price: 545,
pic: '/img/goods/18.jpg'
},
{
name: '御泥坊旗舰店葡萄籽美白淡斑祛黄黑面膜套21片 清洁补水保湿嫩肤',
price: 434,
pic: '/img/goods/19.jpg'
},
{
name: '御泥坊水润黑白面膜贴补水保湿收缩毛孔亮肤玻尿酸护肤套装正品女',
price: 69,
pic: '/img/goods/20.jpg'
},
{
name: '御泥坊小迷糊阿狸加菲猫玻尿酸秋季补水保湿亮肤黑面膜花瑶花男女',
price: 233,
pic: '/img/goods/21.jpg'
},
]
},
//商品分类点击事件
setActiveIndex(e) { //点击分类获取商品分类列表中对应的下标激活类active渲染样式
// console.log(e)
this.setData({
activeIndex: e.currentTarget.dataset.index //获取下标
});
},
//监听热销商品滚动
hotProdScroll(e) {
// console.log(e)
//更新数据
this.setData({
hotProdMarginLeft: e.detail.scrollLeft //获取滚动的距离用于推动进度条
});
}
})
首页主要使用了scroll-view、swiper 这两个微信源生组件和弹性盒子实现布局,代码都有注释就不过多的讲解了
分类页面
分类页与首页差不多都是使用scroll-view微信源生组件和弹性盒子,分类页不同的是左侧边栏与商品列表需要获取手机的窗体高度来固定它们高度来实现上下滑动效果,实现思路如下:
使用弹性模型布局,左侧栏的宽度固定,右边商品列表占满剩余的。
左侧栏使用scroll-view,可以进行上下滑动。
scroll-view的高度需要计算 手机屏幕的高度-搜索框的高度
屏幕高度= wx.getSystemInfoSync().windowHeight 获得,wx.getSystemInfoSync()函数作用是获取手机系统信息,可以通过此函数知道手机屏幕的宽高等信息
搜索框的高度,通过boundingClientRect()获得,boundingClientRect()函数作用是获取元素的高度,在这里我们获取的是搜索框的高度
下面我们来看看分类页的实现代码,wxml代码如下:
<view class='page-goods'>
<!-- 商品搜索 -->
<view class='goods-search-container'>
<navigator class='search-box' bindtap='searchs'>
<image src='/img/search.png'></image>
搜索商品
</navigator>
</view>
</view>
<!-- 商品分类 -->
<view class='goods-type-container'>
<!-- 侧边栏,商品分类 -->
<view class='goods-sider-bar'>
<!--
scroll-y纵向滑动
style='height:{{leftHeight}}px 加载时通过wx.getSystemInfoSync()方法获取手机窗体的高度
-->
<scroll-view class='goods-scroll' scroll-y style='height:{{leftHeight}}px'>
<!--
{{activeIndex==index?"active":""}}动态添加类active 通过setActiveIndex点击事件获取选中项的下标赋值给activeIndex,选中的下标等于循环的下标
则给这个对应下标的商品类添加一个类active,然后渲染active类的样式
-->
<view bindtap='setActiveIndex' data-index='{{index}}' class='goods-type-item {{activeIndex==index?"active":""}}' wx:for='{{protype}}'> {{item}}
</view>
</scroll-view>
</view>
<!-- 商品列表 -->
<!-- 商品列表,需要固定高度才能上下滑动 -->
<scroll-view class='goods-list' scroll-y style='height:{{leftHeight}}px'>
<view class='goods-list-item' wx:for='{{goods}}'>
<!-- 商品图标 -->
<view class='goods-image'>
<image src='{{item.pic}}'></image>
</view>
<!-- 商品信息 -->
<view class='goods-info'>
<view class='title'>{{item.name}}</view>
<view class='price'>已售
<text style='color:red;'>0</text> 件
</view>
<view>
<text style='color:red;font-weight:bold;'>¥{{item.price}}</text>
<text class='original-price'>¥299</text>
</view>
</view>
</view>
</scroll-view>
</view>
wxss样式代码如下:
/*商品搜索*/
.goods-search-container{
background-color: #eee;
padding: 20rpx;
border-bottom: 1px solid rgb(197, 195, 195);
}
.search-box {
border: 1px solid #ddd;
background-color: #fff;
height: 70rpx;
line-height: 70rpx;
border-radius: 5px;
text-align: center;
}
.search-box image{
width: 40rpx;
height: 40rpx;
vertical-align: middle;
}
/* 商品分类 */
.goods-type-container{/*外层容器*/
display: flex;
}
.goods-sider-bar{/*左侧分类*/
background-color: #eee;
width: 200rpx;
overflow: hidden;
}
.goods-list{/*右侧商品列表*/
background-color: #fff;
flex: 1; /*剩下的占满*/
padding: 20rpx;
}
.goods-type-item {
padding: 25rpx 20rpx;
}
.goods-type-item.active{
background-color: #fff;
color: red;
}
/*商品列表信息*/
.goods-list {
background-color: #fff;
flex: 1; /*剩下的占满*/
padding: 20rpx;
overflow: hidden;
box-sizing: border-box;
}
/* 右侧产品列表 */
.goods-list-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
/* 产品图片容器 */
.goods-list-item .goods-image {
width: 200rpx;
height: 200rpx;
line-height: 200rpx;
}
/* 产品图片 */
.goods-list-item .goods-image image {
width: 100%;
height: 100%;
}
/* 产品右侧的描述容器 */
.goods-list-item .goods-info {
flex: 1;/*剩下的填满*/
margin-left: 10px;
position: relative;
overflow: hidden;
border-bottom: 1px solid #ddd;
}
/* 描述下边的横线 */
/* .goods-list-item .goods-info::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 10px;
height: 1px;
background-color: #ddd;
width: 100%;
} */
/* 描述信息的上下间距 */
.goods-info view {
padding: 3px 0;
}
/* 产品名称,超出部分使用 ...表示 */
.goods-info .title {
font-size: 30rpx;
width: 250rpx;
white-space: nowrap;/*文本字段不换行*/
overflow: hidden;/*溢出的文本右侧*/
text-overflow: ellipsis;/*溢出的文本用...省略号表示*/
}
.goods-info .price {
font-size: 24rpx;
}
/* 原价的样式,有删除线 */
.original-price {
color: #777;
font-size: 20rpx;
padding-left: 20rpx;
text-decoration: line-through;/*text-decoration文本装饰:line-through中划线*/
}
样式中值得注意的是对文本的处理
/* 产品名称,超出部分使用 ...表示 */
.goods-info .title {
font-size: 30rpx;
width: 250rpx;
white-space: nowrap;/*文本字段不换行*/
overflow: hidden;/*溢出的文本右侧*/
text-overflow: ellipsis;/*溢出的文本用...省略号表示*/
}
/* 原价的样式,有删除线 */
.original-price {
color: #777;
font-size: 20rpx;
padding-left: 20rpx;
text-decoration: line-through;/*text-decoration文本装饰:line-through中划线*/
}
js代码如下:
// pages/classify/classify.js
Page({
/**
* 页面的初始数据
*/
data: {
protype: [ //类别名称
'热销', '集成灶', '蒸箱烤箱', '水槽', '夏季焕新', '生鲜食品', '美妆服饰', '家居家电', '新鲜水果',
'品质家纺', '酒水饮料', '美容护肤', '家用电器', '内衣鞋袜', '休闲零食'
],
activeIndex: 0, //激活列表
windowHeight: 0, //窗体的高度
leftHeight: 0, //除了搜索框剩余的高度
goods: [{//商品信息
name: '御泥坊男士黑茶清爽控油矿物泥浆面膜去黑头祛痘收缩毛孔补水新品',
price: 2323,
pic: '/img/goods/1.jpg'
},
{
name: '台湾欣兰黑里透白冻膜225g竹炭清洁收缩毛孔温和去黑白头面膜',
price: 169,
pic: '/img/goods/2.jpg'
},
{
name: 'SHERO CHING',
price: 178,
pic: '/img/goods/3.jpg'
},
{
name: 'JS-sdsosodsd',
price: 545,
pic: '/img/goods/4.jpg'
},
{
name: 'asdsaasddsads',
price: 20,
pic: '/img/goods/5.jpg'
},
{
name: '3223434',
price: 199,
pic: '/img/goods/6.jpg'
},
{
name: '【两瓶420】【欣欣】佑珍提拉面膜紧致改善细纹抬头法令纹去黄',
price: 240,
pic: '/img/goods/7.jpg'
},
{
name: '韩国A. by Bom超能婴儿叶子面膜冰凝叶清凉舒缓补水保湿10片包邮',
price: 99,
pic: '/img/goods/8.jpg'
},
{
name: '手串手串手串',
price: 200,
pic: '/img/goods/9.jpg'
},
{
name: 'Bershka 女士 2017秋冬新款粉色飞行员夹克短外套 01232551622',
price: 2323,
pic: '/img/goods/10.jpg'
},
{
name: '御泥坊清爽平衡泥浆面膜清洁 男女泥膜收缩毛孔 去黑头竹炭火山泥',
price: 454,
pic: '/img/goods/11.jpg'
},
{
name: '御泥坊美白嫩肤蚕丝面膜20片男女蚕丝玻尿酸面膜补水美白淡斑保湿',
price: 444,
pic: '/img/goods/12.jpg'
},
{
name: '御泥坊蜂蜜矿物睡眠面膜免洗滋润补水保湿护肤化妆正品男女包邮',
price: 455,
pic: '/img/goods/13.jpg'
},
{
name: '御泥坊清润莹亮黑面膜控油收缩毛孔深清洁玻尿酸男女补水保湿面膜',
price: 454,
pic: '/img/goods/14.jpg'
},
{
name: '御泥坊美白嫩肤黑面膜贴女士蚕丝玻尿酸祛斑面膜补水美白淡斑保湿',
price: 3434,
pic: '/img/goods/15.jpg'
},
{
name: '御泥坊人参矿物蚕丝面膜7片*3盒 紧致抗皱去皱淡化细纹补水正品女',
price: 5454,
pic: '/img/goods/16.jpg'
},
{
name: '御泥坊玫瑰滋养睡眠面膜免洗补水保湿护肤品夜间男女学生收缩毛孔',
price: 344,
pic: '/img/goods/17.jpg'
},
{
name: '御泥坊红豆薏米面膜100g提亮肤色改善暗黄红润调理泥膜正品女包邮',
price: 545,
pic: '/img/goods/18.jpg'
},
{
name: '御泥坊旗舰店葡萄籽美白淡斑祛黄黑面膜套21片 清洁补水保湿嫩肤',
price: 434,
pic: '/img/goods/19.jpg'
},
{
name: '御泥坊水润黑白面膜贴补水保湿收缩毛孔亮肤玻尿酸护肤套装正品女',
price: 69,
pic: '/img/goods/20.jpg'
},
{
name: '御泥坊小迷糊阿狸加菲猫玻尿酸秋季补水保湿亮肤黑面膜花瑶花男女',
price: 233,
pic: '/img/goods/21.jpg'
},
],
},
//左侧分类点击事件
setActiveIndex(e) {//点击激活选中项active类
console.log(e.target.dataset.index)
this.setData({
activeIndex: e.target.dataset.index//获取选中项的下标
});
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
//加载时获取手机系统信息
var sysInfo = wx.getSystemInfoSync()
//获取屏幕高度,导航栏和底部工具之间的高度
var _wHeight = sysInfo.windowHeight;
//创建查询器
var query = wx.createSelectorQuery();
//查询元素
var searchBox = query.select('.goods-search-container');//查找搜索框
var that = this;
searchBox.boundingClientRect(function(rect) {//获取元素高度
// console.log(rect)
//获取搜索框的高度
var height = rect.height;
that.setData({
windowHeight: _wHeight, // windowHeight 手机窗体的高度
leftHeight: _wHeight - height, //左侧分类列表高度=手机窗体的高度-搜索框的高度
})
}).exec();
},
searchs(){
wx.navigateTo({//导航到搜索页
url: '/pages/search/search',
success: function(res) {},
fail: function(res) {},
complete: function(res) {},
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
}
})
代码都有注释就不过多讲解了,详细请看代码。
购物车页面实现思路需先做滑动删除,做好之后就在这个基础之上布局展示商品信息
先看一波滑动删除实现代码:
<!--wxml代码-->
<!-- 滑动删除 -->
<view class="container">
<view class="touch-item {{item.isTouchMove ? 'touch-move-active' : ''}}" data-index="{{index}}" bindtouchstart="touchstart" bindtouchmove="touchmove" wx:for="{{goods}}" wx:key="*this" bindtap='radios' data-index='{{index}}'>
<view class="content">
这里是为自定义内容
</view>
<view class="del" catchtap="del" data-index="{{index}}">删除</view>
</view>
</view>
/*wxss样式代码*/
.touch-item {
font-size: 14px;
display: flex;
justify-content: space-between;
border-bottom:1px solid #ccc;
width: 100%;
overflow: hidden
}
.content {
width: 100%;
/* padding: 10px; */
/* line-height: 22px; */
margin-right:0;
-webkit-transition: all 0.4s;
transition: all 0.4s;
-webkit-transform: translateX(90px);
transform: translateX(90px);
margin-left: -90px
}
.del {
background-color: rgb(255, 0, 0);
width: 90px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
-webkit-transform: translateX(90px);
transform: translateX(90px);
-webkit-transition: all 0.4s;
transition: all 0.4s;
}
.touch-move-active .content,
.touch-move-active .del {
-webkit-transform: translateX(0);
transform: translateX(0);
}
//js代码
var app = getApp()
Page({
data: {
items: [
// id 编号 src 图片路径 title 商品标题 price 价格 num 数量 isTouchMove 控制滑动删除 ishow控制单选的
{
id: 1,
src: "http://47.107.35.195:8012/goods/1.jpg",
title: "御泥坊男士黑茶清爽控油矿物泥浆面膜去黑头祛痘收缩毛孔补水新品",
price: 299,
num: 1,
isTouchMove: false,
ishow: true,
}
],
startX: 0, //开始x轴坐标
startY: 0, //开始y轴坐标
},
onLoad() {
},
//手指触摸动作开始 记录起点X坐标
touchstart(e) {
//开始触摸时 重置所有删除
this.data.items.forEach((v, i)=> {
if (v.isTouchMove) //只操作为true的
v.isTouchMove = false;
})
this.setData({
startX: e.changedTouches[0].clientX, //得到触摸时x轴的坐标
startY: e.changedTouches[0].clientY, //得到触摸时y轴的坐标
items: this.data.items
})
},
//滑动事件处理
touchmove(e) {
var that = this,
index = e.currentTarget.dataset.index, //当前索引得到下标
startX = that.data.startX, //开始X坐标
startY = that.data.startY, //开始Y坐标
touchMoveX = e.changedTouches[0].clientX, //滑动变化坐标
touchMoveY = e.changedTouches[0].clientY, //滑动变化坐标
//获取滑动角度
angle = that.angle({
X: startX,
Y: startY
}, {
X: touchMoveX,
Y: touchMoveY
});
that.data.items.forEach((v, i)=> {
v.isTouchMove = false
//滑动超过30度角 return
if (Math.abs(angle) > 30) return;
if (i == index) {
if (touchMoveX > startX) //右滑
v.isTouchMove = false
else //左滑
v.isTouchMove = true
}
})
//更新数据
that.setData({
items: that.data.items
})
},
/**
* 计算滑动角度
* @param {Object} start 起点坐标
* @param {Object} end 终点坐标
*/
angle(start, end) {
var _X = end.X - start.X,
_Y = end.Y - start.Y
//返回角度 /Math.atan()返回数字的反正切值
return 360 * Math.atan(_Y / _X) / (2 * Math.PI);
},
//删除事件
del(e) {
this.data.items.splice(e.currentTarget.dataset.index, 1)
this.setData({
items: this.data.items,
})
}
})
这样我们的滑动删除效果就做好了,现在我们来说下整体实现的一个思路。布局整体就是外层一个大容器内层俩个子元素,一个子元素是定义内容的,另一个子元素就是删除,中间的内容自定义。布局样式我们就不多说了,需要什么样的布局自己定义。
第一步使用bindtouchstart开始触摸事件
首先我们在data中定义俩个数据,初始值为0。代表触摸点的X轴和Y轴坐标。
通过这个事件我们可以得到手指触摸点的坐标。
第二步使用bindtouchmove开始滑动事件
首先我们需要声明俩个变量接收移动时的坐标touchMoveX和touchMoveY
通过这个事件我们可以得到时实时变化滑动的X轴和Y轴坐标
第三步获取滑动的角度
通过angle({},{})方法得到滑动的角度
第四步循环遍历判断左滑还是右滑
使用数组的forEach()循环把isTouchMove赋值为false 再判断滑动角度大于30度就直接返回,再判断下标是否相同相同再判断滑动的坐标是否大于触摸点的坐标 大于则代表右滑动把删除溢出处理掉 小于则左滑把删除推出来,再使用setData({})更新数据。
第五步计算滑动角度返回角度
使用angle()方法返回角度
angle(start, end) {
var _X = end.X - start.X,
_Y = end.Y - start.Y
//返回角度 /Math.atan()返回数字的反正切值
return 360 * Math.atan(_Y / _X) / (2 * Math.PI);
},
注意:滑动主要是通过三元表达式添加不同的样式 改变是否偏移90px
删除就比较简单了,通过自定义属性传下标回去,根据下标得到对象再使用数组的splice()的方法删除。
滑动删除做好之后就在这个基础之上布局成展示商品信息的页面。代码如下:
<!-- 顶部 -->
<view class="weui-cell weui-cell-top">
<view class="weui-cell__bd">购物车</view>
<view class="weui-cell__ft">{{goods.length}}件</view>
</view>
<!-- 商品列表 -->
<view class="container">
<view wx:if='{{goods.length==0}}' style='color:red;text-align: center;line-height:80rpx;'>您还没有购买商品哦!快去购物吧!</view>
<view class="touch-item {{item.isTouchMove ? 'touch-move-active' : ''}}" data-index="{{index}}" bindtouchstart="touchstart" bindtouchmove="touchmove" wx:for="{{goods}}" wx:key="*this" bindtap='radios' data-index='{{index}}'>
<view class="content">
<!-- {{item.content}} -->
<view class='flex'>
<view class='flex-item1'>
<!-- 单选按钮 -->
<image src='{{item.checkbox?"/img/gou.png":"/img/gou-red.png"}}' class='radio'></image>
<!-- 商品图片 -->
<image src='{{item.goodsImg}}' class='goods-img'></image>
</view>
<view class='flex-item2'>
<!-- 商品标题 -->
<view class='goods-name'>{{item.goodsName}}</view>
<view>
<!-- 商品单价 -->
<text class='price'>¥{{item.price}}</text>
<view class='count'>
<text class='minus' catchtap='minus' data-index='{{index}}'>-</text>
<input type='text' value='{{item.numbers}}' disabled='true'></input>
<text class='plus' catchtap='plus' data-index='{{index}}'>+</text>
</view>
</view>
</view>
</view>
</view>
<view class="del" catchtap="del" data-index="{{index}}">删除</view>
</view>
</view>
<view style='height:90rpx;'></view>
<!-- 底部结算 -->
<view class='bottom'>
<view class="weui-cell">
<view class="weui-cell__hd" catchtap='checkAll'>
<image src='{{checkAll?"/img/gou.png":"/img/gou-red.png"}}'></image>全选
</view>
<view class="weui-cell__bd" style='text-align:center;color:red;'>合计:¥{{money}}</view>
<view class="weui-cell__ft">
<button type='warn' disabled='{{money<=0?true:null}}' bindtap='accounts'>结算</button>
</view>
</view>
</view>
wxss样式代码如下:
/*顶部*/
.weui-cell.weui-cell-top {
background-color: rgba(204, 204, 204, 0.418);
}
/*底部结算*/
.bottom {
position: fixed;
bottom: 0rpx;
width: 100%;
background-color: #f7f7fa;
}
.bottom .weui-cell {
padding: 0px;
line-height: 90rpx;
}
.bottom image {
width: 40rpx;
height: 40rpx;
vertical-align: middle;
margin-top: -10rpx;
margin:0rpx 20rpx;
}
.bottom .weui-cell__ft {
height: 100%;
width: 160rpx;
text-align: center;
}
.bottom .weui-cell__ft button{
line-height: 90rpx;
}
/*滑动删除*/
.touch-item {
font-size: 14px;
display: flex;
justify-content: space-between;
border-bottom:1px solid #ccc;
width: 100%;
overflow: hidden
}
.content {
width: 100%;
/* padding: 10px; */
/* line-height: 22px; */
margin-right:0;
-webkit-transition: all 0.4s;
transition: all 0.4s;
-webkit-transform: translateX(90px);
transform: translateX(90px);
margin-left: -90px
}
.del {
background-color: rgb(255, 0, 0);
width: 90px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
-webkit-transform: translateX(90px);
transform: translateX(90px);
-webkit-transition: all 0.4s;
transition: all 0.4s;
}
.touch-move-active .content,
.touch-move-active .del {
-webkit-transform: translateX(0);
transform: translateX(0);
}
.flex{
display: flex;
}
.flex-item1{
width:250rpx;
}
.flex-item1 .radio{
width: 40rpx;
height: 40rpx;
margin:0rpx 20rpx 60rpx 20rpx;
}
.flex-item1 .goods-img{
width: 150rpx;
height: 150rpx;
margin-top: 14rpx;
border-radius:10rpx;
}
.flex-item2{
flex: 1;
}
.flex-item2 .goods-name{
width: 360rpx;
line-height: 60rpx;
margin: 20rpx 0rpx;
font-size: 30rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.flex-item2 .count{
display: inline-block;
float: right;
margin-right: 30rpx;
}
.flex-item2 .count text{
display: inline-block;
width: 52rpx;
line-height: 52rpx;
border: 1rpx solid #ccc;
text-align: center;
}
.flex-item2 .minus{
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
.flex-item2 .plus{
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.flex-item2 .count input{
width: 50rpx;
border-top: 1rpx solid #ccc;
border-bottom: 1rpx solid #ccc;
text-align: center;
vertical-align: middle;
position: relative;
top: -1rpx;
}
js代码如下:
var app = getApp()
Page({
data: {
goods: [{
//商品图片
goodsImg: '/img/goods/1.jpg',
//商品名称
goodsName: '阿萨姆奶茶',
//商品单价
price: '666',
//商品数量
numbers: 1,
//默认全隐藏删除
isTouchMove: false,
//商品是否被选中,true为未选中
checkbox: true,
},
{
goodsImg: '/img/goods/2.jpg',
goodsName: '阿萨姆奶茶',
price: '888',
numbers: 1,
isTouchMove: false,
checkbox: true,
},
{
goodsImg: '/img/goods/3.jpg',
goodsName: '阿萨姆奶茶',
price: '999',
numbers: 1,
isTouchMove: false,
checkbox: true,
},
{
goodsImg: '/img/goods/4.jpg',
goodsName: '阿萨姆奶茶',
price: '777',
numbers: 1,
isTouchMove: false,
checkbox: true,
},
{
goodsImg: '/img/goods/5.jpg',
goodsName: '阿萨姆奶茶',
price: '555',
numbers: 1,
isTouchMove: false,
checkbox: true,
},
],
startX: 0, //开始坐标
startY: 0,
//是否全选,true为不全选
checkAll: true,
//总金额
money:0
},
//增加数量
plus(e) { //通过自定义属性传下标找到商品,对该商品数量进行累加
// console.log(e)
this.data.goods[e.currentTarget.dataset.index].numbers++ //商品数量++
this.setData({
goods: this.data.goods //重新渲染数据
})
this.account()
// console.log(this.data.goods[e.currentTarget.dataset.index].numbers)
},
//减少数量
minus(e) { //通过自定义属性传下标找到商品,每点击一次对该商品数量-1
// console.log(e)
if (this.data.goods[e.currentTarget.dataset.index].numbers > 1) { //判断该商品购买的数量是否大于1,大于则执行数量--操作,否则不执行
this.data.goods[e.currentTarget.dataset.index].numbers-- //商品数量--
this.setData({
goods: this.data.goods //重新渲染数据
})
this.account()
// console.log(this.data.goods[e.currentTarget.dataset.index].numbers)
}
},
//是否勾选商品
radios(e) {//点击是否选中商品,全部商品选中则全选框被激活
// console.log(e)
//将点击商品的复选框值赋值给变量checkbox方便操作
var checkbox = this.data.goods[e.currentTarget.dataset.index].checkbox;
//三元表达式 checkbox值为true则点击后值为false为选中,否则不被选中
checkbox ? checkbox = false : checkbox = true;
//将变量checkbox重新赋值回来
this.data.goods[e.currentTarget.dataset.index].checkbox = checkbox;
//find方法查找商品列表中是否有商品未被选中,有则返回找到的第一个商品对象,没有则返回undefined,然后将返回的结构赋值给变量obj
var obj = this.data.goods.find(object => object.checkbox == true);
//三元表达式 如果obj为对象则全选框不会选中,否则全选框选中
obj ? this.data.checkAll = true : this.data.checkAll = false;
// console.log(this.data.checkAll)
//更新数据
this.setData({
checkAll: this.data.checkAll,
goods: this.data.goods
})
this.account()
},
//是否全选
checkAll() {
if (this.data.checkAll) {
//全选框激活
this.data.checkAll = false;
//循环选中所有商品
for (var i = 0; i < (this.data.goods).length; i++) {
this.data.goods[i].checkbox = false;
}
} else {
//全选框不激活
this.data.checkAll = true;
//循环所有商品不被选中
for (var i = 0; i < (this.data.goods).length; i++) {
this.data.goods[i].checkbox = true;
}
}
this.setData({ //更新数据
checkAll: this.data.checkAll,
goods: this.data.goods
})
this.account()
},
//合计总金额
account(){
var moneys=0;//总金额
//循环商品列表
for (var i = 0; i < (this.data.goods).length; i++){
//找出选中的商品 将选中的商品金额累加
if (this.data.goods[i].checkbox==false){
//总金额+=商品单价*数量
moneys += this.data.goods[i].price * this.data.goods[i].numbers
}
}
this.setData({ //更新数据
money: this.data.money = moneys
})
// console.log(this.data.money)
},
//点击结算
accounts(){
var that=this;
//filter()过滤器返回新数组 过滤掉选中的商品留下未被选中的为一新数组
that.data.goods = that.data.goods.filter((obj,index)=>obj.checkbox==true);
if (that.data.goods.length == 0) {//判断商品列表中是否有商品,没有则全选框不勾
that.data.checkAll = true
}
that.account();
//更新数据
that.setData({
goods: that.data.goods,
checkAll: that.data.checkAll
})
},
onLoad: function() {
},
//手指触摸动作开始 记录起点X坐标
touchstart: function(e) {
//开始触摸时 重置所有删除
this.data.goods.forEach(function(v, i) {
if (v.isTouchMove) //只操作为true的
v.isTouchMove = false;
})
this.setData({
startX: e.changedTouches[0].clientX,
startY: e.changedTouches[0].clientY,
goods: this.data.goods
})
},
//滑动事件处理
touchmove: function(e) {
var that = this,
index = e.currentTarget.dataset.index, //当前索引
startX = that.data.startX, //开始X坐标
startY = that.data.startY, //开始Y坐标
touchMoveX = e.changedTouches[0].clientX, //滑动变化坐标
touchMoveY = e.changedTouches[0].clientY, //滑动变化坐标
//获取滑动角度
angle = that.angle({
X: startX,
Y: startY
}, {
X: touchMoveX,
Y: touchMoveY
});
that.data.goods.forEach(function(v, i) {
v.isTouchMove = false
//滑动超过30度角 return
if (Math.abs(angle) > 30) return;
if (i == index) {
if (touchMoveX > startX) //右滑
v.isTouchMove = false
else //左滑
v.isTouchMove = true
}
})
//更新数据
that.setData({
goods: that.data.goods
})
},
/**
* 计算滑动角度
* @param {Object} start 起点坐标
* @param {Object} end 终点坐标
*/
angle: function(start, end) {
var _X = end.X - start.X,
_Y = end.Y - start.Y
//返回角度 /Math.atan()返回数字的反正切值
return 360 * Math.atan(_Y / _X) / (2 * Math.PI);
},
//删除事件
del: function (e) {
// debugger
this.data.goods.splice(e.currentTarget.dataset.index, 1)//根据下标删除商品
//find方法查找商品列表中是否有商品未被选中,有则返回找到的第一个商品对象,没有则返回undefined
var obj = this.data.goods.find(object => object.checkbox == true);
// console.log(obj)
//三元表达式 如果find方法找到有未被选中的商品则全选框选中,否则全选框未选中
obj ? this.data.checkAll = true : this.data.checkAll = false;
if (this.data.goods.length == 0) {//判断商品列表中是否有商品,没有则全选框不勾
this.data.checkAll = true
}
this.setData({//更新数据
checkAll: this.data.checkAll,
goods: this.data.goods
})
this.account()//调用结算方法
}
})
以上便是购物车的全部代码,js逻辑处理代码中都有注释,详细看代码
个人中心页
个人中心页就比较简单了,都是一些简单的布局就不过多讲解,布局代码如下:
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<!-- <view class='order-form'>
<text>我的订单</text>
<view class='order-form-right'>
全部订单
<image src='/img/more.png'></image>
</view>
</view> -->
<!-- 订单 -->
<view class='my-order'>
<view class="weui-cells weui-cells_after-title">
<navigator class="weui-cell">
<view class="weui-cell__bd">我的订单</view>
<view class="weui-cell__ft orders">全部订单
<image class='img-more' src="/img/more.png"></image>
</view>
</navigator>
</view>
<!-- 订单列表,采用弹性模型布局 -->
<view class="weui-flex order-list">
<view class="weui-flex__item">
<image src="/img/order/fukuan.png"></image>
<view class="placeholder">待付款</view>
</view>
<view class="weui-flex__item">
<image src='/img/order/fahuo.png'></image>
<view class="placeholder">待发货</view>
</view>
<view class="weui-flex__item">
<image src='/img/order/shouhuo.png'></image>
<view class="placeholder">待收获</view>
</view>
<view class="weui-flex__item">
<image src='/img/order/pingjia.png'></image>
<view class="placeholder">待评价</view>
</view>
<view class="weui-flex__item">
<image src='/img/order/wancheng.png'></image>
<view class="placeholder">已完成</view>
</view>
</view>
</view>
<!-- 分割线 -->
<view class='divider'></view>
<!-- 功能列表 -->
<view class='func-list'>
<view class="weui-cells weui-cells_after-title">
<navigator class="weui-cell">
<view class="weui-cell__hd">
<image class='icon' src="/img/my-info/myquan.png"></image>
</view>
<view class="weui-cell__bd" style='text-align:left;'>我的优惠券</view>
</navigator>
<navigator class="weui-cell">
<view class="weui-cell__hd">
<image class='icon' src="/img/my-info/tuikuan.png"></image>
</view>
<view class="weui-cell__bd" style='text-align:left;'>退款/售后</view>
</navigator>
<navigator class="weui-cell">
<view class="weui-cell__hd">
<image class='icon' src="/img/my-info/dizhi.png"></image>
</view>
<view class="weui-cell__bd" style='text-align:left;'>收货地址</view>
</navigator>
<navigator class="weui-cell">
<view class="weui-cell__hd">
<image class='icon' src="/img/my-info/kefu.png"></image>
</view>
<view class="weui-cell__bd" style='text-align:left;'>官方客服</view>
</navigator>
<navigator class="weui-cell">
<view class="weui-cell__hd">
<image class='icon' src="/img/my-info/shezhi.png"></image>
</view>
<view class="weui-cell__bd" style='text-align:left;'>设置</view>
</navigator>
<navigator class="weui-cell">
<view class="weui-cell__bd" style='text-align:left;'>关于我们</view>
<view class="weui-cell__ft weui-cell__ft_in-access"></view>
</navigator>
</view>
</view>
wxss样式如下:
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 0px;
background-color: #dc143c;
}
/*用户头像*/
.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
/*用户昵称*/
.userinfo-nickname {
color: #fff;
}
/*订单*/
/*
.order-form {
line-height: 50rpx;
padding: 20rpx;
border-bottom: 1px solid #ccc;
}
.order-form-right {
display: inline-block;
float: right;
color: red;
}
.order-form-right image{
width: 40rpx;
height: 40rpx;
vertical-align: middle;
} */
/* [全部订单] 文字为红色 */
.orders {
color: #e02e24; /*红色*/
}
/* 更多图标 */
.orders .img-more {
width: 35rpx;
height: 35rpx;
position: relative;
top: 2px; /*图标要往下移一点,才能与文字对齐*/
}
.order-list .weui-flex__item {
text-align: center;
font-size: 30rpx !important;
}
/* 图片水平居中对齐 */
.order-list .weui-flex__item image {
display: block; /*要设置宽高,就必须是块级元素*/
margin: 10rpx auto; /*图片水平对齐,上下边距10rpx*/
width: 40rpx;
height: 40rpx;
}
/* 分割线 */
.divider {
background-color: #eee;
height: 20rpx;
/* line-height: 30rpx; */
}
.func-list .icon {
max-width: 40rpx !important;
max-height: 40rpx !important;
margin-right: 8rpx;
vertical-align: middle;
}
/* 覆盖默认样式,增加高度 */
.func-list .weui-cell{
padding: 12px 15px;
}
/* 覆盖list组件的默认样式 */
.func-list .weui-cell::before{
left: 0;
border-top-color: #eee;
}
js代码如下:
// pages/my/my.js
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
userInfo: {},//用户信息
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')//是否兼容API
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// console.log(app.globalData)
console.log(this.data.canIUse)
if (app.globalData.userInfo) {//判断用户信息是否为null 不为空
this.setData({
userInfo: app.globalData.userInfo,//将用户信息赋给userInfo渲染用户信息
hasUserInfo: true
})
} else if (this.data.canIUse) {//是否兼容API
console.log(666)
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
console.log(res)
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function (e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
},
})
总结知识点:
1. <scroll-view></scroll-view>滚动视图组件的使用
2. <swiper></swiper>轮播组件的使用
3. 弹性盒子的使用
4. wx.createSelectorQuery().select('.top').boundingClientRect((res) => { console.log(res)}).exec(); api的使用 获取元素信息
5. wx.getSystemInfo({success: function(res) {},fail: function(res) {},complete: function(res) {},}) api的使用 获取用户手机窗体高度
6. bindtouchstart 开始触摸事件的使用
7. bindtouchmove 开始滑动事件的使用
8. bindtap 点击事件的使用和catchtap点击事件阻止冒泡
9. data-自定义名称 自定义属性的使用
10. 数组find()和filter()方法的使用
11. wx.navigateTo({url: '路径',success: function(res) {},fail: function(res) {},complete: function(res) {},}) 跳转页面的方法