42 changed files with 7199 additions and 83 deletions
@ -0,0 +1,236 @@ |
|||||
|
const API = require('../../utils/api.js'); |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
content: '', |
||||
|
images: [], |
||||
|
selectedTopic: null, |
||||
|
showTopicModal: false, |
||||
|
hotTopics: [] |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.loadHotTopics(); |
||||
|
}, |
||||
|
|
||||
|
loadHotTopics() { |
||||
|
API.getHotTopics().then(res => { |
||||
|
if (res.data && res.data.length > 0) { |
||||
|
this.setData({ |
||||
|
hotTopics: res.data |
||||
|
}); |
||||
|
} else { |
||||
|
// 使用默认热门话题
|
||||
|
this.setData({ |
||||
|
hotTopics: [ |
||||
|
{ id: 1, name: '今天你吃蛋了么?', count: 123 }, |
||||
|
{ id: 2, name: '日常分享', count: 456 }, |
||||
|
{ id: 3, name: '鸡蛋行情', count: 789 }, |
||||
|
{ id: 4, name: '养殖经验', count: 321 }, |
||||
|
{ id: 5, name: '美食分享', count: 654 } |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.error('加载热门话题失败:', err); |
||||
|
// 出错时使用默认热门话题
|
||||
|
this.setData({ |
||||
|
hotTopics: [ |
||||
|
{ id: 1, name: '今天你吃蛋了么?', count: 123 }, |
||||
|
{ id: 2, name: '日常分享', count: 456 }, |
||||
|
{ id: 3, name: '鸡蛋行情', count: 789 }, |
||||
|
{ id: 4, name: '养殖经验', count: 321 }, |
||||
|
{ id: 5, name: '美食分享', count: 654 } |
||||
|
] |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onContentChange(e) { |
||||
|
this.setData({ |
||||
|
content: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
chooseImage() { |
||||
|
if (this.data.images.length >= 5) { |
||||
|
wx.showToast({ |
||||
|
title: '最多只能上传5张图片', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
wx.chooseImage({ |
||||
|
count: 5 - this.data.images.length, |
||||
|
sizeType: ['compressed'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
success: (res) => { |
||||
|
this.setData({ |
||||
|
images: [...this.data.images, ...res.tempFilePaths] |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
deleteImage(e) { |
||||
|
const index = e.currentTarget.dataset.index; |
||||
|
const images = this.data.images.filter((_, i) => i !== index); |
||||
|
this.setData({ |
||||
|
images |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
showTopicPicker() { |
||||
|
this.setData({ |
||||
|
showTopicModal: true |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
hideTopicPicker() { |
||||
|
this.setData({ |
||||
|
showTopicModal: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
selectTopic(e) { |
||||
|
const topic = e.currentTarget.dataset.topic; |
||||
|
this.setData({ |
||||
|
selectedTopic: topic, |
||||
|
showTopicModal: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
stopPropagation() { |
||||
|
// 阻止事件冒泡
|
||||
|
}, |
||||
|
|
||||
|
cancel() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
submit() { |
||||
|
console.log('点击了发布按钮,当前数据:', { |
||||
|
content: this.data.content, |
||||
|
images: this.data.images, |
||||
|
selectedTopic: this.data.selectedTopic |
||||
|
}); |
||||
|
|
||||
|
if (!this.data.content.trim() && !this.data.selectedTopic) { |
||||
|
console.log('验证失败:文本内容和话题都为空'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.log('验证通过,开始发布'); |
||||
|
|
||||
|
wx.showLoading({ |
||||
|
title: '发布中...' |
||||
|
}); |
||||
|
|
||||
|
// 先上传图片,获取永久 URL
|
||||
|
this.uploadImages(this.data.images) |
||||
|
.then(uploadedImages => { |
||||
|
console.log('图片上传完成,上传的图片数量:', uploadedImages.length); |
||||
|
|
||||
|
// 获取用户ID
|
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
if (!userId) { |
||||
|
throw new Error('用户未登录'); |
||||
|
} |
||||
|
|
||||
|
// 获取用户电话号码
|
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
const phoneNumber = userInfo?.phoneNumber || wx.getStorageSync('phoneNumber'); |
||||
|
console.log('获取到的用户电话号码:', phoneNumber); |
||||
|
|
||||
|
const postData = { |
||||
|
user_id: userId, |
||||
|
phone: phoneNumber, |
||||
|
content: this.data.content, |
||||
|
images: uploadedImages, |
||||
|
topic: this.data.selectedTopic?.name || this.data.selectedTopic |
||||
|
}; |
||||
|
console.log('准备发送的发布数据:', postData); |
||||
|
|
||||
|
return API.createPost(postData); |
||||
|
}) |
||||
|
.then(res => { |
||||
|
console.log('发布成功,服务器返回:', res); |
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '发布成功', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
setTimeout(() => { |
||||
|
wx.navigateBack(); |
||||
|
}, 1000); |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error('发布动态失败:', err); |
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '发布失败,请重试', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 上传图片获取永久 URL
|
||||
|
uploadImages(tempImagePaths) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
if (!tempImagePaths || tempImagePaths.length === 0) { |
||||
|
resolve([]); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const uploadedImages = []; |
||||
|
let uploadedCount = 0; |
||||
|
let hasError = false; |
||||
|
|
||||
|
tempImagePaths.forEach((tempPath, index) => { |
||||
|
wx.uploadFile({ |
||||
|
url: API.BASE_URL + '/api/eggbar/upload', |
||||
|
filePath: tempPath, |
||||
|
name: 'image', |
||||
|
formData: { |
||||
|
index: index, |
||||
|
total: tempImagePaths.length |
||||
|
}, |
||||
|
success: (res) => { |
||||
|
if (res.statusCode === 200) { |
||||
|
try { |
||||
|
const data = JSON.parse(res.data); |
||||
|
if (data.success && data.imageUrl) { |
||||
|
uploadedImages.push(data.imageUrl); |
||||
|
} else { |
||||
|
console.error('上传图片失败:', data.message || '未知错误'); |
||||
|
hasError = true; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('解析上传响应失败:', e); |
||||
|
hasError = true; |
||||
|
} |
||||
|
} else { |
||||
|
console.error('上传图片失败,HTTP状态码:', res.statusCode); |
||||
|
hasError = true; |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('上传图片失败:', err); |
||||
|
hasError = true; |
||||
|
}, |
||||
|
complete: () => { |
||||
|
uploadedCount++; |
||||
|
if (uploadedCount === tempImagePaths.length) { |
||||
|
if (hasError && uploadedImages.length === 0) { |
||||
|
reject(new Error('所有图片上传失败,请重试')); |
||||
|
} else { |
||||
|
resolve(uploadedImages); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": false, |
||||
|
"backgroundTextStyle": "dark", |
||||
|
"backgroundColor": "#f8f8f8", |
||||
|
"navigationBarBackgroundColor": "#ffffff", |
||||
|
"navigationBarTitleText": "发布动态", |
||||
|
"navigationBarTextStyle": "black" |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
<view> |
||||
|
<view class="header"> |
||||
|
<text class="title">发布动态</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form"> |
||||
|
<view class="form-item"> |
||||
|
<textarea |
||||
|
class="textarea" |
||||
|
placeholder="分享你的想法..." |
||||
|
placeholder-class="placeholder" |
||||
|
value="{{content}}" |
||||
|
bindinput="onContentChange" |
||||
|
maxlength="500" |
||||
|
></textarea> |
||||
|
<text class="counter">{{content.length}}/500</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="label">添加图片</view> |
||||
|
<view class="image-uploader"> |
||||
|
<view class="upload-area" bindtap="chooseImage"> |
||||
|
<text class="upload-icon">+</text> |
||||
|
<text class="upload-text">选择图片</text> |
||||
|
</view> |
||||
|
<view class="image-list"> |
||||
|
<view class="image-item" wx:for="{{images}}" wx:key="index"> |
||||
|
<image class="image" src="{{item}}" mode="aspectFill"></image> |
||||
|
<view class="image-delete" bindtap="deleteImage" data-index="{{index}}"> |
||||
|
<text>×</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="label">选择话题</view> |
||||
|
<view class="topic-picker" bindtap="showTopicPicker"> |
||||
|
<text class="topic-text">{{selectedTopic ? '#' + selectedTopic : '选择话题'}}</text> |
||||
|
<text class="topic-arrow">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="footer"> |
||||
|
<button class="cancel-btn" bindtap="cancel">取消</button> |
||||
|
<button class="submit-btn" bindtap="submit" disabled="{{!content.trim() && !selectedTopic}}"> |
||||
|
发布 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 话题选择器弹窗 --> |
||||
|
<view wx:if="{{showTopicModal}}" class="modal-overlay" bindtap="hideTopicPicker"> |
||||
|
<view class="modal-container" catchtap="stopPropagation"> |
||||
|
<view class="modal-header"> |
||||
|
<text class="modal-title">选择话题</text> |
||||
|
<text class="modal-close" bindtap="hideTopicPicker">×</text> |
||||
|
</view> |
||||
|
<view class="modal-content"> |
||||
|
<view class="topic-item" wx:for="{{hotTopics}}" wx:key="id" bindtap="selectTopic" data-topic="{{item.name}}"> |
||||
|
<text class="topic-name">#{{item.name}}</text> |
||||
|
<text class="topic-count">{{item.count}}人讨论</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,281 @@ |
|||||
|
page { |
||||
|
background-color: #f5f5f5; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
min-height: 100vh; |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(102, 126, 234, 0.3); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
.form { |
||||
|
background: #ffffff; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
} |
||||
|
|
||||
|
.form-item { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333333; |
||||
|
margin-bottom: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.textarea { |
||||
|
width: 100%; |
||||
|
min-height: 200rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
border: 2rpx solid #f0f0f0; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 20rpx; |
||||
|
box-sizing: border-box; |
||||
|
resize: none; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
|
||||
|
.placeholder { |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.counter { |
||||
|
display: block; |
||||
|
text-align: right; |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.image-uploader { |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.upload-area { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border: 2rpx dashed #e0e0e0; |
||||
|
border-radius: 12rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: #fafafa; |
||||
|
margin-bottom: 15rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.upload-area:active { |
||||
|
background: #f0f0f0; |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
.upload-icon { |
||||
|
font-size: 48rpx; |
||||
|
color: #999999; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.upload-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.image-list { |
||||
|
display: flex; |
||||
|
gap: 15rpx; |
||||
|
flex-wrap: wrap; |
||||
|
margin-top: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.image-item { |
||||
|
position: relative; |
||||
|
width: 150rpx; |
||||
|
height: 150rpx; |
||||
|
border-radius: 12rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.image-delete { |
||||
|
position: absolute; |
||||
|
top: 5rpx; |
||||
|
right: 5rpx; |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
background: rgba(0, 0, 0, 0.6); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
color: #ffffff; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.image-delete:active { |
||||
|
background: rgba(0, 0, 0, 0.8); |
||||
|
transform: scale(0.9); |
||||
|
} |
||||
|
|
||||
|
.topic-picker { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 20rpx; |
||||
|
border: 2rpx solid #f0f0f0; |
||||
|
border-radius: 12rpx; |
||||
|
background: #fafafa; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.topic-picker:active { |
||||
|
background: #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.topic-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.topic-arrow { |
||||
|
font-size: 20rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
display: flex; |
||||
|
padding: 20rpx; |
||||
|
background: #ffffff; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.cancel-btn { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
margin-right: 15rpx; |
||||
|
background: #f5f5f5; |
||||
|
color: #333333; |
||||
|
border: none; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.submit-btn { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
color: #ffffff; |
||||
|
border: none; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.submit-btn:disabled { |
||||
|
background: #e0e0e0; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.modal-overlay { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.modal-container { |
||||
|
width: 80%; |
||||
|
max-height: 70vh; |
||||
|
background: #ffffff; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.modal-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 20rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.modal-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.modal-close { |
||||
|
font-size: 36rpx; |
||||
|
color: #999999; |
||||
|
padding: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-content { |
||||
|
padding: 20rpx; |
||||
|
max-height: 50vh; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.topic-item { |
||||
|
padding: 20rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.topic-item:active { |
||||
|
background: #f0f7ff; |
||||
|
} |
||||
|
|
||||
|
.topic-name { |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.topic-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
@ -0,0 +1,347 @@ |
|||||
|
const API = require('../../utils/api.js'); |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
hotTopics: [], |
||||
|
posts: [], |
||||
|
loading: false, |
||||
|
hasMore: true, |
||||
|
page: 1, |
||||
|
pageSize: 10 |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.loadHotTopics(); |
||||
|
this.loadPosts(); |
||||
|
}, |
||||
|
|
||||
|
onShow() { |
||||
|
this.setData({ |
||||
|
page: 1, |
||||
|
hasMore: true, |
||||
|
posts: [] |
||||
|
}); |
||||
|
this.loadPosts(); |
||||
|
}, |
||||
|
|
||||
|
onReachBottom() { |
||||
|
if (this.data.hasMore && !this.data.loading) { |
||||
|
this.loadPosts(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onPullDownRefresh() { |
||||
|
this.setData({ |
||||
|
page: 1, |
||||
|
hasMore: true, |
||||
|
posts: [] |
||||
|
}); |
||||
|
this.loadHotTopics(); |
||||
|
this.loadPosts(); |
||||
|
wx.stopPullDownRefresh(); |
||||
|
}, |
||||
|
|
||||
|
loadHotTopics() { |
||||
|
API.getHotTopics().then(res => { |
||||
|
if (res.data && res.data.length > 0) { |
||||
|
this.setData({ |
||||
|
hotTopics: res.data |
||||
|
}); |
||||
|
} else { |
||||
|
// 使用默认热门话题
|
||||
|
this.setData({ |
||||
|
hotTopics: [ |
||||
|
{ id: 1, name: '今天你吃蛋了么?', count: 123 }, |
||||
|
{ id: 2, name: '日常分享', count: 456 }, |
||||
|
{ id: 3, name: '鸡蛋行情', count: 789 }, |
||||
|
{ id: 4, name: '养殖经验', count: 321 }, |
||||
|
{ id: 5, name: '美食分享', count: 654 } |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.error('加载热门话题失败:', err); |
||||
|
// 出错时使用默认热门话题
|
||||
|
this.setData({ |
||||
|
hotTopics: [ |
||||
|
{ id: 1, name: '今天你吃蛋了么?', count: 123 }, |
||||
|
{ id: 2, name: '日常分享', count: 456 }, |
||||
|
{ id: 3, name: '鸡蛋行情', count: 789 }, |
||||
|
{ id: 4, name: '养殖经验', count: 321 }, |
||||
|
{ id: 5, name: '美食分享', count: 654 } |
||||
|
] |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
loadPosts() { |
||||
|
if (this.data.loading) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
loading: true |
||||
|
}); |
||||
|
|
||||
|
// 获取用户电话号码
|
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
const phoneNumber = userInfo?.phoneNumber || wx.getStorageSync('phoneNumber'); |
||||
|
|
||||
|
// 只有当电话号码存在时才传递,避免传递空值
|
||||
|
const params = { |
||||
|
page: this.data.page, |
||||
|
pageSize: this.data.pageSize |
||||
|
}; |
||||
|
if (phoneNumber) { |
||||
|
params.phone = phoneNumber; |
||||
|
} |
||||
|
|
||||
|
API.getPosts(params).then(res => { |
||||
|
console.log('后端返回的完整响应:', res); |
||||
|
// 正确处理后端返回的响应格式
|
||||
|
let newPosts = res.data && res.data.posts ? res.data.posts : []; |
||||
|
console.log('后端返回的动态数量:', newPosts.length); |
||||
|
console.log('后端返回的动态数据:', newPosts); |
||||
|
|
||||
|
// 处理images字段,确保它是一个数组
|
||||
|
newPosts = newPosts.map(post => { |
||||
|
if (post.images) { |
||||
|
// 如果images是字符串,尝试解析为JSON数组
|
||||
|
if (typeof post.images === 'string') { |
||||
|
try { |
||||
|
post.images = JSON.parse(post.images); |
||||
|
// 确保解析后是数组
|
||||
|
if (!Array.isArray(post.images)) { |
||||
|
post.images = []; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
// 解析失败,设置为空数组
|
||||
|
post.images = []; |
||||
|
} |
||||
|
} else if (!Array.isArray(post.images)) { |
||||
|
// 如果不是字符串也不是数组,设置为空数组
|
||||
|
post.images = []; |
||||
|
} |
||||
|
} else { |
||||
|
// 如果images不存在,设置为空数组
|
||||
|
post.images = []; |
||||
|
} |
||||
|
return post; |
||||
|
}); |
||||
|
|
||||
|
// 如果是第一页且没有数据,使用默认动态数据
|
||||
|
if (this.data.page === 1 && (!newPosts || newPosts.length === 0)) { |
||||
|
newPosts = [ |
||||
|
{ |
||||
|
id: 1, |
||||
|
user_id: '1', |
||||
|
content: '今天的鸡蛋质量真好,客户都很满意!', |
||||
|
images: [], |
||||
|
likes: 12, |
||||
|
comments: 3, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '鸡蛋养殖户', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
user_id: '2', |
||||
|
content: '分享一下我的养殖经验,希望对大家有帮助', |
||||
|
images: [], |
||||
|
likes: 8, |
||||
|
comments: 5, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '养殖专家', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
}, |
||||
|
{ |
||||
|
id: 3, |
||||
|
user_id: '3', |
||||
|
content: '鸡蛋行情不错,今天卖了个好价钱', |
||||
|
images: [], |
||||
|
likes: 15, |
||||
|
comments: 2, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '蛋商小王', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
} |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 根据后端返回的分页信息判断是否还有更多数据
|
||||
|
const shouldHasMore = res.data && res.data.pagination ? |
||||
|
this.data.page < res.data.pagination.totalPages : |
||||
|
this.data.page < 3; |
||||
|
|
||||
|
this.setData({ |
||||
|
posts: this.data.page === 1 ? newPosts : [...this.data.posts, ...newPosts], |
||||
|
loading: false, |
||||
|
hasMore: shouldHasMore, |
||||
|
page: this.data.page + 1 |
||||
|
}); |
||||
|
}).catch(err => { |
||||
|
console.error('加载动态失败:', err); |
||||
|
|
||||
|
// 出错时使用默认动态数据
|
||||
|
if (this.data.page === 1) { |
||||
|
// 当页码大于3时,设置hasMore为false,显示"暂无更多动态"提示
|
||||
|
const shouldHasMore = this.data.page < 3; |
||||
|
|
||||
|
this.setData({ |
||||
|
posts: [ |
||||
|
{ |
||||
|
id: 1, |
||||
|
user_id: '1', |
||||
|
content: '今天的鸡蛋质量真好,客户都很满意!', |
||||
|
images: [], |
||||
|
likes: 12, |
||||
|
comments: 3, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '鸡蛋养殖户', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
user_id: '2', |
||||
|
content: '分享一下我的养殖经验,希望对大家有帮助', |
||||
|
images: [], |
||||
|
likes: 8, |
||||
|
comments: 5, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '养殖专家', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
}, |
||||
|
{ |
||||
|
id: 3, |
||||
|
user_id: '3', |
||||
|
content: '鸡蛋行情不错,今天卖了个好价钱', |
||||
|
images: [], |
||||
|
likes: 15, |
||||
|
comments: 2, |
||||
|
created_at: new Date().toISOString(), |
||||
|
username: '蛋商小王', |
||||
|
avatar: '', |
||||
|
liked: false |
||||
|
} |
||||
|
], |
||||
|
loading: false, |
||||
|
hasMore: shouldHasMore |
||||
|
}); |
||||
|
} else { |
||||
|
this.setData({ |
||||
|
loading: false |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
viewTopic(e) { |
||||
|
const topic = e.currentTarget.dataset.topic; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/eggbar/topic-detail?id=${topic.id}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
viewPost(e) { |
||||
|
const post = e.currentTarget.dataset.post; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/eggbar/post-detail?id=${post.id}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
likePost(e) { |
||||
|
const postId = e.currentTarget.dataset.id; |
||||
|
|
||||
|
// 获取用户电话号码
|
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
const phoneNumber = userInfo?.phoneNumber || wx.getStorageSync('phoneNumber'); |
||||
|
|
||||
|
if (!phoneNumber) { |
||||
|
wx.showToast({ |
||||
|
title: '请先登录获取电话号码', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 前端临时更新状态
|
||||
|
const posts = this.data.posts.map(post => { |
||||
|
if (post.id === postId) { |
||||
|
return { |
||||
|
...post, |
||||
|
liked: !post.liked, |
||||
|
likes: post.liked ? post.likes - 1 : post.likes + 1 |
||||
|
}; |
||||
|
} |
||||
|
return post; |
||||
|
}); |
||||
|
|
||||
|
this.setData({ posts }); |
||||
|
|
||||
|
// 调用API
|
||||
|
API.likePost(postId, phoneNumber).then(res => { |
||||
|
console.log('点赞成功:', res); |
||||
|
wx.showToast({ |
||||
|
title: res.message || '操作成功', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
}).catch(err => { |
||||
|
console.error('点赞失败:', err); |
||||
|
|
||||
|
// 恢复原始状态
|
||||
|
const originalPosts = this.data.posts.map(post => { |
||||
|
if (post.id === postId) { |
||||
|
return { |
||||
|
...post, |
||||
|
liked: !post.liked, |
||||
|
likes: post.liked ? post.likes + 1 : post.likes - 1 |
||||
|
}; |
||||
|
} |
||||
|
return post; |
||||
|
}); |
||||
|
|
||||
|
this.setData({ posts: originalPosts }); |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '操作失败,请重试', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
commentPost(e) { |
||||
|
const postId = e.currentTarget.dataset.id; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/eggbar/post-detail?id=${postId}&focusComment=true` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
sharePost(e) { |
||||
|
const postId = e.currentTarget.dataset.id; |
||||
|
wx.showShareMenu({ |
||||
|
withShareTicket: true, |
||||
|
success: () => { |
||||
|
console.log('分享成功'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
createPost() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/eggbar/create-post' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onShareAppMessage() { |
||||
|
return { |
||||
|
title: '蛋吧 - 鸡蛋行业交流社区', |
||||
|
path: '/pages/eggbar/eggbar', |
||||
|
imageUrl: '/images/eggbar-share.jpg' |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
"navigation-bar": "/components/navigation-bar/navigation-bar" |
||||
|
}, |
||||
|
"enablePullDownRefresh": false, |
||||
|
"backgroundTextStyle": "dark", |
||||
|
"backgroundColor": "#f8f8f8", |
||||
|
"navigationBarBackgroundColor": "#ffffff", |
||||
|
"navigationBarTitleText": "蛋吧", |
||||
|
"navigationBarTextStyle": "black", |
||||
|
"navigationStyle": "custom" |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
<view> |
||||
|
|
||||
|
|
||||
|
<view class="content"> |
||||
|
<view class="header"> |
||||
|
<text class="title">蛋吧</text> |
||||
|
<text class="subtitle">欢迎来到蛋吧社区</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="section"> |
||||
|
<view class="section-title"> |
||||
|
<text class="title-text">热门话题</text> |
||||
|
</view> |
||||
|
<view class="topic-list"> |
||||
|
<view class="topic-item" wx:for="{{hotTopics}}" wx:key="id" bindtap="viewTopic" data-topic="{{item}}"> |
||||
|
<view class="topic-rank">{{index + 1}}</view> |
||||
|
<view class="topic-info"> |
||||
|
<text class="topic-name">{{item.name}}</text> |
||||
|
<text class="topic-count">{{item.count}}人讨论</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="section"> |
||||
|
<view class="section-title"> |
||||
|
<text class="title-text">最新动态</text> |
||||
|
</view> |
||||
|
<view class="post-list"> |
||||
|
<view class="post-item" wx:for="{{posts}}" wx:key="id" bindtap="viewPost" data-post="{{item}}"> |
||||
|
<view class="post-header"> |
||||
|
<image class="post-avatar" src="{{item.avatar}}" mode="aspectFill"></image> |
||||
|
<view class="post-user"> |
||||
|
<text class="post-username">{{item.username}}</text> |
||||
|
<text class="post-time">{{item.time}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="post-content"> |
||||
|
<text class="post-text">{{item.content}}</text> |
||||
|
<view class="post-images" wx:if="{{item.images && item.images.length > 0}}"> |
||||
|
<image class="post-image" wx:for="{{item.images}}" wx:for-item="img" wx:key="index" src="{{img}}" mode="aspectFill"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="post-footer"> |
||||
|
<view class="post-action" bindtap="likePost" data-id="{{item.id}}"> |
||||
|
<text class="action-icon">{{item.liked ? '❤️' : '🤍'}}</text> |
||||
|
<text class="action-text">{{item.likes}}</text> |
||||
|
</view> |
||||
|
<view class="post-action" bindtap="commentPost" data-id="{{item.id}}"> |
||||
|
<text class="action-icon">💬</text> |
||||
|
<text class="action-text">{{item.comments}}</text> |
||||
|
</view> |
||||
|
<view class="post-action" bindtap="sharePost" data-id="{{item.id}}"> |
||||
|
<text class="action-icon">📤</text> |
||||
|
<text class="action-text">分享</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="loading" wx:if="{{loading}}"> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="no-more" wx:if="{{!hasMore && posts.length > 0}}"> |
||||
|
<text>暂无更多动态</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="empty" wx:if="{{posts.length === 0 && !loading}}"> |
||||
|
<text class="empty-icon">📭</text> |
||||
|
<text class="empty-text">暂无动态</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="fab-button" bindtap="createPost"> |
||||
|
<text class="fab-icon">✏️</text> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,270 @@ |
|||||
|
page { |
||||
|
background-color: #f5f5f5; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
min-height: 100vh; |
||||
|
padding-bottom: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 40rpx 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(102, 126, 234, 0.3); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
display: block; |
||||
|
font-size: 48rpx; |
||||
|
font-weight: bold; |
||||
|
color: #ffffff; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.subtitle { |
||||
|
display: block; |
||||
|
font-size: 28rpx; |
||||
|
color: rgba(255, 255, 255, 0.9); |
||||
|
} |
||||
|
|
||||
|
.section { |
||||
|
background: #ffffff; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
margin-bottom: 20rpx; |
||||
|
padding-bottom: 15rpx; |
||||
|
border-bottom: 2rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.title-text { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.topic-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.topic-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 20rpx; |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 12rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.topic-item:active { |
||||
|
transform: scale(0.98); |
||||
|
background: #e9ecef; |
||||
|
} |
||||
|
|
||||
|
.topic-rank { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
border-radius: 50%; |
||||
|
font-size: 24rpx; |
||||
|
font-weight: bold; |
||||
|
color: #ffffff; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.topic-info { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 5rpx; |
||||
|
} |
||||
|
|
||||
|
.topic-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.topic-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.post-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.post-item { |
||||
|
background: #ffffff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 25rpx; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.post-item:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.post-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.post-avatar { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
margin-right: 15rpx; |
||||
|
background: #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.post-user { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 5rpx; |
||||
|
} |
||||
|
|
||||
|
.post-username { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333333; |
||||
|
} |
||||
|
|
||||
|
.post-time { |
||||
|
font-size: 24rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.post-content { |
||||
|
margin-bottom: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.post-text { |
||||
|
display: block; |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
line-height: 1.6; |
||||
|
margin-bottom: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.post-images { |
||||
|
display: flex; |
||||
|
gap: 10rpx; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.post-image { |
||||
|
width: 200rpx; |
||||
|
height: 200rpx; |
||||
|
border-radius: 12rpx; |
||||
|
background: #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.post-footer { |
||||
|
display: flex; |
||||
|
justify-content: space-around; |
||||
|
padding-top: 15rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.post-action { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8rpx; |
||||
|
padding: 10rpx 20rpx; |
||||
|
border-radius: 20rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.post-action:active { |
||||
|
background: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.action-icon { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.action-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666666; |
||||
|
} |
||||
|
|
||||
|
.loading { |
||||
|
text-align: center; |
||||
|
padding: 40rpx; |
||||
|
color: #999999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.no-more { |
||||
|
text-align: center; |
||||
|
padding: 40rpx; |
||||
|
color: #999999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.empty { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 100rpx 20rpx; |
||||
|
} |
||||
|
|
||||
|
.empty-icon { |
||||
|
font-size: 120rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #999999; |
||||
|
} |
||||
|
|
||||
|
.fab-button { |
||||
|
position: fixed; |
||||
|
right: 30rpx; |
||||
|
bottom: 150rpx; |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4); |
||||
|
z-index: 1000; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.fab-button:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: 0 4rpx 15rpx rgba(102, 126, 234, 0.3); |
||||
|
} |
||||
|
|
||||
|
.fab-icon { |
||||
|
font-size: 48rpx; |
||||
|
} |
||||
@ -0,0 +1,496 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productName: '', |
||||
|
specifications: [], |
||||
|
loading: false, |
||||
|
error: '' |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
let productName = ''; |
||||
|
// 首先检查URL参数
|
||||
|
if (options.productName) { |
||||
|
productName = options.productName; |
||||
|
} else { |
||||
|
// 然后检查本地存储(用于wx.switchTab导航)
|
||||
|
productName = wx.getStorageSync('selectedProductName') || ''; |
||||
|
// 清除本地存储中的商品名称,避免影响下次进入
|
||||
|
if (productName) { |
||||
|
wx.removeStorageSync('selectedProductName'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (productName) { |
||||
|
this.setData({ productName: productName }); |
||||
|
this.loadSpecifications(productName); |
||||
|
} else { |
||||
|
// 如果没有商品名称参数,跳转到商品列表页面
|
||||
|
wx.redirectTo({ |
||||
|
url: '/pages/evaluate2/product-list' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
loadSpecifications(productName) { |
||||
|
this.setData({ |
||||
|
loading: true, |
||||
|
specifications: [], |
||||
|
error: '' // 清空错误信息,确保加载时只显示加载状态
|
||||
|
}); |
||||
|
|
||||
|
// 尝试从本地存储获取商品规格映射数据
|
||||
|
const productSpecsMap = wx.getStorageSync('evaluate2ProductSpecsMap') || {}; |
||||
|
|
||||
|
if (productSpecsMap && productSpecsMap[productName]) { |
||||
|
console.log('从本地存储获取商品规格数据'); |
||||
|
|
||||
|
// 提取该商品的规格和价格信息
|
||||
|
const productSpecs = productSpecsMap[productName]; |
||||
|
|
||||
|
// 处理规格数据
|
||||
|
const specPriceMap = {}; |
||||
|
productSpecs.forEach(item => { |
||||
|
const specStr = item.specification; |
||||
|
const price = item.price; |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (!price || price.trim() === '') { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (specStr.length > 0) { |
||||
|
// 处理逗号分隔的多个规格
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
if (spec.includes(',')) { |
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const prices = price.split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (prices.length === 0) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0) { |
||||
|
// 确保价格索引不超出范围
|
||||
|
const priceIndex = index % prices.length; |
||||
|
const matchedPrice = prices[priceIndex] || ''; |
||||
|
|
||||
|
// 只有当价格不为空时才添加该规格
|
||||
|
if (matchedPrice && matchedPrice.trim() !== '') { |
||||
|
// 收集相同规格的所有价格,以便计算平均值
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(matchedPrice); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 转换为规格对象数组
|
||||
|
const specifications = Object.keys(specPriceMap).map(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
|
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
|
||||
|
// 处理每个价格
|
||||
|
const processedPrices = prices.map(price => { |
||||
|
if (!price || price.trim() === '' || isNaN(parseFloat(price))) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const priceValue = parseFloat(price); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
return specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
return (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
} |
||||
|
// 价格>=10的直接使用
|
||||
|
return priceValue; |
||||
|
}).filter(price => price > 0); // 过滤掉0值
|
||||
|
|
||||
|
// 计算处理后价格的平均值
|
||||
|
let finalPrice = 0; |
||||
|
let finalPriceText = ''; |
||||
|
|
||||
|
if (processedPrices.length > 0) { |
||||
|
const sum = processedPrices.reduce((acc, price) => acc + price, 0); |
||||
|
finalPrice = sum / processedPrices.length; |
||||
|
finalPriceText = finalPrice.toFixed(2); |
||||
|
} |
||||
|
|
||||
|
const specObj = { |
||||
|
name: spec, |
||||
|
price: finalPriceText, |
||||
|
priceText: finalPriceText, |
||||
|
finalPrice: finalPrice, |
||||
|
finalPriceText: finalPriceText |
||||
|
}; |
||||
|
|
||||
|
return specObj; |
||||
|
}); |
||||
|
|
||||
|
// 对规格进行排序
|
||||
|
specifications.sort((a, b) => { |
||||
|
// 解析两个规格
|
||||
|
const specA = this.parseSpecification(a.name); |
||||
|
const specB = this.parseSpecification(b.name); |
||||
|
|
||||
|
// 如果有一个规格解析失败,保持原顺序
|
||||
|
if (!specA || !specB) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 1. 按类型排序:净重在前,毛重在后
|
||||
|
if (specA.type !== specB.type) { |
||||
|
return specA.type === '净重' ? -1 : 1; |
||||
|
} |
||||
|
|
||||
|
// 2. 按规格最小值排序:从小到大
|
||||
|
return specA.min - specB.min; |
||||
|
}); |
||||
|
|
||||
|
this.setData({ |
||||
|
specifications: specifications, |
||||
|
error: '', // 清除之前的错误
|
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
} else { |
||||
|
// 如果本地存储中没有数据,尝试从本地存储获取原始商品数据
|
||||
|
const allProducts = wx.getStorageSync('allProducts') || []; |
||||
|
|
||||
|
if (allProducts.length > 0) { |
||||
|
console.log('从本地存储获取原始商品数据'); |
||||
|
this.processSpecifications(productName, allProducts); |
||||
|
} else { |
||||
|
// 如果本地没有数据,再请求服务器
|
||||
|
console.log('本地存储中没有数据,请求服务器获取商品数据'); |
||||
|
const api = require('../../utils/api'); |
||||
|
api.getProducts(1, 1000, 'all', '').then(result => { |
||||
|
// 从返回对象中提取products数组
|
||||
|
const products = result.products || []; |
||||
|
this.processSpecifications(productName, products); |
||||
|
}).catch(err => { |
||||
|
console.error('获取规格失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取规格失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 解析规格,提取类型(净重/毛重)和数值范围
|
||||
|
parseSpecification(spec) { |
||||
|
// 匹配 "净重X-Y" 或 "毛重X-Y" 格式
|
||||
|
const rangeMatch = spec.match(/(净重|毛重)(\d+)-(\d+)/); |
||||
|
if (rangeMatch) { |
||||
|
const type = rangeMatch[1]; // 净重或毛重
|
||||
|
const min = parseFloat(rangeMatch[2]); |
||||
|
const max = parseFloat(rangeMatch[3]); |
||||
|
const avg = (min + max) / 2; |
||||
|
return { |
||||
|
type: type, |
||||
|
min: min, |
||||
|
max: max, |
||||
|
avg: avg |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 匹配 "净重X+" 或 "毛重X+" 格式
|
||||
|
const plusMatch = spec.match(/(净重|毛重)(\d+)\+/); |
||||
|
if (plusMatch) { |
||||
|
const type = plusMatch[1]; // 净重或毛重
|
||||
|
const value = parseFloat(plusMatch[2]); |
||||
|
return { |
||||
|
type: type, |
||||
|
min: value, |
||||
|
max: value, |
||||
|
avg: value |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 匹配 "净重X" 或 "毛重X" 格式
|
||||
|
const singleMatch = spec.match(/(净重|毛重)(\d+)/); |
||||
|
if (singleMatch) { |
||||
|
const type = singleMatch[1]; // 净重或毛重
|
||||
|
const value = parseFloat(singleMatch[2]); |
||||
|
return { |
||||
|
type: type, |
||||
|
min: value, |
||||
|
max: value, |
||||
|
avg: value |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
}, |
||||
|
|
||||
|
processSpecifications(productName, products) { |
||||
|
console.log('处理的商品数据数量:', products.length); |
||||
|
console.log('当前处理的商品名称:', productName); |
||||
|
|
||||
|
// 检查products是否为空
|
||||
|
if (!products || products.length === 0) { |
||||
|
console.error('商品数据为空'); |
||||
|
this.setData({ |
||||
|
error: '商品数据为空', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 过滤出当前商品名称的所有商品
|
||||
|
const filteredProducts = products.filter(product => { |
||||
|
const match = product.productName === productName; |
||||
|
console.log('商品:', product.productName, '规格:', product.specification, '价格:', product.price, '匹配:', match); |
||||
|
return match; |
||||
|
}); |
||||
|
|
||||
|
console.log('过滤后的商品数量:', filteredProducts.length); |
||||
|
console.log('过滤后的商品详情:', filteredProducts); |
||||
|
|
||||
|
// 检查filteredProducts是否为空
|
||||
|
if (filteredProducts.length === 0) { |
||||
|
console.error('未找到商品名称为"' + productName + '"的商品'); |
||||
|
this.setData({ |
||||
|
error: '未找到对应商品名称的商品', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 提取规格和价格,处理可能的空值和空格
|
||||
|
const specPriceMap = {}; |
||||
|
filteredProducts.forEach((product, productIndex) => { |
||||
|
const specStr = (product.specification || product.spec || '').trim(); |
||||
|
const price = product.price || ''; |
||||
|
|
||||
|
console.log(`处理第${productIndex + 1}个商品: 规格字符串='${specStr}', 价格字符串='${price}'`); |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (!price || price.trim() === '') { |
||||
|
console.log(`商品价格为空,跳过处理`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (specStr.length > 0) { |
||||
|
// 处理逗号分隔的多个规格,确保每个规格都被正确分割
|
||||
|
// 首先按逗号分割
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
// 检查规格是否包含多个规格(例如:"净重29-30,净重31-32")
|
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const prices = (price || '').split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
|
||||
|
console.log(`规格数组:`, specs); |
||||
|
console.log(`价格数组:`, prices); |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (prices.length === 0) { |
||||
|
console.log(`价格数组为空,跳过处理`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0) { |
||||
|
// 确保价格索引不超出范围
|
||||
|
const priceIndex = index % prices.length; |
||||
|
const matchedPrice = prices[priceIndex] || ''; |
||||
|
console.log(`规格'${spec}' 配对价格: '${matchedPrice}'`); |
||||
|
|
||||
|
// 只有当价格不为空时才添加该规格
|
||||
|
if (matchedPrice && matchedPrice.trim() !== '') { |
||||
|
// 收集相同规格的所有价格,以便计算平均值
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(matchedPrice); |
||||
|
} else { |
||||
|
console.log(`规格'${spec}' 价格为空,不添加`); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 转换为规格对象数组
|
||||
|
const specifications = Object.keys(specPriceMap).map(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
console.log(`规格'${spec}' 的所有原始价格:`, prices); |
||||
|
|
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
|
||||
|
// 处理每个价格
|
||||
|
const processedPrices = prices.map(price => { |
||||
|
if (!price || price.trim() === '' || isNaN(parseFloat(price))) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const priceValue = parseFloat(price); |
||||
|
console.log(`处理价格: ${priceValue}`); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
console.log(`价格 ${priceValue} < 10,按照公式计算`); |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
return specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
return (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
} |
||||
|
// 价格>=10的直接使用
|
||||
|
return priceValue; |
||||
|
}).filter(price => price > 0); // 过滤掉0值
|
||||
|
|
||||
|
console.log(`规格'${spec}' 处理后的价格:`, processedPrices); |
||||
|
|
||||
|
// 计算处理后价格的平均值
|
||||
|
let finalPrice = 0; |
||||
|
let finalPriceText = ''; |
||||
|
|
||||
|
if (processedPrices.length > 0) { |
||||
|
const sum = processedPrices.reduce((acc, price) => acc + price, 0); |
||||
|
finalPrice = sum / processedPrices.length; |
||||
|
finalPriceText = finalPrice.toFixed(2); |
||||
|
console.log(`规格'${spec}' 处理后价格的平均值: ${finalPriceText} (基于 ${processedPrices.length} 个价格)`); |
||||
|
} else { |
||||
|
console.log(`规格'${spec}' 没有有效价格`); |
||||
|
} |
||||
|
|
||||
|
const specObj = { |
||||
|
name: spec, |
||||
|
price: finalPriceText, |
||||
|
priceText: finalPriceText, |
||||
|
finalPrice: finalPrice, |
||||
|
finalPriceText: finalPriceText |
||||
|
}; |
||||
|
|
||||
|
console.log('创建的规格对象:', specObj); |
||||
|
|
||||
|
return specObj; |
||||
|
}); |
||||
|
|
||||
|
console.log('提取的规格和价格:', specifications); |
||||
|
|
||||
|
// 对规格进行排序
|
||||
|
specifications.sort((a, b) => { |
||||
|
// 解析两个规格
|
||||
|
const specA = this.parseSpecification(a.name); |
||||
|
const specB = this.parseSpecification(b.name); |
||||
|
|
||||
|
// 如果有一个规格解析失败,保持原顺序
|
||||
|
if (!specA || !specB) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 1. 按类型排序:净重在前,毛重在后
|
||||
|
if (specA.type !== specB.type) { |
||||
|
return specA.type === '净重' ? -1 : 1; |
||||
|
} |
||||
|
|
||||
|
// 2. 按规格最小值排序:从小到大
|
||||
|
return specA.min - specB.min; |
||||
|
}); |
||||
|
|
||||
|
console.log('排序后的规格:', specifications); |
||||
|
|
||||
|
this.setData({ |
||||
|
specifications: specifications, |
||||
|
error: '', // 清除之前的错误
|
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到规格详情页面
|
||||
|
goToSpecDetail(e) { |
||||
|
const specItem = e.currentTarget.dataset.spec; |
||||
|
console.log('点击的规格项:', specItem); |
||||
|
console.log('传递的价格:', specItem.finalPriceText); |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/evaluate2/spec-detail?productName=${encodeURIComponent(this.data.productName)}&specification=${encodeURIComponent(specItem.name)}&price=${encodeURIComponent(specItem.finalPriceText)}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
// 返回商品列表页面
|
||||
|
goBackToProductList() { |
||||
|
// 从本地存储中获取之前选择的分类
|
||||
|
const selectedCategory = wx.getStorageSync('selectedCategory') || ''; |
||||
|
console.log('返回商品列表页面,之前选择的分类:', selectedCategory); |
||||
|
|
||||
|
// 构建跳转URL,如果有分类参数就传递
|
||||
|
let url = '/pages/evaluate2/product-list'; |
||||
|
if (selectedCategory) { |
||||
|
url += '?category=' + encodeURIComponent(selectedCategory); |
||||
|
} |
||||
|
|
||||
|
wx.redirectTo({ |
||||
|
url: url |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
console.log('开始下拉刷新'); |
||||
|
if (this.data.productName) { |
||||
|
this.loadSpecifications(this.data.productName); |
||||
|
} else { |
||||
|
wx.stopPullDownRefresh(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark" |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 头部导航栏 --> |
||||
|
<view class="header"> |
||||
|
<view class="header-content" style="width: 4200rpx; display: block; box-sizing: border-box; height: 124rpx"> |
||||
|
<text class="title">规格选择</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<!-- 加载中状态 --> |
||||
|
<view wx:if="{{loading}}" class="loading"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text class="loading-text">正在加载规格数据...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view wx:if="{{error}}" class="error-card"> |
||||
|
<view class="error-icon">⚠️</view> |
||||
|
<text class="error-text">{{error}}</text> |
||||
|
<view class="button-group"> |
||||
|
<button bindtap="loadSpecifications" data-product="{{productName}}" class="btn-primary">重新加载</button> |
||||
|
<button bindtap="goBackToProductList" class="btn-secondary">返回商品列表</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品信息 --> |
||||
|
<view wx:else class="product-card"> |
||||
|
<view class="product-info-container"> |
||||
|
<view class="product-info-row"> |
||||
|
<text class="product-label">商品名称</text> |
||||
|
<button bindtap="goBackToProductList" class="btn-back" style="width: 172rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx; height: 87rpx"> |
||||
|
<text class="btn-back-icon">←</text> |
||||
|
<text class="btn-back-text">更换商品</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
<text class="product-name">{{productName}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 规格列表 --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length > 0}}" class="spec-section"> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">可用规格</text> |
||||
|
<text class="section-count">{{specifications.length}}个</text> |
||||
|
</view> |
||||
|
<view class="spec-grid"> |
||||
|
<view |
||||
|
wx:for="{{specifications}}" |
||||
|
wx:key="item.name" |
||||
|
class="spec-card" |
||||
|
data-spec="{{item}}" |
||||
|
bindtap="goToSpecDetail" |
||||
|
> |
||||
|
<view class="spec-info"> |
||||
|
<text class="spec-name">{{item.name}}</text> |
||||
|
</view> |
||||
|
<view class="spec-price"> |
||||
|
<text class="price-label">参考价格</text> |
||||
|
<text class="price-value">¥{{item.finalPriceText}}</text> |
||||
|
</view> |
||||
|
<view class="spec-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 暂无更多规格提示 --> |
||||
|
<view wx:if="{{specifications.length > 0}}" class="no-more"> |
||||
|
<text class="no-more-text">暂无更多规格选择</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无规格提示 --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length === 0}}" class="empty-state" style="height: 600rpx; display: flex; box-sizing: border-box"> |
||||
|
<view class="empty-icon">📋</view> |
||||
|
<text class="empty-text">该商品暂无可用规格</text> |
||||
|
<button bindtap="goBackToProductList" class="btn-secondary">返回商品列表</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,450 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.08); |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.header-content { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 16rpx; |
||||
|
} |
||||
|
|
||||
|
/* 加载状态 */ |
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border: 8rpx solid rgba(74, 144, 226, 0.2); |
||||
|
border-top: 8rpx solid #4a90e2; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 32rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 错误提示卡片 */ |
||||
|
.error-card { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 48rpx; |
||||
|
margin: 32rpx 0; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 80rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.error-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #e74c3c; |
||||
|
margin-bottom: 32rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式 */ |
||||
|
.button-group { |
||||
|
display: flex; |
||||
|
gap: 16rpx; |
||||
|
width: 100%; |
||||
|
max-width: 600rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-primary { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
color: #fff; |
||||
|
border: none; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(96, 165, 250, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-primary:hover { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 6rpx 16rpx rgba(74, 144, 226, 0.5); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: #fff; |
||||
|
color: #4a90e2; |
||||
|
border: 2rpx solid #4a90e2; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:hover { |
||||
|
background: rgba(74, 144, 226, 0.05); |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
.btn-back { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 8rpx 24rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #4a90e2; |
||||
|
background: transparent; |
||||
|
border: 1rpx solid #4a90e2; |
||||
|
border-radius: 20rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.btn-back:hover { |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(74, 144, 226, 0.2); |
||||
|
} |
||||
|
|
||||
|
.btn-back-icon { |
||||
|
margin-right: 8rpx; |
||||
|
font-size: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-back-text { |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品信息卡片 */ |
||||
|
.product-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
} |
||||
|
|
||||
|
.product-info-container { |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-info-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-label { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
margin-top: 12rpx; |
||||
|
padding: 12rpx 0; |
||||
|
border-top: 1rpx solid #f1f5f9; |
||||
|
} |
||||
|
|
||||
|
/* 规格列表区域 */ |
||||
|
.spec-section { |
||||
|
margin-top: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 24rpx; |
||||
|
padding: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
padding-left: 16rpx; |
||||
|
border-left: 6rpx solid #4a90e2; |
||||
|
} |
||||
|
|
||||
|
.section-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
padding: 6rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 规格网格布局 */ |
||||
|
.spec-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 20rpx; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
/* 规格卡片 */ |
||||
|
.spec-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
min-height: 200rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.spec-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 6rpx; |
||||
|
height: 100%; |
||||
|
background: linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover { |
||||
|
transform: translateY(-6rpx); |
||||
|
box-shadow: 0 12rpx 32rpx rgba(0,0,0,0.15); |
||||
|
background: rgba(255, 255, 255, 1); |
||||
|
} |
||||
|
|
||||
|
.spec-card:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12); |
||||
|
} |
||||
|
|
||||
|
.spec-info { |
||||
|
margin-bottom: 20rpx; |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.spec-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
padding-right: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-price { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.price-label { |
||||
|
font-size: 20rpx; |
||||
|
color: #999; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #e74c3c; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover .price-value { |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
|
||||
|
.spec-arrow { |
||||
|
position: absolute; |
||||
|
top: 32rpx; |
||||
|
right: 28rpx; |
||||
|
font-size: 32rpx; |
||||
|
color: #4a90e2; |
||||
|
font-weight: bold; |
||||
|
opacity: 0.6; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover .spec-arrow { |
||||
|
opacity: 1; |
||||
|
transform: translateX(4rpx); |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.empty-icon { |
||||
|
font-size: 160rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 32rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 48rpx; |
||||
|
line-height: 1.4; |
||||
|
padding: 0 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.content { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.product-card { |
||||
|
padding: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-card { |
||||
|
padding: 28rpx 20rpx; |
||||
|
min-height: 180rpx; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.button-group { |
||||
|
flex-direction: column; |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-primary, |
||||
|
.btn-secondary { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 小屏幕设备适配 */ |
||||
|
@media (max-width: 414rpx) { |
||||
|
.spec-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-card { |
||||
|
padding: 24rpx 16rpx; |
||||
|
min-height: 160rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-name { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 暂无更多规格提示 */ |
||||
|
.no-more { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 48rpx 0; |
||||
|
margin-top: 16rpx; |
||||
|
margin-bottom: 100rpx; /* 添加底部边距,避免被导航栏遮挡 */ |
||||
|
} |
||||
|
|
||||
|
.no-more-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(0, 0, 0, 0.03); |
||||
|
padding: 12rpx 32rpx; |
||||
|
border-radius: 30rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 调整规格网格布局,确保不被导航栏遮挡 */ |
||||
|
.spec-section { |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
@ -0,0 +1,808 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productName: '', |
||||
|
specifications: [], |
||||
|
loading: false, |
||||
|
error: '', |
||||
|
categories: [], |
||||
|
selectedCategory: '', |
||||
|
showOneKeyLoginModal: false // 是否显示登录弹窗
|
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
let productName = ''; |
||||
|
// 首先检查URL参数
|
||||
|
if (options.productName) { |
||||
|
productName = options.productName; |
||||
|
} else { |
||||
|
// 然后检查本地存储(用于wx.switchTab导航)
|
||||
|
productName = wx.getStorageSync('selectedProductName') || ''; |
||||
|
// 清除本地存储中的商品名称,避免影响下次进入
|
||||
|
if (productName) { |
||||
|
wx.removeStorageSync('selectedProductName'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (productName) { |
||||
|
this.setData({ productName: productName }); |
||||
|
this.loadSpecifications(productName); |
||||
|
} else { |
||||
|
// 如果没有商品名称参数,加载所有商品数据并计算
|
||||
|
this.loadAllData(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
console.log('开始下拉刷新'); |
||||
|
this.loadAllData(); |
||||
|
}, |
||||
|
|
||||
|
// 加载所有商品数据并计算
|
||||
|
loadAllData() { |
||||
|
this.setData({ |
||||
|
loading: true, |
||||
|
categories: [] // 清空分类数据,确保加载时只显示加载状态
|
||||
|
}); |
||||
|
|
||||
|
const api = require('../../utils/api'); |
||||
|
// 使用正确的参数调用getProducts方法
|
||||
|
api.getProducts(1, 1000, 'all', '').then(result => { |
||||
|
console.log('API返回结果:', result); |
||||
|
// 从返回对象中提取products数组,如果不存在则使用空数组
|
||||
|
const products = result.products || []; |
||||
|
console.log('提取的products数组:', products); |
||||
|
console.log('products数组长度:', products.length); |
||||
|
|
||||
|
// 存储原始商品数据到本地存储
|
||||
|
wx.setStorageSync('allProducts', products); |
||||
|
|
||||
|
// 过滤出有有效category字段的商品(忽略空字符串和仅包含空格的分类)
|
||||
|
const productsWithCategory = products.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const categoryStr = String(product.category).trim(); |
||||
|
return categoryStr !== ''; |
||||
|
}); |
||||
|
console.log('有有效category的商品:', productsWithCategory); |
||||
|
console.log('有有效category的商品数量:', productsWithCategory.length); |
||||
|
|
||||
|
// 提取所有分类(去除前后空格)
|
||||
|
const categories = [...new Set(productsWithCategory.map(product => { |
||||
|
return String(product.category).trim(); |
||||
|
}))]; |
||||
|
console.log('提取的分类数组:', categories); |
||||
|
console.log('分类数量:', categories.length); |
||||
|
|
||||
|
// 存储分类数据到本地存储
|
||||
|
wx.setStorageSync('evaluate2Categories', categories); |
||||
|
|
||||
|
// 计算每个分类下的商品名称列表
|
||||
|
const categoryProductsMap = {}; |
||||
|
categories.forEach(category => { |
||||
|
// 过滤出该分类下的商品
|
||||
|
const categoryProducts = productsWithCategory.filter(product => { |
||||
|
const productCategory = String(product.category).trim(); |
||||
|
return productCategory === category; |
||||
|
}); |
||||
|
|
||||
|
// 提取商品名称(去除前后空格,去重)
|
||||
|
const productNames = [...new Set(categoryProducts.map(product => { |
||||
|
// 尝试从多个字段获取商品名称
|
||||
|
const nameFields = [product.productName, product.name, product.product]; |
||||
|
for (const field of nameFields) { |
||||
|
if (field) { |
||||
|
const trimmedName = String(field).trim(); |
||||
|
if (trimmedName !== '') { |
||||
|
return trimmedName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
}).filter(name => name !== ''))]; |
||||
|
|
||||
|
categoryProductsMap[category] = productNames; |
||||
|
}); |
||||
|
|
||||
|
// 存储分类商品映射到本地存储
|
||||
|
wx.setStorageSync('evaluate2CategoryProductsMap', categoryProductsMap); |
||||
|
|
||||
|
// 计算每个商品的规格和价格信息
|
||||
|
const productSpecsMap = {}; |
||||
|
products.forEach(product => { |
||||
|
// 尝试从多个字段获取商品名称
|
||||
|
const nameFields = [product.productName, product.name, product.product]; |
||||
|
let productName = ''; |
||||
|
for (const field of nameFields) { |
||||
|
if (field) { |
||||
|
const trimmedName = String(field).trim(); |
||||
|
if (trimmedName !== '') { |
||||
|
productName = trimmedName; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (productName) { |
||||
|
if (!productSpecsMap[productName]) { |
||||
|
productSpecsMap[productName] = []; |
||||
|
} |
||||
|
|
||||
|
// 提取规格和价格
|
||||
|
const specStr = (product.specification || product.spec || '').trim(); |
||||
|
const price = product.price || ''; |
||||
|
|
||||
|
if (specStr && price) { |
||||
|
productSpecsMap[productName].push({ |
||||
|
specification: specStr, |
||||
|
price: price |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 存储商品规格映射到本地存储
|
||||
|
wx.setStorageSync('evaluate2ProductSpecsMap', productSpecsMap); |
||||
|
|
||||
|
// 加载分类数据到页面
|
||||
|
this.setData({ |
||||
|
categories: categories, |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}).catch(err => { |
||||
|
console.error('获取商品数据失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取商品数据失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 加载商品分类数据
|
||||
|
loadCategories() { |
||||
|
this.setData({ loading: true }); |
||||
|
|
||||
|
// 尝试从本地存储中获取分类数据
|
||||
|
const categories = wx.getStorageSync('evaluate2Categories') || []; |
||||
|
|
||||
|
if (categories.length > 0) { |
||||
|
// 如果本地存储中有分类数据,直接使用
|
||||
|
console.log('从本地存储获取分类数据:', categories); |
||||
|
this.setData({ |
||||
|
categories: categories, |
||||
|
loading: false |
||||
|
}); |
||||
|
} else { |
||||
|
// 如果本地存储中没有分类数据,重新加载所有数据
|
||||
|
this.loadAllData(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
loadSpecifications(productName) { |
||||
|
if (!productName) { |
||||
|
this.setData({ |
||||
|
error: '请先选择商品', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.setData({ loading: true, specifications: [] }); |
||||
|
|
||||
|
// 直接从本地存储获取商品数据,避免重复请求
|
||||
|
const localGoods = wx.getStorageSync('goods') || []; |
||||
|
console.log('从本地存储获取的商品数量:', localGoods.length); |
||||
|
|
||||
|
if (localGoods.length > 0) { |
||||
|
this.processSpecifications(productName, localGoods); |
||||
|
} else { |
||||
|
// 如果本地没有数据,再请求服务器
|
||||
|
const api = require('../../utils/api'); |
||||
|
// 使用正确的参数调用getProducts方法
|
||||
|
api.getProducts(1, 1000, 'all', '').then(result => { |
||||
|
// 从返回对象中提取products数组
|
||||
|
const products = result.products || []; |
||||
|
this.processSpecifications(productName, products); |
||||
|
}).catch(err => { |
||||
|
console.error('获取规格失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取规格失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 解析规格,提取类型(净重/毛重)和数值范围
|
||||
|
parseSpecification(spec) { |
||||
|
const weightMatch = spec.match(/(净重|毛重)(\d+)-(\d+)/); |
||||
|
if (weightMatch) { |
||||
|
const type = weightMatch[1]; // 净重或毛重
|
||||
|
const min = parseFloat(weightMatch[2]); |
||||
|
const max = parseFloat(weightMatch[3]); |
||||
|
const avg = (min + max) / 2; |
||||
|
return { |
||||
|
type: type, |
||||
|
min: min, |
||||
|
max: max, |
||||
|
avg: avg |
||||
|
}; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
|
||||
|
processSpecifications(productName, products) { |
||||
|
console.log('处理的商品数据数量:', products.length); |
||||
|
console.log('当前处理的商品名称:', productName); |
||||
|
|
||||
|
// 检查products是否为空
|
||||
|
if (!products || products.length === 0) { |
||||
|
console.error('商品数据为空'); |
||||
|
this.setData({ |
||||
|
error: '商品数据为空', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 过滤出当前商品名称的所有商品
|
||||
|
const filteredProducts = products.filter(product => { |
||||
|
const match = product.productName === productName; |
||||
|
console.log('商品:', product.productName, '规格:', product.specification, '价格:', product.price, '匹配:', match); |
||||
|
return match; |
||||
|
}); |
||||
|
|
||||
|
console.log('过滤后的商品数量:', filteredProducts.length); |
||||
|
console.log('过滤后的商品详情:', filteredProducts); |
||||
|
|
||||
|
// 检查filteredProducts是否为空
|
||||
|
if (filteredProducts.length === 0) { |
||||
|
console.error('未找到商品名称为"' + productName + '"的商品'); |
||||
|
this.setData({ |
||||
|
error: '未找到对应商品名称的商品', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 提取规格和价格,处理可能的空值和空格
|
||||
|
const specPriceMap = {}; |
||||
|
filteredProducts.forEach((product, productIndex) => { |
||||
|
const specStr = (product.specification || product.spec || '').trim(); |
||||
|
const price = product.price || ''; |
||||
|
|
||||
|
console.log(`处理第${productIndex + 1}个商品: 规格字符串='${specStr}', 价格字符串='${price}'`); |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (!price || price.trim() === '') { |
||||
|
console.log(`商品价格为空,跳过处理`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (specStr.length > 0) { |
||||
|
// 处理逗号分隔的多个规格,确保每个规格都被正确分割
|
||||
|
// 首先按逗号分割
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
// 检查规格是否包含多个规格(例如:"净重29-30,净重31-32")
|
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const prices = (price || '').split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
|
||||
|
console.log(`规格数组:`, specs); |
||||
|
console.log(`价格数组:`, prices); |
||||
|
|
||||
|
// 价格为空的不参与计算
|
||||
|
if (prices.length === 0) { |
||||
|
console.log(`价格数组为空,跳过处理`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0) { |
||||
|
// 确保价格索引不超出范围
|
||||
|
const priceIndex = index % prices.length; |
||||
|
const matchedPrice = prices[priceIndex] || ''; |
||||
|
console.log(`规格'${spec}' 配对价格: '${matchedPrice}'`); |
||||
|
|
||||
|
// 只有当价格不为空时才添加该规格
|
||||
|
if (matchedPrice && matchedPrice.trim() !== '') { |
||||
|
// 收集相同规格的所有价格,以便计算平均值
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(matchedPrice); |
||||
|
} else { |
||||
|
console.log(`规格'${spec}' 价格为空,不添加`); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 转换为规格对象数组
|
||||
|
const specifications = Object.keys(specPriceMap).map(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
console.log(`规格'${spec}' 的所有原始价格:`, prices); |
||||
|
|
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
|
||||
|
// 处理每个价格
|
||||
|
const processedPrices = prices.map(price => { |
||||
|
if (!price || price.trim() === '' || isNaN(parseFloat(price))) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const priceValue = parseFloat(price); |
||||
|
console.log(`处理价格: ${priceValue}`); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
console.log(`价格 ${priceValue} < 10,按照公式计算`); |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
return specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
return (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
} |
||||
|
// 价格>=10的直接使用
|
||||
|
return priceValue; |
||||
|
}).filter(price => price > 0); // 过滤掉0值
|
||||
|
|
||||
|
console.log(`规格'${spec}' 处理后的价格:`, processedPrices); |
||||
|
|
||||
|
// 计算处理后价格的平均值
|
||||
|
let finalPrice = 0; |
||||
|
let finalPriceText = ''; |
||||
|
|
||||
|
if (processedPrices.length > 0) { |
||||
|
const sum = processedPrices.reduce((acc, price) => acc + price, 0); |
||||
|
finalPrice = sum / processedPrices.length; |
||||
|
finalPriceText = finalPrice.toFixed(2); |
||||
|
console.log(`规格'${spec}' 处理后价格的平均值: ${finalPriceText} (基于 ${processedPrices.length} 个价格)`); |
||||
|
} else { |
||||
|
console.log(`规格'${spec}' 没有有效价格`); |
||||
|
} |
||||
|
|
||||
|
const specObj = { |
||||
|
name: spec, |
||||
|
price: finalPriceText, |
||||
|
priceText: finalPriceText, |
||||
|
finalPrice: finalPrice, |
||||
|
finalPriceText: finalPriceText |
||||
|
}; |
||||
|
|
||||
|
console.log('创建的规格对象:', specObj); |
||||
|
|
||||
|
return specObj; |
||||
|
}); |
||||
|
|
||||
|
console.log('提取的规格和价格:', specifications); |
||||
|
|
||||
|
// 对规格进行排序
|
||||
|
specifications.sort((a, b) => { |
||||
|
// 解析两个规格
|
||||
|
const specA = this.parseSpecification(a.name); |
||||
|
const specB = this.parseSpecification(b.name); |
||||
|
|
||||
|
// 如果有一个规格解析失败,保持原顺序
|
||||
|
if (!specA || !specB) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 1. 按类型排序:净重在前,毛重在后
|
||||
|
if (specA.type !== specB.type) { |
||||
|
return specA.type === '净重' ? -1 : 1; |
||||
|
} |
||||
|
|
||||
|
// 2. 按规格最小值排序:从小到大
|
||||
|
return specA.min - specB.min; |
||||
|
}); |
||||
|
|
||||
|
console.log('排序后的规格:', specifications); |
||||
|
|
||||
|
this.setData({ |
||||
|
specifications: specifications, |
||||
|
error: '', // 清除之前的错误
|
||||
|
loading: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到规格详情页面
|
||||
|
goToSpecDetail(e) { |
||||
|
const specItem = e.currentTarget.dataset.spec; |
||||
|
console.log('点击的规格项:', specItem); |
||||
|
console.log('传递的价格:', specItem.finalPriceText); |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/evaluate1/spec-detail?productName=${encodeURIComponent(this.data.productName)}&specification=${encodeURIComponent(specItem.name)}&price=${encodeURIComponent(specItem.finalPriceText)}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
// 返回商品列表页面
|
||||
|
goBackToProductList() { |
||||
|
wx.redirectTo({ |
||||
|
url: '/pages/evaluate1/product-list' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择分类,跳转到商品列表页面
|
||||
|
selectCategory(e) { |
||||
|
const category = e.currentTarget.dataset.category; |
||||
|
console.log('选择分类:', category); |
||||
|
|
||||
|
// 检查用户登录状态
|
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
|
||||
|
if (!openid || !userId) { |
||||
|
// 用户未登录,显示登录弹窗
|
||||
|
console.log('用户未登录,显示登录弹窗'); |
||||
|
this.setData({ |
||||
|
showOneKeyLoginModal: true |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 检查用户合作状态
|
||||
|
const userInfo = wx.getStorageSync('userInfo') || {}; |
||||
|
const partnerStatus = userInfo.partnerstatus || ''; |
||||
|
|
||||
|
if (partnerStatus !== 'approved') { |
||||
|
// 合作状态未批准,显示提示
|
||||
|
wx.showModal({ |
||||
|
title: '提示', |
||||
|
content: '需要入住成功才能查看', |
||||
|
confirmText: '立即入驻', |
||||
|
success: function(res) { |
||||
|
if (res.confirm) { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/profile/authentication/index' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 跳转到商品列表页面,并传递分类参数
|
||||
|
wx.redirectTo({ |
||||
|
url: `/pages/evaluate2/product-list?category=${encodeURIComponent(category)}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 关闭登录弹窗
|
||||
|
closeOneKeyLoginModal() { |
||||
|
this.setData({ |
||||
|
showOneKeyLoginModal: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 处理登录授权
|
||||
|
async onGetPhoneNumber(e) { |
||||
|
console.log('收到手机号授权事件:', e.detail); |
||||
|
|
||||
|
// 关闭手机号授权弹窗
|
||||
|
this.setData({ showOneKeyLoginModal: false }); |
||||
|
|
||||
|
// 用户点击拒绝授权
|
||||
|
if (e.detail.errMsg === 'getPhoneNumber:fail user deny') { |
||||
|
wx.showToast({ |
||||
|
title: '需要授权手机号才能使用', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 处理没有权限的情况
|
||||
|
if (e.detail.errMsg === 'getPhoneNumber:fail no permission') { |
||||
|
wx.showToast({ |
||||
|
title: '当前环境无法获取手机号权限', |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 检查是否已经登录,避免重复授权
|
||||
|
const existingOpenid = wx.getStorageSync('openid'); |
||||
|
const existingUserId = wx.getStorageSync('userId'); |
||||
|
const existingUserInfo = wx.getStorageSync('userInfo'); |
||||
|
|
||||
|
if (existingOpenid && existingUserId && existingUserInfo && existingUserInfo.phoneNumber) { |
||||
|
console.log('用户已登录且手机号有效,登录流程已完成'); |
||||
|
wx.showToast({ |
||||
|
title: '您已登录', |
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
wx.showLoading({ title: '登录中...', mask: true }); |
||||
|
|
||||
|
try { |
||||
|
if (e.detail.errMsg === 'getPhoneNumber:ok') { |
||||
|
// 用户同意授权,实际处理授权流程
|
||||
|
console.log('用户同意授权获取手机号'); |
||||
|
|
||||
|
// 1. 先执行微信登录获取code
|
||||
|
const loginRes = await new Promise((resolve, reject) => { |
||||
|
wx.login({ |
||||
|
success: resolve, |
||||
|
fail: reject |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
if (!loginRes.code) { |
||||
|
throw new Error('获取登录code失败'); |
||||
|
} |
||||
|
|
||||
|
console.log('获取登录code成功:', loginRes.code); |
||||
|
|
||||
|
// 2. 使用code换取openid
|
||||
|
const API = require('../../utils/api'); |
||||
|
const openidRes = await API.getOpenid(loginRes.code); |
||||
|
|
||||
|
// 改进错误处理逻辑,更宽容地处理服务器返回格式
|
||||
|
let openid = null; |
||||
|
let userId = null; |
||||
|
console.log('openidRes完整响应:', JSON.stringify(openidRes)); |
||||
|
|
||||
|
if (openidRes && typeof openidRes === 'object') { |
||||
|
// 适配服务器返回格式:{success: true, code: 200, message: '获取openid成功', data: {openid, userId}}
|
||||
|
if (openidRes.data && typeof openidRes.data === 'object') { |
||||
|
console.log('识别到标准服务器返回格式,从data字段提取信息'); |
||||
|
openid = openidRes.data.openid || openidRes.data.OpenID || null; |
||||
|
userId = openidRes.data.userId || null; |
||||
|
} else { |
||||
|
// 尝试从响应对象中直接提取openid
|
||||
|
console.log('尝试从根对象直接提取openid'); |
||||
|
openid = openidRes.openid || openidRes.OpenID || null; |
||||
|
userId = openidRes.userId || null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!openid) { |
||||
|
console.error('无法从服务器响应中提取openid,完整响应:', JSON.stringify(openidRes)); |
||||
|
throw new Error(`获取openid失败: 服务器返回数据格式可能不符合预期`); |
||||
|
} |
||||
|
|
||||
|
console.log('获取openid成功:', openid); |
||||
|
|
||||
|
// 3. 存储openid和session_key
|
||||
|
wx.setStorageSync('openid', openid); |
||||
|
|
||||
|
// 从服务器返回中获取session_key
|
||||
|
if (openidRes && openidRes.session_key) { |
||||
|
wx.setStorageSync('sessionKey', openidRes.session_key); |
||||
|
} else if (openidRes && openidRes.data && openidRes.data.session_key) { |
||||
|
wx.setStorageSync('sessionKey', openidRes.data.session_key); |
||||
|
} |
||||
|
|
||||
|
// 优先使用从服务器响应data字段中提取的userId
|
||||
|
if (userId) { |
||||
|
wx.setStorageSync('userId', userId); |
||||
|
console.log('使用从服务器data字段提取的userId:', userId); |
||||
|
} else if (openidRes && openidRes.userId) { |
||||
|
wx.setStorageSync('userId', openidRes.userId); |
||||
|
console.log('使用服务器根对象中的userId:', openidRes.userId); |
||||
|
} else { |
||||
|
// 生成临时userId
|
||||
|
const tempUserId = 'user_' + Date.now(); |
||||
|
wx.setStorageSync('userId', tempUserId); |
||||
|
console.log('生成临时userId:', tempUserId); |
||||
|
} |
||||
|
|
||||
|
// 4. 上传手机号加密数据到服务器解密
|
||||
|
const phoneData = { |
||||
|
...e.detail, |
||||
|
openid: openid |
||||
|
}; |
||||
|
|
||||
|
console.log('准备上传手机号加密数据到服务器'); |
||||
|
const phoneRes = await API.uploadPhoneNumberData(phoneData); |
||||
|
|
||||
|
// 改进手机号解密结果的处理逻辑
|
||||
|
if (!phoneRes || (!phoneRes.success && !phoneRes.phoneNumber)) { |
||||
|
// 如果服务器返回格式不标准但包含手机号,也接受
|
||||
|
if (phoneRes && phoneRes.phoneNumber) { |
||||
|
console.warn('服务器返回格式可能不符合预期,但成功获取手机号'); |
||||
|
} else { |
||||
|
throw new Error('获取手机号失败: ' + (phoneRes && phoneRes.message ? phoneRes.message : '未知错误')); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检查是否有手机号冲突
|
||||
|
const hasPhoneConflict = phoneRes.phoneNumberConflict || false; |
||||
|
const isNewPhone = phoneRes.isNewPhone || true; |
||||
|
const phoneNumber = phoneRes.phoneNumber || null; |
||||
|
|
||||
|
// 如果有手机号冲突且没有返回手机号,使用实际返回的手机号
|
||||
|
const finalPhoneNumber = phoneNumber; |
||||
|
|
||||
|
console.log('手机号解密结果:', { |
||||
|
phoneNumber: finalPhoneNumber, |
||||
|
hasPhoneConflict: hasPhoneConflict, |
||||
|
isNewPhone: isNewPhone |
||||
|
}); |
||||
|
|
||||
|
// 5. 获取用户微信名称和头像
|
||||
|
let userProfile = null; |
||||
|
try { |
||||
|
userProfile = await new Promise((resolve, reject) => { |
||||
|
wx.getUserProfile({ |
||||
|
desc: '用于完善会员资料', |
||||
|
success: resolve, |
||||
|
fail: reject |
||||
|
}); |
||||
|
}); |
||||
|
console.log('获取用户信息成功:', userProfile); |
||||
|
} catch (err) { |
||||
|
console.warn('获取用户信息失败:', err); |
||||
|
// 如果获取失败,使用默认值
|
||||
|
} |
||||
|
|
||||
|
// 6. 创建用户信息
|
||||
|
const tempUserInfo = { |
||||
|
name: userProfile ? (userProfile.userInfo.name || userProfile.userInfo.nickName) : '微信用户', |
||||
|
// 获取微信头像失败时使用微信默认头像
|
||||
|
avatarUrl: userProfile ? userProfile.userInfo.avatarUrl : 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', |
||||
|
gender: userProfile ? userProfile.userInfo.gender : 0, |
||||
|
country: userProfile ? userProfile.userInfo.country : '', |
||||
|
province: userProfile ? userProfile.userInfo.province : '', |
||||
|
city: userProfile ? userProfile.userInfo.city : '', |
||||
|
language: userProfile ? userProfile.userInfo.language : 'zh_CN', |
||||
|
phoneNumber: finalPhoneNumber |
||||
|
}; |
||||
|
|
||||
|
// 7. 保存用户信息
|
||||
|
const storedUserId = wx.getStorageSync('userId'); |
||||
|
const users = wx.getStorageSync('users') || {}; |
||||
|
const currentUserType = users[storedUserId] && users[storedUserId].type ? users[storedUserId].type : 'buyer'; |
||||
|
|
||||
|
console.log('用户身份类型:', currentUserType); |
||||
|
|
||||
|
// 8. 保存用户信息到本地和服务器
|
||||
|
console.log('开始保存用户信息...'); |
||||
|
await this.saveUserInfo(tempUserInfo, currentUserType); |
||||
|
console.log('用户信息保存完成'); |
||||
|
|
||||
|
wx.hideLoading(); |
||||
|
|
||||
|
// 根据服务器返回的结果显示不同的提示
|
||||
|
if (phoneRes && phoneRes.phoneNumberConflict) { |
||||
|
wx.showModal({ |
||||
|
title: '登录成功', |
||||
|
content: '您的手机号已被其他账号绑定', |
||||
|
showCancel: false, |
||||
|
confirmText: '我知道了', |
||||
|
success(res) { |
||||
|
if (res.confirm) { |
||||
|
console.log('用户点击了我知道了'); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 1. 登录成功提示
|
||||
|
wx.showToast({ |
||||
|
title: '登录成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
|
}); |
||||
|
|
||||
|
// 2. 从服务器获取最新用户信息
|
||||
|
const userInfoRes = await API.getUserInfo(openid); |
||||
|
|
||||
|
// 3. 获取服务器返回的partnerstatus
|
||||
|
const serverUserInfo = userInfoRes.data; |
||||
|
const partnerStatus = serverUserInfo.partnerstatus || 'pending'; |
||||
|
|
||||
|
// 4. 更新本地缓存
|
||||
|
const localUserInfo = wx.getStorageSync('userInfo') || {}; |
||||
|
const updatedUserInfo = { |
||||
|
...localUserInfo, |
||||
|
partnerstatus: partnerStatus |
||||
|
}; |
||||
|
wx.setStorageSync('userInfo', updatedUserInfo); |
||||
|
|
||||
|
// 5. 检查partnerstatus值
|
||||
|
if (partnerStatus !== 'approved') { |
||||
|
// 显示入驻提示
|
||||
|
setTimeout(() => { |
||||
|
wx.showModal({ |
||||
|
title: '提示', |
||||
|
content: '需要入住成功才能查看', |
||||
|
confirmText: '立即入驻', |
||||
|
success: function(res) { |
||||
|
if (res.confirm) { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/profile/authentication/index' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, 2000); |
||||
|
} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
wx.hideLoading(); |
||||
|
console.error('登录失败:', error); |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 保存用户信息
|
||||
|
async saveUserInfo(userInfo, userType = 'buyer') { |
||||
|
return new Promise(async (resolve, reject) => { |
||||
|
try { |
||||
|
const storedUserId = wx.getStorageSync('userId'); |
||||
|
const users = wx.getStorageSync('users') || {}; |
||||
|
|
||||
|
// 更新用户信息
|
||||
|
users[storedUserId] = { |
||||
|
...users[storedUserId], |
||||
|
...userInfo, |
||||
|
type: userType, |
||||
|
lastLogin: new Date().toISOString() |
||||
|
}; |
||||
|
|
||||
|
// 保存到本地存储
|
||||
|
wx.setStorageSync('users', users); |
||||
|
wx.setStorageSync('userInfo', userInfo); |
||||
|
|
||||
|
// 上传用户信息到服务器
|
||||
|
const API = require('../../utils/api'); |
||||
|
const submitData = { |
||||
|
openid: wx.getStorageSync('openid'), |
||||
|
userId: storedUserId, |
||||
|
...userInfo, |
||||
|
type: userType |
||||
|
}; |
||||
|
|
||||
|
await API.request('/api/user/update', 'POST', submitData).then(res => { |
||||
|
console.log('用户信息上传成功:', res); |
||||
|
resolve({ success: true, message: '用户信息保存成功' }); |
||||
|
}).catch(err => { |
||||
|
console.error('用户信息上传失败:', err); |
||||
|
// 即使服务器上传失败,也继续流程,只在本地保存
|
||||
|
resolve({ success: false, message: '本地保存成功,服务器同步失败' }); |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error('保存用户信息失败:', error); |
||||
|
reject(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark" |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 头部导航栏 --> |
||||
|
<view class="header"> |
||||
|
<view class="header-content" style="width: 600rpx; display: block; box-sizing: border-box; height: 106rpx"> |
||||
|
<text class="title">分类选择</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 登录弹窗 --> |
||||
|
<view wx:if="{{showOneKeyLoginModal}}" class="auth-modal-overlay"> |
||||
|
<view class="auth-modal-container"> |
||||
|
<view class="auth-modal-title">授权登录</view> |
||||
|
<view class="auth-modal-content">授权您的手机号后才能使用完整功能</view> |
||||
|
<view class="auth-modal-buttons"> |
||||
|
<button |
||||
|
class="auth-primary-button" |
||||
|
open-type="getPhoneNumber" |
||||
|
bindgetphonenumber="onGetPhoneNumber" |
||||
|
> |
||||
|
授权手机号 |
||||
|
</button> |
||||
|
<button |
||||
|
class="auth-cancel-button" |
||||
|
bindtap="closeOneKeyLoginModal" |
||||
|
> |
||||
|
取消 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content" style="width: 600rpx; display: block; box-sizing: border-box"> |
||||
|
<!-- 加载中状态 --> |
||||
|
<view wx:if="{{loading}}" class="loading"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text class="loading-text">正在加载数据...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view wx:if="{{error}}" class="error-card"> |
||||
|
<view class="error-icon">⚠️</view> |
||||
|
<text class="error-text">{{error}}</text> |
||||
|
<view class="button-group"> |
||||
|
<button bindtap="loadCategories" class="btn-primary">重新加载</button> |
||||
|
<button bindtap="goBackToProductList" class="btn-secondary">返回商品列表</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 分类选择区域 --> |
||||
|
<view wx:else class="category-section"> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">商品分类</text> |
||||
|
<text class="section-count">{{categories.length}}个</text> |
||||
|
</view> |
||||
|
<view class="category-grid"> |
||||
|
<view |
||||
|
wx:for="{{categories}}" |
||||
|
wx:key="item" |
||||
|
class="category-card" |
||||
|
data-category="{{item}}" |
||||
|
bindtap="selectCategory" |
||||
|
> |
||||
|
<view class="category-info"> |
||||
|
<text class="category-name">{{item}}</text> |
||||
|
</view> |
||||
|
<view class="category-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品信息 (当有productName时显示) --> |
||||
|
<view wx:if="{{productName}}" class="product-card"> |
||||
|
<view class="product-info-container"> |
||||
|
<view class="product-info-row"> |
||||
|
<text class="product-label">商品名称</text> |
||||
|
<button bindtap="goBackToProductList" class="btn-back"> |
||||
|
<text class="btn-back-icon">←</text> |
||||
|
<text class="btn-back-text">更换商品</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
<text class="product-name">{{productName}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 规格列表 (当有specifications时显示) --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length > 0}}" class="spec-section"> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">可用规格</text> |
||||
|
<text class="section-count">{{specifications.length}}个</text> |
||||
|
</view> |
||||
|
<view class="spec-grid"> |
||||
|
<view |
||||
|
wx:for="{{specifications}}" |
||||
|
wx:key="item.name" |
||||
|
class="spec-card" |
||||
|
data-spec="{{item}}" |
||||
|
bindtap="goToSpecDetail" |
||||
|
> |
||||
|
<view class="spec-info"> |
||||
|
<text class="spec-name">{{item.name}}</text> |
||||
|
</view> |
||||
|
<view class="spec-price"> |
||||
|
<text class="price-label">参考价格</text> |
||||
|
<text class="price-value">¥{{item.finalPriceText}}</text> |
||||
|
</view> |
||||
|
<view class="spec-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 暂无更多规格提示 --> |
||||
|
<view wx:if="{{specifications.length > 0}}" class="no-more"> |
||||
|
<text class="no-more-text">暂无更多规格选择</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无规格提示 --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length === 0 && productName}}" class="empty-state"> |
||||
|
<view class="empty-icon">📋</view> |
||||
|
<text class="empty-text">该商品暂无可用规格</text> |
||||
|
<button bindtap="goBackToProductList" class="btn-secondary">返回商品列表</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,652 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.08); |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.header-content { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 16rpx; |
||||
|
} |
||||
|
|
||||
|
/* 加载状态 */ |
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border: 8rpx solid rgba(74, 144, 226, 0.2); |
||||
|
border-top: 8rpx solid #4a90e2; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 32rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 错误提示卡片 */ |
||||
|
.error-card { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 48rpx; |
||||
|
margin: 32rpx 0; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 80rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.error-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #e74c3c; |
||||
|
margin-bottom: 32rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式 */ |
||||
|
.button-group { |
||||
|
display: flex; |
||||
|
gap: 16rpx; |
||||
|
width: 100%; |
||||
|
max-width: 600rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-primary { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
color: #fff; |
||||
|
border: none; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(96, 165, 250, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-primary:hover { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 6rpx 16rpx rgba(74, 144, 226, 0.5); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: #fff; |
||||
|
color: #4a90e2; |
||||
|
border: 2rpx solid #4a90e2; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:hover { |
||||
|
background: rgba(74, 144, 226, 0.05); |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
.btn-back { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 8rpx 24rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #4a90e2; |
||||
|
background: transparent; |
||||
|
border: 1rpx solid #4a90e2; |
||||
|
border-radius: 20rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.btn-back:hover { |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(74, 144, 226, 0.2); |
||||
|
} |
||||
|
|
||||
|
.btn-back-icon { |
||||
|
margin-right: 8rpx; |
||||
|
font-size: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-back-text { |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品信息卡片 */ |
||||
|
.product-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
} |
||||
|
|
||||
|
.product-info-container { |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-info-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-label { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
margin-top: 12rpx; |
||||
|
padding: 12rpx 0; |
||||
|
border-top: 1rpx solid #f1f5f9; |
||||
|
} |
||||
|
|
||||
|
/* 规格列表区域 */ |
||||
|
.spec-section { |
||||
|
margin-top: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 24rpx; |
||||
|
padding: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
padding-left: 16rpx; |
||||
|
border-left: 6rpx solid #4a90e2; |
||||
|
} |
||||
|
|
||||
|
.section-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
padding: 6rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 规格网格布局 */ |
||||
|
.spec-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 20rpx; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
/* 规格卡片 */ |
||||
|
.spec-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
min-height: 200rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.spec-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 6rpx; |
||||
|
height: 100%; |
||||
|
background: linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover { |
||||
|
transform: translateY(-6rpx); |
||||
|
box-shadow: 0 12rpx 32rpx rgba(0,0,0,0.15); |
||||
|
background: rgba(255, 255, 255, 1); |
||||
|
} |
||||
|
|
||||
|
.spec-card:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12); |
||||
|
} |
||||
|
|
||||
|
.spec-info { |
||||
|
margin-bottom: 20rpx; |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.spec-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
padding-right: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-price { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.price-label { |
||||
|
font-size: 20rpx; |
||||
|
color: #999; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #e74c3c; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover .price-value { |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
|
||||
|
.spec-arrow { |
||||
|
position: absolute; |
||||
|
top: 32rpx; |
||||
|
right: 28rpx; |
||||
|
font-size: 32rpx; |
||||
|
color: #4a90e2; |
||||
|
font-weight: bold; |
||||
|
opacity: 0.6; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.spec-card:hover .spec-arrow { |
||||
|
opacity: 1; |
||||
|
transform: translateX(4rpx); |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.empty-icon { |
||||
|
font-size: 160rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 32rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 48rpx; |
||||
|
line-height: 1.4; |
||||
|
padding: 0 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.content { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.product-card { |
||||
|
padding: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-card { |
||||
|
padding: 28rpx 20rpx; |
||||
|
min-height: 180rpx; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.button-group { |
||||
|
flex-direction: column; |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-primary, |
||||
|
.btn-secondary { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 小屏幕设备适配 */ |
||||
|
@media (max-width: 414rpx) { |
||||
|
.spec-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-card { |
||||
|
padding: 24rpx 16rpx; |
||||
|
min-height: 160rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-name { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.price-value { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 暂无更多规格提示 */ |
||||
|
.no-more { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 48rpx 0; |
||||
|
margin-top: 16rpx; |
||||
|
margin-bottom: 100rpx; /* 添加底部边距,避免被导航栏遮挡 */ |
||||
|
} |
||||
|
|
||||
|
.no-more-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(0, 0, 0, 0.03); |
||||
|
padding: 12rpx 32rpx; |
||||
|
border-radius: 30rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 分类选择区域 */ |
||||
|
.category-section { |
||||
|
margin-top: 24rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.category-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
gap: 20rpx; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.category-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
min-height: 150rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.category-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 6rpx; |
||||
|
height: 100%; |
||||
|
background: linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
} |
||||
|
|
||||
|
.category-card:hover { |
||||
|
transform: translateY(-6rpx); |
||||
|
box-shadow: 0 12rpx 32rpx rgba(0,0,0,0.15); |
||||
|
background: rgba(255, 255, 255, 1); |
||||
|
} |
||||
|
|
||||
|
.category-card:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12); |
||||
|
} |
||||
|
|
||||
|
.category-info { |
||||
|
flex: 1; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.category-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
} |
||||
|
|
||||
|
.category-arrow { |
||||
|
font-size: 32rpx; |
||||
|
color: #4a90e2; |
||||
|
font-weight: bold; |
||||
|
opacity: 0.6; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.category-card:hover .category-arrow { |
||||
|
opacity: 1; |
||||
|
transform: translateX(4rpx); |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 - 分类区域 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.category-grid { |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.category-card { |
||||
|
padding: 28rpx 20rpx; |
||||
|
min-height: 130rpx; |
||||
|
} |
||||
|
|
||||
|
.category-name { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 414rpx) { |
||||
|
.category-grid { |
||||
|
grid-template-columns: 1fr; |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.category-card { |
||||
|
padding: 24rpx 16rpx; |
||||
|
min-height: 110rpx; |
||||
|
} |
||||
|
|
||||
|
.category-name { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 调整规格网格布局,确保不被导航栏遮挡 */ |
||||
|
.spec-section { |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 登录弹窗样式 */ |
||||
|
.auth-modal-overlay { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background-color: rgba(0, 0, 0, 0.5); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-container { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 48rpx; |
||||
|
width: 80%; |
||||
|
max-width: 500rpx; |
||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
} |
||||
|
|
||||
|
.auth-modal-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
text-align: center; |
||||
|
margin-bottom: 24rpx; |
||||
|
color: #2c3e50; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-content { |
||||
|
font-size: 28rpx; |
||||
|
text-align: center; |
||||
|
margin-bottom: 48rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.5; |
||||
|
padding: 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-buttons { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-primary-button { |
||||
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
color: white; |
||||
|
border: none; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 24rpx; |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
min-height: 88rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 4rpx 16rpx rgba(96, 165, 250, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.auth-primary-button:hover { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 24rpx rgba(96, 165, 250, 0.6); |
||||
|
} |
||||
|
|
||||
|
.auth-cancel-button { |
||||
|
background: rgba(255, 255, 255, 0.9); |
||||
|
color: #2c3e50; |
||||
|
border: 2rpx solid #e2e8f0; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 24rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
min-height: 88rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.auth-cancel-button:hover { |
||||
|
background: rgba(236, 240, 241, 0.8); |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
@ -0,0 +1,508 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productNames: [], |
||||
|
loading: false, |
||||
|
error: '', |
||||
|
category: '', |
||||
|
priceRange: { // 价格范围
|
||||
|
min: 0, |
||||
|
max: 0, |
||||
|
hasPrice: false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 解析规格,提取类型(净重/毛重)和数值范围
|
||||
|
parseSpecification(spec) { |
||||
|
const weightMatch = spec.match(/(净重|毛重)(\d+)-(\d+)/); |
||||
|
if (weightMatch) { |
||||
|
const type = weightMatch[1]; // 净重或毛重
|
||||
|
const min = parseFloat(weightMatch[2]); |
||||
|
const max = parseFloat(weightMatch[3]); |
||||
|
const avg = (min + max) / 2; |
||||
|
return { |
||||
|
type: type, |
||||
|
min: min, |
||||
|
max: max, |
||||
|
avg: avg |
||||
|
}; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
// 对分类参数进行 URL 解码
|
||||
|
const category = options.category ? decodeURIComponent(options.category) : ''; |
||||
|
this.setData({ category: category }); |
||||
|
console.log('解码后的分类:', category); |
||||
|
this.loadProductNames(); |
||||
|
}, |
||||
|
|
||||
|
loadProductNames() { |
||||
|
this.setData({ |
||||
|
loading: true, |
||||
|
error: '', |
||||
|
productNames: [] // 清空商品名称数据,确保加载时只显示加载状态
|
||||
|
}); |
||||
|
|
||||
|
console.log('开始加载商品列表,当前分类:', this.data.category); |
||||
|
|
||||
|
// 尝试从本地存储中获取分类商品映射数据
|
||||
|
const categoryProductsMap = wx.getStorageSync('evaluate2CategoryProductsMap') || {}; |
||||
|
|
||||
|
if (categoryProductsMap && Object.keys(categoryProductsMap).length > 0) { |
||||
|
console.log('从本地存储获取分类商品映射数据'); |
||||
|
|
||||
|
// 获取当前分类下的商品名称列表
|
||||
|
let productNames = []; |
||||
|
if (this.data.category) { |
||||
|
productNames = categoryProductsMap[this.data.category] || []; |
||||
|
console.log(`分类"${this.data.category}"下的商品数量:`, productNames.length); |
||||
|
console.log('商品名称列表:', productNames); |
||||
|
} else { |
||||
|
// 如果没有分类参数,获取所有商品名称
|
||||
|
const allProductNames = []; |
||||
|
Object.values(categoryProductsMap).forEach(names => { |
||||
|
allProductNames.push(...names); |
||||
|
}); |
||||
|
// 去重
|
||||
|
productNames = [...new Set(allProductNames)]; |
||||
|
console.log('所有商品数量:', productNames.length); |
||||
|
} |
||||
|
|
||||
|
// 计算价格范围
|
||||
|
let priceRange = { min: 0, max: 0, hasPrice: false }; |
||||
|
|
||||
|
// 从原始商品数据中计算价格范围
|
||||
|
const allProducts = wx.getStorageSync('allProducts') || []; |
||||
|
console.log('本地存储中的商品总数:', allProducts.length); |
||||
|
console.log('当前分类:', this.data.category); |
||||
|
if (allProducts.length > 0) { |
||||
|
// 过滤出当前分类下的商品
|
||||
|
const categoryProducts = allProducts.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const productCategory = String(product.category).trim(); |
||||
|
return productCategory === this.data.category; |
||||
|
}); |
||||
|
console.log('当前分类下的商品数量:', categoryProducts.length); |
||||
|
console.log('当前分类下的商品详情:', categoryProducts); |
||||
|
|
||||
|
// 按规格分组计算平均价格
|
||||
|
const specPriceMap = {}; |
||||
|
categoryProducts.forEach(product => { |
||||
|
console.log('处理商品:', product.productName || product.name, '价格:', product.price, '规格:', product.specification || product.spec); |
||||
|
if (product.price && (product.specification || product.spec)) { |
||||
|
const priceStr = String(product.price).trim(); |
||||
|
const specStr = String(product.specification || product.spec).trim(); |
||||
|
|
||||
|
console.log('商品价格字符串:', priceStr, '规格字符串:', specStr); |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const priceArray = priceStr.split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
console.log('处理后的价格数组:', priceArray); |
||||
|
|
||||
|
// 处理逗号分隔的多个规格
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
console.log('处理后的规格数组:', specs); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
specs = processedSpecs; |
||||
|
console.log('最终规格数组:', specs); |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
console.log('处理规格:', spec, '对应价格索引:', index); |
||||
|
if (spec.length > 0 && index < priceArray.length) { |
||||
|
const price = priceArray[index]; |
||||
|
if (price && price.trim() !== '') { |
||||
|
const priceValue = parseFloat(price); |
||||
|
if (!isNaN(priceValue)) { |
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
console.log('解析规格结果:', specInfo); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
let finalPrice = priceValue; |
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
finalPrice = specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
finalPrice = (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
console.log('价格计算:', priceValue, '->', finalPrice); |
||||
|
} |
||||
|
|
||||
|
// 按规格分组存储价格
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(finalPrice); |
||||
|
console.log('存储价格:', finalPrice, '到规格:', spec); |
||||
|
} else { |
||||
|
console.log('价格解析失败:', price); |
||||
|
} |
||||
|
} else { |
||||
|
console.log('价格为空或无效:', price); |
||||
|
} |
||||
|
} else { |
||||
|
console.log('规格长度为0或价格索引超出范围:', spec.length, index, priceArray.length); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
console.log('商品缺少价格或规格:', !product.price ? '无价格' : '', !product.specification && !product.spec ? '无规格' : ''); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 计算每个规格的平均价格,然后找出最低和最高
|
||||
|
const specAvgPrices = []; |
||||
|
Object.keys(specPriceMap).forEach(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
if (prices.length > 0) { |
||||
|
const avgPrice = prices.reduce((sum, price) => sum + price, 0) / prices.length; |
||||
|
specAvgPrices.push(avgPrice); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (specAvgPrices.length > 0) { |
||||
|
priceRange.min = Math.round(Math.min(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.max = Math.round(Math.max(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.hasPrice = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
productNames: productNames, |
||||
|
priceRange: priceRange, |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
} else { |
||||
|
// 如果本地存储中没有数据,尝试从本地存储获取原始商品数据
|
||||
|
const allProducts = wx.getStorageSync('allProducts') || []; |
||||
|
|
||||
|
if (allProducts.length > 0) { |
||||
|
console.log('从本地存储获取原始商品数据'); |
||||
|
|
||||
|
// 过滤出有有效category字段的商品
|
||||
|
const productsWithCategory = allProducts.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const categoryStr = String(product.category).trim(); |
||||
|
return categoryStr !== ''; |
||||
|
}); |
||||
|
|
||||
|
// 移除价格过滤,获取所有商品
|
||||
|
let filteredProducts = productsWithCategory; |
||||
|
|
||||
|
// 如果有分类参数,过滤出该分类下的商品
|
||||
|
if (this.data.category) { |
||||
|
console.log('当前分类:', this.data.category); |
||||
|
const targetCategory = String(this.data.category).trim(); |
||||
|
filteredProducts = filteredProducts.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const productCategory = String(product.category).trim(); |
||||
|
return productCategory === targetCategory; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 提取商品名称,支持多种字段
|
||||
|
const productNames = filteredProducts.map(product => { |
||||
|
// 尝试从多个字段获取商品名称
|
||||
|
const nameFields = [product.productName, product.name, product.product]; |
||||
|
for (const field of nameFields) { |
||||
|
if (field) { |
||||
|
const trimmedName = String(field).trim(); |
||||
|
if (trimmedName !== '') { |
||||
|
return trimmedName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
}).filter(name => name !== ''); |
||||
|
|
||||
|
// 去重
|
||||
|
const uniqueProductNames = [...new Set(productNames)]; |
||||
|
console.log('最终商品名称列表:', uniqueProductNames); |
||||
|
|
||||
|
// 计算价格范围
|
||||
|
let priceRange = { min: 0, max: 0, hasPrice: false }; |
||||
|
|
||||
|
// 从过滤后的商品中计算价格范围,按规格分组计算平均价格
|
||||
|
const specPriceMap = {}; |
||||
|
filteredProducts.forEach(product => { |
||||
|
if (product.price && (product.specification || product.spec)) { |
||||
|
const priceStr = String(product.price).trim(); |
||||
|
const specStr = String(product.specification || product.spec).trim(); |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const priceArray = priceStr.split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
|
||||
|
// 处理逗号分隔的多个规格
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0 && index < priceArray.length) { |
||||
|
const price = priceArray[index]; |
||||
|
if (price && price.trim() !== '') { |
||||
|
const priceValue = parseFloat(price); |
||||
|
if (!isNaN(priceValue)) { |
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
let finalPrice = priceValue; |
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
finalPrice = specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
finalPrice = (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 按规格分组存储价格
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(finalPrice); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 计算每个规格的平均价格,然后找出最低和最高
|
||||
|
const specAvgPrices = []; |
||||
|
Object.keys(specPriceMap).forEach(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
if (prices.length > 0) { |
||||
|
const avgPrice = prices.reduce((sum, price) => sum + price, 0) / prices.length; |
||||
|
specAvgPrices.push(avgPrice); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (specAvgPrices.length > 0) { |
||||
|
priceRange.min = Math.round(Math.min(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.max = Math.round(Math.max(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.hasPrice = true; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
productNames: uniqueProductNames, |
||||
|
priceRange: priceRange, |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
} else { |
||||
|
// 如果本地存储中也没有原始商品数据,调用API获取数据
|
||||
|
console.log('本地存储中没有数据,调用API获取商品数据...'); |
||||
|
const api = require('../../utils/api'); |
||||
|
// 使用正确的参数调用getProducts方法
|
||||
|
api.getProducts(1, 1000, 'all', '').then(result => { |
||||
|
console.log('API返回结果:', result); |
||||
|
|
||||
|
// 从返回对象中提取products数组,如果不存在则使用空数组
|
||||
|
const products = result.products || []; |
||||
|
|
||||
|
// 过滤出有有效category字段的商品
|
||||
|
const productsWithCategory = products.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const categoryStr = String(product.category).trim(); |
||||
|
return categoryStr !== ''; |
||||
|
}); |
||||
|
|
||||
|
// 移除价格过滤,获取所有商品
|
||||
|
let filteredProducts = productsWithCategory; |
||||
|
|
||||
|
// 如果有分类参数,过滤出该分类下的商品
|
||||
|
if (this.data.category) { |
||||
|
console.log('当前分类:', this.data.category); |
||||
|
const targetCategory = String(this.data.category).trim(); |
||||
|
filteredProducts = filteredProducts.filter(product => { |
||||
|
if (!product.category) return false; |
||||
|
const productCategory = String(product.category).trim(); |
||||
|
return productCategory === targetCategory; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 提取商品名称,支持多种字段
|
||||
|
const productNames = filteredProducts.map(product => { |
||||
|
// 尝试从多个字段获取商品名称
|
||||
|
const nameFields = [product.productName, product.name, product.product]; |
||||
|
for (const field of nameFields) { |
||||
|
if (field) { |
||||
|
const trimmedName = String(field).trim(); |
||||
|
if (trimmedName !== '') { |
||||
|
return trimmedName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
}).filter(name => name !== ''); |
||||
|
|
||||
|
// 去重
|
||||
|
const uniqueProductNames = [...new Set(productNames)]; |
||||
|
console.log('最终商品名称列表:', uniqueProductNames); |
||||
|
|
||||
|
// 计算价格范围
|
||||
|
let priceRange = { min: 0, max: 0, hasPrice: false }; |
||||
|
|
||||
|
// 从过滤后的商品中计算价格范围,按规格分组计算平均价格
|
||||
|
const specPriceMap = {}; |
||||
|
filteredProducts.forEach(product => { |
||||
|
if (product.price && (product.specification || product.spec)) { |
||||
|
const priceStr = String(product.price).trim(); |
||||
|
const specStr = String(product.specification || product.spec).trim(); |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const priceArray = priceStr.split(',').map(p => p.trim()).filter(p => p && p.trim() !== ''); |
||||
|
|
||||
|
// 处理逗号分隔的多个规格
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0 && index < priceArray.length) { |
||||
|
const price = priceArray[index]; |
||||
|
if (price && price.trim() !== '') { |
||||
|
const priceValue = parseFloat(price); |
||||
|
if (!isNaN(priceValue)) { |
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
|
||||
|
// 价格<10的需要按照公式计算
|
||||
|
let finalPrice = priceValue; |
||||
|
if (priceValue < 10 && specInfo) { |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
finalPrice = specInfo.avg * priceValue; |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
finalPrice = (specInfo.avg - 5) * priceValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 按规格分组存储价格
|
||||
|
if (!specPriceMap[spec]) { |
||||
|
specPriceMap[spec] = []; |
||||
|
} |
||||
|
specPriceMap[spec].push(finalPrice); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 计算每个规格的平均价格,然后找出最低和最高
|
||||
|
const specAvgPrices = []; |
||||
|
Object.keys(specPriceMap).forEach(spec => { |
||||
|
const prices = specPriceMap[spec]; |
||||
|
if (prices.length > 0) { |
||||
|
const avgPrice = prices.reduce((sum, price) => sum + price, 0) / prices.length; |
||||
|
specAvgPrices.push(avgPrice); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (specAvgPrices.length > 0) { |
||||
|
priceRange.min = Math.round(Math.min(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.max = Math.round(Math.max(...specAvgPrices) * 100) / 100; |
||||
|
priceRange.hasPrice = true; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
productNames: uniqueProductNames, |
||||
|
priceRange: priceRange, |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}).catch(err => { |
||||
|
console.error('获取商品列表失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取商品列表失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
selectProduct(e) { |
||||
|
const productName = e.currentTarget.dataset.product; |
||||
|
console.log('选择商品:', productName); |
||||
|
// 将商品名称和当前分类存储到本地存储
|
||||
|
wx.setStorageSync('selectedProductName', productName); |
||||
|
wx.setStorageSync('selectedCategory', this.data.category); // 存储当前分类
|
||||
|
// 使用wx.switchTab导航到tabBar页面
|
||||
|
wx.switchTab({ |
||||
|
url: '/pages/evaluate2/index', |
||||
|
success: function(res) { |
||||
|
console.log('跳转成功:', res); |
||||
|
}, |
||||
|
fail: function(err) { |
||||
|
console.error('跳转失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回分类选择页面
|
||||
|
goBackToCategories() { |
||||
|
console.log('返回分类选择页面'); |
||||
|
wx.redirectTo({ |
||||
|
url: '/pages/evaluate2/one' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
console.log('开始下拉刷新'); |
||||
|
this.loadProductNames(); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark", |
||||
|
"disableSwipeBack": true |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 头部导航栏 --> |
||||
|
<view class="header"> |
||||
|
<view class="header-content" style="display: flex; align-items: center; justify-content: space-between; box-sizing: border-box; padding: 0 20rpx;"> |
||||
|
<button class="title" bindtap="goBackToCategories">返回分类</button> |
||||
|
<text class="title">商品选择</text> |
||||
|
<view style="width: 120rpx;"></view> <!-- 占位,保持标题居中 --> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<!-- 加载中状态 --> |
||||
|
<view wx:if="{{loading}}" class="loading"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text class="loading-text">正在加载商品数据...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view wx:if="{{error}}" class="error-card"> |
||||
|
<view class="error-icon">⚠️</view> |
||||
|
<text class="error-text">{{error}}</text> |
||||
|
<button bindtap="loadProductNames" class="btn-primary">重新加载</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品名称列表 --> |
||||
|
<view wx:else class="product-section"> |
||||
|
<!-- 当前选择的分类 --> |
||||
|
<view wx:if="{{category}}" class="current-category"> |
||||
|
<text class="current-category-label">当前分类:</text> |
||||
|
<text class="current-category-name">{{category}}</text> |
||||
|
<!-- 价格范围 --> |
||||
|
<view wx:if="{{priceRange.hasPrice}}" class="price-range"> |
||||
|
<text class="price-range-label">价格范围:</text> |
||||
|
<text class="price-range-value">¥{{priceRange.min}} - ¥{{priceRange.max}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">商品列表</text> |
||||
|
<text class="section-count">{{productNames.length}}个商品</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 有商品时显示网格 --> |
||||
|
<view wx:if="{{productNames.length > 0}}" class="product-grid"> |
||||
|
<view |
||||
|
wx:for="{{productNames}}" |
||||
|
wx:key="*this" |
||||
|
class="product-card" |
||||
|
data-product="{{item}}" |
||||
|
bindtap="selectProduct" |
||||
|
> |
||||
|
<view class="product-icon">🛍️</view> |
||||
|
<text class="product-name">{{item}}</text> |
||||
|
<view class="product-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无商品时显示空状态 --> |
||||
|
<view wx:if="{{productNames.length === 0}}" class="empty-state"> |
||||
|
<view class="empty-icon">📦</view> |
||||
|
<text class="empty-text"> |
||||
|
{{category ? '该分类下暂无商品' : '暂无商品'}} |
||||
|
</text> |
||||
|
<button bindtap="loadProductNames" class="btn-secondary">重新加载</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 暂无更多商品提示 --> |
||||
|
<view wx:if="{{productNames.length > 0}}" class="no-more"> |
||||
|
<text class="no-more-text">暂无更多商品选择</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,415 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
overflow-x: hidden; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.08); |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.header-content { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 返回按钮样式 */ |
||||
|
.back-button { |
||||
|
width: 120rpx; |
||||
|
height: 60rpx; |
||||
|
line-height: 60rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #4a90e2; |
||||
|
background: transparent; |
||||
|
border: none; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.back-button:hover { |
||||
|
color: #3b82f6; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 16rpx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
overflow-x: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 加载状态 */ |
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border: 8rpx solid rgba(74, 144, 226, 0.2); |
||||
|
border-top: 8rpx solid #4a90e2; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 32rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 错误提示卡片 */ |
||||
|
.error-card { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 48rpx; |
||||
|
margin: 32rpx 0; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 80rpx; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.error-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #e74c3c; |
||||
|
margin-bottom: 32rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式 */ |
||||
|
.btn-primary { |
||||
|
width: 240rpx; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
color: #fff; |
||||
|
border: none; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(96, 165, 250, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-primary:hover { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 6rpx 16rpx rgba(74, 144, 226, 0.5); |
||||
|
} |
||||
|
|
||||
|
/* 商品列表区域 */ |
||||
|
.product-section { |
||||
|
margin-top: 24rpx; |
||||
|
} |
||||
|
|
||||
|
/* 当前选择的分类 */ |
||||
|
.current-category { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
padding: 16rpx 20rpx; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
border-left: 6rpx solid #4a90e2; |
||||
|
} |
||||
|
|
||||
|
.current-category-label { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.current-category-name { |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
color: #4a90e2; |
||||
|
} |
||||
|
|
||||
|
/* 价格范围 */ |
||||
|
.price-range { |
||||
|
margin-top: 8rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.price-range-label { |
||||
|
font-size: 22rpx; |
||||
|
color: #666; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.price-range-value { |
||||
|
font-size: 22rpx; |
||||
|
font-weight: 500; |
||||
|
color: #ff6b81; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 24rpx; |
||||
|
padding: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
padding-left: 16rpx; |
||||
|
border-left: 6rpx solid #4a90e2; |
||||
|
} |
||||
|
|
||||
|
.section-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(74, 144, 226, 0.1); |
||||
|
padding: 6rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品网格布局 */ |
||||
|
.product-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 20rpx; |
||||
|
margin-top: 16rpx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
overflow-x: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 商品卡片 */ |
||||
|
.product-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 20rpx; |
||||
|
padding: 32rpx 24rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
text-align: center; |
||||
|
min-height: 180rpx; |
||||
|
backdrop-filter: blur(8rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 6rpx; |
||||
|
background: linear-gradient(90deg, #60a5fa 0%, #3b82f6 100%); |
||||
|
} |
||||
|
|
||||
|
.product-card:hover { |
||||
|
transform: translateY(-6rpx); |
||||
|
box-shadow: 0 12rpx 32rpx rgba(0,0,0,0.15); |
||||
|
background: rgba(255, 255, 255, 1); |
||||
|
} |
||||
|
|
||||
|
.product-card:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12); |
||||
|
} |
||||
|
|
||||
|
.product-icon { |
||||
|
font-size: 72rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
opacity: 0.9; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.product-card:hover .product-icon { |
||||
|
transform: scale(1.1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
word-break: break-word; |
||||
|
margin-bottom: 20rpx; |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.product-arrow { |
||||
|
position: absolute; |
||||
|
bottom: 20rpx; |
||||
|
right: 24rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #4a90e2; |
||||
|
font-weight: bold; |
||||
|
opacity: 0.6; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.product-card:hover .product-arrow { |
||||
|
opacity: 1; |
||||
|
transform: translateX(4rpx); |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.content { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.product-card { |
||||
|
padding: 28rpx 20rpx; |
||||
|
min-height: 160rpx; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.product-icon { |
||||
|
font-size: 60rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 小屏幕设备适配 */ |
||||
|
@media (max-width: 414rpx) { |
||||
|
.product-grid { |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.product-card { |
||||
|
padding: 20rpx 16rpx; |
||||
|
min-height: 140rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
|
||||
|
.product-icon { |
||||
|
font-size: 50rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 暂无更多商品提示 */ |
||||
|
.no-more { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 48rpx 0; |
||||
|
margin-top: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.no-more-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background: rgba(0, 0, 0, 0.03); |
||||
|
padding: 12rpx 32rpx; |
||||
|
border-radius: 30rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 160rpx 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.empty-icon { |
||||
|
font-size: 160rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 32rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 48rpx; |
||||
|
line-height: 1.4; |
||||
|
padding: 0 40rpx; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
width: 200rpx; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 40rpx; |
||||
|
background: #fff; |
||||
|
color: #4a90e2; |
||||
|
border: 2rpx solid #4a90e2; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:hover { |
||||
|
background: rgba(74, 144, 226, 0.05); |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productName: '', |
||||
|
specification: '', |
||||
|
price: 0, |
||||
|
quantity: 1, |
||||
|
totalPrice: 0, |
||||
|
loading: false, |
||||
|
error: '' |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
console.log('接收到的参数:', options); |
||||
|
// 即使参数不完整,也要尝试获取并设置
|
||||
|
const productName = options.productName ? decodeURIComponent(options.productName) : ''; |
||||
|
const specification = options.specification ? decodeURIComponent(options.specification) : ''; |
||||
|
let price = 0; |
||||
|
|
||||
|
if (options.price) { |
||||
|
const decodedPrice = decodeURIComponent(options.price); |
||||
|
console.log('解码后的价格:', decodedPrice); |
||||
|
price = parseFloat(decodedPrice) || 0; |
||||
|
} |
||||
|
|
||||
|
console.log('解析后的参数:', { productName, specification, price }); |
||||
|
|
||||
|
this.setData({ |
||||
|
productName: productName, |
||||
|
specification: specification, |
||||
|
price: price, |
||||
|
totalPrice: 0 // 初始时总价为0,不显示
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 件数输入变化
|
||||
|
onQuantityChange(e) { |
||||
|
const quantity = parseInt(e.detail.value) || 1; |
||||
|
this.setData({ |
||||
|
quantity: quantity |
||||
|
// 只更新件数,不更新总价,等待点击计算按钮
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 减少数量
|
||||
|
decreaseQuantity() { |
||||
|
if (this.data.quantity > 1) { |
||||
|
this.setData({ |
||||
|
quantity: this.data.quantity - 1 |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 增加数量
|
||||
|
increaseQuantity() { |
||||
|
this.setData({ |
||||
|
quantity: this.data.quantity + 1 |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 计算价格
|
||||
|
calculatePrice() { |
||||
|
const totalPrice = Math.round(this.data.price * this.data.quantity * 10) / 10; |
||||
|
this.setData({ |
||||
|
totalPrice: totalPrice |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: '计算完成', |
||||
|
icon: 'success', |
||||
|
duration: 1000 |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh() { |
||||
|
console.log('开始下拉刷新'); |
||||
|
// 重新加载页面数据
|
||||
|
// 由于spec-detail页面的数据是通过URL参数传递的,这里可以重新获取参数并设置数据
|
||||
|
const options = this.options || {}; |
||||
|
const productName = options.productName ? decodeURIComponent(options.productName) : ''; |
||||
|
const specification = options.specification ? decodeURIComponent(options.specification) : ''; |
||||
|
let price = 0; |
||||
|
|
||||
|
if (options.price) { |
||||
|
const decodedPrice = decodeURIComponent(options.price); |
||||
|
price = parseFloat(decodedPrice) || 0; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
productName: productName, |
||||
|
specification: specification, |
||||
|
price: price, |
||||
|
totalPrice: 0 // 初始时总价为0,不显示
|
||||
|
}); |
||||
|
|
||||
|
// 结束下拉刷新
|
||||
|
wx.stopPullDownRefresh(); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark" |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
<view class="page-container"> |
||||
|
<!-- 头部导航栏 --> |
||||
|
<view class="page-header"> |
||||
|
<text class="header-title">{{productName || '规格详情'}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 主要内容区域 --> |
||||
|
<view class="main-content"> |
||||
|
<!-- 规格信息卡片 --> |
||||
|
<view class="info-card"> |
||||
|
<text class="card-subtitle">规格信息</text> |
||||
|
<view class="info-row"> |
||||
|
<text class="info-label">规格</text> |
||||
|
<text class="info-value">{{specification}}</text> |
||||
|
</view> |
||||
|
<view class="info-row"> |
||||
|
<text class="info-label">单价</text> |
||||
|
<text class="info-value price">¥{{price || 0}}</text> |
||||
|
</view> |
||||
|
<text class="info-hint">您已选择此规格进行估价</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 数量设置卡片 --> |
||||
|
<view class="control-card"> |
||||
|
<text class="card-subtitle">数量设置</text> |
||||
|
<view class="quantity-box"> |
||||
|
<button bindtap="decreaseQuantity" class="quantity-btn minus" style="width: 180rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">-</button> |
||||
|
<input style="width: 420rpx; display: block; box-sizing: border-box" |
||||
|
class="quantity-input" |
||||
|
type="number" |
||||
|
value="{{quantity}}" |
||||
|
bindinput="onQuantityChange" |
||||
|
min="1" |
||||
|
/> |
||||
|
<button bindtap="increaseQuantity" class="quantity-btn plus" style="width: 180rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">+</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 计算按钮 --> |
||||
|
<view class="button-section"> |
||||
|
<button bindtap="calculatePrice" class="primary-btn" style="height: 120rpx; display: block; box-sizing: border-box; left: 0rpx; top: 0rpx">计算预估价格</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 结果展示卡片 --> |
||||
|
<view class="result-card" wx:if="{{totalPrice > 0}}"> |
||||
|
<text class="card-subtitle">预估结果</text> |
||||
|
<view class="result-row"> |
||||
|
<text class="result-label">预估总价</text> |
||||
|
<text class="result-value">¥{{totalPrice}}</text> |
||||
|
</view> |
||||
|
<text class="result-hint">此价格为预估价格,实际价格可能会有所变动</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 返回按钮 --> |
||||
|
<view class="button-section bottom"> |
||||
|
<button bindtap="goBack" class="secondary-btn" style="height: 120rpx; display: block; box-sizing: border-box; left: 0rpx; top: 0rpx">返回规格列表</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,401 @@ |
|||||
|
/* 页面容器 */ |
||||
|
.page-container { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 头部样式 */ |
||||
|
.page-header { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); |
||||
|
padding: 28rpx 0; |
||||
|
text-align: center; |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.header-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
/* 主要内容区域 */ |
||||
|
.main-content { |
||||
|
flex: 1; |
||||
|
padding: 24rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 24rpx; |
||||
|
padding-bottom: 48rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 卡片基础样式 */ |
||||
|
.info-card, |
||||
|
.control-card, |
||||
|
.result-card { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
border-radius: 24rpx; |
||||
|
padding: 32rpx; |
||||
|
box-shadow: 0 6rpx 24rpx rgba(0,0,0,0.08); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.info-card:hover, |
||||
|
.control-card:hover, |
||||
|
.result-card:hover { |
||||
|
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12); |
||||
|
transform: translateY(-4rpx); |
||||
|
background: rgba(255, 255, 255, 0.98); |
||||
|
} |
||||
|
|
||||
|
/* 卡片标题 */ |
||||
|
.card-subtitle { |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
color: #64748b; |
||||
|
margin-bottom: 24rpx; |
||||
|
padding-bottom: 16rpx; |
||||
|
border-bottom: 1rpx solid #f1f5f9; |
||||
|
} |
||||
|
|
||||
|
/* 信息行样式 */ |
||||
|
.info-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 16rpx 0; |
||||
|
border-bottom: 1rpx solid #f8fafc; |
||||
|
} |
||||
|
|
||||
|
.info-row:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
font-size: 26rpx; |
||||
|
color: #64748b; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
} |
||||
|
|
||||
|
.info-value.price { |
||||
|
color: #ef4444; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
/* 提示信息 */ |
||||
|
.info-hint { |
||||
|
font-size: 22rpx; |
||||
|
color: #94a3b8; |
||||
|
text-align: center; |
||||
|
margin-top: 20rpx; |
||||
|
padding-top: 20rpx; |
||||
|
border-top: 1rpx solid #f1f5f9; |
||||
|
} |
||||
|
|
||||
|
/* 数量控制 */ |
||||
|
.quantity-box { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 24rpx; |
||||
|
margin-top: 12rpx; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.quantity-btn { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
background: rgba(255, 255, 255, 0.9); |
||||
|
color: #3b82f6; |
||||
|
border: 2rpx solid rgba(96, 165, 250, 0.4); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); |
||||
|
} |
||||
|
|
||||
|
.quantity-btn:hover { |
||||
|
background: rgba(59, 130, 246, 0.1); |
||||
|
border-color: rgba(59, 130, 246, 0.6); |
||||
|
transform: scale(1.1); |
||||
|
} |
||||
|
|
||||
|
.quantity-btn.minus:hover { |
||||
|
background: rgba(239, 68, 68, 0.1); |
||||
|
border-color: rgba(239, 68, 68, 0.6); |
||||
|
color: #ef4444; |
||||
|
} |
||||
|
|
||||
|
.quantity-btn.plus:hover { |
||||
|
background: rgba(16, 185, 129, 0.1); |
||||
|
border-color: rgba(16, 185, 129, 0.6); |
||||
|
color: #10b981; |
||||
|
} |
||||
|
|
||||
|
.quantity-input { |
||||
|
width: 160rpx; |
||||
|
height: 80rpx; |
||||
|
border: 2rpx solid rgba(96, 165, 250, 0.4); |
||||
|
border-radius: 16rpx; |
||||
|
padding: 0 24rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
text-align: center; |
||||
|
color: #2c3e50; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
box-shadow: inset 0 2rpx 8rpx rgba(0,0,0,0.08); |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 按钮区域 */ |
||||
|
.button-section { |
||||
|
margin-top: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.button-section.bottom { |
||||
|
margin-top: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式 */ |
||||
|
.primary-btn { |
||||
|
width: 100%; |
||||
|
height: 92rpx; |
||||
|
line-height: 92rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 46rpx; |
||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
||||
|
color: #fff; |
||||
|
border: none; |
||||
|
box-shadow: 0 6rpx 24rpx rgba(59, 130, 246, 0.4); |
||||
|
transition: all 0.3s ease; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.primary-btn:hover { |
||||
|
transform: translateY(-4rpx); |
||||
|
box-shadow: 0 8rpx 32rpx rgba(59, 130, 246, 0.5); |
||||
|
} |
||||
|
|
||||
|
.secondary-btn { |
||||
|
width: 100%; |
||||
|
height: 84rpx; |
||||
|
line-height: 84rpx; |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 42rpx; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
color: #3b82f6; |
||||
|
border: 2rpx solid rgba(59, 130, 246, 0.4); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
transition: all 0.3s ease; |
||||
|
text-align: center; |
||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.08); |
||||
|
} |
||||
|
|
||||
|
.secondary-btn:hover { |
||||
|
background: rgba(59, 130, 246, 0.08); |
||||
|
border-color: rgba(59, 130, 246, 0.6); |
||||
|
transform: translateY(-4rpx); |
||||
|
box-shadow: 0 6rpx 24rpx rgba(59, 130, 246, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 结果卡片 */ |
||||
|
.result-card { |
||||
|
animation: fadeInUp 0.6s ease-out; |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeInUp { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(30rpx); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 结果行 */ |
||||
|
.result-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.result-label { |
||||
|
font-size: 26rpx; |
||||
|
color: #64748b; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.result-value { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #ef4444; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(239, 68, 68, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 结果提示 */ |
||||
|
.result-hint { |
||||
|
font-size: 22rpx; |
||||
|
color: #94a3b8; |
||||
|
text-align: center; |
||||
|
margin-top: 16rpx; |
||||
|
padding-top: 16rpx; |
||||
|
border-top: 1rpx solid #f1f5f9; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.main-content { |
||||
|
padding: 20rpx; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.info-card, |
||||
|
.control-card, |
||||
|
.result-card { |
||||
|
padding: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.header-title { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.card-subtitle { |
||||
|
font-size: 24rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.info-label, |
||||
|
.info-value { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.info-value.price { |
||||
|
font-size: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.result-value { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-box { |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-btn { |
||||
|
width: 72rpx; |
||||
|
height: 72rpx; |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-input { |
||||
|
width: 140rpx; |
||||
|
height: 72rpx; |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.primary-btn { |
||||
|
height: 84rpx; |
||||
|
line-height: 84rpx; |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.secondary-btn { |
||||
|
height: 76rpx; |
||||
|
line-height: 76rpx; |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 小屏幕设备适配 */ |
||||
|
@media (max-width: 375rpx) { |
||||
|
.main-content { |
||||
|
padding: 16rpx; |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.info-card, |
||||
|
.control-card, |
||||
|
.result-card { |
||||
|
padding: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.header-title { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.card-subtitle { |
||||
|
font-size: 22rpx; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.info-label, |
||||
|
.info-value { |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
|
||||
|
.info-value.price { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.result-value { |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-box { |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-btn { |
||||
|
width: 64rpx; |
||||
|
height: 64rpx; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-input { |
||||
|
width: 120rpx; |
||||
|
height: 64rpx; |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.primary-btn { |
||||
|
height: 76rpx; |
||||
|
line-height: 76rpx; |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.secondary-btn { |
||||
|
height: 68rpx; |
||||
|
line-height: 68rpx; |
||||
|
font-size: 22rpx; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,366 @@ |
|||||
|
// pages/profile/authentication/index.js
|
||||
|
const API = require('../../../utils/api.js'); |
||||
|
Page({ |
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
idCardFront: '', // 身份证人像面
|
||||
|
idCardBack: '', // 身份证国徽面
|
||||
|
businessLicense: '', // 营业执照
|
||||
|
idcard1: null, // 身份证正面文件信息
|
||||
|
idcard2: null, // 身份证反面文件信息
|
||||
|
businessLicenseFile: null, // 营业执照文件信息
|
||||
|
name: '', // 姓名
|
||||
|
idNumber: '', // 身份证号
|
||||
|
address: '', // 居住地址
|
||||
|
validStart: '', // 有效期开始
|
||||
|
validEnd: '' // 有效期结束
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 返回上一页 |
||||
|
*/ |
||||
|
navigateBack() { |
||||
|
wx.navigateBack({ delta: 1 }); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 上传身份证人像面 |
||||
|
*/ |
||||
|
uploadIdCardFront() { |
||||
|
this.uploadImage('idCardFront'); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 上传身份证国徽面 |
||||
|
*/ |
||||
|
uploadIdCardBack() { |
||||
|
this.uploadImage('idCardBack'); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 上传营业执照 |
||||
|
*/ |
||||
|
uploadBusinessLicense() { |
||||
|
this.uploadImage('businessLicense'); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 通用图片上传方法 |
||||
|
* @param {string} field - 上传的字段名 |
||||
|
*/ |
||||
|
uploadImage(field) { |
||||
|
const _this = this; |
||||
|
wx.chooseImage({ |
||||
|
count: 1, |
||||
|
sizeType: ['compressed'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
success: (res) => { |
||||
|
const tempFilePaths = res.tempFilePaths; |
||||
|
if (tempFilePaths && tempFilePaths.length > 0) { |
||||
|
// 根据字段类型更新页面数据
|
||||
|
if (field === 'businessLicense') { |
||||
|
_this.setData({ |
||||
|
businessLicense: tempFilePaths[0], |
||||
|
businessLicenseFile: { |
||||
|
path: tempFilePaths[0], |
||||
|
name: `营业执照_${new Date().getTime()}.jpg` |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
_this.setData({ |
||||
|
[field === 'idCardFront' ? 'idCardFront' : 'idCardBack']: tempFilePaths[0], |
||||
|
[field === 'idCardFront' ? 'idcard1' : 'idcard2']: { |
||||
|
path: tempFilePaths[0], |
||||
|
name: `身份证${field === 'idCardFront' ? '正面' : '反面'}_${new Date().getTime()}.jpg` |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 模拟识别成功后填充信息
|
||||
|
_this.simulateOcrResult(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('选择图片失败:', err); |
||||
|
wx.showToast({ |
||||
|
title: '选择图片失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 模拟OCR识别结果 |
||||
|
*/ |
||||
|
simulateOcrResult() { |
||||
|
// 模拟识别成功后填充信息
|
||||
|
this.setData({ |
||||
|
name: '张三', |
||||
|
idNumber: '110101199001011234', |
||||
|
address: '北京市朝阳区建国路88号', |
||||
|
validStart: '2020-01-01', |
||||
|
validEnd: '2030-01-01' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 上传文件到服务器 |
||||
|
*/ |
||||
|
async uploadFileToServer(filePath, fileType) { |
||||
|
try { |
||||
|
console.log(`开始上传${fileType}文件:`, filePath); |
||||
|
|
||||
|
const result = await API.uploadSettlementFile(filePath, fileType); |
||||
|
|
||||
|
if (result && result.fileUrl) { |
||||
|
console.log(`${fileType}上传成功:`, result.fileUrl); |
||||
|
return result.fileUrl; |
||||
|
} else { |
||||
|
throw new Error(`${fileType}上传失败`); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error(`${fileType}上传失败:`, error); |
||||
|
wx.showToast({ |
||||
|
title: `${fileType}上传失败`, |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
throw error; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 提交认证 |
||||
|
*/ |
||||
|
async submitAuth() { |
||||
|
// 验证是否上传了身份证和营业执照
|
||||
|
if (!this.data.idCardFront || !this.data.idCardBack || !this.data.businessLicense) { |
||||
|
wx.showToast({ |
||||
|
title: '请上传身份证正反面和营业执照', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 检查用户是否已登录
|
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
|
||||
|
if (!openid || !userId) { |
||||
|
wx.showToast({ |
||||
|
title: '请先登录', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
wx.showLoading({ title: '正在提交认证信息...', mask: true }); |
||||
|
|
||||
|
try { |
||||
|
let idcard1Url = this.data.idcard1?.path || this.data.idCardFront; |
||||
|
let idcard2Url = this.data.idcard2?.path || this.data.idCardBack; |
||||
|
let businessLicenseUrl = this.data.businessLicenseFile?.path || this.data.businessLicense; |
||||
|
|
||||
|
// 检查是否是本地路径(需要上传)还是远程URL(直接使用)
|
||||
|
const isLocalPath = (path) => path && (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('wxfile://') || path.startsWith('cloud://')); |
||||
|
|
||||
|
// 上传身份证正面(仅当是本地路径时)
|
||||
|
if (this.data.idcard1?.path && !isLocalPath(this.data.idcard1.path)) { |
||||
|
idcard1Url = await this.uploadFileToServer(this.data.idcard1.path, 'idCardFront'); |
||||
|
} |
||||
|
|
||||
|
// 上传身份证反面(仅当是本地路径时)
|
||||
|
if (this.data.idcard2?.path && !isLocalPath(this.data.idcard2.path)) { |
||||
|
idcard2Url = await this.uploadFileToServer(this.data.idcard2.path, 'idCardBack'); |
||||
|
} |
||||
|
|
||||
|
// 上传营业执照(仅当是本地路径时)
|
||||
|
if (this.data.businessLicenseFile?.path && !isLocalPath(this.data.businessLicenseFile.path)) { |
||||
|
businessLicenseUrl = await this.uploadFileToServer(this.data.businessLicenseFile.path, 'businessLicense'); |
||||
|
} |
||||
|
|
||||
|
console.log('文件处理完成,正面:', idcard1Url, '反面:', idcard2Url, '营业执照:', businessLicenseUrl); |
||||
|
|
||||
|
// 准备提交数据
|
||||
|
const submitData = { |
||||
|
openid: openid, |
||||
|
userId: userId, |
||||
|
idcard1: idcard1Url, |
||||
|
idcard2: idcard2Url, |
||||
|
businesslicenseurl: businessLicenseUrl, |
||||
|
name: this.data.name, |
||||
|
idNumber: this.data.idNumber, |
||||
|
address: this.data.address |
||||
|
}; |
||||
|
|
||||
|
console.log('提交数据:', submitData); |
||||
|
|
||||
|
// 调用后端API提交数据
|
||||
|
const result = await API.request('/api/user/update', 'POST', submitData); |
||||
|
|
||||
|
console.log('认证提交结果:', result); |
||||
|
|
||||
|
if (result && result.success) { |
||||
|
// 更新本地存储的用户信息
|
||||
|
const userInfo = wx.getStorageSync('userInfo') || {}; |
||||
|
const updatedUserInfo = { |
||||
|
...userInfo, |
||||
|
idcard1: idcard1Url, |
||||
|
idcard2: idcard2Url, |
||||
|
businesslicenseurl: businessLicenseUrl, |
||||
|
name: this.data.name, |
||||
|
idNumber: this.data.idNumber, |
||||
|
address: this.data.address |
||||
|
}; |
||||
|
wx.setStorageSync('userInfo', updatedUserInfo); |
||||
|
|
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '认证成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
|
}); |
||||
|
|
||||
|
// 延时返回上一页
|
||||
|
setTimeout(() => { |
||||
|
this.navigateBack(); |
||||
|
}, 1500); |
||||
|
} else { |
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: result.message || '认证失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
wx.hideLoading(); |
||||
|
console.error('认证提交失败:', error); |
||||
|
wx.showToast({ |
||||
|
title: '提交失败,请重试', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
// 页面显示时加载已存在的认证信息
|
||||
|
this.loadExistingAuthData(); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 从服务器加载已存在的认证信息 |
||||
|
*/ |
||||
|
loadExistingAuthData() { |
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
|
||||
|
if (!openid || !userId) { |
||||
|
console.log('用户未登录,无法加载认证信息'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.log('开始加载用户认证信息,openid:', openid, 'userId:', userId); |
||||
|
|
||||
|
// 调用API获取用户信息,包括认证数据
|
||||
|
API.getUserInfo(openid).then(res => { |
||||
|
console.log('获取用户认证信息响应:', res); |
||||
|
|
||||
|
if (res && res.success && res.data) { |
||||
|
const userData = res.data; |
||||
|
console.log('用户认证数据:', userData); |
||||
|
|
||||
|
// 构建更新数据对象
|
||||
|
const updateData = { |
||||
|
name: userData.name || '', |
||||
|
idNumber: userData.idNumber || userData.id_number || '', |
||||
|
address: userData.address || '', |
||||
|
validStart: userData.validStart || userData.valid_start || '', |
||||
|
validEnd: userData.validEnd || userData.valid_end || '' |
||||
|
}; |
||||
|
|
||||
|
// 处理身份证正面图片
|
||||
|
if (userData.idcard1) { |
||||
|
updateData.idcard1 = { path: userData.idcard1 }; |
||||
|
updateData.idCardFront = userData.idcard1; |
||||
|
console.log('已加载身份证正面:', userData.idcard1); |
||||
|
} |
||||
|
|
||||
|
// 处理身份证反面图片
|
||||
|
if (userData.idcard2) { |
||||
|
updateData.idcard2 = { path: userData.idcard2 }; |
||||
|
updateData.idCardBack = userData.idcard2; |
||||
|
console.log('已加载身份证反面:', userData.idcard2); |
||||
|
} |
||||
|
|
||||
|
// 处理营业执照图片
|
||||
|
if (userData.businesslicenseurl) { |
||||
|
updateData.businessLicenseFile = { path: userData.businesslicenseurl }; |
||||
|
updateData.businessLicense = userData.businesslicenseurl; |
||||
|
console.log('已加载营业执照:', userData.businesslicenseurl); |
||||
|
} |
||||
|
|
||||
|
// 更新页面数据
|
||||
|
this.setData(updateData); |
||||
|
console.log('认证信息加载完成,当前数据:', this.data); |
||||
|
} else { |
||||
|
console.log('未获取到用户认证信息或获取失败'); |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.error('加载用户认证信息失败:', err); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"navigationBarTitleText": "个人认证", |
||||
|
"navigationBarBackgroundColor": "#07c160", |
||||
|
"navigationBarTextStyle": "white" |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 身份证上传区域 --> |
||||
|
<view class="upload-section"> |
||||
|
<!-- 人像面上传 --> |
||||
|
<view class="upload-item"> |
||||
|
<view class="upload-label"> |
||||
|
<view class="label-title">人像面</view> |
||||
|
<view class="label-desc">上传您身份证头像面</view> |
||||
|
</view> |
||||
|
<view class="upload-area" bindtap="uploadIdCardFront"> |
||||
|
<image wx:if="{{idCardFront}}" src="{{idCardFront}}" class="uploaded-image"></image> |
||||
|
<view wx:else class="upload-placeholder"> |
||||
|
<view class="upload-icon"> |
||||
|
<text style="font-size: 48rpx;">+</text> |
||||
|
</view> |
||||
|
<view class="upload-text">点击上传人像面</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 国徽面上传 --> |
||||
|
<view class="upload-item"> |
||||
|
<view class="upload-label"> |
||||
|
<view class="label-title">国徽面</view> |
||||
|
<view class="label-desc">上传您身份证国徽面</view> |
||||
|
</view> |
||||
|
<view class="upload-area" bindtap="uploadIdCardBack"> |
||||
|
<image wx:if="{{idCardBack}}" src="{{idCardBack}}" class="uploaded-image"></image> |
||||
|
<view wx:else class="upload-placeholder"> |
||||
|
<view class="upload-icon"> |
||||
|
<text style="font-size: 48rpx;">+</text> |
||||
|
</view> |
||||
|
<view class="upload-text">点击上传国徽面</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 营业执照上传 --> |
||||
|
<view class="upload-item"> |
||||
|
<view class="upload-label"> |
||||
|
<view class="label-title">营业执照</view> |
||||
|
<view class="label-desc">上传您的营业执照</view> |
||||
|
</view> |
||||
|
<view class="upload-area" bindtap="uploadBusinessLicense"> |
||||
|
<image wx:if="{{businessLicense}}" src="{{businessLicense}}" class="uploaded-image"></image> |
||||
|
<view wx:else class="upload-placeholder"> |
||||
|
<view class="upload-icon"> |
||||
|
<text style="font-size: 48rpx;">+</text> |
||||
|
</view> |
||||
|
<view class="upload-text">点击上传营业执照</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
|
||||
|
<!-- 认证按钮 --> |
||||
|
<view class="auth-btn" bindtap="submitAuth"> |
||||
|
<text class="auth-btn-text">认证</text> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,165 @@ |
|||||
|
/* pages/profile/authentication/index.wxss */ |
||||
|
|
||||
|
.container { |
||||
|
background-color: #f5f5f5; |
||||
|
min-height: 100vh; |
||||
|
padding-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 顶部导航栏 */ |
||||
|
.header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
padding: 20rpx 30rpx; |
||||
|
height: 100rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.back-btn { |
||||
|
width: 80rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.right-icon { |
||||
|
width: 80rpx; |
||||
|
} |
||||
|
|
||||
|
/* 上传区域 */ |
||||
|
.upload-section { |
||||
|
background-color: white; |
||||
|
margin: 20rpx 0; |
||||
|
padding: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.upload-item { |
||||
|
display: flex; |
||||
|
margin-bottom: 40rpx; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.upload-label { |
||||
|
flex: 1; |
||||
|
margin-right: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.label-title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.label-desc { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.upload-area { |
||||
|
width: 320rpx; |
||||
|
height: 200rpx; |
||||
|
border: 2rpx dashed #ddd; |
||||
|
border-radius: 12rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.upload-placeholder { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.upload-icon { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
background-color: #f0f0f0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.upload-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.uploaded-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border-radius: 12rpx; |
||||
|
} |
||||
|
|
||||
|
/* 信息展示区域 */ |
||||
|
.info-section { |
||||
|
background-color: white; |
||||
|
margin: 20rpx 0; |
||||
|
padding: 0 30rpx; |
||||
|
} |
||||
|
|
||||
|
.info-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 24rpx 0; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.info-item:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
width: 180rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
flex: 1; |
||||
|
font-size: 28rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 提示信息 */ |
||||
|
.tip-section { |
||||
|
padding: 30rpx; |
||||
|
margin: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.tip-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #ff6b6b; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
/* 认证按钮 */ |
||||
|
.auth-btn { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
text-align: center; |
||||
|
padding: 30rpx; |
||||
|
margin: 0 30rpx; |
||||
|
border-radius: 20rpx; |
||||
|
margin-top: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-btn-text { |
||||
|
display: block; |
||||
|
} |
||||
@ -1 +1,151 @@ |
|||||
/* pages/profile/index.wxss */ |
/* pages/profile/index.wxss */ |
||||
|
|
||||
|
/* 全局样式重置 */ |
||||
|
page { |
||||
|
background-color: #f5f5f5; |
||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
/* 容器样式 */ |
||||
|
.container { |
||||
|
padding: 20rpx; |
||||
|
width: 100%; |
||||
|
max-width: 100vw; |
||||
|
overflow-x: hidden; |
||||
|
box-sizing: border-box; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
/* 卡片样式 */ |
||||
|
.card { |
||||
|
background-color: white; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.card:hover { |
||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12); |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
/* 标题样式 */ |
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
position: relative; |
||||
|
padding-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.title::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
width: 80rpx; |
||||
|
height: 4rpx; |
||||
|
background: linear-gradient(90deg, #1677ff, #1890ff); |
||||
|
border-radius: 2rpx; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式 */ |
||||
|
.btn { |
||||
|
width: 100%; |
||||
|
padding: 20rpx; |
||||
|
border-radius: 10rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: bold; |
||||
|
transition: all 0.3s ease; |
||||
|
margin-bottom: 20rpx; |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.btn:hover { |
||||
|
opacity: 0.9; |
||||
|
transform: scale(1.02); |
||||
|
} |
||||
|
|
||||
|
.btn:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
/* 标签样式 */ |
||||
|
.tag { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 12rpx 24rpx; |
||||
|
border-radius: 24rpx; |
||||
|
margin: 10rpx; |
||||
|
font-size: 26rpx; |
||||
|
font-weight: bold; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.tag:hover { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.15); |
||||
|
} |
||||
|
|
||||
|
.tag:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
/* 链接项样式 */ |
||||
|
.link-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 30rpx 0; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.link-item:hover { |
||||
|
background-color: rgba(22, 119, 255, 0.05); |
||||
|
border-radius: 10rpx; |
||||
|
} |
||||
|
|
||||
|
/* 退出登录按钮样式 */ |
||||
|
.logout-btn { |
||||
|
background-color: #ff4d4f; |
||||
|
color: white; |
||||
|
padding: 12rpx 4rpx; |
||||
|
border-radius: 20rpx; |
||||
|
font-size: 20rpx; |
||||
|
width: 176rpx; |
||||
|
display: block; |
||||
|
box-sizing: border-box; |
||||
|
transition: all 0.3s ease; |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.logout-btn:hover { |
||||
|
background-color: #ff7875; |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
|
||||
|
.logout-btn:active { |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
/* 授权登录按钮样式 */ |
||||
|
.auth-btn { |
||||
|
margin: 20rpx 0; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.auth-btn:hover { |
||||
|
transform: scale(1.02); |
||||
|
} |
||||
|
|
||||
|
.auth-btn:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// 测试创建动态API
|
||||
|
const http = require('http'); |
||||
|
|
||||
|
// 测试数据
|
||||
|
const testData = { |
||||
|
content: '测试发布帖子', |
||||
|
images: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg'], |
||||
|
topic: '今天你吃蛋了么?' |
||||
|
}; |
||||
|
|
||||
|
// 发送POST请求到主服务器
|
||||
|
const options = { |
||||
|
hostname: 'localhost', |
||||
|
port: 3003, |
||||
|
path: '/api/eggbar/posts', |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
'Content-Length': Buffer.byteLength(JSON.stringify(testData)) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const req = http.request(options, (res) => { |
||||
|
console.log(`状态码: ${res.statusCode}`); |
||||
|
console.log(`响应头: ${JSON.stringify(res.headers)}`); |
||||
|
|
||||
|
let data = ''; |
||||
|
res.on('data', (chunk) => { |
||||
|
data += chunk; |
||||
|
}); |
||||
|
|
||||
|
res.on('end', () => { |
||||
|
console.log('响应体:', data); |
||||
|
console.log('测试完成'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
req.on('error', (e) => { |
||||
|
console.error(`请求错误: ${e.message}`); |
||||
|
}); |
||||
|
|
||||
|
// 发送请求体
|
||||
|
req.write(JSON.stringify(testData)); |
||||
|
req.end(); |
||||
|
|
||||
|
console.log('正在发送测试请求到 http://localhost:3003/api/eggbar/posts...'); |
||||
|
console.log('测试数据:', testData); |
||||
Loading…
Reference in new issue