微信小程序实战项目(商品列表)-four

251 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

1.商品列表效果图

微信截图_20221212113147.png

2.商品列表分解图

微信截图_20221212113158.png

3.商品列表业务逻辑

1.加载商品列表数据

2.启用下拉页面功能

2.1.页面的json文件中开启设置enablePullDownRefresh:true

2.2.页面的js中,绑定事件onPullDownRefresh

3.启用上拉页面功能,onReachBottom页面触底事件

4.加载下一页功能

4.接口API

1.获取商品列表数据

https://api-hmugo-web.itheima.net/api/public/v1/goods/search

5.关键技术

1.小程序配置文件中启用上拉下拉功能

1.1启用下拉功能

这个启用下拉功能用来刷新,微信小程序文档提供了刷新事件为onPullDownRefresh,首先需要在商品列表对应的页面配置进行index.json添加为 "enablePullDownRefresh": true,然后触发下拉刷新事件为onPullDownRefresh(),代码如下:

这个触发刷新事件后,就会重置数据的数组、重置页码为1、重新发送请求数据、数据请求成功回来,需要手动的关闭并等待效果。

// 下拉刷新事件 
   onPullDownRefresh(){
        // 1 重置数据的数组
        this.setData({
            goodsList:[]
        })
        // 2  重置页码
        this.QueryParams.pagenum=1;
        // 3  发送请求数据,调用到获取数据的url
        this.getGoodsList();
   }

如果请求成功回来,需要手动的关闭并等待效果,这个一般放在获取数据的函数里面,因为它的函数里面触发下拉刷新事件就会和数据一起重置并关闭等待效果,代码如下:

// 关闭下拉刷新的窗口 如果没有调用下拉刷新的窗口 直接关闭也不会报错
 wx.stopPullDownRefresh();

因为这种等待效果大概5秒,刷新时大概一秒就完事!所以需要去关闭等待效果。注意:刷新和等待效果是两回事。

在没关闭等待效果的情况如下:

微信截图_20221212113209.png

1.2启用上拉功能

启用上拉功能可以用来分页,如果没有分页的话,想想如果数据量非常大时,然后一次性地返回所有数据给前台,那么页面的打开速度及图片加载就容易会下降。考虑到用户使用流量,如果用户只看第一页的内容,而不用看下面的内容,这样会浪费流量。所以采取启用上拉分页来作优化,当使用上拉分页的时候,后台不需要一次性返回数据给前台,如果用户需要下拉下面的内容,那就会加载中加数据的内容出来了。

实现思路原理:

当用户滚到底部的时候,开始加载下一页数据。首先找到触发上拉事件(onReachBottom),然后判断一下有没有下一页数据。首先我说一下分页页数的详细,首先获取到总页数也就是说获取数据内容的条数,然后计算最后的总页数,比如:总页数=Math.ceil(总条数/每一页数据的行数),每一页数据的行数就是显示一页数据的多少行,比如这个显示一页数据为10行,那么它就是10,假如为Math.ceil(23/10)=3。Math.ceil就是向上取舍近整数,比如2.3取为3。然后这样获取到当前的页码,判断一下当前的页码是否大于等于总页数,如果没有下一页数据,那么到这为止,并弹出提示框;如果有下一页数据,然后来加载下一页的数据,增加当前的页码+1,重新发送请求数据并加载下一页的新数据内容,注意:如果数据请求回来,必须要对data中的数据进行拼接,而不是全部替换掉的!

    1. 找到触发上拉事件(onReachBottom)
    1. 判断有没有下一页数据
  • 2.1 首先获取总条数

  • 计算:总页数 = Math.ceil(总条数 / 每一页数据的行数)

  • 2.2 获取到当前的页码

  • 2.3 判断一下 当前的页码是否>=总页数

    1. 如果没有下一页数据,弹出一个提示框“没有下一页了”。
    1. 如果还有下一页数据 然后来加载下一页数据
  • 4.1 增加当前的页码+1

  • 4.2 重新发送请求数据并加载下一页的新数据内容

  • 4.3 如果数据请求成功回来,要对data中的数据进行拼接

