7 changed files with 1140 additions and 137 deletions
@ -0,0 +1,440 @@ |
|||
// 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, // 当前预览图片的索引
|
|||
// 图片缩放相关状态
|
|||
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); |
|||
|
|||
// 支持两种参数传递方式:直接传递商品数据或仅传递商品ID
|
|||
if (options.goodsData) { |
|||
try { |
|||
// 解析JSON字符串为商品对象
|
|||
const goodsData = JSON.parse(decodeURIComponent(options.goodsData)); |
|||
console.log('解析后的商品数据:', goodsData); |
|||
|
|||
// 设置商品详情数据
|
|||
this.setData({ |
|||
goodsDetail: goodsData |
|||
}); |
|||
} 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); |
|||
} 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.quantity, |
|||
yolk: product.yolk, |
|||
spec: product.specification, |
|||
region: product.region, |
|||
contact_phone: product.contactPhone, |
|||
product_contact: product.contactName, |
|||
imageUrls: product.images || [], |
|||
displayGrossWeight: formatGrossWeight(grossWeightValue, product.weight), |
|||
isReserved: reservedGoodsIds.some(itemId => String(itemId) === productIdStr), |
|||
reservedCount: finalReservationCount, |
|||
created_at: product.createdAt, |
|||
updated_at: product.updatedAt |
|||
}; |
|||
|
|||
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) { |
|||
console.log('预览图片事件:', 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: index || 0, |
|||
// 重置图片缩放状态
|
|||
scale: 1, |
|||
offsetX: 0, |
|||
offsetY: 0, |
|||
isScaling: false |
|||
}); |
|||
}, |
|||
|
|||
// 关闭图片预览
|
|||
closeImagePreview() { |
|||
this.setData({ |
|||
showImagePreview: false, |
|||
// 重置图片缩放状态
|
|||
scale: 1, |
|||
offsetX: 0, |
|||
offsetY: 0, |
|||
isScaling: false |
|||
}); |
|||
}, |
|||
|
|||
// 图片预览切换
|
|||
onPreviewImageChange(e) { |
|||
console.log('图片预览切换:', e); |
|||
this.setData({ |
|||
previewImageIndex: e.detail.current, |
|||
// 重置当前图片的缩放状态
|
|||
scale: 1, |
|||
offsetX: 0, |
|||
offsetY: 0, |
|||
isScaling: false |
|||
}); |
|||
}, |
|||
|
|||
// 处理图片点击事件(双击放大/缩小)
|
|||
handleImageTap(e) { |
|||
console.log('图片点击事件:', e); |
|||
const now = Date.now(); |
|||
const DOUBLE_TAP_DELAY = 300; |
|||
|
|||
// 检查是否为双击
|
|||
if (now - this.lastTapTime < DOUBLE_TAP_DELAY) { |
|||
// 双击事件
|
|||
if (this.doubleTapTimer) { |
|||
clearTimeout(this.doubleTapTimer); |
|||
} |
|||
|
|||
// 切换缩放状态
|
|||
const newScale = this.data.scale === 1 ? 2 : 1; |
|||
this.setData({ |
|||
scale: newScale, |
|||
isScaling: false |
|||
}); |
|||
} else { |
|||
// 单击事件,设置双击计时器
|
|||
if (this.doubleTapTimer) { |
|||
clearTimeout(this.doubleTapTimer); |
|||
} |
|||
|
|||
this.doubleTapTimer = setTimeout(() => { |
|||
// 单击后300毫秒内没有第二次点击,视为单击
|
|||
// 可以在这里添加单击事件的处理逻辑
|
|||
}, DOUBLE_TAP_DELAY); |
|||
} |
|||
|
|||
this.lastTapTime = now; |
|||
}, |
|||
|
|||
// 计算两点之间的距离
|
|||
getDistance(point1, point2) { |
|||
const dx = point2.clientX - point1.clientX; |
|||
const dy = point2.clientY - point1.clientY; |
|||
return Math.sqrt(dx * dx + dy * dy); |
|||
}, |
|||
|
|||
// 处理触摸开始事件
|
|||
handleTouchStart(e) { |
|||
console.log('触摸开始事件:', e); |
|||
if (e.touches.length === 2) { |
|||
// 双指触摸,计算初始距离
|
|||
this.setData({ |
|||
startDistance: this.getDistance(e.touches[0], e.touches[1]), |
|||
lastScale: this.data.scale, |
|||
isScaling: true |
|||
}); |
|||
} else if (e.touches.length === 1) { |
|||
// 单指触摸,记录初始位置
|
|||
this.setData({ |
|||
initialTouch: { |
|||
x: e.touches[0].clientX, |
|||
y: e.touches[0].clientY |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 处理触摸移动事件
|
|||
handleTouchMove(e) { |
|||
console.log('触摸移动事件:', 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.getDistance(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) { |
|||
console.log('触摸结束事件:', e); |
|||
if (e.touches.length === 0) { |
|||
// 触摸结束,更新最后缩放比例
|
|||
this.setData({ |
|||
lastScale: this.data.scale, |
|||
isScaling: false, |
|||
initialTouch: null |
|||
}); |
|||
} else if (e.touches.length === 1) { |
|||
// 单指触摸结束,保留初始触摸点
|
|||
this.setData({ |
|||
initialTouch: { |
|||
x: e.touches[0].clientX, |
|||
y: e.touches[0].clientY |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 拨打电话
|
|||
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(); |
|||
} |
|||
}); |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText": "商品详情", |
|||
"enablePullDownRefresh": false |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
<!-- pages/goods-detail/goods-detail.wxml --> |
|||
<view class="goods-detail-page"> |
|||
<!-- 页面头部 --> |
|||
<view class="page-header"> |
|||
<view class="header-left"></view> |
|||
<view class="header-center"> |
|||
<text class="header-title">商品详情</text> |
|||
</view> |
|||
<view class="header-right" bindtap="goBack"> |
|||
<text class="close-icon">×</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 商品详情内容 --> |
|||
<view class="goods-detail-content"> |
|||
<!-- 商品图片轮播 --> |
|||
<view class="goods-image-slider"> |
|||
<swiper indicator-dots="{{true}}" autoplay="{{true}}" interval="3000" duration="500"> |
|||
<block wx:for="{{goodsDetail.imageUrls}}" wx:key="index"> |
|||
<swiper-item> |
|||
<image |
|||
src="{{item}}" |
|||
mode="scaleToFill" |
|||
class="slider-image" |
|||
bindtap="previewImage" |
|||
data-urls="{{goodsDetail.imageUrls}}" |
|||
data-index="{{index}}" |
|||
/> |
|||
</swiper-item> |
|||
</block> |
|||
</swiper> |
|||
</view> |
|||
|
|||
<!-- 商品基本信息 --> |
|||
<view class="goods-info"> |
|||
<text class="goods-name">{{goodsDetail.name}}</text> |
|||
<view class="goods-price"> |
|||
<text class="price-symbol">价格:</text> |
|||
<text class="price-value">{{goodsDetail.price}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 商品详细信息网格 --> |
|||
<view class="info-grid"> |
|||
<view class="info-row"> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">地区</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.region || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">规格</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.spec || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="info-row"> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">蛋黄</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.yolk || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">斤重</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.displayGrossWeight || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="info-row"> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">件数</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.minOrder || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="info-item"> |
|||
<view class="info-label-container"> |
|||
<text class="info-label">关注人数</text> |
|||
</view> |
|||
<view class="info-value-container"> |
|||
<text class="info-value">{{goodsDetail.reservedCount || '0'}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 联系信息 --> |
|||
<view class="contact-info"> |
|||
<text class="contact-label">联系信息</text> |
|||
<view class="contact-content"> |
|||
<view class="contact-item"> |
|||
<text class="contact-icon user-icon">👤</text> |
|||
<text class="contact-label-text">联系人:</text> |
|||
<text class="contact-text">{{goodsDetail.product_contact || '暂无'}}</text> |
|||
</view> |
|||
<view class="contact-item phone-item"> |
|||
<text class="contact-icon phone-icon">📞</text> |
|||
<view class="phone-info"> |
|||
<text class="contact-label-text">联系电话:</text> |
|||
<text class="contact-text">{{goodsDetail.contact_phone || '暂无'}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 操作按钮区域 --> |
|||
<view class="action-buttons"> |
|||
<button |
|||
class="call-button bottom-button" |
|||
bindtap="makePhoneCall" |
|||
data-phone="{{goodsDetail.contact_phone}}" |
|||
> |
|||
拨打电话 |
|||
</button> |
|||
<button |
|||
class="want-button bottom-button" |
|||
bindtap="onClickWantInDetail" |
|||
data-id="{{goodsDetail.id}}" |
|||
disabled="{{goodsDetail.isReserved}}" |
|||
> |
|||
{{goodsDetail.isReserved ? '已预约' : '我想要'}} |
|||
</button> |
|||
</view> |
|||
|
|||
<!-- 图片预览弹窗 --> |
|||
<view class="image-preview-container" wx:if="{{showImagePreview}}"> |
|||
<view class="preview-header"> |
|||
<view class="preview-close" bindtap="closeImagePreview"> |
|||
<text class="close-icon">×</text> |
|||
</view> |
|||
<view class="preview-indicator"> |
|||
<text>{{previewImageIndex + 1}} / {{previewImageUrls.length}}</text> |
|||
</view> |
|||
</view> |
|||
<swiper |
|||
class="preview-swiper" |
|||
current="{{previewImageIndex}}" |
|||
bindchange="onPreviewImageChange" |
|||
> |
|||
<block wx:for="{{previewImageUrls}}" wx:key="index"> |
|||
<swiper-item> |
|||
<view |
|||
class="preview-image-wrapper" |
|||
bindtap="handleImageTap" |
|||
bindtouchstart="handleTouchStart" |
|||
bindtouchmove="handleTouchMove" |
|||
bindtouchend="handleTouchEnd" |
|||
> |
|||
<image |
|||
src="{{item}}" |
|||
mode="aspectFit" |
|||
class="preview-image" |
|||
style="transform: scale({{scale}}) translate({{offsetX}}px, {{offsetY}}px); transform-origin: center center; transition: transform 0.1s ease-out;" |
|||
/> |
|||
</view> |
|||
</swiper-item> |
|||
</block> |
|||
</swiper> |
|||
</view> |
|||
</view> |
|||
@ -0,0 +1,514 @@ |
|||
/* pages/goods-detail/goods-detail.wxss */ |
|||
|
|||
/* 页面容器 */ |
|||
.goods-detail-page { |
|||
min-height: 100vh; |
|||
background-color: #f5f7fa; |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; |
|||
} |
|||
|
|||
/* 页面头部 */ |
|||
.page-header { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
height: 44px; |
|||
background: #ffffff; |
|||
padding: 0 16px; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 100; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
|||
} |
|||
|
|||
.header-left { |
|||
width: 40px; |
|||
} |
|||
|
|||
.header-center { |
|||
flex: 1; |
|||
text-align: center; |
|||
} |
|||
|
|||
.header-title { |
|||
font-size: 17px; |
|||
font-weight: 600; |
|||
color: #000000; |
|||
letter-spacing: 0.5px; |
|||
} |
|||
|
|||
.header-right { |
|||
width: 40px; |
|||
text-align: right; |
|||
} |
|||
|
|||
.close-icon { |
|||
font-size: 28px; |
|||
color: #000000; |
|||
font-weight: 300; |
|||
line-height: 1; |
|||
} |
|||
|
|||
/* 商品详情内容区域 */ |
|||
.goods-detail-content { |
|||
padding-top: 44px; |
|||
padding-bottom: 100px; |
|||
} |
|||
|
|||
/* 商品图片轮播 */ |
|||
.goods-image-slider { |
|||
width: 100%; |
|||
height: 280px; |
|||
background: linear-gradient(135deg, #f0f4ff 0%, #d9e4ff 100%); |
|||
overflow: hidden; |
|||
position: relative; |
|||
} |
|||
|
|||
.goods-image-slider::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
height: 1px; |
|||
background: linear-gradient(90deg, transparent, rgba(0,0,0,0.05), transparent); |
|||
} |
|||
|
|||
.goods-image-slider swiper { |
|||
width: 100%; |
|||
height: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.goods-image-slider swiper-item { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.slider-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: contain; |
|||
border-radius: 8px; |
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.slider-image:active { |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
/* 商品基本信息 */ |
|||
.goods-info { |
|||
background-color: #ffffff; |
|||
padding: 20px 16px 16px; |
|||
position: relative; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
|||
} |
|||
|
|||
.goods-info::after { |
|||
content: ''; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 16px; |
|||
right: 16px; |
|||
height: 1px; |
|||
background: linear-gradient(90deg, transparent, #f0f0f0, transparent); |
|||
} |
|||
|
|||
.goods-name { |
|||
display: block; |
|||
font-size: 20px; |
|||
font-weight: 700; |
|||
color: #262626; |
|||
margin-bottom: 12px; |
|||
line-height: 1.4; |
|||
letter-spacing: -0.2px; |
|||
} |
|||
|
|||
.goods-price { |
|||
display: flex; |
|||
align-items: baseline; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.price-symbol { |
|||
font-size: 15px; |
|||
color: #666; |
|||
margin-right: 4px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.price-value { |
|||
font-size: 28px; |
|||
color: #ff4d4f; |
|||
font-weight: 700; |
|||
letter-spacing: -0.5px; |
|||
} |
|||
|
|||
.price-value::before { |
|||
content: '¥'; |
|||
font-size: 20px; |
|||
margin-right: 2px; |
|||
} |
|||
|
|||
/* 商品详细信息网格 */ |
|||
.info-grid { |
|||
background-color: #ffffff; |
|||
margin: 12px 0; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
|||
} |
|||
|
|||
.info-row { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
margin-bottom: 16px; |
|||
background: #f0f5ff; |
|||
border-radius: 10px; |
|||
overflow: hidden; |
|||
border: 1px solid #d6e4ff; |
|||
} |
|||
|
|||
.info-row:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.info-item { |
|||
flex: 0 0 50%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 14px 16px; |
|||
box-sizing: border-box; |
|||
position: relative; |
|||
} |
|||
|
|||
.info-item:nth-child(odd)::after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: 1px; |
|||
background: linear-gradient(180deg, transparent, #e8e8e8, transparent); |
|||
} |
|||
|
|||
.info-label-container { |
|||
margin-bottom: 6px; |
|||
} |
|||
|
|||
.info-label { |
|||
font-size: 13px; |
|||
color: #8c8c8c; |
|||
font-weight: 500; |
|||
text-transform: uppercase; |
|||
letter-spacing: 0.3px; |
|||
} |
|||
|
|||
.info-value-container { |
|||
min-height: 24px; |
|||
} |
|||
|
|||
.info-value { |
|||
font-size: 16px; |
|||
color: #000000d9; |
|||
font-weight: 600; |
|||
line-height: 1.3; |
|||
} |
|||
|
|||
/* 联系信息 */ |
|||
.contact-info { |
|||
margin: 16px; |
|||
padding: 16px; |
|||
border-radius: 12px; |
|||
background: #ffffff; |
|||
border: 1px solid #d6e4ff; |
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); |
|||
} |
|||
|
|||
.contact-label { |
|||
display: block; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
color: #2f54eb; |
|||
margin-bottom: 16px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.contact-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.contact-item { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 8px 0; |
|||
} |
|||
|
|||
.phone-item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 8px 0; |
|||
} |
|||
|
|||
.contact-icon { |
|||
font-size: 16px; |
|||
margin-right: 10px; |
|||
flex-shrink: 0; |
|||
width: 20px; |
|||
text-align: center; |
|||
color: #2f54eb; |
|||
} |
|||
|
|||
.user-icon { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.phone-icon { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.contact-label-text { |
|||
font-size: 14px; |
|||
color: #595959; |
|||
margin-right: 8px; |
|||
white-space: nowrap; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.contact-text { |
|||
font-size: 14px; |
|||
color: #262626; |
|||
flex-shrink: 0; |
|||
margin-right: 16px; |
|||
} |
|||
|
|||
.phone-info { |
|||
display: flex; |
|||
flex-direction: row; |
|||
flex: 1; |
|||
align-items: center; |
|||
} |
|||
|
|||
.phone-info .contact-label-text { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.call-button { |
|||
padding: 0 24px; |
|||
height: 36px; |
|||
line-height: 36px; |
|||
font-size: 14px; |
|||
color: #ffffff; |
|||
background-color: #2f54eb; |
|||
border: 1px solid #2f54eb; |
|||
border-radius: 4px; |
|||
flex-shrink: 0; |
|||
box-shadow: 0 2px 8px rgba(47, 84, 235, 0.2); |
|||
} |
|||
|
|||
.call-button:active { |
|||
background-color: #4066ff; |
|||
border-color: #4066ff; |
|||
box-shadow: 0 4px 12px rgba(47, 84, 235, 0.3); |
|||
} |
|||
|
|||
.call-button:active { |
|||
background-color: #1d39c4; |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
/* 操作按钮区域 */ |
|||
.action-buttons { |
|||
position: fixed; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
padding: 16px; |
|||
background-color: #ffffff; |
|||
border-top: 1px solid #f0f0f0; |
|||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06); |
|||
z-index: 99; |
|||
display: flex; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.bottom-button { |
|||
flex: 1; |
|||
height: 52px; |
|||
border-radius: 26px; |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border: none; |
|||
outline: none; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.call-button.bottom-button { |
|||
background-color: #ffffff; |
|||
color: #2f54eb; |
|||
border: 2px solid #2f54eb; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
.want-button.bottom-button { |
|||
background: linear-gradient(135deg, #2f54eb 0%, #1d39c4 100%); |
|||
color: #ffffff; |
|||
box-shadow: 0 4px 12px rgba(47, 84, 235, 0.2); |
|||
} |
|||
|
|||
.call-button.bottom-button:active { |
|||
background-color: #f0f4ff; |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
.want-button.bottom-button:active { |
|||
transform: translateY(2px) scale(0.98); |
|||
box-shadow: 0 2px 6px rgba(47, 84, 235, 0.15); |
|||
} |
|||
|
|||
.want-button.bottom-button[disabled] { |
|||
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%); |
|||
color: #ffffff !important; |
|||
border: none; |
|||
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.2); |
|||
opacity: 1; |
|||
} |
|||
|
|||
.want-button.bottom-button[disabled]:active { |
|||
transform: none; |
|||
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3); |
|||
} |
|||
|
|||
/* 图片预览弹窗 */ |
|||
.image-preview-container { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: #000000; |
|||
z-index: 1000; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.preview-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20px 16px; |
|||
color: #ffffff; |
|||
z-index: 10; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
backdrop-filter: blur(10px); |
|||
} |
|||
|
|||
.preview-close { |
|||
width: 44px; |
|||
height: 44px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: rgba(255, 255, 255, 0.1); |
|||
border-radius: 22px; |
|||
transition: all 0.2s ease; |
|||
} |
|||
|
|||
.preview-close:active { |
|||
background: rgba(255, 255, 255, 0.2); |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
.close-icon { |
|||
font-size: 24px; |
|||
color: #ffffff; |
|||
font-weight: 300; |
|||
} |
|||
|
|||
.preview-indicator { |
|||
font-size: 15px; |
|||
color: #ffffff; |
|||
font-weight: 500; |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.preview-swiper { |
|||
flex: 1; |
|||
width: 100%; |
|||
} |
|||
|
|||
.preview-image-wrapper { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: relative; |
|||
padding: 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.preview-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
max-width: 100%; |
|||
max-height: 100%; |
|||
object-fit: contain; |
|||
border-radius: 8px; |
|||
} |
|||
|
|||
/* 添加一些微动画效果 */ |
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(20px); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.goods-info, |
|||
.info-grid, |
|||
.contact-info { |
|||
animation: fadeIn 0.4s ease-out; |
|||
} |
|||
|
|||
.info-grid { |
|||
animation-delay: 0.1s; |
|||
} |
|||
|
|||
.contact-info { |
|||
animation-delay: 0.2s; |
|||
} |
|||
|
|||
/* 响应式调整 */ |
|||
@media (min-height: 800px) { |
|||
.goods-image-slider { |
|||
height: 320px; |
|||
} |
|||
|
|||
.goods-name { |
|||
font-size: 22px; |
|||
} |
|||
|
|||
.price-value { |
|||
font-size: 32px; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue