init
This commit is contained in:
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;
|
||||
}
|
||||
Reference in New Issue
Block a user