微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件

上一教程,我们通过讲解组件监听事件,把组件的数组数据转换成对象,最后直接与wxml绑定,更方便快捷。

http://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/%e5%be%ae%e4%bf%a1%e5%b0%8f%e7%a8%8b%e5%ba%8f%e7%b3%bb%e5%88%97%ef%bc%88%e5%85%ad%ef%bc%89%ef%bc%9a%e4%bd%bf%e7%94%a8%e7%bb%84%e4%bb%b6%e7%9b%91%e5%90%ac%e4%ba%8b%e4%bb%b6%e8%bf%87%e6%bb%a4%e6%95%b0/

利用lin-ui的WaterFlow组件,使用小程序的抽象节点

抽象节点与插槽的区别

  • 抽象节点:需要调用者把“自定义组件传入”;优点:灵活性高
  • 插槽:只需要调用者把数据传入;优点:易用性极高

1、在app.json全局引入lin-ui的water-flow组件

{
    "pages": [
        "pages/home/home"
    ],
    "window": {
        "backgroundTextStyle": "light",
        "navigationBarBackgroundColor": "#fff",
        "navigationBarTitleText": "Weixin",
        "navigationBarTextStyle": "black"
    },
    "style": "v2",
    "sitemapLocation": "sitemap.json",
    "usingComponents": {
        "l-grid2":"/miniprogram_npm/lin-ui/grid/index",
        "l-price":"/miniprogram_npm/lin-ui/price/index",
        "l-water-flow":"/miniprogram_npm/lin-ui/water-flow/index"

      }
}
基础技术、小程序、技术与框架微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件插图

2、在home.wxml中插入lin-ui组件

<!--pages/home/home.wxml-->
<span>{{WebTitle}}</span>
<view>
    <image class="top-theme" src="{{topTheme.entrance_img}}" ></image>
    <swiper class="swiper"
        indicator-dots="{{true}}"
        indicator-active-color="#157658"
        autoplay="{{true}}"
        circular="{{true}}"
    >
        <block wx:for="{{bannerB.items}}">
            <swiper-item>
                <image class="swiper" src="{{item.img}}"></image>>
            </swiper-item>
        </block>
    </swiper>

    <s-category-grid grid="{{grid}}"></s-category-grid>
    <image class="activityD" src="{{activityD.entrance_img}}"></image>

    <s-spu-scroll 
        m-scroll-class="spu-scroll"
        theme="{{themeE}}"
        spu-list="{{themeESpu}}"
        wx:if="{{topTheme.online}}">
    </s-spu-scroll>

    <image src="{{themeF.entrance_img}}" class="quality"></image>

    <s-hot-list banner="{{bannerG}}"></s-hot-list>

    <image src="{{themeH.entrance_img}}" class="fashion"></image>

    <view class="spu-bottom">
        <!-- 抽象节点引入的自定义组件名 spu-preview -->
        <l-water-flow generic:l-water-flow-itme="spu-preview"></l-water-flow>
    </view>
</view>
基础技术、小程序、技术与框架微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件插图1

抽象节点引入的自定义组件名:spu-preview

因为抽象节点需要调用者把“自定义组件:spu-preview”传入,所以也需要定义新的自定义组件“spu-preview”

3、准备瀑布流数据,在utils中创建paging.js

paging.js需要处理哪些逻辑呢?分页数据

  • 没有任何数据,为空的情况
  • 最后一页时,没有下一页;没有更多数据
  • 累加。因为小程序的setData机制,所以需要从[1,N]
  • 分页数据的状态:a.正在加载 b.加载完成 c.没有更多数据 d.空
  • 上划页面触底加载时避免用户重复请求,对数据库压力,应对方法:a.redis 数据锁
  • 例如按钮button 防抖和截流。

防抖和截流常用场景:

  • button的禁用,点击后禁止继续点击
  • 倒计时无法点击
  • 模态,整页加入loading页
import {Http} from "./http";

