1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

通过微信开发者工具 商城模板 创建新小程序

This commit is contained in:
2023-03-06 23:52:24 +08:00
parent ada464a8cc
commit c21ff901d5
393 changed files with 52952 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
Component({
options: {
addGlobalClass: true,
},
/**
* 组件的属性列表
*/
properties: {
isAllSelected: {
type: Boolean,
value: false,
},
totalAmount: {
type: Number,
value: 1,
},
totalGoodsNum: {
type: Number,
value: 0,
observer(num) {
const isDisabled = num == 0;
setTimeout(() => {
this.setData({
isDisabled,
});
});
},
},
totalDiscountAmount: {
type: Number,
value: 0,
},
bottomHeight: {
type: Number,
value: 100,
},
fixed: Boolean,
},
data: {
isDisabled: false,
},
methods: {
handleSelectAll() {
const { isAllSelected } = this.data;
this.setData({
isAllSelected: !isAllSelected,
});
this.triggerEvent('handleSelectAll', {
isAllSelected: isAllSelected,
});
},
handleToSettle() {
if (this.data.isDisabled) return;
this.triggerEvent('handleToSettle');
},
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"price": "/components/price/index",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,31 @@
<view class="cart-bar__placeholder" wx:if="{{fixed}}" />
<view class="cart-bar {{fixed ? 'cart-bar--fixed' : ''}} flex flex-v-center" style="bottom: {{fixed ? 'calc(' + bottomHeight + 'rpx + env(safe-area-inset-bottom))' : ''}};">
<t-icon
size="40rpx"
color="{{isAllSelected ? '#FA4126' : '#BBBBBB'}}"
name="{{isAllSelected ? 'check-circle-filled' : 'circle'}}"
class="cart-bar__check"
catchtap="handleSelectAll"
/>
<text>全选</text>
<view class="cart-bar__total flex1">
<view>
<text class="cart-bar__total--bold text-padding-right">总计</text>
<price
price="{{totalAmount || '0'}}"
fill="{{false}}"
decimalSmaller
class="cart-bar__total--bold cart-bar__total--price"
/>
<text class="cart-bar__total--normal">(不含运费)</text>
</view>
<view wx:if="{{totalDiscountAmount}}">
<text class="cart-bar__total--normal text-padding-right">已优惠</text>
<price class="cart-bar__total--normal" price="{{totalDiscountAmount || '0'}}" fill="{{false}}" />
</view>
</view>
<view catchtap="handleToSettle" class="{{!isDisabled ? '' : 'disabled-btn'}} account-btn" hover-class="{{!isDisabled ? '' : 'hover-btn'}}">
去结算({{totalGoodsNum}})
</view>
</view>

View File

@@ -0,0 +1,80 @@
.cart-bar__placeholder {
height: 100rpx;
}
.flex {
display: flex;
}
.flex-v-center {
align-items: center;
}
.flex1 {
flex: 1;
}
.algin-bottom {
text-align: end;
}
.cart-bar--fixed {
position: fixed;
left: 0;
right: 0;
z-index: 99;
bottom: calc(100rpx + env(safe-area-inset-bottom));
}
.cart-bar {
height: 112rpx;
background-color: #fff;
border-top: 1rpx solid #e5e5e5;
padding: 16rpx 32rpx;
box-sizing: border-box;
font-size: 24rpx;
line-height: 36rpx;
color: #333;
}
.cart-bar .cart-bar__check {
margin-right: 12rpx;
}
.cart-bar .cart-bar__total {
margin-left: 24rpx;
}
.cart-bar .account-btn {
width: 192rpx;
height: 80rpx;
border-radius: 40rpx;
background-color: #fa4126;
font-size: 28rpx;
font-weight: bold;
line-height: 80rpx;
color: #ffffff;
text-align: center;
}
.cart-bar .disabled-btn {
background-color: #cccccc !important;
}
.cart-bar .hover-btn {
opacity: 0.5;
}
.cart-bar__total .cart-bar__total--bold {
font-size: 28rpx;
line-height: 40rpx;
color: #333;
font-weight: bold;
}
.cart-bar__total .cart-bar__total--normal {
font-size: 24rpx;
line-height: 32rpx;
color: #999;
}
.cart-bar__total .cart-bar__total--price {
color: #fa4126;
font-weight: bold;
}
.text-padding-right {
padding-right: 4rpx;
}

View File

@@ -0,0 +1,23 @@
Component({
properties: {
imgUrl: {
type: String,
value:
'https://cdn-we-retail.ym.tencent.com/miniapp/template/empty-cart.png',
},
tip: {
type: String,
value: '购物车是空的',
},
btnText: {
type: String,
value: '去首页',
},
},
data: {},
methods: {
handleClick() {
this.triggerEvent('handleClick');
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,6 @@
<view class="cart-empty">
<t-image t-class="cart-img" src="{{imgUrl}}" />
<view class="tip">{{tip}}</view>
<view class="btn" bind:tap="handleClick">{{btnText}}</view>
</view>

View File

@@ -0,0 +1,33 @@
.cart-empty {
padding: 64rpx 0rpx;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
height: calc(100vh - 100rpx);
background-color: #f5f5f5;
}
.cart-empty .cart-img {
width: 160rpx;
height: 160rpx;
margin-bottom: 24rpx;
}
.cart-empty .tip {
font-size: 28rpx;
line-height: 40rpx;
color: #999;
margin-bottom: 24rpx;
}
.cart-empty .btn {
width: 240rpx;
height: 72rpx;
border-radius: 36rpx;
text-align: center;
line-height: 72rpx;
border: 2rpx solid #fa4126;
color: #fa4126;
background-color: transparent;
font-size: 28rpx;
font-weight: bold;
}

View File

@@ -0,0 +1,166 @@
import Toast from 'tdesign-miniprogram/toast/index';
const shortageImg =
'https://cdn-we-retail.ym.tencent.com/miniapp/cart/shortage.png';
Component({
isSpecsTap: false, // 标记本次点击事件是否因为点击specs触发由于底层goods-card组件没有catch specs点击事件只能在此处加状态来避免点击specs时触发跳转商品详情
externalClasses: ['wr-class'],
properties: {
storeGoods: {
type: Array,
observer(storeGoods) {
for (const store of storeGoods) {
for (const activity of store.promotionGoodsList) {
for (const goods of activity.goodsPromotionList) {
goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
}
}
for (const goods of store.shortageGoodsList) {
goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
}
}
this.setData({ _storeGoods: storeGoods });
},
},
invalidGoodItems: {
type: Array,
observer(invalidGoodItems) {
invalidGoodItems.forEach((goods) => {
goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
});
this.setData({ _invalidGoodItems: invalidGoodItems });
},
},
thumbWidth: { type: null },
thumbHeight: { type: null },
},
data: {
shortageImg,
isShowSpecs: false,
currentGoods: {},
isShowToggle: false,
_storeGoods: [],
_invalidGoodItems: [],
},
methods: {
// 删除商品
deleteGoods(e) {
const { goods } = e.currentTarget.dataset;
this.triggerEvent('delete', { goods });
},
// 清空失效商品
clearInvalidGoods() {
this.triggerEvent('clearinvalidgoods');
},
// 选中商品
selectGoods(e) {
const { goods } = e.currentTarget.dataset;
this.triggerEvent('selectgoods', {
goods,
isSelected: !goods.isSelected,
});
},
changeQuantity(num, goods) {
this.triggerEvent('changequantity', {
goods,
quantity: num,
});
},
changeStepper(e) {
const { value } = e.detail;
const { goods } = e.currentTarget.dataset;
let num = value;
if (value > goods.stack) {
num = goods.stack;
}
this.changeQuantity(num, goods);
},
input(e) {
const { value } = e.detail;
const { goods } = e.currentTarget.dataset;
const num = value;
this.changeQuantity(num, goods);
},
overlimit(e) {
const text =
e.detail.type === 'minus'
? '该商品数量不能减少了哦'
: '同一商品最多购买999件';
Toast({
context: this,
selector: '#t-toast',
message: text,
});
},
// 去凑单/再逛逛
gotoBuyMore(e) {
const { promotion, storeId = '' } = e.currentTarget.dataset;
this.triggerEvent('gocollect', { promotion, storeId });
},
// 选中门店
selectStore(e) {
const { storeIndex } = e.currentTarget.dataset;
const store = this.data.storeGoods[storeIndex];
const isSelected = !store.isSelected;
if (store.storeStockShortage && isSelected) {
Toast({
context: this,
selector: '#t-toast',
message: '部分商品库存不足',
});
return;
}
this.triggerEvent('selectstore', {
store,
isSelected,
});
},
// 展开/收起切换
showToggle() {
this.setData({
isShowToggle: !this.data.isShowToggle,
});
},
// 展示规格popup
specsTap(e) {
this.isSpecsTap = true;
const { goods } = e.currentTarget.dataset;
this.setData({
isShowSpecs: true,
currentGoods: goods,
});
},
hideSpecsPopup() {
this.setData({
isShowSpecs: false,
});
},
goGoodsDetail(e) {
if (this.isSpecsTap) {
this.isSpecsTap = false;
return;
}
const { goods } = e.currentTarget.dataset;
this.triggerEvent('goodsclick', { goods });
},
gotoCoupons() {
wx.navigateTo({ url: '/pages/coupon/coupon-list/index' });
},
},
});

View File

@@ -0,0 +1,11 @@
{
"component": true,
"usingComponents": {
"t-toast": "tdesign-miniprogram/toast/toast",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-stepper": "tdesign-miniprogram/stepper/stepper",
"swipeout": "/components/swipeout/index",
"goods-card": "../../components/goods-card/index",
"specs-popup": "../../components/specs-popup/index"
}
}

View File

@@ -0,0 +1,152 @@
<wxs src="./index.wxs" module="handlePromotion" />
<wxs src="./utils.wxs" module="utils" />
<view class="cart-group">
<view class="goods-wrap" wx:for="{{_storeGoods}}" wx:for-item="store" wx:for-index="si" wx:key="storeId">
<view class="cart-store">
<t-icon
size="40rpx"
color="{{store.isSelected ? '#FA4126' : '#BBBBBB'}}"
name="{{store.isSelected ? 'check-circle-filled' : 'circle'}}"
class="cart-store__check"
bindtap="selectStore"
data-store-index="{{si}}"
/>
<view class="cart-store__content">
<view class="store-title">
<t-icon prefix="wr" size="40rpx" color="#333333" name="store" />
<view class="store-name">{{store.storeName}}</view>
</view>
<view class="get-coupon" catch:tap="gotoCoupons">优惠券</view>
</view>
</view>
<block wx:for="{{store.promotionGoodsList}}" wx:for-item="promotion" wx:for-index="promoindex" wx:key="promoindex">
<view
class="promotion-wrap"
wx:if="{{handlePromotion.hasPromotion(promotion.promotionCode)}}"
bindtap="gotoBuyMore"
data-promotion="{{promotion}}"
data-store-id="{{store.storeId}}"
>
<view class="promotion-title">
<view class="promotion-icon">{{promotion.tag}}</view>
<view class="promotion-text">{{promotion.description}}</view>
</view>
<view class="promotion-action action-btn" hover-class="action-btn--active">
<view class="promotion-action-label"> {{promotion.isNeedAddOnShop == 1 ? '去凑单' : '再逛逛'}} </view>
<t-icon name="chevron-right" size="32rpx" color="#BBBBBB" />
</view>
</view>
<view
class="goods-item"
wx:for="{{promotion.goodsPromotionList}}"
wx:for-item="goods"
wx:for-index="gi"
wx:key="extKey"
>
<swipeout right-width="{{ 72 }}">
<view class="goods-item-info">
<view class="check-wrap" catchtap="selectGoods" data-goods="{{goods}}">
<t-icon
size="40rpx"
color="{{goods.isSelected ? '#FA4126' : '#BBBBBB'}}"
name="{{goods.isSelected ? 'check-circle-filled' : 'circle'}}"
class="check"
/>
</view>
<view class="goods-sku-info">
<goods-card
layout="horizontal-wrap"
thumb-width="{{thumbWidth}}"
thumb-height="{{thumbHeight}}"
centered="{{true}}"
data="{{goods}}"
data-goods="{{goods}}"
catchspecs="specsTap"
catchclick="goGoodsDetail"
>
<view slot="thumb-cover" class="stock-mask" wx:if="{{goods.shortageStock || goods.stockQuantity <= 3}}">
仅剩{{goods.stockQuantity}}件
</view>
<view slot="append-body" class="goods-stepper">
<view class="stepper-tip" wx:if="{{goods.shortageStock}}">库存不足</view>
<t-stepper
classname="stepper-info"
value="{{goods.quantity}}"
min="{{1}}"
max="{{999}}"
data-goods="{{goods}}"
data-gi="{{gi}}"
data-si="{{si}}"
catchchange="changeStepper"
catchblur="input"
catchoverlimit="overlimit"
theme="filled"
/>
</view>
</goods-card>
</view>
</view>
<view slot="right" class="swiper-right-del" bindtap="deleteGoods" data-goods="{{goods}}"> 删除 </view>
</swipeout>
</view>
<view
class="promotion-line-wrap"
wx:if="{{handlePromotion.hasPromotion(promotion.promotionCode) && promoindex != (store.promotionGoodsList.length - 2)}}"
>
<view class="promotion-line" />
</view>
</block>
<block wx:if="{{store.shortageGoodsList.length>0}}">
<view
class="goods-item"
wx:for="{{store.shortageGoodsList}}"
wx:for-item="goods"
wx:for-index="gi"
wx:key="extKey"
>
<swipeout right-width="{{ 72 }}">
<view class="goods-item-info">
<view class="check-wrap">
<view class="unCheck-icon" />
</view>
<view class="goods-sku-info">
<goods-card
layout="horizontal-wrap"
thumb-width="{{thumbWidth}}"
thumb-height="{{thumbHeight}}"
centered="{{true}}"
data="{{goods}}"
data-goods="{{goods}}"
catchspecs="specsTap"
catchclick="goGoodsDetail"
>
<view slot="thumb-cover" class="no-storage-mask" wx:if="{{goods.stockQuantity <=0}}">
<view class="no-storage-content">无货</view>
</view>
</goods-card>
</view>
</view>
<view slot="right" class="swiper-right-del" bindtap="deleteGoods" data-goods="{{goods}}"> 删除 </view>
</swipeout>
</view>
<view
class="promotion-line-wrap"
wx:if="{{handlePromotion.hasPromotion(promotion.promotionCode) && promoindex != (store.promotionGoodsList.length - 2)}}"
>
<view class="promotion-line" />
</view>
</block>
</view>
</view>
<specs-popup
show="{{isShowSpecs}}"
title="{{currentGoods.title || ''}}"
price="{{currentGoods.price || ''}}"
thumb="{{utils.imgCut(currentGoods.thumb, 180, 180)}}"
specs="{{currentGoods.specs || []}}"
zIndex="{{999}}"
bindclose="hideSpecsPopup"
/>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,5 @@
var hasPromotion = function (code) {
return code && code !== 'EMPTY_PROMOTION';
};
module.exports.hasPromotion = hasPromotion;

View File

@@ -0,0 +1,335 @@
.cart-group {
border-radius: 8rpx;
}
.cart-group .goods-wrap {
margin-top: 40rpx;
background-color: #fff;
border-radius: 8rpx;
overflow: hidden;
}
.cart-group .goods-wrap:first-of-type {
margin-top: 0;
}
.cart-group .cart-store {
height: 96rpx;
background-color: #fff;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0rpx 24rpx 0rpx 36rpx;
}
.cart-group .cart-store .cart-store__check {
padding: 28rpx 32rpx 28rpx 0rpx;
}
.cart-group .cart-store__content {
box-sizing: border-box;
flex: auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.cart-group .cart-store__content .store-title {
flex: auto;
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
display: flex;
align-items: center;
font-weight: bold;
overflow: hidden;
}
.cart-group .cart-store__content .store-title .wr-store {
font-size: 32rpx;
}
.cart-group .cart-store__content .store-title .store-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-left: 12rpx;
}
.cart-group .cart-store__content .get-coupon {
width: 112rpx;
height: 40rpx;
border-radius: 20rpx;
background-color: #ffecf9;
line-height: 40rpx;
text-align: center;
font-size: 26rpx;
color: #fa4126;
}
.cart-group .promotion-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0rpx 24rpx 32rpx 36rpx;
background-color: #ffffff;
font-size: 24rpx;
line-height: 36rpx;
color: #222427;
}
.cart-group .promotion-wrap .promotion-title {
font-weight: bold;
flex: auto;
overflow: hidden;
margin-right: 20rpx;
display: flex;
align-items: center;
}
.cart-group .promotion-wrap .promotion-title .promotion-icon {
flex: none;
font-weight: normal;
display: inline-block;
padding: 0 8rpx;
color: #ffffff;
background: #fa4126;
font-size: 20rpx;
height: 32rpx;
line-height: 32rpx;
margin-right: 16rpx;
border-radius: 16rpx;
}
.cart-group .promotion-wrap .promotion-title .promotion-text {
flex: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cart-group .promotion-wrap .promotion-action {
flex: none;
color: #333333;
}
.cart-group .promotion-line-wrap {
background-color: #fff;
height: 2rpx;
display: flex;
justify-content: flex-end;
}
.cart-group .promotion-line-wrap .promotion-line {
width: 684rpx;
height: 2rpx;
background-color: #e6e6e6;
}
.cart-group .goods-item-info {
display: flex;
background-color: #fff;
align-items: flex-start;
}
.cart-group .goods-item-info .check-wrap {
margin-top: 56rpx;
padding: 20rpx 28rpx 20rpx 36rpx;
}
.cart-group .goods-item-info .check-wrap .unCheck-icon {
box-sizing: border-box;
width: 36rpx;
height: 36rpx;
border-radius: 20rpx;
background: #f5f5f5;
border: 2rpx solid #bbbbbb;
}
.cart-group .goods-item-info .goods-sku-info {
padding: 0rpx 32rpx 40rpx 0;
flex-grow: 1;
}
.cart-group .goods-item-info .goods-sku-info .stock-mask {
position: absolute;
color: #fff;
font-size: 24rpx;
bottom: 0rpx;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
height: 40rpx;
line-height: 40rpx;
text-align: center;
}
.cart-group .goods-item-info .goods-sku-info .goods-stepper {
position: absolute;
right: 0;
bottom: 8rpx;
}
.cart-group .goods-item-info .goods-sku-info .goods-stepper .stepper-tip {
position: absolute;
top: -36rpx;
right: 0;
height: 28rpx;
color: #ff2525;
font-size: 20rpx;
line-height: 28rpx;
}
.cart-group .shortage-line {
width: 662rpx;
height: 2rpx;
background-color: #e6e6e6;
margin: 0 auto;
}
.cart-group .shortage-goods-wrap {
background-color: #fff;
}
.cart-group .shortage-goods-wrap .shortage-tip-title {
height: 72rpx;
line-height: 72rpx;
padding-left: 28rpx;
font-size: 24rpx;
color: #999;
}
.stepper-info {
margin-left: auto;
}
.invalid-goods-wrap {
background-color: #fff;
border-radius: 8rpx;
margin-top: 40rpx;
}
.invalid-goods-wrap .invalid-head {
display: flex;
justify-content: space-between;
padding: 30rpx 20rpx;
font-size: 24rpx;
border-bottom: 2rpx solid #f6f6f6;
}
.invalid-goods-wrap .invalid-head .invalid-title {
color: #333;
font-size: 28rpx;
font-weight: 600;
}
.invalid-goods-wrap .invalid-head .invalid-clear {
color: #fa4126;
}
.invalid-goods-wrap .toggle {
display: flex;
height: 80rpx;
justify-content: center;
align-items: center;
font-size: 24rpx;
color: #fa4126;
}
.invalid-goods-wrap .toggle .m-r-6 {
margin-right: 6rpx;
}
.invalid-goods-wrap .toggle .top-icon {
display: inline-block;
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-bottom: 10rpx solid #fa4126;
}
.invalid-goods-wrap .toggle .down-icon {
display: inline-block;
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #fa4126;
}
.action-btn {
display: flex;
align-items: center;
}
.action-btn .action-btn-arrow {
font-size: 20rpx;
margin-left: 8rpx;
}
.action-btn--active {
opacity: 0.5;
}
.swiper-right-del {
height: calc(100% - 40rpx);
width: 144rpx;
background-color: #fa4126;
font-size: 28rpx;
color: white;
display: flex;
justify-content: center;
align-items: center;
}
.goods-stepper .stepper {
border: none;
border-radius: 0;
height: auto;
width: 168rpx;
overflow: visible;
}
.goods-stepper .stepper .stepper__minus,
.goods-stepper .stepper .stepper__plus {
width: 44rpx;
height: 44rpx;
background-color: #f5f5f5;
}
.goods-stepper .stepper .stepper__minus--hover,
.goods-stepper .stepper .stepper__plus--hover {
background-color: #f5f5f5;
}
.goods-stepper .stepper .stepper__minus .wr-icon,
.goods-stepper .stepper .stepper__plus .wr-icon {
font-size: 24rpx;
}
.goods-stepper .stepper .stepper__minus {
position: relative;
}
.goods-stepper .stepper .stepper__minus::after {
position: absolute;
display: block;
content: ' ';
left: -20rpx;
right: -5rpx;
top: -20rpx;
bottom: -20rpx;
background-color: transparent;
}
.goods-stepper .stepper .stepper__plus {
position: relative;
}
.goods-stepper .stepper .stepper__plus::after {
position: absolute;
display: block;
content: ' ';
left: -5rpx;
right: -20rpx;
top: -20rpx;
bottom: -20rpx;
background-color: transparent;
}
.goods-stepper .stepper .stepper__input {
width: 72rpx;
height: 44rpx;
background-color: #f5f5f5;
font-size: 24rpx;
color: #222427;
font-weight: 600;
border-left: none;
border-right: none;
min-height: 40rpx;
margin: 0 4rpx;
display: flex;
align-items: center;
}
.goods-sku-info .no-storage-mask {
position: absolute;
color: #fff;
bottom: 0rpx;
left: 0rpx;
background-color: rgba(0, 0, 0, 0.1);
height: 192rpx;
width: 192rpx;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
.no-storage-mask .no-storage-content {
width: 128rpx;
height: 128rpx;
border-radius: 64rpx;
background-color: rgba(0, 0, 0, 0.4);
text-align: center;
line-height: 128rpx;
font-size: 28rpx;
}

View File

@@ -0,0 +1,20 @@
module.exports.slice = function(arr) {
return arr.slice(0, 2);
};
module.exports.imgCut = function(url, width, height) {
if (url && (url.slice(0, 5) === 'http:' || url.slice(0, 6) === 'https:' || url.slice(0, 2) === '//')) {
var argsStr = 'imageMogr2/thumbnail/!' + width + 'x' + height + 'r';
if (url.indexOf('?') > -1) {
url = url + '&' + argsStr;
} else {
url = url + '?' + argsStr;
}
if (url.slice(0, 5) === 'http:') {
url = 'https://' + url.slice(5)
}
if (url.slice(0, 2) === '//') {
url = 'https:' + url
}
}
return url;
};

View File

@@ -0,0 +1,243 @@
Component({
options: {
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
addGlobalClass: true,
},
intersectionObserverContext: null,
externalClasses: [
'card-class',
'title-class',
'desc-class',
'num-class',
'thumb-class',
'specs-class',
'price-class',
'origin-price-class',
'price-prefix-class',
],
properties: {
hidden: {
// 设置为null代表不做类型转换
type: null,
value: false,
observer(hidden) {
// null就是代表没有设置没有设置的话不setData防止祖先组件触发的setHidden操作被覆盖
if (hidden !== null) {
this.setHidden(!!hidden);
}
},
},
id: {
type: String,
// `goods-card-88888888`
// 不能在这里写生成逻辑如果在这里写那么假设有多个goods-list时他们将共享这个值
value: '',
observer: (id) => {
this.genIndependentID(id);
if (this.properties.thresholds?.length) {
this.createIntersectionObserverHandle();
}
},
},
data: {
type: Object,
observer(goods) {
// 有ID的商品才渲染
if (!goods) {
return;
}
/** 划线价是否有效 */
let isValidityLinePrice = true;
// 判断一次划线价格是否合理
if (
goods.originPrice &&
goods.price &&
goods.originPrice < goods.price
) {
isValidityLinePrice = false;
}
// 敲定换行数量默认值
if (goods.lineClamp === undefined || goods.lineClamp <= 0) {
// tag数组长度 大于0 且 可见
// 指定换行为1行
if ((goods.tags?.length || 0) > 0 && !goods.hideKey?.tags) {
goods.lineClamp = 1;
} else {
goods.lineClamp = 2;
}
}
this.setData({ goods, isValidityLinePrice });
},
},
layout: {
type: String,
value: 'horizontal',
},
thumbMode: {
type: String,
value: 'aspectFill',
},
priceFill: {
type: Boolean,
value: true,
},
currency: {
type: String,
value: '¥',
},
lazyLoad: {
type: Boolean,
value: false,
},
centered: {
type: Boolean,
value: false,
},
pricePrefix: {
type: String,
value: '',
},
/** 元素可见监控阈值, 数组长度大于0就创建 */
thresholds: {
type: Array,
value: [],
observer(current) {
if (current && current.length) {
this.createIntersectionObserverHandle();
} else {
this.clearIntersectionObserverHandle();
}
},
},
specsIconClassPrefix: {
type: String,
value: 'wr',
},
specsIcon: {
type: String,
value: 'expand_more',
},
addCartIconClassPrefix: {
type: String,
value: 'wr',
},
addCartIcon: {
type: String,
value: 'cart',
},
},
data: {
hiddenInData: false,
independentID: '',
goods: { id: '' },
/** 保证划线价格不小于原价,否则不渲染划线价 */
isValidityLinePrice: false,
},
lifetimes: {
ready() {
this.init();
},
detached() {
this.clear();
},
},
methods: {
clickHandle() {
this.triggerEvent('click', { goods: this.data.goods });
},
clickThumbHandle() {
this.triggerEvent('thumb', { goods: this.data.goods });
},
clickSpecsHandle() {
this.triggerEvent('specs', { goods: this.data.goods });
},
clickTagHandle(evt) {
const { index } = evt.currentTarget.dataset;
this.triggerEvent('tag', { goods: this.data.goods, index });
},
// 加入购物车
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, cb) {
let independentID;
if (id) {
independentID = id;
} else {
independentID = `goods-card-${~~(Math.random() * 10 ** 8)}`;
}
this.setData({ independentID }, cb);
},
init() {
const { thresholds, id, hidden } = this.properties;
if (hidden !== null) {
this.setHidden(!!hidden);
}
this.genIndependentID(id || '', () => {
if (thresholds && thresholds.length) {
this.createIntersectionObserverHandle();
}
});
},
clear() {
this.clearIntersectionObserverHandle();
},
setHidden(hidden) {
this.setData({ hiddenInData: !!hidden });
},
createIntersectionObserverHandle() {
if (this.intersectionObserverContext || !this.data.independentID) {
return;
}
this.intersectionObserverContext = wx
.createIntersectionObserver(this, {
thresholds: this.properties.thresholds,
})
.relativeToViewport();
this.intersectionObserverContext.observe(
`#${this.data.independentID}`,
(res) => {
this.intersectionObserverCB(res);
},
);
},
intersectionObserverCB(ob) {
this.triggerEvent('ob', {
goods: this.data.goods,
context: this.intersectionObserverContext,
ob,
});
},
clearIntersectionObserverHandle() {
if (this.intersectionObserverContext) {
try {
this.intersectionObserverContext.disconnect();
} catch (e) {}
this.intersectionObserverContext = null;
}
},
},
});

View File

@@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"price": "/components/price/index",
"t-tag": "tdesign-miniprogram/tag/tag",
"t-image": "/components/webp-image/index",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,75 @@
<view
id="{{independentID}}"
class="wr-goods-card card-class {{ layout }} {{ centered ? 'center' : ''}}"
bind:tap="clickHandle"
data-goods="{{ goods }}"
hidden="{{hiddenInData}}"
>
<view class="wr-goods-card__main">
<view class="wr-goods-card__thumb thumb-class" bind:tap="clickThumbHandle">
<!-- data-src 是方便加购动画读取图片用的 -->
<t-image
t-class="wr-goods-card__thumb-com"
wx:if="{{ !!goods.thumb && !goods.hideKey.thumb }}"
src="{{ goods.thumb }}"
mode="{{ thumbMode }}"
lazy-load="{{ lazyLoad }}"
/>
<slot name="thumb-cover" />
</view>
<view class="wr-goods-card__body">
<view class="wr-goods-card__long_content">
<view wx:if="{{ goods.title && !goods.hideKey.title }}" class="wr-goods-card__title title-class" style="-webkit-line-clamp: {{ goods.lineClamp }};">
<slot name="before-title" />
{{ goods.title }}
</view>
<slot name="after-title" />
<view wx:if="{{ goods.desc && !goods.hideKey.desc }}" class="wr-goods-card__desc desc-class">{{ goods.desc }}</view>
<slot name="after-desc" />
<view wx:if="{{ goods.specs && goods.specs.length > 0 && !goods.hideKey.specs }}" class="wr-goods-card__specs__desc specs-class" bind:tap="clickSpecsHandle">
<view class="wr-goods-card__specs__desc-text">{{ goods.specs }}</view>
<t-icon name="chevron-down" size="32rpx" color="#999999" />
</view>
<view class="goods_tips" wx:if="{{goods.stockQuantity !== 0 && goods.quantity >= goods.stockQuantity}}">库存不足</view>
</view>
<view class="wr-goods-card__short_content">
<block wx:if="{{goods.stockQuantity !== 0}}">
<view wx:if="{{ pricePrefix }}" class="wr-goods-card__price__prefix price-prefix-class">{{ pricePrefix }}</view>
<slot name="price-prefix" />
<view wx:if="{{ goods.price && !goods.hideKey.price }}" class="wr-goods-card__price">
<price
wr-class="price-class"
symbol="{{currency}}"
price="{{goods.price}}"
fill="{{priceFill}}"
decimalSmaller
/>
</view>
<view wx:if="{{ goods.originPrice && !goods.hideKey.originPrice && isValidityLinePrice }}" class="wr-goods-card__origin-price">
<price
wr-class="origin-price-class"
symbol="{{currency}}"
price="{{goods.originPrice}}"
fill="{{priceFill}}"
/>
</view>
<slot name="origin-price" />
<view wx:if="{{goods.num && !goods.hideKey.num}}" class="wr-goods-card__num num-class">
<text class="wr-goods-card__num__prefix">x </text>
{{ goods.num }}
</view>
</block>
<block wx:else>
<view class="no_storage">
<view>请重新选择商品规格</view>
<view class="no_storage__right">重选</view>
</view>
</block>
</view>
<slot name="append-body" />
</view>
<slot name="footer" />
</view>
<slot name="append-card" />
</view>

View File

@@ -0,0 +1,260 @@
.wr-goods-card {
box-sizing: border-box;
font-size: 24rpx;
}
/* */
.wr-goods-card__main {
position: relative;
display: flex;
padding: 0;
background: transparent;
}
.wr-goods-card.center .wr-goods-card__main {
align-items: flex-start;
justify-content: center;
}
.wr-goods-card__thumb {
flex-shrink: 0;
position: relative;
width: 140rpx;
height: 140rpx;
}
.wr-goods-card__thumb-com {
width: 192rpx;
height: 192rpx;
border-radius: 8rpx;
overflow: hidden;
}
.wr-goods-card__thumb:empty {
display: none;
margin: 0;
}
.wr-goods-card__body {
display: flex;
margin: 0 0 0 20rpx;
flex-direction: row;
flex: 1 1 auto;
min-height: 192rpx;
}
.wr-goods-card__long_content {
display: flex;
flex-direction: column;
overflow: hidden;
flex: 1 1 auto;
}
.wr-goods-card__long_content .goods_tips {
width: 100%;
margin-top: 16rpx;
text-align: right;
color: #fa4126;
font-size: 24rpx;
line-height: 32rpx;
font-weight: bold;
}
.wr-goods-card__title {
flex-shrink: 0;
font-size: 28rpx;
color: #333;
line-height: 40rpx;
font-weight: 400;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
}
.wr-goods-card__title__prefix-tags {
display: inline-flex;
}
.wr-goods-card__title__prefix-tags .prefix-tag {
margin: 0 8rpx 0 0;
}
.wr-goods-card__desc {
font-size: 24rpx;
color: #f5f5f5;
line-height: 40rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.wr-goods-card__specs__desc,
.wr-goods-card__specs__text {
font-size: 24rpx;
height: 32rpx;
line-height: 32rpx;
color: #999999;
margin: 8rpx 0;
}
.wr-goods-card__specs__desc {
display: flex;
align-self: flex-start;
flex-direction: row;
background: #f5f5f5;
border-radius: 8rpx;
padding: 4rpx 8rpx;
}
.wr-goods-card__specs__desc-text {
height: 100%;
max-width: 380rpx;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.wr-goods-card__specs__desc-icon {
line-height: inherit;
margin-left: 8rpx;
font-size: 24rpx;
color: #bbb;
}
.wr-goods-card__specs__text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.wr-goods-card__tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 16rpx 0 0 0;
}
.wr-goods-card__tag {
color: #fa550f;
background: transparent;
font-size: 20rpx;
border: 1rpx solid #fa550f;
padding: 0 8rpx;
height: 30rpx;
line-height: 30rpx;
margin: 0 8rpx 8rpx 0;
display: block;
overflow: hidden;
white-space: nowrap;
word-break: keep-all;
text-overflow: ellipsis;
}
.wr-goods-card__short_content {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-end;
margin: 0 0 0 46rpx;
}
.wr-goods-card__price__prefix {
order: 0;
color: #666;
margin: 0;
}
.wr-goods-card__price {
white-space: nowrap;
font-weight: bold;
order: 1;
color: #fa4126;
font-size: 36rpx;
margin: 0;
line-height: 48rpx;
}
.wr-goods-card__origin-price {
white-space: nowrap;
font-weight: normal;
order: 2;
color: #aaaaaa;
font-size: 24rpx;
margin: 0;
}
.wr-goods-card__num {
white-space: nowrap;
order: 4;
font-size: 24rpx;
color: #999;
margin: 20rpx 0 0 auto;
}
.wr-goods-card__num__prefix {
color: inherit;
}
.wr-goods-card__add-cart {
order: 3;
margin: auto 0 0 auto;
}
.wr-goods-card.horizontal-wrap .wr-goods-card__thumb {
width: 192rpx;
height: 192rpx;
border-radius: 8rpx;
overflow: hidden;
}
.wr-goods-card.horizontal-wrap .wr-goods-card__body {
flex-direction: column;
}
.wr-goods-card.horizontal-wrap .wr-goods-card__short_content {
flex-direction: row;
align-items: center;
margin: 16rpx 0 0 0;
}
.wr-goods-card.horizontal-wrap .wr-goods-card__num {
margin: 0 0 0 auto;
}
.wr-goods-card.vertical .wr-goods-card__main {
padding: 0 0 22rpx 0;
flex-direction: column;
}
.wr-goods-card.vertical .wr-goods-card__thumb {
width: 340rpx;
height: 340rpx;
}
.wr-goods-card.vertical .wr-goods-card__body {
margin: 20rpx 20rpx 0 20rpx;
flex-direction: column;
}
.wr-goods-card.vertical .wr-goods-card__long_content {
overflow: hidden;
}
.wr-goods-card.vertical .wr-goods-card__title {
line-height: 36rpx;
}
.wr-goods-card.vertical .wr-goods-card__short_content {
margin: 20rpx 0 0 0;
}
.wr-goods-card.vertical .wr-goods-card__price {
order: 2;
color: #fa4126;
margin: 20rpx 0 0 0;
}
.wr-goods-card.vertical .wr-goods-card__origin-price {
order: 1;
}
.wr-goods-card.vertical .wr-goods-card__add-cart {
position: absolute;
bottom: 20rpx;
right: 20rpx;
}
.wr-goods-card__short_content .no_storage {
display: flex;
align-items: center;
justify-content: space-between;
height: 40rpx;
color: #333;
font-size: 24rpx;
line-height: 32rpx;
width: 100%;
}
.no_storage .no_storage__right {
width: 80rpx;
height: 40rpx;
border-radius: 20rpx;
border: 2rpx solid #fa4126;
line-height: 40rpx;
text-align: center;
color: #fa4126;
}

View File

@@ -0,0 +1,72 @@
Component({
options: {
addGlobalClass: true,
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
},
properties: {
show: {
type: Boolean,
value: false,
},
value: {
type: String,
value: '',
},
title: {
type: String,
observer(newVal) {
this.setData({ 'goods.title': newVal });
},
},
price: {
type: String,
value: '',
observer(newVal) {
this.setData({ 'goods.price': newVal });
},
},
thumb: {
type: String,
value: '',
observer(newVal) {
this.setData({ 'goods.thumb': newVal });
},
},
thumbMode: {
type: String,
value: 'aspectFit',
},
zIndex: {
type: Number,
value: 99,
},
specs: {
type: Array,
value: [],
},
},
data: {
goods: {
title: '',
thumb: '',
price: '',
hideKey: {
originPrice: true,
tags: true,
specs: true,
num: true,
},
},
},
methods: {
onClose() {
this.triggerEvent('close');
},
onCloseOver() {
this.triggerEvent('closeover');
},
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"goods-card": "../../components/goods-card/index"
}
}

View File

@@ -0,0 +1,26 @@
<t-popup
close-on-overlay-click="{{true}}"
visible="{{show}}"
placement="bottom"
z-index="{{zIndex}}"
>
<view class="specs-popup">
<view>
<goods-card data="{{goods}}" layout="horizontal-wrap" thumb-mode="{{thumbMode}}" />
<view class="section">
<view class="title">已选规格</view>
<view class="options">
<view
wx:for="{{specs}}"
wx:for-item="spec"
wx:key="spec"
class="option"
>{{spec}}
</view>
</view>
</view>
</view>
<view class="bottom-btn" hover-class="bottom-btn--active" bindtap="onClose">我知道了</view>
</view>
</t-popup>

View File

@@ -0,0 +1,68 @@
.specs-popup {
width: 100vw;
box-sizing: border-box;
padding: 32rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)) 32rpx;
max-height: 80vh;
display: flex;
flex-direction: column;
background-color: white;
border-radius: 20rpx 20rpx 0 0;
}
.specs-popup .section {
margin-top: 44rpx;
flex: auto;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.specs-popup .section .title {
font-size: 26rpx;
color: #4f5356;
}
.specs-popup .section .options {
color: #333333;
font-size: 24rpx;
margin-right: -26rpx;
}
.specs-popup .section .options .option {
display: inline-block;
margin-top: 24rpx;
height: 56rpx;
line-height: 56rpx;
padding: 0 16rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
max-width: 100%;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.specs-popup .section .options .option:not(:last-child) {
margin-right: 26rpx;
}
.specs-popup .bottom-btn {
margin-top: 42rpx;
position: relative;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background-color: white;
color: #fa4126;
}
.specs-popup .bottom-btn--active {
opacity: 0.5;
}
.specs-popup .bottom-btn::after {
display: block;
content: ' ';
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1px solid #fa4126;
border-radius: 80rpx;
transform: scale(0.5);
transform-origin: left top;
}

View File

@@ -0,0 +1,294 @@
import Dialog from 'tdesign-miniprogram/dialog/index';
import Toast from 'tdesign-miniprogram/toast/index';
import { fetchCartGroupData } from '../../services/cart/cart';
Page({
data: {
cartGroupData: null,
},
// 调用自定义tabbar的init函数使页面与tabbar激活状态保持一致
onShow() {
this.getTabBar().init();
},
onLoad() {
this.refreshData();
},
refreshData() {
this.getCartGroupData().then((res) => {
let isEmpty = true;
const cartGroupData = res.data;
// 一些组件中需要的字段可能接口并没有返回,或者返回的数据结构与预期不一致,需要在此先对数据做一些处理
// 统计门店下加购的商品是否全选、是否存在缺货/无货
for (const store of cartGroupData.storeGoods) {
store.isSelected = true; // 该门店已加购商品是否全选
store.storeStockShortage = false; // 该门店已加购商品是否存在库存不足
if (!store.shortageGoodsList) {
store.shortageGoodsList = []; // 该门店已加购商品如果库存为0需单独分组
}
for (const activity of store.promotionGoodsList) {
activity.goodsPromotionList = activity.goodsPromotionList.filter((goods) => {
goods.originPrice = undefined;
// 统计是否有加购数大于库存数的商品
if (goods.quantity > goods.stockQuantity) {
store.storeStockShortage = true;
}
// 统计是否全选
if (!goods.isSelected) {
store.isSelected = false;
}
// 库存为0无货的商品单独分组
if (goods.stockQuantity > 0) {
return true;
}
store.shortageGoodsList.push(goods);
return false;
});
if (activity.goodsPromotionList.length > 0) {
isEmpty = false;
}
}
if (store.shortageGoodsList.length > 0) {
isEmpty = false;
}
}
cartGroupData.invalidGoodItems = cartGroupData.invalidGoodItems.map((goods) => {
goods.originPrice = undefined;
return goods;
});
cartGroupData.isNotEmpty = !isEmpty;
this.setData({ cartGroupData });
});
},
findGoods(spuId, skuId) {
let currentStore;
let currentActivity;
let currentGoods;
const { storeGoods } = this.data.cartGroupData;
for (const store of storeGoods) {
for (const activity of store.promotionGoodsList) {
for (const goods of activity.goodsPromotionList) {
if (goods.spuId === spuId && goods.skuId === skuId) {
currentStore = store;
currentActivity = currentActivity;
currentGoods = goods;
return {
currentStore,
currentActivity,
currentGoods,
};
}
}
}
}
return {
currentStore,
currentActivity,
currentGoods,
};
},
// 注:实际场景时应该调用接口获取购物车数据
getCartGroupData() {
const { cartGroupData } = this.data;
if (!cartGroupData) {
return fetchCartGroupData();
}
return Promise.resolve({ data: cartGroupData });
},
// 选择单个商品
// 注:实际场景时应该调用接口更改选中状态
selectGoodsService({ spuId, skuId, isSelected }) {
this.findGoods(spuId, skuId).currentGoods.isSelected = isSelected;
return Promise.resolve();
},
// 全选门店
// 注:实际场景时应该调用接口更改选中状态
selectStoreService({ storeId, isSelected }) {
const currentStore = this.data.cartGroupData.storeGoods.find((s) => s.storeId === storeId);
currentStore.isSelected = isSelected;
currentStore.promotionGoodsList.forEach((activity) => {
activity.goodsPromotionList.forEach((goods) => {
goods.isSelected = isSelected;
});
});
return Promise.resolve();
},
// 加购数量变更
// 注:实际场景时应该调用接口
changeQuantityService({ spuId, skuId, quantity }) {
this.findGoods(spuId, skuId).currentGoods.quantity = quantity;
return Promise.resolve();
},
// 删除加购商品
// 注:实际场景时应该调用接口
deleteGoodsService({ spuId, skuId }) {
function deleteGoods(group) {
for (const gindex in group) {
const goods = group[gindex];
if (goods.spuId === spuId && goods.skuId === skuId) {
group.splice(gindex, 1);
return gindex;
}
}
return -1;
}
const { storeGoods, invalidGoodItems } = this.data.cartGroupData;
for (const store of storeGoods) {
for (const activity of store.promotionGoodsList) {
if (deleteGoods(activity.goodsPromotionList) > -1) {
return Promise.resolve();
}
}
if (deleteGoods(store.shortageGoodsList) > -1) {
return Promise.resolve();
}
}
if (deleteGoods(invalidGoodItems) > -1) {
return Promise.resolve();
}
return Promise.reject();
},
// 清空失效商品
// 注:实际场景时应该调用接口
clearInvalidGoodsService() {
this.data.cartGroupData.invalidGoodItems = [];
return Promise.resolve();
},
onGoodsSelect(e) {
const {
goods: { spuId, skuId },
isSelected,
} = e.detail;
const { currentGoods } = this.findGoods(spuId, skuId);
Toast({
context: this,
selector: '#t-toast',
message: `${isSelected ? '选择' : '取消'}"${
currentGoods.title.length > 5 ? `${currentGoods.title.slice(0, 5)}...` : currentGoods.title
}"`,
icon: '',
});
this.selectGoodsService({ spuId, skuId, isSelected }).then(() => this.refreshData());
},
onStoreSelect(e) {
const {
store: { storeId },
isSelected,
} = e.detail;
this.selectStoreService({ storeId, isSelected }).then(() => this.refreshData());
},
onQuantityChange(e) {
const {
goods: { spuId, skuId },
quantity,
} = e.detail;
const { currentGoods } = this.findGoods(spuId, skuId);
const stockQuantity = currentGoods.stockQuantity > 0 ? currentGoods.stockQuantity : 0; // 避免后端返回的是-1
// 加购数量超过库存数量
if (quantity > stockQuantity) {
// 加购数量等于库存数量的情况下继续加购
if (currentGoods.quantity === stockQuantity && quantity - stockQuantity === 1) {
Toast({
context: this,
selector: '#t-toast',
message: '当前商品库存不足',
});
return;
}
Dialog.confirm({
title: '商品库存不足',
content: `当前商品库存不足,最大可购买数量为${stockQuantity}`,
confirmBtn: '修改为最大可购买数量',
cancelBtn: '取消',
})
.then(() => {
this.changeQuantityService({
spuId,
skuId,
quantity: stockQuantity,
}).then(() => this.refreshData());
})
.catch(() => {});
return;
}
this.changeQuantityService({ spuId, skuId, quantity }).then(() => this.refreshData());
},
goCollect() {
/** 活动肯定有一个活动ID用来获取活动banner活动商品列表等 */
const promotionID = '123';
wx.navigateTo({
url: `/pages/promotion-detail/index?promotion_id=${promotionID}`,
});
},
goGoodsDetail(e) {
const { spuId, storeId } = e.detail.goods;
wx.navigateTo({
url: `/pages/goods/details/index?spuId=${spuId}&storeId=${storeId}`,
});
},
clearInvalidGoods() {
// 实际场景时应该调用接口清空失效商品
this.clearInvalidGoodsService().then(() => this.refreshData());
},
onGoodsDelete(e) {
const {
goods: { spuId, skuId },
} = e.detail;
Dialog.confirm({
content: '确认删除该商品吗?',
confirmBtn: '确定',
cancelBtn: '取消',
}).then(() => {
this.deleteGoodsService({ spuId, skuId }).then(() => {
Toast({ context: this, selector: '#t-toast', message: '商品删除成功' });
this.refreshData();
});
});
},
onSelectAll(event) {
const { isAllSelected } = event?.detail ?? {};
Toast({
context: this,
selector: '#t-toast',
message: `${isAllSelected ? '取消' : '点击'}了全选按钮`,
});
// 调用接口改变全选
},
onToSettle() {
const goodsRequestList = [];
this.data.cartGroupData.storeGoods.forEach((store) => {
store.promotionGoodsList.forEach((promotion) => {
promotion.goodsPromotionList.forEach((m) => {
if (m.isSelected == 1) {
goodsRequestList.push(m);
}
});
});
});
wx.setStorageSync('order.goodsRequestList', JSON.stringify(goodsRequestList));
wx.navigateTo({ url: '/pages/order/order-confirm/index?type=cart' });
},
onGotoHome() {
wx.switchTab({ url: '/pages/home/home' });
},
});

View File

@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "购物车",
"usingComponents": {
"cart-group": "./components/cart-group/index",
"cart-empty": "./components/cart-empty/index",
"cart-bar": "./components/cart-bar/index",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-dialog": "tdesign-miniprogram/dialog/dialog"
}
}

View File

@@ -0,0 +1,31 @@
<!-- 分层购物车 -->
<block wx:if="{{cartGroupData.isNotEmpty}}">
<cart-group
store-goods="{{ cartGroupData.storeGoods }}"
invalid-good-items="{{ cartGroupData.invalidGoodItems }}"
bindselectgoods="onGoodsSelect"
bindselectstore="onStoreSelect"
bindchangequantity="onQuantityChange"
bindgocollect="goCollect"
bindgoodsclick="goGoodsDetail"
bindclearinvalidgoods="clearInvalidGoods"
binddelete="onGoodsDelete"
/>
<view class="gap" />
<!-- 商品小计以及结算按钮 -->
<cart-bar
is-all-selected="{{cartGroupData.isAllSelected}}"
total-amount="{{cartGroupData.totalAmount}}"
total-goods-num="{{cartGroupData.selectedGoodsCount}}"
total-discount-amount="{{cartGroupData.totalDiscountAmount}}"
fixed="{{true}}"
bottomHeight="{{112}}"
bindhandleSelectAll="onSelectAll"
bindhandleToSettle="onToSettle"
/>
</block>
<!-- 购物车空态 -->
<cart-empty wx:else bind:handleClick="onGotoHome" />
<t-toast id="t-toast" />
<t-dialog id="t-dialog" />

View File

@@ -0,0 +1,13 @@
:host {
padding-bottom: 100rpx;
}
.gap {
height: 100rpx;
width: 100%;
}
.t-button {
--td-button-default-color: #000;
--td-button-primary-text-color: #fa4126;
}

View File

@@ -0,0 +1,57 @@
const statusMap = {
default: { text: '去使用', theme: 'primary' },
useless: { text: '已使用', theme: 'default' },
disabled: { text: '已过期', theme: 'default' },
};
Component({
options: {
addGlobalClass: true,
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
},
externalClasses: ['coupon-class'],
properties: {
couponDTO: {
type: Object,
value: {}, // 优惠券数据
},
},
data: {
btnText: '',
btnTheme: '',
},
observers: {
couponDTO: function (couponDTO) {
if (!couponDTO) {
return;
}
const statusInfo = statusMap[couponDTO.status];
this.setData({
btnText: statusInfo.text,
btnTheme: statusInfo.theme,
});
},
},
attached() {},
methods: {
// 跳转到详情页
gotoDetail() {
wx.navigateTo({
url: `/pages/coupon/coupon-detail/index?id=${this.data.couponDTO.key}`,
});
},
// 跳转到商品列表
gotoGoodsList() {
wx.navigateTo({
url: `/pages/coupon/coupon-activity-goods/index?id=${this.data.couponDTO.key}`,
});
},
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"ui-coupon-card": "../ui-coupon-card/index",
"t-button": "tdesign-miniprogram/button/button"
}
}

View File

@@ -0,0 +1,23 @@
<ui-coupon-card
title="{{couponDTO.title || ''}}"
type="{{couponDTO.type || ''}}"
value="{{couponDTO.value || '0'}}"
tag="{{couponDTO.tag || ''}}"
desc="{{couponDTO.desc || ''}}"
currency="{{couponDTO.currency || ''}}"
timeLimit="{{couponDTO.timeLimit || ''}}"
status="{{couponDTO.status || ''}}"
bind:tap="gotoDetail"
>
<view slot="operator" class="coupon-btn-slot">
<t-button
t-class="coupon-btn-{{btnTheme}}"
theme="{{btnTheme}}"
variant="outline"
shape="round"
size="extra-small"
bind:tap="gotoGoodsList"
>{{btnText}}
</t-button>
</view>
</ui-coupon-card>

View File

@@ -0,0 +1,9 @@
.coupon-btn-default {
display: none;
}
.coupon-btn-primary {
--td-button-extra-small-padding-horizontal: 26rpx;
--td-button-primary-outline-color: #fa4126;
--td-button-primary-outline-border-color: #fa4126;
}

View File

@@ -0,0 +1,17 @@
Component({
data: { icon: 'cart' },
properties: {
count: {
type: Number,
},
},
methods: {
goToCart() {
wx.switchTab({
url: '/pages/cart/index',
});
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,14 @@
<view class="floating-button" bind:tap="goToCart">
<view class="floating-inner-container">
<t-icon
prefix="wr"
name="{{icon}}"
size="42rpx"
color="#FFFFFF"
/>
</view>
<view class="floating-right-top">
{{count}}
</view>
</view>

View File

@@ -0,0 +1,30 @@
.floating-button {
position: fixed;
right: 20rpx;
bottom: 108rpx;
}
.floating-button .floating-inner-container {
display: flex;
align-items: center;
justify-content: center;
height: 96rpx;
width: 96rpx;
background-color: rgba(0, 0, 0, 0.8);
opacity: 0.7;
border-radius: 48rpx;
}
.floating-button .floating-right-top {
position: absolute;
right: 0rpx;
top: 0rpx;
height: 28rpx;
background: #fa4126;
border-radius: 64rpx;
font-weight: bold;
font-size: 22rpx;
line-height: 28rpx;
color: #fff;
padding: 0 8rpx;
}

View File

@@ -0,0 +1,87 @@
Component({
options: {
addGlobalClass: true,
multipleSlots: true,
},
externalClasses: ['coupon-class'],
properties: {
mask: {
type: Boolean,
value: false, // 是否添加遮罩
},
superposable: {
type: Boolean,
value: false, // 是否可叠加
},
type: {
type: String,
value: '', // 优惠券类型CouponType
},
value: {
type: String,
value: '', // 优惠金额
},
tag: {
type: String,
value: '', // 优惠标签优惠券名字标签img
},
desc: {
type: String,
value: '', // 优惠金额描述,金额下方
},
title: {
type: String, // 优惠券名称
value: '',
},
timeLimit: {
type: String, // 优惠券时限
value: '',
},
ruleDesc: {
type: String, // 优惠券适用规则描述
value: '',
},
currency: {
type: String,
value: '¥', // 优惠货币
},
status: {
type: String,
value: 'default',
},
image: {
type: String,
value: '',
},
},
data: {
CouponType: {
MJ_COUPON: 1,
ZK_COUPON: 2,
MJF_COUPON: 3,
GIFT_COUPON: 4,
},
theme: 'primary',
},
observers: {
status: function (value) {
let theme = 'primary';
// 已过期或已使用的券 颜色置灰
if (value === 'useless' || value === 'disabled') {
theme = 'weak';
}
this.setData({ theme });
},
},
attached() {
this.setData({
color: `color${this.properties.colorStyle}`,
});
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,54 @@
<wxs module="tools">
function isBigValue(value) {
var values = (value + '').split('.');
if (values[1] && values[0].length >= 3) return true;
else return false
}
function getBigValues(value) {
return value.split('.');
}
module.exports = { isBigValue: isBigValue, getBigValues: getBigValues };
</wxs>
<view class="wr-coupon coupon-class theme-{{theme}}">
<view class="wr-coupon__left">
<view wx:if="{{type == CouponType.ZK_COUPON || type === CouponType.MERCHANT_ZK_COUPON}}">
<text class="wr-coupon__left--value">{{value}}</text>
<text class="wr-coupon__left--unit">折</text>
<view class="wr-coupon__left--desc">{{desc}}</view>
</view>
<view wx:if="{{type == CouponType.MJ_COUPON || type === CouponType.MERCHANT_MJ_COUPON}}">
<text class="wr-coupon__left--value" wx:if="{{tools.isBigValue(value)}}">
<text class="wr-coupon__left--value-int">{{tools.getBigValues(value)[0]}}</text>
<text class="wr-coupon__left--value-decimal">.{{tools.getBigValues(value)[1]}}</text>
</text>
<text class="wr-coupon__left--value" wx:else>{{value / 100}}</text>
<text class="wr-coupon__left--unit">元</text>
<view class="wr-coupon__left--desc">{{desc}}</view>
</view>
<view wx:if="{{type === CouponType.MJF_COUPON || type === CouponType.MYF_COUPON}}">
<text class="wr-coupon__left--value" style="font-family: PingFang SC;font-size: 44rpx">免邮</text>
<view class="wr-coupon__left--desc">{{desc}}</view>
</view>
<view wx:if="{{type == CouponType.GIFT_COUPON}}">
<t-image t-class="wr-coupon__left--image" src="{{image}}" mode="aspectFill" />
</view>
</view>
<view class="wr-coupon__right">
<view class="wr-coupon__right--title">
<text class="coupon-title">{{title}}</text>
<view class="coupon-time">{{timeLimit}}</view>
<view class="coupon-desc">
<view wx:if="{{ruleDesc}}">{{ruleDesc}}</view>
</view>
</view>
<view class="wr-coupon__right--oper">
<slot name="operator" />
</view>
</view>
<view wx:if="{{status === 'useless' || status === 'disabled'}}" class="wr-coupon__seal seal-{{status}}}" />
<view wx:if="{{mask}}" class="wr-coupon__mask" />
<view wx:if="{{superposable}}" class="wr-coupon__tag">可叠加</view>
</view>

View File

@@ -0,0 +1,147 @@
.wr-coupon {
display: flex;
background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-bg-nocorners.png');
background-size: 100% 100%;
background-repeat: no-repeat;
position: relative;
margin-bottom: 24rpx;
overflow: hidden;
}
.theme-weak.wr-coupon {
background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-bg-grey2.png');
}
.wr-coupon__left {
width: 200rpx;
height: 180rpx;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
color: #fa4126;
overflow: hidden;
position: relative;
}
.theme-weak .wr-coupon__left {
color: #333;
}
.wr-coupon__left--value {
font-size: 64rpx;
line-height: 88rpx;
font-weight: bold;
font-family: 'DIN Alternate', cursive;
}
.wr-coupon__left--value-int {
font-size: 48rpx;
line-height: 88rpx;
}
.wr-coupon__left--value-decimal {
font-size: 36rpx;
line-height: 48rpx;
}
.wr-coupon__left--image {
width: 128rpx;
height: 128rpx;
border-radius: 8px;
margin-top: 30rpx;
}
.wr-coupon__left--unit {
font-size: 24rpx;
line-height: 32rpx;
}
.wr-coupon__left--desc {
font-size: 24rpx;
line-height: 32rpx;
color: #fa4126;
}
.theme-weak .wr-coupon__left--desc {
color: #333;
}
.wr-coupon__right {
flex-grow: 1;
padding: 0 20rpx;
height: 180rpx;
box-sizing: border-box;
overflow: hidden;
display: flex;
align-items: center;
}
.wr-coupon__right--title {
display: flex;
-webkit-display: flex;
flex-direction: column;
align-items: flex-start;
color: #999999;
font-size: 24rpx;
flex: 1;
}
.wr-coupon__right--title .coupon-title {
max-width: 320rpx;
color: #333333;
font-size: 28rpx;
line-height: 40rpx;
font-weight: bold;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
white-space: normal;
}
.wr-coupon__right--title .coupon-time {
margin-top: 16rpx;
/* // letter-spacing: -0.05em; */
}
.wr-coupon__right--title .coupon-desc {
margin-top: 8rpx;
}
.wr-coupon__right--title .coupon-arrow {
font-size: 22rpx;
}
.wr-coupon__right--oper {
display: flex;
justify-content: center;
align-items: center;
}
.wr-coupon__mask {
width: 702rpx;
height: 182rpx;
position: absolute;
top: 0;
left: 0;
background-color: #ffffff;
opacity: 0.5;
}
.wr-coupon__tag {
position: absolute;
top: 8px;
right: -24rpx;
text-align: center;
width: 106rpx;
height: 28rpx;
opacity: 0.9;
font-size: 20rpx;
line-height: 28rpx;
color: #fa4126;
border: 0.5px solid #fa4126;
box-sizing: border-box;
transform: rotate(45deg);
}
.wr-coupon__seal {
width: 128rpx;
height: 128rpx;
position: absolute;
top: 0;
right: 0;
background-size: 100% 100%;
}
.wr-coupon__seal.seal-useless {
background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/seal-used.png');
}
.wr-coupon__seal.seal-disabled {
background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-expired.png');
}

View File

@@ -0,0 +1,78 @@
import { fetchCouponDetail } from '../../../services/coupon/index';
import { fetchGoodsList } from '../../../services/good/fetchGoods';
import Toast from 'tdesign-miniprogram/toast/index';
Page({
data: {
goods: [],
detail: {},
couponTypeDesc: '',
showStoreInfoList: false,
cartNum: 2,
},
id: '',
onLoad(query) {
const id = parseInt(query.id);
this.id = id;
this.getCouponDetail(id);
this.getGoodsList(id);
},
getCouponDetail(id) {
fetchCouponDetail(id).then(({ detail }) => {
this.setData({ detail });
if (detail.type === 2) {
if (detail.base > 0) {
this.setData({
couponTypeDesc: `${detail.base / 100}${detail.value}`,
});
} else {
this.setData({ couponTypeDesc: `${detail.value}` });
}
} else if (detail.type === 1) {
if (detail.base > 0) {
this.setData({
couponTypeDesc: `${detail.base / 100}元减${detail.value / 100}`,
});
} else {
this.setData({ couponTypeDesc: `${detail.value / 100}` });
}
}
});
},
getGoodsList(id) {
fetchGoodsList(id).then((goods) => {
this.setData({ goods });
});
},
openStoreList() {
this.setData({
showStoreInfoList: true,
});
},
closeStoreList() {
this.setData({
showStoreInfoList: false,
});
},
goodClickHandle(e) {
const { index } = e.detail;
const { spuId } = this.data.goods[index];
wx.navigateTo({ url: `/pages/goods/details/index?spuId=${spuId}` });
},
cartClickHandle() {
Toast({
context: this,
selector: '#t-toast',
message: '点击加入购物车',
});
},
});

View File

@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "活动商品",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-popup": "tdesign-miniprogram/popup/popup",
"t-toast": "tdesign-miniprogram/toast/toast",
"goods-list": "/components/goods-list/index",
"floating-button": "../components/floating-button/index"
}
}

View File

@@ -0,0 +1,40 @@
<view class="coupon-page-container">
<view class="notice-bar-content">
<view class="notice-bar-text">
以下商品可使用
<text class="height-light">{{couponTypeDesc}}</text>
优惠券
</view>
<t-icon name="help-circle" size="32rpx" color="#AAAAAA" bind:tap="openStoreList" />
</view>
<view class="goods-list-container">
<goods-list
wr-class="goods-list-wrap"
goodsList="{{goods}}"
bind:click="goodClickHandle"
bind:addcart="cartClickHandle"
/>
</view>
<floating-button count="{{cartNum}}" />
<t-popup visible="{{showStoreInfoList}}" placement="bottom" bind:visible-change="closeStoreList">
<t-icon slot="closeBtn" name="close" size="40rpx" bind:tap="closeStoreList" />
<view class="popup-content-wrap">
<view class="popup-content-title"> 规则详情 </view>
<view class="desc-group-wrap">
<view wx:if="{{detail && detail.timeLimit}}" class="item-wrap">
<view class="item-title">优惠券有效时间</view>
<view class="item-label">{{detail.timeLimit}}</view>
</view>
<view wx:if="{{detail && detail.desc}}" class="item-wrap">
<view class="item-title">优惠券说明</view>
<view class="item-label">{{detail.desc}}</view>
</view>
<view wx:if="{{detail && detail.useNotes}}" class="item-wrap">
<view class="item-title">使用须知</view>
<view class="item-label">{{detail.useNotes}}</view>
</view>
</view>
</view>
</t-popup>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,69 @@
page {
background-color: #f5f5f5;
}
.coupon-page-container .notice-bar-content {
display: flex;
flex-direction: row;
align-items: center;
padding: 8rpx 0;
}
.coupon-page-container .notice-bar-text {
font-size: 26rpx;
line-height: 36rpx;
font-weight: 400;
color: #666666;
margin-left: 24rpx;
margin-right: 12rpx;
}
.coupon-page-container .notice-bar-text .height-light {
color: #fa550f;
}
.coupon-page-container .popup-content-wrap {
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
.coupon-page-container .popup-content-title {
font-size: 32rpx;
color: #333;
text-align: center;
height: 104rpx;
line-height: 104rpx;
position: relative;
}
.coupon-page-container .desc-group-wrap {
padding-bottom: env(safe-area-inset-bottom);
}
.coupon-page-container .desc-group-wrap .item-wrap {
margin: 0 30rpx 30rpx;
}
.coupon-page-container .desc-group-wrap .item-title {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.coupon-page-container .desc-group-wrap .item-label {
font-size: 24rpx;
color: #666;
margin-top: 12rpx;
white-space: pre-line;
word-break: break-all;
line-height: 34rpx;
}
.coupon-page-container .goods-list-container {
margin: 0 24rpx 24rpx;
}
.coupon-page-container .goods-list-wrap {
background: #f5f5f5 !important;
}

View File

@@ -0,0 +1,32 @@
import { fetchCouponDetail } from '../../../services/coupon/index';
Page({
data: {
detail: null,
storeInfoList: [],
storeInfoStr: '',
showStoreInfoList: false,
},
id: '',
onLoad(query) {
const id = parseInt(query.id);
this.id = id;
this.getGoodsList(id);
},
getGoodsList(id) {
fetchCouponDetail(id).then(({ detail }) => {
this.setData({
detail,
});
});
},
navGoodListHandle() {
wx.navigateTo({
url: `/pages/coupon/coupon-activity-goods/index?id=${this.id}`,
});
},
});

View File

@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "优惠券详情",
"usingComponents": {
"coupon-card": "../components/coupon-card/index",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-button": "tdesign-miniprogram/button/button",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,45 @@
<!-- 优惠券 -->
<view class="coupon-card-wrap">
<coupon-card couponDTO="{{detail}}" />
</view>
<!-- 说明 -->
<view class="desc-wrap">
<t-cell-group t-class="desc-group-wrap">
<t-cell
wx:if="{{detail && detail.desc}}"
t-class="t-class-cell"
t-class-title="t-class-title"
t-class-note="t-class-note"
title="规则说明"
note="{{detail && detail.desc}}"
/>
<t-cell
wx:if="{{detail && detail.timeLimit}}"
t-class="t-class-cell"
t-class-title="t-class-title"
t-class-note="t-class-note"
title="有效时间"
note="{{detail && detail.timeLimit}}"
/>
<t-cell
wx:if="{{detail && detail.storeAdapt}}"
t-class="t-class-cell"
t-class-title="t-class-title"
t-class-note="t-class-note"
title="适用范围"
note="{{detail && detail.storeAdapt}}"
/>
<t-cell
wx:if="{{detail && detail.useNotes}}"
t-class="t-class-cell"
t-class-title="t-class-title"
t-class-note="t-class-note"
title="使用须知"
note="{{detail && detail.useNotes}}"
/>
</t-cell-group>
<!-- 查看可用商品 -->
<view class="button-wrap">
<t-button shape="round" block bindtap="navGoodListHandle"> 查看可用商品 </t-button>
</view>
</view>

View File

@@ -0,0 +1,91 @@
page {
background-color: #f5f5f5;
}
.coupon-card-wrap {
background-color: #fff;
padding: 32rpx 32rpx 1rpx;
}
.desc-wrap {
margin-top: 24rpx;
}
.desc-wrap .button-wrap {
margin: 50rpx 32rpx 0;
}
.desc-group-wrap .t-class-cell {
align-items: flex-start;
}
.desc-group-wrap .t-class-title {
font-size: 26rpx;
width: 140rpx;
flex: none;
color: #888;
}
.desc-group-wrap .t-class-note {
font-size: 26rpx;
word-break: break-all;
white-space: pre-line;
justify-content: flex-start;
color: #333;
}
.desc-group-wrap {
border-radius: 8rpx;
overflow: hidden;
--cell-label-font-size: 26rpx;
--cell-label-line-height: 36rpx;
--cell-label-color: #999;
}
.desc-group-wrap.in-popup {
border-radius: 0;
overflow: auto;
max-height: 828rpx;
}
.desc-group-wrap .wr-cell__title {
color: #333;
font-size: 28rpx;
}
/* .desc-group-wrap .max-width-cell {
overflow: hidden;
} */
/* .desc-group-wrap .signal-line-label {
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.desc-group-wrap .multi-line-label {
word-break: break-all;
white-space: pre-line;
} */
.popup-content-wrap {
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
.popup-content-title {
font-size: 32rpx;
color: #333;
text-align: center;
height: 104rpx;
line-height: 104rpx;
position: relative;
}
.popup-content-title .close-icon {
position: absolute;
top: 24rpx;
right: 24rpx;
}

View File

@@ -0,0 +1,77 @@
import { fetchCouponList } from '../../../services/coupon/index';
Page({
data: {
status: 0,
list: [
{
text: '可使用',
key: 0,
},
{
text: '已使用',
key: 1,
},
{
text: '已失效',
key: 2,
},
],
couponList: [],
},
onLoad() {
this.init();
},
init() {
this.fetchList();
},
fetchList(status = this.data.status) {
let statusInFetch = '';
switch (Number(status)) {
case 0: {
statusInFetch = 'default';
break;
}
case 1: {
statusInFetch = 'useless';
break;
}
case 2: {
statusInFetch = 'disabled';
break;
}
default: {
throw new Error(`unknown fetchStatus: ${statusInFetch}`);
}
}
fetchCouponList(statusInFetch).then((couponList) => {
this.setData({ couponList });
});
},
tabChange(e) {
const { value } = e.detail;
this.setData({ status: value });
this.fetchList(value);
},
goCouponCenterHandle() {
wx.showToast({ title: '去领券中心', icon: 'none' });
},
onPullDownRefresh_() {
this.setData(
{
couponList: [],
},
() => {
this.fetchList();
},
);
},
});

View File

@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "优惠券",
"usingComponents": {
"t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
"t-tabs": "tdesign-miniprogram/tabs/tabs",
"t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
"t-icon": "tdesign-miniprogram/icon/icon",
"coupon-card": "../components/coupon-card/index"
}
}

View File

@@ -0,0 +1,42 @@
<t-tabs
defaultValue="{{status}}"
bind:change="tabChange"
tabList="{{list}}"
t-class="tabs-external__inner"
t-class-item="tabs-external__item"
t-class-active="tabs-external__active"
t-class-track="tabs-external__track"
>
<t-tab-panel
wx:for="{{list}}"
wx:for-index="index"
wx:for-item="tab"
wx:key="key"
label="{{tab.text}}"
value="{{tab.key}}"
/>
</t-tabs>
<view class="coupon-list-wrap">
<t-pull-down-refresh
t-class-indicator="t-class-indicator"
id="t-pull-down-refresh"
bind:refresh="onPullDownRefresh_"
background="#fff"
>
<view class="coupon-list-item" wx:for="{{couponList}}" wx:key="key">
<coupon-card couponDTO="{{item}}" />
</view>
</t-pull-down-refresh>
<view class="center-entry">
<view class="center-entry-btn" bind:tap="goCouponCenterHandle">
<view>领券中心</view>
<t-icon
name="chevron-right"
color="#fa4126"
size="40rpx"
style="line-height: 28rpx;"
/>
</view>
</view>
</view>

View File

@@ -0,0 +1,78 @@
page {
height: 100%;
}
.tabs-external__inner {
height: 88rpx;
width: 100%;
line-height: 88rpx;
z-index: 100;
}
.tabs-external__inner {
font-size: 26rpx;
color: #333333;
position: fixed;
width: 100vw;
top: 0;
left: 0;
}
.tabs-external__inner .tabs-external__track {
background: #fa4126 !important;
}
.tabs-external__inner .tabs-external__item {
color: #666;
}
.tabs-external__inner .tabs-external__active {
font-size: 28rpx;
color: #fa4126 !important;
}
.tabs-external__inner.order-nav .order-nav-item .bottom-line {
bottom: 12rpx;
}
.coupon-list-wrap {
margin-top: 32rpx;
margin-left: 32rpx;
margin-right: 32rpx;
overflow-y: auto;
padding-bottom: 100rpx;
padding-bottom: calc(constant(safe-area-inset-top) + 100rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 100rpx);
-webkit-overflow-scrolling: touch;
}
.center-entry {
box-sizing: content-box;
border-top: 1rpx solid #dce0e4;
background-color: #fff;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
.center-entry-btn {
color: #fa4126;
font-size: 28rpx;
text-align: center;
line-height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
height: 100rpx;
}
.coupon-list-wrap .t-pull-down-refresh__bar {
background: #fff !important;
}
.t-class-indicator {
color: #b9b9b9 !important;
}

View File

@@ -0,0 +1,95 @@
# Sidebar 侧边导航
### 引入
全局引入在miniprogram根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
```json
// app.json 或 index.json
"usingComponents": {
"wr-sidebar": "path/to/components/goods-category/wr-sidebar/index",
"wr-sidebar-item": "path/to/component/goods-category/wr-sidebar/wr-sidebar-item/index"
}
```
## 代码演示
### 基础用法
通过在`wr-sidebar`上设置`activeKey`属性来控制选中项
```html
<wr-sidebar active-key="{{ activeKey }}" bind:change="onChange">
<wr-sidebar-item title="标签名称" />
<wr-sidebar-item title="标签名称" />
<wr-sidebar-item title="标签名称" />
</wr-sidebar>
```
``` javascript
Page({
data: {
activeKey: 0
},
onChange(event) {
wx.showToast({
icon: 'none',
title: `切换至第${event.detail}项`
});
}
});
```
### 提示气泡(暂未实现)
设置`dot`属性后,会在右上角展示一个小红点。设置`info`属性后,会在右上角展示相应的徽标
```html
<wr-sidebar active-key="{{ activeKey }}">
<wr-sidebar-item title="标签名称" dot />
<wr-sidebar-item title="标签名称" info="5" />
<wr-sidebar-item title="标签名称" info="99+" />
</wr-sidebar>
```
## API
### Sidebar Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|-----------|-----------|-----------|-------------|-------------|
| activeKey | 选中项的索引 | *string \| number* | `0` | - |
### Sidebar Event
| 事件名 | 说明 | 参数 |
|------|------|------|
| change | 切换选项时触发 | 当前选中选项的索引 |
### Sidebar 外部样式类
| 类名 | 说明 |
|-----------|-----------|
| custom-class | 根节点样式类 |
### SidebarItem Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|-----------|-----------|-----------|-------------|-------------|
| title | 内容 | *string* | `''` | - |
| disabled | 是否禁用 | | *boolean* | `false` | - |
| dot | 是否显示右上角小红点 | *boolean* | `false` | - |
| info | 提示消息 | *string \| number* | `''` | - |
### SidebarItem Event
| 事件名 | 说明 | 参数 |
|------|------|------|
| click | 点击徽章时触发 | 当前徽章的索引 |
### SidebarItem 外部样式类
| 类名 | 说明 |
|-----------|-----------|
| custom-class | 根节点样式类 |

View File

@@ -0,0 +1,51 @@
Component({
relations: {
'../../c-sidebar/index': {
type: 'ancestor',
linked(target) {
this.parent = target;
},
},
},
externalClasses: ['custom-class'],
properties: {
title: String,
disabled: Boolean,
},
data: {
topRightRadius: false,
bottomRightRadius: false,
},
methods: {
setActive(selected) {
return this.setData({ selected });
},
onClick() {
const { parent } = this;
if (!parent || this.properties.disabled) {
return;
}
const index = parent.children.indexOf(this);
parent.setActive(index).then(() => {
this.triggerEvent('click', index);
parent.triggerEvent('change', { index });
});
},
setTopRightRadius(val) {
return this.setData({
topRightRadius: val,
});
},
setBottomRightRadius(val) {
return this.setData({
bottomRightRadius: val,
});
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,10 @@
<view class="c-sidebar-item-container">
<view
class="c-sidebar-item {{ selected ? 'active' : '' }} {{ disabled ? 'disabled' : '' }} {{topRightRadius ? 'top-right-radius' : ''}} {{bottomRightRadius ? 'bottom-right-radius' : ''}} custom-class"
hover-class="c-sidebar-item--hover"
hover-stay-time="70"
bind:tap="onClick"
>
<view class="c-sidebar-item__text text-overflow"> {{ title }} </view>
</view>
</view>

View File

@@ -0,0 +1,60 @@
.c-sidebar-item {
display: flex;
justify-content: center;
text-align: center;
background-color: #f5f5f5;
color: #222427;
padding: 20rpx 0;
font-size: 26rpx;
}
.c-sidebar-item.active {
position: relative;
background: white;
}
.c-sidebar-item.active::before {
content: '';
position: absolute;
width: 6rpx;
height: 48rpx;
background-color: #fa4126;
left: 0;
top: 50%;
transform: translate(0, -50%);
border-radius: 64rpx;
}
.c-sidebar-item__text {
width: 136rpx;
height: 36rpx;
padding: 8rpx 0;
line-height: 36rpx;
text-align: center;
font-size: 28rpx;
color: #666666;
}
.c-sidebar-item.active .c-sidebar-item__text {
background-color: white;
border-radius: 36rpx;
color: #fa4126;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.top-right-radius {
border-top-right-radius: 16rpx;
}
.bottom-right-radius {
border-bottom-right-radius: 16rpx;
}
.c-sidebar-item-container {
background-color: white;
}

View File

@@ -0,0 +1,106 @@
Component({
relations: {
'./c-sidebar-item/index': {
type: 'descendant',
linked(target) {
this.children.push(target);
this.setActive(this.properties.activeKey, true);
},
unlinked(target) {
this.children = this.children.filter((item) => item !== target);
this.setActive(this.properties.activeKey, true);
},
},
},
externalClasses: ['custom-class'],
properties: {
activeKey: {
type: Number,
value: 0,
},
},
observers: {
activeKey(newVal) {
this.setActive(newVal);
},
},
created() {
this.children = [];
this.currentActive = -1;
this.topRightRadiusItemIndexs = [];
this.bottomRightRadiusItemIndexs = [];
},
methods: {
setActive(activeKey, isChildrenChange) {
const {
children,
currentActive,
topRightRadiusItemIndexs: preTopRightRadiusItemIndexs,
bottomRightRadiusItemIndexs: preBottomRightRadiusItemIndexs,
} = this;
if (!children.length) {
return Promise.resolve();
}
if (activeKey === currentActive && !isChildrenChange) {
return Promise.resolve();
}
this.currentActive = activeKey;
this.topRightRadiusItemIndexs = this.getTopRightRadiusItemIndexs(
activeKey,
children,
);
this.bottomRightRadiusItemIndexs = this.getBottomRightRadiusItemIndexs(
activeKey,
children,
);
const stack = []; // 任务列表存放调用子组件的setActive后返回的一堆promise
// 将旧的选中项改为false
if (currentActive !== activeKey && children[currentActive]) {
stack.push(children[currentActive].setActive(false));
}
// 将新的选中项改为true
if (children[activeKey]) {
stack.push(children[activeKey].setActive(true));
}
preTopRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setTopRightRadius(false));
});
preBottomRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setBottomRightRadius(false));
});
this.topRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setTopRightRadius(true));
});
this.bottomRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setBottomRightRadius(true));
});
return Promise.all(stack);
},
getTopRightRadiusItemIndexs(activeKey, children) {
const { length } = children;
if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
if (activeKey !== 0) return [0];
if (activeKey < length - 1) return [activeKey + 1];
return [];
},
getBottomRightRadiusItemIndexs(activeKey) {
if (activeKey !== 0) return [activeKey - 1];
return [];
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true
}

View File

@@ -0,0 +1,3 @@
<scroll-view class="c-sidebar custom-class" scroll-y>
<slot />
</scroll-view>

View File

@@ -0,0 +1,9 @@
.c-sidebar {
width: 176rpx;
height: 100vh;
}
.c-sidebar::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}

View File

@@ -0,0 +1,37 @@
Component({
externalClasses: ['custom-class'],
properties: {
tabList: Array,
},
data: {
unfolded: false,
boardMaxHeight: null,
},
attached() {
wx.createSelectorQuery()
.in(this)
.select('.c-tabbar-more')
.boundingClientRect((rect) => {
this.setData({ boardMaxHeight: rect.height });
})
.exec();
},
methods: {
changeFold() {
this.setData({
unfolded: !this.data.unfolded,
});
const { unfolded } = this.data;
this.triggerEvent('change', { unfolded });
},
onSelect(event) {
const activeKey = event.currentTarget.dataset.index;
this.triggerEvent('select', activeKey);
this.changeFold();
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,25 @@
<view class="c-tabbar-more">
<view class="c-tabbar-more__btn" bind:tap="changeFold">
<view class="wr {{unfolded ? 'wr-arrow-up':'wr-arrow-down'}}"></view>
</view>
<view class="t-tabbar-more__boardwrapper" wx:if="{{ unfolded }}">
<view class="t-tabbar-more__mask"></view>
<scroll-view
class="c-tabbar-more__board"
style="{{ boardMaxHeight ? 'height:' + boardMaxHeight + 'px;' : '' }}"
scroll-y
>
<view class="c-tabbar-more__boardinner">
<view
class="c-tabbar-more__item text-overflow"
wx:for="{{ tabList }}"
wx:key="index"
data-index="{{ index }}"
bind:tap="onSelect"
>
{{ item.name }}
</view>
</view>
</scroll-view>
</view>
</view>

View File

@@ -0,0 +1,63 @@
.c-tabbar-more {
width: 100%;
height: calc(100% - var(--tabbar-height, 100rpx));
position: absolute;
top: var(--tabbar-height, 100rpx);
}
.c-tabbar-more__btn {
position: absolute;
top: calc(0rpx - var(--tabbar-height, 100rpx));
right: 0;
width: 80rpx;
height: var(--tabbar-height, 100rpx);
line-height: var(--tabbar-height, 100rpx);
background-color: var(--tabbar-background-color, white);
box-shadow: -20rpx 0 20rpx -10rpx var(--tabbar-background-color, white);
text-align: center;
}
.c-tabbar-more__btn .market {
font-size: 20rpx;
}
.t-tabbar-more__boardwrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.t-tabbar-more__mask {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.c-tabbar-more__board {
position: absolute;
top: 0;
left: 0;
width: 100%;
max-height: 100%;
}
.c-tabbar-more__boardinner {
padding: 20rpx 0 20rpx 20rpx;
background-color: var(--tabbar-background-color, white);
display: flex;
flex-flow: row wrap;
}
.c-tabbar-more__item {
margin: 0 20rpx 20rpx 0;
flex: 0 0 calc((100% - 60rpx) / 3);
box-sizing: border-box;
padding: 0 10rpx;
border-radius: 30rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
font-size: 22rpx;
color: #5d5d5d;
background-color: #eee;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@@ -0,0 +1,68 @@
Component({
externalClasses: ['custom-class'],
properties: {
activeKey: {
type: Number,
value: 0,
},
tabList: {
type: Array,
value: [],
},
showMore: Boolean, // 是否需要下拉功能
},
observers: {
activeKey(newVal) {
if (this.properties.tabList && newVal) {
this.setActive(newVal).catch((e) => {
console.error(e);
});
}
},
},
data: {
currentActive: -1,
},
attached() {
this.setActive(this.properties.activeKey).catch((e) => {
console.error(e);
});
},
methods: {
setActive(activeKey) {
if (
!this.properties.tabList[activeKey] ||
this.properties.tabList[activeKey].disabled
) {
return Promise.reject('数据异常或不可操作');
}
return new Promise((resolve) => {
this.setData(
{
currentActive: activeKey,
},
() => resolve(),
);
});
},
onClick(event) {
let activeKey;
if (event.type === 'select') {
activeKey = event.detail;
} else {
activeKey = event.currentTarget.dataset.index;
}
this.setActive(activeKey)
.then(() => {
const { currentActive } = this.data;
this.triggerEvent('change', { index: currentActive });
})
.catch((e) => {
console.error(e);
});
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"c-tabbar-more": "./c-tabbar-more/index"
}
}

View File

@@ -0,0 +1,29 @@
<view class="c-tabbar custom-class">
<scroll-view
wx:if="{{ tabList.length > 0 }}"
class="c-tabbar__scroll"
scroll-x="true"
scroll-into-view="{{ 'id-' + currentActive }}"
>
<view
class="c-tabbar__inner {{showMore && tabList.length > 4 ? 'c-tabbar__inner_more' : ''}}"
>
<view
wx:for="{{ tabList }}"
wx:key="index"
id="{{ 'id-' + index }}"
class="c-tabbar-item {{ currentActive === index ? 'active' : '' }} {{ item.disabled ? 'disabled' : '' }}"
bind:tap="onClick"
data-index="{{index}}"
>
<view class="c-tabbar-item__text"> {{ item.name }} </view>
</view>
</view>
</scroll-view>
<c-tabbar-more
wx:if="{{ showMore && tabList.length > 4 }}"
tabList="{{tabList}}"
bindselect="onClick"
/>
<slot />
</view>

View File

@@ -0,0 +1,53 @@
.c-tabbar {
width: 100%;
height: 100%;
position: relative;
--tabbar-height: 100rpx;
--tabbar-fontsize: 28rpx;
--tabbar-background-color: white;
}
.c-tabbar__inner {
display: flex;
flex-flow: row nowrap;
}
.c-tabbar__scroll {
position: relative;
}
.c-tabbar__scroll::after {
content: '';
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: -1px;
height: 1px;
background-color: #eee;
z-index: 1;
}
.c-tabbar__inner.c-tabbar__inner_more::after {
content: '';
display: block;
width: 100rpx;
height: 100rpx;
flex: none;
}
.c-tabbar-item {
flex: none;
height: 100rpx;
color: #282828;
font-size: 28rpx;
padding: 0 20rpx;
}
.c-tabbar-item.active:not(.disabled) {
color: #0071ce;
position: relative;
}
.c-tabbar-item.disabled {
color: #ccc;
}
.c-tabbar-item__text {
width: 100%;
text-align: center;
height: 100rpx;
line-height: 100rpx;
}

View File

@@ -0,0 +1,75 @@
Component({
externalClasses: ['custom-class'],
properties: {
category: {
type: Array,
},
initActive: {
type: Array,
value: [],
observer(newVal, oldVal) {
if (newVal[0] !== oldVal[0]) {
this.setActiveKey(newVal[0], 0);
}
},
},
isSlotRight: {
type: Boolean,
value: false,
},
level: {
type: Number,
value: 3,
},
},
data: {
activeKey: 0,
subActiveKey: 0,
},
attached() {
if (this.properties.initActive && this.properties.initActive.length > 0) {
this.setData({
activeKey: this.properties.initActive[0],
subActiveKey: this.properties.initActive[1] || 0,
});
}
},
methods: {
onParentChange(event) {
this.setActiveKey(event.detail.index, 0).then(() => {
this.triggerEvent('change', [
this.data.activeKey,
this.data.subActiveKey,
]);
});
},
onChildChange(event) {
this.setActiveKey(this.data.activeKey, event.detail.index).then(() => {
this.triggerEvent('change', [
this.data.activeKey,
this.data.subActiveKey,
]);
});
},
changCategory(event) {
const { item } = event.currentTarget.dataset;
this.triggerEvent('changeCategory', {
item,
});
},
setActiveKey(key, subKey) {
return new Promise((resolve) => {
this.setData(
{
activeKey: key,
subActiveKey: subKey,
},
() => {
resolve();
},
);
});
},
},
});

View File

@@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"c-tabbar": "./components/c-tabbar/index",
"c-sidebar": "./components/c-sidebar/index",
"c-sidebar-item": "./components/c-sidebar/c-sidebar-item/index",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,61 @@
<view class="goods-category custom-class">
<c-sidebar custom-class="custom-sidebar" bindchange="onParentChange" activeKey="{{activeKey}}">
<c-sidebar-item
wx:for="{{ category }}"
wx:key="index"
title="{{ item.name }}"
disabled="{{ item.disabled }}"
/>
</c-sidebar>
<view class="goods-category__right">
<c-tabbar
wx:if="{{isSlotRight}}"
activeKey="{{subActiveKey}}"
bindchange="onChildChange"
showMore
>
<slot/>
</c-tabbar>
<view wx:if="{{!isSlotRight}}" class="goods-category-normal">
<view class="goods-category-normal-item" wx:if="{{category[activeKey].children && category[activeKey].children.length > 0}}">
<block wx:for="{{category[activeKey].children}}" wx:key="index" wx:if="{{level === 3 && item.children && item.children.length > 0}}">
<view class="flex goods-category-normal-item-title">
{{item.name}}
</view>
<view class="goods-category-normal-item-container">
<view
class="goods-category-normal-item-container-item"
wx:for="{{item.children}}"
wx:for-index="subIndex"
wx:key="subIndex"
wx:for-item="subItem"
bindtap="changCategory"
data-item="{{subItem}}"
>
<t-image src="{{subItem.thumbnail}}" t-class="image" />
<view class="flex goods-category-normal-item-container-item-title">
{{subItem.name}}
</view>
</view>
</view>
</block>
<view class="goods-category-normal-item-second-container" wx:if="{{level === 2}}">
<block wx:for="{{category[activeKey].children}}" wx:key="index">
<view
class="goods-category-normal-item-second-container-item"
wx:for-key="index"
bindtap="changCategory"
data-item="{{item}}"
>
<t-image src="{{item.thumbnail}}" t-class="image" />
<view class="flex goods-category-normal-item-container-item-title">
{{item.name}}
</view>
</view>
</block>
</view>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,102 @@
.goods-category {
display: flex;
}
.custom-sidebar {
height: 100%;
}
.goods-category__right {
height: 100%;
flex: auto;
width: 0;
position: relative;
overflow: scroll;
-webkit-overflow-scrolling: touch;
background-color: white;
}
.flex {
display: flex;
}
.goods-category-normal {
margin: 28rpx 34rpx 0rpx 32rpx;
}
.goods-category-normal-item-title {
font-size: 28rpx;
font-weight: 500;
}
.goods-category-normal-item-container {
background-color: #fff;
border-radius: 8rpx;
padding-top: 28rpx;
margin-top: -24rpx;
margin-bottom: 30rpx;
display: flex;
flex-wrap: wrap;
}
.goods-category-normal-item-container-item {
height: 196rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 24rpx;
width: 33.3%;
}
.goods-category-normal-item-container-item .image {
width: 144rpx;
height: 144rpx;
}
.goods-category-normal-item-container-item-title {
justify-content: center;
font-size: 24rpx;
color: #666666;
margin-top: 20rpx;
}
.goods-category .custom-sidebar {
background-color: #f5f5f5;
}
.custom-sidebar {
width: 180rpx;
height: 100vh;
}
.custom-sidebar::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.goods-category-normal-item-second-container {
background-color: #fff;
border-radius: 8rpx;
margin-top: 8rpx;
margin-bottom: 30rpx;
display: grid;
grid-template-columns: 33.33% 33.33% 33.33%;
}
.goods-category-normal-item-second-container-item {
height: 200rpx;
text-align: center;
margin-top: 20rpx;
}
.goods-category-normal-item-second-container-item .image {
width: 144rpx;
height: 144rpx;
}
.goods-category-normal-item-second-container-item-title {
justify-content: center;
font-size: 24rpx;
color: #222427;
}

View File

@@ -0,0 +1,28 @@
import { getCategoryList } from '../../../services/good/fetchCategoryList';
Page({
data: {
list: [],
},
async init() {
try {
const result = await getCategoryList();
this.setData({
list: result,
});
} catch (error) {
console.error('err:', error);
}
},
onShow() {
this.getTabBar().init();
},
onChange() {
wx.navigateTo({
url: '/pages/goods/list/index',
});
},
onLoad() {
this.init(true);
},
});

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "分类",
"usingComponents": {
"goods-category": "./components/goods-category/index"
}
}

View File

@@ -0,0 +1,8 @@
<view class="wrap">
<goods-category
level="{{3}}"
custom-class="goods-category-class"
category="{{list}}"
bind:changeCategory="onChange"
/>
</view>

View File

@@ -0,0 +1,23 @@
.tabbar-position {
position: fixed !important;
bottom: 0;
left: 0;
width: 100%;
}
.wrap {
height: 100vh;
overflow: hidden;
}
.goods-category-class {
background-color: #f6f6f6 !important;
height: 100%;
}
.goods-category-class .goods-category-normal-item-container-item {
margin-top: 20rpx;
}
page {
min-height: none;
padding-bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,36 @@
// pages/goods/comments/components/comments-card/images-videos/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
resources: {
type: Array,
value: [],
},
},
/**
* 组件的初始数据
*/
data: {
classType: 'single',
},
observers: {
resources: function (newVal) {
if (newVal.length <= 1) {
this.setData({ classType: 'single' });
} else if (newVal.length === 2) {
this.setData({ classType: 'double' });
} else {
this.setData({ classType: 'multiple' });
}
},
},
/**
* 组件的方法列表
*/
methods: {},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"my-video": "../my-video/index",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,15 @@
<view class="images-videos-container container-{{classType}}">
<view
class="resource-container resource-container-{{classType}}"
wx:for="{{resources}}"
wx:for-item="resource"
wx:key="*this"
>
<t-image wx:if="{{resource.type === 'image'}}" t-class="resource-item-{{classType}}" src="{{resource.src}}" />
<my-video wx:else videoSrc="{{resource.src}} " my-video="resource-item-{{classType}}">
<t-image t-class="resource-item resource-item-{{classType}}" slot="cover-img" src="{{resource.coverSrc}}" />
<image class="play-icon" slot="play-icon" src="./assets/play.png" />
</my-video>
</view>
</view>

View File

@@ -0,0 +1,68 @@
.resource-item-single {
width: 360rpx;
height: 360rpx;
border-radius: 8rpx;
}
.resource-item-double {
width: 334rpx;
height: 334rpx;
border-radius: 8rpx;
}
.resource-item-multiple {
width: 218rpx;
height: 218rpx;
border-radius: 8rpx;
}
.resource-container-single {
padding-left: 0;
padding-top: 0;
}
.resource-container-double {
padding-left: 18rpx;
padding-top: 18rpx;
}
.resource-container-multiple {
padding-left: 16rpx;
padding-top: 16rpx;
}
.container-single {
margin-left: 0;
}
.container-double {
margin-left: -18rpx;
margin-top: -18rpx;
}
.container-multiple {
margin-left: -16rpx;
margin-top: -16rpx;
}
.resource-container {
display: flex;
}
.play-icon {
width: 96rpx;
height: 96rpx;
}
.images-videos-container {
display: flex;
flex-wrap: wrap;
}
.image {
border-radius: 8rpx;
}
.cover-img-container {
background-color: white;
}

View File

@@ -0,0 +1,55 @@
Component({
externalClasses: ['my-video', 'my-cover-img', 'my-play-icon'],
properties: {
videoSrc: { type: String },
},
data: {
isShow: true,
},
options: {
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
},
attached() {
this.videoContext = wx.createVideoContext('myVideo', this);
},
fullScreen: false,
methods: {
// 点击封面自定义播放按钮时触发
bindplay(e) {
this.setData({
isShow: false,
});
this.videoContext.play();
this.triggerEvent('play', e);
},
bindplayByVideo(e) {
this.setData({
isShow: false,
});
this.triggerEvent('play', e);
},
// 监听播放到末尾时触发
bindended(e) {
if (!this.fullScreen) {
this.setData({
isShow: true,
});
}
this.triggerEvent('ended', e);
},
// 监听暂停播放时触发
bindpause(e) {
this.triggerEvent('pause', e);
},
bindfullscreenchange(e) {
const fullScreen = e?.detail?.fullScreen;
this.fullScreen = fullScreen;
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,26 @@
<video
id="myVideo"
src="{{videoSrc}}"
enable-danmu
controls
show-fullscreen-btn
show-center-play-btn="{{false}}"
auto-pause-if-navigate
auto-pause-if-open-native
show-play-btn
object-fit="contain"
bindpause="bindpause"
bindended="bindended"
bindplay="bindplayByVideo"
class="video my-video"
bindfullscreenchange="bindfullscreenchange"
>
<view class="video_cover" wx:if="{{isShow}}">
<view class="my-cover-img">
<slot name="cover-img" />
</view>
<view class="video_play_icon my-play-icon" bindtap="bindplay">
<slot name="play-icon" />
</view>
</view>
</video>

View File

@@ -0,0 +1,21 @@
.video .video_cover {
width: 100%;
height: 100%;
position: relative;
}
.video .video_play_icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 5;
}
.video .video_txt {
margin: 10rpx auto;
}
.video {
display: flex;
}

View File

@@ -0,0 +1,51 @@
Component({
externalClasses: ['wr-class'],
options: {
multipleSlots: true,
},
properties: {
goodsDetailInfo: {
type: String,
value: '',
},
sellerReply: {
type: String,
value: '',
},
userHeadUrl: {
type: String,
value: '',
},
userName: {
type: String,
default: '',
},
commentContent: {
type: String,
value: '',
},
commentScore: {
type: Number,
value: 0,
},
commentTime: {
type: String,
value: '',
},
commentResources: {
type: Array,
value: [],
},
},
data: {
showMoreStatus: false,
showContent: false,
hideText: false,
eleHeight: null,
overText: false,
isDisabled: true,
startColors: ['#FFC51C', '#DDDDDD'],
},
methods: {},
});

View File

@@ -0,0 +1,8 @@
{
"component": true,
"usingComponents": {
"t-rate": "tdesign-miniprogram/rate/rate",
"images-videos": "./components/images-videos",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,27 @@
<view class="comments-card-item wr-class">
<view class="comments-card-item-container">
<view class="comments-title">
<view class="comments-card-item-userImg">
<t-image t-class="userImg" src="{{userHeadUrl}}" />
</view>
<view class="userName">{{userName}}</view>
<text class="commentTime">{{commentTime}}</text>
</view>
<view class="comments-info">
<view class="rate">
<t-rate value="{{commentScore}}" size="14" gap="2" color="{{['#ffc51c', '#ddd']}}" />
</view>
<view class="goods-info-text" wx:if="{{goodsDetailInfo}}">{{goodsDetailInfo}}</view>
</view>
<view class="comments-card-item-container-content">
<view class="content-text" hidden="{{showContent}}"> {{commentContent}} </view>
</view>
<view class="comments-card-item-container-image" wx:if="{{commentResources.length > 0}}">
<images-videos resources="{{commentResources}}" />
</view>
<view class="comments-card-reply" wx:if="{{sellerReply}}">
<text class="prefix">店家回复:</text>
<text class="content">{{sellerReply}}</text>
</view>
</view>
</view>

View File

@@ -0,0 +1,172 @@
@import '../../../../../style/theme.wxss';
.comments-card-item {
padding: 32rpx;
display: flex;
background-color: #fff;
position: relative;
}
.comments-card-item::after {
content: '';
position: absolute;
bottom: 0rpx;
width: 686rpx;
height: 2rpx;
background-color: #f5f5f5;
}
.comments-card-item-userImg {
display: flex;
}
.comments-card-item-userImg .userImg {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
}
.comments-card-item-container {
width: 100%;
}
.comments-card-item-container-name {
display: flex;
font-size: 28rpx;
color: #333;
font-weight: 600;
align-items: center;
}
.comments-card-item-container-name .userName {
margin-right: 12rpx;
}
.comments-card-item-container-date {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
display: flex;
}
.comments-card-item-container-content {
margin-top: 16rpx;
position: relative;
}
.comments-card-item-container-content .content-text {
font-size: 28rpx;
white-space: normal;
word-break: break-all;
font-weight: normal;
}
.comments-card-item-container-content .hide-text {
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 5;
text-align: justify;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.comments-card-item-container-content .showMore {
position: absolute;
width: 112rpx;
height: 36rpx;
bottom: 0;
right: 0;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.2) 0,
rgba(255, 255, 255, 0.45) 20%,
rgba(255, 255, 255, 0.7) 25%,
rgba(255, 255, 255, 0.9) 30%,
rgba(255, 255, 255, 0.95) 35%,
#ffffff 50%,
#fff 100%
);
font-size: 26rpx;
color: #fa550f;
line-height: 36rpx;
text-align: right;
}
.comments-card-item-container-image {
margin-top: 24rpx;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.comments-card-item-container-image .commentImg {
border-radius: 8rpx;
margin-top: 12rpx;
}
.comments-card-item-container-image .commentImg3 {
width: 196rpx;
height: 196rpx;
}
.comments-card-item-container-image .commentImg2 {
width: 300rpx;
height: 300rpx;
}
.comments-card-item-container-image .commentImg1 {
width: 404rpx;
height: 404rpx;
}
.comments-card-item-container .comments-title {
display: flex;
align-items: center;
position: relative;
}
.comments-title .userName {
font-size: 26rpx;
color: #333333;
margin-left: 24rpx;
}
.comments-title .commentTime {
font-size: 24rpx;
color: #999999;
position: absolute;
right: 0;
}
.comments-info {
display: flex;
align-items: center;
margin-top: 18rpx;
}
.comments-info .rate {
margin-right: 24rpx;
}
.comments-info .goods-info-text {
font-size: 24rpx;
color: #999999;
}
.comments-card-item-container .comments-card-reply {
background-color: #f5f5f5;
padding: 24rpx 16rpx;
margin-top: 24rpx;
}
.comments-card-item-container .comments-card-reply .prefix {
font-size: 26rpx;
font-weight: bold;
color: #666666;
}
.comments-card-item-container .comments-card-reply .content {
font-size: 26rpx;
color: #666666;
}

View File

@@ -0,0 +1,86 @@
// import { getCommentDetail } from '../../../../services/good/comments/fetchCommentDetail';
import Toast from 'tdesign-miniprogram/toast/index';
Page({
data: {
serviceRateValue: 1,
goodRateValue: 1,
conveyRateValue: 1,
isAnonymous: false,
uploadFiles: [],
gridConfig: {
width: 218,
height: 218,
column: 3,
},
isAllowedSubmit: false,
imgUrl: '',
title: '',
goodsDetail: '',
imageProps: {
mode: 'aspectFit',
},
},
onLoad(options) {
this.setData({
imgUrl: options.imgUrl,
title: options.title,
goodsDetail: options.specs,
});
},
onRateChange(e) {
const { value } = e?.detail;
const item = e?.currentTarget?.dataset?.item;
this.setData({ [item]: value }, () => {
this.updateButtonStatus();
});
},
onAnonymousChange(e) {
const status = !!e?.detail?.checked;
this.setData({ isAnonymous: status });
},
handleSuccess(e) {
const { files } = e.detail;
this.setData({
uploadFiles: files,
});
},
handleRemove(e) {
const { index } = e.detail;
const { uploadFiles } = this.data;
uploadFiles.splice(index, 1);
this.setData({
uploadFiles,
});
},
onTextAreaChange(e) {
const value = e?.detail?.value;
this.textAreaValue = value;
this.updateButtonStatus();
},
updateButtonStatus() {
const { serviceRateValue, goodRateValue, conveyRateValue, isAllowedSubmit } = this.data;
const { textAreaValue } = this;
const temp = serviceRateValue && goodRateValue && conveyRateValue && textAreaValue;
if (temp !== isAllowedSubmit) this.setData({ isAllowedSubmit: temp });
},
onSubmitBtnClick() {
const { isAllowedSubmit } = this.data;
if (!isAllowedSubmit) return;
Toast({
context: this,
selector: '#t-toast',
message: '评价提交成功',
icon: 'check-circle',
});
wx.navigateBack();
},
});

View File

@@ -0,0 +1,13 @@
{
"navigationBarTitleText": "评价商品",
"usingComponents": {
"t-image": "/components/webp-image/index",
"t-rate": "tdesign-miniprogram/rate/rate",
"t-textarea": "tdesign-miniprogram/textarea/textarea",
"t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
"t-button": "tdesign-miniprogram/button/button",
"t-upload": "tdesign-miniprogram/upload/upload",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}

View File

@@ -0,0 +1,90 @@
<view class="page-container">
<view class="comment-card">
<view class="goods-info-container">
<view class="goods-image-container">
<t-image t-class="goods-image" src="{{imgUrl}}" />
</view>
<view class="goods-title-container">
<view class="goods-title">{{title}}</view>
<view class="goods-detail">{{goodsDetail}}</view>
</view>
</view>
<view class="rate-container">
<text class="rate-title">商品评价</text>
<view class="rate">
<t-rate
value="{{goodRateValue}}"
bind:change="onRateChange"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="goodRateValue"
/>
</view>
</view>
<view class="textarea-container">
<t-textarea
t-class="textarea"
maxlength="{{500}}"
indicator
placeholder="对商品满意吗?评论一下"
bind:change="onTextAreaChange"
/>
</view>
<view class="upload-container">
<t-upload
media-type="{{['image','video']}}"
files="{{uploadFiles}}"
bind:remove="handleRemove"
bind:success="handleSuccess"
gridConfig="{{gridConfig}}"
imageProps="{{imageProps}}"
/>
</view>
<view class="anonymous-box">
<t-checkbox bind:change="onAnonymousChange" checked="{{isAnonymous}}" color="#FA4126" />
<view class="name">匿名评价</view>
</view>
</view>
</view>
<view class="comment-card convey-card">
<view class="convey-comment-title">物流服务评价</view>
<view class="rate-container">
<text class="rate-title">物流评价</text>
<view class="rate">
<t-rate
value="{{conveyRateValue}}"
bind:change="onRateChange"
variant="filled"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="conveyRateValue"
/>
</view>
</view>
<view class="rate-container">
<text class="rate-title">服务评价</text>
<view class="rate">
<t-rate
value="{{serviceRateValue}}"
bind:change="onRateChange"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="serviceRateValue"
/>
</view>
</view>
</view>
<view class="submit-button-container">
<t-button
content="提交"
block
shape="round"
t-class="submit-button{{isAllowedSubmit ? '' : '-disabled'}}"
bind:tap="onSubmitBtnClick"
/>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,168 @@
page {
background-color: #f5f5f5;
}
.page-container .comment-card {
padding: 24rpx 32rpx 28rpx;
background-color: #ffffff;
}
.comment-card .goods-info-container .goods-image {
width: 112rpx;
height: 112rpx;
border-radius: 8rpx;
}
.comment-card .goods-info-container {
display: flex;
align-items: center;
}
.comment-card .goods-info-container .goods-title-container {
padding-left: 24rpx;
}
.comment-card .goods-info-container .goods-title {
font-size: 28rpx;
font-weight: normal;
}
.comment-card .goods-info-container .goods-detail {
font-size: 24rpx;
font-weight: normal;
color: #999999;
margin-top: 16rpx;
}
.comment-card .rate-container {
display: flex;
align-items: center;
margin-top: 22rpx;
}
.comment-card .rate-container .rate-title {
font-size: 28rpx;
font-weight: bold;
margin-right: 12rpx;
}
.comment-card .textarea-container {
margin-top: 22rpx;
}
.comment-card .textarea-container .textarea {
height: 294rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
font-size: 28rpx;
font-weight: normal;
}
.page-container .t-checkbox__bordered {
display: none;
}
.page-container .anonymous-box {
display: flex;
align-items: center;
padding-top: 52rpx;
}
.page-container .anonymous-box .name {
font-size: 28rpx;
font-weight: normal;
color: #999999;
padding-left: 28rpx;
}
.page-container .t-checkbox {
padding: 0rpx !important;
}
.page-container .t-checkbox__content {
display: none;
}
.comment-card .convey-comment-title {
font-size: 28rpx;
font-weight: bold;
}
.convey-card {
background-color: #ffffff;
margin-top: 24rpx;
padding: 32rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
.convey-card .rate-container .rate-title {
font-weight: normal;
}
.page-container .t-checkbox__icon-left {
margin-right: 0rpx !important;
}
.submit-button-container {
padding: 12rpx 32rpx;
display: flex;
width: 100vw;
box-sizing: border-box;
justify-content: center;
position: fixed;
bottom: 0;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
background-color: #ffffff;
z-index: 99;
}
.submit-button-container .submit-button {
--td-button-default-color: #fff;
--td-button-default-bg-color: #fa4126;
--td-button-default-border-color: #fa4126;
--td-button-default-active-bg-color: #fa42269c;
}
.submit-button-container .submit-button-disabled {
--td-button-default-color: #fff;
--td-button-default-bg-color: #ccc;
--td-button-default-border-color: #ccc;
--td-button-default-active-bg-color: rgba(204, 204, 204, 0.789);
}
.page-container .upload-container {
margin-top: 24rpx;
}
.page-container .t-upload__wrapper {
border-radius: 8rpx;
overflow: hidden;
}
.page-container .submmit-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 12;
padding: 12rpx 32rpx;
padding-bottom: env(safe-area-inset-bottom);
background-color: #fff;
height: 112rpx;
}
.page-container .submmit-bar-button {
border-radius: 48rpx !important;
padding: 0 !important;
}
.page-container .t-upload__close-btn {
background-color: rgba(0, 0, 0, 0.4);
border-bottom-left-radius: 8rpx;
width: 36rpx;
height: 36rpx;
}
.upload-container .upload-addcontent-slot {
font-size: 26rpx;
}

View File

@@ -0,0 +1,227 @@
import { fetchComments } from '../../../services/comments/fetchComments';
import { fetchCommentsCount } from '../../../services/comments/fetchCommentsCount';
import dayjs from 'dayjs';
const layoutMap = {
0: 'vertical',
};
Page({
data: {
pageLoading: false,
commentList: [],
pageNum: 1,
myPageNum: 1,
pageSize: 10,
total: 0,
myTotal: 0,
hasLoaded: false,
layoutText: layoutMap[0],
loadMoreStatus: 0,
myLoadStatus: 0,
spuId: '1060004',
commentLevel: '',
hasImage: '',
commentType: '',
totalCount: 0,
countObj: {
badCount: '0',
commentCount: '0',
goodCount: '0',
middleCount: '0',
hasImageCount: '0',
uidCount: '0',
},
},
onLoad(options) {
this.getCount(options);
this.getComments(options);
},
async getCount(options) {
try {
const result = await fetchCommentsCount(
{
spuId: options.spuId,
},
{
method: 'POST',
},
);
this.setData({
countObj: result,
});
// const { data, code = '' } = result;
// if (code.toUpperCase() === 'SUCCESS') {
// wx.setNavigationBarTitle({
// title: `全部评价(${data.commentCount})`,
// });
// this.setData({
// countObj: data,
// });
// } else {
// wx.showToast({
// title: '查询失败,请稍候重试',
// });
// }
} catch (error) {}
},
generalQueryData(reset) {
const { hasImage, pageNum, pageSize, spuId, commentLevel } = this.data;
const params = {
pageNum: 1,
pageSize: 30,
queryParameter: {
spuId,
},
};
if (
Number(commentLevel) === 3 ||
Number(commentLevel) === 2 ||
Number(commentLevel) === 1
) {
params.queryParameter.commentLevel = Number(commentLevel);
}
if (hasImage && hasImage === '1') {
params.queryParameter.hasImage = true;
} else {
delete params.queryParameter.hasImage;
}
// 重置请求
if (reset) return params;
return {
...params,
pageNum: pageNum + 1,
pageSize,
};
},
async init(reset = true) {
const { loadMoreStatus, commentList = [] } = this.data;
const params = this.generalQueryData(reset);
// 在加载中或者无更多数据,直接返回
if (loadMoreStatus !== 0) return;
this.setData({
loadMoreStatus: 1,
});
try {
const data = await fetchComments(params, {
method: 'POST',
});
const code = 'SUCCESS';
if (code.toUpperCase() === 'SUCCESS') {
const { pageList, totalCount = 0 } = data;
pageList.forEach((item) => {
// eslint-disable-next-line no-param-reassign
item.commentTime = dayjs(Number(item.commentTime)).format(
'YYYY/MM/DD HH:mm',
);
});
if (Number(totalCount) === 0 && reset) {
this.setData({
commentList: [],
hasLoaded: true,
total: totalCount,
loadMoreStatus: 2,
});
return;
}
const _commentList = reset ? pageList : commentList.concat(pageList);
const _loadMoreStatus =
_commentList.length === Number(totalCount) ? 2 : 0;
this.setData({
commentList: _commentList,
pageNum: params.pageNum || 1,
totalCount: Number(totalCount),
loadMoreStatus: _loadMoreStatus,
});
} else {
wx.showToast({
title: '查询失败,请稍候重试',
});
}
} catch (error) {}
this.setData({
hasLoaded: true,
});
},
getScoreArray(score) {
var array = [];
for (let i = 0; i < 5; i++) {
if (i < score) {
array.push(2);
} else {
array.push(0);
}
}
return array;
},
getComments(options) {
const { commentLevel = -1, spuId, hasImage = '' } = options;
if (commentLevel !== -1) {
this.setData({
commentLevel: commentLevel,
});
}
this.setData({
hasImage: hasImage,
commentType: hasImage ? '4' : '',
spuId: spuId,
});
this.init(true);
},
changeTag(e) {
var { commenttype } = e.currentTarget.dataset;
var { commentType } = this.data;
if (commentType === commenttype) return;
this.setData({
loadMoreStatus: 0,
commentList: [],
total: 0,
myTotal: 0,
myPageNum: 1,
pageNum: 1,
});
if (commenttype === '' || commenttype === '5') {
this.setData({
hasImage: '',
commentLevel: '',
});
} else if (commenttype === '4') {
this.setData({
hasImage: '1',
commentLevel: '',
});
} else {
this.setData({
hasImage: '',
commentLevel: commenttype,
});
}
if (commenttype === '5') {
this.setData({
myLoadStatus: 1,
commentType: commenttype,
});
this.getMyCommentsList();
} else {
this.setData({
myLoadStatus: 0,
commentType: commenttype,
});
this.init(true);
}
},
onReachBottom() {
const { total = 0, commentList } = this.data;
if (commentList.length === total) {
this.setData({
loadMoreStatus: 2,
});
return;
}
this.init(false);
},
});

View File

@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "全部评价",
"usingComponents": {
"t-tag": "tdesign-miniprogram/tag/tag",
"comments-card": "./components/comments-card/index",
"t-load-more": "/components/load-more/index"
}
}

View File

@@ -0,0 +1,50 @@
<view class="comments-header">
<t-tag t-class="comments-header-tag {{commentType === '' ? 'comments-header-active' : ''}}" data-commentType="" bindtap="changeTag">
全部({{countObj.commentCount}})
</t-tag>
<t-tag
t-class="comments-header-tag {{commentType === '5' ? 'comments-header-active' : ''}}"
wx:if="{{countObj.uidCount !== '0'}}"
data-commentType="5"
bindtap="changeTag"
>
自己({{countObj.uidCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '4' ? 'comments-header-active' : ''}}" data-commentType="4" bindtap="changeTag">
带图({{countObj.hasImageCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '3' ? 'comments-header-active' : ''}}" data-commentType="3" bindtap="changeTag">
好评({{countObj.goodCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '2' ? 'comments-header-active' : ''}}" data-commentType="2" bindtap="changeTag">
中评({{countObj.middleCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '1' ? 'comments-header-active' : ''}}" data-commentType="1" bindtap="changeTag">
差评({{countObj.badCount}})
</t-tag>
</view>
<view class="comments-card-list">
<block wx:for="{{commentList}}" wx:key="index">
<comments-card
commentScore="{{item.commentScore}}"
userName="{{item.userName}}"
commentResources="{{item.commentResources || []}}"
commentContent="{{item.commentContent}}"
isAnonymity="{{item.isAnonymity}}"
commentTime="{{item.commentTime}}"
isAutoComment="{{item.isAutoComment}}"
userHeadUrl="{{item.userHeadUrl}}"
specInfo="{{item.specInfo}}"
sellerReply="{{item.sellerReply || ''}}"
goodsDetailInfo="{{item.goodsDetailInfo || ''}}"
/>
</block>
<t-load-more
t-class="no-more"
status="{{loadMoreStatus}}"
no-more-text="没有更多了"
color="#BBBBBB"
failedColor="#FA550F"
/>
</view>

View File

@@ -0,0 +1,49 @@
/* 层级定义
@z-index-0: 1;
@z-index-1: 100;
@z-index-2: 200;
@z-index-5: 500;
@z-index-component: 1000; // 通用组件级别
@z-index-dropdown: @z-index-component;
@z-index-sticky: @z-index-component + 20;
@z-index-fixed: @z-index-component + 30;
@z-index-modal-backdrop:@z-index-component + 40;
@z-index-modal:@z-index-component + 50;
@z-index-popover:@z-index-component + 60;
@z-index-tooltip:@z-index-component + 70;
*/
/* var() css变量适配*/
page {
background-color: #FFFFFF;
}
.comments-header {
display: flex;
flex-wrap: wrap;
padding: 32rpx 32rpx 0rpx;
background-color: #fff;
margin-top: -24rpx;
margin-left: -24rpx;
}
.comments-header-tag {
margin-top: 24rpx;
margin-left: 24rpx;
height: 56rpx !important;
font-size: 24rpx !important;
justify-content: center;
background-color: #F5F5F5 !important;
border-radius: 8rpx !important;
border: 1px solid #F5F5F5 !important;
}
.comments-header-active {
background-color: #FFECE9 !important;
color: #FA4126 !important;
border: 1px solid #FA4126 !important;
}
.no-more {
padding-left: 20rpx;
padding-right: 20rpx;
}

View File

@@ -0,0 +1,66 @@
Component({
externalClasses: ['wr-sold-out', 'wr-class'],
options: { multipleSlots: true },
properties: {
soldout: {
// 商品是否下架
type: Boolean,
value: false,
},
jumpArray: {
type: Array,
value: [],
},
isStock: {
type: Boolean,
value: true,
}, // 是否有库存
isSlotButton: {
type: Boolean,
value: false,
}, // 是否开启按钮插槽
shopCartNum: {
type: Number, // 购物车气泡数量
},
buttonType: {
type: Number,
value: 0,
},
minDiscountPrice: {
type: String,
value: '',
},
minSalePrice: {
type: String,
value: '',
},
},
data: {
fillPrice: false,
},
methods: {
toAddCart() {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toAddCart');
},
toBuyNow(e) {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toBuyNow', e);
},
toNav(e) {
const { url } = e.currentTarget.dataset;
return this.triggerEvent('toNav', {
e,
url,
});
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,38 @@
<view class="flex soldout flex-center wr-sold-out" wx:if="{{soldout || !isStock}}">
{{soldout ? '商品已下架' : '商品已售馨'}}
</view>
<view class="footer-cont flex flex-between wr-class">
<view class="flex flex-between bottom-operate-left" wx:if="{{jumpArray.length > 0}}">
<view
wx:for="{{jumpArray}}"
wx:key="index"
class="icon-warp operate-wrap"
bindtap="toNav"
data-ele="foot_navigation"
data-index="{{index}}"
data-url="{{item.url}}"
>
<view>
<text wx:if="{{shopCartNum > 0 && item.showCartNum}}" class="tag-cart-num">
{{shopCartNum > 99 ? '99+' : shopCartNum}}
</text>
<t-icon prefix="wr" name="{{item.iconName}}" size="40rpx" />
<view class="operate-text">{{item.title}}</view>
</view>
</view>
</view>
<block wx:if="{{buttonType === 1}}">
<view class="flex buy-buttons">
<view class="bar-separately {{soldout || !isStock ? 'bar-addCart-disabled' : ''}}" bindtap="toAddCart">
加入购物车
</view>
<view class="bar-buy {{soldout || !isStock ? 'bar-buyNow-disabled' : ''}}" bindtap="toBuyNow">
立即购买
</view>
</view>
</block>
<block wx:if="{{isSlotButton}}">
<slot name="buyButton" />
</block>
</view>

View File

@@ -0,0 +1,107 @@
.footer-cont {
background-color: #fff;
padding: 16rpx;
}
.icon-warp {
width: 110rpx;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.operate-wrap {
position: relative;
}
.bottom-operate-left {
width: 100%;
}
.bottom-operate-left .icon-warp {
width: 50%;
}
.tag-cart-num {
display: inline-block;
position: absolute;
left: 50rpx;
right: auto;
top: 6rpx;
color: #fff;
line-height: 24rpx;
text-align: center;
z-index: 99;
white-space: nowrap;
min-width: 28rpx;
border-radius: 14rpx;
background-color: #fa550f !important;
font-size: 20rpx;
font-weight: 400;
padding: 2rpx 6rpx;
}
.operate-text {
color: #666;
font-size: 20rpx;
}
.soldout {
height: 80rpx;
background: rgba(170, 170, 170, 1);
width: 100%;
color: #fff;
}
.addCart-disabled,
.bar-addCart-disabled {
background: rgba(221, 221, 221, 1) !important;
color: #fff !important;
font-size: 28rpx;
}
.buyNow-disabled,
.bar-buyNow-disabled {
background: rgba(198, 198, 198, 1) !important;
color: #fff !important;
font-size: 28rpx;
}
.bar-separately,
.bar-buy {
width: 254rpx;
height: 80rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.bar-separately {
background: #ffece9;
color: #fa4126;
border-radius: 40rpx 0 0 40rpx;
}
.bar-buy {
background-color: #fa4126;
border-radius: 0rpx 40rpx 40rpx 0rpx;
}
.flex {
display: flex;
display: -webkit-flex;
}
.flex-center {
justify-content: center;
-webkit-justify-content: center;
align-items: center;
-webkit-align-items: center;
}
.flex-between {
justify-content: space-between;
-webkit-justify-content: space-between;
}

Some files were not shown because too many files have changed in this diff Show More