|
|
|
|
// pages/goods-detail/goods-detail.js
|
|
|
|
|
const API = require('../../utils/api.js')
|
|
|
|
|
|
|
|
|
|
// 格式化毛重显示的辅助函数
|
|
|
|
|
function formatGrossWeight(grossWeight, weight) {
|
|
|
|
|
console.log('===== formatGrossWeight 函数调用 =====');
|
|
|
|
|
console.log('输入参数:');
|
|
|
|
|
console.log('- grossWeight:', grossWeight, '(类型:', typeof grossWeight, ')');
|
|
|
|
|
console.log('- weight:', weight, '(类型:', typeof weight, ')');
|
|
|
|
|
|
|
|
|
|
// 1. 优先使用grossWeight,只要它不是null、不是undefined、不是空字符串
|
|
|
|
|
if (grossWeight !== null && grossWeight !== undefined && grossWeight !== '') {
|
|
|
|
|
console.log('使用grossWeight参数');
|
|
|
|
|
return grossWeight;
|
|
|
|
|
}
|
|
|
|
|
// 如果grossWeight无效,尝试使用weight字段
|
|
|
|
|
if (weight !== null && weight !== undefined && weight !== '') {
|
|
|
|
|
console.log('使用weight参数');
|
|
|
|
|
return weight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 新增逻辑:如果grossWeight和weight都无效,返回空字符串以支持文字输入
|
|
|
|
|
console.log('两个参数都无效,返回空字符串');
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Page({
|
|
|
|
|
data: {
|
|
|
|
|
goodsDetail: {}, // 当前商品详情
|
|
|
|
|
showImagePreview: false, // 控制图片预览弹窗显示
|
|
|
|
|
previewImageUrls: [], // 预览的图片URL列表
|
|
|
|
|
previewImageIndex: 0, // 当前预览图片的索引
|
|
|
|
|
fromSeller: false, // 是否来自seller页面
|
|
|
|
|
// 图片缩放相关状态
|
|
|
|
|
scale: 1, // 当前缩放比例
|
|
|
|
|
lastScale: 1, // 上一次缩放比例
|
|
|
|
|
startDistance: 0, // 双指起始距离
|
|
|
|
|
doubleTapTimer: null, // 双击计时器
|
|
|
|
|
lastTapTime: 0, // 上一次单击时间
|
|
|
|
|
isScaling: false, // 是否正在缩放中
|
|
|
|
|
offsetX: 0, // X轴偏移量
|
|
|
|
|
offsetY: 0, // Y轴偏移量
|
|
|
|
|
initialTouch: null, // 初始触摸点
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onLoad(options) {
|
|
|
|
|
console.log('商品详情页面加载,参数:', options);
|
|
|
|
|
|
|
|
|
|
// 检查是否来自seller页面
|
|
|
|
|
const fromSeller = options.fromSeller === '1' ? true : false;
|
|
|
|
|
|
|
|
|
|
// 支持两种参数传递方式:直接传递商品数据或仅传递商品ID
|
|
|
|
|
if (options.goodsData) {
|
|
|
|
|
try {
|
|
|
|
|
// 解析JSON字符串为商品对象
|
|
|
|
|
const goodsData = JSON.parse(decodeURIComponent(options.goodsData));
|
|
|
|
|
console.log('解析后的商品数据:', goodsData);
|
|
|
|
|
|
|
|
|
|
// 确保商品ID的一致性
|
|
|
|
|
const productIdStr = String(goodsData.productId || goodsData.id);
|
|
|
|
|
|
|
|
|
|
// 无论是否有传递数据,都调用API获取最新的商品详情(特别是联系人信息)
|
|
|
|
|
this.loadGoodsDetail(productIdStr);
|
|
|
|
|
|
|
|
|
|
// 设置来源标识
|
|
|
|
|
this.setData({
|
|
|
|
|
fromSeller: fromSeller
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析商品数据失败:', error);
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '数据解析错误',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 2000
|
|
|
|
|
});
|
|
|
|
|
// 2秒后返回上一页
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
wx.navigateBack();
|
|
|
|
|
}, 2000);
|
|
|
|
|
}
|
|
|
|
|
} else if (options.id) {
|
|
|
|
|
// 如果只传递了商品ID,则从服务器加载商品详情
|
|
|
|
|
this.loadGoodsDetail(options.id);
|
|
|
|
|
|
|
|
|
|
// 设置来源标识
|
|
|
|
|
this.setData({
|
|
|
|
|
fromSeller: fromSeller
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '参数错误',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 2000
|
|
|
|
|
});
|
|
|
|
|
// 2秒后返回上一页
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
wx.navigateBack();
|
|
|
|
|
}, 2000);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 加载商品详情
|
|
|
|
|
loadGoodsDetail(goodsId) {
|
|
|
|
|
wx.showLoading({
|
|
|
|
|
title: '加载中',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API.getProductDetail({ productId: goodsId })
|
|
|
|
|
.then(res => {
|
|
|
|
|
console.log('获取商品详情成功:', res);
|
|
|
|
|
if (res && res.code === 200 && res.data) {
|
|
|
|
|
// 从本地存储获取已预约商品ID列表
|
|
|
|
|
const reservedGoodsIds = wx.getStorageSync('reservedGoodsIds') || [];
|
|
|
|
|
const product = res.data;
|
|
|
|
|
|
|
|
|
|
// 确保商品ID的一致性
|
|
|
|
|
const productIdStr = String(product.productId || product.id);
|
|
|
|
|
|
|
|
|
|
// 增强的预约人数计算逻辑
|
|
|
|
|
const selectedValue = product.selected;
|
|
|
|
|
const reservedCountValue = product.reservedCount;
|
|
|
|
|
const reservationCountValue = product.reservationCount;
|
|
|
|
|
|
|
|
|
|
const finalReservationCount = selectedValue !== undefined && selectedValue !== null ? selectedValue :
|
|
|
|
|
(reservedCountValue !== undefined && reservedCountValue !== null ? reservedCountValue :
|
|
|
|
|
(reservationCountValue || 0));
|
|
|
|
|
|
|
|
|
|
// 处理grossWeight为null或无效的情况,返回空字符串以支持文字输入
|
|
|
|
|
const grossWeightValue = product.grossWeight !== null && product.grossWeight !== undefined ? product.grossWeight : '';
|
|
|
|
|
|
|
|
|
|
// 转换商品数据格式
|
|
|
|
|
const formattedGoods = {
|
|
|
|
|
id: productIdStr,
|
|
|
|
|
productId: productIdStr,
|
|
|
|
|
name: product.productName,
|
|
|
|
|
price: product.price,
|
|
|
|
|
minOrder: product.minOrder || product.quantity,
|
|
|
|
|
yolk: product.yolk,
|
|
|
|
|
spec: product.spec || product.specification,
|
|
|
|
|
region: product.region,
|
|
|
|
|
contact_phone: product.contact_phone || product.contactPhone,
|
|
|
|
|
product_contact: product.product_contact || product.contactName,
|
|
|
|
|
imageUrls: product.imageUrls || product.images || [],
|
|
|
|
|
displayGrossWeight: formatGrossWeight(grossWeightValue, product.weight),
|
|
|
|
|
isReserved: reservedGoodsIds.some(itemId => String(itemId) === productIdStr),
|
|
|
|
|
reservedCount: finalReservationCount,
|
|
|
|
|
created_at: product.created_at || product.createdAt,
|
|
|
|
|
updated_at: product.updated_at || product.updatedAt,
|
|
|
|
|
status: product.status
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
goodsDetail: formattedGoods
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '获取商品详情失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 2000
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
console.error('获取商品详情失败:', err);
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '获取商品详情失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 2000
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.finally(() => {
|
|
|
|
|
wx.hideLoading();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 预览图片
|
|
|
|
|
previewImage(e) {
|
|
|
|
|
const { urls, index } = e.currentTarget.dataset;
|
|
|
|
|
if (!urls || urls.length === 0) {
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '没有图片可预览',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
showImagePreview: true,
|
|
|
|
|
previewImageUrls: urls,
|
|
|
|
|
previewImageIndex: parseInt(index || 0)
|
|
|
|
|
});
|
|
|
|
|
this.resetZoom();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 关闭图片预览
|
|
|
|
|
closeImagePreview() {
|
|
|
|
|
this.setData({
|
|
|
|
|
showImagePreview: false
|
|
|
|
|
});
|
|
|
|
|
this.resetZoom();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 重置缩放状态
|
|
|
|
|
resetZoom() {
|
|
|
|
|
this.setData({
|
|
|
|
|
scale: 1,
|
|
|
|
|
lastScale: 1,
|
|
|
|
|
offsetX: 0,
|
|
|
|
|
offsetY: 0,
|
|
|
|
|
initialTouch: null
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 图片预览切换
|
|
|
|
|
onPreviewImageChange(e) {
|
|
|
|
|
this.setData({
|
|
|
|
|
previewImageIndex: e.detail.current
|
|
|
|
|
});
|
|
|
|
|
// 切换图片时重置缩放状态
|
|
|
|
|
this.resetZoom();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 处理图片点击事件(单击/双击判断)
|
|
|
|
|
handleImageTap(e) {
|
|
|
|
|
const currentTime = Date.now();
|
|
|
|
|
const lastTapTime = this.data.lastTapTime || 0;
|
|
|
|
|
|
|
|
|
|
// 判断是否为双击(300ms内连续点击)
|
|
|
|
|
if (currentTime - lastTapTime < 300) {
|
|
|
|
|
// 双击事件
|
|
|
|
|
if (this.data.doubleTapTimer) {
|
|
|
|
|
clearTimeout(this.data.doubleTapTimer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 切换放大/缩小状态
|
|
|
|
|
const newScale = this.data.scale === 1 ? 2 : 1;
|
|
|
|
|
this.setData({
|
|
|
|
|
scale: newScale,
|
|
|
|
|
lastScale: newScale,
|
|
|
|
|
offsetX: 0,
|
|
|
|
|
offsetY: 0,
|
|
|
|
|
lastTapTime: 0 // 重置双击状态
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 单击事件,设置延迟来检测是否会成为双击
|
|
|
|
|
if (this.data.doubleTapTimer) {
|
|
|
|
|
clearTimeout(this.data.doubleTapTimer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
lastTapTime: currentTime,
|
|
|
|
|
doubleTapTimer: setTimeout(() => {
|
|
|
|
|
// 确认是单击,关闭图片预览
|
|
|
|
|
this.closeImagePreview();
|
|
|
|
|
}, 300)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 计算两点之间的距离
|
|
|
|
|
calculateDistance(touch1, touch2) {
|
|
|
|
|
const dx = touch2.clientX - touch1.clientX;
|
|
|
|
|
const dy = touch2.clientY - touch1.clientY;
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 处理触摸开始事件
|
|
|
|
|
handleTouchStart(e) {
|
|
|
|
|
console.log('触摸开始事件:', e);
|
|
|
|
|
const touches = e.touches;
|
|
|
|
|
|
|
|
|
|
if (touches.length === 1) {
|
|
|
|
|
// 单指:准备拖动
|
|
|
|
|
this.setData({
|
|
|
|
|
initialTouch: {
|
|
|
|
|
x: touches[0].clientX,
|
|
|
|
|
y: touches[0].clientY
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else if (touches.length === 2) {
|
|
|
|
|
// 双指:记录起始距离,准备缩放
|
|
|
|
|
const distance = this.calculateDistance(touches[0], touches[1]);
|
|
|
|
|
this.setData({
|
|
|
|
|
startDistance: distance,
|
|
|
|
|
isScaling: true,
|
|
|
|
|
lastScale: this.data.scale
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 处理触摸移动事件
|
|
|
|
|
handleTouchMove(e) {
|
|
|
|
|
const touches = e.touches;
|
|
|
|
|
|
|
|
|
|
if (touches.length === 1 && this.data.initialTouch && this.data.scale !== 1) {
|
|
|
|
|
// 单指拖动(只有在缩放状态下才允许拖动)
|
|
|
|
|
const deltaX = touches[0].clientX - this.data.initialTouch.x;
|
|
|
|
|
const deltaY = touches[0].clientY - this.data.initialTouch.y;
|
|
|
|
|
|
|
|
|
|
// 计算新的偏移量
|
|
|
|
|
let newOffsetX = this.data.offsetX + deltaX;
|
|
|
|
|
let newOffsetY = this.data.offsetY + deltaY;
|
|
|
|
|
|
|
|
|
|
// 边界限制
|
|
|
|
|
const windowWidth = wx.getSystemInfoSync().windowWidth;
|
|
|
|
|
const windowHeight = wx.getSystemInfoSync().windowHeight;
|
|
|
|
|
const maxOffsetX = (windowWidth * (this.data.scale - 1)) / 2;
|
|
|
|
|
const maxOffsetY = (windowHeight * (this.data.scale - 1)) / 2;
|
|
|
|
|
|
|
|
|
|
newOffsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, newOffsetX));
|
|
|
|
|
newOffsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, newOffsetY));
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
offsetX: newOffsetX,
|
|
|
|
|
offsetY: newOffsetY,
|
|
|
|
|
initialTouch: {
|
|
|
|
|
x: touches[0].clientX,
|
|
|
|
|
y: touches[0].clientY
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else if (touches.length === 2) {
|
|
|
|
|
// 双指缩放
|
|
|
|
|
const currentDistance = this.calculateDistance(touches[0], touches[1]);
|
|
|
|
|
const scale = (currentDistance / this.data.startDistance) * this.data.lastScale;
|
|
|
|
|
|
|
|
|
|
// 限制缩放范围在0.5倍到3倍之间
|
|
|
|
|
const newScale = Math.max(0.5, Math.min(3, scale));
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
scale: newScale,
|
|
|
|
|
isScaling: true
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 处理触摸结束事件
|
|
|
|
|
handleTouchEnd(e) {
|
|
|
|
|
this.setData({
|
|
|
|
|
isScaling: false,
|
|
|
|
|
lastScale: this.data.scale,
|
|
|
|
|
initialTouch: null
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 拨打电话
|
|
|
|
|
makePhoneCall(e) {
|
|
|
|
|
console.log('拨打电话事件:', e);
|
|
|
|
|
const phoneNumber = e.currentTarget.dataset.phone;
|
|
|
|
|
if (phoneNumber) {
|
|
|
|
|
wx.showModal({
|
|
|
|
|
title: '联系人电话',
|
|
|
|
|
content: phoneNumber,
|
|
|
|
|
showCancel: true,
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
confirmText: '拨打',
|
|
|
|
|
success: (res) => {
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
wx.makePhoneCall({
|
|
|
|
|
phoneNumber: phoneNumber,
|
|
|
|
|
success: () => {
|
|
|
|
|
console.log('拨打电话成功');
|
|
|
|
|
},
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
console.error('拨打电话失败', err);
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '拨打电话失败',
|
|
|
|
|
icon: 'none'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 我想要(预约)
|
|
|
|
|
onClickWantInDetail(e) {
|
|
|
|
|
console.log('我想要事件:', e);
|
|
|
|
|
const { id } = e.currentTarget.dataset;
|
|
|
|
|
if (!id) return;
|
|
|
|
|
|
|
|
|
|
// 从本地存储获取openid
|
|
|
|
|
const openid = wx.getStorageSync('openid');
|
|
|
|
|
console.log('openid:', openid);
|
|
|
|
|
|
|
|
|
|
// 检查是否已登录
|
|
|
|
|
if (!openid) {
|
|
|
|
|
// 如果未登录,显示授权登录弹窗
|
|
|
|
|
this.setData({ showAuthModal: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取已预约商品ID列表
|
|
|
|
|
let reservedGoodsIds = wx.getStorageSync('reservedGoodsIds') || [];
|
|
|
|
|
|
|
|
|
|
// 检查是否已经预约过
|
|
|
|
|
if (reservedGoodsIds.some(itemId => String(itemId) === String(id))) {
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '您已经预约过该商品',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 1500
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加到已预约列表
|
|
|
|
|
reservedGoodsIds.push(id);
|
|
|
|
|
wx.setStorageSync('reservedGoodsIds', reservedGoodsIds);
|
|
|
|
|
|
|
|
|
|
// 更新页面状态
|
|
|
|
|
this.setData({
|
|
|
|
|
'goodsDetail.isReserved': true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 调用API记录预约
|
|
|
|
|
API.reserveProduct({ id: id })
|
|
|
|
|
.then(res => {
|
|
|
|
|
console.log('预约成功:', res);
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '预约成功',
|
|
|
|
|
icon: 'success',
|
|
|
|
|
duration: 1500
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
console.error('预约失败:', err);
|
|
|
|
|
// 如果API调用失败,从本地列表中移除
|
|
|
|
|
reservedGoodsIds = reservedGoodsIds.filter(itemId => String(itemId) !== String(id));
|
|
|
|
|
wx.setStorageSync('reservedGoodsIds', reservedGoodsIds);
|
|
|
|
|
// 更新页面状态
|
|
|
|
|
this.setData({
|
|
|
|
|
'goodsDetail.isReserved': false
|
|
|
|
|
});
|
|
|
|
|
wx.showToast({
|
|
|
|
|
title: '预约失败,请重试',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 1500
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 返回上一页
|
|
|
|
|
goBack() {
|
|
|
|
|
wx.navigateBack();
|
|
|
|
|
}
|
|
|
|
|
});
|