Browse Source

添加商品详情页面并修改相关文件

pull/1/head
徐飞洋 3 months ago
parent
commit
c0833bc71c
  1. 3
      app.json
  2. 43
      pages/buyer/index.js
  3. 96
      pages/buyer/index.wxml
  4. 440
      pages/goods-detail/goods-detail.js
  5. 4
      pages/goods-detail/goods-detail.json
  6. 175
      pages/goods-detail/goods-detail.wxml
  7. 514
      pages/goods-detail/goods-detail.wxss

3
app.json

@ -8,7 +8,8 @@
"pages/seller/index", "pages/seller/index",
"pages/profile/index", "pages/profile/index",
"pages/notopen/index", "pages/notopen/index",
"pages/create-supply/index" "pages/create-supply/index",
"pages/goods-detail/goods-detail"
], ],
"subpackages": [ "subpackages": [
{ {

43
pages/buyer/index.js

@ -1642,7 +1642,7 @@ Page({
}); });
}, },
// 显示商品详情 // 跳转到商品详情页面
showGoodsDetail: function (e) { showGoodsDetail: function (e) {
// 检查用户是否登录 // 检查用户是否登录
const openid = wx.getStorageSync('openid'); const openid = wx.getStorageSync('openid');
@ -1664,17 +1664,11 @@ Page({
} }
const goodsItem = e.currentTarget.dataset.item; const goodsItem = e.currentTarget.dataset.item;
this.setData({ // 跳转到商品详情页面,并传递商品数据,使用encodeURIComponent编码JSON字符串
currentGoodsDetail: goodsItem, wx.navigateTo({
showGoodsDetail: true url: '/pages/goods-detail/goods-detail?goodsData=' + encodeURIComponent(JSON.stringify(goodsItem))
}); });
// 隐藏底部导航栏 - 通过更新全局数据
const app = getApp();
if (app && app.globalData) {
app.globalData.showTabBar = false;
}
// 同时尝试直接更新tabBar的选中状态 // 同时尝试直接更新tabBar的选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) { if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar(); const tabBar = this.getTabBar();
@ -1693,36 +1687,7 @@ Page({
); );
}, },
// 关闭商品详情
closeGoodsDetail: function () {
this.setData({
showGoodsDetail: false,
currentGoodsDetail: {}
});
// 显示底部导航栏 - 通过更新全局数据
const app = getApp();
if (app && app.globalData) {
app.globalData.showTabBar = true;
}
// 同时尝试直接更新tabBar的选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar();
if (tabBar.setData) {
tabBar.setData({ show: true });
}
}
},
// 在详情弹窗中点击"我想要"按钮
onClickWantInDetail: function (e) {
const goodsId = e.currentTarget.dataset.id;
// 先关闭详情弹窗
this.closeGoodsDetail();
// 然后调用原来的"我想要"方法
this.onClickWant({ currentTarget: { dataset: { id: goodsId } } });
},
// 获取手机号并登录 // 获取手机号并登录
onGetPhoneNumber: function (e) { onGetPhoneNumber: function (e) {

96
pages/buyer/index.wxml

@ -189,103 +189,7 @@
</view> </view>
</view> </view>
<!-- 商品详情弹窗 - 商务风格 -->
<view wx:if="{{showGoodsDetail}}" class="modal-overlay">
<view class="goods-detail-modal">
<!-- 弹窗头部 -->
<view class="goods-detail-header">
<view class="goods-detail-title">商品详情</view>
<view class="goods-detail-close" bindtap="closeGoodsDetail">×</view>
</view>
<!-- 弹窗内容 -->
<view class="goods-detail-content">
<!-- 商品图片 -->
<view wx:if="{{currentGoodsDetail.imageUrls && currentGoodsDetail.imageUrls.length > 0}}" class="goods-image-section">
<swiper class="goods-image-swiper" indicator-dots="true" indicator-color="rgba(0,0,0,0.3)" indicator-active-color="#1677ff">
<block wx:for="{{currentGoodsDetail.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx" class="goods-image-item">
<swiper-item>
<image src="{{img}}" mode="aspectFill" class="goods-image" bindtap="previewImage" data-urls="{{currentGoodsDetail.imageUrls}}" data-index="{{idx}}"></image>
</swiper-item>
</block>
</swiper>
</view>
<!-- 商品基本信息 -->
<view class="goods-info-section">
<view class="goods-name">{{currentGoodsDetail.name}}</view>
<view class="goods-price">价格: {{currentGoodsDetail.price || '暂无'}}</view>
<!-- 商品信息网格 -->
<view class="goods-info-grid">
<view class="goods-info-item">
<view class="goods-info-label">地区</view>
<view class="goods-info-value">{{currentGoodsDetail.region}}</view>
</view>
<view class="goods-info-item">
<view class="goods-info-label">规格</view>
<view class="goods-info-value">{{currentGoodsDetail.spec || '无'}}</view>
</view>
<view class="goods-info-item">
<view class="goods-info-label">蛋黄</view>
<view class="goods-info-value">{{currentGoodsDetail.yolk || '无'}}</view>
</view>
<view class="goods-info-item">
<view class="goods-info-label">斤重</view>
<view class="goods-info-value">{{currentGoodsDetail.displayGrossWeight}}</view>
</view>
<view class="goods-info-item">
<view class="goods-info-label">件数</view>
<view class="goods-info-value">{{currentGoodsDetail.minOrder}}件</view>
</view>
<view class="goods-info-item">
<view class="goods-info-label">关注人数</view>
<view class="goods-info-value">{{currentGoodsDetail.reservedCount || 0}}人</view>
</view>
</view>
<!-- 联系信息 -->
<view class="goods-contact-section">
<view class="goods-contact-title">联系信息</view>
<view class="goods-contact-item">
<text class="goods-contact-icon">👤</text>
<text>联系人: {{currentGoodsDetail.product_contact || '暂无'}}</text>
</view>
<view class="goods-contact-item">
<text class="goods-contact-icon">📞</text>
<text>联系电话: {{currentGoodsDetail.contact_phone || '暂无'}}</text>
<button
wx:if="{{currentGoodsDetail.contact_phone}}"
class="call-phone-button"
bindtap="makePhoneCall"
data-phone="{{currentGoodsDetail.contact_phone}}"
>
拨打电话
</button>
</view>
</view>
</view>
</view>
<!-- 操作按钮区域 -->
<view class="goods-action-section">
<button
wx:if="{{currentGoodsDetail.isReserved}}"
class="goods-action-button reserved"
>
已预约✓
</button>
<button
wx:else
class="goods-action-button"
bindtap="onClickWantInDetail"
data-id="{{currentGoodsDetail.id}}"
>
我想要
</button>
</view>
</view>
</view>
<!-- 四分之一页空白区域 --> <!-- 四分之一页空白区域 -->
<view class="quarter-page-blank"></view> <view class="quarter-page-blank"></view>

440
pages/goods-detail/goods-detail.js

@ -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();
}
});

4
pages/goods-detail/goods-detail.json

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "商品详情",
"enablePullDownRefresh": false
}

175
pages/goods-detail/goods-detail.wxml

@ -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>

514
pages/goods-detail/goods-detail.wxss

@ -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…
Cancel
Save