目录
1 分类
1.1 接口
1.2 窗口限制
1.3 选中状态样式判断
1.4 点击左侧时右侧会到顶点
1.5 源码
2 购物车
2.1 store
2.2 tabBar徽标
2.3 滑动删除
2.4 结算
2.4.1 结算前登录
2.4.2 结算功能
2.5 触发组件事件
2.6 源码
1 分类
分类最上部是搜索区域,与首页的搜索区域相同,这里直接复用
右侧区域的内容 会根据点击不同左侧区域 而变化
左侧区域和右侧区域均可以向下滚动
滚动后切换到别的页面,然后再切换回来,滚动的位置不变(记录位置是scroll-view的默认效果,不用额外进行修改)
1.1 接口
接口中包含三级标题。紫色框子是一级标题,橙色框子是二级标题,绿色框子是三级标题
url是https://api-hmugo-web.itheima.net/api/public/v1/categories 直接发get请求就行了
1.2 窗口限制
分类页面的最外层只有两个部分,一个是搜索框,除了搜索框剩下都属于另外一部分。为了让左侧和右侧都有拖动了效果,所以这里对高度进行了限制
如果取消高度限制,页面中的scroll-view-container就废了,左侧和右侧就会同时进行拖动
限制高度的值根据机型进行调节,wx.getWindowInfo()可以获得当前机型的高度,减50是减去屏幕最下方的tarbar高度。高度在页面初次加载的时候就进行计算
1.3 选中状态样式判断
左侧列表被选中后会有这个红色的部分
每次点击左侧列表会记录left_active_num,讲left_active_num与循环时的index进行比对,如果相同的多给一个active的类名
1.4 点击左侧时右侧会到顶点
每一次点击左侧的时候,右侧的内容是不同的,但实际上元素都是一个。所以点击左侧时,右侧不会默认回到顶点
这里用变量的原因是给0这个常量第二次就懒加载了,只能点击左侧之后每次都赋值一个新的0
1.5 源码
wxml
<!--pages/category/category.wxml-->
<black_horse_search></black_horse_search>
<view class="scroll-view-container" style="height:{{windowHeight}};">
<!-- <view class="scroll-view-container" style="height:200px;"> --><!-- 左侧滚动区域 --><scroll-view class="left-scroll-view" scroll-y="true"><view wx:for="{{cateList}}" wx:key="cat_id" class="{{['scroll-view-item',index==left_active_num?'active':'']}}" bindtap="left_scroll_click" data-click_item_index="{{index}}">{{item.cat_name}}</view></scroll-view><!-- 右侧滚动区域 --><!-- 老版本的scroll-top不能给相同的值,也就是你一直给0是不行的 --><scroll-view class="right-scroll-view" scroll-y="true" scroll-top="{{right_scrollTop}}"><view class="cate-lv2" wx:for="{{cateLevel2}}" wx:key="index"><view class="cate-lv2-title">/ {{item.cat_name}} /</view><view class="cate-lv3-list"><view class="cate-lv3-item" wx:for="{{item.children}}" wx:for-index="index3" wx:for-item="item3" wx:key="index3" bindtap="cate_lv3_item_click" data-cat_name="{{item3.cat_name}}" data-cat_id="{{item3.cat_id}}"><image src="{{item3.cat_icon}}" mode="" data-cat_name="{{item3.cat_name}}" data-cat_id="{{item3.cat_id}}"/><text data-cat_name="{{item3.cat_name}}" data-cat_id="{{item3.cat_id}}" >{{item3.cat_name}}</text></view></view></view></scroll-view>
</view>
wxss
/* pages/category/category.wxss */
.scroll-view-container {display: flex;
}.scroll-view-container .left-scroll-view {width: 120px;
}.scroll-view-container .left-scroll-view .scroll-view-item.active {position: relative;background-color: #ffffff;
}.scroll-view-container .left-scroll-view .scroll-view-item.active::before {position:absolute;top:50%;left:0;content:' ';display:block;width: 3px;height:30px;background-color: #C00000;transform: translateY(-50%);
}.scroll-view-container .left-scroll-view .scroll-view-item {background-color: #f7f7f7;line-height: 60px;text-align: center;font-size: 12px;
}.cate-lv2-title {font-size:12px;font-weight:bold;text-align: center;padding: 15px 0;
}.cate-lv3-list {display:flex;flex-wrap:wrap
}.cate-lv3-list .cate-lv3-item {width:33.33%;display:flex;flex-direction:column;justify-content: center;align-items: center;margin-bottom:10px;
}.cate-lv3-list .cate-lv3-item image{width:60px;height:60px;
}.cate-lv3-list .cate-lv3-item text{font-size:12px;
}
js
// pages/category/category.js
import {createStoreBindings} from 'mobx-miniprogram-bindings'
import {store} from '../../store/store.js'const App = getApp()Page({cate_lv3_item_click(e) {wx.navigateTo({url:'/subpackage_goods_list/goods_list?cid=' + e.target.dataset.cat_id + '&&query=' + e.target.dataset.cat_name})},left_scroll_click(e) {const click_item_index = e.target.dataset.click_item_indexthis.setData({left_active_num:click_item_index})this.setData({cateLevel2:this.data.cateList[Number(click_item_index)].children})this.setData({right_scrollTop:0})},get_cateList() {wx.request({url:App.base_url + '/api/public/v1/categories',method:'GET',success:(val) => {// 获取所有分类this.setData({cateList:val.data.message})// 获取二级分类,二级分类的内容在一级分类中有,我们在这里弄好处理,这里给一个初始值,后面在左侧点击事件中还会再改this.setData({cateLevel2:val.data.message[0].children})},fail:() => {console.log('获取左侧失败')wx.showToast({title:'获取左侧失败',icon:'error',duration:2000})}})},/*** 页面的初始数据*/data: {windowHeight:0 + 'px',cateList:[],cateLevel2:[],left_active_num:0,right_scrollTop:0},/*** 生命周期函数--监听页面加载*/onLoad(options) {this.storeBindings = createStoreBindings(this,{store,actions:['set_cart_tabBar_badge']})this.setData({windowHeight:(wx.getWindowInfo().windowHeight-50) + 'px'})},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {// 获取分类列表this.get_cateList()// 设置购物车tabBar的徽标this.set_cart_tabBar_badge()},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函数--监听页面隐藏*/onHide() {},/*** 生命周期函数--监听页面卸载*/onUnload() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh() {},/*** 页面上拉触底事件的处理函数*/onReachBottom() {},/*** 用户点击右上角分享*/onShareAppMessage() {}
})
2 购物车
购物车中的内容在这个项目中都是放在本地的,在实际开发中一般是放在云端
购物车为空时会显示这个页面
2.1 store
store.js是放全局变量的js文件,创建方法可以参考 18.全局数据共享 mobx-miniprogram与mobx-miniprogram-bindings-CSDN博客
源码
import {action,observable
} from 'mobx-miniprogram'export const store = observable({// 购物车总数的计算属性// 计算购物车一共多少件get cart_shop_total() {let total = 0this.cart_list.forEach(goods => total += goods.goods_count)return total},// 计算购物车选中的一共多少钱get cart_shop_choosed_total_price() {let total_price = 0this.cart_list.forEach(goods => {if (goods.goods_state == true) {total_price = total_price + goods.goods_count * goods.goods_price}})return total_price.toFixed(2)},// 计算购物车选中的一共多少件get cart_shop_choosed_total_num() {let total_num = 0this.cart_list.forEach(goods => {if (goods.goods_state == true) {total_num = total_num + goods.goods_count}})return total_num},// 计算是否全选get is_all_checked() {let all_checked = truethis.cart_list.forEach(goods => {if (goods.goods_state == false) {all_checked = false}})return all_checked},// 从本地存储中获取购物车商品信息cart_list: wx.getStorageSync('cart_list') || [],// 添加购物车方法add_cart_obj: action(function (goods_obj) {let findResult = this.cart_list.find((x) => x.goods_id === goods_obj.goods_id)if (!findResult) {this.cart_list = [...this.cart_list, goods_obj]} else {findResult.goods_count++}wx.setStorageSync('cart_list', this.cart_list)}),// 设置购物车tabBar的徽标set_cart_tabBar_badge:action(function() {wx.setTabBarBadge({index: 2,text: String(this.cart_shop_total),})}),// 改变购物车商品所有选中状态updateAllGoodsState:action(function(all_checked_now_state) {this.cart_list.forEach(goods => {goods.goods_state = !all_checked_now_state})let new_cart_list = JSON.stringify(this.cart_list)this.cart_list = JSON.parse(new_cart_list)wx.setStorageSync('cart_list', this.cart_list)}),// 改变购物车商品选中状态updateGoodsState:action(function(goods) {let findResult = this.cart_list.find(x=>x.goods_id === goods.goods_id)if (findResult) {findResult.goods_state = !goods.goods_statuslet new_cart_list = JSON.stringify(this.cart_list)this.cart_list = JSON.parse(new_cart_list)}wx.setStorageSync('cart_list', this.cart_list)}),// 购物车商品数量 + 1add_one_GoodsNum:action(function(goods) {let findResult = this.cart_list.find(x=>x.goods_id === goods.goods_id)if (findResult) {findResult.goods_count = findResult.goods_count + 1let new_cart_list = JSON.stringify(this.cart_list)this.cart_list = JSON.parse(new_cart_list)}wx.setStorageSync('cart_list', this.cart_list)}),// 购物车商品数量 - 1sub_one_GoodsNum:action(function(goods) {let findResult = this.cart_list.find(x=>x.goods_id === goods.goods_id)if (findResult) {if (findResult.goods_count > 1) {findResult.goods_count = findResult.goods_count - 1let new_cart_list = JSON.stringify(this.cart_list)this.cart_list = JSON.parse(new_cart_list)}}wx.setStorageSync('cart_list', this.cart_list)}),// 删除购物车商品delete_store_goods:action(function(goods) {this.cart_list = this.cart_list.filter(x=>x.goods_id != goods.goods_id)wx.setStorageSync('cart_list', this.cart_list)}),// 是否是从“购物车”跳转到“我的”界面且需要跳转回“购物车”页面need_navigate_back:false,yes_need_navigate_back:action(function() {this.need_navigate_back = true}),cancel_need_navigate_back:action(function() {this.need_navigate_back = false})
})
2.2 tabBar徽标
tarBar上的徽标是购物车中商品的数量
用到的是wx.setTabBarBadge()这个api
index是第几个tarbar,按0、1、2这么排,购物车是第二个
text是要写什么东西,类型为字符串,也就是说可以写其他的文字上去
当切换到任意tarbar的时候会更新徽标,在加入购物车的时候会更新徽标,在改变商品数量的时候会更新徽标
使用方法时引入store然后用this调用就行了
2.3 滑动删除
用的是movable-area与movable-view
movable-view必须放在movable-area中,其余就没什么好讲的了,有例子照抄就行了
2.4 结算
2.4.1 结算前登录
这个功能需要“购物车”和“我的”这两个tarbar共同完成,所以需要用到全局数据
这个变量是给"我的"中的一键登录按钮用的,直接点一键登录按钮就不需要跳转,从购物车界面过来再点一键登录按钮就需要跳转
2.4.2 结算功能
目前小程序结算功能是不开放给个人开发者的,如果要开通的话只有一个公司的营业执照也是不行的,需要有实际的开发需求才能用微信支付的接口
2.5 触发组件事件
这些是触发组件的事件,感兴趣可以看一下这个 15-5.自定义组件的通信-CSDN博客
2.6 源码
wxml
<!--pages/cart/cart.wxml--><!-- 收货地址区域 -->
<view class="cart-container" wx:if="{{cart_list.length != 0}}"><view><!-- 选择收货地址的盒子 --><view class="address-choose-box" wx:if="{{!choosed_address.username}}"><button type="primary" size="mini" class="btnChooseAddress" bind:tap="go_to_choose_address">请选择收货地址+</button></view><!-- 渲染收货信息的盒子 --><view class="address-info-box" wx:else bind:tap="go_to_choose_address"><view class="row1"><view class="row1-left"><view class="username">收货人:<text>{{choosed_address.username}}</text></view></view><view class="row1-right"><view class="phone">电话:<text>{{choosed_address.phone}}</text></view><image src="/src/arrowright.png" mode="" style="width:16px;height:16px" /></view></view><view class="row2"><view class="row2-left">收货地址:</view><view class="row2-right">{{choosed_address.address_detail}}</view></view></view><!-- 底部的边框线 --><image src="/src/cart_border@2x.png" class="address-border"></image></view><!-- 购物车标题 --><view class="cart-title"><image src="../../src/shop.png" mode="" style="width:18px;height:18px;"/><text class="cart-title-text">购物车</text></view><!-- 购物车内容 --><view class="cart_content_view"><block wx:for="{{cart_list}}" wx:key="index" ><movable-area data-goods_id="{{item.goods_id}}"><movable-view direction="horizontal" data-goods_id="{{item.goods_id}}"><goods_list_item goods_small_logo="{{item.goods_small_logo}}" goods_id="{{item.goods_id}}" goods_price="{{item.goods_price}}" goods_name="{{item.goods_name}}" goods_count="{{item.goods_count}}" showRadio="true" showNumberBox="true" goods_checked="{{item.goods_state}}" bind:radio_change="change_radio_state" bind:add_one="add_one" bind:sub_one="sub_one"></goods_list_item><view class="itemDelet" bindtap="delete_goods" data-goods_id="{{item.goods_id}}">删除</view></movable-view></movable-area></block></view><!-- 底部结算区域 --><view class="my-settle-container"><label class="radio"><checkbox color="#C00000" checked="{{is_all_checked}}" bind:tap="click_all_checked_button"/><text>全选</text></label><view class="amount-box">合计:<text class="amount">¥{{cart_shop_choosed_total_price}}</text></view><view class="btn-settle" bind:tap="buy_cart">结算({{cart_shop_choosed_total_num}})</view></view>
</view>
<!-- 当购物车里什么都没有时出现的东西 -->
<view class="empty-cart" wx:else><image src="/src/empty_cart.png" class="empty-img"></image><text class="tip-text">空空如也~</text></view>
wxss
/* pages/cart/cart.wxss *//* 购物车标题 */
.cart-title {height:40px;display:flex;align-items:center;font-size:14px;padding-left:5px;border-bottom:1px solid #efefef;white-space: wrap;
}.cart-title .cart-title-text {margin-left:10px;white-space: wrap;
}/* 购物车内容的滑动效果 */
movable-area {display: flex;flex-direction: row;width: calc(100% + 120rpx);justify-content: center;left: -120rpx;height: 130px;z-index:0;
}movable-view {display: flex;flex-direction: row;width: calc(100% - 120rpx);z-index: 1001;left: 120rpx;
}.itemDelet {position: absolute;text-align: center;right: -125rpx;line-height: 130px;height:130px;background-color: rgb(194,0,2);margin-top: 0rpx;margin-right: 6rpx;width: 100rpx;text-align: right;padding-right: 20rpx;color: #fff;
}/* 收货地址 */
.address-border {display: block;width: 100%;height: 5px;
}.address-choose-box {height: 90px;display: flex;align-items: center;justify-content: center;
}.address-info-box {font-size:12px;height:90px;display:flex;flex-direction:column;justify-content:center;padding:0 5px;
}.address-info-box .row1 {display: flex;justify-content: space-between;
}.address-info-box .row1 .row1-right {display: flex;align-items: center;
}.phone {margin-right: 5px;
}.address-info-box .row2 {display: flex;align-items: center;margin-top: 10px;
}.address-info-box .row2 .row2-left {white-space: nowrap;
}/* 底部结算区域 */
.my-settle-container {position: fixed;bottom: 0;left: 0;width: 100%;height: 50px;background-color: white;display: flex;justify-content: space-between;align-items: center;padding-left: 5px;font-size: 14px;z-index:999;
}.my-settle-container .radio {display:flex;align-items:center;
}.my-settle-container .amount {color:#c00000
}.my-settle-container .btn-settle {height: 50px;min-width: 100px;background-color: #c00000;color: white;line-height: 50px;text-align: center;padding: 0 10px;
}.cart_content_view {padding-bottom:50px;
}/* 当购物车什么都没有的时候的区域 */
.empty-cart {display: flex;flex-direction: column;align-items: center;padding-top: 150px;
}.empty-cart .empty-img {width:90px;height:90px;
}.empty-cart .tip-text {font-size:12px;color:gray;margin-top:15px;
}
js
// pages/cart/cart.js
import {createStoreBindings} from 'mobx-miniprogram-bindings'
import {store} from '../../store/store.js'const App = getApp()Page({buy_cart() {if (this.data.cart_shop_choosed_total_num == 0) {wx.showToast({title: '请选择要结算的商品',mask:true,icon:'none'})setTimeout(function() {wx.hideToast()},800)return}if (!this.data.choosed_address.username) {wx.showToast({title: '请选择收货地址',mask:true,icon:'none'})setTimeout(function() {wx.hideToast()},800)return}if (this.data.userinfo == '') {wx.showToast({title: '请先登录',mask:true,icon:'none'})setTimeout(()=>{wx.hideToast()wx.switchTab({url: '/pages/my/my',success:()=>{this.yes_need_navigate_back()}})},800)return }},click_all_checked_button() {this.updateAllGoodsState(this.data.is_all_checked)},go_to_choose_address() {wx.navigateTo({url:'/subpackage_choose_address/choose_address'})},change_radio_state(e) {this.updateGoodsState(e.detail)},add_one(e) {this.add_one_GoodsNum(e.detail)this.set_cart_tabBar_badge()},sub_one(e) {this.sub_one_GoodsNum(e.detail)this.set_cart_tabBar_badge()},delete_goods(e) {this.delete_store_goods(e.target.dataset)this.set_cart_tabBar_badge()},/*** 页面的初始数据*/data: {// address:{'name':'suyu'}choosed_address:{},userinfo:'',from:''},/*** 生命周期函数--监听页面加载*/onLoad(options) {this.storeBindings = createStoreBindings(this,{store,fields:['cart_list','cart_shop_choosed_total_price','cart_shop_choosed_total_num','is_all_checked'],actions:['set_cart_tabBar_badge','updateGoodsState','add_one_GoodsNum','sub_one_GoodsNum','delete_store_goods','updateAllGoodsState','yes_need_navigate_back']})},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {// 设置购物车tabBar的徽标this.set_cart_tabBar_badge()this.setData({choosed_address:wx.getStorageSync('choosed_address') || {}})},/*** 生命周期函数--监听页面显示*/onShow() {this.setData({choosed_address:wx.getStorageSync('choosed_address') || {}})this.setData({userinfo:wx.getStorageSync('userinfo') || ''})},/*** 生命周期函数--监听页面隐藏*/onHide() {},/*** 生命周期函数--监听页面卸载*/onUnload() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh() {},/*** 页面上拉触底事件的处理函数*/onReachBottom() {},/*** 用户点击右上角分享*/onShareAppMessage() {}
})