实现思路,代码如下:

// 接口要的参数
    QueryParams:{
        query:"",
        cid:"",
        pagenum:1,
        pagesize:10
    },
 // 总页数
    totalPages:1,
/**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {
       this.QueryParams.cid=options.cid;
       this.getGoodsList();
 
    },
 
// 获取商品列表数据
    async getGoodsList(){
        const res=await request({url:"/goods/search",data:this.QueryParams});
        //获取  总条数
        const total=res.total;
        // 计算总页数
        this.totalPages=Math.ceil(total/this.QueryParams.pagesize);
        console.log (this.totalPages);
        this.setData({
            // 拼接了数组
            goodsList:[...this.data.goodsList,...res.goods]
        })
 
            // 关闭下拉刷新的窗口 如果没有调用下拉刷新的窗口 直接关闭也不会报错
            wx.stopPullDownRefresh();
    },
 
 //页面上滑 滚动条触底事件
   onReachBottom(){
      //   1 判断还有没有下一页数据
      if(this.QueryParams.pagenum>=this.totalPages){
          // 没有下一页数据
        //  console.log('%c'+"没有下一页数据","color:red;font-size:100px;background-image:linear-gradient(to right,#0094ff,pink)");
        wx.showToast({
            title: '没有下一页数据'
        });
          
      }else{
          // 还有下一页数据
        //   console.log('%c'+"有下一页数据","color:red;font-size:100px;background-image:linear-gradient(to right,#0094ff,pink)");
        this.QueryParams.pagenum++;
        this.getGoodsList();
      }
   },

只要理解这个分页上拉的实现原理,这样分析代码是没问题的!

2.tab栏是小程序的自定义组件(有组件事件和参数交互)

这种tab栏(标签切换栏)的布局很常见,尤其是关于电商,比如订单页面那种,待发货、待付款、待收货等。目前微信开发文档现在有提供了tab栏,但是看了微信开发文档的tab栏之后感觉它还不如自己搞tabs栏,而且顺便可以把它当做练习。好了,废话不多说。

首先创建一个组件库文件夹,新建一个components/Tabs/Tabs,最好是在微信开发者工具创建文件,因为它可以整个包(wxml、wxss、js、json)目录出来。

然后做个tab栏的布局,components/Tabs/Tabs.wxml, 表示绑定这些的标签和子组件的自定义事件,这是基本的wxml如下:

<view class="tabs">
    <view class="tabs_title">
        <view 
        wx:for="{{tabs}}"
        wx:key="id"
        class="title_item  {{item.isActive?'active':''}}"
        bindtap="handleItemTap"
        data-index="{{index}}"
        >
            {{item.value}}
        </view>
    </view>
</view>

我相信读者小程序开发的基础或Vue的基础都能看懂,什么的语法糖其实很像Vue,我说明一下这些的作用:

1.wx:for="{{tabs}}"主要是用来渲染列表,需要的是组件之外传入,也可以在js的组件中里面自定义个数组。

2.以上的循环完成之后一般需要加上wx:key="id"及data-index="{{index}}",它们主要用来唯一标识每一项,这样后面对每一项的ID进行操作,因为我们程序一般要明确知道切换每一个项的操作,而且在切换到不同项的做出对应的操作,如果没有定义这些数据,那么后面工作会bug。

3.class="{{item.isActive?'active':' '}}"主要表示已选中的某一项,当该项被选中后需要改变某颜色,比如:当active与当前项的索引isActive相等时才表示选中。

4.bindtap=“handleItemTap”表示触发点击事件,同时在js中触发并进行操作。

另外标签其实就是叫插槽,在每不同的标题栏会有不同的显示,一般在显示内容的页面,这个slot也可以控制显示与隐藏功能。

components/Tabs/Tabs.js,这个主要是用子组件向父组件传递数据,其实原理上来说,当点击事件触发的时候,也需要触发父组件的自定义事件,这样同时传递数据给父组件。比如:this.triggerEvent("父组件自定义事件的名称",要传递的参数),JS如下:

    // components/Tabs/Tabs.js
Component({
    /**
     * 组件的属性列表
     */
    {//里面存放的是要从父组件中接收的数据
    properties: {
        tabs:{
            type:Array,
            value:[]
        }
    },
 
    /**
     * 组件的初始数据
     */
    data: {
 
    },
 
    /**
     * 组件的方法列表
     */
    methods: {
        // 1 点击事件
          /*点击事件触发的时候
          触发父组件中的自定义事件,同时传递数据给父组件
          this.triggerEvent("父组件自定义事件的名称",要传递的参数)*/
 
        handleItemTap(e){
            //1 获取点击的索引,获取ID的值
            const {index}=e.currentTarget.dataset;
            //2 触发 父组件中的事件 自定义
            this.triggerEvent("tabsItemChange",{index});
        }
    }
})

