上一篇我们搭建了底部的导航条,这一篇来拆解一下首页的功能。首页有如下功能
- 可以进行搜索
- 显示轮播图
- 横向可拖动的页签
- 图文卡片列表
1 home.json
因为是要使用组件库的组件搭建页面,自然是先需要引入自定义组件
{ "navigationBarTitleText": "首页", "onReachBottomDistance": 10, "backgroundTextStyle": "light", "enablePullDownRefresh": true, "usingComponents": { "t-search": "tdesign-miniprogram/search/search", "t-loading": "tdesign-miniprogram/loading/loading", "t-swiper": "tdesign-miniprogram/swiper/swiper", "t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav", "t-image": "/components/webp-image/index", "t-icon": "tdesign-miniprogram/icon/icon", "t-toast": "tdesign-miniprogram/toast/toast", "t-tabs": "tdesign-miniprogram/tabs/tabs", "t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel", "goods-list": "/components/goods-list/index", "load-more": "/components/load-more/index" } }
引入的组件还是不少的,贴入配置后发现控制台报错,因为这里既使用到了TDesign中的组件,也使用到了自定义组件,我们需要将报错的组件,自己搭建一下。
其实解决问题就像俄罗斯套娃一样,拿走一个里边还有一个,直到你拿到最后一个才可以
2 goods-list组件
选中components文件夹,右键新建一个文件夹
输入goods-list,然后在goods-list文件夹上右键,点击新建Page
然后输入index,自动生成四个文件,index.wxml、index.wxss、index.json、index.js
自定义组件也是包含四个文件,要依次看模板的代码
index.json
{ "component": true, "usingComponents": { "goods-card": "/components/goods-card/index" } }
这里goods-list又继续引用了goods-card组件
index.js
Component({ externalClasses: ['wr-class'], properties: { goodsList: { type: Array, value: [], }, id: { type: String, value: '', observer: (id) => { this.genIndependentID(id); }, }, thresholds: { type: Array, value: [], }, }, data: { independentID: '', }, lifetimes: { ready() { this.init(); }, }, methods: { onClickGoods(e) { const { index } = e.currentTarget.dataset; this.triggerEvent('click', { ...e.detail, index }); }, onAddCart(e) { const { index } = e.currentTarget.dataset; this.triggerEvent('addcart', { ...e.detail, index }); }, onClickGoodsThumb(e) { const { index } = e.currentTarget.dataset; this.triggerEvent('thumb', { ...e.detail, index }); }, init() { this.genIndependentID(this.id || ''); }, genIndependentID(id) { if (id) { this.setData({ independentID: id }); } else { this.setData({ independentID: `goods-list-${~~(Math.random() * 10 ** 8)}`, }); } }, }, });
自定义组件的externalClasses表示外部样式类,可以在引用的时候传入样式来改变组件的样式。properties表示组件对外暴露的属性,可以根据组件的需要进行设置。method表示组件可以响应的事件,看目前的设置事件是和电商业务相关的,具体是什么含义,我们在调用的时候再分析
index.wxml
<view class="goods-list-wrap wr-class" id="{{independentID}}"> <block wx:for="{{goodsList}}" wx:for-item="item" wx:key="index"> <goods-card id="{{independentID}}-gd-{{index}}" data="{{item}}" currency="{{item.currency || '¥'}}" thresholds="{{thresholds}}" class="goods-card-inside" data-index="{{index}}" bind:thumb="onClickGoodsThumb" bind:click="onClickGoods" bind:add-cart="onAddCart" /> </block> </view>
这是组件的内容部分,他又使用了一个goods-card组件
index.wxss
.goods-list-wrap { display: flex; flex-flow: row wrap; justify-content: space-between; padding: 0; background: #fff; }
样式部分还是很简单的,他是设置了一个流式布局,元素是按行排列,要求自动换行,水平对齐是两端对齐,没有内边距并设置了一定的背景色
3 goods-card组件
在components文件夹下再新建一个goods-card文件夹
然后在goods-card文件夹新建一个Page
index.json
{ "component": true, "usingComponents": { "price": "/components/price/index", "t-icon": "tdesign-miniprogram/icon/icon", "t-image": "/components/webp-image/index" } }
好家伙真还是俄罗斯套娃,这个组件又套了两个组件,需要继续新建
index.js
Component({ options: { addGlobalClass: true, }, properties: { id: { type: String, value: '', observer(id) { this.genIndependentID(id); if (this.properties.thresholds?.length) { this.createIntersectionObserverHandle(); } }, }, data: { type: Object, observer(data) { if (!data) { return; } let isValidityLinePrice = true; if (data.originPrice && data.price && data.originPrice < data.price) { isValidityLinePrice = false; } this.setData({ goods: data, isValidityLinePrice }); }, }, currency: { type: String, value: '¥', }, thresholds: { type: Array, value: [], observer(thresholds) { if (thresholds && thresholds.length) { this.createIntersectionObserverHandle(); } else { this.clearIntersectionObserverHandle(); } }, }, }, data: { independentID: '', goods: { id: '' }, isValidityLinePrice: false, }, lifetimes: { ready() { this.init(); }, detached() { this.clear(); }, }, pageLifeTimes: {}, methods: { clickHandle() { this.triggerEvent('click', { goods: this.data.goods }); }, clickThumbHandle() { this.triggerEvent('thumb', { goods: this.data.goods }); }, addCartHandle(e) { const { id } = e.currentTarget; const { id: cardID } = e.currentTarget.dataset; this.triggerEvent('add-cart', { ...e.detail, id, cardID, goods: this.data.goods, }); }, genIndependentID(id) { let independentID; if (id) { independentID = id; } else { independentID = `goods-card-${~~(Math.random() * 10 ** 8)}`; } this.setData({ independentID }); }, init() { const { thresholds, id } = this.properties; this.genIndependentID(id); if (thresholds && thresholds.length) { this.createIntersectionObserverHandle(); } }, clear() { this.clearIntersectionObserverHandle(); }, intersectionObserverContext: null, createIntersectionObserverHandle() { if (this.intersectionObserverContext || !this.data.independentID) { return; } this.intersectionObserverContext = this.createIntersectionObserver({ thresholds: this.properties.thresholds, }).relativeToViewport(); this.intersectionObserverContext.observe( `#${this.data.independentID}`, (res) => { this.intersectionObserverCB(res); }, ); }, intersectionObserverCB() { this.triggerEvent('ob', { goods: this.data.goods, context: this.intersectionObserverContext, }); }, clearIntersectionObserverHandle() { if (this.intersectionObserverContext) { try { this.intersectionObserverContext.disconnect(); } catch (e) {} this.intersectionObserverContext = null; } }, }, });
这个组件里边的代码会更复杂一点
index.wxml
<view id="{{independentID}}" class="goods-card" bind:tap="clickHandle" data-goods="{{ goods }}" > <view class="goods-card__main"> <view class="goods-card__thumb" bind:tap="clickThumbHandle"> <t-image wx:if="{{ !!goods.thumb }}" t-class="goods-card__img" src="{{ goods.thumb }}" mode="aspectFill" lazy-load /> </view> <view class="goods-card__body"> <view class="goods-card__upper"> <view wx:if="{{ goods.title }}" class="goods-card__title"> {{ goods.title }} </view> <view wx:if="{{ goods.tags && !!goods.tags.length }}" class="goods-card__tags"> <view wx:for="{{ goods.tags }}" wx:key="index" wx:for-item="tag" class="goods-card__tag" data-index="{{index}}" > {{tag}} </view> </view> </view> <view class="goods-card__down"> <price wx:if="{{ goods.price }}" wr-class="spec-for-price" symbol-class="spec-for-symbol" symbol="{{currency}}" price="{{goods.price}}" /> <price wx:if="{{ goods.originPrice && isValidityLinePrice }}" wr-class="goods-card__origin-price" symbol="{{currency}}" price="{{goods.originPrice}}" type="delthrough" /> <t-icon class="goods-card__add-cart" prefix="wr" name="cartAdd" id="{{independentID}}-cart" data-id="{{independentID}}" catchtap="addCartHandle" size="48rpx" color="#FA550F" /> </view> </view> </view> </view>
index.wxss
.goods-card { box-sizing: border-box; font-size: 24rpx; border-radius: 0 0 16rpx 16rpx; border-bottom: none; } .goods-card__main { position: relative; display: flex; line-height: 1; padding: 0; background: transparent; width: 342rpx; border-radius: 0 0 16rpx 16rpx; align-items: center; justify-content: center; margin-bottom: 16rpx; flex-direction: column; } .goods-card__thumb { flex-shrink: 0; position: relative; width: 340rpx; height: 340rpx; } .goods-card__thumb:empty { display: none; margin: 0; } .goods-card__img { display: block; width: 100%; height: 100%; border-radius: 16rpx 16rpx 0 0; overflow: hidden; } .goods-card__body { display: flex; flex: 1 1 auto; background: #fff; border-radius: 0 0 16rpx 16rpx; padding: 16rpx 24rpx 18rpx; flex-direction: column; } .goods-card__upper { display: flex; flex-direction: column; overflow: hidden; flex: 1 1 auto; } .goods-card__title { flex-shrink: 0; font-size: 28rpx; color: #333; font-weight: 400; display: -webkit-box; height: 72rpx; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; word-break: break-word; line-height: 36rpx; } .goods-card__tags { display: flex; flex-direction: row; flex-wrap: wrap; margin: 8rpx 0 0 0; } .goods-card__tag { color: #fa4126; background: transparent; font-size: 20rpx; border: 1rpx solid #fa4126; padding: 0 8rpx; border-radius: 16rpx; line-height: 30rpx; margin: 0 8rpx 8rpx 0; display: block; overflow: hidden; white-space: nowrap; word-break: keep-all; text-overflow: ellipsis; } .goods-card__down { display: flex; position: relative; flex-direction: row; justify-content: flex-start; align-items: baseline; line-height: 32rpx; margin: 8rpx 0 0 0; } .goods-card__origin-price { white-space: nowrap; font-weight: 700; order: 2; color: #bbbbbb; font-size: 24rpx; margin: 0 0 0 8rpx; } .goods-card__add-cart { order: 3; margin: auto 0 0 auto; position: absolute; bottom: 0; right: 0; } .spec-for-price { font-size: 36rpx; white-space: nowrap; font-weight: 700; order: 1; color: #fa4126; margin: 0; } .spec-for-symbol { font-size: 24rpx; }
总结
看首页功能,其实看wxml文件并不复杂,复杂在了既调用了组件库中的组件,又自己封装了很多组件,而且是俄罗斯套娃,一层嵌套一层,这么个看要想用熟练一套模板也不是简单的事情。