class Paging{
   strat
   count
   req
   locaker=false
   url
   moreData=true
   accumulator=[]
    /** 定义参数 */
   constructor(req,count=10,start=0){
    this.start=start
    this.count=count
    this.req=req
    this.url=req.url
   }
   /** 获取更新的数据 */
   async getMoreDate(){
       //获取数据的逻辑:1、判断是否有锁,没锁的话 加锁然后request查询数据,最后解锁
       //getLocker  
       //request
       //releaseLocker
       if(!this.moreData){
           return
       }
       if(!this._getLocker){
            return
       }
       const data = await this._actualGetData()
       
       this._releaseLocker();
       return data
   }
   /** 跑接口 根据不同的结果,返回不同对象
    * 
    */
   async _actualGetData(){
        const req =  this._getCurrentReq()
        let paging = await Http.request(req)
        if(!paging){
            return null
        }
        /** 没有数据,返回空 */
        if(paging.total===0){
            return {
                empty:true,
                items:[],
                moreData:false,
                accumulator:[]
            }
        }
        /** 判断是否存在更多数据 */
        this.moreData = Paging._moreData(paging.total_page,paging.page)

        if(this.moreData){
            this.start +=this.count
        }
        this._accumulate(paging.items)
        return{
            empty:false,
            items:paging.items,
            moreData:this.moreData,
            accumulator:this.accumulator
        }
   }
   /** 合并item */
   _accumulate(items){
        this.accumulator = this.accumulator.concat(items)
   }
   /** 获取是否有更多内容 */
   static _moreData(totalPage,pageNum){
        return pageNum<totalPage-1
   }
   /** 获取当前的url */
   _getCurrentReq(){
        let url =this.url;
        const params = `start=${this.start}&count=${this.count}`;
        if(url.includes('?')){
            url +='&'+params
        }else{
            url +='?'+params
        }
        this.req.url=url
        return this.req
   }
   /* 获取当前锁的状态 */
   _getLocker(){
        if(this.locker){
            return false;
        }
        this.locker=true
        return true
   }
   /** 解锁 */
   _releaseLocker(){
        this.locker=false
   }
}
export{
    Paging
}

4、在model中创建spu-paging.js

在spu-paging.js中实例化并返回,在home.js中调用该类的对象

import {Http} from "../../utils/http"
import { Paging } from "../../utils/paging"
class SpuPaging{
    static  getLatestPaging(){
        /** 实例化,传入url, */
        return new Paging(
            {url:`spu/latest`},3
        )
    }
}

export{
    SpuPaging

}
基础技术、小程序、技术与框架微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件插图2

5、在home.js调用spu.js中的函数

瀑布流使用到触发底部事件:onReachBottom

// pages/home/home.js
import { Banner } from "../model/banner.js";
import { category } from "../model/category.js";
import { Theme  } from "../model/theme.js"
import { Activity  } from "../model/activity.js"
import { SpuPaging } from "../model/spu-paging.js";

