Files
ai_dianshang/miniprogram/pages/goods/details/components/goods-specs-popup/index.js
2025-11-17 14:11:46 +08:00

365 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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,
});
},
},
});