Initial commit
This commit is contained in:
59
miniprogram/pages/cart/components/cart-bar/index.js
Normal file
59
miniprogram/pages/cart/components/cart-bar/index.js
Normal 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');
|
||||
},
|
||||
},
|
||||
});
|
||||
7
miniprogram/pages/cart/components/cart-bar/index.json
Normal file
7
miniprogram/pages/cart/components/cart-bar/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"price": "/components/price/index",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
31
miniprogram/pages/cart/components/cart-bar/index.wxml
Normal file
31
miniprogram/pages/cart/components/cart-bar/index.wxml
Normal 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>
|
||||
|
||||
80
miniprogram/pages/cart/components/cart-bar/index.wxss
Normal file
80
miniprogram/pages/cart/components/cart-bar/index.wxss
Normal 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;
|
||||
}
|
||||
22
miniprogram/pages/cart/components/cart-empty/index.js
Normal file
22
miniprogram/pages/cart/components/cart-empty/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
Component({
|
||||
properties: {
|
||||
imgUrl: {
|
||||
type: String,
|
||||
value: 'https://tdesign.gtimg.com/miniprogram/template/retail/template/empty-cart.png',
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
value: '购物车是空的',
|
||||
},
|
||||
btnText: {
|
||||
type: String,
|
||||
value: '去首页',
|
||||
},
|
||||
},
|
||||
data: {},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.triggerEvent('handleClick');
|
||||
},
|
||||
},
|
||||
});
|
||||
6
miniprogram/pages/cart/components/cart-empty/index.json
Normal file
6
miniprogram/pages/cart/components/cart-empty/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-image": "/components/webp-image/index"
|
||||
}
|
||||
}
|
||||
6
miniprogram/pages/cart/components/cart-empty/index.wxml
Normal file
6
miniprogram/pages/cart/components/cart-empty/index.wxml
Normal 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>
|
||||
|
||||
33
miniprogram/pages/cart/components/cart-empty/index.wxss
Normal file
33
miniprogram/pages/cart/components/cart-empty/index.wxss
Normal 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;
|
||||
}
|
||||
182
miniprogram/pages/cart/components/cart-group/index.js
Normal file
182
miniprogram/pages/cart/components/cart-group/index.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
|
||||
const shortageImg = 'https://tdesign.gtimg.com/miniprogram/template/retail/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;
|
||||
// 仅当需要凑单时才触发跳转,“再逛逛”点击不跳转
|
||||
if (promotion && promotion.isNeedAddOnShop == 1) {
|
||||
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,
|
||||
});
|
||||
},
|
||||
|
||||
// 确认选择SKU
|
||||
onSpecsConfirm(e) {
|
||||
const { skuId, spuId, quantity } = e.detail;
|
||||
const currentGoods = this.data.currentGoods;
|
||||
|
||||
// 触发父组件事件,通知更新SKU
|
||||
this.triggerEvent('changesku', {
|
||||
oldSpuId: currentGoods.spuId,
|
||||
oldSkuId: currentGoods.skuId,
|
||||
newSpuId: spuId,
|
||||
newSkuId: skuId,
|
||||
quantity: quantity || 1,
|
||||
});
|
||||
|
||||
this.hideSpecsPopup();
|
||||
},
|
||||
|
||||
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' });
|
||||
},
|
||||
},
|
||||
});
|
||||
11
miniprogram/pages/cart/components/cart-group/index.json
Normal file
11
miniprogram/pages/cart/components/cart-group/index.json
Normal 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"
|
||||
}
|
||||
}
|
||||
155
miniprogram/pages/cart/components/cart-group/index.wxml
Normal file
155
miniprogram/pages/cart/components/cart-group/index.wxml
Normal file
@@ -0,0 +1,155 @@
|
||||
<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)}}"
|
||||
spuId="{{currentGoods.spuId}}"
|
||||
skuId="{{currentGoods.skuId}}"
|
||||
zIndex="{{11001}}"
|
||||
bindclose="hideSpecsPopup"
|
||||
bindconfirm="onSpecsConfirm"
|
||||
/>
|
||||
|
||||
<t-toast id="t-toast" />
|
||||
5
miniprogram/pages/cart/components/cart-group/index.wxs
Normal file
5
miniprogram/pages/cart/components/cart-group/index.wxs
Normal file
@@ -0,0 +1,5 @@
|
||||
var hasPromotion = function (code) {
|
||||
return code && code !== 'EMPTY_PROMOTION';
|
||||
};
|
||||
|
||||
module.exports.hasPromotion = hasPromotion;
|
||||
335
miniprogram/pages/cart/components/cart-group/index.wxss
Normal file
335
miniprogram/pages/cart/components/cart-group/index.wxss
Normal 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;
|
||||
}
|
||||
20
miniprogram/pages/cart/components/cart-group/utils.wxs
Normal file
20
miniprogram/pages/cart/components/cart-group/utils.wxs
Normal 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;
|
||||
};
|
||||
248
miniprogram/pages/cart/components/goods-card/index.js
Normal file
248
miniprogram/pages/cart/components/goods-card/index.js
Normal file
@@ -0,0 +1,248 @@
|
||||
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,
|
||||
});
|
||||
},
|
||||
|
||||
handleImageError(e) {
|
||||
console.warn('购物车商品卡片图片加载失败:', e.detail);
|
||||
// 可以在这里设置默认图片或其他错误处理逻辑
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
9
miniprogram/pages/cart/components/goods-card/index.json
Normal file
9
miniprogram/pages/cart/components/goods-card/index.json
Normal 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"
|
||||
}
|
||||
}
|
||||
76
miniprogram/pages/cart/components/goods-card/index.wxml
Normal file
76
miniprogram/pages/cart/components/goods-card/index.wxml
Normal file
@@ -0,0 +1,76 @@
|
||||
<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 }}"
|
||||
bind:error="handleImageError"
|
||||
/>
|
||||
<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>
|
||||
|
||||
260
miniprogram/pages/cart/components/goods-card/index.wxss
Normal file
260
miniprogram/pages/cart/components/goods-card/index.wxss
Normal 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;
|
||||
}
|
||||
273
miniprogram/pages/cart/components/specs-popup/index.js
Normal file
273
miniprogram/pages/cart/components/specs-popup/index.js
Normal file
@@ -0,0 +1,273 @@
|
||||
import { fetchGood } from '../../../../services/good/fetchGood';
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
|
||||
Component({
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
|
||||
},
|
||||
|
||||
properties: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer(newVal) {
|
||||
if (newVal && this.data.spuId) {
|
||||
this.loadProductDetail();
|
||||
}
|
||||
},
|
||||
},
|
||||
spuId: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
skuId: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
goods: {
|
||||
title: '',
|
||||
thumb: '',
|
||||
price: '',
|
||||
hideKey: {
|
||||
originPrice: true,
|
||||
tags: true,
|
||||
specs: true,
|
||||
num: true,
|
||||
},
|
||||
},
|
||||
specList: [],
|
||||
skuList: [],
|
||||
selectedSku: {},
|
||||
isAllSelectedSku: false,
|
||||
selectSkuSellsPrice: 0,
|
||||
currentPrice: '',
|
||||
buyNum: 1,
|
||||
loading: false,
|
||||
},
|
||||
methods: {
|
||||
async loadProductDetail() {
|
||||
const { spuId, skuId } = this.data;
|
||||
if (!spuId) return;
|
||||
|
||||
this.setData({ loading: true });
|
||||
|
||||
try {
|
||||
const detail = await fetchGood(spuId);
|
||||
const { specList = [], skuList = [], minSalePrice } = detail;
|
||||
|
||||
// 处理SKU数据
|
||||
const skuArray = skuList.map((sku) => ({
|
||||
...sku,
|
||||
specInfo: sku.specInfo || [],
|
||||
quantity: Number(
|
||||
(sku.stockInfo && sku.stockInfo.stockQuantity) || sku.stock || sku.quantity || 0
|
||||
),
|
||||
price: Number((sku.priceInfo && sku.priceInfo[0] && sku.priceInfo[0].price) || sku.price || 0),
|
||||
skuId: sku.skuId || sku.id,
|
||||
}));
|
||||
|
||||
// 初始化规格选择,默认选中当前SKU
|
||||
this.initSpecSelection(specList, skuArray, skuId);
|
||||
|
||||
this.setData({
|
||||
specList,
|
||||
skuList: skuArray,
|
||||
selectSkuSellsPrice: minSalePrice,
|
||||
currentPrice: minSalePrice,
|
||||
loading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载商品详情失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '加载失败,请重试',
|
||||
theme: 'error',
|
||||
});
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
initSpecSelection(specList, skuArray, currentSkuId) {
|
||||
const selectedSku = {};
|
||||
|
||||
// 查找当前SKU
|
||||
const currentSku = skuArray.find(sku => String(sku.skuId) === String(currentSkuId));
|
||||
|
||||
console.log('[初始化规格] 当前SKU ID:', currentSkuId);
|
||||
console.log('[初始化规格] 匹配的SKU:', currentSku);
|
||||
|
||||
if (currentSku && Array.isArray(currentSku.specInfo)) {
|
||||
// 标记当前SKU的规格为选中
|
||||
currentSku.specInfo.forEach((spec) => {
|
||||
const specId = spec.specId;
|
||||
const specValueId = spec.specValueId;
|
||||
|
||||
specList.forEach((group) => {
|
||||
if (String(group.specId) === String(specId)) {
|
||||
group.specValueList.forEach((sv) => {
|
||||
sv.isSelected = String(sv.specValueId) === String(specValueId);
|
||||
});
|
||||
selectedSku[group.specId] = specValueId;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const isAllSelectedSku = specList.every((spec) => !!selectedSku[spec.specId]);
|
||||
|
||||
// 更新价格
|
||||
const initPrice = currentSku ? currentSku.price : (skuArray[0] ? skuArray[0].price : 0);
|
||||
console.log('[初始化规格] 初始价格:', initPrice);
|
||||
|
||||
this.setData({
|
||||
selectedSku,
|
||||
isAllSelectedSku,
|
||||
selectSkuSellsPrice: initPrice,
|
||||
currentPrice: initPrice,
|
||||
'goods.price': initPrice,
|
||||
});
|
||||
},
|
||||
|
||||
toChooseItem(e) {
|
||||
const { specid, id } = e.currentTarget.dataset;
|
||||
const { specList, skuList } = this.data;
|
||||
|
||||
// 更新选中状态
|
||||
specList.forEach((spec) => {
|
||||
if (String(spec.specId) === String(specid)) {
|
||||
spec.specValueList.forEach((specValue) => {
|
||||
specValue.isSelected = String(specValue.specValueId) === String(id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新selectedSku
|
||||
const selectedSku = { ...this.data.selectedSku };
|
||||
selectedSku[specid] = id;
|
||||
|
||||
const isAllSelectedSku = specList.every((spec) => !!selectedSku[spec.specId]);
|
||||
|
||||
this.setData({
|
||||
specList,
|
||||
selectedSku,
|
||||
isAllSelectedSku,
|
||||
});
|
||||
|
||||
// 查找SKU并更新价格
|
||||
if (isAllSelectedSku) {
|
||||
const matchedSku = this.findMatchedSku(selectedSku, skuList);
|
||||
if (matchedSku) {
|
||||
console.log('[SKU选择] 匹配SKU:', matchedSku);
|
||||
console.log('[SKU选择] SKU价格:', matchedSku.price);
|
||||
this.setData({
|
||||
selectSkuSellsPrice: matchedSku.price,
|
||||
currentPrice: matchedSku.price,
|
||||
'goods.price': matchedSku.price,
|
||||
});
|
||||
} else {
|
||||
console.warn('[SKU选择] 未找到匹配SKU');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
findMatchedSku(selectedSku, skuList) {
|
||||
return skuList.find((sku) => {
|
||||
if (!sku.specInfo || sku.specInfo.length === 0) return false;
|
||||
|
||||
return sku.specInfo.every((spec) => {
|
||||
const specId = String(spec.specId);
|
||||
const specValueId = String(spec.specValueId);
|
||||
const selectedSpecValueId = String(selectedSku[specId] || '');
|
||||
return selectedSpecValueId === specValueId;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.triggerEvent('close');
|
||||
},
|
||||
|
||||
onConfirm() {
|
||||
const { isAllSelectedSku, selectedSku, skuList, spuId, buyNum } = this.data;
|
||||
|
||||
if (!isAllSelectedSku) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '请选择完整的商品规格',
|
||||
theme: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找匹配SKU
|
||||
const matchedSku = this.findMatchedSku(selectedSku, skuList);
|
||||
|
||||
if (!matchedSku) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '该规格不存在',
|
||||
theme: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 触发confirm事件,传递数量
|
||||
this.triggerEvent('confirm', {
|
||||
spuId,
|
||||
skuId: matchedSku.skuId,
|
||||
price: matchedSku.price,
|
||||
quantity: buyNum,
|
||||
});
|
||||
},
|
||||
|
||||
handleBuyNumChange(e) {
|
||||
const { value } = e.detail;
|
||||
this.setData({
|
||||
buyNum: value,
|
||||
});
|
||||
},
|
||||
|
||||
onCloseOver() {
|
||||
this.triggerEvent('closeover');
|
||||
},
|
||||
},
|
||||
});
|
||||
7
miniprogram/pages/cart/components/specs-popup/index.json
Normal file
7
miniprogram/pages/cart/components/specs-popup/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-popup": "tdesign-miniprogram/popup/popup",
|
||||
"goods-card": "../../components/goods-card/index"
|
||||
}
|
||||
}
|
||||
55
miniprogram/pages/cart/components/specs-popup/index.wxml
Normal file
55
miniprogram/pages/cart/components/specs-popup/index.wxml
Normal file
@@ -0,0 +1,55 @@
|
||||
<t-popup visible="{{show}}" placement="bottom" z-index="{{zIndex}}" bind:visible-change="onClose">
|
||||
<view class="specs-popup">
|
||||
<view class="popup-close" bindtap="onClose">
|
||||
<t-icon name="close" size="36rpx" />
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="popup-header">
|
||||
<goods-card data="{{goods}}" layout="horizontal-wrap" thumb-mode="{{thumbMode}}" />
|
||||
</view>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<t-loading theme="circular" size="40rpx" text="加载中..." />
|
||||
</view>
|
||||
|
||||
<!-- 规格选择 -->
|
||||
<view wx:else class="spec-selection">
|
||||
<view class="spec-group" wx:for="{{specList}}" wx:key="specId">
|
||||
<view class="spec-group-title">{{item.title}}</view>
|
||||
<view class="spec-options">
|
||||
<view
|
||||
wx:for="{{item.specValueList}}"
|
||||
wx:for-item="specValue"
|
||||
wx:key="specValueId"
|
||||
class="spec-option {{specValue.isSelected ? 'spec-option--active' : ''}}"
|
||||
data-specid="{{item.specId}}"
|
||||
data-id="{{specValue.specValueId}}"
|
||||
bindtap="toChooseItem"
|
||||
>
|
||||
{{specValue.specValue}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数量选择 -->
|
||||
<view class="quantity-section">
|
||||
<view class="quantity-label">购买数量</view>
|
||||
<t-stepper
|
||||
value="{{buyNum}}"
|
||||
min="{{1}}"
|
||||
max="{{999}}"
|
||||
theme="filled"
|
||||
bind:change="handleBuyNumChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认按钮 -->
|
||||
<view class="bottom-btn {{!isAllSelectedSku ? 'bottom-btn--disabled' : ''}}" hover-class="bottom-btn--active" bindtap="onConfirm">
|
||||
确认
|
||||
</view>
|
||||
</view>
|
||||
</t-popup>
|
||||
<t-toast id="t-toast" />
|
||||
126
miniprogram/pages/cart/components/specs-popup/index.wxss
Normal file
126
miniprogram/pages/cart/components/specs-popup/index.wxss
Normal file
@@ -0,0 +1,126 @@
|
||||
.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;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 30rpx;
|
||||
z-index: 9;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.price-section {
|
||||
margin-top: 24rpx;
|
||||
padding: 0 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 36rpx;
|
||||
color: #fa4126;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 80rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spec-selection {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.spec-group {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.spec-group-title {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.spec-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.spec-option {
|
||||
min-width: 128rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
padding: 0 24rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f5f5f5;
|
||||
border: 2rpx solid #f5f5f5;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.spec-option--active {
|
||||
background-color: rgba(250, 65, 38, 0.04);
|
||||
border-color: #fa4126;
|
||||
color: #fa4126;
|
||||
}
|
||||
|
||||
.quantity-section {
|
||||
margin-top: 40rpx;
|
||||
padding-top: 32rpx;
|
||||
border-top: 1rpx solid #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.quantity-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bottom-btn {
|
||||
margin-top: 42rpx;
|
||||
position: relative;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #fa4126;
|
||||
color: white;
|
||||
border-radius: 40rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bottom-btn--disabled {
|
||||
background-color: #dddddd;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.bottom-btn--active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
555
miniprogram/pages/cart/index.js
Normal file
555
miniprogram/pages/cart/index.js
Normal file
@@ -0,0 +1,555 @@
|
||||
import Dialog from 'tdesign-miniprogram/dialog/index';
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
import {
|
||||
fetchCartGroupData,
|
||||
updateCartItem,
|
||||
removeFromCart,
|
||||
addToCart,
|
||||
selectCartItem,
|
||||
selectAllCartItems
|
||||
} from '../../services/cart/cart';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
cartGroupData: null,
|
||||
refresherTriggered: false, // 下拉刷新状态
|
||||
},
|
||||
|
||||
// 调用自定义tabbar的init函数,使页面与tabbar激活状态保持一致
|
||||
onShow() {
|
||||
this.getTabBar().init();
|
||||
// 每次显示页面时刷新购物车数据,确保数据是最新的
|
||||
this.refreshData();
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.refreshData();
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
this.getCartGroupData().then((res) => {
|
||||
let isEmpty = true;
|
||||
const cartGroupData = res.data;
|
||||
// 一些组件中需要的字段可能接口并没有返回,或者返回的数据结构与预期不一致,需要在此先对数据做一些处理
|
||||
// 统计门店下加购的商品是否全选、是否存在缺货/无货
|
||||
if (cartGroupData && cartGroupData.storeGoods) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cartGroupData && cartGroupData.invalidGoodItems) {
|
||||
cartGroupData.invalidGoodItems = cartGroupData.invalidGoodItems.map((goods) => {
|
||||
goods.originPrice = undefined;
|
||||
return goods;
|
||||
});
|
||||
}
|
||||
if (cartGroupData) {
|
||||
cartGroupData.isNotEmpty = !isEmpty;
|
||||
}
|
||||
this.setData({ cartGroupData });
|
||||
});
|
||||
},
|
||||
|
||||
findGoods(spuId, skuId) {
|
||||
let currentStore;
|
||||
let currentActivity;
|
||||
let currentGoods;
|
||||
if (!this.data.cartGroupData || !this.data.cartGroupData.storeGoods) {
|
||||
return {
|
||||
currentStore: null,
|
||||
currentActivity: null,
|
||||
currentGoods: null,
|
||||
};
|
||||
}
|
||||
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 = activity;
|
||||
currentGoods = goods;
|
||||
return {
|
||||
currentStore,
|
||||
currentActivity,
|
||||
currentGoods,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
currentStore,
|
||||
currentActivity,
|
||||
currentGoods,
|
||||
};
|
||||
},
|
||||
|
||||
// 获取购物车数据 - 每次都从后端获取最新数据
|
||||
getCartGroupData() {
|
||||
return fetchCartGroupData();
|
||||
},
|
||||
|
||||
// 选择单个商品
|
||||
selectGoodsService({ spuId, skuId, isSelected }) {
|
||||
// 先更新本地状态
|
||||
const { currentGoods } = this.findGoods(spuId, skuId);
|
||||
if (currentGoods) {
|
||||
currentGoods.isSelected = isSelected ? 1 : 0;
|
||||
}
|
||||
|
||||
// 调用API更新后端状态
|
||||
const cartId = currentGoods ? currentGoods.uid : spuId;
|
||||
return selectCartItem(cartId, isSelected).catch(err => {
|
||||
console.error('选择商品失败:', err);
|
||||
// 如果API调用失败,恢复本地状态
|
||||
if (currentGoods) {
|
||||
currentGoods.isSelected = isSelected ? 0 : 1;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
// 全选门店
|
||||
// 注:实际场景时应该调用接口更改选中状态
|
||||
selectStoreService({ storeId, isSelected }) {
|
||||
if (!this.data.cartGroupData || !this.data.cartGroupData.storeGoods) {
|
||||
return;
|
||||
}
|
||||
const currentStore = this.data.cartGroupData.storeGoods.find((s) => s.storeId === storeId);
|
||||
if (currentStore) {
|
||||
currentStore.isSelected = isSelected;
|
||||
// 添加数据验证,确保数组存在
|
||||
if (Array.isArray(currentStore.promotionGoodsList)) {
|
||||
currentStore.promotionGoodsList.forEach((activity) => {
|
||||
if (Array.isArray(activity.goodsPromotionList)) {
|
||||
activity.goodsPromotionList.forEach((goods) => {
|
||||
goods.isSelected = isSelected;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
// 加购数量变更
|
||||
async changeQuantityService(spuId, skuId, quantity) {
|
||||
try {
|
||||
const goods = this.findGoods(spuId, skuId);
|
||||
if (!goods) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '商品不存在',
|
||||
theme: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用后端API,传递skuId
|
||||
await updateCartItem(spuId, quantity, skuId);
|
||||
|
||||
// 更新本地数据
|
||||
goods.quantity = quantity;
|
||||
this.setData({
|
||||
cartGroupData: this.data.cartGroupData,
|
||||
});
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '修改成功',
|
||||
theme: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('修改数量失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: error.message || '修改失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 删除商品
|
||||
async deleteGoodsService(spuId, skuId) {
|
||||
try {
|
||||
const goods = this.findGoods(spuId, skuId);
|
||||
if (!goods.currentGoods) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '商品不存在',
|
||||
theme: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用后端API,传递skuId
|
||||
await removeFromCart(spuId, skuId);
|
||||
|
||||
// 重新获取购物车数据
|
||||
this.refreshData();
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '删除成功',
|
||||
theme: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除商品失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: error.message || '删除失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 清空失效商品
|
||||
// 注:实际场景时应该调用接口
|
||||
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, stockQuantity).then(() => this.refreshData());
|
||||
})
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
this.changeQuantityService(spuId, skuId, quantity).then(() => this.refreshData());
|
||||
},
|
||||
|
||||
// SKU变更
|
||||
async onSkuChange(e) {
|
||||
const { oldSpuId, oldSkuId, newSpuId, newSkuId, quantity } = e.detail;
|
||||
|
||||
try {
|
||||
// 先删除旧SKU
|
||||
await removeFromCart(oldSpuId, oldSkuId);
|
||||
|
||||
// 再添加新SKU,使用用户选择的数量
|
||||
await addToCart(newSpuId, newSkuId, quantity || 1);
|
||||
|
||||
// 刷新购物车数据
|
||||
this.refreshData();
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '规格修改成功',
|
||||
theme: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('SKU变更失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: error.message || '修改规格失败',
|
||||
theme: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
goCollect() {
|
||||
/** 活动肯定有一个活动ID,用来获取活动banner,活动商品列表等 */
|
||||
const promotionID = '123';
|
||||
wx.navigateTo({
|
||||
url: `/pages/promotion/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(() => {
|
||||
// deleteGoodsService 内部已经包含了Toast提示和数据刷新,无需重复操作
|
||||
this.deleteGoodsService(spuId, skuId);
|
||||
});
|
||||
},
|
||||
|
||||
onSelectAll(event) {
|
||||
const { isAllSelected } = event?.detail ?? {};
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: `${isAllSelected ? '取消' : '点击'}了全选按钮`,
|
||||
});
|
||||
|
||||
// 调用接口改变全选
|
||||
selectAllCartItems(!isAllSelected).then(() => {
|
||||
this.refreshData();
|
||||
}).catch(err => {
|
||||
console.error('全选操作失败:', err);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '操作失败,请重试',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onToSettle() {
|
||||
const goodsRequestList = [];
|
||||
let totalAmount = 0;
|
||||
let selectedCount = 0;
|
||||
|
||||
// 添加数据验证,确保数据结构存在
|
||||
if (this.data.cartGroupData && Array.isArray(this.data.cartGroupData.storeGoods)) {
|
||||
this.data.cartGroupData.storeGoods.forEach((store) => {
|
||||
if (Array.isArray(store.promotionGoodsList)) {
|
||||
store.promotionGoodsList.forEach((promotion) => {
|
||||
if (Array.isArray(promotion.goodsPromotionList)) {
|
||||
promotion.goodsPromotionList.forEach((m) => {
|
||||
if (m.isSelected == 1) {
|
||||
// 检查库存
|
||||
if (m.quantity > m.stockQuantity) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: `${m.title}库存不足,请调整数量`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
goodsRequestList.push({
|
||||
...m,
|
||||
// 确保价格格式正确
|
||||
price: parseInt(m.price) || 0,
|
||||
originPrice: parseInt(m.originPrice) || parseInt(m.price) || 0,
|
||||
});
|
||||
selectedCount += m.quantity;
|
||||
totalAmount += (parseInt(m.price) || 0) * m.quantity;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证是否有选中的商品
|
||||
if (goodsRequestList.length === 0) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '请选择要结算的商品',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证用户是否登录
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
Dialog.confirm({
|
||||
title: '提示',
|
||||
content: '请先登录后再进行结算',
|
||||
confirmBtn: '去登录',
|
||||
cancelBtn: '取消',
|
||||
}).then(() => {
|
||||
wx.navigateTo({ url: '/pages/login/index' });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存结算数据到本地存储
|
||||
const orderData = {
|
||||
goodsRequestList,
|
||||
totalAmount,
|
||||
selectedCount,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
try {
|
||||
wx.setStorageSync('order.goodsRequestList', JSON.stringify(goodsRequestList));
|
||||
wx.setStorageSync('order.settlementData', JSON.stringify(orderData));
|
||||
|
||||
// 跳转到订单确认页面
|
||||
wx.navigateTo({
|
||||
url: '/pages/order/order-confirm/index?type=cart',
|
||||
success: () => {
|
||||
console.log('跳转到订单确认页面成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转失败:', err);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '跳转失败,请重试',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存订单数据失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '数据保存失败,请重试',
|
||||
});
|
||||
}
|
||||
},
|
||||
onGotoHome() {
|
||||
wx.switchTab({ url: '/pages/home/home' });
|
||||
},
|
||||
|
||||
// 下拉刷新相关方法
|
||||
onRefresh() {
|
||||
console.log('开始下拉刷新');
|
||||
this.setData({
|
||||
refresherTriggered: true,
|
||||
});
|
||||
|
||||
// 重新获取购物车数据
|
||||
this.refreshData();
|
||||
|
||||
// 模拟网络请求延迟,确保用户能看到刷新动画
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
refresherTriggered: false,
|
||||
});
|
||||
|
||||
// 显示刷新成功提示
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '刷新成功',
|
||||
theme: 'success',
|
||||
duration: 1500,
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
onRefresherPulling(e) {
|
||||
console.log('下拉中', e.detail);
|
||||
},
|
||||
|
||||
onRefresherRestore(e) {
|
||||
console.log('刷新恢复', e.detail);
|
||||
},
|
||||
|
||||
onRefresherAbort(e) {
|
||||
console.log('刷新中止', e.detail);
|
||||
this.setData({
|
||||
refresherTriggered: false,
|
||||
});
|
||||
},
|
||||
|
||||
// 分享功能
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '购物车 - 查看我的心仪好物',
|
||||
path: '/pages/cart/index'
|
||||
};
|
||||
},
|
||||
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '购物车 - 查看我的心仪好物'
|
||||
};
|
||||
}
|
||||
});
|
||||
10
miniprogram/pages/cart/index.json
Normal file
10
miniprogram/pages/cart/index.json
Normal 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"
|
||||
}
|
||||
}
|
||||
52
miniprogram/pages/cart/index.wxml
Normal file
52
miniprogram/pages/cart/index.wxml
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- 下拉刷新容器 -->
|
||||
<scroll-view
|
||||
class="cart-scroll-view"
|
||||
scroll-y="{{true}}"
|
||||
refresher-enabled="{{true}}"
|
||||
refresher-threshold="{{45}}"
|
||||
refresher-default-style="black"
|
||||
refresher-background="white"
|
||||
refresher-triggered="{{refresherTriggered}}"
|
||||
bindrefresherrefresh="onRefresh"
|
||||
bindrefresherpulling="onRefresherPulling"
|
||||
bindrefresherrestore="onRefresherRestore"
|
||||
bindrefresherabort="onRefresherAbort"
|
||||
>
|
||||
<!-- 分层购物车 -->
|
||||
<block wx:if="{{cartGroupData.isNotEmpty}}">
|
||||
<cart-group
|
||||
store-goods="{{ cartGroupData.storeGoods }}"
|
||||
invalid-good-items="{{ cartGroupData.invalidGoodItems }}"
|
||||
bindselectgoods="onGoodsSelect"
|
||||
bindselectstore="onStoreSelect"
|
||||
bindchangequantity="onQuantityChange"
|
||||
bindchangesku="onSkuChange"
|
||||
bindgocollect="goCollect"
|
||||
bindgoodsclick="goGoodsDetail"
|
||||
bindclearinvalidgoods="clearInvalidGoods"
|
||||
binddelete="onGoodsDelete"
|
||||
/>
|
||||
|
||||
<view class="gap" />
|
||||
</block>
|
||||
<!-- 购物车空态 -->
|
||||
<cart-empty wx:else bind:handleClick="onGotoHome" />
|
||||
|
||||
<!-- 底部占位,避免被底部栏遮挡 -->
|
||||
<view class="bottom-placeholder"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 商品小计以及结算按钮 -->
|
||||
<cart-bar
|
||||
wx:if="{{cartGroupData.isNotEmpty}}"
|
||||
is-all-selected="{{cartGroupData.isAllSelected}}"
|
||||
total-amount="{{cartGroupData.totalAmount}}"
|
||||
total-goods-num="{{cartGroupData.selectedGoodsCount}}"
|
||||
total-discount-amount="{{cartGroupData.totalDiscountAmount}}"
|
||||
fixed="{{true}}"
|
||||
bottomHeight="{{110}}"
|
||||
bindhandleSelectAll="onSelectAll"
|
||||
bindhandleToSettle="onToSettle"
|
||||
/>
|
||||
<t-toast id="t-toast" />
|
||||
<t-dialog id="t-dialog" />
|
||||
26
miniprogram/pages/cart/index.wxss
Normal file
26
miniprogram/pages/cart/index.wxss
Normal file
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cart-scroll-view {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.gap {
|
||||
height: 100rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-placeholder {
|
||||
height: 120rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.t-button {
|
||||
--td-button-default-color: #000;
|
||||
--td-button-primary-text-color: #fa4126;
|
||||
}
|
||||
Reference in New Issue
Block a user