Files
ai_dianshang/miniprogram/pages/goods/details/components/goods-specs-popup/index.js

365 lines
10 KiB
JavaScript
Raw Normal View History

2025-11-17 14:11:46 +08:00
/* eslint-disable no-param-reassign */
/* eslint-disable no-nested-ternary */
import Toast from 'tdesign-miniprogram/toast/index';
Component({
options: {
multipleSlots: true,
addGlobalClass: true,
},
properties: {
src: {
type: String,
},
title: String,
show: {
type: Boolean,
value: false,
},
limitBuyInfo: {
type: String,
value: '',
},
isStock: {
type: Boolean,
value: true,
},
limitMaxCount: {
type: Number,
value: 999,
},
limitMinCount: {
type: Number,
value: 1,
},
skuList: {
type: Array,
value: [],
observer(skuList) {
if (skuList && skuList.length > 0) {
if (this.initStatus) {
this.initData();
}
}
},
},
specList: {
type: Array,
value: [],
observer(specList) {
if (specList && specList.length > 0) {
this.initData();
}
},
},
outOperateStatus: {
type: Boolean,
value: false,
},
hasAuth: {
type: Boolean,
value: false,
},
count: {
type: Number,
value: 1,
observer(count) {
this.setData({
buyNum: count,
});
},
},
},
initStatus: false,
selectedSku: {},
selectSpecObj: {},
data: {
buyNum: 1,
isAllSelectedSku: false,
},
methods: {
initData() {
const { skuList } = this.properties;
const { specList } = this.properties;
// 为每个规格值计算库存信息
specList.forEach((item) => {
if (item.specValueList.length > 0) {
item.specValueList.forEach((subItem) => {
const obj = this.checkSkuStockQuantity(subItem, skuList, item);
subItem.hasStockObj = obj;
// 初始化未选中状态
subItem.isSelected = false;
});
}
});
// 默认选择策略优先选择第一个有库存的SKU组合其次选择第一个SKU
const getQty = (sku) => {
return Number((sku.stockInfo && sku.stockInfo.stockQuantity) ?? sku.stock ?? sku.quantity ?? 0) || 0;
};
const defaultSku = (skuList || []).find((s) => getQty(s) > 0) || (skuList || [])[0];
// 构建 selectedSku 映射,并在 specList 中标记选中项
const selectedSkuMap = {};
if (defaultSku && Array.isArray(defaultSku.specInfo)) {
defaultSku.specInfo.forEach((spec) => {
const specId = spec.specId;
const specValueId = spec.specValueId;
const specTitle = spec.specTitle;
const specValue = spec.specValue;
// 在对应的规格组内标记选中项
specList.forEach((group) => {
const sameGroup = String(group.specId) === String(specId) || (!!specTitle && group.title === specTitle);
if (sameGroup && Array.isArray(group.specValueList)) {
group.specValueList.forEach((sv) => {
const matchById = specValueId != null && String(sv.specValueId) === String(specValueId);
const matchByText = specValue != null && sv.specValue != null && sv.specValue === specValue;
sv.isSelected = matchById || matchByText;
});
// 记录所选项的ID优先使用ID
const chosen = group.specValueList.find((sv) => sv.isSelected);
if (chosen) {
selectedSkuMap[group.specId] = chosen.specValueId;
}
}
});
});
} else {
// 当没有SKU或SKU缺少specInfo时退化为每组选择第一个有库存的规格值
specList.forEach((group) => {
const firstAvailable = (group.specValueList || []).find((sv) => sv.hasStockObj?.hasStock) || (group.specValueList || [])[0];
if (firstAvailable) {
group.specValueList.forEach((sv) => { sv.isSelected = sv === firstAvailable; });
selectedSkuMap[group.specId] = firstAvailable.specValueId;
}
});
}
// 更新组件状态
const isAllSelectedSku = specList.every((spec) => !!selectedSkuMap[spec.specId]);
this.selectedSku = selectedSkuMap;
this.selectSpecObj = {};
this.setData({
specList,
isAllSelectedSku,
});
// 通知父页面已完成默认选择,便于价格与图片联动
this.triggerEvent('change', {
selectedSku: this.selectedSku,
isAllSelectedSku,
});
this.initStatus = true;
},
// 兼容两种数据结构:
// 1) sku.specInfo 使用 { specId, specValueId }
// 2) sku.specInfo 使用 { specTitle, specValue }
// 同时 specList.subItem 具备 { specValueId, specValue }
checkSkuStockQuantity(specValueItem, skuList, specGroupItem) {
let hasStock = false;
let stockQuantity = 0;
skuList.forEach((sku) => {
if (sku.specInfo) {
sku.specInfo.forEach((spec) => {
// 统一以字符串比较,避免类型不一致导致匹配失败
const matchById =
spec.specValueId != null && specValueItem.specValueId != null &&
String(spec.specValueId) === String(specValueItem.specValueId);
const sameGroupByTitle =
spec.specTitle && specGroupItem && specGroupItem.title && spec.specTitle === specGroupItem.title;
const matchByText =
spec.specValue != null && specValueItem.specValue != null && spec.specValue === specValueItem.specValue &&
(sameGroupByTitle || true); // 若无 title 也放行文本匹配
if (matchById || matchByText) {
// 兼容多种库存字段来源,优先 stockInfo.stockQuantity
const qty = Number(
(sku.stockInfo && sku.stockInfo.stockQuantity) ?? sku.stock ?? sku.quantity ?? 0
) || 0;
stockQuantity += qty;
if (qty > 0) {
hasStock = true;
}
}
});
}
});
return {
hasStock,
stockQuantity,
};
},
toChooseItem(e) {
const { specid, id, val } = e.currentTarget.dataset;
// 放开点击,避免因错误的 hasStock 计算而阻断选择
const { specList } = this.data;
const { skuList } = this.properties;
specList.forEach((spec) => {
if (String(spec.specId) === String(specid)) {
spec.specValueList.forEach((specValue) => {
if (String(specValue.specValueId) === String(id)) {
specValue.isSelected = !specValue.isSelected;
} else {
specValue.isSelected = false;
}
});
}
});
this.selectSpecObj[specid] = {
specId: specid,
specValueId: id,
specValue: val,
};
this.selectedSku[specid] = id;
const isAllSelectedSku = this.isAllSelectedSku();
this.setData({
specList,
isAllSelectedSku,
});
// 与父页面绑定的事件名保持一致:父页面使用 bind:change="chooseSpecItem"
this.triggerEvent('change', {
selectedSku: this.selectedSku,
isAllSelectedSku,
});
},
isAllSelectedSku() {
const { specList } = this.properties;
let isAllSelectedSku = true;
specList.forEach((spec) => {
if (!this.selectedSku[spec.specId]) {
isAllSelectedSku = false;
}
});
return isAllSelectedSku;
},
getSelectedSkuId() {
const { skuList } = this.properties;
const { selectedSku } = this;
let selectedSkuId = '';
skuList.forEach((sku) => {
let isMatch = true;
if (sku.specInfo) {
sku.specInfo.forEach((spec) => {
// 统一字符串比较避免类型不一致导致匹配失败
if (String(selectedSku[spec.specId]) !== String(spec.specValueId)) {
isMatch = false;
}
});
}
if (isMatch) {
selectedSkuId = sku.skuId;
}
});
return selectedSkuId;
},
handlePopupHide() {
// 触发与父页面绑定一致的事件名,确保可以关闭弹窗
this.triggerEvent('closeSpecsPopup');
},
specsConfirm() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('specsConfirm', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
addCart() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('addCart', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
buyNow() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('buyNow', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
setBuyNum(e) {
const { value } = e.detail;
this.setData({
buyNum: value,
});
},
changeNum(e) {
const { buyNum } = e.detail;
this.setData({
buyNum,
});
},
handleBuyNumChange(e) {
const { value } = e.detail;
this.setData({
buyNum: value,
});
// 触发父组件的 changeNum 事件,通知价格更新
this.triggerEvent('changeNum', {
buyNum: value,
});
},
},
});