init
This commit is contained in:
BIN
miniprogram/pages/goods/.DS_Store
vendored
Normal file
BIN
miniprogram/pages/goods/.DS_Store
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,36 @@
|
||||
// pages/goods/comments/components/comments-card/images-videos/index.js
|
||||
Component({
|
||||
/**
|
||||
* 组件的属性列表
|
||||
*/
|
||||
properties: {
|
||||
resources: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 组件的初始数据
|
||||
*/
|
||||
data: {
|
||||
classType: 'single',
|
||||
},
|
||||
|
||||
observers: {
|
||||
resources: function (newVal) {
|
||||
if (newVal.length <= 1) {
|
||||
this.setData({ classType: 'single' });
|
||||
} else if (newVal.length === 2) {
|
||||
this.setData({ classType: 'double' });
|
||||
} else {
|
||||
this.setData({ classType: 'multiple' });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 组件的方法列表
|
||||
*/
|
||||
methods: {},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"my-video": "../my-video/index",
|
||||
"t-image": "/components/webp-image/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<view class="images-videos-container container-{{classType}}">
|
||||
<view
|
||||
class="resource-container resource-container-{{classType}}"
|
||||
wx:for="{{resources}}"
|
||||
wx:for-item="resource"
|
||||
wx:key="*this"
|
||||
>
|
||||
<t-image wx:if="{{resource.type === 'image'}}" t-class="resource-item-{{classType}}" src="{{resource.src}}" />
|
||||
<my-video wx:else videoSrc="{{resource.src}} " my-video="resource-item-{{classType}}">
|
||||
<t-image t-class="resource-item resource-item-{{classType}}" slot="cover-img" src="{{resource.coverSrc}}" />
|
||||
<image class="play-icon" slot="play-icon" src="./assets/play.png" />
|
||||
</my-video>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
.resource-item-single {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.resource-item-double {
|
||||
width: 334rpx;
|
||||
height: 334rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.resource-item-multiple {
|
||||
width: 218rpx;
|
||||
height: 218rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.resource-container-single {
|
||||
padding-left: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.resource-container-double {
|
||||
padding-left: 18rpx;
|
||||
padding-top: 18rpx;
|
||||
}
|
||||
|
||||
.resource-container-multiple {
|
||||
padding-left: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.container-single {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.container-double {
|
||||
margin-left: -18rpx;
|
||||
margin-top: -18rpx;
|
||||
}
|
||||
|
||||
.container-multiple {
|
||||
margin-left: -16rpx;
|
||||
margin-top: -16rpx;
|
||||
}
|
||||
|
||||
.resource-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.images-videos-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.image {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.cover-img-container {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
Component({
|
||||
externalClasses: ['my-video', 'my-cover-img', 'my-play-icon'],
|
||||
properties: {
|
||||
videoSrc: { type: String },
|
||||
},
|
||||
data: {
|
||||
isShow: true,
|
||||
},
|
||||
|
||||
options: {
|
||||
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
|
||||
},
|
||||
|
||||
attached() {
|
||||
this.videoContext = wx.createVideoContext('myVideo', this);
|
||||
},
|
||||
|
||||
fullScreen: false,
|
||||
|
||||
methods: {
|
||||
// 点击封面自定义播放按钮时触发
|
||||
bindplay(e) {
|
||||
this.setData({
|
||||
isShow: false,
|
||||
});
|
||||
this.videoContext.play();
|
||||
this.triggerEvent('play', e);
|
||||
},
|
||||
|
||||
bindplayByVideo(e) {
|
||||
this.setData({
|
||||
isShow: false,
|
||||
});
|
||||
this.triggerEvent('play', e);
|
||||
},
|
||||
|
||||
// 监听播放到末尾时触发
|
||||
bindended(e) {
|
||||
if (!this.fullScreen) {
|
||||
this.setData({
|
||||
isShow: true,
|
||||
});
|
||||
}
|
||||
this.triggerEvent('ended', e);
|
||||
},
|
||||
// 监听暂停播放时触发
|
||||
bindpause(e) {
|
||||
this.triggerEvent('pause', e);
|
||||
},
|
||||
bindfullscreenchange(e) {
|
||||
const fullScreen = e?.detail?.fullScreen;
|
||||
this.fullScreen = fullScreen;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<video
|
||||
id="myVideo"
|
||||
src="{{videoSrc}}"
|
||||
enable-danmu
|
||||
controls
|
||||
show-fullscreen-btn
|
||||
show-center-play-btn="{{false}}"
|
||||
auto-pause-if-navigate
|
||||
auto-pause-if-open-native
|
||||
show-play-btn
|
||||
object-fit="contain"
|
||||
bindpause="bindpause"
|
||||
bindended="bindended"
|
||||
bindplay="bindplayByVideo"
|
||||
class="video my-video"
|
||||
bindfullscreenchange="bindfullscreenchange"
|
||||
>
|
||||
<view class="video_cover" wx:if="{{isShow}}">
|
||||
<view class="my-cover-img">
|
||||
<slot name="cover-img" />
|
||||
</view>
|
||||
<view class="video_play_icon my-play-icon" bindtap="bindplay">
|
||||
<slot name="play-icon" />
|
||||
</view>
|
||||
</view>
|
||||
</video>
|
||||
@@ -0,0 +1,21 @@
|
||||
.video .video_cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video .video_play_icon {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.video .video_txt {
|
||||
margin: 10rpx auto;
|
||||
}
|
||||
|
||||
.video {
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
options: {
|
||||
multipleSlots: true,
|
||||
},
|
||||
properties: {
|
||||
goodsDetailInfo: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
sellerReply: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
userHeadUrl: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
userName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
commentContent: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
commentScore: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
commentTime: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
commentResources: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
showMoreStatus: false,
|
||||
showContent: false,
|
||||
hideText: false,
|
||||
eleHeight: null,
|
||||
overText: false,
|
||||
isDisabled: true,
|
||||
startColors: ['#FFC51C', '#DDDDDD'],
|
||||
},
|
||||
methods: {},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-rate": "tdesign-miniprogram/rate/rate",
|
||||
"images-videos": "./components/images-videos",
|
||||
"t-image": "/components/webp-image/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<view class="comments-card-item wr-class">
|
||||
<view class="comments-card-item-container">
|
||||
<view class="comments-title">
|
||||
<view class="comments-card-item-userImg">
|
||||
<t-image t-class="userImg" src="{{userHeadUrl}}" />
|
||||
</view>
|
||||
<view class="userName">{{userName}}</view>
|
||||
<text class="commentTime">{{commentTime}}</text>
|
||||
</view>
|
||||
<view class="comments-info">
|
||||
<view class="rate">
|
||||
<t-rate value="{{commentScore}}" size="14" gap="2" color="{{['#ffc51c', '#ddd']}}" />
|
||||
</view>
|
||||
<view class="goods-info-text" wx:if="{{goodsDetailInfo}}">{{goodsDetailInfo}}</view>
|
||||
</view>
|
||||
<view class="comments-card-item-container-content">
|
||||
<view class="content-text" hidden="{{showContent}}"> {{commentContent}} </view>
|
||||
</view>
|
||||
<view class="comments-card-item-container-image" wx:if="{{commentResources.length > 0}}">
|
||||
<images-videos resources="{{commentResources}}" />
|
||||
</view>
|
||||
<view class="comments-card-reply" wx:if="{{sellerReply}}">
|
||||
<text class="prefix">店家回复:</text>
|
||||
<text class="content">{{sellerReply}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,172 @@
|
||||
@import '../../../../../style/theme.wxss';
|
||||
|
||||
.comments-card-item {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comments-card-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
width: 686rpx;
|
||||
height: 2rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.comments-card-item-userImg {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.comments-card-item-userImg .userImg {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.comments-card-item-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comments-card-item-container-name {
|
||||
display: flex;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comments-card-item-container-name .userName {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container-date {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.comments-card-item-container-content {
|
||||
margin-top: 16rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comments-card-item-container-content .content-text {
|
||||
font-size: 28rpx;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.comments-card-item-container-content .hide-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 5;
|
||||
text-align: justify;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.comments-card-item-container-content .showMore {
|
||||
position: absolute;
|
||||
width: 112rpx;
|
||||
height: 36rpx;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0.2) 0,
|
||||
rgba(255, 255, 255, 0.45) 20%,
|
||||
rgba(255, 255, 255, 0.7) 25%,
|
||||
rgba(255, 255, 255, 0.9) 30%,
|
||||
rgba(255, 255, 255, 0.95) 35%,
|
||||
#ffffff 50%,
|
||||
#fff 100%
|
||||
);
|
||||
font-size: 26rpx;
|
||||
color: #fa550f;
|
||||
line-height: 36rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.comments-card-item-container-image {
|
||||
margin-top: 24rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.comments-card-item-container-image .commentImg {
|
||||
border-radius: 8rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container-image .commentImg3 {
|
||||
width: 196rpx;
|
||||
height: 196rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container-image .commentImg2 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container-image .commentImg1 {
|
||||
width: 404rpx;
|
||||
height: 404rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container .comments-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comments-title .userName {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.comments-title .commentTime {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.comments-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.comments-info .rate {
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.comments-info .goods-info-text {
|
||||
font-size: 24rpx;
|
||||
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.comments-card-item-container .comments-card-reply {
|
||||
background-color: #f5f5f5;
|
||||
padding: 24rpx 16rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.comments-card-item-container .comments-card-reply .prefix {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.comments-card-item-container .comments-card-reply .content {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
222
miniprogram/pages/goods/comments/create/index.js
Normal file
222
miniprogram/pages/goods/comments/create/index.js
Normal file
@@ -0,0 +1,222 @@
|
||||
import { createComment } from '../../../../services/comments/createComment';
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
serviceRateValue: 5,
|
||||
goodRateValue: 5,
|
||||
conveyRateValue: 5,
|
||||
isAnonymous: false,
|
||||
uploadFiles: [],
|
||||
images: [],
|
||||
gridConfig: {
|
||||
width: 218,
|
||||
height: 218,
|
||||
column: 3,
|
||||
},
|
||||
isAllowedSubmit: false,
|
||||
submitting: false,
|
||||
imgUrl: '',
|
||||
title: '',
|
||||
goodsDetail: '',
|
||||
orderNo: '',
|
||||
goodsId: '',
|
||||
maxImages: 9,
|
||||
imageProps: {
|
||||
mode: 'aspectFit',
|
||||
},
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
console.log('评价页面加载参数:', options);
|
||||
this.setData({
|
||||
imgUrl: options.imgUrl ? decodeURIComponent(options.imgUrl) : '',
|
||||
title: options.title ? decodeURIComponent(options.title) : '',
|
||||
goodsDetail: options.specs ? decodeURIComponent(options.specs) : '',
|
||||
orderNo: options.orderNo || '',
|
||||
goodsId: options.goodsId || '',
|
||||
});
|
||||
this.updateButtonStatus();
|
||||
},
|
||||
|
||||
onRateChange(e) {
|
||||
const { value } = e?.detail;
|
||||
const item = e?.currentTarget?.dataset?.item;
|
||||
this.setData({ [item]: value }, () => {
|
||||
this.updateButtonStatus();
|
||||
});
|
||||
},
|
||||
|
||||
onAnonymousChange(e) {
|
||||
const status = !!e?.detail?.checked;
|
||||
this.setData({ isAnonymous: status });
|
||||
},
|
||||
|
||||
|
||||
|
||||
handleSuccess(e) {
|
||||
// 兼容原有的上传组件
|
||||
const { files } = e.detail;
|
||||
const images = files.map(file => file.url);
|
||||
this.setData({
|
||||
uploadFiles: files,
|
||||
images
|
||||
});
|
||||
},
|
||||
|
||||
handleRemove(e) {
|
||||
const { index } = e.detail;
|
||||
const { uploadFiles, images } = this.data;
|
||||
uploadFiles.splice(index, 1);
|
||||
images.splice(index, 1);
|
||||
this.setData({
|
||||
uploadFiles,
|
||||
images
|
||||
});
|
||||
},
|
||||
|
||||
// 上传图片到服务器
|
||||
async uploadImages(images) {
|
||||
if (!images || images.length === 0) return [];
|
||||
|
||||
const uploadPromises = images.map(imagePath => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const uploadUrl = `${getApp().globalData.config.apiBaseUrl}/upload/image`;
|
||||
const token = wx.getStorageSync('token');
|
||||
console.log('上传URL:', uploadUrl);
|
||||
console.log('Token:', token);
|
||||
|
||||
wx.uploadFile({
|
||||
url: uploadUrl,
|
||||
filePath: imagePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 200) {
|
||||
resolve(data.data.url);
|
||||
} else {
|
||||
reject(new Error(data.message || '上传失败'));
|
||||
}
|
||||
} catch (e) {
|
||||
reject(new Error('解析上传结果失败'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(uploadPromises);
|
||||
},
|
||||
|
||||
onTextAreaChange(e) {
|
||||
const value = e?.detail?.value;
|
||||
this.textAreaValue = value;
|
||||
this.updateButtonStatus();
|
||||
},
|
||||
|
||||
updateButtonStatus() {
|
||||
const { serviceRateValue, goodRateValue, conveyRateValue, submitting } = this.data;
|
||||
const { textAreaValue } = this;
|
||||
const temp = serviceRateValue && goodRateValue && conveyRateValue && textAreaValue && !submitting;
|
||||
this.setData({ isAllowedSubmit: temp });
|
||||
},
|
||||
|
||||
async onSubmitBtnClick() {
|
||||
const {
|
||||
isAllowedSubmit,
|
||||
submitting,
|
||||
serviceRateValue,
|
||||
goodRateValue,
|
||||
conveyRateValue,
|
||||
isAnonymous,
|
||||
images,
|
||||
orderNo,
|
||||
goodsId
|
||||
} = this.data;
|
||||
|
||||
if (!isAllowedSubmit || submitting) return;
|
||||
|
||||
const content = this.textAreaValue;
|
||||
if (!content || !content.trim()) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '请输入评价内容',
|
||||
icon: 'error-circle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ submitting: true });
|
||||
this.updateButtonStatus();
|
||||
|
||||
try {
|
||||
// 上传图片
|
||||
let uploadedImages = [];
|
||||
if (images.length > 0) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '正在上传图片...',
|
||||
icon: 'loading',
|
||||
});
|
||||
uploadedImages = await this.uploadImages(images);
|
||||
}
|
||||
|
||||
// 计算综合评分(取三个评分的平均值)
|
||||
const averageRating = Math.round((serviceRateValue + goodRateValue + conveyRateValue) / 3);
|
||||
|
||||
// 提交评论
|
||||
const commentData = {
|
||||
orderNo: orderNo,
|
||||
productId: goodsId,
|
||||
rating: averageRating,
|
||||
content: content.trim(),
|
||||
images: uploadedImages,
|
||||
isAnonymous: isAnonymous
|
||||
};
|
||||
|
||||
console.log('提交评论数据:', commentData);
|
||||
|
||||
await createComment(commentData);
|
||||
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '评价提交成功',
|
||||
icon: 'check-circle',
|
||||
});
|
||||
|
||||
// 设置上一页刷新标志
|
||||
const pages = getCurrentPages();
|
||||
const prevPage = pages[pages.length - 2]; // 上一页(订单详情页)
|
||||
if (prevPage) {
|
||||
prevPage.setData({ backRefresh: true });
|
||||
}
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
wx.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交评价失败:', error);
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: error.message || '提交失败,请重试',
|
||||
icon: 'error-circle',
|
||||
});
|
||||
} finally {
|
||||
this.setData({ submitting: false });
|
||||
this.updateButtonStatus();
|
||||
}
|
||||
},
|
||||
});
|
||||
13
miniprogram/pages/goods/comments/create/index.json
Normal file
13
miniprogram/pages/goods/comments/create/index.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"navigationBarTitleText": "评价商品",
|
||||
"usingComponents": {
|
||||
"t-image": "/components/webp-image/index",
|
||||
"t-rate": "tdesign-miniprogram/rate/rate",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea",
|
||||
"t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-upload": "tdesign-miniprogram/upload/upload",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-toast": "tdesign-miniprogram/toast/toast"
|
||||
}
|
||||
}
|
||||
93
miniprogram/pages/goods/comments/create/index.wxml
Normal file
93
miniprogram/pages/goods/comments/create/index.wxml
Normal file
@@ -0,0 +1,93 @@
|
||||
<view class="page-container">
|
||||
<view class="comment-card">
|
||||
<view class="goods-info-container">
|
||||
<view class="goods-image-container">
|
||||
<t-image t-class="goods-image" src="{{imgUrl}}" />
|
||||
</view>
|
||||
<view class="goods-title-container">
|
||||
<view class="goods-title">{{title}}</view>
|
||||
<view class="goods-detail">{{goodsDetail}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rate-container">
|
||||
<text class="rate-title">商品评价</text>
|
||||
<view class="rate">
|
||||
<t-rate
|
||||
value="{{goodRateValue}}"
|
||||
bind:change="onRateChange"
|
||||
size="26"
|
||||
gap="6"
|
||||
color="{{['#ffc51c', '#ddd']}}"
|
||||
data-item="goodRateValue"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="textarea-container">
|
||||
<t-textarea
|
||||
t-class="textarea"
|
||||
maxlength="{{500}}"
|
||||
indicator
|
||||
placeholder="对商品满意吗?评论一下"
|
||||
bind:change="onTextAreaChange"
|
||||
/>
|
||||
</view>
|
||||
<view class="upload-container">
|
||||
<t-upload
|
||||
media-type="{{['image']}}"
|
||||
files="{{uploadFiles}}"
|
||||
bind:remove="handleRemove"
|
||||
bind:success="handleSuccess"
|
||||
gridConfig="{{gridConfig}}"
|
||||
imageProps="{{imageProps}}"
|
||||
max="{{maxImages}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="anonymous-box">
|
||||
<t-checkbox bind:change="onAnonymousChange" checked="{{isAnonymous}}" color="#FA4126" />
|
||||
<view class="name">匿名评价</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="comment-card convey-card">
|
||||
<view class="convey-comment-title">物流服务评价</view>
|
||||
<view class="rate-container">
|
||||
<text class="rate-title">物流评价</text>
|
||||
<view class="rate">
|
||||
<t-rate
|
||||
value="{{conveyRateValue}}"
|
||||
bind:change="onRateChange"
|
||||
variant="filled"
|
||||
size="26"
|
||||
gap="6"
|
||||
color="{{['#ffc51c', '#ddd']}}"
|
||||
data-item="conveyRateValue"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rate-container">
|
||||
<text class="rate-title">服务评价</text>
|
||||
<view class="rate">
|
||||
<t-rate
|
||||
value="{{serviceRateValue}}"
|
||||
bind:change="onRateChange"
|
||||
size="26"
|
||||
gap="6"
|
||||
color="{{['#ffc51c', '#ddd']}}"
|
||||
data-item="serviceRateValue"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-button-container">
|
||||
<t-button
|
||||
content="{{submitting ? '提交中...' : '提交'}}"
|
||||
block
|
||||
shape="round"
|
||||
loading="{{submitting}}"
|
||||
disabled="{{!isAllowedSubmit || submitting}}"
|
||||
t-class="submit-button{{isAllowedSubmit && !submitting ? '' : '-disabled'}}"
|
||||
bind:tap="onSubmitBtnClick"
|
||||
/>
|
||||
</view>
|
||||
<t-toast id="t-toast" />
|
||||
168
miniprogram/pages/goods/comments/create/index.wxss
Normal file
168
miniprogram/pages/goods/comments/create/index.wxss
Normal file
@@ -0,0 +1,168 @@
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-container .comment-card {
|
||||
padding: 24rpx 32rpx 28rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.comment-card .goods-info-container .goods-image {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.comment-card .goods-info-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comment-card .goods-info-container .goods-title-container {
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
|
||||
.comment-card .goods-info-container .goods-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.comment-card .goods-info-container .goods-detail {
|
||||
font-size: 24rpx;
|
||||
font-weight: normal;
|
||||
color: #999999;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.comment-card .rate-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.comment-card .rate-container .rate-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.comment-card .textarea-container {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.comment-card .textarea-container .textarea {
|
||||
height: 294rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.page-container .t-checkbox__bordered {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container .anonymous-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 52rpx;
|
||||
}
|
||||
|
||||
.page-container .anonymous-box .name {
|
||||
font-size: 28rpx;
|
||||
font-weight: normal;
|
||||
color: #999999;
|
||||
padding-left: 28rpx;
|
||||
}
|
||||
|
||||
.page-container .t-checkbox {
|
||||
padding: 0rpx !important;
|
||||
}
|
||||
|
||||
.page-container .t-checkbox__content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment-card .convey-comment-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.convey-card {
|
||||
background-color: #ffffff;
|
||||
margin-top: 24rpx;
|
||||
padding: 32rpx;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
|
||||
}
|
||||
|
||||
.convey-card .rate-container .rate-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.page-container .t-checkbox__icon-left {
|
||||
margin-right: 0rpx !important;
|
||||
}
|
||||
|
||||
.submit-button-container {
|
||||
padding: 12rpx 32rpx;
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
|
||||
background-color: #ffffff;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.submit-button-container .submit-button {
|
||||
--td-button-default-color: #fff;
|
||||
--td-button-default-bg-color: #fa4126;
|
||||
--td-button-default-border-color: #fa4126;
|
||||
--td-button-default-active-bg-color: #fa42269c;
|
||||
}
|
||||
|
||||
.submit-button-container .submit-button-disabled {
|
||||
--td-button-default-color: #fff;
|
||||
--td-button-default-bg-color: #ccc;
|
||||
--td-button-default-border-color: #ccc;
|
||||
--td-button-default-active-bg-color: rgba(204, 204, 204, 0.789);
|
||||
}
|
||||
|
||||
.page-container .upload-container {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.page-container .t-upload__wrapper {
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-container .submmit-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 12;
|
||||
padding: 12rpx 32rpx;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background-color: #fff;
|
||||
height: 112rpx;
|
||||
}
|
||||
|
||||
.page-container .submmit-bar-button {
|
||||
border-radius: 48rpx !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.page-container .t-upload__close-btn {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-bottom-left-radius: 8rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.upload-container .upload-addcontent-slot {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
394
miniprogram/pages/goods/comments/index.js
Normal file
394
miniprogram/pages/goods/comments/index.js
Normal file
@@ -0,0 +1,394 @@
|
||||
import { fetchComments } from '../../../services/comments/fetchComments';
|
||||
import { fetchCommentsCount } from '../../../services/comments/fetchCommentsCount';
|
||||
import { fetchUserComments } from '../../../services/comments/createComment';
|
||||
import dayjs from 'dayjs';
|
||||
const layoutMap = {
|
||||
0: 'vertical',
|
||||
};
|
||||
Page({
|
||||
data: {
|
||||
pageLoading: false,
|
||||
commentList: [],
|
||||
pageNum: 1,
|
||||
myPageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
myTotal: 0,
|
||||
hasLoaded: false,
|
||||
layoutText: layoutMap[0],
|
||||
loadMoreStatus: 0,
|
||||
myLoadStatus: 0,
|
||||
spuId: '1060004',
|
||||
commentLevel: '',
|
||||
hasImage: '',
|
||||
commentType: '',
|
||||
totalCount: 0,
|
||||
countObj: {
|
||||
badCount: '0',
|
||||
commentCount: '0',
|
||||
goodCount: '0',
|
||||
middleCount: '0',
|
||||
hasImageCount: '0',
|
||||
uidCount: '0',
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
console.log('[评论页面] onLoad 接收到的参数:', options);
|
||||
const { productId, spuId } = options;
|
||||
// 兼容新旧参数名
|
||||
const id = productId || spuId;
|
||||
console.log('[评论页面] 最终使用的spuId:', id);
|
||||
|
||||
if (id) {
|
||||
this.setData({ spuId: id });
|
||||
|
||||
console.log('[评论页面] 开始获取评论统计...');
|
||||
this.getCount({ spuId: id });
|
||||
|
||||
console.log('[评论页面] 开始获取评论列表...');
|
||||
this.getComments({ spuId: id });
|
||||
}
|
||||
},
|
||||
async getCount(options) {
|
||||
const { spuId } = options;
|
||||
console.log('[评论页面] getCount 开始,spuId:', spuId, '类型:', typeof spuId);
|
||||
|
||||
try {
|
||||
// 直接传递spuId作为productId参数
|
||||
const result = await fetchCommentsCount(spuId);
|
||||
console.log('[评论页面] fetchCommentsCount 返回数据:', result);
|
||||
|
||||
// 尝试获取用户自己的评论数
|
||||
try {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (token) {
|
||||
const userCommentsData = await fetchUserComments({
|
||||
page: 1,
|
||||
pageSize: 1
|
||||
});
|
||||
if (userCommentsData && userCommentsData.total !== undefined) {
|
||||
result.uidCount = String(userCommentsData.total);
|
||||
console.log('[评论页面] 获取到用户评论数:', userCommentsData.total);
|
||||
}
|
||||
}
|
||||
} catch (userError) {
|
||||
console.log('[评论页面] 获取用户评论数失败,使用默认值0:', userError.message);
|
||||
// 保持默认值0,不影响主要功能
|
||||
}
|
||||
|
||||
console.log('[评论页面] 设置评论统计数据:', result);
|
||||
this.setData({
|
||||
countObj: result,
|
||||
});
|
||||
// const { data, code = '' } = result;
|
||||
// if (code.toUpperCase() === 'SUCCESS') {
|
||||
// wx.setNavigationBarTitle({
|
||||
// title: `全部评价(${data.commentCount})`,
|
||||
// });
|
||||
// this.setData({
|
||||
// countObj: data,
|
||||
// });
|
||||
// } else {
|
||||
// wx.showToast({
|
||||
// title: '查询失败,请稍候重试',
|
||||
// });
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('[评论页面] 获取评论统计失败:', error);
|
||||
wx.showToast({
|
||||
title: '获取评论失败,请稍候重试',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({
|
||||
loadMoreStatus: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
generalQueryData(reset) {
|
||||
const { hasImage, pageNum, pageSize, spuId, commentLevel } = this.data;
|
||||
const params = {
|
||||
pageNum: 1,
|
||||
pageSize: 30,
|
||||
queryParameter: {
|
||||
spuId,
|
||||
},
|
||||
};
|
||||
if (
|
||||
Number(commentLevel) === 3 ||
|
||||
Number(commentLevel) === 2 ||
|
||||
Number(commentLevel) === 1
|
||||
) {
|
||||
params.queryParameter.commentLevel = Number(commentLevel);
|
||||
}
|
||||
if (hasImage && hasImage === '1') {
|
||||
params.queryParameter.hasImage = true;
|
||||
} else {
|
||||
delete params.queryParameter.hasImage;
|
||||
}
|
||||
// 重置请求
|
||||
if (reset) return params;
|
||||
|
||||
return {
|
||||
...params,
|
||||
pageNum: pageNum + 1,
|
||||
pageSize,
|
||||
};
|
||||
},
|
||||
async init(reset = true) {
|
||||
console.log('[评论页面] init 开始,reset:', reset);
|
||||
const { loadMoreStatus, commentList = [], spuId, hasImage, commentLevel } = this.data;
|
||||
console.log('[评论页面] 当前页面数据状态:', { loadMoreStatus, commentListLength: commentList.length, spuId, hasImage, commentLevel });
|
||||
|
||||
// 在加载中或者无更多数据,直接返回
|
||||
if (loadMoreStatus !== 0) {
|
||||
console.log('[评论页面] 跳过加载,loadMoreStatus:', loadMoreStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[评论页面] 设置加载状态为1');
|
||||
this.setData({
|
||||
loadMoreStatus: 1,
|
||||
});
|
||||
|
||||
// 构造正确的API参数格式
|
||||
const apiParams = {
|
||||
productId: spuId,
|
||||
page: reset ? 1 : this.data.pageNum + 1,
|
||||
pageSize: this.data.pageSize,
|
||||
};
|
||||
|
||||
// 添加评分筛选
|
||||
if (commentLevel && (commentLevel === '1' || commentLevel === '2' || commentLevel === '3')) {
|
||||
apiParams.rating = Number(commentLevel);
|
||||
}
|
||||
|
||||
// 添加图片筛选
|
||||
if (hasImage === '1') {
|
||||
apiParams.hasImages = true;
|
||||
}
|
||||
|
||||
console.log('[评论页面] API调用参数:', apiParams);
|
||||
|
||||
try {
|
||||
console.log('[评论页面] 开始调用fetchComments API...');
|
||||
const data = await fetchComments(apiParams);
|
||||
console.log('[评论页面] fetchComments API返回数据:', data);
|
||||
if (data && data.comments) {
|
||||
const { comments, totalCount = 0 } = data;
|
||||
|
||||
// 转换数据格式以匹配页面期望的结构
|
||||
const pageList = comments.map((item) => ({
|
||||
...item,
|
||||
commentTime: dayjs(item.created_at).format('YYYY/MM/DD HH:mm'),
|
||||
userName: item.user_name,
|
||||
userHeadUrl: item.user_avatar,
|
||||
commentScore: item.rating,
|
||||
commentContent: item.content,
|
||||
commentImageList: item.images || [],
|
||||
commentResources: (item.images || []).map(imageUrl => ({
|
||||
src: imageUrl,
|
||||
type: 'image'
|
||||
})),
|
||||
isAnonymous: item.is_anonymous,
|
||||
replyContent: item.reply_content,
|
||||
specValue: item.product_spec
|
||||
}));
|
||||
|
||||
if (Number(totalCount) === 0 && reset) {
|
||||
this.setData({
|
||||
commentList: [],
|
||||
hasLoaded: true,
|
||||
total: totalCount,
|
||||
loadMoreStatus: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const _commentList = reset ? pageList : commentList.concat(pageList);
|
||||
const _loadMoreStatus =
|
||||
_commentList.length >= Number(totalCount) ? 2 : 0;
|
||||
this.setData({
|
||||
commentList: _commentList,
|
||||
pageNum: data.pageNum || 1,
|
||||
totalCount: Number(totalCount),
|
||||
loadMoreStatus: _loadMoreStatus,
|
||||
});
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '查询失败,请稍候重试',
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
this.setData({
|
||||
hasLoaded: true,
|
||||
});
|
||||
},
|
||||
getScoreArray(score) {
|
||||
var array = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i < score) {
|
||||
array.push(2);
|
||||
} else {
|
||||
array.push(0);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
},
|
||||
getComments(options) {
|
||||
console.log('[评论页面] getComments 开始,参数:', options);
|
||||
|
||||
const { commentLevel = -1, spuId, hasImage = '' } = options;
|
||||
if (commentLevel !== -1) {
|
||||
this.setData({
|
||||
commentLevel: commentLevel,
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
hasImage: hasImage,
|
||||
commentType: hasImage ? '4' : '',
|
||||
spuId: spuId,
|
||||
};
|
||||
|
||||
console.log('[评论页面] 设置评论筛选条件:', updateData);
|
||||
this.setData(updateData);
|
||||
|
||||
console.log('[评论页面] 开始初始化评论列表...');
|
||||
this.init(true);
|
||||
},
|
||||
|
||||
// 获取我的评论列表
|
||||
async getMyCommentsList() {
|
||||
console.log('[评论页面] getMyCommentsList 开始');
|
||||
const { myLoadStatus, myPageNum, pageSize } = this.data;
|
||||
|
||||
// 在加载中,直接返回
|
||||
if (myLoadStatus !== 0) {
|
||||
console.log('[评论页面] 跳过加载,myLoadStatus:', myLoadStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[评论页面] 设置我的评论加载状态为1');
|
||||
this.setData({
|
||||
myLoadStatus: 1,
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('[评论页面] 开始调用fetchUserComments API...');
|
||||
const data = await fetchUserComments({
|
||||
page: myPageNum,
|
||||
pageSize: pageSize
|
||||
});
|
||||
console.log('[评论页面] fetchUserComments API返回数据:', data);
|
||||
|
||||
if (data && data.list) {
|
||||
const { list, total = 0 } = data;
|
||||
|
||||
// 转换数据格式以匹配页面期望的结构
|
||||
const pageList = list.map((item) => ({
|
||||
...item,
|
||||
commentTime: dayjs(item.created_at).format('YYYY/MM/DD HH:mm'),
|
||||
userName: item.user_name || '我',
|
||||
userHeadUrl: item.user_avatar || 'https://tdesign.gtimg.com/miniprogram/template/retail/avatar/avatar1.png',
|
||||
commentScore: item.rating,
|
||||
commentContent: item.content,
|
||||
commentImageList: item.images || [],
|
||||
commentResources: (item.images || []).map(imageUrl => ({
|
||||
src: imageUrl,
|
||||
type: 'image'
|
||||
})),
|
||||
isAnonymous: item.is_anonymous,
|
||||
replyContent: item.reply_content,
|
||||
specValue: item.product_spec
|
||||
}));
|
||||
|
||||
if (Number(total) === 0) {
|
||||
this.setData({
|
||||
commentList: [],
|
||||
myTotal: total,
|
||||
myLoadStatus: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const _commentList = myPageNum === 1 ? pageList : this.data.commentList.concat(pageList);
|
||||
const _myLoadStatus = _commentList.length >= Number(total) ? 2 : 0;
|
||||
|
||||
this.setData({
|
||||
commentList: _commentList,
|
||||
myPageNum: myPageNum + 1,
|
||||
myTotal: Number(total),
|
||||
myLoadStatus: _myLoadStatus,
|
||||
});
|
||||
} else {
|
||||
console.log('[评论页面] fetchUserComments API返回空结果或无list字段');
|
||||
this.setData({
|
||||
commentList: [],
|
||||
myLoadStatus: 2,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[评论页面] 获取我的评论列表失败:', error);
|
||||
wx.showToast({
|
||||
title: '获取我的评论失败',
|
||||
icon: 'none'
|
||||
});
|
||||
this.setData({
|
||||
myLoadStatus: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
changeTag(e) {
|
||||
var { commenttype } = e.currentTarget.dataset;
|
||||
var { commentType } = this.data;
|
||||
if (commentType === commenttype) return;
|
||||
this.setData({
|
||||
loadMoreStatus: 0,
|
||||
commentList: [],
|
||||
total: 0,
|
||||
myTotal: 0,
|
||||
myPageNum: 1,
|
||||
pageNum: 1,
|
||||
});
|
||||
if (commenttype === '' || commenttype === '5') {
|
||||
this.setData({
|
||||
hasImage: '',
|
||||
commentLevel: '',
|
||||
});
|
||||
} else if (commenttype === '4') {
|
||||
this.setData({
|
||||
hasImage: '1',
|
||||
commentLevel: '',
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
hasImage: '',
|
||||
commentLevel: commenttype,
|
||||
});
|
||||
}
|
||||
if (commenttype === '5') {
|
||||
this.setData({
|
||||
myLoadStatus: 1,
|
||||
commentType: commenttype,
|
||||
});
|
||||
this.getMyCommentsList();
|
||||
} else {
|
||||
this.setData({
|
||||
myLoadStatus: 0,
|
||||
commentType: commenttype,
|
||||
});
|
||||
this.init(true);
|
||||
}
|
||||
},
|
||||
onReachBottom() {
|
||||
const { total = 0, commentList } = this.data;
|
||||
if (commentList.length === total) {
|
||||
this.setData({
|
||||
loadMoreStatus: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.init(false);
|
||||
},
|
||||
});
|
||||
8
miniprogram/pages/goods/comments/index.json
Normal file
8
miniprogram/pages/goods/comments/index.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "全部评价",
|
||||
"usingComponents": {
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"comments-card": "./components/comments-card/index",
|
||||
"t-load-more": "/components/load-more/index"
|
||||
}
|
||||
}
|
||||
50
miniprogram/pages/goods/comments/index.wxml
Normal file
50
miniprogram/pages/goods/comments/index.wxml
Normal file
@@ -0,0 +1,50 @@
|
||||
<view class="comments-header">
|
||||
<t-tag t-class="comments-header-tag {{commentType === '' ? 'comments-header-active' : ''}}" data-commentType="" bindtap="changeTag">
|
||||
全部({{countObj.commentCount}})
|
||||
</t-tag>
|
||||
<t-tag
|
||||
t-class="comments-header-tag {{commentType === '5' ? 'comments-header-active' : ''}}"
|
||||
wx:if="{{countObj.uidCount !== '0'}}"
|
||||
data-commentType="5"
|
||||
bindtap="changeTag"
|
||||
>
|
||||
自己({{countObj.uidCount}})
|
||||
</t-tag>
|
||||
<t-tag t-class="comments-header-tag {{commentType === '4' ? 'comments-header-active' : ''}}" data-commentType="4" bindtap="changeTag">
|
||||
带图({{countObj.hasImageCount}})
|
||||
</t-tag>
|
||||
<t-tag t-class="comments-header-tag {{commentType === '3' ? 'comments-header-active' : ''}}" data-commentType="3" bindtap="changeTag">
|
||||
好评({{countObj.goodCount}})
|
||||
</t-tag>
|
||||
<t-tag t-class="comments-header-tag {{commentType === '2' ? 'comments-header-active' : ''}}" data-commentType="2" bindtap="changeTag">
|
||||
中评({{countObj.middleCount}})
|
||||
</t-tag>
|
||||
<t-tag t-class="comments-header-tag {{commentType === '1' ? 'comments-header-active' : ''}}" data-commentType="1" bindtap="changeTag">
|
||||
差评({{countObj.badCount}})
|
||||
</t-tag>
|
||||
</view>
|
||||
<view class="comments-card-list">
|
||||
<block wx:for="{{commentList}}" wx:key="index">
|
||||
<comments-card
|
||||
commentScore="{{item.commentScore}}"
|
||||
userName="{{item.userName}}"
|
||||
commentResources="{{item.commentResources || []}}"
|
||||
commentContent="{{item.commentContent}}"
|
||||
isAnonymity="{{item.isAnonymity}}"
|
||||
commentTime="{{item.commentTime}}"
|
||||
isAutoComment="{{item.isAutoComment}}"
|
||||
userHeadUrl="{{item.userHeadUrl}}"
|
||||
specInfo="{{item.specInfo}}"
|
||||
sellerReply="{{item.sellerReply || ''}}"
|
||||
goodsDetailInfo="{{item.goodsDetailInfo || ''}}"
|
||||
/>
|
||||
</block>
|
||||
<t-load-more
|
||||
t-class="no-more"
|
||||
status="{{loadMoreStatus}}"
|
||||
no-more-text="没有更多了"
|
||||
color="#BBBBBB"
|
||||
failedColor="#FA550F"
|
||||
/>
|
||||
</view>
|
||||
|
||||
49
miniprogram/pages/goods/comments/index.wxss
Normal file
49
miniprogram/pages/goods/comments/index.wxss
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 层级定义
|
||||
@z-index-0: 1;
|
||||
@z-index-1: 100;
|
||||
@z-index-2: 200;
|
||||
@z-index-5: 500;
|
||||
@z-index-component: 1000; // 通用组件级别
|
||||
@z-index-dropdown: @z-index-component;
|
||||
@z-index-sticky: @z-index-component + 20;
|
||||
@z-index-fixed: @z-index-component + 30;
|
||||
@z-index-modal-backdrop:@z-index-component + 40;
|
||||
@z-index-modal:@z-index-component + 50;
|
||||
@z-index-popover:@z-index-component + 60;
|
||||
@z-index-tooltip:@z-index-component + 70;
|
||||
*/
|
||||
/* var() css变量适配*/
|
||||
page {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.comments-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 32rpx 32rpx 0rpx;
|
||||
background-color: #fff;
|
||||
margin-top: -24rpx;
|
||||
margin-left: -24rpx;
|
||||
}
|
||||
|
||||
.comments-header-tag {
|
||||
margin-top: 24rpx;
|
||||
margin-left: 24rpx;
|
||||
height: 56rpx !important;
|
||||
font-size: 24rpx !important;
|
||||
justify-content: center;
|
||||
background-color: #F5F5F5 !important;
|
||||
border-radius: 8rpx !important;
|
||||
border: 1px solid #F5F5F5 !important;
|
||||
}
|
||||
|
||||
.comments-header-active {
|
||||
background-color: #FFECE9 !important;
|
||||
color: #FA4126 !important;
|
||||
border: 1px solid #FA4126 !important;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
74
miniprogram/pages/goods/details/components/buy-bar/index.js
Normal file
74
miniprogram/pages/goods/details/components/buy-bar/index.js
Normal 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');
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
181
miniprogram/pages/goods/details/components/buy-bar/index.wxss
Normal file
181
miniprogram/pages/goods/details/components/buy-bar/index.wxss
Normal 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;
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-popup": "tdesign-miniprogram/popup/popup",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
979
miniprogram/pages/goods/details/index.js
Normal file
979
miniprogram/pages/goods/details/index.js
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
18
miniprogram/pages/goods/details/index.json
Normal file
18
miniprogram/pages/goods/details/index.json
Normal 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"
|
||||
}
|
||||
}
|
||||
181
miniprogram/pages/goods/details/index.wxml
Normal file
181
miniprogram/pages/goods/details/index.wxml
Normal 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" />
|
||||
810
miniprogram/pages/goods/details/index.wxss
Normal file
810
miniprogram/pages/goods/details/index.wxss
Normal 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;
|
||||
}
|
||||
}
|
||||
373
miniprogram/pages/goods/list/index.js
Normal file
373
miniprogram/pages/goods/list/index.js
Normal file
@@ -0,0 +1,373 @@
|
||||
import { fetchGoodsList } from '../../../services/good/fetchGoodsList';
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
|
||||
const initFilters = {
|
||||
overall: 1,
|
||||
sorts: '',
|
||||
layout: 0,
|
||||
};
|
||||
|
||||
Page({
|
||||
data: {
|
||||
goodsList: [],
|
||||
layout: 0,
|
||||
sorts: '',
|
||||
overall: 1,
|
||||
show: false,
|
||||
minVal: '',
|
||||
maxVal: '',
|
||||
prices: [],
|
||||
filter: initFilters,
|
||||
hasLoaded: false,
|
||||
loadMoreStatus: 0,
|
||||
loading: true,
|
||||
categoryId: '',
|
||||
categoryName: '',
|
||||
keywords: '',
|
||||
},
|
||||
|
||||
pageNum: 1,
|
||||
pageSize: 30,
|
||||
total: 0,
|
||||
|
||||
handleFilterChange(e) {
|
||||
const { layout, overall, sorts } = e.detail;
|
||||
const { sorts: oldSorts, overall: oldOverall, layout: oldLayout } = this.data;
|
||||
|
||||
console.log('[GoodsList] 筛选条件变化:', {
|
||||
oldState: {
|
||||
layout: oldLayout === 1 ? '列表' : '网格',
|
||||
overall: oldOverall === 1 ? '综合排序' : '其他排序',
|
||||
sorts: oldSorts || '默认'
|
||||
},
|
||||
newState: {
|
||||
layout: layout === 1 ? '列表' : '网格',
|
||||
overall: overall === 1 ? '综合排序' : '其他排序',
|
||||
sorts: sorts || '默认'
|
||||
},
|
||||
sortText: sorts === 'asc' ? '价格从低到高' : sorts === 'desc' ? '价格从高到低' : '默认排序',
|
||||
timestamp: new Date().toLocaleString(),
|
||||
action: '重置页码并重新加载商品列表'
|
||||
});
|
||||
|
||||
this.pageNum = 1;
|
||||
this.setData({
|
||||
layout,
|
||||
sorts,
|
||||
overall,
|
||||
filter: {
|
||||
layout,
|
||||
overall,
|
||||
sorts,
|
||||
},
|
||||
loadMoreStatus: 0,
|
||||
});
|
||||
|
||||
console.log('[GoodsList] 开始重新加载商品列表:', {
|
||||
pageNum: this.pageNum,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
this.init(true);
|
||||
},
|
||||
|
||||
generalQueryData(reset = false) {
|
||||
const { keywords, minVal, maxVal, sorts, overall } = this.data;
|
||||
const { pageNum, pageSize } = this;
|
||||
const params = {
|
||||
sort: overall === 1 ? 0 : 1, // 0 综合,1 价格
|
||||
pageNum: reset ? 1 : pageNum,
|
||||
pageSize: pageSize,
|
||||
keyword: keywords || '',
|
||||
};
|
||||
|
||||
console.log('[GoodsList] 构建查询参数 - 基础参数:', {
|
||||
overall: overall === 1 ? '综合排序' : '其他排序',
|
||||
sorts: sorts || '默认',
|
||||
pageNum: params.pageNum,
|
||||
pageSize: params.pageSize,
|
||||
keyword: params.keyword,
|
||||
reset: reset,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
// 添加分类ID参数
|
||||
if (this.categoryId) {
|
||||
params.category_id = this.categoryId;
|
||||
console.log('[GoodsList] 添加分类参数:', {
|
||||
categoryId: this.categoryId,
|
||||
categoryName: this.data.categoryName || '未知分类'
|
||||
});
|
||||
}
|
||||
|
||||
// 设置排序参数
|
||||
if (sorts && sorts !== '') {
|
||||
params.sort = 1;
|
||||
params.sortType = sorts === 'desc' ? 1 : 0;
|
||||
console.log('[GoodsList] 设置价格排序参数:', {
|
||||
sorts: sorts,
|
||||
sortType: params.sortType,
|
||||
sortText: sorts === 'desc' ? '价格从高到低' : '价格从低到高'
|
||||
});
|
||||
}
|
||||
|
||||
// 设置价格筛选参数(传输单位改为“分”,乘以100并取整)
|
||||
const min = Number(minVal);
|
||||
const max = Number(maxVal);
|
||||
if (!Number.isNaN(min) && min > 0) {
|
||||
params.minPrice = Math.round(min * 100);
|
||||
console.log('[GoodsList] 设置最低价格(分):', params.minPrice);
|
||||
}
|
||||
if (!Number.isNaN(max) && max > 0) {
|
||||
params.maxPrice = Math.round(max * 100);
|
||||
console.log('[GoodsList] 设置最高价格(分):', params.maxPrice);
|
||||
}
|
||||
|
||||
console.log('[GoodsList] 最终查询参数:', {
|
||||
params: params,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
if (reset) return params;
|
||||
return {
|
||||
...params,
|
||||
pageNum: pageNum + 1,
|
||||
};
|
||||
},
|
||||
|
||||
async init(reset = true) {
|
||||
const { loadMoreStatus, goodsList = [] } = this.data;
|
||||
const params = this.generalQueryData(reset);
|
||||
if (loadMoreStatus !== 0) return;
|
||||
this.setData({
|
||||
loadMoreStatus: 1,
|
||||
loading: true,
|
||||
});
|
||||
try {
|
||||
const result = await fetchGoodsList(params);
|
||||
console.log('[GoodsList] API调用结果:', {
|
||||
result: result,
|
||||
hasSpuList: !!result.spuList,
|
||||
spuListLength: result.spuList ? result.spuList.length : 0,
|
||||
totalCount: result.totalCount,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
if (result && result.spuList) {
|
||||
const { spuList, totalCount = 0 } = result;
|
||||
if (totalCount === 0 && reset) {
|
||||
this.total = totalCount;
|
||||
this.setData({
|
||||
emptyInfo: {
|
||||
tip: '抱歉,未找到相关商品',
|
||||
},
|
||||
hasLoaded: true,
|
||||
loadMoreStatus: 0,
|
||||
loading: false,
|
||||
goodsList: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const _goodsList = reset ? spuList : goodsList.concat(spuList);
|
||||
const _loadMoreStatus = _goodsList.length === totalCount ? 2 : 0;
|
||||
this.pageNum = params.pageNum || 1;
|
||||
this.total = totalCount;
|
||||
|
||||
console.log('[GoodsList] 更新商品列表数据:', {
|
||||
reset: reset,
|
||||
oldGoodsListLength: goodsList.length,
|
||||
newGoodsListLength: _goodsList.length,
|
||||
totalCount: totalCount,
|
||||
loadMoreStatus: _loadMoreStatus,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
this.setData({
|
||||
goodsList: _goodsList,
|
||||
loadMoreStatus: _loadMoreStatus,
|
||||
});
|
||||
} else {
|
||||
console.error('[GoodsList] API返回数据格式错误:', result);
|
||||
this.setData({
|
||||
loading: false,
|
||||
});
|
||||
wx.showToast({
|
||||
title: '查询失败,请稍候重试',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
this.setData({
|
||||
hasLoaded: true,
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 接收分类参数
|
||||
if (options.categoryId) {
|
||||
this.categoryId = options.categoryId;
|
||||
this.setData({
|
||||
categoryId: options.categoryId,
|
||||
categoryName: decodeURIComponent(options.categoryName || ''),
|
||||
});
|
||||
}
|
||||
|
||||
// 接收搜索关键词参数
|
||||
if (options.keyword) {
|
||||
this.setData({
|
||||
keywords: decodeURIComponent(options.keyword),
|
||||
});
|
||||
}
|
||||
|
||||
this.init(true);
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
const { goodsList } = this.data;
|
||||
const { total = 0 } = this;
|
||||
if (goodsList.length === total) {
|
||||
this.setData({
|
||||
loadMoreStatus: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.init(false);
|
||||
},
|
||||
|
||||
handleAddCart() {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '点击加购',
|
||||
});
|
||||
},
|
||||
|
||||
tagClickHandle() {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '点击标签',
|
||||
});
|
||||
},
|
||||
|
||||
gotoGoodsDetail(e) {
|
||||
const { index } = e.detail;
|
||||
const { spuId } = this.data.goodsList[index];
|
||||
wx.navigateTo({
|
||||
url: `/pages/goods/details/index?spuId=${spuId}`,
|
||||
});
|
||||
},
|
||||
|
||||
showFilterPopup() {
|
||||
this.setData({
|
||||
show: true,
|
||||
});
|
||||
},
|
||||
|
||||
showFilterPopupClose() {
|
||||
this.setData({
|
||||
show: false,
|
||||
});
|
||||
},
|
||||
|
||||
onMinValAction(e) {
|
||||
const { value } = e.detail;
|
||||
this.setData({ minVal: value });
|
||||
},
|
||||
|
||||
onMaxValAction(e) {
|
||||
const { value } = e.detail;
|
||||
this.setData({ maxVal: value });
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.pageNum = 1;
|
||||
this.setData({
|
||||
minVal: '',
|
||||
maxVal: '',
|
||||
prices: [],
|
||||
goodsList: [],
|
||||
loadMoreStatus: 0,
|
||||
}, () => {
|
||||
this.init(true);
|
||||
});
|
||||
},
|
||||
|
||||
confirm() {
|
||||
const { minVal, maxVal } = this.data;
|
||||
|
||||
// 验证价格范围
|
||||
if (minVal && maxVal && parseFloat(minVal) > parseFloat(maxVal)) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '最低价不能大于最高价',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建价格筛选数组
|
||||
const prices = [];
|
||||
if (minVal || maxVal) {
|
||||
prices.push({
|
||||
min: minVal || 0,
|
||||
max: maxVal || '不限'
|
||||
});
|
||||
}
|
||||
|
||||
// 应用价格筛选
|
||||
this.pageNum = 1;
|
||||
this.setData(
|
||||
{
|
||||
show: false,
|
||||
prices: prices,
|
||||
goodsList: [],
|
||||
loadMoreStatus: 0,
|
||||
},
|
||||
() => {
|
||||
this.init(true);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
// 分享功能
|
||||
onShareAppMessage() {
|
||||
const { categoryName, keywords } = this.data;
|
||||
let title = '精选商品列表';
|
||||
let path = '/pages/goods/list/index';
|
||||
|
||||
if (categoryName) {
|
||||
title = `${categoryName} - 精选商品`;
|
||||
path = `/pages/goods/list/index?categoryId=${this.categoryId}&categoryName=${encodeURIComponent(categoryName)}`;
|
||||
} else if (keywords) {
|
||||
title = `${keywords} - 搜索结果`;
|
||||
path = `/pages/goods/list/index?keywords=${encodeURIComponent(keywords)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
title: title,
|
||||
path: path
|
||||
};
|
||||
},
|
||||
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
const { categoryName, keywords } = this.data;
|
||||
let title = '精选商品列表';
|
||||
|
||||
if (categoryName) {
|
||||
title = `${categoryName} - 精选商品`;
|
||||
} else if (keywords) {
|
||||
title = `${keywords} - 搜索结果`;
|
||||
}
|
||||
|
||||
return {
|
||||
title: title
|
||||
};
|
||||
}
|
||||
});
|
||||
12
miniprogram/pages/goods/list/index.json
Normal file
12
miniprogram/pages/goods/list/index.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarTitleText": "商品列表",
|
||||
"usingComponents": {
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"goods-list": "/components/goods-list/index",
|
||||
"filter": "/components/filter/index",
|
||||
"filter-popup": "/components/filter-popup/index",
|
||||
"load-more": "/components/load-more/index"
|
||||
}
|
||||
}
|
||||
56
miniprogram/pages/goods/list/index.wxml
Normal file
56
miniprogram/pages/goods/list/index.wxml
Normal file
@@ -0,0 +1,56 @@
|
||||
<view class="goods-list-container">
|
||||
<filter
|
||||
wr-class="filter-container"
|
||||
bind:change="handleFilterChange"
|
||||
layout="{{layout}}"
|
||||
sorts="{{sorts}}"
|
||||
overall="{{overall}}"
|
||||
prices="{{prices}}"
|
||||
bind:showFilterPopup="showFilterPopup"
|
||||
>
|
||||
<filter-popup
|
||||
slot="filterPopup"
|
||||
show="{{show}}"
|
||||
bind:showFilterPopupClose="showFilterPopupClose"
|
||||
bind:reset="reset"
|
||||
bind:confirm="confirm"
|
||||
>
|
||||
<view class="price-container" slot="filterSlot">
|
||||
<view class="price-between">价格区间</view>
|
||||
<view class="price-ipts-wrap">
|
||||
<t-input
|
||||
align="center"
|
||||
type="number"
|
||||
t-class="price-ipt"
|
||||
placeholder="最低价"
|
||||
value="{{minVal}}"
|
||||
bindchange="onMinValAction"
|
||||
/>
|
||||
<view class="price-divided">-</view>
|
||||
<t-input
|
||||
align="center"
|
||||
type="number"
|
||||
t-class="price-ipt"
|
||||
placeholder="最高价"
|
||||
value="{{maxVal}}"
|
||||
bindchange="onMaxValAction"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</filter-popup>
|
||||
</filter>
|
||||
<view class="empty-wrap" wx:if="{{goodsList.length === 0 && hasLoaded}}">
|
||||
<t-empty t-class="empty-tips" size="240rpx" description="暂无相关商品" />
|
||||
</view>
|
||||
<view class="category-goods-list" wx:if="{{goodsList.length}}">
|
||||
<goods-list
|
||||
wr-class="wr-goods-list"
|
||||
goodsList="{{goodsList}}"
|
||||
show-cart="{{false}}"
|
||||
bind:click="gotoGoodsDetail"
|
||||
bind:addcart="handleAddCart"
|
||||
/>
|
||||
</view>
|
||||
<load-more wx:if="{{goodsList.length > 0}}" status="{{loadMoreStatus}}" no-more-text="没有更多了" />
|
||||
</view>
|
||||
<t-toast id="t-toast" />
|
||||
99
miniprogram/pages/goods/list/index.wxss
Normal file
99
miniprogram/pages/goods/list/index.wxss
Normal file
@@ -0,0 +1,99 @@
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.goods-list-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.goods-list-container .t-search {
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.goods-list-container .t-class__input-container {
|
||||
height: 64rpx !important;
|
||||
border-radius: 32rpx !important;
|
||||
}
|
||||
|
||||
.goods-list-container .t-search__left-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.goods-list-container .t-search__input {
|
||||
font-size: 28rpx !important;
|
||||
color: rgb(116, 116, 116) !important;
|
||||
}
|
||||
|
||||
.goods-list-container .category-goods-list {
|
||||
background-color: #f2f2f2;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 20rpx 24rpx;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.goods-list-container .wr-goods-list {
|
||||
background: #f2f2f2 !important;
|
||||
}
|
||||
|
||||
.goods-list-container .t-image__mask {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.goods-list-container .empty-wrap {
|
||||
margin-top: 184rpx;
|
||||
margin-bottom: 120rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.goods-list-container .empty-wrap .empty-tips .empty-content .content-text {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.goods-list-container .price-container {
|
||||
padding: 32rpx;
|
||||
height: 100vh;
|
||||
max-width: 632rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 30rpx 0 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.goods-list-container .price-between {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(51, 51, 51, 1);
|
||||
}
|
||||
|
||||
.goods-list-container .price-ipts-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-top: 24rpx;
|
||||
|
||||
--td-input-bg-color: rgba(245, 245, 245, 1);
|
||||
--td-input-vertical-padding: 4rpx;
|
||||
--td-input-border-color: transparent;
|
||||
}
|
||||
|
||||
.goods-list-container .price-ipts-wrap .price-divided {
|
||||
width: 16rpx;
|
||||
margin: 0 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.goods-list-container .price-ipts-wrap .t-input__wrapper {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.goods-list-container .price-ipts-wrap .t-input__content,
|
||||
.goods-list-container .price-ipts-wrap .t-input__placeholder {
|
||||
font-size: 24rpx !important;
|
||||
}
|
||||
|
||||
.goods-list-container .price-ipts-wrap .price-ipt {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
276
miniprogram/pages/goods/result/index.js
Normal file
276
miniprogram/pages/goods/result/index.js
Normal file
@@ -0,0 +1,276 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { getSearchResult } from '../../../services/good/fetchSearchResult';
|
||||
import Toast from 'tdesign-miniprogram/toast/index';
|
||||
|
||||
const initFilters = {
|
||||
overall: 1,
|
||||
sorts: '',
|
||||
};
|
||||
|
||||
Page({
|
||||
data: {
|
||||
goodsList: [],
|
||||
sorts: '',
|
||||
overall: 1,
|
||||
show: false,
|
||||
minVal: '',
|
||||
maxVal: '',
|
||||
minSalePriceFocus: false,
|
||||
maxSalePriceFocus: false,
|
||||
filter: initFilters,
|
||||
hasLoaded: false,
|
||||
keywords: '',
|
||||
loadMoreStatus: 0,
|
||||
loading: true,
|
||||
},
|
||||
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 30,
|
||||
|
||||
onLoad(options) {
|
||||
const { searchValue = '' } = options || {};
|
||||
// 解码URL编码的搜索值,确保中文字符正确显示
|
||||
const decodedSearchValue = searchValue ? decodeURIComponent(searchValue) : '';
|
||||
this.setData(
|
||||
{
|
||||
keywords: decodedSearchValue,
|
||||
},
|
||||
() => {
|
||||
this.init(true);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
generalQueryData(reset = false) {
|
||||
const { filter, keywords, minVal, maxVal } = this.data;
|
||||
const { pageNum, pageSize } = this;
|
||||
const { sorts, overall } = filter;
|
||||
const params = {
|
||||
sort: 0, // 0 综合,1 价格
|
||||
pageNum: 1,
|
||||
pageSize: 30,
|
||||
keyword: keywords,
|
||||
};
|
||||
|
||||
if (sorts) {
|
||||
params.sort = 1;
|
||||
params.sortType = sorts === 'desc' ? 1 : 0;
|
||||
}
|
||||
if (overall) {
|
||||
params.sort = 0;
|
||||
} else {
|
||||
params.sort = 1;
|
||||
}
|
||||
params.minPrice = minVal ? minVal * 100 : 0;
|
||||
params.maxPrice = maxVal ? maxVal * 100 : undefined;
|
||||
if (reset) return params;
|
||||
return {
|
||||
...params,
|
||||
pageNum: pageNum + 1,
|
||||
pageSize,
|
||||
};
|
||||
},
|
||||
|
||||
async init(reset = true) {
|
||||
const { loadMoreStatus, goodsList = [] } = this.data;
|
||||
const params = this.generalQueryData(reset);
|
||||
if (loadMoreStatus !== 0) return;
|
||||
this.setData({
|
||||
loadMoreStatus: 1,
|
||||
loading: true,
|
||||
});
|
||||
try {
|
||||
const result = await getSearchResult(params);
|
||||
console.log('🔍 搜索结果页面收到数据:', result);
|
||||
|
||||
if (result && result.spuList) {
|
||||
const { spuList, totalCount = 0 } = result;
|
||||
if (totalCount === 0 && reset) {
|
||||
this.total = totalCount;
|
||||
this.setData({
|
||||
emptyInfo: {
|
||||
tip: '抱歉,未找到相关商品',
|
||||
},
|
||||
hasLoaded: true,
|
||||
loadMoreStatus: 0,
|
||||
loading: false,
|
||||
goodsList: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const _goodsList = reset ? spuList : goodsList.concat(spuList);
|
||||
_goodsList.forEach((v) => {
|
||||
v.tags = v.spuTagList ? v.spuTagList.map((u) => u.title) : (v.tags || []);
|
||||
v.hideKey = { desc: true };
|
||||
});
|
||||
const _loadMoreStatus = _goodsList.length === totalCount ? 2 : 0;
|
||||
this.pageNum = params.pageNum || 1;
|
||||
this.total = totalCount;
|
||||
this.setData({
|
||||
goodsList: _goodsList,
|
||||
loadMoreStatus: _loadMoreStatus,
|
||||
});
|
||||
} else {
|
||||
console.error('🔍 搜索结果格式错误:', result);
|
||||
this.setData({
|
||||
loading: false,
|
||||
emptyInfo: {
|
||||
tip: '搜索结果格式错误,请稍候重试',
|
||||
},
|
||||
});
|
||||
wx.showToast({
|
||||
title: '查询失败,请稍候重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🔍 搜索请求失败:', error);
|
||||
this.setData({
|
||||
loading: false,
|
||||
emptyInfo: {
|
||||
tip: '网络连接失败,请检查网络后重试',
|
||||
},
|
||||
});
|
||||
wx.showToast({
|
||||
title: error.message || '网络请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
this.setData({
|
||||
hasLoaded: true,
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
|
||||
handleCartTap() {
|
||||
wx.switchTab({
|
||||
url: '/pages/cart/index',
|
||||
});
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
this.setData(
|
||||
{
|
||||
goodsList: [],
|
||||
loadMoreStatus: 0,
|
||||
},
|
||||
() => {
|
||||
this.init(true);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
const { goodsList } = this.data;
|
||||
const { total = 0 } = this;
|
||||
if (goodsList.length === total) {
|
||||
this.setData({
|
||||
loadMoreStatus: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.init(false);
|
||||
},
|
||||
|
||||
handleAddCart() {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message: '点击加购',
|
||||
});
|
||||
},
|
||||
|
||||
gotoGoodsDetail(e) {
|
||||
const { index } = e.detail;
|
||||
const { spuId } = this.data.goodsList[index];
|
||||
wx.navigateTo({
|
||||
url: `/pages/goods/details/index?spuId=${spuId}`,
|
||||
});
|
||||
},
|
||||
|
||||
handleFilterChange(e) {
|
||||
const { overall, sorts } = e.detail;
|
||||
const { total } = this;
|
||||
const _filter = {
|
||||
sorts,
|
||||
overall,
|
||||
};
|
||||
this.setData({
|
||||
filter: _filter,
|
||||
sorts,
|
||||
overall,
|
||||
});
|
||||
|
||||
this.pageNum = 1;
|
||||
this.setData(
|
||||
{
|
||||
goodsList: [],
|
||||
loadMoreStatus: 0,
|
||||
},
|
||||
() => {
|
||||
total && this.init(true);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
showFilterPopup() {
|
||||
this.setData({
|
||||
show: true,
|
||||
});
|
||||
},
|
||||
|
||||
showFilterPopupClose() {
|
||||
this.setData({
|
||||
show: false,
|
||||
});
|
||||
},
|
||||
|
||||
onMinValAction(e) {
|
||||
const { value } = e.detail;
|
||||
this.setData({ minVal: value });
|
||||
},
|
||||
|
||||
onMaxValAction(e) {
|
||||
const { value } = e.detail;
|
||||
this.setData({ maxVal: value });
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.setData({ minVal: '', maxVal: '' });
|
||||
},
|
||||
|
||||
confirm() {
|
||||
const { minVal, maxVal } = this.data;
|
||||
let message = '';
|
||||
if (minVal && !maxVal) {
|
||||
message = `价格最小是${minVal}`;
|
||||
} else if (!minVal && maxVal) {
|
||||
message = `价格范围是0-${maxVal}`;
|
||||
} else if (minVal && maxVal && minVal <= maxVal) {
|
||||
message = `价格范围${minVal}-${this.data.maxVal}`;
|
||||
} else {
|
||||
message = '请输入正确范围';
|
||||
}
|
||||
if (message) {
|
||||
Toast({
|
||||
context: this,
|
||||
selector: '#t-toast',
|
||||
message,
|
||||
});
|
||||
}
|
||||
this.pageNum = 1;
|
||||
this.setData(
|
||||
{
|
||||
show: false,
|
||||
goodsList: [],
|
||||
loadMoreStatus: 0,
|
||||
},
|
||||
() => {
|
||||
// 保留价格筛选值,确保传入后端API
|
||||
this.init(true);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
15
miniprogram/pages/goods/result/index.json
Normal file
15
miniprogram/pages/goods/result/index.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"navigationBarTitleText": "搜索",
|
||||
"usingComponents": {
|
||||
"t-search": "tdesign-miniprogram/search/search",
|
||||
"t-input": "tdesign-miniprogram/input/input",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-toast": "tdesign-miniprogram/toast/toast",
|
||||
"goods-list": "/components/goods-list/index",
|
||||
"filter": "/components/filter/index",
|
||||
"filter-popup": "/components/filter-popup/index",
|
||||
"load-more": "/components/load-more/index",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
},
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
67
miniprogram/pages/goods/result/index.wxml
Normal file
67
miniprogram/pages/goods/result/index.wxml
Normal file
@@ -0,0 +1,67 @@
|
||||
<view class="result-container">
|
||||
<t-search
|
||||
t-class="t-search"
|
||||
t-class-input-container="t-class__input-container"
|
||||
t-class-left="t-search__left-icon"
|
||||
t-class-input="t-search__input"
|
||||
value="{{keywords}}"
|
||||
leftIcon=""
|
||||
placeholder="iPhone12pro"
|
||||
bind:submit="handleSubmit"
|
||||
>
|
||||
<t-icon slot="left-icon" prefix="wr" name="search" size="40rpx" color="#bbb" />
|
||||
</t-search>
|
||||
<filter
|
||||
wr-class="filter-container"
|
||||
bind:change="handleFilterChange"
|
||||
layout="{{layout}}"
|
||||
sorts="{{sorts}}"
|
||||
overall="{{overall}}"
|
||||
bind:showFilterPopup="showFilterPopup"
|
||||
>
|
||||
<filter-popup
|
||||
show="{{show}}"
|
||||
slot="filterPopup"
|
||||
bind:showFilterPopupClose="showFilterPopupClose"
|
||||
bind:reset="reset"
|
||||
bind:confirm="confirm"
|
||||
>
|
||||
<view class="price-container" slot="filterSlot">
|
||||
<view class="price-between">价格区间</view>
|
||||
<view class="price-ipts-wrap">
|
||||
<t-input
|
||||
type="number"
|
||||
t-class="price-ipt"
|
||||
t-class-input="t-class-input"
|
||||
placeholder="最低价"
|
||||
value="{{minVal}}"
|
||||
bindchange="onMinValAction"
|
||||
/>
|
||||
<view class="price-divided">-</view>
|
||||
<t-input
|
||||
type="number"
|
||||
t-class="price-ipt"
|
||||
t-class-input="t-class-input"
|
||||
placeholder="最高价"
|
||||
value="{{maxVal}}"
|
||||
bindchange="onMaxValAction"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</filter-popup>
|
||||
</filter>
|
||||
<view class="empty-wrap" wx:if="{{goodsList.length === 0 && hasLoaded}}">
|
||||
<t-empty t-class="empty-tips" size="240rpx" description="暂无相关商品" />
|
||||
</view>
|
||||
<view class="category-goods-list" wx:if="{{goodsList.length}}">
|
||||
<goods-list
|
||||
wr-class="wr-goods-list"
|
||||
goodsList="{{goodsList}}"
|
||||
show-cart="{{false}}"
|
||||
bind:click="gotoGoodsDetail"
|
||||
bind:addcart="handleAddCart"
|
||||
/>
|
||||
</view>
|
||||
<load-more wx:if="{{goodsList.length > 0}}" status="{{loadMoreStatus}}" no-more-text="没有更多了" />
|
||||
</view>
|
||||
<t-toast id="t-toast" />
|
||||
114
miniprogram/pages/goods/result/index.wxss
Normal file
114
miniprogram/pages/goods/result/index.wxss
Normal file
@@ -0,0 +1,114 @@
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
page view {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.result-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-container .t-search {
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.result-container .t-class__input-container {
|
||||
height: 64rpx !important;
|
||||
border-radius: 32rpx !important;
|
||||
}
|
||||
|
||||
.result-container .t-search__left-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.result-container .t-search__input {
|
||||
font-size: 28rpx !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.result-container .category-goods-list {
|
||||
background-color: #f2f2f2;
|
||||
overflow-y: scroll;
|
||||
padding: 20rpx 24rpx;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.result-container .wr-goods-list {
|
||||
background: #f2f2f2 !important;
|
||||
}
|
||||
|
||||
.result-container .t-image__mask {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.result-container .empty-wrap {
|
||||
margin-top: 184rpx;
|
||||
margin-bottom: 120rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.result-container .empty-wrap .empty-tips .empty-content .content-text {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.result-container .price-container {
|
||||
padding: 32rpx;
|
||||
height: 100vh;
|
||||
max-width: 632rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 30rpx 0 0 30rpx;
|
||||
}
|
||||
|
||||
.result-container .price-between {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(51, 51, 51, 1);
|
||||
}
|
||||
|
||||
.result-container .price-ipts-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.result-container .price-ipts-wrap .price-divided {
|
||||
position: relative;
|
||||
width: 22rpx;
|
||||
margin: 0 20rpx;
|
||||
color: #222427;
|
||||
}
|
||||
|
||||
.result-container .price-ipts-wrap .price-ipt {
|
||||
box-sizing: border-box;
|
||||
width: 246rpx;
|
||||
font-size: 24rpx;
|
||||
height: 56rpx;
|
||||
padding: 0 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 8rpx;
|
||||
color: #333;
|
||||
background: rgba(245, 245, 245, 1);
|
||||
}
|
||||
|
||||
.t-class-input {
|
||||
font-size: 24rpx !important;
|
||||
}
|
||||
|
||||
.t-search__clear {
|
||||
font-size: 40rpx !important;
|
||||
}
|
||||
|
||||
.result-container .price-ipts-wrap .price-ipt::after {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.result-container .t-input__control {
|
||||
font-size: 24rpx !important;
|
||||
text-align: center;
|
||||
}
|
||||
299
miniprogram/pages/goods/search/index.js
Normal file
299
miniprogram/pages/goods/search/index.js
Normal file
@@ -0,0 +1,299 @@
|
||||
import {
|
||||
getSearchHistory,
|
||||
getSearchPopular,
|
||||
} from '../../../services/good/fetchSearchHistory';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
historyWords: [],
|
||||
popularWords: [],
|
||||
searchValue: '',
|
||||
searchSuggestions: [],
|
||||
showSuggestions: false,
|
||||
dialog: {
|
||||
title: '确认删除当前历史记录',
|
||||
showCancelButton: true,
|
||||
message: '',
|
||||
},
|
||||
dialogShow: false,
|
||||
},
|
||||
|
||||
deleteType: 0,
|
||||
deleteIndex: '',
|
||||
searchTimer: null,
|
||||
|
||||
onShow() {
|
||||
this.loadLocalHistory();
|
||||
this.queryPopular();
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadLocalHistory();
|
||||
},
|
||||
|
||||
// 加载本地历史记录
|
||||
loadLocalHistory() {
|
||||
try {
|
||||
const historyWords = wx.getStorageSync('searchHistory') || [];
|
||||
this.setData({
|
||||
historyWords: historyWords.slice(0, 10), // 最多显示10条历史记录
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载搜索历史失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 保存搜索历史到本地
|
||||
saveSearchHistory(keyword) {
|
||||
try {
|
||||
let historyWords = wx.getStorageSync('searchHistory') || [];
|
||||
|
||||
// 移除重复项
|
||||
historyWords = historyWords.filter(item => item !== keyword);
|
||||
|
||||
// 添加到开头
|
||||
historyWords.unshift(keyword);
|
||||
|
||||
// 限制历史记录数量
|
||||
historyWords = historyWords.slice(0, 20);
|
||||
|
||||
wx.setStorageSync('searchHistory', historyWords);
|
||||
this.setData({
|
||||
historyWords: historyWords.slice(0, 10),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存搜索历史失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空本地历史记录
|
||||
clearLocalHistory() {
|
||||
try {
|
||||
wx.removeStorageSync('searchHistory');
|
||||
this.setData({
|
||||
historyWords: [],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清空搜索历史失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 删除单条历史记录
|
||||
deleteHistoryItem(index) {
|
||||
try {
|
||||
let historyWords = wx.getStorageSync('searchHistory') || [];
|
||||
historyWords.splice(index, 1);
|
||||
wx.setStorageSync('searchHistory', historyWords);
|
||||
this.setData({
|
||||
historyWords: historyWords.slice(0, 10),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除历史记录失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async queryHistory() {
|
||||
try {
|
||||
const data = await getSearchHistory();
|
||||
const code = 'Success';
|
||||
if (String(code).toUpperCase() === 'SUCCESS') {
|
||||
const { historyWords = [] } = data;
|
||||
this.setData({
|
||||
historyWords,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
async queryPopular() {
|
||||
try {
|
||||
const data = await getSearchPopular();
|
||||
const code = 'Success';
|
||||
if (String(code).toUpperCase() === 'SUCCESS') {
|
||||
const { popularWords = [] } = data;
|
||||
this.setData({
|
||||
popularWords,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索输入处理
|
||||
onSearchInput(e) {
|
||||
const value = e.detail.value || '';
|
||||
this.setData({
|
||||
searchValue: value,
|
||||
});
|
||||
|
||||
// 清除之前的定时器
|
||||
if (this.searchTimer) {
|
||||
clearTimeout(this.searchTimer);
|
||||
}
|
||||
|
||||
if (value.trim()) {
|
||||
// 延迟搜索建议
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.getSearchSuggestions(value.trim());
|
||||
}, 300);
|
||||
} else {
|
||||
this.setData({
|
||||
showSuggestions: false,
|
||||
searchSuggestions: [],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取搜索建议
|
||||
getSearchSuggestions(keyword) {
|
||||
const { historyWords, popularWords } = this.data;
|
||||
const allWords = [...historyWords, ...popularWords];
|
||||
|
||||
// 过滤出包含关键词的建议
|
||||
const suggestions = allWords
|
||||
.filter(word => word.toLowerCase().includes(keyword.toLowerCase()))
|
||||
.filter((word, index, arr) => arr.indexOf(word) === index) // 去重
|
||||
.slice(0, 8); // 最多显示8条建议
|
||||
|
||||
this.setData({
|
||||
searchSuggestions: suggestions,
|
||||
showSuggestions: suggestions.length > 0,
|
||||
});
|
||||
},
|
||||
|
||||
// 点击搜索建议
|
||||
onSuggestionTap(e) {
|
||||
const { suggestion } = e.currentTarget.dataset;
|
||||
this.performSearch(suggestion);
|
||||
},
|
||||
|
||||
// 清空搜索输入
|
||||
onSearchClear() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
showSuggestions: false,
|
||||
searchSuggestions: [],
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索输入获得焦点
|
||||
onSearchFocus() {
|
||||
const { searchValue } = this.data;
|
||||
if (searchValue.trim()) {
|
||||
this.getSearchSuggestions(searchValue.trim());
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索输入失去焦点
|
||||
onSearchBlur() {
|
||||
// 延迟隐藏建议,让点击事件能够触发
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
showSuggestions: false,
|
||||
});
|
||||
}, 200);
|
||||
},
|
||||
|
||||
confirm() {
|
||||
if (this.deleteType === 0) {
|
||||
// 清空所有历史记录
|
||||
this.clearLocalHistory();
|
||||
} else {
|
||||
// 删除单条历史记录
|
||||
this.deleteHistoryItem(this.deleteIndex);
|
||||
}
|
||||
this.setData({
|
||||
dialogShow: false,
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
this.setData({ dialogShow: false });
|
||||
},
|
||||
|
||||
handleClearHistory() {
|
||||
const { dialog } = this.data;
|
||||
this.deleteType = 1;
|
||||
this.setData({
|
||||
dialog: {
|
||||
...dialog,
|
||||
message: '确认删除所有历史记录',
|
||||
},
|
||||
dialogShow: true,
|
||||
});
|
||||
},
|
||||
|
||||
deleteCurr(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
this.deleteIndex = index;
|
||||
this.deleteType = 1;
|
||||
this.setData({
|
||||
dialogShow: true,
|
||||
'dialog.message': '确认删除这条搜索记录?',
|
||||
});
|
||||
},
|
||||
|
||||
handleHistoryTap(e) {
|
||||
const { historyWords } = this.data;
|
||||
const { dataset } = e.currentTarget;
|
||||
const _searchValue = historyWords[dataset.index || 0] || '';
|
||||
if (_searchValue) {
|
||||
this.performSearch(_searchValue);
|
||||
}
|
||||
},
|
||||
|
||||
handlePopularTap(e) {
|
||||
const { popularword } = e.currentTarget.dataset;
|
||||
this.performSearch(popularword);
|
||||
},
|
||||
|
||||
// 执行搜索
|
||||
performSearch(keyword) {
|
||||
if (!keyword || !keyword.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入搜索关键词',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const searchValue = keyword.trim();
|
||||
|
||||
// 保存到搜索历史
|
||||
this.saveSearchHistory(searchValue);
|
||||
|
||||
// 跳转到搜索结果页
|
||||
wx.navigateTo({
|
||||
url: `/pages/goods/result/index?searchValue=${encodeURIComponent(searchValue)}`,
|
||||
});
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
// 安全地获取搜索值
|
||||
const value = e?.detail?.value || this.data.searchValue;
|
||||
|
||||
// 确保value是字符串类型
|
||||
const searchValue = typeof value === 'string' ? value.trim() : '';
|
||||
|
||||
// 使用统一的搜索方法
|
||||
this.performSearch(searchValue);
|
||||
},
|
||||
|
||||
// 分享功能
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '商品搜索 - 查找你想要的商品',
|
||||
path: '/pages/goods/search/index'
|
||||
};
|
||||
},
|
||||
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '商品搜索 - 查找你想要的商品'
|
||||
};
|
||||
}
|
||||
});
|
||||
8
miniprogram/pages/goods/search/index.json
Normal file
8
miniprogram/pages/goods/search/index.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "搜索",
|
||||
"usingComponents": {
|
||||
"t-search": "tdesign-miniprogram/search/search",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog"
|
||||
}
|
||||
}
|
||||
83
miniprogram/pages/goods/search/index.wxml
Normal file
83
miniprogram/pages/goods/search/index.wxml
Normal file
@@ -0,0 +1,83 @@
|
||||
<view class="search-page">
|
||||
<view class="search-input-container">
|
||||
<t-search
|
||||
t-class-input-container="t-class__input-container"
|
||||
t-class-input="t-search__input"
|
||||
value="{{searchValue}}"
|
||||
leftIcon=""
|
||||
placeholder="搜索商品"
|
||||
bind:submit="handleSubmit"
|
||||
bind:change="onSearchInput"
|
||||
bind:focus="onSearchFocus"
|
||||
bind:blur="onSearchBlur"
|
||||
bind:clear="onSearchClear"
|
||||
focus
|
||||
>
|
||||
<t-icon slot="left-icon" prefix="wr" name="search" size="40rpx" color="#bbb" />
|
||||
</t-search>
|
||||
|
||||
<!-- 搜索建议 -->
|
||||
<view class="search-suggestions" wx:if="{{showSuggestions}}">
|
||||
<view
|
||||
class="suggestion-item"
|
||||
wx:for="{{searchSuggestions}}"
|
||||
wx:key="*this"
|
||||
data-suggestion="{{item}}"
|
||||
bind:tap="onSuggestionTap"
|
||||
>
|
||||
<t-icon prefix="wr" name="search" size="32rpx" color="#999" />
|
||||
<text class="suggestion-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-wrap">
|
||||
<view class="history-wrap">
|
||||
<view class="search-header">
|
||||
<text class="search-title">历史搜索</text>
|
||||
<text class="search-clear" bind:tap="handleClearHistory">清除</text>
|
||||
</view>
|
||||
<view class="search-content">
|
||||
<view
|
||||
class="search-item"
|
||||
hover-class="hover-history-item"
|
||||
wx:for="{{historyWords}}"
|
||||
bind:tap="handleHistoryTap"
|
||||
bindlongpress="deleteCurr"
|
||||
data-index="{{index}}"
|
||||
data-historyword="{{item}}"
|
||||
wx:key="*this"
|
||||
>
|
||||
{{item}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popular-wrap">
|
||||
<view class="search-header">
|
||||
<text class="search-title">热门搜索</text>
|
||||
</view>
|
||||
<view class="search-content">
|
||||
<view
|
||||
class="search-item"
|
||||
hover-class="hover-history-item"
|
||||
wx:for="{{popularWords}}"
|
||||
bind:tap="handlePopularTap"
|
||||
data-index="{{index}}"
|
||||
data-popularword="{{item}}"
|
||||
wx:key="*this"
|
||||
>
|
||||
{{item}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<t-dialog
|
||||
visible="{{dialogShow}}"
|
||||
content="{{dialog.message}}"
|
||||
bindconfirm="confirm"
|
||||
bind:close="close"
|
||||
confirm-btn="确定"
|
||||
cancel-btn="{{dialog.showCancelButton ? '取消' : null}}"
|
||||
t-class-confirm="dialog__button-confirm"
|
||||
t-class-cancel="dialog__button-cancel"
|
||||
/>
|
||||
</view>
|
||||
118
miniprogram/pages/goods/search/index.wxss
Normal file
118
miniprogram/pages/goods/search/index.wxss
Normal file
@@ -0,0 +1,118 @@
|
||||
.search-page {
|
||||
box-sizing: border-box;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.search-page .t-class__input-container {
|
||||
height: 64rpx !important;
|
||||
border-radius: 32rpx !important;
|
||||
}
|
||||
|
||||
.search-page .t-search__input {
|
||||
font-size: 28rpx !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.search-input-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-suggestions {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.suggestion-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.suggestion-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.suggestion-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-page .search-wrap {
|
||||
margin-top: 44rpx;
|
||||
}
|
||||
|
||||
.search-page .history-wrap {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-page .search-header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-page .search-title {
|
||||
font-size: 30rpx;
|
||||
font-family: PingFangSC-Semibold, PingFang SC;
|
||||
font-weight: 600;
|
||||
color: rgba(51, 51, 51, 1);
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.search-page .search-clear {
|
||||
font-size: 24rpx;
|
||||
font-family: PingFang SC;
|
||||
line-height: 32rpx;
|
||||
color: #999999;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.search-page .search-content {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.search-page .search-item {
|
||||
color: #333333;
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
font-weight: normal;
|
||||
margin-right: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 38rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.search-page .hover-history-item {
|
||||
position: relative;
|
||||
top: 3rpx;
|
||||
left: 3rpx;
|
||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1) inset;
|
||||
}
|
||||
|
||||
.add-notes__confirm {
|
||||
color: #fa4126 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user