update #18

Merged
hzj merged 24 commits from update into Xfy 1 month ago
  1. 13
      app.json
  2. 24
      custom-tab-bar/index.js
  3. 4
      custom-tab-bar/index.wxml
  4. 236
      pages/eggbar/create-post.js
  5. 9
      pages/eggbar/create-post.json
  6. 68
      pages/eggbar/create-post.wxml
  7. 281
      pages/eggbar/create-post.wxss
  8. 347
      pages/eggbar/eggbar.js
  9. 12
      pages/eggbar/eggbar.json
  10. 79
      pages/eggbar/eggbar.wxml
  11. 270
      pages/eggbar/eggbar.wxss
  12. 496
      pages/evaluate2/index.js
  13. 5
      pages/evaluate2/index.json
  14. 78
      pages/evaluate2/index.wxml
  15. 450
      pages/evaluate2/index.wxss
  16. 808
      pages/evaluate2/one.js
  17. 5
      pages/evaluate2/one.json
  18. 123
      pages/evaluate2/one.wxml
  19. 652
      pages/evaluate2/one.wxss
  20. 508
      pages/evaluate2/product-list.js
  21. 6
      pages/evaluate2/product-list.json
  22. 72
      pages/evaluate2/product-list.wxml
  23. 415
      pages/evaluate2/product-list.wxss
  24. 102
      pages/evaluate2/spec-detail.js
  25. 5
      pages/evaluate2/spec-detail.json
  26. 59
      pages/evaluate2/spec-detail.wxml
  27. 401
      pages/evaluate2/spec-detail.wxss
  28. 96
      pages/goods-detail/goods-detail.js
  29. 2
      pages/goods-detail/goods-detail.wxml
  30. 366
      pages/profile/authentication/index.js
  31. 6
      pages/profile/authentication/index.json
  32. 61
      pages/profile/authentication/index.wxml
  33. 165
      pages/profile/authentication/index.wxss
  34. 164
      pages/profile/index.js
  35. 9
      pages/profile/index.wxml
  36. 150
      pages/profile/index.wxss
  37. 2
      project.private.config.json
  38. 4
      server-example/.env
  39. 598
      server-example/server-mysql.js
  40. 47
      server-example/test-post-api.js
  41. 78
      utils/api.js

13
app.json

