This commit is contained in:
sjk
2025-11-17 14:11:46 +08:00
commit ad4a600af9
1659 changed files with 171560 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
Component({
externalClasses: ['wr-sold-out', 'wr-class'],
options: { multipleSlots: true },
properties: {
soldout: {
// 商品是否下架
type: Boolean,
value: false,
},
jumpArray: {
type: Array,
value: [],
},
isStock: {
type: Boolean,
value: true,
}, // 是否有库存
isSlotButton: {
type: Boolean,
value: false,
}, // 是否开启按钮插槽
shopCartNum: {
type: Number, // 购物车气泡数量
},
buttonType: {
type: Number,
value: 0,
},
minDiscountPrice: {
type: String,
value: '',
},
minSalePrice: {
type: String,
value: '',
},
isFavorite: {
type: Boolean,
value: false,
},
},
data: {
fillPrice: false,
},
methods: {
toAddCart() {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toAddCart');
},
toBuyNow(e) {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toBuyNow', e);
},
toNav(e) {
const { url } = e.currentTarget.dataset;
return this.triggerEvent('toNav', {
e,
url,
});
},
onToggleFavorite() {
this.triggerEvent('onToggleFavorite');
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,44 @@
<view class="flex soldout flex-center wr-sold-out" wx:if="{{soldout || !isStock}}">
{{soldout ? '商品已下架' : '商品已售馨'}}
</view>
<view class="footer-cont flex flex-between wr-class">
<view class="flex flex-between bottom-operate-left" wx:if="{{jumpArray.length > 0}}">
<view
wx:for="{{jumpArray}}"
wx:key="index"
class="icon-warp operate-wrap"
bindtap="toNav"
data-ele="foot_navigation"
data-index="{{index}}"
data-url="{{item.url}}"
>
<view>
<text wx:if="{{shopCartNum > 0 && item.showCartNum}}" class="tag-cart-num">
{{shopCartNum > 99 ? '99+' : shopCartNum}}
</text>
<t-icon prefix="wr" name="{{item.iconName}}" size="40rpx" />
<view class="operate-text">{{item.title}}</view>
</view>
</view>
<view class="icon-warp operate-wrap favorite-wrap" bindtap="onToggleFavorite">
<view>
<t-icon name="{{isFavorite ? 'heart-filled' : 'heart'}}" size="40rpx" color="{{isFavorite ? '#ff4757' : '#666'}}" />
<view class="operate-text">{{isFavorite ? '已收藏' : '收藏'}}</view>
</view>
</view>
</view>
<block wx:if="{{buttonType === 1}}">
<view class="flex buy-buttons">
<view class="bar-separately {{soldout || !isStock ? 'bar-addCart-disabled' : ''}}" bindtap="toAddCart">
加入购物车
</view>
<view class="bar-buy {{soldout || !isStock ? 'bar-buyNow-disabled' : ''}}" bindtap="toBuyNow">
立即购买
</view>
</view>
</block>
<block wx:if="{{isSlotButton}}">
<slot name="buyButton" />
</block>
</view>

View File

@@ -0,0 +1,181 @@
.footer-cont {
background-color: #fff;
padding: 12rpx 12rpx calc(env(safe-area-inset-bottom) + 12rpx) 12rpx;
display: flex;
align-items: center;
gap: 20rpx;
position: sticky;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.icon-warp {
width: 100rpx;
height: 80rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background: #f8f8f8;
border-radius: 16rpx;
border: 1rpx solid #f0f0f0;
transition: all 0.3s ease;
}
.icon-warp:active {
background: #f0f0f0;
transform: scale(0.95);
}
.operate-wrap {
position: relative;
flex: 1;
}
.bottom-operate-left {
display: flex;
gap: 12rpx;
flex: none;
min-width: 0;
}
.bottom-operate-left .icon-warp {
width: 100rpx;
flex: none;
}
.tag-cart-num {
display: inline-block;
position: absolute;
left: 50rpx;
right: auto;
top: 6rpx;
color: #fff;
line-height: 24rpx;
text-align: center;
z-index: 99;
white-space: nowrap;
min-width: 28rpx;
border-radius: 14rpx;
background-color: #fa550f !important;
font-size: 20rpx;
font-weight: 400;
padding: 2rpx 6rpx;
}
.operate-text {
color: #666;
font-size: 20rpx;
margin-top: 4rpx;
font-weight: 400;
}
.soldout {
height: 80rpx;
background: linear-gradient(135deg, #ccc 0%, #aaa 100%);
width: 100%;
color: #fff;
border-radius: 16rpx;
font-size: 28rpx;
font-weight: 500;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.addCart-disabled,
.bar-addCart-disabled {
background: linear-gradient(135deg, #ddd 0%, #ccc 100%) !important;
color: #fff !important;
font-size: 28rpx;
border-radius: 16rpx 0 0 16rpx !important;
}
.buyNow-disabled,
.bar-buyNow-disabled {
background: linear-gradient(135deg, #ccc 0%, #aaa 100%) !important;
color: #fff !important;
font-size: 28rpx;
border-radius: 0 16rpx 16rpx 0 !important;
}
.bar-separately,
.bar-buy {
height: 80rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 4rpx 12rpx rgba(250, 65, 38, 0.2);
flex: 1 1 auto;
min-width: 160rpx;
box-sizing: border-box;
}
.bar-separately {
background: linear-gradient(135deg, #fff5f4 0%, #ffece9 100%);
color: #fa4126;
border-radius: 16rpx 0 0 16rpx;
border: 2rpx solid #fa4126;
box-shadow: 0 4rpx 12rpx rgba(250, 65, 38, 0.1);
}
.bar-separately:active {
background: linear-gradient(135deg, #ffece9 0%, #ffe0dc 100%);
transform: scale(0.98);
}
.bar-buy {
background: linear-gradient(135deg, #fa4126 0%, #e63312 100%);
border-radius: 0 16rpx 16rpx 0;
}
.bar-buy:active {
background: linear-gradient(135deg, #e63312 0%, #d12a0f 100%);
transform: scale(0.98);
}
/* 收藏按钮样式 */
.favorite-wrap {
position: relative;
}
.favorite-wrap .operate-text {
color: #666;
transition: color 0.3s ease;
}
.favorite-wrap:active .operate-text {
color: #ff4757;
}
.flex {
display: flex;
display: -webkit-flex;
}
.flex-center {
justify-content: center;
-webkit-justify-content: center;
align-items: center;
-webkit-align-items: center;
}
.flex-between {
justify-content: space-between;
-webkit-justify-content: space-between;
}
/* 购买按钮容器自适应剩余空间 */
.buy-buttons {
display: flex;
gap: 0;
flex: 1 1 auto;
min-width: 0;
}

View File

@@ -0,0 +1,364 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-nested-ternary */
import Toast from 'tdesign-miniprogram/toast/index';
Component({
options: {
multipleSlots: true,
addGlobalClass: true,
},
properties: {
src: {
type: String,
},
title: String,
show: {
type: Boolean,
value: false,
},
limitBuyInfo: {
type: String,
value: '',
},
isStock: {
type: Boolean,
value: true,
},
limitMaxCount: {
type: Number,
value: 999,
},
limitMinCount: {
type: Number,
value: 1,
},
skuList: {
type: Array,
value: [],
observer(skuList) {
if (skuList && skuList.length > 0) {
if (this.initStatus) {
this.initData();
}
}
},
},
specList: {
type: Array,
value: [],
observer(specList) {
if (specList && specList.length > 0) {
this.initData();
}
},
},
outOperateStatus: {
type: Boolean,
value: false,
},
hasAuth: {
type: Boolean,
value: false,
},
count: {
type: Number,
value: 1,
observer(count) {
this.setData({
buyNum: count,
});
},
},
},
initStatus: false,
selectedSku: {},
selectSpecObj: {},
data: {
buyNum: 1,
isAllSelectedSku: false,
},
methods: {
initData() {
const { skuList } = this.properties;
const { specList } = this.properties;
// 为每个规格值计算库存信息
specList.forEach((item) => {
if (item.specValueList.length > 0) {
item.specValueList.forEach((subItem) => {
const obj = this.checkSkuStockQuantity(subItem, skuList, item);
subItem.hasStockObj = obj;
// 初始化未选中状态
subItem.isSelected = false;
});
}
});
// 默认选择策略优先选择第一个有库存的SKU组合其次选择第一个SKU
const getQty = (sku) => {
return Number((sku.stockInfo && sku.stockInfo.stockQuantity) ?? sku.stock ?? sku.quantity ?? 0) || 0;
};
const defaultSku = (skuList || []).find((s) => getQty(s) > 0) || (skuList || [])[0];
// 构建 selectedSku 映射,并在 specList 中标记选中项
const selectedSkuMap = {};
if (defaultSku && Array.isArray(defaultSku.specInfo)) {
defaultSku.specInfo.forEach((spec) => {
const specId = spec.specId;
const specValueId = spec.specValueId;
const specTitle = spec.specTitle;
const specValue = spec.specValue;
// 在对应的规格组内标记选中项
specList.forEach((group) => {
const sameGroup = String(group.specId) === String(specId) || (!!specTitle && group.title === specTitle);
if (sameGroup && Array.isArray(group.specValueList)) {
group.specValueList.forEach((sv) => {
const matchById = specValueId != null && String(sv.specValueId) === String(specValueId);
const matchByText = specValue != null && sv.specValue != null && sv.specValue === specValue;
sv.isSelected = matchById || matchByText;
});
// 记录所选项的ID优先使用ID
const chosen = group.specValueList.find((sv) => sv.isSelected);
if (chosen) {
selectedSkuMap[group.specId] = chosen.specValueId;
}
}
});
});
} else {
// 当没有SKU或SKU缺少specInfo时退化为每组选择第一个有库存的规格值
specList.forEach((group) => {
const firstAvailable = (group.specValueList || []).find((sv) => sv.hasStockObj?.hasStock) || (group.specValueList || [])[0];
if (firstAvailable) {
group.specValueList.forEach((sv) => { sv.isSelected = sv === firstAvailable; });
selectedSkuMap[group.specId] = firstAvailable.specValueId;
}
});
}
// 更新组件状态
const isAllSelectedSku = specList.every((spec) => !!selectedSkuMap[spec.specId]);
this.selectedSku = selectedSkuMap;
this.selectSpecObj = {};
this.setData({
specList,
isAllSelectedSku,
});
// 通知父页面已完成默认选择,便于价格与图片联动
this.triggerEvent('change', {
selectedSku: this.selectedSku,
isAllSelectedSku,
});
this.initStatus = true;
},
// 兼容两种数据结构:
// 1) sku.specInfo 使用 { specId, specValueId }
// 2) sku.specInfo 使用 { specTitle, specValue }
// 同时 specList.subItem 具备 { specValueId, specValue }
checkSkuStockQuantity(specValueItem, skuList, specGroupItem) {
let hasStock = false;
let stockQuantity = 0;
skuList.forEach((sku) => {
if (sku.specInfo) {
sku.specInfo.forEach((spec) => {
// 统一以字符串比较,避免类型不一致导致匹配失败
const matchById =
spec.specValueId != null && specValueItem.specValueId != null &&
String(spec.specValueId) === String(specValueItem.specValueId);
const sameGroupByTitle =
spec.specTitle && specGroupItem && specGroupItem.title && spec.specTitle === specGroupItem.title;
const matchByText =
spec.specValue != null && specValueItem.specValue != null && spec.specValue === specValueItem.specValue &&
(sameGroupByTitle || true); // 若无 title 也放行文本匹配
if (matchById || matchByText) {
// 兼容多种库存字段来源,优先 stockInfo.stockQuantity
const qty = Number(
(sku.stockInfo && sku.stockInfo.stockQuantity) ?? sku.stock ?? sku.quantity ?? 0
) || 0;
stockQuantity += qty;
if (qty > 0) {
hasStock = true;
}
}
});
}
});
return {
hasStock,
stockQuantity,
};
},
toChooseItem(e) {
const { specid, id, val } = e.currentTarget.dataset;
// 放开点击,避免因错误的 hasStock 计算而阻断选择
const { specList } = this.data;
const { skuList } = this.properties;
specList.forEach((spec) => {
if (String(spec.specId) === String(specid)) {
spec.specValueList.forEach((specValue) => {
if (String(specValue.specValueId) === String(id)) {
specValue.isSelected = !specValue.isSelected;
} else {
specValue.isSelected = false;
}
});
}
});
this.selectSpecObj[specid] = {
specId: specid,
specValueId: id,
specValue: val,
};
this.selectedSku[specid] = id;
const isAllSelectedSku = this.isAllSelectedSku();
this.setData({
specList,
isAllSelectedSku,
});
// 与父页面绑定的事件名保持一致:父页面使用 bind:change="chooseSpecItem"
this.triggerEvent('change', {
selectedSku: this.selectedSku,
isAllSelectedSku,
});
},
isAllSelectedSku() {
const { specList } = this.properties;
let isAllSelectedSku = true;
specList.forEach((spec) => {
if (!this.selectedSku[spec.specId]) {
isAllSelectedSku = false;
}
});
return isAllSelectedSku;
},
getSelectedSkuId() {
const { skuList } = this.properties;
const { selectedSku } = this;
let selectedSkuId = '';
skuList.forEach((sku) => {
let isMatch = true;
if (sku.specInfo) {
sku.specInfo.forEach((spec) => {
// 统一字符串比较避免类型不一致导致匹配失败
if (String(selectedSku[spec.specId]) !== String(spec.specValueId)) {
isMatch = false;
}
});
}
if (isMatch) {
selectedSkuId = sku.skuId;
}
});
return selectedSkuId;
},
handlePopupHide() {
// 触发与父页面绑定一致的事件名,确保可以关闭弹窗
this.triggerEvent('closeSpecsPopup');
},
specsConfirm() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('specsConfirm', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
addCart() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('addCart', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
buyNow() {
const { isAllSelectedSku } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择完整的商品规格',
theme: 'warning',
direction: 'column',
});
return;
}
this.triggerEvent('buyNow', {
selectedSku: this.selectedSku,
buyNum: this.data.buyNum,
});
},
setBuyNum(e) {
const { value } = e.detail;
this.setData({
buyNum: value,
});
},
changeNum(e) {
const { buyNum } = e.detail;
this.setData({
buyNum,
});
},
handleBuyNumChange(e) {
const { value } = e.detail;
this.setData({
buyNum: value,
});
// 触发父组件的 changeNum 事件,通知价格更新
this.triggerEvent('changeNum', {
buyNum: value,
});
},
},
});

View File

@@ -0,0 +1,10 @@
{
"component": true,
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "/components/webp-image/index",
"t-stepper": "tdesign-miniprogram/stepper/stepper",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}

View File

@@ -0,0 +1,84 @@
<t-popup visible="{{show}}" placement="bottom" bind:visible-change="handlePopupHide">
<view class="popup-container">
<view class="popup-close" bindtap="handlePopupHide">
<t-icon name="close" size="36rpx" />
</view>
<view class="popup-sku-header">
<t-image t-class="popup-sku-header__img" src="{{src}}" />
<view class="popup-sku-header__goods-info">
<view class="popup-sku__goods-name">{{title}}</view>
<view class="goods-price-container">
<slot name="goods-price" />
</view>
<!-- 已选规格 -->
<view class="popup-sku__selected-spec">
<view>选择:</view>
<view wx:for="{{specList}}" wx:key="specId">
<view
class="popup-sku__selected-item"
wx:for="{{item.specValueList}}"
wx:for-item="selectedItem"
wx:if="{{selectedItem.isSelected}}"
wx:key="specValueId"
>
{{selectedItem.specValue}}
</view>
</view>
</view>
</view>
</view>
<view class="popup-sku-body">
<view class="popup-sku-group-container">
<view class="popup-sku-row" wx:for="{{specList}}" wx:key="specId">
<view class="popup-sku-row__title">{{item.title}}</view>
<block
wx:for="{{item.specValueList}}"
wx:for-item="valuesItem"
wx:for-index="valuesIndex"
wx:key="specValueId"
>
<view
class="popup-sku-row__item {{valuesItem.isSelected ? 'popup-sku-row__item--active' : ''}} {{!valuesItem.hasStockObj.hasStock || !isStock ? 'disabled-sku-selected' : ''}}"
data-specid="{{item.specId}}"
data-id="{{valuesItem.specValueId}}"
data-val="{{valuesItem.specValue}}"
data-hasStock="{{valuesItem.hasStockObj.hasStock}}"
bindtap="toChooseItem"
>
{{valuesItem.specValue}}
</view>
</block>
</view>
</view>
<view class="popup-sku-stepper-stock">
<view class="popup-sku-stepper-container">
<view class="popup-sku__stepper-title">
购买数量
<view class="limit-text" wx:if="{{limitBuyInfo}}"> ({{limitBuyInfo}}) </view>
</view>
<t-stepper value="{{buyNum}}" min="{{1}}" max="{{2}}" theme="filled" bind:change="handleBuyNumChange" />
</view>
</view>
</view>
<view wx:if="{{outOperateStatus}}" class="single-confirm-btn {{!isStock ? 'disabled' : ''}}" bindtap="specsConfirm">
确定
</view>
<view
class="popup-sku-actions flex flex-between {{!isStock ? 'popup-sku-disabled' : ''}}"
wx:if="{{!outOperateStatus}}"
>
<view class="sku-operate">
<view class="selected-sku-btn sku-operate-addCart {{!isStock ? 'disabled' : ''}}" bindtap="addCart">
加入购物车
</view>
</view>
<view class="sku-operate">
<view class="selected-sku-btn sku-operate-buyNow {{!isStock ? 'disabled' : ''}}" bindtap="buyNow">
立即购买
</view>
</view>
</view>
<slot name="bottomSlot" />
</view>
</t-popup>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,312 @@
.popup-container {
background-color: #ffffff;
position: relative;
z-index: 100;
border-radius: 16rpx 16rpx 0 0;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
.popup-container .popup-close {
position: absolute;
right: 30rpx;
top: 30rpx;
z-index: 9;
color: #999999;
}
.popup-sku-header {
display: flex;
padding: 30rpx 28rpx 0 30rpx;
}
.popup-sku-header .popup-sku-header__img {
width: 176rpx;
height: 176rpx;
border-radius: 8rpx;
background: #d8d8d8;
margin-right: 24rpx;
}
.popup-sku-header .popup-sku-header__goods-info {
position: relative;
width: 500rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__goods-name {
font-size: 28rpx;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: normal;
overflow: hidden;
width: 430rpx;
text-overflow: ellipsis;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__selected-spec {
display: flex;
color: #333333;
font-size: 26rpx;
line-height: 36rpx;
}
.popup-sku__total-price {
display: flex;
align-items: center;
margin-top: 8rpx;
font-size: 24rpx;
color: #666666;
}
.popup-sku__total-price .total-price-label {
margin-right: 8rpx;
}
.popup-sku-header
.popup-sku-header__goods-info
.popup-sku__selected-spec
.popup-sku__selected-item {
margin-right: 10rpx;
}
.popup-sku-body {
margin: 0 30rpx 40rpx;
max-height: 600rpx;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.popup-sku-body .popup-sku-group-container .popup-sku-row {
padding: 32rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.popup-sku-row__title {
font-size: 26rpx;
color: #333;
}
.popup-sku-body .popup-sku-group-container .popup-sku-row .popup-sku-row__item {
font-size: 24rpx;
color: #333;
min-width: 128rpx;
height: 56rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
border: 2rpx solid #f5f5f5;
margin: 19rpx 26rpx 0 0;
padding: 0 16rpx;
display: inline-flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.popup-sku-row__item.popup-sku-row__item--active {
border: 2rpx solid #fa4126;
color: #fa4126;
background: rgba(255, 95, 21, 0.04);
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.disabled-sku-selected {
background: #f5f5f5 !important;
color: #cccccc;
}
.popup-sku-body .popup-sku-stepper-stock .popup-sku-stepper-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40rpx 0;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-sku__stepper-title {
display: flex;
font-size: 26rpx;
color: #333;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-sku__stepper-title
.limit-text {
margin-left: 10rpx;
color: #999999;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper {
display: flex;
flex-flow: row nowrap;
align-items: center;
font-size: 28px;
height: 48rpx;
line-height: 62rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-btn,
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap {
position: relative;
height: 100%;
text-align: center;
background-color: #f5f5f5;
border-radius: 4rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap {
color: #282828;
display: flex;
max-width: 76rpx;
align-items: center;
justify-content: space-between;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap
.input-num {
height: 100%;
width: auto;
font-weight: 600;
font-size: 30rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-btn {
width: 48rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__minus {
margin-right: 4rpx;
border-radius: 4rpx;
color: #9a979b;
display: flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus {
margin-left: 4rpx;
border-radius: 4rpx;
color: #9a979b;
display: flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus::after {
width: 24rpx;
height: 3rpx;
background-color: #999999;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus::before {
width: 3rpx;
height: 24rpx;
background-color: #999999;
}
.popup-sku-actions {
font-size: 32rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
padding: 0 20rpx;
}
.popup-sku-actions .sku-operate {
height: 80rpx;
width: 50%;
color: #fff;
border-radius: 48rpx;
}
.popup-sku-actions .sku-operate .sku-operate-addCart {
background-color: #ffece9;
color: #fa4126;
border-radius: 48rpx 0 0 48rpx;
}
.popup-sku-actions .sku-operate .sku-operate-addCart.disabled {
background: rgb(221, 221, 221);
color: #fff;
}
.popup-sku-actions .sku-operate .sku-operate-buyNow {
background-color: #fa4126;
border-radius: 0 48rpx 48rpx 0;
}
.popup-sku-actions .sku-operate .sku-operate-buyNow.disabled {
color: #fff;
background: rgb(198, 198, 198);
}
.popup-sku-actions .sku-operate .selected-sku-btn {
width: 100%;
}
.popup-container .single-confirm-btn {
border-radius: 48rpx;
color: #ffffff;
margin: 0 32rpx;
font-size: 32rpx;
height: 80rpx;
text-align: center;
line-height: 88rpx;
background-color: #fa4126;
}
.popup-container .single-confirm-btn.disabled {
font-size: 32rpx;
color: #fff;
background-color: #dddddd;
}

View File

@@ -0,0 +1,35 @@
Component({
options: {
multipleSlots: true,
},
properties: {
list: Array,
title: {
type: String,
value: '促销说明',
},
show: {
type: Boolean,
},
},
// data: {
// list: [],
// },
methods: {
change(e) {
const { index } = e.currentTarget.dataset;
this.triggerEvent('promotionChange', {
index,
});
},
closePromotionPopup() {
this.triggerEvent('closePromotionPopup', {
show: false,
});
},
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,34 @@
<t-popup visible="{{show}}" placement="bottom" bind:visible-change="closePromotionPopup">
<view class="promotion-popup-container">
<view class="promotion-popup-close" bindtap="closePromotionPopup">
<t-icon name="close" size="36rpx" />
</view>
<view class="promotion-popup-title">
<view class="title">{{title}}</view>
</view>
<view class="promotion-popup-content">
<view class="promotion-detail-list">
<view
class="list-item"
wx:for="{{list}}"
wx:key="index"
bindtap="change"
data-index="{{index}}"
>
<view class="tag">{{item.tag}}</view>
<view class="content">
<text class="list-content">{{item.label ? item.label : ''}}</text>
</view>
<t-icon
class="collect-btn"
name="chevron-right"
size="40rpx"
color="#bbb"
/>
</view>
</view>
</view>
<slot name="promotion-bottom" />
</view>
</t-popup>

View File

@@ -0,0 +1,131 @@
.promotion-popup-container {
background-color: #ffffff;
position: relative;
z-index: 100;
border-radius: 16rpx 16rpx 0 0;
}
.promotion-popup-container .promotion-popup-close {
position: absolute;
right: 30rpx;
top: 30rpx;
z-index: 9;
color: rgba(153, 153, 153, 1);
}
.promotion-popup-container .promotion-popup-close .market {
font-size: 25rpx;
color: #999;
}
.promotion-popup-container .promotion-popup-title {
height: 100rpx;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.promotion-popup-container .promotion-popup-title {
font-size: 32rpx;
color: #222427;
font-weight: 600;
}
.promotion-popup-container .promotion-popup-content {
min-height: 400rpx;
max-height: 600rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.promotion-popup-container .promotion-popup-content .promotion-detail-list {
margin: 0 30rpx;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item:last-child {
margin-bottom: env(safe-area-inset-bottom);
border-bottom: 0;
padding-bottom: calc(28rpx + env(safe-area-inset-bottom));
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item {
display: flex;
justify-content: space-between;
padding: 10rpx 0 28rpx;
position: relative;
font-size: 24rpx;
color: #222427;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.tag {
box-sizing: border-box;
font-size: 20rpx;
line-height: 32rpx;
padding: 2rpx 12rpx;
background-color: #ffece9;
margin-right: 16rpx;
display: inline-flex;
color: #fa4126;
border-radius: 54rpx;
flex-shrink: 0;
position: relative;
top: 2rpx;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.content {
font-size: 28rpx;
color: #222427;
flex: 1;
line-height: 40rpx;
display: flex;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.content
.list-content {
width: 440rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.collect-btn {
font-size: 24rpx;
flex-shrink: 0;
margin-left: 20rpx;
display: flex;
align-items: center;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.collect-btn
.linkText {
margin-right: 8rpx;
}

View File

@@ -0,0 +1,979 @@
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();
}
}
});

View File

@@ -0,0 +1,18 @@
{
"navigationBarTitleText": "商品详情",
"usingComponents": {
"t-image": "/components/webp-image/index",
"t-tag": "tdesign-miniprogram/tag/tag",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-rate": "tdesign-miniprogram/rate/rate",
"t-swiper": "tdesign-miniprogram/swiper/swiper",
"t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav",
"t-button": "tdesign-miniprogram/button/button",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-popup": "tdesign-miniprogram/popup/popup",
"price": "/components/price/index",
"buy-bar": "./components/buy-bar/index",
"promotion-popup": "./components/promotion-popup/index",
"goods-specs-popup": "./components/goods-specs-popup/index"
}
}

View File

@@ -0,0 +1,181 @@
<view class="goods-detail-page">
<view class="goods-head">
<t-swiper
wx:if="{{details.images.length > 0}}"
height="750rpx"
current="{{current}}"
autoplay="{{autoplay}}"
duration="{{duration}}"
interval="{{interval}}"
navigation="{{navigation}}"
list="{{details.images}}"
></t-swiper>
<view class="goods-info">
<view class="goods-title">
<view class="goods-name">{{details.title}}</view>
</view>
<view class="goods-number">
<view class="goods-price">
<price
wr-class="class-goods-price"
symbol-class="class-goods-symbol"
price="{{minSalePrice}}"
type="lighter"
/>
<view class="goods-price-up">起</view>
<price wr-class="class-goods-del" price="{{maxLinePrice}}" type="delthrough" />
</view>
<view class="sales-share-row">
<view class="sold-num">已售{{soldNum}}</view>
<button class="share-btn-small" open-type="share">
<t-icon name="share" size="32rpx" color="#666" />
<view class="share-text-small">分享</view>
</button>
</view>
</view>
<view wx:if="{{activityList.length > 0}}" class="goods-activity" bindtap="showPromotionPopup">
<view class="tags-container">
<view wx:for="{{activityList}}" data-promotionId="{{item.promotionId}}" wx:key="index" wx:if="{{index<4}}">
<view class="goods-activity-tag">{{item.tag}}</view>
</view>
</view>
</view>
</view>
<!-- 评论入口 -->
<view class="comments-entry" bindtap="navToCommentsListPage">
<view class="comments-entry-content">
<view class="comments-entry-left">
<view class="comments-title">
<text class="comments-title-label">商品评价</text>
<text class="comments-title-count">({{ commentsStatistics.commentCount || 0 }})</text>
</view>
<view class="comments-summary" wx:if="{{ commentsStatistics.commentCount > 0 }}">
<text class="good-rate">{{commentsStatistics.goodRate}}% 好评</text>
<text class="view-all">查看全部评价</text>
</view>
<view class="comments-summary" wx:else>
<text class="no-comments">暂无评价,点击查看详情</text>
</view>
</view>
<view class="comments-entry-right">
<t-icon name="chevron-right" size="40rpx" color="#BBBBBB" />
</view>
</view>
</view>
</view>
<!-- 商品详细介绍区域 -->
<view class="desc-content" wx:if="{{(details.desc && details.desc.length > 0) || details.descriptionText || details.details}}">
<!-- 详情介绍标题 -->
<view class="desc-content__title">
<t-image t-class="img" src="{{recLeftImg}}" />
<text class="desc-content__title--text">详情介绍</text>
<t-image t-class="img" src="{{recRightImg}}" />
</view>
<!-- 商品基本信息 -->
<view class="desc-basic-info" wx:if="{{details.brand || details.category}}">
<view class="desc-info-item" wx:if="{{details.brand}}">
<text class="desc-info-label">品牌:</text>
<text class="desc-info-value">{{details.brand}}</text>
</view>
<view class="desc-info-item" wx:if="{{details.category}}">
<text class="desc-info-label">分类:</text>
<text class="desc-info-value">{{details.category}}</text>
</view>
</view>
<!-- 文本描述 -->
<view class="desc-content__text" wx:if="{{details.descriptionText}}">
<view class="desc-text-title">商品描述</view>
<text class="desc-text-content">{{details.descriptionText}}</text>
</view>
<!-- 详细信息 -->
<view class="desc-details" wx:if="{{details.details}}">
<view class="desc-text-title">详细信息</view>
<text class="desc-text-content">{{details.details}}</text>
</view>
<!-- 详情图片展示 -->
<view class="desc-images" wx:if="{{details.desc && details.desc.length > 0}}">
<view class="desc-text-title">图片详情</view>
<view class="desc-image-container" wx:for="{{details.desc}}" wx:key="index">
<t-image
t-class="desc-content__img"
src="{{item}}"
mode="widthFix"
bind:load="onDescImageLoad"
bind:error="onDescImageError"
bind:tap="previewDescImage"
data-url="{{item}}"
data-index="{{index}}"
lazy-load="{{true}}"
loading="{{true}}"
/>
</view>
</view>
<!-- 当没有任何内容时的占位 -->
<view class="desc-empty" wx:if="{{!details.desc || details.desc.length === 0 && !details.descriptionText && !details.details}}">
<t-icon name="image" size="48" color="#cccccc" />
<text class="desc-empty-text">暂无详细介绍</text>
</view>
</view>
<view class="goods-bottom-operation">
<buy-bar
jumpArray="{{jumpArray}}"
soldout="{{soldout}}"
isStock="{{isStock}}"
shopCartNum="{{cartNum}}"
buttonType="{{buttonType}}"
isFavorite="{{isFavorite}}"
bind:toAddCart="toAddCart"
bind:toNav="toNav"
bind:toBuyNow="buyItNow"
bind:onToggleFavorite="onToggleFavorite"
class="goods-details-card"
/>
</view>
<goods-specs-popup
id="goodsSpecsPopup"
show="{{isSpuSelectPopupShow}}"
title="{{details.title || ''}}"
src="{{specImg ? specImg : primaryImage}}"
specList="{{details.specList || []}}"
skuList="{{skuArray}}"
limitBuyInfo="{{details.limitInfo && details.limitInfo[0] && details.limitInfo[0].text || ''}}"
bind:closeSpecsPopup="handlePopupHide"
bind:change="chooseSpecItem"
bind:changeNum="changeNum"
bind:addCart="addCart"
bind:buyNow="gotoBuy"
bind:specsConfirm="specsConfirm"
isStock="{{isStock}}"
outOperateStatus="{{outOperateStatus}}"
>
<view slot="goods-price">
<view class="popup-sku__price">
<price
wx:if="{{!isAllSelectedSku || (!promotionSubCode && isAllSelectedSku)}}"
price="{{totalPrice || (selectSkuSellsPrice ? selectSkuSellsPrice : minSalePrice)}}"
wr-class="popup-sku__price-num"
symbol-class="popup-sku__price-symbol"
/>
<price
wx:if="{{selectSkuSellsPrice === 0 && minSalePrice !== maxSalePrice && !isAllSelectedSku}}"
price="{{maxSalePrice * (buyNum || 1)}}"
wr-class="popup-sku__price-del"
type="delthrough"
/>
</view>
</view>
</goods-specs-popup>
<promotion-popup
list="{{list}}"
bind:closePromotionPopup="closePromotionPopup"
show="{{isShowPromotionPop}}"
bind:promotionChange="promotionChange"
/>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,810 @@
@import '../../../style/global.wxss';
page {
width: 100%;
background-color: #f5f5f5;
overflow-x: hidden;
}
.goods-detail-page {
width: 100%;
max-width: 100%;
overflow-x: hidden;
box-sizing: border-box;
}
.goods-detail-page .goods-head {
background-color: #fff;
border-radius: 0 0 24rpx 24rpx;
overflow: hidden;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
.goods-detail-page .goods-info {
margin: 0 auto;
padding: 26rpx 30rpx 28rpx 30rpx;
background-color: transparent;
box-sizing: border-box;
}
.goods-detail-page .swipe-img {
width: 100%;
height: 750rpx;
border-radius: 0 0 24rpx 24rpx;
}
/* 商品详情页面整体样式优化 */
.goods-detail-page {
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
min-height: 100vh;
}
/* 轮播图区域优化 */
.goods-detail-page .goods-head {
position: relative;
background: #fff;
border-radius: 0 0 32rpx 32rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
}
.goods-detail-page .goods-head .goods-swipe {
position: relative;
}
.goods-detail-page .goods-head .goods-swipe .goods-swipe-img {
width: 100%;
height: 750rpx;
object-fit: cover;
transition: transform 0.3s ease;
}
.goods-detail-page .goods-head .goods-swipe .goods-swipe-img:active {
transform: scale(0.98);
}
/* 分享按钮优化 */
.goods-detail-page .goods-head .share-btn {
position: absolute;
top: 40rpx;
right: 30rpx;
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10rpx);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
z-index: 10;
}
.goods-detail-page .goods-head .share-btn:active {
transform: scale(0.9);
background: rgba(255, 255, 255, 1);
}
/* 商品信息区域优化 */
.goods-detail-page .goods-info {
background: #fff;
margin: 20rpx 30rpx;
padding: 32rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
border: 1rpx solid #f0f0f0;
}
/* 价格显示优化 */
.goods-detail-page .goods-info .goods-price {
display: flex;
align-items: baseline;
margin-bottom: 24rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.goods-detail-page .goods-info .goods-price .class-goods-price {
font-size: 48rpx;
color: #fa4126;
font-weight: 700;
margin-right: 16rpx;
}
.goods-detail-page .goods-info .goods-price .class-goods-del {
position: relative;
font-weight: normal;
color: #999999;
font-size: 28rpx;
text-decoration: line-through;
}
/* 销量信息优化 */
.goods-detail-page .goods-info .goods-number {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.goods-detail-page .goods-info .goods-number .sold-num {
font-size: 26rpx;
color: #666666;
display: flex;
align-items: center;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 12rpx 20rpx;
border-radius: 24rpx;
border: 1rpx solid #e9ecef;
}
/* 销量与分享同一行展示 */
.goods-detail-page .goods-info .sales-share-row {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 16rpx;
}
.goods-detail-page .goods-info .sales-share-row .share-btn-small {
display: flex;
align-items: center;
gap: 8rpx;
background: transparent;
border: none;
padding: 0;
color: #666666;
font-size: 26rpx;
}
.goods-detail-page .goods-info .sales-share-row .share-btn-small::after {
border: none;
}
/* 促销活动区域优化 */
.goods-detail-page .goods-info .goods-activity {
display: flex;
margin: 24rpx 0;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #fff5f5 0%, #ffeaea 100%);
padding: 20rpx 24rpx;
border-radius: 20rpx;
border: 1rpx solid #ffe5e5;
position: relative;
overflow: hidden;
}
.goods-detail-page .goods-info .goods-activity::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4rpx;
height: 100%;
background: linear-gradient(180deg, #fa4126 0%, #ff6b4a 100%);
}
.goods-detail-page .goods-info .goods-activity .tags-container {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
flex: 1;
}
.goods-detail-page .goods-info .goods-activity .tags-container .goods-activity-tag {
background: linear-gradient(135deg, #fa4126 0%, #ff6b4a 100%);
color: #fff;
font-size: 22rpx;
font-weight: 600;
padding: 10rpx 18rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(250, 65, 38, 0.3);
transition: all 0.3s ease;
}
.goods-detail-page .goods-info .goods-activity .tags-container .goods-activity-tag:active {
transform: scale(0.95);
}
/* 商品标题优化 */
.goods-detail-page .goods-info .goods-title {
margin-top: 24rpx;
}
.goods-detail-page .goods-info .goods-title .goods-name {
font-size: 34rpx;
font-weight: 600;
color: #333333;
line-height: 1.5;
margin-bottom: 24rpx;
letter-spacing: 0.5rpx;
}
/* 规格选择区域优化 */
.spu-select {
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
margin: 24rpx 30rpx;
display: flex;
align-items: center;
padding: 28rpx 32rpx;
font-size: 28rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
border: 1rpx solid #f0f0f0;
transition: all 0.3s ease;
position: relative;
}
.spu-select::after {
content: '';
position: absolute;
right: 24rpx;
top: 50%;
transform: translateY(-50%);
width: 16rpx;
height: 16rpx;
border-right: 2rpx solid #999;
border-bottom: 2rpx solid #999;
transform: translateY(-50%) rotate(45deg);
}
.spu-select:active {
transform: scale(0.98);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.12);
}
.spu-select .label {
margin-right: 32rpx;
text-align: center;
flex-shrink: 0;
color: #666666;
font-weight: 600;
font-size: 28rpx;
}
.spu-select .content {
flex: 1;
color: #333333;
font-size: 28rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 评论区域优化 */
.goods-comment-wrap {
background: #fff;
margin: 24rpx 30rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.goods-comment-wrap .goods-comment-head {
padding: 32rpx;
border-bottom: 1rpx solid #f5f5f5;
background: linear-gradient(135deg, #fafbfc 0%, #ffffff 100%);
}
.goods-comment-wrap .goods-comment-head .goods-comment-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.goods-comment-wrap .goods-comment-head .goods-comment-info {
display: flex;
align-items: center;
gap: 24rpx;
}
.goods-comment-wrap .goods-comment-head .goods-comment-info .rating-info {
display: flex;
align-items: center;
gap: 8rpx;
}
.goods-comment-wrap .goods-comment-head .goods-comment-info .rating-score {
font-size: 28rpx;
font-weight: 600;
color: #fa4126;
}
.goods-comment-wrap .goods-comment-head .goods-comment-info .rating-count {
font-size: 24rpx;
color: #999999;
}
/* 评论列表优化 */
.goods-comment-wrap .goods-comment-list {
padding: 0 32rpx 32rpx;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item {
padding: 24rpx 0;
border-bottom: 1rpx solid #f8f9fa;
transition: all 0.3s ease;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item:last-child {
border-bottom: none;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item:active {
background: #fafbfc;
margin: 0 -32rpx;
padding: 24rpx 32rpx;
border-radius: 12rpx;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-user {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-user .user-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
margin-right: 16rpx;
border: 2rpx solid #f0f0f0;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-user .user-info {
flex: 1;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-user .user-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-bottom: 4rpx;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-user .comment-time {
font-size: 22rpx;
color: #999999;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-content {
font-size: 28rpx;
color: #666666;
line-height: 1.6;
margin-bottom: 16rpx;
}
.goods-comment-wrap .goods-comment-list .goods-comment-item .comment-rating {
display: flex;
align-items: center;
gap: 8rpx;
}
/* 商品详情介绍区域优化 */
.goods-detail-container {
background: #fff;
margin: 24rpx 30rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.goods-detail-container .goods-detail-title {
padding: 32rpx;
background: linear-gradient(135deg, #fafbfc 0%, #ffffff 100%);
border-bottom: 1rpx solid #f5f5f5;
font-size: 32rpx;
font-weight: 600;
color: #333333;
text-align: center;
}
.goods-detail-container .goods-detail-content {
padding: 32rpx;
}
.goods-detail-container .goods-detail-content .detail-image {
width: 100%;
border-radius: 12rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
/* 底部购买栏优化 */
.goods-bottom-operation {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.95) 20%, #ffffff 100%);
backdrop-filter: blur(20rpx);
padding: 20rpx 30rpx 40rpx;
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.08);
z-index: 100;
}
/* 响应式优化 */
@media (max-width: 375px) {
.goods-detail-page .goods-info .goods-price .class-goods-price {
font-size: 44rpx;
}
.goods-detail-page .goods-info .goods-title .goods-name {
font-size: 32rpx;
}
}
/* 加载动画 */
.loading-animation {
display: flex;
justify-content: center;
align-items: center;
padding: 60rpx;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f0f0f0;
border-top: 4rpx solid #fa4126;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 80rpx 40rpx;
color: #999999;
}
.empty-state .empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-state .empty-text {
font-size: 28rpx;
text-align: center;
line-height: 1.5;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price {
display: flex;
align-items: baseline;
color: #fa4126;
margin-top: 48rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-num {
font-size: 64rpx;
color: #fa4126;
font-weight: bold;
font-family: DIN Alternate;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-del {
position: relative;
font-weight: normal;
left: 12rpx;
bottom: 2rpx;
color: #999999;
font-size: 32rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-symbol {
font-size: 36rpx;
color: #fa4126;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-max-num {
font-size: 48rpx;
}
.goods-detail-page .goods-head {
--td-swiper-radius: 0;
}
.t-toast__content {
z-index: 12000 !important;
}
/* 评论入口样式 */
.comments-entry {
margin: 20rpx 30rpx 0 30rpx;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
border: 1rpx solid #f0f0f0;
overflow: hidden;
}
.comments-entry-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 28rpx;
transition: background-color 0.2s ease;
}
.comments-entry:active .comments-entry-content {
background-color: #f8f8f8;
}
.comments-entry-left {
flex: 1;
}
.comments-title {
display: flex;
align-items: baseline;
margin-bottom: 8rpx;
}
.comments-title-label {
color: #222222;
font-size: 30rpx;
font-weight: 600;
line-height: 1;
}
.comments-title-count {
color: #666666;
font-size: 26rpx;
font-weight: 400;
line-height: 1;
margin-left: 8rpx;
}
.comments-summary {
display: flex;
align-items: center;
gap: 16rpx;
}
.good-rate {
color: #fa4126;
font-size: 24rpx;
font-weight: 500;
background: #fff5f5;
padding: 4rpx 12rpx;
border-radius: 12rpx;
border: 1rpx solid #ffe5e5;
}
.view-all {
color: #666666;
font-size: 24rpx;
font-weight: 400;
}
.no-comments {
color: #999999;
font-size: 24rpx;
font-weight: 400;
}
.comments-entry-right {
margin-left: 20rpx;
}
/* 商品详细介绍样式优化 */
.desc-content {
background: #fff;
margin: 24rpx 30rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
border: 1rpx solid #f0f0f0;
}
.desc-content__title {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 32rpx;
background: linear-gradient(135deg, #fafbfc 0%, #ffffff 100%);
border-bottom: 1rpx solid #f5f5f5;
position: relative;
}
.desc-content__title::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: linear-gradient(135deg, #fa4126 0%, #ff6b4a 100%);
border-radius: 2rpx;
}
.desc-content__title--text {
font-size: 32rpx;
font-weight: 600;
color: #333333;
letter-spacing: 1rpx;
}
.desc-content__title .img {
width: 32rpx;
height: 32rpx;
opacity: 0.6;
}
/* 基本信息样式 */
.desc-basic-info {
padding: 32rpx;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-bottom: 1rpx solid #f5f5f5;
}
.desc-info-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding: 16rpx 20rpx;
background: #fff;
border-radius: 12rpx;
border: 1rpx solid #f0f0f0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.desc-info-item:last-child {
margin-bottom: 0;
}
.desc-info-label {
font-size: 28rpx;
color: #666666;
font-weight: 500;
min-width: 80rpx;
}
.desc-info-value {
font-size: 28rpx;
color: #333333;
font-weight: 600;
flex: 1;
}
/* 文本内容样式 */
.desc-content__text,
.desc-details {
padding: 32rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.desc-text-title {
font-size: 30rpx;
font-weight: 600;
color: #333333;
margin-bottom: 20rpx;
padding-bottom: 12rpx;
border-bottom: 2rpx solid #fa4126;
display: inline-block;
position: relative;
}
.desc-text-title::after {
content: '';
position: absolute;
bottom: -2rpx;
left: 0;
width: 100%;
height: 2rpx;
background: linear-gradient(135deg, #fa4126 0%, #ff6b4a 100%);
border-radius: 1rpx;
}
.desc-text-content {
font-size: 28rpx;
color: #666666;
line-height: 1.8;
text-align: justify;
background: #fafbfc;
padding: 24rpx;
border-radius: 12rpx;
border: 1rpx solid #f0f0f0;
margin-top: 16rpx;
}
/* 图片展示样式 */
.desc-images {
padding: 32rpx;
}
.desc-image-container {
margin-bottom: 24rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
position: relative;
}
.desc-image-container:last-child {
margin-bottom: 0;
}
.desc-image-container:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
}
.desc-image-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(250, 65, 38, 0.1) 0%, rgba(255, 107, 74, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s ease;
z-index: 1;
pointer-events: none;
}
.desc-image-container:active::before {
opacity: 1;
}
.desc-content__img {
width: 100%;
display: block;
border-radius: 16rpx;
transition: all 0.3s ease;
}
/* 空状态样式 */
.desc-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 32rpx;
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%);
}
.desc-empty-text {
font-size: 28rpx;
color: #999999;
margin-top: 16rpx;
font-weight: 400;
}
/* 响应式优化 */
@media (max-width: 375px) {
.desc-content {
margin: 20rpx 24rpx;
}
.desc-content__title {
padding: 28rpx 24rpx;
}
.desc-basic-info,
.desc-content__text,
.desc-details,
.desc-images {
padding: 28rpx 24rpx;
}
.desc-content__title--text {
font-size: 30rpx;
}
}