这是最基本的样式文件,这个很简单,详细不多说了!wxcss如下:

    /* components/Tabs/Tabs.wxss */
.tabs{}
.tabs_title{
    display: flex;
    padding:15rpx 0;
 
}
.title_item{
    display: flex;
    justify-content: center;
    align-items: center;
    flex:1;
    padding:15rpx 0;
}
.active{
    color:var(--themeColor);
    border-bottom:5rpx solid currentColor;
}
.tabs_content{}

以上的内容,这就是子组件的Tabs。然后接下来是父组件:

  接下来的是Page/goods_list/index,这是商品列表的页面。 Page/goods_list/index.wxml,这作为绑定标签和父组件的自定义事件,如下:   我先说说一下,我只针对slot标签的事,其他不用管,它们都是从后台数据过来的,我们根据数组的isActive用来判断点击哪个标题栏与对应的父组件向子组件传递标签和内容。

Page/goods_list/index.wxml,代码如下:

    <!-- 监听自定义事件 -->
<Tabs tabs="{{tabs}}" bindtabsItemChange="handleTabsItemChange" >
    <!-- 内容 -->
    <block wx:if="{{tabs[0].isActive}}">
        <view class="first_tab">
            <navigator class="goods_item"
            wx:for="{{goodsList}}"
            wx:key="goods_id"
            url="/pages/goos_detail/index?goods_id={{item.goods_id}}"
            >
                <!-- 左侧 图片容器 -->
                   
                <!-- 右侧 商品容器 -->
                  
            </navigator>
 
        </view>
    </block>
    
    <block wx:elif="{{tabs[1].isActive}}">1</block>
 
    <block wx:elif="{{tabs[2].isActive}}">2</block>
</Tabs>

然后子组件(components/Tabs/Tabs.wxml),slot的标签并接收到数据并显示内容。

     <view class="tabs_content">
        <slot></slot>
 </view>

在js父组件中的数据与自定义事件,js代码如下:

    Page({
    data: {
       tabs:[
           {
               id:0,
               value:"综合",
               isActive:true
           },
           {
               id:1,
               value:"销量", 
               isActive:false
           },
           {
                id:2,
                value:"价格",
                isActive:false
           }
       ],
       goodsList:[]
    },
 
    //标题点击事件 从子组件传递过来
   handleTabsItemChange(e){
       // 1 获取被点击的标题索引
       const {index}=e.detail;
       // 2 修改源数组   
       let {tabs}=this.data;
       //循环数组
     //[].forEach遍历数组,它在遍历数组的时候修改了v,也会导致原数组被修改
       tabs.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
       // 3 赋值到data中
       this.setData({
           goodsList:res.goods
       })
   }
)}

这样搞定OK!

  最终如下:

微信截图_20221212113222.png