@ -5,11 +5,18 @@
"pages/evaluate1/index", "pages/evaluate1/index",
"pages/evaluate1/product-list", "pages/evaluate1/product-list",
"pages/evaluate1/spec-detail", "pages/evaluate1/spec-detail",
"pages/evaluate2/index",
"pages/evaluate2/one",
"pages/evaluate2/product-list",
"pages/evaluate2/spec-detail",
"pages/eggbar/eggbar",
"pages/eggbar/create-post",
"pages/settlement/index", "pages/settlement/index",
"pages/publish/index", "pages/publish/index",
"pages/buyer/index", "pages/buyer/index",
"pages/seller/index", "pages/seller/index",
"pages/profile/index", "pages/profile/index",
"pages/profile/authentication/index",
"pages/favorites/index", "pages/favorites/index",
"pages/notopen/index", "pages/notopen/index",
"pages/create-supply/index", "pages/create-supply/index",
@ -77,12 +84,12 @@
"text": "消息" "text": "消息"
}, },
{ {
"pagePath": "pages/evaluate1/index", "pagePath": "pages/evaluate2/index",
"text": "估" "text": "估"
}, },
{ {
"pagePath": "pages/settlement/index", "pagePath": "pages/eggbar/eggbar",
"text": "入驻" "text": "蛋吧"
}, },
{ {
"pagePath": "pages/favorites/index", "pagePath": "pages/favorites/index",

24
custom-tab-bar/index.js

@ -15,8 +15,8 @@ Component({
tabBarItems: [ tabBarItems: [
{ key: 'index', route: 'pages/index/index' }, { key: 'index', route: 'pages/index/index' },
{ key: 'chat', route: 'pages/chat/index', badgeKey: 'chat' }, { key: 'chat', route: 'pages/chat/index', badgeKey: 'chat' },
{ key: 'evaluate', route: 'pages/evaluate1/index' }, { key: 'evaluate', route: 'pages/evaluate2/one' },
{ key: 'settlement', route: 'pages/settlement/index' }, { key: 'settlement', route: 'pages/eggbar/eggbar' },
{ key: 'favorites', route: 'pages/favorites/index' }, { key: 'favorites', route: 'pages/favorites/index' },
{ key: 'profile', route: 'pages/profile/index' } { key: 'profile', route: 'pages/profile/index' }
] ]
@ -94,8 +94,8 @@ Component({
const tabBarPages = [ const tabBarPages = [
'pages/index/index', 'pages/index/index',
'pages/chat/index', 'pages/chat/index',
'pages/evaluate1/index', 'pages/evaluate2/one',
'pages/settlement/index', 'pages/eggbar/eggbar',
'pages/favorites/index', 'pages/favorites/index',
'pages/profile/index' 'pages/profile/index'
]; ];
@ -178,11 +178,11 @@ Component({
// 跳转到估价页面 // 跳转到估价页面
goToEvaluatePage() { goToEvaluatePage() {
console.log('点击了估价按钮,跳转到evaluate1页面') console.log('点击了估价按钮,跳转到evaluate2/one页面')
wx.switchTab({ wx.switchTab({
url: '/pages/evaluate1/index', url: '/pages/evaluate2/one',
success: (res) => { success: (res) => {
console.log('switchTab到evaluate1成功:', res) console.log('switchTab到evaluate2/one成功:', res)
// 更新选中状态 // 更新选中状态
this.setData({ selected: 'evaluate' }) this.setData({ selected: 'evaluate' })
// 更新全局数据 // 更新全局数据
@ -192,13 +192,13 @@ Component({
} }
}, },
fail: (err) => { fail: (err) => {
console.error('switchTab到evaluate1失败:', err) console.error('switchTab到evaluate2/one失败:', err)
// 失败时尝试使用reLaunch // 失败时尝试使用reLaunch
console.log('尝试使用reLaunch跳转到evaluate1...') console.log('尝试使用reLaunch跳转到evaluate2/one...')
wx.reLaunch({ wx.reLaunch({
url: '/pages/evaluate1/index', url: '/pages/evaluate2/one',
success: (res) => { success: (res) => {
console.log('reLaunch到evaluate1成功:', res) console.log('reLaunch到evaluate2/one成功:', res)
// 更新选中状态 // 更新选中状态
this.setData({ selected: 'evaluate' }) this.setData({ selected: 'evaluate' })
// 更新全局数据 // 更新全局数据
@ -208,7 +208,7 @@ Component({
} }
}, },
fail: (err) => { fail: (err) => {
console.error('reLaunch到evaluate1也失败:', err) console.error('reLaunch到evaluate2/one也失败:', err)
} }
}) })
} }

4
custom-tab-bar/index.wxml

@ -33,13 +33,13 @@
<!-- 右侧按钮组 --> <!-- 右侧按钮组 -->
<view class="tab-bar-right"> <view class="tab-bar-right">
<view class="tab-bar-item {{selected === 'settlement' ? 'active' : ''}}" <view class="tab-bar-item {{selected === 'settlement' ? 'active' : ''}}"
data-path="pages/settlement/index" data-path="pages/eggbar/eggbar"
data-key="settlement" data-key="settlement"
bindtap="switchTab"> bindtap="switchTab">
<view class="tab-bar-icon"> <view class="tab-bar-icon">
<view class="tab-bar-badge" wx:if="{{badges['settlement']}}">{{badges['settlement']}}</view> <view class="tab-bar-badge" wx:if="{{badges['settlement']}}">{{badges['settlement']}}</view>
</view> </view>
<view class="tab-bar-text">入驻</view> <view class="tab-bar-text">蛋吧</view>
</view> </view>

236
pages/eggbar/create-post.js

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

9
pages/eggbar/create-post.json

@ -0,0 +1,9 @@
{
"usingComponents": {},
"enablePullDownRefresh": false,
"backgroundTextStyle": "dark",
"backgroundColor": "#f8f8f8",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "发布动态",
"navigationBarTextStyle": "black"
}

68
pages/eggbar/create-post.wxml

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

281
pages/eggbar/create-post.wxss

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

347
pages/eggbar/eggbar.js

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

12
pages/eggbar/eggbar.json

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

79
pages/eggbar/eggbar.wxml

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

270
pages/eggbar/eggbar.wxss

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

496
pages/evaluate2/index.js

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

5
pages/evaluate2/index.json

@ -0,0 +1,5 @@
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}

78
pages/evaluate2/index.wxml

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

450
pages/evaluate2/index.wxss

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

808
pages/evaluate2/one.js

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

5
pages/evaluate2/one.json

@ -0,0 +1,5 @@
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}

123
pages/evaluate2/one.wxml

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

652
pages/evaluate2/one.wxss

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

508
pages/evaluate2/product-list.js

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

6
pages/evaluate2/product-list.json

@ -0,0 +1,6 @@
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark",
"disableSwipeBack": true
}

72
pages/evaluate2/product-list.wxml

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

415
pages/evaluate2/product-list.wxss

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

102
pages/evaluate2/spec-detail.js

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

5
pages/evaluate2/spec-detail.json

@ -0,0 +1,5 @@
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}

59
pages/evaluate2/spec-detail.wxml

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

401
pages/evaluate2/spec-detail.wxss

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

96
pages/goods-detail/goods-detail.js

@ -1815,11 +1815,12 @@ Page({
const productId = this.data.goodsDetail.productId || this.data.goodsDetail.id; const productId = this.data.goodsDetail.productId || this.data.goodsDetail.id;
// 准备评论数据 // 准备评论数据
// 只发送服务器需要的参数:productId、phoneNumber和comments // 只发送服务器需要的参数:productId、phoneNumber、comments和review
const commentData = { const commentData = {
productId: String(productId), productId: String(productId),
phoneNumber: phoneNumber ? String(phoneNumber) : null, phoneNumber: phoneNumber ? String(phoneNumber) : null,
comments: content comments: content,
review: 0 // 新提交的评论默认为待审核状态
}; };
// 调试日志 // 调试日志
@ -1851,7 +1852,8 @@ Page({
liked: false, liked: false,
hated: false, hated: false,
replies: [], replies: [],
phoneNumber: phoneNumber // 添加用户标识信息,用于判断是否可以删除 phoneNumber: phoneNumber, // 添加用户标识信息,用于判断是否可以删除
review: 0 // 新提交的评论默认为待审核状态
}; };
// 更新评论列表 - add new comment to the end // 更新评论列表 - add new comment to the end
@ -1885,31 +1887,6 @@ Page({
}); });
}, },
// 20 default comments
getDefaultComments() {
return [
"鸡蛋品相贼好无破损,规格统一超适合批发",
"个头匀溜大小一致,装箱发货一点不费劲",
"包装严实防震,整车运输下来个个完好",
"蛋液浓稠清亮,品相达标完全符合供货要求",
"性价比真绝了,新鲜度在线囤货超划算",
"农家散养蛋品相佳,蛋黄紧实供货超稳定",
"物流嗖嗖快,到货鸡蛋无磕碰超省心",
"蛋壳干净无污渍,分拣打包效率直接拉满",
"个个饱满无瘪壳,市场铺货回头客贼多",
"分量超足规格齐,商超供货完全没毛病",
"无抗生素达标蛋,走商超渠道妥妥放心",
"蛋壳硬度够,装卸搬运全程零损耗",
"防震包装太贴心,长途运输损耗率超低",
"保鲜期够长,放一周品相依旧很能打",
"蛋体完整无瑕疵,分拣挑拣省超多功夫",
"品质稳定没色差,长期合作完全没问题",
"货源稳定供货及时,补货节奏卡得刚刚好",
"发货快包装硬,对接商超渠道超靠谱",
"蛋黄蛋清分层好,加工拿货性价比拉满",
"品质远超预期,后续订单必须锁定这家"
];
},
// Seeded random number generator for consistent results // Seeded random number generator for consistent results
seededRandom(seed) { seededRandom(seed) {
@ -1952,7 +1929,8 @@ Page({
hated: false, hated: false,
replies: [], replies: [],
phoneNumber: '', phoneNumber: '',
isDefault: true isDefault: true,
review: 1 // 默认评论默认为审核通过状态
})); }));
}, },
@ -2003,9 +1981,18 @@ Page({
}); });
commentsData = uniqueComments; commentsData = uniqueComments;
// Always add default comments at the beginning // 应用审核逻辑:审核通过的评论所有人可见,未审核通过的评论仅自己可见
const defaultComments = this.getConsistentRandomComments(productId, 2); const currentUserPhone = this.data.currentUserPhone;
commentsData = [...defaultComments, ...commentsData]; const filteredComments = commentsData.filter(comment => {
const reviewStatus = comment.review || 0; // 默认值为0(待审核)
// 审核通过的评论(review=1)所有人可见
// 未审核通过的评论(review=0或2)仅评论作者可见
return reviewStatus === 1 || comment.phoneNumber === currentUserPhone;
});
console.log('应用审核逻辑后剩余评论数量:', filteredComments.length);
// Use only filtered comments without default comments
commentsData = filteredComments;
// 检查返回的评论是否都属于当前用户 // 检查返回的评论是否都属于当前用户
const allCommentsBelongToCurrentUser = commentsData.every(comment => const allCommentsBelongToCurrentUser = commentsData.every(comment =>
@ -2026,6 +2013,8 @@ Page({
id: comment.id || `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, id: comment.id || `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
// 确保phoneNumber字段存在 // 确保phoneNumber字段存在
phoneNumber: comment.phoneNumber || comment.userPhone || '', phoneNumber: comment.phoneNumber || comment.userPhone || '',
// 确保review字段存在
review: comment.review || 0,
// 格式化时间 // 格式化时间
time: timeUtils.formatRelativeTime(comment.time) time: timeUtils.formatRelativeTime(comment.time)
})); }));
@ -2038,11 +2027,10 @@ Page({
.catch(err => { .catch(err => {
console.error('获取评论失败:', err); console.error('获取评论失败:', err);
console.error('错误详情:', JSON.stringify(err, null, 2)); console.error('错误详情:', JSON.stringify(err, null, 2));
// 加载失败时使用默认评论 // 加载失败时使用空数组
console.log('使用默认评论'); console.log('使用空评论数组');
const defaultComments = this.getConsistentRandomComments(productId, 2);
this.setData({ this.setData({
comments: defaultComments comments: []
}); });
}); });
}, },
@ -2951,6 +2939,42 @@ Page({
onCompareClick: function () { onCompareClick: function () {
console.log('用户点击了对比价格按钮,准备显示弹窗'); console.log('用户点击了对比价格按钮,准备显示弹窗');
// 检查用户登录状态
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 idcard1 = userInfo.idcard1;
const idcard2 = userInfo.idcard2;
console.log('检查身份证信息:', { idcard1, idcard2 });
if (!idcard1 || !idcard2) {
console.log('身份证信息不完整,跳转到认证页面');
wx.showToast({
title: '信息不完整,请先完成身份认证',
icon: 'none',
duration: 2000
});
// 跳转到认证页面
setTimeout(() => {
wx.navigateTo({
url: '/pages/profile/authentication/index'
});
}, 1000);
return;
}
// 直接获取当前页面滚动位置 // 直接获取当前页面滚动位置
wx.createSelectorQuery().selectViewport().scrollOffset(function(res) { wx.createSelectorQuery().selectViewport().scrollOffset(function(res) {
console.log('记录当前滚动位置:', res.scrollTop); console.log('记录当前滚动位置:', res.scrollTop);

2
pages/goods-detail/goods-detail.wxml

@ -72,7 +72,7 @@
<!-- 商品基本信息 --> <!-- 商品基本信息 -->
<view class="goods-info"> <view class="goods-info">
<view style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10rpx;"> <view style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0rpx;">
<view style="display: flex; align-items: center; flex: 1;"> <view style="display: flex; align-items: center; flex: 1;">
<view wx:if="{{goodsDetail.status === 'sold_out'}}" style="display: inline-block; margin-right: 10rpx; font-size: 18rpx; color: #fff; background: linear-gradient(135deg, #8c8c8c 0%, #a6a6a6 100%); padding: 4rpx 10rpx; border-radius: 15rpx; vertical-align: middle; backdrop-filter: blur(10rpx); border: 1rpx solid rgba(255, 255, 255, 0.3); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15), inset 0 1rpx 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2); font-weight: bold; margin-top: -20rpx;">售空</view> <view wx:if="{{goodsDetail.status === 'sold_out'}}" style="display: inline-block; margin-right: 10rpx; font-size: 18rpx; color: #fff; background: linear-gradient(135deg, #8c8c8c 0%, #a6a6a6 100%); padding: 4rpx 10rpx; border-radius: 15rpx; vertical-align: middle; backdrop-filter: blur(10rpx); border: 1rpx solid rgba(255, 255, 255, 0.3); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15), inset 0 1rpx 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2); font-weight: bold; margin-top: -20rpx;">售空</view>
<view wx:elif="{{goodsDetail.supplyStatus}}" style="display: inline-block; margin-right: 10rpx; font-size: 18rpx; color: #fff; background: rgba(218, 165, 32, 0.8); padding: 4rpx 10rpx; border-radius: 15rpx; vertical-align: middle; backdrop-filter: blur(10rpx); border: 1rpx solid rgba(255, 255, 255, 0.3); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15), inset 0 1rpx 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2); font-weight: bold; margin-top: -20rpx;">{{goodsDetail.supplyStatus}}</view> <view wx:elif="{{goodsDetail.supplyStatus}}" style="display: inline-block; margin-right: 10rpx; font-size: 18rpx; color: #fff; background: rgba(218, 165, 32, 0.8); padding: 4rpx 10rpx; border-radius: 15rpx; vertical-align: middle; backdrop-filter: blur(10rpx); border: 1rpx solid rgba(255, 255, 255, 0.3); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15), inset 0 1rpx 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2); font-weight: bold; margin-top: -20rpx;">{{goodsDetail.supplyStatus}}</view>

366
pages/profile/authentication/index.js

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

6
pages/profile/authentication/index.json

@ -0,0 +1,6 @@
{
"usingComponents": {},
"navigationBarTitleText": "个人认证",
"navigationBarBackgroundColor": "#07c160",
"navigationBarTextStyle": "white"
}

61
pages/profile/authentication/index.wxml

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

165
pages/profile/authentication/index.wxss

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

164
pages/profile/index.js

@ -25,7 +25,14 @@ Page({
needPhoneAuth: false, // 是否需要重新授权手机号 needPhoneAuth: false, // 是否需要重新授权手机号
locationInfo: '', // 位置信息 locationInfo: '', // 位置信息
hasLocationAuth: false, // 是否已经授权位置 hasLocationAuth: false, // 是否已经授权位置
isInPersonnel: false // 用户手机号是否在personnel表中 isInPersonnel: false, // 用户手机号是否在personnel表中
currentAvatarIndex: 0, // 当前显示的头像索引,默认显示第一张
rawAvatarUrl: null, // 存储原始的头像URL数据(可能是数组)
// 固定的头像URL数组
avatarUrls: [
"https://my-supplier-photos.oss-cn-chengdu.aliyuncs.com/products/%E6%B5%B7%E8%93%9D%E7%81%B0/image/7a2a8a17a83ba4d3d4270828531e2041.jpeg",
"https://my-supplier-photos.oss-cn-chengdu.aliyuncs.com/products/%E4%BC%8A%E8%8E%8E%E7%B2%89/image/1b2a0ba28eaa17c16c3674985ccee05c.jpeg"
]
}, },
onLoad() { onLoad() {
@ -55,6 +62,63 @@ Page({
return phoneStr.substring(0, 3) + '****' + phoneStr.substring(7) return phoneStr.substring(0, 3) + '****' + phoneStr.substring(7)
}, },
// 处理头像URL,确保正确解析数组形式的OSS URL
processAvatarUrl(avatarUrl) {
if (!avatarUrl) {
return 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';
}
// 如果是字符串,尝试解析为JSON数组
if (typeof avatarUrl === 'string') {
// 去除字符串两端的空格和引号
let cleanedUrl = avatarUrl.trim();
// 去除可能存在的前后引号
if ((cleanedUrl.startsWith('"') && cleanedUrl.endsWith('"')) ||
(cleanedUrl.startsWith('`') && cleanedUrl.endsWith('`'))) {
cleanedUrl = cleanedUrl.substring(1, cleanedUrl.length - 1);
}
try {
const parsedUrl = JSON.parse(cleanedUrl);
// 如果解析成功且是数组且不为空,使用第一个元素
if (Array.isArray(parsedUrl) && parsedUrl.length > 0) {
// 清理第一个元素中的空格和引号
let firstUrl = parsedUrl[0];
if (typeof firstUrl === 'string') {
firstUrl = firstUrl.trim();
if ((firstUrl.startsWith('"') && firstUrl.endsWith('"')) ||
(firstUrl.startsWith('`') && firstUrl.endsWith('`'))) {
firstUrl = firstUrl.substring(1, firstUrl.length - 1);
}
}
return firstUrl;
}
// 如果解析成功但不是数组,直接返回清理后的URL
return cleanedUrl;
} catch (e) {
// 解析失败,返回清理后的URL
return cleanedUrl;
}
}
// 如果是数组且不为空,使用第一个元素
if (Array.isArray(avatarUrl) && avatarUrl.length > 0) {
let firstUrl = avatarUrl[0];
if (typeof firstUrl === 'string') {
// 清理第一个元素中的空格和引号
firstUrl = firstUrl.trim();
if ((firstUrl.startsWith('"') && firstUrl.endsWith('"')) ||
(firstUrl.startsWith('`') && firstUrl.endsWith('`'))) {
firstUrl = firstUrl.substring(1, firstUrl.length - 1);
}
}
return firstUrl;
}
// 其他情况返回默认头像
return 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';
},
// 加载用户信息 // 加载用户信息
loadUserInfo() { loadUserInfo() {
console.log('开始加载用户信息') console.log('开始加载用户信息')
@ -64,15 +128,31 @@ Page({
const localUserInfo = wx.getStorageSync('userInfo') || {} const localUserInfo = wx.getStorageSync('userInfo') || {}
if (app.globalData.userInfo) { if (app.globalData.userInfo) {
const userInfo = { ...app.globalData.userInfo } const userInfo = { ...app.globalData.userInfo }
// 使用固定的头像URL数组
const avatarUrls = this.data.avatarUrls;
// 使用第一张头像作为默认显示
userInfo.avatarUrl = avatarUrls[0];
userInfo.hiddenPhoneNumber = this.hidePhoneNumber(userInfo.phoneNumber) userInfo.hiddenPhoneNumber = this.hidePhoneNumber(userInfo.phoneNumber)
this.setData({ userInfo }) this.setData({
userInfo,
rawAvatarUrl: avatarUrls
})
// 更新全局状态中的头像URL,确保下次加载时使用固定的头像
app.globalData.userInfo = userInfo;
wx.setStorageSync('userInfo', userInfo);
} else { } else {
const userInfo = { ...localUserInfo } const userInfo = { ...localUserInfo }
// 使用固定的头像URL数组
const avatarUrls = this.data.avatarUrls;
// 使用第一张头像作为默认显示
userInfo.avatarUrl = avatarUrls[0];
userInfo.hiddenPhoneNumber = this.hidePhoneNumber(userInfo.phoneNumber) userInfo.hiddenPhoneNumber = this.hidePhoneNumber(userInfo.phoneNumber)
app.globalData.userInfo = userInfo app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo);
this.setData({ this.setData({
userInfo, userInfo,
needPhoneAuth: !userInfo.phoneNumber needPhoneAuth: !userInfo.phoneNumber,
rawAvatarUrl: avatarUrls
}) })
} }
@ -199,12 +279,20 @@ Page({
...serverUserInfo ...serverUserInfo
} }
// 使用固定的头像URL数组
const avatarUrls = this.data.avatarUrls;
// 使用第一张头像作为默认显示
updatedUserInfo.avatarUrl = avatarUrls[0];
// 添加隐藏的电话号码 // 添加隐藏的电话号码
updatedUserInfo.hiddenPhoneNumber = this.hidePhoneNumber(updatedUserInfo.phoneNumber) updatedUserInfo.hiddenPhoneNumber = this.hidePhoneNumber(updatedUserInfo.phoneNumber)
app.globalData.userInfo = updatedUserInfo app.globalData.userInfo = updatedUserInfo
wx.setStorageSync('userInfo', updatedUserInfo) wx.setStorageSync('userInfo', updatedUserInfo)
this.setData({ userInfo: updatedUserInfo }) this.setData({
userInfo: updatedUserInfo,
rawAvatarUrl: avatarUrls
})
// 同步更新用户身份信息(当前身份由数据库决定) // 同步更新用户身份信息(当前身份由数据库决定)
if (serverUserInfo.type) { if (serverUserInfo.type) {
@ -232,12 +320,20 @@ Page({
...serverUserInfo ...serverUserInfo
} }
// 使用固定的头像URL数组
const avatarUrls = this.data.avatarUrls;
// 使用第一张头像作为默认显示
updatedUserInfo.avatarUrl = avatarUrls[0];
// 添加隐藏的电话号码 // 添加隐藏的电话号码
updatedUserInfo.hiddenPhoneNumber = this.hidePhoneNumber(updatedUserInfo.phoneNumber) updatedUserInfo.hiddenPhoneNumber = this.hidePhoneNumber(updatedUserInfo.phoneNumber)
app.globalData.userInfo = updatedUserInfo app.globalData.userInfo = updatedUserInfo
wx.setStorageSync('userInfo', updatedUserInfo) wx.setStorageSync('userInfo', updatedUserInfo)
this.setData({ userInfo: updatedUserInfo }) this.setData({
userInfo: updatedUserInfo,
rawAvatarUrl: avatarUrls
})
// 同步更新用户身份信息(当前身份由数据库决定) // 同步更新用户身份信息(当前身份由数据库决定)
if (serverUserInfo.type) { if (serverUserInfo.type) {
@ -1066,4 +1162,62 @@ Page({
}); });
}, },
// 跳转到个人认证页面
navigateToAuthentication() {
wx.navigateTo({
url: '/pages/profile/authentication/index'
});
},
// 点击头像切换图片
onAvatarClick() {
// 使用固定的头像URL数组
const avatarUrls = this.data.avatarUrls;
// 检查是否有至少两张图片可以切换
if (avatarUrls.length < 2) {
wx.showToast({
title: '没有足够的头像可以切换',
icon: 'none',
duration: 2000
});
return;
}
// 切换到下一张头像
let nextIndex = this.data.currentAvatarIndex + 1;
if (nextIndex >= avatarUrls.length) {
nextIndex = 0; // 如果已经是最后一张,回到第一张
}
// 显示切换头像的提示
wx.showModal({
title: '切换头像',
content: `确定要切换到第${nextIndex + 1}张头像吗?`,
success: (res) => {
if (res.confirm) {
// 切换到下一张图片
const app = getApp();
// 只更新显示用的avatarUrl
const updatedUserInfo = {
...this.data.userInfo,
avatarUrl: avatarUrls[nextIndex] // 直接使用下一张图片用于显示
};
// 更新页面显示
this.setData({
userInfo: updatedUserInfo,
currentAvatarIndex: nextIndex
});
wx.showToast({
title: '头像切换成功',
icon: 'success',
duration: 2000
});
}
}
});
},
}) })

9
pages/profile/index.wxml

@ -3,7 +3,8 @@
<view style="display: flex; align-items: center;"> <view style="display: flex; align-items: center;">
<image <image
src="{{userInfo.avatarUrl || '/images/你有好蛋.png'}}" src="{{userInfo.avatarUrl || '/images/你有好蛋.png'}}"
style="width: 100rpx; height: 100rpx; border-radius: 50%; margin-right: 20rpx;" style="width: 100rpx; height: 100rpx; border-radius: 50%; margin-right: 20rpx; cursor: pointer;"
bindtap="onAvatarClick"
></image> ></image>
<view> <view>
<view style="font-size: 32rpx; font-weight: bold;">{{userInfo.hiddenPhoneNumber || userInfo.phoneNumber || '未登录'}}</view> <view style="font-size: 32rpx; font-weight: bold;">{{userInfo.hiddenPhoneNumber || userInfo.phoneNumber || '未登录'}}</view>
@ -54,7 +55,11 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 个人认证 -->
<view class=".card" bindtap="navigateToAuthentication">
<text style="font-size: 28rpx; color: #333;">个人认证</text>
<text style="font-size: 28rpx; color: #999;">></text>
</view>
<!-- 位置授权 --> <!-- 位置授权 -->
<view class="card"> <view class="card">
<view class="title">位置信息</view> <view class="title">位置信息</view>

150
pages/profile/index.wxss

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

2
project.private.config.json

@ -1,6 +1,6 @@
{ {
"libVersion": "3.10.3", "libVersion": "3.10.3",
"projectname": "xcx22", "projectname": "wxxcx1",
"setting": { "setting": {
"urlCheck": false, "urlCheck": false,
"coverView": true, "coverView": true,

4
server-example/.env

@ -4,10 +4,10 @@ WECHAT_APPSECRET=78fd81bce5a2968a8e7c607ae68c4c0b
WECHAT_TOKEN=your-random-token WECHAT_TOKEN=your-random-token
# MySQL数据库配置(请根据您的实际环境修改) # MySQL数据库配置(请根据您的实际环境修改)
# 如果是首次使用,可能需要先在MySQL中创建wechat_app数据库 # 连接到eggbar数据库
DB_HOST=1.95.162.61 DB_HOST=1.95.162.61
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=wechat_app DB_DATABASE=eggbar
# 请使用您实际的MySQL用户名 # 请使用您实际的MySQL用户名
DB_USER=root DB_USER=root
# 请使用您实际的MySQL密码 # 请使用您实际的MySQL密码

598
server-example/server-mysql.js

@ -125,6 +125,214 @@ app.post('/api/test/post', (req, res) => {
}); });
}); });
// Eggbar 帖子创建接口
app.post('/api/eggbar/posts', async (req, res) => {
try {
console.log('===== 收到帖子创建请求 =====');
console.log('1. 收到请求体:', JSON.stringify(req.body, null, 2));
const { user_id, content, images, topic, phone } = req.body;
// 数据验证
if (!user_id) {
return res.status(400).json({
success: false,
code: 400,
message: '缺少用户ID'
});
}
// 处理图片数据
let imagesData = null;
if (images && Array.isArray(images) && images.length > 0) {
imagesData = images;
}
// 创建帖子记录
const newPost = await EggbarPost.create({
user_id: user_id,
phone: phone || null,
content: content || null,
images: imagesData,
topic: topic || null
});
console.log('2. 帖子创建成功,ID:', newPost.id);
res.json({
success: true,
code: 200,
message: '帖子发布成功',
data: {
postId: newPost.id
}
});
} catch (error) {
console.error('创建帖子失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '创建帖子失败: ' + error.message
});
}
});
// Eggbar 帖子列表接口
app.get('/api/eggbar/posts', async (req, res) => {
try {
console.log('===== 收到帖子列表请求 =====');
// 获取分页参数
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.pageSize) || 10;
const offset = (page - 1) * pageSize;
console.log('查询参数:', {
page,
pageSize,
offset
});
// 使用新的 Sequelize 实例查询 eggbar 数据库
const tempSequelize = new Sequelize('eggbar', dbConfig.user, dbConfig.password, {
host: dbConfig.host,
port: dbConfig.port,
dialect: 'mysql',
logging: console.log,
timezone: '+08:00'
});
// 从数据库获取帖子列表
const posts = await tempSequelize.query(
'SELECT * FROM eggbar_posts ORDER BY created_at DESC',
{
type: tempSequelize.QueryTypes.SELECT
}
);
console.log('数据库查询结果数量:', posts.length);
console.log('数据库查询结果:', posts);
// 关闭临时连接
await tempSequelize.close();
console.log('原始查询结果:', posts);
console.log('查询结果类型:', typeof posts);
console.log('是否为数组:', Array.isArray(posts));
// 确保查询结果为数组
const postsArray = Array.isArray(posts) ? posts : (posts ? [posts] : []);
// 手动处理分页
const totalCount = postsArray.length;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedPosts = postsArray.slice(startIndex, endIndex);
console.log('查询结果:', {
postCount: paginatedPosts.length,
totalCount: totalCount
});
// 格式化响应数据
let formattedPosts = paginatedPosts.map(post => {
// 解析images字段,确保它是一个数组
let images = [];
if (post.images) {
if (typeof post.images === 'string') {
try {
images = JSON.parse(post.images);
if (!Array.isArray(images)) {
images = [];
}
} catch (e) {
images = [];
}
} else if (Array.isArray(post.images)) {
images = post.images;
} else {
images = [];
}
}
return {
id: post.id,
user_id: post.user_id,
phone: post.phone,
content: post.content,
images: images,
topic: post.topic,
likes: post.likes || 0,
comments: post.comments || 0,
shares: post.shares || 0,
status: post.status,
created_at: post.created_at,
updated_at: post.updated_at
};
});
// 检查用户是否已点赞
const phone = req.query.phone || req.headers['x-phone'];
if (phone) {
try {
// 批量检查点赞状态
const postsWithLikedStatus = await Promise.all(formattedPosts.map(async post => {
const existingLike = await EggbarLike.findOne({
where: {
post_id: post.id,
phone: phone
}
});
return {
...post,
liked: !!existingLike
};
}));
formattedPosts = postsWithLikedStatus;
} catch (error) {
console.warn('批量检查点赞状态时出错:', error);
// 如果出错,给所有帖子添加默认未点赞状态
formattedPosts = formattedPosts.map(post => ({
...post,
liked: false
}));
}
} else {
// 没有电话号码,给所有帖子添加默认未点赞状态
formattedPosts = formattedPosts.map(post => ({
...post,
liked: false
}));
}
console.log('5. 帖子列表格式化完成,带点赞状态');
console.log('格式化后的数据数量:', formattedPosts.length);
res.json({
success: true,
code: 200,
message: '获取帖子列表成功',
data: {
posts: formattedPosts,
pagination: {
page,
pageSize,
total: totalCount,
totalPages: Math.ceil(totalCount / pageSize)
}
}
});
} catch (error) {
console.error('获取帖子列表失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '获取帖子列表失败: ' + error.message
});
}
});
// 获取封面图片列表接口 // 获取封面图片列表接口
app.get('/api/cover', async (req, res) => { app.get('/api/cover', async (req, res) => {
try { try {
@ -150,6 +358,48 @@ app.get('/api/cover', async (req, res) => {
} }
}); });
// 创建动态接口(已废弃 - 使用上面的实际实现)
app.post('/api/eggbar/posts/deprecated', async (req, res) => {
try {
console.log('===== 创建动态接口被调用 =====');
console.log('收到的请求数据:', req.body);
const { content, images, topic } = req.body;
// 验证参数
if (!content && !topic) {
return res.json({
success: false,
message: '文本内容和话题至少需要填写一项'
});
}
// 模拟创建动态
console.log('创建动态成功:', {
content,
images,
topic
});
res.json({
success: true,
message: '发布成功',
data: {
content,
images,
topic
}
});
} catch (error) {
console.error('创建动态失败:', error);
res.json({
success: false,
message: '发布失败,请重试',
error: error.message
});
}
});
// 创建临时文件夹用于存储上传的文件 // 创建临时文件夹用于存储上传的文件
const uploadTempDir = path.join(__dirname, 'temp-uploads'); const uploadTempDir = path.join(__dirname, 'temp-uploads');
if (!fs.existsSync(uploadTempDir)) { if (!fs.existsSync(uploadTempDir)) {
@ -219,6 +469,143 @@ const upload = multer({
fileFilter: fileFilter fileFilter: fileFilter
}); });
// Eggbar 图片上传接口
app.post('/api/eggbar/upload', upload.single('image'), async (req, res) => {
try {
console.log('===== 收到图片上传请求 =====');
console.log('1. 文件信息:', req.file);
console.log('2. 表单数据:', req.body);
if (!req.file) {
return res.status(400).json({
success: false,
message: '没有收到文件'
});
}
const tempFilePath = req.file.path;
try {
// 使用OSS上传图片
const imageUrl = await OssUploader.uploadFile(tempFilePath, 'eggbar', 'image');
console.log('3. 图片上传成功,URL:', imageUrl);
// 删除临时文件
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
console.log('4. 临时文件已删除:', tempFilePath);
}
res.json({
success: true,
message: '图片上传成功',
imageUrl: imageUrl
});
} finally {
// 确保临时文件被删除,即使OSS上传失败
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
console.log('临时文件已清理:', tempFilePath);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
}
} catch (error) {
console.error('上传图片失败:', error);
res.status(500).json({
success: false,
message: '上传图片失败: ' + error.message
});
}
});
// Eggbar 点赞接口
app.post('/api/eggbar/posts/:postId/like', async (req, res) => {
try {
const postId = parseInt(req.params.postId);
const { phone } = req.body;
console.log('===== 收到点赞请求 =====');
console.log('1. 动态ID:', postId);
console.log('2. 电话号码:', phone);
// 数据验证
if (!postId || !phone) {
return res.status(400).json({
success: false,
code: 400,
message: '缺少必要参数'
});
}
// 检查动态是否存在
const post = await EggbarPost.findByPk(postId);
if (!post) {
return res.status(404).json({
success: false,
code: 404,
message: '动态不存在'
});
}
// 检查用户是否已经点过赞
const existingLike = await EggbarLike.findOne({
where: {
post_id: postId,
phone: phone
}
});
let isLiked = false;
let newLikeCount = post.likes || 0;
if (existingLike) {
// 已经点过赞,取消点赞
await existingLike.destroy();
newLikeCount = Math.max(0, newLikeCount - 1);
isLiked = false;
console.log('3. 取消点赞成功');
} else {
// 没点过赞,添加点赞
await EggbarLike.create({
post_id: postId,
phone: phone
});
newLikeCount = newLikeCount + 1;
isLiked = true;
console.log('3. 点赞成功');
}
// 更新动态的点赞数
await EggbarPost.update(
{ likes: newLikeCount },
{ where: { id: postId } }
);
console.log('4. 点赞数更新成功,新点赞数:', newLikeCount);
res.json({
success: true,
code: 200,
message: isLiked ? '点赞成功' : '取消点赞成功',
data: {
isLiked: isLiked,
likes: newLikeCount
}
});
} catch (error) {
console.error('点赞操作失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '点赞操作失败: ' + error.message
});
}
});
// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后) // 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后)
app.use((req, res, next) => { app.use((req, res, next) => {
// 使用统一的时间处理函数获取当前时间 // 使用统一的时间处理函数获取当前时间
@ -399,6 +786,29 @@ const userLoginSequelize = new Sequelize(
} }
); );
// 3. eggbar数据源连接
const eggbarSequelize = new Sequelize(
'eggbar',
dbConfig.user,
dbConfig.password,
{
host: dbConfig.host,
port: dbConfig.port,
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
},
logging: console.log,
define: {
timestamps: false
},
timezone: '+08:00' // 设置时区为UTC+8
}
);
// 为保持兼容性,保留默认sequelize引用(指向wechat_app) // 为保持兼容性,保留默认sequelize引用(指向wechat_app)
const sequelize = wechatAppSequelize; const sequelize = wechatAppSequelize;
@ -556,6 +966,11 @@ const comments = sequelize.define('Comment', {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 0, defaultValue: 0,
comment: '点踩数' comment: '点踩数'
},
review: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '审核字段'
} }
}, { }, {
tableName: 'comments', tableName: 'comments',
@ -786,6 +1201,14 @@ User.init({
notice: { notice: {
type: DataTypes.STRING(255) // 通知提醒 type: DataTypes.STRING(255) // 通知提醒
}, },
idcard1: {
type: DataTypes.TEXT, // 身份证正面
comment: '身份证正面'
},
idcard2: {
type: DataTypes.TEXT, // 身份证反面
comment: '身份证反面'
},
// 时间字段 // 时间字段
created_at: { created_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
@ -1075,6 +1498,11 @@ Comment.init({
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 0, defaultValue: 0,
comment: '点踩数' comment: '点踩数'
},
review: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '审核字段'
} }
}, { }, {
sequelize, sequelize,
@ -1296,6 +1724,120 @@ Cover.init({
timestamps: false timestamps: false
}); });
// Eggbar 帖子模型 - 用于存储用户发布的动态
class EggbarPost extends Model { }
EggbarPost.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '帖子ID'
},
user_id: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '用户ID'
},
phone: {
type: DataTypes.STRING(20),
allowNull: true,
comment: '用户电话号码'
},
content: {
type: DataTypes.TEXT,
allowNull: true,
comment: '动态内容'
},
images: {
type: DataTypes.TEXT,
allowNull: true,
comment: '图片URL数组',
get() {
const value = this.getDataValue('images');
return value ? JSON.parse(value) : [];
},
set(value) {
this.setDataValue('images', JSON.stringify(value));
}
},
topic: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '话题'
},
likes: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '点赞数'
},
comments: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '评论数'
},
shares: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '分享数'
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: true,
defaultValue: 'active',
comment: '状态'
},
created_at: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW,
onUpdate: Sequelize.NOW,
comment: '更新时间'
}
}, {
sequelize: eggbarSequelize,
modelName: 'EggbarPost',
tableName: 'eggbar_posts',
timestamps: false
});
// Eggbar 点赞模型 - 用于存储用户点赞记录
class EggbarLike extends Model { }
EggbarLike.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '点赞记录ID'
},
post_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '动态ID'
},
phone: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '电话号码'
},
created_at: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW,
comment: '创建时间'
}
}, {
sequelize: eggbarSequelize,
modelName: 'EggbarLike',
tableName: 'eggbar_likes',
timestamps: false
});
// 定义模型之间的关联关系 // 定义模型之间的关联关系
// 用户和商品的一对多关系 (卖家发布商品) // 用户和商品的一对多关系 (卖家发布商品)
@ -2561,7 +3103,11 @@ app.post('/api/products/upload', upload.array('images', 10), async (req, res) =>
const uploadedFileUrls = new Set(); const uploadedFileUrls = new Set();
// 准备文件路径数组 // 准备文件路径数组
const filePaths = uploadedFiles.map(file => file.path); const filePaths = uploadedFiles.map(file => {
// 添加到临时文件清理列表
tempFilesToClean.push(file.path);
return file.path;
});
// 使用商品名称作为文件夹名,确保每个商品的图片独立存储 // 使用商品名称作为文件夹名,确保每个商品的图片独立存储
// 移除商品名称中的特殊字符,确保可以作为合法的文件夹名 // 移除商品名称中的特殊字符,确保可以作为合法的文件夹名
@ -3735,15 +4281,30 @@ app.post('/api/products/upload', upload.array('images', 10), async (req, res) =>
code: 500, code: 500,
error: err.message error: err.message
}); });
} finally {
// 确保临时文件被清理
if (tempFilesToClean.length > 0) {
try {
cleanTempFiles(tempFilesToClean);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
} }
}); });
// 【关键修复】在 handleAddImagesToExistingProduct 函数中加强图片合并逻辑 // 【关键修复】在 handleAddImagesToExistingProduct 函数中加强图片合并逻辑
async function handleAddImagesToExistingProduct(req, res, existingProductId, uploadedFiles) { async function handleAddImagesToExistingProduct(req, res, existingProductId, uploadedFiles) {
let transaction; let transaction;
let tempFilesToClean = [];
try { try {
console.log('【图片更新模式】开始处理图片上传到已存在商品,商品ID:', existingProductId); console.log('【图片更新模式】开始处理图片上传到已存在商品,商品ID:', existingProductId);
// 收集需要清理的临时文件路径
for (const file of uploadedFiles) {
tempFilesToClean.push(file.path);
}
// 使用事务确保数据一致性 // 使用事务确保数据一致性
transaction = await sequelize.transaction(); transaction = await sequelize.transaction();
@ -3901,6 +4462,15 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl
code: 500, code: 500,
error: error.message error: error.message
}); });
} finally {
// 确保临时文件被清理
if (tempFilesToClean.length > 0) {
try {
cleanTempFiles(tempFilesToClean);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
} }
} }
@ -4380,7 +4950,7 @@ app.post('/api/comments/get', async (req, res) => {
// 提交评论 // 提交评论
app.post('/api/comments/add', async (req, res) => { app.post('/api/comments/add', async (req, res) => {
try { try {
const { productId, phoneNumber, comments } = req.body; const { productId, phoneNumber, comments, review = 0 } = req.body;
if (!productId || !phoneNumber || !comments) { if (!productId || !phoneNumber || !comments) {
return res.status(400).json({ return res.status(400).json({
@ -4395,6 +4965,7 @@ app.post('/api/comments/add', async (req, res) => {
productId, productId,
phoneNumber, phoneNumber,
comments, comments,
review,
time: new Date() time: new Date()
}); });
@ -7159,6 +7730,7 @@ app.post('/api/settlement/submit', async (req, res) => {
// 上传入驻文件 // 上传入驻文件
app.post('/api/settlement/upload', upload.single('file'), async (req, res) => { app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
let tempFilePath = null;
try { try {
const { openid, fileType } = req.body; const { openid, fileType } = req.body;
@ -7180,12 +7752,13 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
}); });
} }
tempFilePath = req.file.path;
// 上传文件到OSS - 使用静态方法调用 // 上传文件到OSS - 使用静态方法调用
// 注意:OssUploader.uploadFile直接返回URL字符串,而不是包含url属性的对象 // 注意:OssUploader.uploadFile直接返回URL字符串,而不是包含url属性的对象
const fileUrl = await OssUploader.uploadFile(req.file.path, `settlement/${fileType}/${Date.now()}_${req.file.originalname}`); const fileUrl = await OssUploader.uploadFile(tempFilePath, `settlement/${fileType}/${Date.now()}_${req.file.originalname}`);
// 删除临时文件
fs.unlinkSync(req.file.path);
// 确保返回的URL是干净的字符串,移除可能存在的反引号和空格 // 确保返回的URL是干净的字符串,移除可能存在的反引号和空格
const cleanFileUrl = String(fileUrl).replace(/[` ]/g, ''); const cleanFileUrl = String(fileUrl).replace(/[` ]/g, '');
@ -7202,16 +7775,21 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
} catch (error) { } catch (error) {
console.error('入驻文件上传失败:', error); console.error('入驻文件上传失败:', error);
// 清理临时文件
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({ res.status(500).json({
success: false, success: false,
code: 500, code: 500,
message: '文件上传失败: ' + error.message message: '文件上传失败: ' + error.message
}); });
} finally {
// 确保临时文件被清理
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
console.log('临时文件已清理:', tempFilePath);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
} }
}); });

47
server-example/test-post-api.js

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

78
utils/api.js

@ -1364,11 +1364,14 @@ module.exports = {
console.log('商品数据:', productData); console.log('商品数据:', productData);
console.log('图片数量:', imageUrls.length); console.log('图片数量:', imageUrls.length);
// 【新增】确保sellerId使用userId // 【关键修复】确保sellerId使用userId,无论是否已存在
const userId = wx.getStorageSync('userId'); const userId = wx.getStorageSync('userId');
if (userId && productData.sellerId) { if (userId) {
console.log('【修复】确保sellerId使用userId:', userId); console.log('【修复】确保sellerId使用userId:', userId);
productData.sellerId = userId; // 确保使用userId productData.sellerId = userId;
} else {
console.error('【错误】本地缓存中没有userId,请重新登录');
return Promise.reject(new Error('用户未登录,请重新登录'));
} }
// 如果没有图片,使用普通请求 // 如果没有图片,使用普通请求
@ -1382,7 +1385,7 @@ module.exports = {
// 创建包含所有图片URL的商品数据 // 创建包含所有图片URL的商品数据
const productDataWithAllImages = { const productDataWithAllImages = {
...productData, ...productData,
sellerId: userId || productData.sellerId, // 【确保】使用userId sellerId: userId, // 【确保】使用userId
imageUrls: imageUrls, // 设置imageUrls字段,确保服务器端能正确识别 imageUrls: imageUrls, // 设置imageUrls字段,确保服务器端能正确识别
allImageUrls: imageUrls, // 添加完整的图片URL列表(备用字段) allImageUrls: imageUrls, // 添加完整的图片URL列表(备用字段)
// 生成会话ID,确保所有图片上传关联同一个商品 // 生成会话ID,确保所有图片上传关联同一个商品
@ -1403,11 +1406,14 @@ module.exports = {
console.log('===== 最终版uploadProductWithRecursiveImages开始执行 ====='); console.log('===== 最终版uploadProductWithRecursiveImages开始执行 =====');
console.log('待上传图片数量:', imageUrls.length); console.log('待上传图片数量:', imageUrls.length);
// 【新增】确保sellerId使用userId // 【关键修复】确保sellerId使用userId,无论是否已存在
const userId = wx.getStorageSync('userId'); const userId = wx.getStorageSync('userId');
if (userId && productData.sellerId) { if (userId) {
console.log('【修复】确保sellerId使用userId:', userId); console.log('【修复】确保sellerId使用userId:', userId);
productData.sellerId = userId; productData.sellerId = userId;
} else {
console.error('【错误】本地缓存中没有userId,请重新登录');
return Promise.reject(new Error('用户未登录,请重新登录'));
} }
// 防御性检查 // 防御性检查
@ -4500,5 +4506,65 @@ module.exports = {
resolve({ exists: false, error: error.message }); resolve({ exists: false, error: error.message });
}); });
}); });
},
// 获取热门话题
getHotTopics: function() {
return new Promise((resolve, reject) => {
request('/api/eggbar/hot-topics', 'GET')
.then(response => {
console.log('获取热门话题成功:', response);
resolve(response);
})
.catch(error => {
console.error('获取热门话题失败:', error);
reject(error);
});
});
},
// 获取动态列表
getPosts: function(params) {
return new Promise((resolve, reject) => {
request('/api/eggbar/posts', 'GET', params)
.then(response => {
console.log('获取动态列表成功:', response);
resolve(response);
})
.catch(error => {
console.error('获取动态列表失败:', error);
reject(error);
});
});
},
// 点赞动态
likePost: function(postId, phone) {
return new Promise((resolve, reject) => {
request(`/api/eggbar/posts/${postId}/like`, 'POST', { phone: phone })
.then(response => {
console.log('点赞成功:', response);
resolve(response);
})
.catch(error => {
console.error('点赞失败:', error);
reject(error);
});
});
},
// 创建动态
createPost: function(postData) {
return new Promise((resolve, reject) => {
request('/api/eggbar/posts', 'POST', postData)
.then(response => {
console.log('创建动态成功:', response);
resolve(response);
})
.catch(error => {
console.error('创建动态失败:', error);
reject(error);
});
});
} }
}; };
Loading…
Cancel
Save