【愚公系列】2022年10月 微信小程序-电商项目-使用vtabs实现商品列表页

2022-10-05 18:03:21 浏览数 (2)

文章目录

  • 前言
  • 一、使用vtabs
    • 1.安装对应的包
    • 2.获取分页列表和数据
    • 3.效果

前言

要实现商品列表页需要使用到weui的纵向选项卡(vtabs)功能,用于让用户在不同的视图中进行切换。

vtabs

vtab-content

属性名

类型

默认值

必选

描述

tab-index

Number

0

yes

vtabs 数据索引(基于 0)

一、使用vtabs

1.安装对应的包

代码语言:javascript复制
npm i @miniprogram-component-plus/vtabs
npm i @miniprogram-component-plus/vtabs-content 

安装完包之后在微信开发者工具中选择构建npm包,这样vtabs就可以在小程序中使用了

2.获取分页列表和数据

代码语言:javascript复制
<mp-vtabs wx-else vtabs="{{vtabs}}" activeTab="{{activeTab}}" bindtabclick="onTabCLick" bindchange="onChange" class="test" id="category-vtabs" bindscrolltoindexlower="onScrollToIndexLower">
	<block wx:for="{{vtabs}}" wx:key="title">
		<mp-vtabs-content id="goods-content{{index}}" tabIndex="{{index}}">
			<view class="vtabs-content-item">
        <view style="line-height:50px;background-color: rgba(255,255,255,1);text-align: center;">{{item.title}}</view>
				<view wx:for="{{goodsListMap[item.id].rows}}" wx:for-index="index2" wx:for-item="item2" wx:key="id">
					<van-card bindtap="onTapGoods" data-id="{{item2.id}}" price="{{item2.start_price}}" desc="" title="{{item2.goods_name}}" thumb="{{ item2.goods_infos[0].content }}">
						<view slot="footer">
							<van-icon size="24px" name="shopping-cart-o" />
						</view>
					</van-card>
				</view>
			</view>
		</mp-vtabs-content>
	</block>
</mp-vtabs>
代码语言:javascript复制
{
  "usingComponents": {
    "mp-vtabs": "@miniprogram-component-plus/vtabs/index",
    "mp-vtabs-content": "@miniprogram-component-plus/vtabs-content/index",
    "van-card": "@vant/weapp/card/index",
    "van-icon": "@vant/weapp/icon/index"
  }
}
代码语言:javascript复制
Page({
  data: {
    //存放vtabs的数据
    vtabs: [],
    //当前行为索引
    activeTab: 0,
    //商品列表映射
    goodsListMap:{},
    //加载字段
    loading:true,
  },

  /**
   * 获取分类
   */
  async onLoad() {
    let categories = await wx.wxp.request({
      url: 'http://localhost:3000/goods/categories',
    })

    if (categories) categories = categories.data.data
    let vtabs = []
    for(let j=0;j<categories.length;j  ){
      let item = categories[j]
      if (j<3) this.getGoodsListByCategory(item.id,j)
      vtabs.push({title: item.category_name, id: item.id})
    }
    this.setData({
      vtabs,
      loading: false
    })
  },

  /**
   * 点击事件
   * @param {*} e 
   */
  async onTapGoods(e){
    wx.showLoading({
      title: 'Loading..',
    })
    let goodsId = e.currentTarget.dataset.id 
    let goods = await wx.wxp.request({
      url: `http://localhost:3000/goods/goods/${goodsId}`,
    })
    console.log(goods)
   
    if (goods){
      goods = goods.data.data 
      wx.navigateTo({
        url: `/pages/goods/index?goodsId=${goodsId}`,
        success: function(res) {
          res.eventChannel.emit('goodsData', { data: goods })
        }
      })
    }
    wx.hideLoading()
  },

  /**
   * 
   * @param {重新计算高度} index 
   */
  reClacChildHeight(index){
    // calcChildHeight
    const goodsContent = this.selectComponent(`#goods-content${index}`)
    // console.log(goodsContent);
    
    const categoryVtabs = this.selectComponent('#category-vtabs')
    categoryVtabs.calcChildHeight(goodsContent)
  },

  /**
   * 获取分类明细列表
   * @param {*} categoryId 
   * @param {*} index 
   * @param {*} loadNextPage 
   */
  async getGoodsListByCategory(categoryId, index, loadNextPage = false){
    const pageSize = 10
    let pageIndex = 1
    let listMap = this.data.goodsListMap[categoryId]
    if (listMap){
      if (listMap.rows.length >= listMap.count) return 
      if (listMap.pageIndex){
        pageIndex = listMap.pageIndex
        if (loadNextPage) pageIndex  
      }
    }
    let goodsData = await wx.wxp.request({
      url: `http://localhost:3000/goods/goods?page_index=${pageIndex}&page_size=${pageSize}&category_id=${categoryId}`,
    })
    if (goodsData){
      goodsData = goodsData.data.data;
    }

    if (listMap){
      listMap.pageIndex = pageIndex
      listMap.count = goodsData.count
      listMap.rows.push(...goodsData.rows)
      this.setData({
        [`goodsListMap[${categoryId}]`]:listMap
      })
    }else{
      goodsData.pageIndex = pageIndex
      this.setData({
        [`goodsListMap[${categoryId}]`]:goodsData
      })
    }
    this.reClacChildHeight(index)
  },

  onScrollToIndexLower(e){
    console.log("scroll to index lower",e.detail);
    let index = e.detail.index;
    if (index != this.data.lastIndexForLoadMore){
      let cate = this.data.vtabs[index]
      let categoryId = cate.id
      this.getGoodsListByCategory(categoryId,index, true)
      this.data.lastIndexForLoadMore = index 
    }
  },

  onCategoryChanged(index){
    let cate = this.data.vtabs[index]
    let category_id = cate.id 
    if (!this.data.goodsListMap[category_id]){
      this.getGoodsListByCategory(category_id, index)
    }
  },

  onTabCLick(e) {
    const index = e.detail.index
    console.log('tabClick', index)
    this.onCategoryChanged(index)
  },

  onChange(e) {
    const index = e.detail.index
    console.log('change', index)
    this.onCategoryChanged(index)
  }

})
代码语言:javascript复制
@import '../../static/style/common.wxss';

