980 lines
28 KiB
JavaScript
980 lines
28 KiB
JavaScript
|
|
import Toast from 'tdesign-miniprogram/toast/index';
|
|||
|
|
import { fetchGood } from '../../../services/good/fetchGood';
|
|||
|
|
import { fetchActivityList } from '../../../services/activity/fetchActivityList';
|
|||
|
|
const { fetchComments } = require('../../../services/comments/fetchComments');
|
|||
|
|
const { fetchCommentsCount } = require('../../../services/comments/fetchCommentsCount');
|
|||
|
|
import { addToCart } from '../../../services/cart/cart';
|
|||
|
|
import { checkIsFavorite, toggleFavorite } from '../../../services/good/favorite';
|
|||
|
|
|
|||
|
|
import { cdnBase } from '../../../config/index';
|
|||
|
|
|
|||
|
|
const imgPrefix = `${cdnBase}/`;
|
|||
|
|
|
|||
|
|
const recLeftImg = `${imgPrefix}common/rec-left.png`;
|
|||
|
|
const recRightImg = `${imgPrefix}common/rec-right.png`;
|
|||
|
|
const obj2Params = (obj = {}, encode = false) => {
|
|||
|
|
const result = [];
|
|||
|
|
Object.keys(obj).forEach((key) => result.push(`${key}=${encode ? encodeURIComponent(obj[key]) : obj[key]}`));
|
|||
|
|
|
|||
|
|
return result.join('&');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
Page({
|
|||
|
|
data: {
|
|||
|
|
commentsList: [],
|
|||
|
|
commentsStatistics: {
|
|||
|
|
badCount: 0,
|
|||
|
|
commentCount: 0,
|
|||
|
|
goodCount: 0,
|
|||
|
|
goodRate: 0,
|
|||
|
|
hasImageCount: 0,
|
|||
|
|
middleCount: 0,
|
|||
|
|
},
|
|||
|
|
isShowPromotionPop: false,
|
|||
|
|
activityList: [],
|
|||
|
|
recLeftImg,
|
|||
|
|
recRightImg,
|
|||
|
|
details: {
|
|||
|
|
limitInfo: [], // 确保 limitInfo 有默认值,防止数组访问错误
|
|||
|
|
desc: [], // 默认空数组,避免 .length 访问 undefined
|
|||
|
|
descriptionText: '', // 默认空字符串,确保兜底文本判断正常
|
|||
|
|
},
|
|||
|
|
jumpArray: [
|
|||
|
|
{
|
|||
|
|
title: '购物车',
|
|||
|
|
url: '/pages/cart/index',
|
|||
|
|
iconName: 'cart',
|
|||
|
|
showCartNum: true,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
isStock: true,
|
|||
|
|
cartNum: 0,
|
|||
|
|
soldout: false,
|
|||
|
|
buttonType: 1,
|
|||
|
|
buyNum: 1,
|
|||
|
|
selectedAttrStr: '',
|
|||
|
|
skuArray: [],
|
|||
|
|
primaryImage: '',
|
|||
|
|
specImg: '',
|
|||
|
|
isSpuSelectPopupShow: false,
|
|||
|
|
isAllSelectedSku: false,
|
|||
|
|
buyType: 0,
|
|||
|
|
outOperateStatus: false, // 是否外层加入购物车
|
|||
|
|
operateType: 0,
|
|||
|
|
selectSkuSellsPrice: 0,
|
|||
|
|
maxLinePrice: 0,
|
|||
|
|
minSalePrice: 0,
|
|||
|
|
maxSalePrice: 0,
|
|||
|
|
list: [],
|
|||
|
|
spuId: '',
|
|||
|
|
navigation: { type: 'fraction' },
|
|||
|
|
current: 0,
|
|||
|
|
autoplay: true,
|
|||
|
|
duration: 500,
|
|||
|
|
interval: 5000,
|
|||
|
|
soldNum: 0, // 已售数量
|
|||
|
|
isFavorite: false, // 收藏状态
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onLoad(options) {
|
|||
|
|
console.log('[商品详情页] onLoad 接收到的参数:', options);
|
|||
|
|
const { spuId, skuId } = options;
|
|||
|
|
// 兼容新旧参数名,优先使用spuId,如果没有则使用skuId
|
|||
|
|
const id = spuId || skuId;
|
|||
|
|
console.log('[商品详情页] 最终使用的spuId:', id, '类型:', typeof id);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
spuId: id,
|
|||
|
|
});
|
|||
|
|
this.init(id);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onShow() {
|
|||
|
|
this.getCartNum();
|
|||
|
|
// 返回页面时重新校验收藏状态,避免 onLoad 不触发导致状态不更新
|
|||
|
|
const { spuId } = this.data;
|
|||
|
|
if (spuId) {
|
|||
|
|
this.checkFavoriteStatus(spuId);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async getDetail(spuId) {
|
|||
|
|
try {
|
|||
|
|
const detail = await fetchGood(spuId);
|
|||
|
|
console.log('[FETCHED_DETAIL_RAW]', {
|
|||
|
|
id: spuId,
|
|||
|
|
desc: detail && detail.desc,
|
|||
|
|
descType: detail && (Array.isArray(detail.desc) ? 'array' : typeof detail.desc),
|
|||
|
|
descriptionText: detail && detail.descriptionText,
|
|||
|
|
});
|
|||
|
|
const {
|
|||
|
|
images,
|
|||
|
|
primaryImage,
|
|||
|
|
minSalePrice,
|
|||
|
|
maxSalePrice,
|
|||
|
|
maxLinePrice,
|
|||
|
|
spuId: id,
|
|||
|
|
available,
|
|||
|
|
skuList = [],
|
|||
|
|
specList = [],
|
|||
|
|
desc = [],
|
|||
|
|
descriptionText = '',
|
|||
|
|
etitle = '',
|
|||
|
|
soldNum = 0,
|
|||
|
|
...rest
|
|||
|
|
} = detail;
|
|||
|
|
|
|||
|
|
const skuArray = skuList.map((sku) => ({
|
|||
|
|
...sku,
|
|||
|
|
specInfo: sku.specInfo || [],
|
|||
|
|
// 规范库存为数值,避免字符串或undefined导致判断错误
|
|||
|
|
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,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
details: {
|
|||
|
|
...rest,
|
|||
|
|
images: images || [primaryImage],
|
|||
|
|
primaryImage,
|
|||
|
|
minSalePrice,
|
|||
|
|
maxSalePrice,
|
|||
|
|
maxLinePrice,
|
|||
|
|
spuId: id,
|
|||
|
|
available,
|
|||
|
|
skuList,
|
|||
|
|
specList,
|
|||
|
|
desc,
|
|||
|
|
descriptionText,
|
|||
|
|
etitle,
|
|||
|
|
soldNum,
|
|||
|
|
},
|
|||
|
|
primaryImage,
|
|||
|
|
minSalePrice,
|
|||
|
|
maxSalePrice,
|
|||
|
|
maxLinePrice,
|
|||
|
|
soldNum, // 添加soldNum到页面数据中
|
|||
|
|
skuArray,
|
|||
|
|
// 全局库存仅依据 SKU 数量判断,避免 available 缺失导致售罄
|
|||
|
|
isStock: skuArray.some((sku) => Number(sku.quantity) > 0),
|
|||
|
|
selectSkuSellsPrice: minSalePrice,
|
|||
|
|
specImg: primaryImage,
|
|||
|
|
totalPrice: minSalePrice * (this.data.buyNum || 1),
|
|||
|
|
}, () => {
|
|||
|
|
this.debugLogDetails();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取活动信息
|
|||
|
|
this.getActivityList();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取商品详情失败:', error);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '获取商品信息失败',
|
|||
|
|
theme: 'error',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
debugLogDetails() {
|
|||
|
|
const d = this.data.details || {};
|
|||
|
|
console.log('[DETAILS_RENDER_CHECK]', {
|
|||
|
|
spuId: d.spuId,
|
|||
|
|
title: d.title,
|
|||
|
|
imagesLen: (d.images || []).length,
|
|||
|
|
descType: Array.isArray(d.desc) ? 'array' : typeof d.desc,
|
|||
|
|
descLen: Array.isArray(d.desc) ? d.desc.length : (d.desc ? 1 : 0),
|
|||
|
|
firstDesc: Array.isArray(d.desc) ? d.desc[0] : d.desc,
|
|||
|
|
descriptionTextLen: (d.descriptionText || '').length,
|
|||
|
|
showTitle: ((Array.isArray(d.desc) && d.desc.length > 0) || !!d.descriptionText),
|
|||
|
|
showImages: (Array.isArray(d.desc) && d.desc.length > 0),
|
|||
|
|
showTextFallback: ((!Array.isArray(d.desc) || d.desc.length === 0) && !!d.descriptionText),
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async getActivityList() {
|
|||
|
|
try {
|
|||
|
|
const activityRes = await fetchActivityList();
|
|||
|
|
// 兼容不同返回结构:数组 / { list: [] } / 其他
|
|||
|
|
const activityList = Array.isArray(activityRes)
|
|||
|
|
? activityRes
|
|||
|
|
: (activityRes && Array.isArray(activityRes.list) ? activityRes.list : []);
|
|||
|
|
// 将活动列表映射为弹窗显示需要的 list 结构
|
|||
|
|
const list = activityList.map((item) => ({
|
|||
|
|
tag: item.tag || '活动',
|
|||
|
|
label: (item.activityLadder && item.activityLadder[0] && item.activityLadder[0].label) || '',
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
activityList: activityList || [],
|
|||
|
|
list,
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取活动列表失败:', error);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handlePopupHide() {
|
|||
|
|
this.setData({
|
|||
|
|
isSpuSelectPopupShow: false,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
showSkuSelectPopup(type) {
|
|||
|
|
// 确保有商品图片显示
|
|||
|
|
const specImg = this.data.specImg || this.data.primaryImage || '';
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
buyType: type || 0,
|
|||
|
|
outOperateStatus: type >= 1,
|
|||
|
|
isSpuSelectPopupShow: true,
|
|||
|
|
specImg: specImg, // 修复弹窗图片显示问题
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加触觉反馈
|
|||
|
|
wx.vibrateShort({
|
|||
|
|
type: 'light'
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
buyItNow() {
|
|||
|
|
const { details } = this.data;
|
|||
|
|
const hasSpecs = details.specList && details.specList.length > 0;
|
|||
|
|
|
|||
|
|
if (hasSpecs) {
|
|||
|
|
// 有规格,显示规格选择弹窗
|
|||
|
|
this.showSkuSelectPopup(1);
|
|||
|
|
} else {
|
|||
|
|
// 没有规格,直接购买
|
|||
|
|
this.gotoBuy(1);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toAddCart() {
|
|||
|
|
const { details } = this.data;
|
|||
|
|
const hasSpecs = details.specList && details.specList.length > 0;
|
|||
|
|
|
|||
|
|
if (hasSpecs) {
|
|||
|
|
// 有规格,显示规格选择弹窗
|
|||
|
|
this.showSkuSelectPopup(2);
|
|||
|
|
} else {
|
|||
|
|
// 没有规格,直接加入购物车
|
|||
|
|
this.addCart();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toNav(e) {
|
|||
|
|
const { url } = e.detail;
|
|||
|
|
wx.switchTab({
|
|||
|
|
url: url,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
showCurImg(e) {
|
|||
|
|
const { index } = e.detail;
|
|||
|
|
const { images } = this.data.details;
|
|||
|
|
wx.previewImage({
|
|||
|
|
current: images[index],
|
|||
|
|
urls: images, // 需要预览的图片http链接列表
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
chooseSpecItem(e) {
|
|||
|
|
const { specList } = this.data.details;
|
|||
|
|
const { selectedSku, isAllSelectedSku } = e.detail;
|
|||
|
|
|
|||
|
|
// 如果没有全部选择规格,重置价格但保持库存状态为true(因为有可用的SKU组合)
|
|||
|
|
if (!isAllSelectedSku) {
|
|||
|
|
// 计算总库存量来判断是否有库存
|
|||
|
|
const totalStock = this.data.skuArray.reduce((sum, sku) => sum + sku.quantity, 0);
|
|||
|
|
const unitPrice = this.data.minSalePrice;
|
|||
|
|
const buyNum = this.data.buyNum || 1;
|
|||
|
|
this.setData({
|
|||
|
|
selectSkuSellsPrice: unitPrice,
|
|||
|
|
isStock: totalStock > 0,
|
|||
|
|
specImg: this.data.primaryImage,
|
|||
|
|
totalPrice: unitPrice * buyNum,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
isAllSelectedSku,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取匹配的SKU项目
|
|||
|
|
this.getSkuItem(specList, selectedSku);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
getSkuItem(specList, selectedSku) {
|
|||
|
|
const { skuArray, primaryImage, minSalePrice, buyNum } = this.data;
|
|||
|
|
const selectedSkuValues = this.getSelectedSkuValues(specList, selectedSku);
|
|||
|
|
let selectedAttrStr = ` 件 `;
|
|||
|
|
selectedSkuValues.forEach((item) => {
|
|||
|
|
selectedAttrStr += `,${item.specValue} `;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 查找匹配的SKU项目
|
|||
|
|
const skuItem = skuArray.find((item) => {
|
|||
|
|
let status = true;
|
|||
|
|
(item.specInfo || []).forEach((subItem) => {
|
|||
|
|
// 确保specId类型一致进行比较
|
|||
|
|
const specId = String(subItem.specId);
|
|||
|
|
const specValueId = String(subItem.specValueId);
|
|||
|
|
const selectedSpecValueId = String(selectedSku[specId] || selectedSku[subItem.specId] || '');
|
|||
|
|
|
|||
|
|
if (!selectedSpecValueId || selectedSpecValueId !== specValueId) {
|
|||
|
|
status = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return status;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.selectSpecsName(selectedSkuValues.length > 0 ? selectedAttrStr : '');
|
|||
|
|
|
|||
|
|
if (skuItem) {
|
|||
|
|
const unitPrice = skuItem.price || minSalePrice || 0;
|
|||
|
|
this.setData({
|
|||
|
|
selectItem: skuItem,
|
|||
|
|
selectSkuSellsPrice: unitPrice,
|
|||
|
|
isStock: skuItem.quantity > 0,
|
|||
|
|
specImg: skuItem.image || primaryImage,
|
|||
|
|
totalPrice: unitPrice * (buyNum || 1),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 没有找到匹配的SKU时,检查是否还有其他可用的SKU
|
|||
|
|
const totalStock = skuArray.reduce((sum, sku) => sum + sku.quantity, 0);
|
|||
|
|
const unitPrice = minSalePrice || 0;
|
|||
|
|
this.setData({
|
|||
|
|
selectItem: null,
|
|||
|
|
selectSkuSellsPrice: unitPrice,
|
|||
|
|
isStock: totalStock > 0,
|
|||
|
|
specImg: primaryImage,
|
|||
|
|
totalPrice: unitPrice * (buyNum || 1),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return skuItem;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取已选择的sku名称
|
|||
|
|
getSelectedSkuValues(skuTree, selectedSku) {
|
|||
|
|
const normalizedTree = this.normalizeSkuTree(skuTree);
|
|||
|
|
return Object.keys(selectedSku).reduce((selectedValues, skuKeyStr) => {
|
|||
|
|
const skuValues = normalizedTree[skuKeyStr];
|
|||
|
|
const skuValueId = selectedSku[skuKeyStr];
|
|||
|
|
if (skuValueId !== '') {
|
|||
|
|
const filteredValues = skuValues.filter((value) => {
|
|||
|
|
return value.specValueId === skuValueId;
|
|||
|
|
});
|
|||
|
|
const skuValue = filteredValues.length > 0 ? filteredValues[0] : null;
|
|||
|
|
skuValue && selectedValues.push(skuValue);
|
|||
|
|
}
|
|||
|
|
return selectedValues;
|
|||
|
|
}, []);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
normalizeSkuTree(skuTree) {
|
|||
|
|
const normalizedTree = {};
|
|||
|
|
skuTree.forEach((treeItem) => {
|
|||
|
|
normalizedTree[treeItem.specId] = treeItem.specValueList;
|
|||
|
|
});
|
|||
|
|
return normalizedTree;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
selectSpecsName(selectSpecsName) {
|
|||
|
|
if (selectSpecsName) {
|
|||
|
|
this.setData({
|
|||
|
|
selectedAttrStr: selectSpecsName,
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
this.setData({
|
|||
|
|
selectedAttrStr: '',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async addCart() {
|
|||
|
|
const { isAllSelectedSku, buyNum, details, selectSkuSellsPrice, minSalePrice, selectItem } = this.data;
|
|||
|
|
|
|||
|
|
// 检查商品是否有规格,如果有规格但未全部选择,则提示选择规格
|
|||
|
|
const hasSpecs = details.specList && details.specList.length > 0;
|
|||
|
|
if (hasSpecs && !isAllSelectedSku) {
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '请选择规格',
|
|||
|
|
icon: '',
|
|||
|
|
duration: 1000,
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查用户是否已登录
|
|||
|
|
const token = wx.getStorageSync('token');
|
|||
|
|
if (!token) {
|
|||
|
|
wx.showModal({
|
|||
|
|
title: '提示',
|
|||
|
|
content: '请先登录后再添加购物车',
|
|||
|
|
confirmText: '去登录',
|
|||
|
|
cancelText: '取消',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
wx.navigateTo({ url: '/pages/login/index' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 显示加载提示
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: '加入购物车中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 调用后端API添加到购物车
|
|||
|
|
const productId = parseInt(details.spuId || this.data.spuId) || 0;
|
|||
|
|
// 获取正确的SKU ID
|
|||
|
|
let skuId = 0;
|
|||
|
|
if (hasSpecs && isAllSelectedSku && selectItem) {
|
|||
|
|
// 有规格且已选择完整规格,使用选中的SKU ID
|
|||
|
|
skuId = parseInt(selectItem.skuId) || 0;
|
|||
|
|
} else if (!hasSpecs && details.skuList && details.skuList.length > 0) {
|
|||
|
|
// 无规格商品,使用第一个SKU ID
|
|||
|
|
skuId = parseInt(details.skuList[0].skuId) || 0;
|
|||
|
|
}
|
|||
|
|
// 如果都没有,skuId 保持为 0
|
|||
|
|
|
|||
|
|
await addToCart(productId, skuId, buyNum);
|
|||
|
|
|
|||
|
|
// 隐藏加载提示
|
|||
|
|
wx.hideLoading();
|
|||
|
|
|
|||
|
|
// 更新购物车数量显示
|
|||
|
|
this.getCartNum();
|
|||
|
|
|
|||
|
|
// 关闭弹窗
|
|||
|
|
this.handlePopupHide();
|
|||
|
|
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '已加入购物车',
|
|||
|
|
icon: 'check-circle',
|
|||
|
|
duration: 1500,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
// 隐藏加载提示
|
|||
|
|
wx.hideLoading();
|
|||
|
|
|
|||
|
|
console.error('添加到购物车失败:', error);
|
|||
|
|
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: error.message || '添加到购物车失败',
|
|||
|
|
icon: '',
|
|||
|
|
duration: 2000,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
gotoBuy(type) {
|
|||
|
|
const { isAllSelectedSku, buyNum, details, selectSkuSellsPrice, minSalePrice } = this.data;
|
|||
|
|
|
|||
|
|
// 检查商品是否有规格,如果有规格但未全部选择,则提示选择规格
|
|||
|
|
const hasSpecs = details.specList && details.specList.length > 0;
|
|||
|
|
if (hasSpecs && !isAllSelectedSku) {
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '请选择规格',
|
|||
|
|
icon: '',
|
|||
|
|
duration: 1000,
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 修复SKU选择逻辑:优先使用用户选择的SKU,如果没有选择则使用第一个SKU
|
|||
|
|
let selectedSkuId;
|
|||
|
|
let selectedSkuPrice;
|
|||
|
|
|
|||
|
|
if (this.data.selectItem && this.data.selectItem.skuId) {
|
|||
|
|
// 用户已选择规格,使用选择的SKU和价格
|
|||
|
|
selectedSkuId = parseInt(this.data.selectItem.skuId);
|
|||
|
|
selectedSkuPrice = this.data.selectItem.price || selectSkuSellsPrice || minSalePrice;
|
|||
|
|
} else if (details.skuList && details.skuList.length > 0) {
|
|||
|
|
// 没有选择规格但有SKU列表,使用第一个SKU
|
|||
|
|
selectedSkuId = parseInt(details.skuList[0].skuId);
|
|||
|
|
selectedSkuPrice = details.skuList[0].price || minSalePrice;
|
|||
|
|
} else {
|
|||
|
|
// 没有SKU列表,使用SPU ID
|
|||
|
|
selectedSkuId = parseInt(details.spuId);
|
|||
|
|
selectedSkuPrice = minSalePrice;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建订单商品数据
|
|||
|
|
const orderItem = {
|
|||
|
|
spuId: details.spuId,
|
|||
|
|
skuId: selectedSkuId,
|
|||
|
|
title: details.title,
|
|||
|
|
primaryImage: details.primaryImage,
|
|||
|
|
price: selectedSkuPrice,
|
|||
|
|
quantity: buyNum,
|
|||
|
|
specInfo: this.data.selectedAttrStr,
|
|||
|
|
totalPrice: selectedSkuPrice * buyNum,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 关闭弹窗
|
|||
|
|
this.handlePopupHide();
|
|||
|
|
|
|||
|
|
// 获取用户选择的规格信息,而不是默认的第一个规格值
|
|||
|
|
let specInfo = [];
|
|||
|
|
if (this.data.selectItem && this.data.selectItem.specInfo) {
|
|||
|
|
// 如果用户已选择规格,使用选择的规格信息
|
|||
|
|
specInfo = this.data.selectItem.specInfo.map((spec) => ({
|
|||
|
|
specTitle: spec.specTitle || '',
|
|||
|
|
specValue: spec.specValue || '',
|
|||
|
|
}));
|
|||
|
|
} else if (details.specList && details.specList.length > 0) {
|
|||
|
|
// 如果没有选择规格但有规格列表,使用第一个规格值
|
|||
|
|
specInfo = details.specList.map((item) => ({
|
|||
|
|
specTitle: item.title,
|
|||
|
|
specValue: item.specValueList?.[0]?.specValue || '',
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加调试日志,查看传递的数据
|
|||
|
|
console.log('=== 商品详情页面传递的数据 ===');
|
|||
|
|
console.log('selectItem:', this.data.selectItem);
|
|||
|
|
console.log('selectedSkuId:', selectedSkuId);
|
|||
|
|
console.log('specInfo:', specInfo);
|
|||
|
|
console.log('selectedAttrStr:', this.data.selectedAttrStr);
|
|||
|
|
console.log('selectSkuSellsPrice:', this.data.selectSkuSellsPrice);
|
|||
|
|
console.log('minSalePrice:', this.data.minSalePrice);
|
|||
|
|
console.log('最终使用的价格:', selectedSkuPrice);
|
|||
|
|
if (this.data.selectItem) {
|
|||
|
|
console.log('selectItem.price:', this.data.selectItem.price);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 跳转到订单确认页面
|
|||
|
|
const query = {
|
|||
|
|
quantity: buyNum,
|
|||
|
|
storeId: '1',
|
|||
|
|
spuId: details.spuId,
|
|||
|
|
goodsName: details.title,
|
|||
|
|
skuId: selectedSkuId,
|
|||
|
|
available: details.available,
|
|||
|
|
price: selectedSkuPrice,
|
|||
|
|
specInfo: specInfo,
|
|||
|
|
primaryImage: details.primaryImage,
|
|||
|
|
thumb: details.primaryImage,
|
|||
|
|
title: details.title,
|
|||
|
|
};
|
|||
|
|
let urlQueryStr = obj2Params({
|
|||
|
|
goodsRequestList: JSON.stringify([query]),
|
|||
|
|
}, true);
|
|||
|
|
urlQueryStr = urlQueryStr ? `?${urlQueryStr}` : '';
|
|||
|
|
const path = `/pages/order/order-confirm/index${urlQueryStr}`;
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: path,
|
|||
|
|
fail: (error) => {
|
|||
|
|
console.error('跳转订单确认页面失败:', error);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '跳转失败,请重试',
|
|||
|
|
icon: 'error',
|
|||
|
|
duration: 2000,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
specsConfirm() {
|
|||
|
|
const { buyType } = this.data;
|
|||
|
|
if (buyType === 1) {
|
|||
|
|
this.gotoBuy(1);
|
|||
|
|
} else {
|
|||
|
|
this.addCart();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
changeNum(e) {
|
|||
|
|
const buyNum = e.detail.buyNum;
|
|||
|
|
const { selectSkuSellsPrice, minSalePrice } = this.data;
|
|||
|
|
const unitPrice = selectSkuSellsPrice || minSalePrice || 0;
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
buyNum: buyNum,
|
|||
|
|
totalPrice: unitPrice * buyNum,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
closePromotionPopup() {
|
|||
|
|
this.setData({
|
|||
|
|
isShowPromotionPop: false,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
promotionChange(e) {
|
|||
|
|
const { index } = e.detail;
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/pages/promotion/promotion-detail/index?promotion_id=${index}`,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
showPromotionPopup() {
|
|||
|
|
this.setData({
|
|||
|
|
isShowPromotionPop: true,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加初始化方法
|
|||
|
|
init(spuId) {
|
|||
|
|
this.getDetail(spuId);
|
|||
|
|
this.getCommentsList(spuId);
|
|||
|
|
this.getCommentsStatistics(spuId);
|
|||
|
|
this.getCartNum(); // 获取购物车数量
|
|||
|
|
this.checkFavoriteStatus(spuId); // 检查收藏状态
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取评论统计数据
|
|||
|
|
async getCommentsStatistics(spuId) {
|
|||
|
|
try {
|
|||
|
|
console.log('[DEBUG] 开始获取评论统计数据, spuId:', spuId);
|
|||
|
|
const res = await fetchCommentsCount(spuId);
|
|||
|
|
console.log('[DEBUG] 评论统计API返回结果:', res);
|
|||
|
|
|
|||
|
|
if (res) {
|
|||
|
|
const goodRate = res.total_count > 0 ? Math.round((res.good_count / res.total_count) * 100) : 0;
|
|||
|
|
|
|||
|
|
const commentsStatistics = {
|
|||
|
|
commentCount: res.total_count || 0,
|
|||
|
|
goodCount: res.good_count || 0,
|
|||
|
|
middleCount: res.medium_count || 0,
|
|||
|
|
badCount: res.bad_count || 0,
|
|||
|
|
goodRate: goodRate,
|
|||
|
|
hasImageCount: res.image_count || 0,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log('[DEBUG] 设置评论统计数据:', commentsStatistics);
|
|||
|
|
console.log('[DEBUG] 评论组件显示条件:', commentsStatistics.commentCount > 0);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
commentsStatistics: commentsStatistics
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
console.log('[DEBUG] 评论统计API返回空结果');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[DEBUG] 获取评论统计失败:', error);
|
|||
|
|
// 设置默认值
|
|||
|
|
this.setData({
|
|||
|
|
commentsStatistics: {
|
|||
|
|
commentCount: 0,
|
|||
|
|
goodCount: 0,
|
|||
|
|
middleCount: 0,
|
|||
|
|
badCount: 0,
|
|||
|
|
goodRate: 0,
|
|||
|
|
hasImageCount: 0,
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取评论列表数据
|
|||
|
|
async getCommentsList(spuId) {
|
|||
|
|
try {
|
|||
|
|
console.log('[DEBUG] 开始获取评论列表数据, spuId:', spuId);
|
|||
|
|
const res = await fetchComments({ productId: spuId, page: 1, pageSize: 3 });
|
|||
|
|
console.log('[DEBUG] 评论列表API返回结果:', res);
|
|||
|
|
|
|||
|
|
if (res && res.comments) {
|
|||
|
|
// 转换评论数据格式
|
|||
|
|
const commentsList = res.comments.map(comment => ({
|
|||
|
|
id: comment.id,
|
|||
|
|
userName: comment.is_anonymous ? '匿名用户' : (comment.user_name || '用户'),
|
|||
|
|
userHeadUrl: comment.user_avatar || 'https://tdesign.gtimg.com/miniprogram/template/retail/avatar/avatar1.png',
|
|||
|
|
commentScore: comment.rating || 5,
|
|||
|
|
commentContent: comment.content || '',
|
|||
|
|
commentTime: comment.created_at ? this.formatTime(comment.created_at) : '',
|
|||
|
|
specInfo: comment.product_spec || '',
|
|||
|
|
commentResources: comment.images || [],
|
|||
|
|
isAnonymity: comment.is_anonymous || false,
|
|||
|
|
sellerReply: comment.reply_content || '',
|
|||
|
|
goodsDetailInfo: ''
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
console.log('[DEBUG] 转换后的评论列表:', commentsList);
|
|||
|
|
console.log('[DEBUG] 评论列表长度:', commentsList.length);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
commentsList: commentsList
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
console.log('[DEBUG] 评论列表API返回空结果或无comments字段');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[DEBUG] 获取评论列表失败:', error);
|
|||
|
|
// 设置空数组
|
|||
|
|
this.setData({
|
|||
|
|
commentsList: []
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 格式化时间
|
|||
|
|
formatTime(timestamp) {
|
|||
|
|
const date = new Date(parseInt(timestamp));
|
|||
|
|
const year = date.getFullYear();
|
|||
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|||
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|||
|
|
return `${year}-${month}-${day}`;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 跳转到评论列表页面
|
|||
|
|
navToCommentsListPage() {
|
|||
|
|
const { spuId } = this.data;
|
|||
|
|
console.log('[商品详情页] 准备跳转到评论页面,spuId:', spuId, '类型:', typeof spuId);
|
|||
|
|
|
|||
|
|
if (spuId) {
|
|||
|
|
const url = `/pages/goods/comments/index?productId=${spuId}`;
|
|||
|
|
console.log('[商品详情页] 跳转URL:', url);
|
|||
|
|
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: url,
|
|||
|
|
success: () => {
|
|||
|
|
console.log('[商品详情页] 成功跳转到评论页面');
|
|||
|
|
},
|
|||
|
|
fail: (error) => {
|
|||
|
|
console.error('[商品详情页] 跳转评论页面失败:', error);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '暂时无法查看更多评论',
|
|||
|
|
theme: 'warning',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
console.error('[商品详情页] spuId为空,无法跳转到评论页面');
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '商品信息异常,无法查看评论',
|
|||
|
|
theme: 'error',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 预览评论图片
|
|||
|
|
previewCommentImages(e) {
|
|||
|
|
const { images, current } = e.currentTarget.dataset;
|
|||
|
|
if (images && images.length > 0) {
|
|||
|
|
const urls = images.map(img => img.src);
|
|||
|
|
wx.previewImage({
|
|||
|
|
urls: urls,
|
|||
|
|
current: urls[current || 0]
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 点赞评论
|
|||
|
|
async likeComment(e) {
|
|||
|
|
const { commentId, index } = e.currentTarget.dataset;
|
|||
|
|
try {
|
|||
|
|
// 这里可以调用点赞API
|
|||
|
|
// await likeCommentAPI(commentId);
|
|||
|
|
|
|||
|
|
// 模拟点赞成功,更新UI
|
|||
|
|
const commentsList = [...this.data.commentsList];
|
|||
|
|
if (commentsList[index]) {
|
|||
|
|
commentsList[index].isLiked = !commentsList[index].isLiked;
|
|||
|
|
commentsList[index].likeCount = (commentsList[index].likeCount || 0) + (commentsList[index].isLiked ? 1 : -1);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
commentsList
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: commentsList[index].isLiked ? '点赞成功' : '取消点赞',
|
|||
|
|
theme: 'success',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('点赞失败:', error);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '操作失败,请稍后重试',
|
|||
|
|
theme: 'error',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 详情介绍图片加载成功事件日志
|
|||
|
|
onDescImageLoad(e) {
|
|||
|
|
const url = (e && e.currentTarget && e.currentTarget.dataset && e.currentTarget.dataset.url) || '';
|
|||
|
|
const index = (e && e.currentTarget && e.currentTarget.dataset && e.currentTarget.dataset.index) || '';
|
|||
|
|
console.log('[DESC_IMAGE_LOAD]', { url, index });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 详情介绍图片加载失败事件日志
|
|||
|
|
onDescImageError(e) {
|
|||
|
|
const url = (e && e.currentTarget && e.currentTarget.dataset && e.currentTarget.dataset.url) || '';
|
|||
|
|
const index = (e && e.currentTarget && e.currentTarget.dataset && e.currentTarget.dataset.index) || '';
|
|||
|
|
const errMsg = (e && e.detail && e.detail.errMsg) || '';
|
|||
|
|
console.log('[DESC_IMAGE_ERROR]', { url, index, errMsg });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 预览详情图片
|
|||
|
|
previewDescImage(e) {
|
|||
|
|
const { url, index } = e.currentTarget.dataset;
|
|||
|
|
const { desc } = this.data.details;
|
|||
|
|
|
|||
|
|
if (desc && desc.length > 0) {
|
|||
|
|
wx.previewImage({
|
|||
|
|
current: url,
|
|||
|
|
urls: desc,
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('预览图片失败:', err);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '预览图片失败',
|
|||
|
|
theme: 'error',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 检查商品收藏状态
|
|||
|
|
async checkFavoriteStatus(spuId) {
|
|||
|
|
try {
|
|||
|
|
const isFavorite = await checkIsFavorite(spuId);
|
|||
|
|
this.setData({
|
|||
|
|
isFavorite: isFavorite
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('检查收藏状态失败:', error);
|
|||
|
|
// 如果检查失败,默认为未收藏状态
|
|||
|
|
this.setData({
|
|||
|
|
isFavorite: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 切换收藏状态
|
|||
|
|
async onToggleFavorite() {
|
|||
|
|
const { spuId, isFavorite } = this.data;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const newFavoriteStatus = await toggleFavorite(spuId, isFavorite);
|
|||
|
|
this.setData({
|
|||
|
|
isFavorite: newFavoriteStatus
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 显示提示信息
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: newFavoriteStatus ? '已添加到收藏' : '已取消收藏',
|
|||
|
|
theme: 'success',
|
|||
|
|
direction: 'column',
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('切换收藏状态失败:', error);
|
|||
|
|
Toast({
|
|||
|
|
context: this,
|
|||
|
|
selector: '#t-toast',
|
|||
|
|
message: '操作失败,请重试',
|
|||
|
|
theme: 'error',
|
|||
|
|
direction: 'column',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取购物车数量
|
|||
|
|
getCartNum() {
|
|||
|
|
const cartData = wx.getStorageSync('cartData') || [];
|
|||
|
|
const totalCartNum = cartData.reduce((sum, item) => sum + item.quantity, 0);
|
|||
|
|
this.setData({
|
|||
|
|
cartNum: totalCartNum
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加分享功能
|
|||
|
|
onShareAppMessage() {
|
|||
|
|
const { details } = this.data;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
title: details.title || '精选好物推荐',
|
|||
|
|
path: `/pages/goods/details/index?spuId=${details.spuId}`,
|
|||
|
|
imageUrl: details.primaryImage || ''
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加分享到朋友圈功能
|
|||
|
|
onShareTimeline() {
|
|||
|
|
const { details } = this.data;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
title: details.title || '精选好物推荐',
|
|||
|
|
query: `spuId=${details.spuId}`,
|
|||
|
|
imageUrl: details.primaryImage || ''
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加页面滚动优化
|
|||
|
|
onPageScroll(e) {
|
|||
|
|
const { scrollTop } = e;
|
|||
|
|
|
|||
|
|
// 根据滚动位置调整导航栏透明度
|
|||
|
|
if (scrollTop > 100) {
|
|||
|
|
wx.setNavigationBarColor({
|
|||
|
|
frontColor: '#000000',
|
|||
|
|
backgroundColor: '#ffffff',
|
|||
|
|
animation: {
|
|||
|
|
duration: 300,
|
|||
|
|
timingFunc: 'easeOut'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
wx.setNavigationBarColor({
|
|||
|
|
frontColor: '#000000',
|
|||
|
|
backgroundColor: '#ffffff',
|
|||
|
|
animation: {
|
|||
|
|
duration: 300,
|
|||
|
|
timingFunc: 'easeOut'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加下拉刷新功能
|
|||
|
|
onPullDownRefresh() {
|
|||
|
|
const { spuId } = this.data;
|
|||
|
|
|
|||
|
|
if (spuId) {
|
|||
|
|
this.getDetail(spuId).finally(() => {
|
|||
|
|
wx.stopPullDownRefresh();
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
wx.stopPullDownRefresh();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|