前言
要实现商品列表页需要使用到weui的纵向选项卡(vtabs)功能,用于让用户在不同的视图中进行切换。
vtabs
属性名 | 类型 | 默认值 | 必选 | 描述 |
---|---|---|---|---|
vtabs | Array | [] | yes | 数据项格式为{title} |
active-tab | Number | no | 激活选项卡索引 | |
tab-bar-class | String | no | 选项卡样式 | |
active-class | String | no | 行为样式 | |
tab-bar-line-color | String | #ff0000 | no | 选定项目带下划线的一面的颜色 |
tab-inactive-text-color | String | #000000 | no | 未选中的字体颜色 |
tab-bar-active-text-color | String | #ff0000 | no | 检查字体颜色 |
tab-inactive-bg-color | String | #eeeeee | no | 未选中的背景色 |
tab-active-bg-color | String | #ffffff | no | 检查背景颜色 |
animation | Boolean | true | no | 打开动画 |
Bindtablick | eventhandle | no | 触发时点击选项卡,e.detail={index} | |
bindchange | eventhandle | no | 内容区域滚动原因选项卡更改时触发,以及.detail={index} |
vtab-content
属性名 | 类型 | 默认值 | 必选 | 描述 |
---|---|---|---|---|
tab-index | Number | yes | vtabs 数据索引(基于 0) |
一、使用vtabs
1.安装对应的包
npm i @miniprogram-component-plus/vtabs
npm i @miniprogram-component-plus/vtabs-content
安装完包之后在微信开发者工具中选择构建npm包,这样vtabs就可以在小程序中使用了
2.获取分页列表和数据
<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>
{
"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"
}
}
Page({
data: {
//存放vtabs的数据
vtabs: [],
//当前行为索引
activeTab: 0,
//商品列表映射
goodsListMap:{},
//加载字段
loading:true,
},
/**
* 获取分类
*/
async onl oad() {
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)
}
})
@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;
}
vtabs中index.js改写
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 });
}
}
}
});
/***/ })
/******/ });