Page({
    /**
     * 页面的初始数据
     */
    data: {
        WebTitle:"Fox_Test",
        topTheme: null,
        themeE:null,
        bannerB:null,
        grid:[],
        activity:null,
        themeESpu:null,
        paging:null,
        loadtype:"loading",
        loadObj:{
            "loadingType":"loading",
            "endtext":null
        }
        
        
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad : async function(){
       this.initAllDate();
       this.initBottomSpuList();
    },
    /**定义获取瀑布流元素事件 */
    initBottomSpuList:async function(){
        /** SouPaging已经在spu-paging.js实例化了 直接可以调用对象 */ 
        const paging = SpuPaging.getLatestPaging()
        this.data.paging = paging;
        const data = await paging.getMoreDate()
        console.log(data);
        if(!data){
            return
        }
        wx.lin.renderWaterFlow(data.items)
    },
    initAllDate: async function(){
            const themes = new Theme();
            await themes.GetThemes();
            const themeA = await themes.getHomelocationA();
            const themeE = await themes.getHomelocationE();
            const themeF = await themes.getHomelocationF();
            let themeESpu = [];
            if(themeE.online){
                const data = await Theme.getHomeLocationESpu();
                if(data){
                    /*截取数组0~8个 */
                    themeESpu = data.spu_list.slice(0,8);
                }
            }
            const bannerB =  await Banner.getHomeLocationB();
            const grid =  await category.getGridCategory();
            const activityD =await Activity.getHomeLocationD();
            const bannerG =  await Banner.getHomeLocationG();
            const themeH = await themes.getHomelocationH();
            this.setData({
                activityD:activityD,
                topTheme:themeA,
                themeE:themeE,
                bannerB:bannerB,
                grid:grid,
                themeESpu:themeESpu,
                themeF:themeF,
                bannerG:bannerG,
                themeH:themeH
                
            })
    },
    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom: async function () {
        /* getMoreDate*/
        const data = await this.data.paging.getMoreDate();
        if(!data){
            return
        }
        wx.lin.renderWaterFlow(data.items)
        if(!data.moreData){
            this.setData({
                loadObj:{
                    "loadingType":'end',
                    "end-text":"没更多内容了,亲"
                },
                loadtype:'end'
                // loadingType:loadingType
            })
        }
    },
})

6、创建自定义组件“spu-preview”

6.1因为瀑布流会使用到加载提示控件,所以需要引入lin-ui的“ loadmore ”组件

在app.json中引入lin-ui的loadmore组件和自定义spu-preview组件

{
    "pages": [
        "pages/home/home"
    ],
    "window": {
        "backgroundTextStyle": "light",
        "navigationBarBackgroundColor": "#fff",
        "navigationBarTitleText": "Weixin",
        "navigationBarTextStyle": "black",
        "onReachBottomDistance": 50
    },
    "style": "v2",
    "sitemapLocation": "sitemap.json",
    "usingComponents": {
        "l-grid2":"/miniprogram_npm/lin-ui/grid/index",
        "l-price":"/miniprogram_npm/lin-ui/price/index",
        "l-water-flow":"/miniprogram_npm/lin-ui/water-flow/index",
        "l-tag":"/miniprogram_npm/lin-ui/tag/index",
        "l-loadmore":"/miniprogram_npm/lin-ui/loadmore/index",
        "m-spu-preview":"/components/spu-preview/index"
      }
}

6.2 编写组件的主体框架,index.wxml

<!--考虑到原价和折扣价的复用性,引入处理价格的wxs-->
<wxs src="../../wxs/price.wxs" module="p"></wxs>

<view class="container">
    <!-- 设置高度自适应方法1 使用小程序官方的image方法-->
    <!--
    <image mode="widthFix" class="img" src="{{data.img}}" ></image>
    -->
    <!--方法二:动态计算宽高比 onImgLoad在组件的index.js的methods定义,切勿忘记后面的rpx   -->
    <image bing:load="onImgLoad" style="width:{{w}}rpx;height:{{h}}rpx" class="img" src="{{data.img}}"></image>
    <view class="content-container">
        <text class="tittle">{{data.tittle}}</text>
        <view class="tags">
            <!--因为tags不是每个都有,所以需要在index.js处理-->
            <block wx:for="{{tags}}">
            <!-- 注意此处使用了lin-ui的l-tag所以需要在APP.json或home.json引入组件-->
                <l-tag type="reading" class="m-tag" size="super-mini">{{item}}</l-tag>
            </block>
        </view>
        <view class="price-row">
            <!--原价和折扣价 考虑到复用性,所以使用wxs对价格进行处理-->
            <l-price
            color:='#157658'
            value-size='28'
            unit-size='20'
            autofix
            value="{{p.mainPrice(data.price,data.discount_price)}}"></l-price>
            <l-price l-class="discount-price" wx:if="{{data.discount_price?true:false}}" 
            deleted
            color='#999999'
            size="26"
            value="{{p.slashedPrice(data.price,data.discount_price)}}"></l-price>
        </view>
        <text class="subtitle">{{data.subtitle}}</text> 
    </view>
</view>
基础技术、小程序、技术与框架微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件插图3

6.3 处理tags问题,把文本转换成对象,修改index.js

// components/spu-preview/index.js
Component({
    /**
     * 组件的属性列表
     */
    externalClasses:['m-tag'],
    properties: {
        data:Object
    },

    /**
     * 组件的初始数据
     */
    data: {
        tags:Array
    },
    /** 数据处理,在tag转换成数据 */
    observers:{
        data:function(data){
            if(!data){
                return
            }
            if(!data.tags){
                return
            }
            const tags = data.tags.split('$')
            this.setData({
                tags:tags
            })
        }
    },
    /**
     * 组件的方法列表
     */
    methods: {
        /** 瀑布流重算高度 */
        onImgLoad(event){
            const {width,hegiht}=event.detail
            this.setData({
                w:340,
                h:340*height/width
            })
        }
    }
})
基础技术、小程序、技术与框架微信小程序系列(七):利用lin-ui和抽象节点编写瀑布流组件插图4

6.4、增加组件的样式,修改组件的index.wxss

/* components/spupreview/index.wxss */
.container{
    width: 340rpx;
    display: flex;
    flex-direction: column;
    box-shadow: 0px 0px 8px 0px rgba(119,163,149,0.2);
    margin-bottom: 30rpx;
    background-color: #ffffff;
}
.content-container{
    display:flex;
    flex-direction: column;

}
.tittle{
    font-size: 28rpx;
    columns: #333333;
}
.img{
    width: 100%;
    height:360rpx;
}
.tags{
    display: flex;
    flex-direction: row;
    margin-bottom: 8rpx;
    margin-top: 6rpx;
    flex-wrap:wrap;
}
.subtitle{
    font-size: 24rpx;
    font-weight: 300;
    margin-top:6rpx;
    color:#888
}
.price-row{
    display:flex;
    flex-direction: row;
}
.discount-price{
    margin-left:20rpx;
}
.m-tag{
    background-color:#DCEBE6 !important;
    color:#157658 !important;
    padding-left:30rpx !important;
    padding-right: 30rpx !important;
    height: 80rpx !important;
    margin-right: 5rpx !important;
}

6.5 修改home.wxml,引入loadmore组件

<!--考虑到原价和折扣价的复用性,引入处理价格的wxs-->
<wxs src="../../wxs/price.wxs" module="p"></wxs>

<view class="container">
    <!-- 设置高度自适应方法1 使用小程序官方的image方法-->
    <!--
    <image mode="widthFix" class="img" src="{{data.img}}" ></image>
    -->
    <!--方法二:动态计算宽高比 onImgLoad在组件的index.js的methods定义,切勿忘记后面的rpx   -->
    <image bing:load="onImgLoad" style="width:{{w}}rpx;height:{{h}}rpx" class="img" src="{{data.img}}"></image>
    <view class="content-container">
        <text class="tittle">{{data.tittle}}</text>
        <view class="tags">
            <!--因为tags不是每个都有,所以需要在index.js处理-->
            <block wx:for="{{tags}}">
            <!-- 注意此处使用了lin-ui的l-tag所以需要在APP.json或home.json引入组件-->
                <l-tag type="reading" class="m-tag" size="super-mini">{{item}}</l-tag>
            </block>
        </view>
        <view class="price-row">
            <!--原价和折扣价 考虑到复用性,所以使用wxs对价格进行处理-->
            <l-price
            color:='#157658'
            value-size='28'
            unit-size='20'
            autofix
            value="{{p.mainPrice(data.price,data.discount_price)}}"></l-price>
            <l-price l-class="discount-price" wx:if="{{data.discount_price?true:false}}" 
            deleted
            color='#999999'
            size="26"
            value="{{p.slashedPrice(data.price,data.discount_price)}}"></l-price>
        </view>
        <text class="subtitle">{{data.subtitle}}</text> 
    </view>
</view>

功能大功告成!!!!

下一节,我们将讲解关于组件的传参教程。

http://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%B3%BB%E5%88%97%EF%BC%88%E5%85%AB%EF%BC%89%EF%BC%9A%E5%88%9B%E5%BB%BA%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E9%A1%B5/