page{
    background-color: #FFFFFF;
    height: 100%;
}

.vtabs-content-item {
    width: 100%;
    height: auto;
    min-height: 200px;
    box-sizing: border-box;
    border-bottom: 1px solid #ccc;
    /* padding-bottom: 20px; */
}

.van-card__title {
    padding-top: 10px;
}
代码语言:javascript复制
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 6);
/******/ })
/************************************************************************/
/******/ ({

/***/ 6:
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Component({
    options: {
        addGlobalClass: true,
        pureDataPattern: /^_/,
        multipleSlots: true
    },
    properties: {
        vtabs: { type: Array, value: [] },
        tabBarClass: { type: String, value: '' },
        activeClass: { type: String, value: '' },
        tabLineColor: { type: String, value: '#ff0000' },
        tabInactiveTextColor: { type: String, value: '#000000' },
        tabActiveTextColor: { type: String, value: '#ff0000' },
        tabInactiveBgColor: { type: String, value: '#eeeeee' },
        tabActiveBgColor: { type: String, value: '#ffffff' },
        activeTab: { type: Number, value: 0 },
        animation: { type: Boolean, value: true }
    },
    data: {
        currentView: 0,
        contentScrollTop: 0,
        _heightRecords: [],
        _contentHeight: {}
    },
    observers: {
        activeTab: function activeTab(_activeTab) {
            this.scrollTabBar(_activeTab);
        }
    },
    relations: {
        '../vtabs-content/index': {
            type: 'child',
            linked: function linked(target) {
                var _this = this;

                target.calcHeight(function (rect) {
                    _this.data._contentHeight[target.data.tabIndex] = rect.height;
                    if (_this._calcHeightTimer) {
                        clearTimeout(_this._calcHeightTimer);
                    }
                    _this._calcHeightTimer = setTimeout(function () {
                        _this.calcHeight();
                    }, 100);
                });
            },
            unlinked: function unlinked(target) {
                delete this.data._contentHeight[target.data.tabIndex];
            }
        }
    },
    lifetimes: {
        attached: function attached() {}
    },
    methods: {
        calcChildHeight:function(target){
          var _this = this;

          target.calcHeight(function (rect) {
              _this.data._contentHeight[target.data.tabIndex] = rect.height;
              if (_this._calcHeightTimer) {
                  clearTimeout(_this._calcHeightTimer);
              }
              _this._calcHeightTimer = setTimeout(function () {
                  _this.calcHeight();
              }, 100);
          });
        },
        calcHeight: function calcHeight() {
            var length = this.data.vtabs.length;
            var _contentHeight = this.data._contentHeight;
            var _heightRecords = [];
            var temp = 0;
            for (var i = 0; i < length; i  ) {
                _heightRecords[i] = temp   (_contentHeight[i] || 0);
                temp = _heightRecords[i];
            }
            this.data._heightRecords = _heightRecords;
        },
        scrollTabBar: function scrollTabBar(index) {
            var len = this.data.vtabs.length;
            if (len === 0) return;
            var currentView = index < 6 ? 0 : index - 5;
            if (currentView >= len) currentView = len - 1;
            this.setData({ currentView: currentView });
        },
        handleTabClick: function handleTabClick(e) {
            var _heightRecords = this.data._heightRecords;
            var index = e.currentTarget.dataset.index;
            var contentScrollTop = _heightRecords[index - 1] || 0;
            this.triggerEvent('tabclick', { index: index });
            this.setData({
                activeTab: index,
                contentScrollTop: contentScrollTop
            });
        },
        handleContentScroll: function handleContentScroll(e) {
          var _heightRecords = this.data._heightRecords;
          if (_heightRecords.length === 0) return;
          var length = this.data.vtabs.length;
          var scrollTop = e.detail.scrollTop;
          var index = -1;
          if (scrollTop >= _heightRecords[this.data.activeTab]-windowHeight-50){
              // 滚动到底部还有50个px时
              this.triggerEvent('scrolltoindexlower', { index: this.data.activeTab });
          }
          const windowHeight = wx.getSystemInfoSync().windowHeight
          if (scrollTop >= _heightRecords[0]-windowHeight) {
              for (var i = 1; i < length; i  ) {
                  if (scrollTop >= _heightRecords[i - 1]-windowHeight && scrollTop < _heightRecords[i]-windowHeight) {
                      index = i;
                      break;
                  }
              }
          }else{
              index = 0
          }
          if (index > -1 && index !== this.data.activeTab) {
              this.triggerEvent('change', { index: index });
              this.setData({ activeTab: index });
          }
      }
    }
});

/***/ })

/******/ });

3.效果

0 人点赞