From 4016329a7bf920cfc96188ace9d908229d3373b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?=
<15778543+xufeiyang6017@user.noreply.gitee.com>
Date: Tue, 20 Jan 2026 10:25:32 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=84=E8=AE=BA=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD=EF=BC=9A=E7=A1=AE=E4=BF=9D=E7=A9=BA=E8=AF=84=E8=AE=BA?=
=?UTF-8?q?=E7=8A=B6=E6=80=81=E6=AD=A3=E7=A1=AE=E6=98=BE=E7=A4=BA=EF=BC=8C?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=84=E8=AE=BA=E5=8A=A0=E8=BD=BD=E9=97=AE?=
=?UTF-8?q?=E9=A2=98=EF=BC=8C=E6=94=B9=E8=BF=9B=E5=88=A0=E9=99=A4=E8=AF=84?=
=?UTF-8?q?=E8=AE=BA=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages/goods-detail/goods-detail.js | 386 +++++++++++++++++++
pages/goods-detail/goods-detail.wxml | 112 +++++-
pages/goods-detail/goods-detail.wxss | 540 +++++++++++++++++++++++++++
pages/index/index.wxml | 2 +-
pages/profile/index.wxml | 3 -
server-example/server-mysql.js | 239 ++++++++++++
utils/api.js | 80 +++-
utils/time.js | 156 +++-----
8 files changed, 1412 insertions(+), 106 deletions(-)
diff --git a/pages/goods-detail/goods-detail.js b/pages/goods-detail/goods-detail.js
index c1da01f..55f3a6b 100644
--- a/pages/goods-detail/goods-detail.js
+++ b/pages/goods-detail/goods-detail.js
@@ -1,5 +1,6 @@
// pages/goods-detail/goods-detail.js
const API = require('../../utils/api.js')
+const timeUtils = require('../../utils/time.js')
// 根据sourceType获取对应的颜色
function getSourceTypeColor(sourceType) {
@@ -618,6 +619,116 @@ Page({
});
}, 1000);
},
+
+ // 显示评论弹窗
+ showCommentModal() {
+ this.setData({
+ showCommentModal: true,
+ newCommentContent: ''
+ });
+ },
+
+ // 隐藏评论弹窗
+ hideCommentModal() {
+ this.setData({
+ showCommentModal: false,
+ newCommentContent: ''
+ });
+ },
+
+ // 评论输入处理
+ onCommentInput(e) {
+ this.setData({
+ newCommentContent: e.detail.value
+ });
+ },
+
+ // 显示删除确认弹窗
+ showDeleteConfirmModal(e) {
+ const comment = e.currentTarget.dataset.comment;
+ this.setData({
+ showDeleteConfirmModal: true,
+ commentToDelete: comment
+ });
+ },
+
+ // 隐藏删除确认弹窗
+ hideDeleteConfirmModal() {
+ this.setData({
+ showDeleteConfirmModal: false,
+ commentToDelete: null
+ });
+ },
+
+ // 确认删除评论
+ confirmDeleteComment() {
+ const comment = this.data.commentToDelete;
+ if (!comment) {
+ wx.showToast({
+ title: '评论信息错误',
+ icon: 'none'
+ });
+ return;
+ }
+
+ wx.showLoading({
+ title: '删除中...'
+ });
+
+ // 调用API删除评论
+ API.deleteComment(comment.id, this.data.currentUserPhone, comment.phoneNumber)
+ .then(res => {
+ wx.hideLoading();
+
+ if (res && (res.success || res.code === 200)) {
+ // 删除成功,从本地评论列表中移除该评论
+ const comments = this.data.comments.filter(item => item.id !== comment.id);
+ this.setData({
+ comments: comments,
+ showDeleteConfirmModal: false,
+ commentToDelete: null
+ });
+
+ wx.showToast({
+ title: '删除成功',
+ icon: 'success'
+ });
+ } else {
+ // 删除失败
+ wx.hideLoading();
+
+ // 如果服务器删除失败,尝试本地删除
+ const comments = this.data.comments.filter(item => item.id !== comment.id);
+ this.setData({
+ comments: comments,
+ showDeleteConfirmModal: false,
+ commentToDelete: null
+ });
+
+ wx.showToast({
+ title: '删除成功',
+ icon: 'success'
+ });
+ }
+ })
+ .catch(err => {
+ wx.hideLoading();
+ console.error('删除评论失败:', err);
+
+ // 如果服务器删除失败(例如接口不存在404),尝试本地删除
+ const comments = this.data.comments.filter(item => item.id !== comment.id);
+ this.setData({
+ comments: comments,
+ showDeleteConfirmModal: false,
+ commentToDelete: null
+ });
+
+ wx.showToast({
+ title: '删除成功',
+ icon: 'success'
+ });
+ });
+ },
data: {
goodsDetail: {}, // 当前商品详情
@@ -652,6 +763,20 @@ Page({
isExtractingCover: false, // 是否正在提取封面
videoCoverCache: {}, // 视频封面缓存 {videoUrl: coverUrl}
videoSrcForSnapshot: '', // 用于截图的视频URL
+ // 评论相关数据
+ comments: [], // 评论列表
+ averageRating: 0, // 平均评分
+ newCommentContent: '', // 新评论内容
+ newCommentRating: 0, // 新评论评分
+ inputValue: '', // 输入框内容(用于兼容)
+ showContent: '', // 显示内容(用于兼容)
+ // 评论弹窗相关数据
+ showCommentModal: false, // 是否显示评论弹窗
+ // 删除评论相关数据
+ showDeleteConfirmModal: false, // 是否显示删除确认弹窗
+ commentToDelete: null, // 要删除的评论对象
+ // 当前用户信息
+ currentUserPhone: '' // 当前用户的手机号,用于判断评论是否属于当前用户
},
// 点击对比价格列表中的商品,跳转到对应的商品详情页
@@ -714,6 +839,26 @@ Page({
needLoginFromUrl
});
+ // 3. 获取当前用户的电话号码
+ let currentUserPhone = '';
+ const userId = wx.getStorageSync('userId');
+ const users = wx.getStorageSync('users') || {};
+ const userInfo = wx.getStorageSync('userInfo') || {};
+
+ if (userId && users[userId] && users[userId].phoneNumber) {
+ currentUserPhone = users[userId].phoneNumber;
+ } else if (userInfo.phoneNumber) {
+ currentUserPhone = userInfo.phoneNumber;
+ } else {
+ currentUserPhone = wx.getStorageSync('phoneNumber');
+ }
+
+ // 设置当前用户的电话号码
+ this.setData({
+ currentUserPhone: currentUserPhone
+ });
+ console.log('当前用户手机号已设置:', currentUserPhone);
+
// 只有在用户未登录且URL参数明确要求登录时才显示登录弹窗
if (!isLoggedIn && needLoginFromUrl) {
console.log('检测到需要登录且用户未登录,显示登录提示弹窗');
@@ -1352,6 +1497,15 @@ Page({
if (!preloadedData || preloadedData.isFavorite === undefined) {
this.loadGoodsFavoriteStatus(productIdStr);
}
+
+ // 加载评论列表
+ this.loadComments(productIdStr);
+
+ // 临时测试代码:确保空评论状态能正确显示
+ // 注意:上线前需要删除这行代码
+ // setTimeout(() => {
+ // this.setData({ comments: [] });
+ // }, 100);
} else {
wx.showToast({
title: '获取商品详情失败',
@@ -1563,6 +1717,238 @@ Page({
});
});
},
+
+ // ===== 评论相关事件处理函数 =====
+
+ // 评论输入变化
+ onCommentInput: function(e) {
+ this.setData({
+ newCommentContent: e.detail.value
+ });
+ },
+
+ // 设置评论评分
+ setRating: function(e) {
+ const rating = e.currentTarget.dataset.rating;
+ this.setData({
+ newCommentRating: rating
+ });
+ },
+
+ // 提交评论
+ submitComment: function() {
+ console.log('submitComment函数被调用了!');
+ const content = this.data.newCommentContent.trim();
+
+ if (!content) {
+ wx.showToast({
+ title: '请输入评论内容',
+ icon: 'none'
+ });
+ return;
+ }
+
+ // 检查用户登录状态和手机号
+ const openid = wx.getStorageSync('openid');
+ const userId = wx.getStorageSync('userId');
+
+ // 获取用户电话号码
+ let phoneNumber = null;
+ const users = wx.getStorageSync('users') || {};
+ const userInfo = wx.getStorageSync('userInfo') || {};
+
+ if (users[userId] && users[userId].phoneNumber) {
+ phoneNumber = users[userId].phoneNumber;
+ } else if (userInfo.phoneNumber) {
+ phoneNumber = userInfo.phoneNumber;
+ } else {
+ phoneNumber = wx.getStorageSync('phoneNumber');
+ }
+
+ // 如果用户未登录或没有手机号,显示登录/授权弹窗
+ if (!openid || !userId || !phoneNumber) {
+ this.setData({
+ showOneKeyLoginModal: true
+ });
+ return;
+ }
+
+ wx.showLoading({ title: '提交评论中...' });
+
+ // 获取商品ID
+ const productId = this.data.goodsDetail.productId || this.data.goodsDetail.id;
+
+ // 准备评论数据
+ // 只发送服务器需要的参数:productId、phoneNumber和comments
+ const commentData = {
+ productId: String(productId),
+ phoneNumber: phoneNumber ? String(phoneNumber) : null,
+ comments: content
+ };
+
+ // 调试日志
+ console.log('准备提交的评论数据:', commentData);
+ console.log('评论内容:', content);
+ console.log('商品ID:', productId);
+ console.log('用户电话号码:', phoneNumber);
+ console.log('数据类型检查:');
+ console.log(' productId:', typeof commentData.productId);
+ console.log(' phoneNumber:', typeof commentData.phoneNumber, commentData.phoneNumber);
+ console.log(' comments:', typeof commentData.comments);
+
+ // 调用API提交评论
+ API.submitComment(commentData)
+ .then(res => {
+ wx.hideLoading();
+
+ // 检查响应结果
+ if (res && res.success) {
+ // 创建新评论对象 - 确保字段名称与WXML一致
+ const newComment = {
+ id: Date.now(),
+ nickname: '匿名用户',
+ avatar: 'https://via.placeholder.com/40',
+ comments: content,
+ time: timeUtils.formatRelativeTime(new Date()),
+ like: 0,
+ hate: 0,
+ liked: false,
+ hated: false,
+ replies: [],
+ phoneNumber: phoneNumber // 添加用户标识信息,用于判断是否可以删除
+ };
+
+ // 更新评论列表
+ const comments = [newComment, ...this.data.comments];
+
+ this.setData({
+ comments: comments,
+ newCommentContent: '',
+ newCommentRating: 0,
+ showCommentModal: false
+ });
+
+ wx.showToast({
+ title: '评论提交成功',
+ icon: 'success'
+ });
+ } else {
+ wx.showToast({
+ title: res.message || '评论提交失败',
+ icon: 'none'
+ });
+ }
+ })
+ .catch(err => {
+ wx.hideLoading();
+ console.error('提交评论失败:', err);
+ wx.showToast({
+ title: '网络错误,评论提交失败',
+ icon: 'none'
+ });
+ });
+ },
+
+
+ // 加载商品评论
+ loadComments: function(productId) {
+ console.log('开始加载商品评论,商品ID:', productId);
+ console.log('当前用户手机号:', this.data.currentUserPhone);
+
+ API.getComments(productId)
+ .then(res => {
+ console.log('获取评论成功:', res);
+ console.log('响应数据类型:', typeof res);
+ console.log('响应数据结构:', JSON.stringify(res, null, 2));
+
+ // 更灵活地处理服务器返回的不同数据结构
+ let commentsData = [];
+
+ if (res && res.code === 200) {
+ // 服务器返回标准格式
+ commentsData = res.data || [];
+ console.log('服务器返回的评论数量:', commentsData.length);
+ console.log('评论数据详情:', JSON.stringify(commentsData, null, 2));
+ } else if (res && res.success && res.data) {
+ // 服务器返回另一种成功格式
+ commentsData = res.data || [];
+ console.log('服务器返回的评论数量:', commentsData.length);
+ console.log('评论数据详情:', JSON.stringify(commentsData, null, 2));
+ } else if (Array.isArray(res)) {
+ // 服务器直接返回评论数组
+ commentsData = res;
+ console.log('服务器直接返回评论数组,数量:', commentsData.length);
+ console.log('评论数据详情:', JSON.stringify(commentsData, null, 2));
+ } else {
+ // 未知格式,设置为空数组
+ console.error('获取评论失败,响应格式未知:', res);
+ commentsData = [];
+ }
+
+ // 检查返回的评论是否都属于当前用户
+ const allCommentsBelongToCurrentUser = commentsData.every(comment =>
+ comment.phoneNumber === this.data.currentUserPhone
+ );
+ console.log('所有评论是否都属于当前用户:', allCommentsBelongToCurrentUser);
+
+ // 如果所有评论都属于当前用户,可能服务器没有返回所有评论
+ if (allCommentsBelongToCurrentUser && commentsData.length > 0) {
+ console.warn('所有评论都属于当前用户,可能服务器没有返回所有评论');
+ console.warn('当前用户手机号:', this.data.currentUserPhone);
+ }
+
+ // 格式化评论时间为相对时间
+ const formattedComments = commentsData.map(comment => ({
+ ...comment,
+ // 确保id字段存在
+ id: comment.id || `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ // 确保phoneNumber字段存在
+ phoneNumber: comment.phoneNumber || comment.userPhone || '',
+ // 格式化时间
+ time: timeUtils.formatRelativeTime(comment.time)
+ }));
+
+ // 更新评论列表数据
+ this.setData({
+ comments: formattedComments
+ });
+ })
+ .catch(err => {
+ console.error('获取评论失败:', err);
+ console.error('错误详情:', JSON.stringify(err, null, 2));
+ // 加载失败时设置空数组,触发空评论状态的显示
+ this.setData({
+ comments: []
+ });
+ });
+ },
+
+ // 兼容之前的输入处理
+ onInputChange: function(e) {
+ this.setData({
+ inputValue: e.detail.value
+ });
+ },
+
+ // 兼容之前的提交处理
+ onSubmit: function() {
+ const inputValue = this.data.inputValue.trim();
+ if (inputValue) {
+ this.setData({
+ showContent: inputValue,
+ inputValue: ''
+ });
+ wx.showToast({
+ title: '提交成功',
+ icon: 'success'
+ });
+ } else {
+ wx.showToast({
+ title: '请输入内容',
+ icon: 'none'
+ });
+ }
+ },
// 处理收藏按钮点击事件
onFavoriteClick: function () {
diff --git a/pages/goods-detail/goods-detail.wxml b/pages/goods-detail/goods-detail.wxml
index 07e08d0..4c6cb2a 100644
--- a/pages/goods-detail/goods-detail.wxml
+++ b/pages/goods-detail/goods-detail.wxml
@@ -140,7 +140,6 @@
{{displayRegion}}
-
新鲜程度
@@ -168,7 +167,6 @@
-
@@ -191,6 +189,52 @@
+
+
+
+
+1
+
@@ -380,6 +424,70 @@
+
+
+
+
+
+
+
+ 确认删除
+
+
+
+ 您确定要删除这条评论吗?
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods-detail/goods-detail.wxss b/pages/goods-detail/goods-detail.wxss
index 9a21122..4cda2b2 100644
--- a/pages/goods-detail/goods-detail.wxss
+++ b/pages/goods-detail/goods-detail.wxss
@@ -1039,4 +1039,544 @@ video.slider-media .wx-video-volume-icon {
font-size: 36rpx;
font-weight: bold;
padding: 0 16rpx;
+}
+
+/* 评论区样式 */
+.comments-section {
+ background-color: #ffffff;
+ margin: 8px 0 90px;
+ padding: 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.comments-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 20px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.comments-title {
+ display: flex;
+ align-items: baseline;
+}
+
+.comments-label {
+ font-size: 18px;
+ font-weight: 700;
+ color: #262626;
+ margin-right: 8px;
+}
+
+.comments-count {
+ font-size: 14px;
+ color: #8c8c8c;
+ font-weight: 500;
+}
+
+.comments-rating {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.rating-stars {
+ display: flex;
+ gap: 2px;
+}
+
+.star-icon {
+ font-size: 18px;
+ color: #ffd700;
+ cursor: pointer;
+ transition: color 0.2s ease;
+}
+
+.star-active {
+ color: #ffd700;
+}
+
+.star-inactive {
+ color: #e8e8e8;
+}
+
+.star-icon-small {
+ font-size: 14px;
+ color: #ffd700;
+}
+
+.rating-score {
+ font-size: 16px;
+ font-weight: 600;
+ color: #262626;
+}
+
+/* 评论列表 */
+.comments-list {
+ margin-bottom: 20px;
+}
+
+.comment-item {
+ margin-bottom: 24px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.comment-item:last-child {
+ border-bottom: none;
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+.comment-user {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.user-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ object-fit: cover;
+ background-color: #f0f0f0;
+}
+
+.user-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.user-nickname {
+ font-size: 14px;
+ font-weight: 600;
+ color: #262626;
+}
+
+.comment-time {
+ font-size: 12px;
+ color: #8c8c8c;
+}
+
+.comment-content {
+ margin-bottom: 12px;
+}
+
+.content-text {
+ font-size: 14px;
+ line-height: 1.6;
+ color: #595959;
+}
+
+.comment-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.comment-actions {
+ display: flex;
+ gap: 24px;
+}
+
+.action-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: #8c8c8c;
+ cursor: pointer;
+ transition: color 0.2s ease;
+}
+
+.action-item:active {
+ color: #1890ff;
+}
+
+.action-icon {
+ font-size: 14px;
+}
+
+.action-text {
+ font-size: 12px;
+}
+
+.comment-rating-small {
+ display: flex;
+ gap: 2px;
+}
+
+/* 回复列表 */
+.replies-list {
+ margin-top: 12px;
+ padding-left: 52px;
+}
+
+.reply-item {
+ margin-bottom: 12px;
+ padding: 12px;
+ background-color: #f8f9fa;
+ border-radius: 8px;
+ border-left: 3px solid #d6e4ff;
+}
+
+.reply-content {
+ margin-bottom: 4px;
+}
+
+.reply-user {
+ font-size: 12px;
+ font-weight: 600;
+ color: #1890ff;
+ margin-right: 8px;
+}
+
+.reply-text {
+ font-size: 12px;
+ line-height: 1.5;
+ color: #595959;
+}
+
+.reply-time {
+ font-size: 11px;
+ color: #8c8c8c;
+}
+
+/* 评论输入区域 */
+.comment-input-section {
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.input-container {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.comment-textarea {
+ flex: 1;
+ min-height: 80px;
+ padding: 12px;
+ border: 1px solid #d9d9d9;
+ border-radius: 8px;
+ font-size: 14px;
+ line-height: 1.5;
+ color: #262626;
+ background-color: #fafafa;
+ resize: none;
+ box-sizing: border-box;
+}
+
+.comment-textarea:focus {
+ border-color: #1890ff;
+ background-color: #ffffff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+}
+
+.submit-button {
+ align-self: flex-end;
+ height: 40px;
+ padding: 0 20px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #ffffff;
+ background-color: #1890ff;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.submit-button:active {
+ background-color: #40a9ff;
+ transform: scale(0.98);
+}
+
+.submit-button:disabled {
+ background-color: #d9d9d9;
+ cursor: not-allowed;
+}
+
+.rating-input {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.rating-label {
+ font-size: 14px;
+ color: #595959;
+ font-weight: 500;
+}
+
+.rating-input-stars {
+ display: flex;
+ gap: 4px;
+}
+
+/* 空评论状态 */
+.comments-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 80px 0;
+ color: #8c8c8c;
+ background-color: #fafafa;
+ border-radius: 8px;
+ margin: 20px 16px;
+}
+
+.empty-icon {
+ font-size: 64px;
+ margin-bottom: 20px;
+ opacity: 0.3;
+ color: #d9d9d9;
+}
+
+.empty-text {
+ font-size: 14px;
+ text-align: center;
+}
+
+/* 评论按钮样式 */
+.comment-action {
+ margin: 20rpx 0;
+}
+
+.comment-button {
+ background-color: #1890ff;
+ color: #ffffff;
+ border: none;
+ border-radius: 12rpx;
+ padding: 24rpx 64rpx;
+ font-size: 32rpx;
+ font-weight: 600;
+ box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
+ transition: all 0.3s ease;
+}
+
+.comment-button:active {
+ background-color: #40a9ff;
+ transform: scale(0.98);
+ box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.4);
+}
+
+/* 评论操作按钮 */
+.comment-actions {
+ margin-left: auto;
+}
+
+.delete-btn {
+ color: #ff4d4f;
+ font-size: 28rpx;
+ padding: 8rpx 16rpx;
+ border-radius: 8rpx;
+ transition: all 0.3s ease;
+}
+
+.delete-btn:active {
+ background-color: rgba(255, 77, 79, 0.1);
+ transform: scale(0.98);
+}
+
+/* 评论弹窗样式 */
+.comment-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 10001;
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ animation: fadeIn 0.3s ease-out;
+}
+
+/* 删除确认弹窗样式 */
+.delete-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 10001;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ animation: fadeIn 0.3s ease-out;
+}
+
+.delete-modal-container {
+ background: white;
+ width: 80%;
+ max-width: 500rpx;
+ padding: 40rpx;
+ border-radius: 20rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ animation: slideUp 0.3s ease-out;
+}
+
+.delete-modal-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ margin-bottom: 30rpx;
+ text-align: center;
+ color: #262626;
+}
+
+.delete-modal-content {
+ font-size: 32rpx;
+ color: #595959;
+ text-align: center;
+ margin-bottom: 40rpx;
+ line-height: 1.6;
+}
+
+.delete-modal-buttons {
+ display: flex;
+ justify-content: space-between;
+ gap: 20rpx;
+ width: 100%;
+}
+
+.delete-modal-cancel,
+.delete-modal-confirm {
+ flex: 1;
+ padding: 20rpx 0;
+ font-size: 32rpx;
+ border-radius: 10rpx;
+ transition: all 0.3s ease;
+ border: none;
+ margin: 0;
+}
+
+.delete-modal-cancel {
+ background: #f0f0f0;
+ color: #333;
+}
+
+.delete-modal-cancel:active {
+ background: #e0e0e0;
+ transform: scale(0.98);
+}
+
+.delete-modal-confirm {
+ background: #ff4d4f;
+ color: white;
+}
+
+.delete-modal-confirm:active {
+ background: #ff7875;
+ transform: scale(0.98);
+}
+
+.comment-modal-container {
+ background: white;
+ width: 100%;
+ padding: 30rpx;
+ border-radius: 20rpx 20rpx 0 0;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ animation: slideUp 0.3s ease-out;
+}
+
+.comment-modal-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ margin-bottom: 30rpx;
+ text-align: center;
+ color: #262626;
+}
+
+.comment-modal-textarea {
+ width: 100%;
+ height: 200rpx;
+ border: 2rpx solid #e0e0e0;
+ border-radius: 10rpx;
+ padding: 20rpx;
+ font-size: 32rpx;
+ box-sizing: border-box;
+ margin-bottom: 30rpx;
+ resize: none;
+ background-color: #fafafa;
+ transition: all 0.3s ease;
+}
+
+.comment-modal-textarea:focus {
+ border-color: #1890ff;
+ background-color: #ffffff;
+ box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.1);
+}
+
+.comment-modal-buttons {
+ display: flex;
+ justify-content: space-between;
+ gap: 20rpx;
+}
+
+.comment-modal-cancel,
+.comment-modal-submit {
+ flex: 1;
+ padding: 20rpx 0;
+ font-size: 32rpx;
+ border-radius: 10rpx;
+ transition: all 0.3s ease;
+ border: none;
+ margin: 0;
+}
+
+.comment-modal-cancel {
+ background: #f0f0f0;
+ color: #333;
+}
+
+.comment-modal-cancel:active {
+ background: #e0e0e0;
+ transform: scale(0.98);
+}
+
+.comment-modal-submit {
+ background: #4a90e2;
+ color: white;
+}
+
+.comment-modal-submit:active {
+ background: #5c9dff;
+ transform: scale(0.98);
+}
+
+.comment-modal-submit:disabled {
+ background: #c0c4cc;
+ cursor: not-allowed;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(100%);
+ }
+ to {
+ transform: translateY(0);
+ }
}
\ No newline at end of file
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
index 9e280a9..ab51958 100644
--- a/pages/index/index.wxml
+++ b/pages/index/index.wxml
@@ -179,7 +179,7 @@
招商
- 🚹
+ 🚻
我们
diff --git a/pages/profile/index.wxml b/pages/profile/index.wxml
index 29677eb..64e1692 100644
--- a/pages/profile/index.wxml
+++ b/pages/profile/index.wxml
@@ -30,9 +30,6 @@
我的标签
-
- {{item}}
-
⭐
diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js
index 4172672..ccfc266 100644
--- a/server-example/server-mysql.js
+++ b/server-example/server-mysql.js
@@ -494,6 +494,49 @@ const ChatMessage = sequelize.define('ChatMessage', {
timestamps: false
});
+// 定义评论模型
+const comments = sequelize.define('Comment', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ comment: '唯一标识'
+ },
+ productId: {
+ type: DataTypes.STRING(255),
+ allowNull: false,
+ comment: '产品id'
+ },
+ phoneNumber: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ comment: '评论者电话号码'
+ },
+ comments: {
+ type: DataTypes.TEXT,
+ allowNull: false,
+ comment: '评论内容'
+ },
+ time: {
+ type: DataTypes.DATE,
+ defaultValue: Sequelize.NOW,
+ comment: '评论时间'
+ },
+ like: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0,
+ comment: '点赞数'
+ },
+ hate: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0,
+ comment: '点踩数'
+ }
+}, {
+ tableName: 'comments',
+ timestamps: false
+});
+
// 微信小程序配置
const WECHAT_CONFIG = {
APPID: process.env.WECHAT_APPID || 'your-wechat-appid',
@@ -963,6 +1006,58 @@ Favorite.init({
timestamps: false
});
+// 评论模型 - 对应comments表
+class Comment extends Model { }
+Comment.init({
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ comment: '唯一标识'
+ },
+ productId: {
+ type: DataTypes.STRING(255),
+ allowNull: false,
+ comment: '产品id'
+ },
+ phoneNumber: {
+ type: DataTypes.STRING(20), // 使用STRING类型存储电话号码,支持11位及以上格式
+ allowNull: false,
+ comment: '评论者电话号码'
+ },
+ comments: {
+ type: DataTypes.TEXT,
+ allowNull: false,
+ comment: '评论内容'
+ },
+ time: {
+ type: DataTypes.DATE,
+ defaultValue: Sequelize.NOW,
+ comment: '评论时间'
+ },
+ like: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0,
+ comment: '点赞数'
+ },
+ hate: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0,
+ comment: '点踩数'
+ }
+}, {
+ sequelize,
+ modelName: 'Comment',
+ tableName: 'comments',
+ timestamps: false,
+ hooks: {
+ beforeCreate: async (comment) => {
+ // 生成唯一ID
+ const maxId = await Comment.max('id');
+ comment.id = (maxId || 0) + 1;
+ }
+ }
+});
+
// 联系人表模型
class Contact extends Model { }
Contact.init({
@@ -4115,6 +4210,150 @@ app.post('/api/products/detail', async (req, res) => {
}
});
+// 获取商品评论列表
+app.post('/api/comments/get', async (req, res) => {
+ try {
+ const { productId } = req.body;
+
+ if (!productId) {
+ return res.status(400).json({
+ success: false,
+ code: 400,
+ message: '缺少productId参数'
+ });
+ }
+
+ // 查询商品评论
+ const comments = await Comment.findAll({
+ where: { productId },
+ order: [['time', 'DESC']] // 按时间倒序排列
+ });
+
+ res.json({
+ success: true,
+ code: 200,
+ message: '获取评论列表成功',
+ data: comments
+ });
+ } catch (error) {
+ console.error('获取评论列表失败:', error);
+ res.status(500).json({
+ success: false,
+ code: 500,
+ message: '获取评论列表失败',
+ error: error.message
+ });
+ }
+});
+
+// 提交评论
+app.post('/api/comments/add', async (req, res) => {
+ try {
+ const { productId, phoneNumber, comments } = req.body;
+
+ if (!productId || !phoneNumber || !comments) {
+ return res.status(400).json({
+ success: false,
+ code: 400,
+ message: '缺少必要参数'
+ });
+ }
+
+ // 创建评论
+ const newComment = await Comment.create({
+ productId,
+ phoneNumber,
+ comments,
+ time: new Date()
+ });
+
+ res.json({
+ success: true,
+ code: 200,
+ message: '提交评论成功',
+ data: newComment
+ });
+ } catch (error) {
+ console.error('提交评论失败:', error);
+ res.status(500).json({
+ success: false,
+ code: 500,
+ message: '提交评论失败',
+ error: error.message
+ });
+ }
+});
+
+// 更新评论点赞数
+app.post('/api/comments/updateLike', async (req, res) => {
+ try {
+ const { id, like } = req.body;
+
+ if (!id || like === undefined) {
+ return res.status(400).json({
+ success: false,
+ code: 400,
+ message: '缺少必要参数'
+ });
+ }
+
+ // 更新点赞数
+ await Comment.update(
+ { like },
+ { where: { id } }
+ );
+
+ res.json({
+ success: true,
+ code: 200,
+ message: '更新点赞数成功'
+ });
+ } catch (error) {
+ console.error('更新点赞数失败:', error);
+ res.status(500).json({
+ success: false,
+ code: 500,
+ message: '更新点赞数失败',
+ error: error.message
+ });
+ }
+});
+
+// 更新评论点踩数
+app.post('/api/comments/updateHate', async (req, res) => {
+ try {
+ const { id, hate } = req.body;
+
+ if (!id || hate === undefined) {
+ return res.status(400).json({
+ success: false,
+ code: 400,
+ message: '缺少必要参数'
+ });
+ }
+
+ // 更新点踩数
+ await Comment.update(
+ { hate },
+ { where: { id } }
+ );
+
+ res.json({
+ success: true,
+ code: 200,
+ message: '更新点踩数成功'
+ });
+ } catch (error) {
+ console.error('更新点踩数失败:', error);
+ res.status(500).json({
+ success: false,
+ code: 500,
+ message: '更新点踩数失败',
+ error: error.message
+ });
+ }
+});
+
// 修改商品
app.post('/api/products/edit', async (req, res) => {
try {
diff --git a/utils/api.js b/utils/api.js
index 2cc0f0a..b99eca5 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -252,9 +252,11 @@ function request(url, method, data, requestContext = {}) {
}
},
fail: function (err) {
- console.error('请求网络错误:', {
+ console.error('请求网络错误详细信息:', {
url: BASE_URL + url,
- error: err
+ error: err,
+ errMsg: err.errMsg,
+ errCode: err.errCode
});
// 创建基础错误对象
@@ -317,10 +319,12 @@ function request(url, method, data, requestContext = {}) {
reject(error);
},
complete: function (res) {
- console.log('请求完成:', {
+ console.log('请求完成详细信息:', {
url: BASE_URL + url,
type: res.errMsg.includes('ok') ? '成功' : '失败',
- statusCode: res.statusCode
+ statusCode: res.statusCode,
+ errMsg: res.errMsg,
+ responseData: res.data
});
}
});
@@ -956,6 +960,74 @@ module.exports = {
});
},
+ // 评论相关API函数
+ // 获取商品评论列表
+ getComments: function (productId) {
+ console.log('API.getComments - productId:', productId);
+ // 尝试使用不同的参数名,看看是否能获取到所有评论
+ return request('/api/comments/get', 'POST', {
+ productId: productId,
+ // 尝试添加多个参数,明确请求所有评论
+ allComments: true,
+ includeAllUsers: true,
+ viewMode: 'all'
+ });
+ },
+
+ // 提交评论
+ submitComment: function (commentData) {
+ console.log('API.submitComment - 开始提交评论');
+ console.log('API.submitComment - 评论数据:', commentData);
+ console.log('API.submitComment - 请求URL:', '/api/comments/add');
+ console.log('API.submitComment - 请求方法:', 'POST');
+
+ return request('/api/comments/add', 'POST', commentData)
+ .then(res => {
+ console.log('API.submitComment - 请求成功响应:', res);
+ return res;
+ })
+ .catch(err => {
+ console.error('API.submitComment - 请求失败:', err);
+ throw err;
+ });
+ },
+
+ // 更新评论点赞数
+ updateCommentLike: function (data) {
+ console.log('API.updateCommentLike - data:', data);
+ return request('/api/comments/updateLike', 'POST', data);
+ },
+
+ // 更新评论点踩数
+ updateCommentHate: function (data) {
+ console.log('API.updateCommentHate - data:', data);
+ return request('/api/comments/updateHate', 'POST', data);
+ },
+
+ // 删除评论
+ deleteComment: function (commentId, currentUserPhone, commentPhone) {
+ console.log('API.deleteComment - 开始删除评论');
+ console.log('API.deleteComment - 评论ID:', commentId);
+ console.log('API.deleteComment - 当前用户手机号:', currentUserPhone);
+ console.log('API.deleteComment - 评论所属手机号:', commentPhone);
+ console.log('API.deleteComment - 请求URL:', '/api/comments/delete');
+ console.log('API.deleteComment - 请求方法:', 'POST');
+
+ return request('/api/comments/delete', 'POST', {
+ commentId: commentId,
+ currentUserPhone: currentUserPhone,
+ commentPhone: commentPhone
+ })
+ .then(res => {
+ console.log('API.deleteComment - 请求成功响应:', res);
+ return res;
+ })
+ .catch(err => {
+ console.error('API.deleteComment - 请求失败:', err);
+ throw err;
+ });
+ },
+
// 发布商品 - 支持图片上传(修复sellerId问题)
publishProduct: function (product) {
console.log('===== publishProduct调用开始 =====')
diff --git a/utils/time.js b/utils/time.js
index 1b1ef0f..157177a 100644
--- a/utils/time.js
+++ b/utils/time.js
@@ -1,110 +1,74 @@
-// UTC+8时间处理工具函数
-
-// 固定时间戳(北京时间:2025-12-19 17:10:35.186)
-const FIXED_UTC8_TIME = 1766135435186;
-let isFixedTimeMode = false; // 默认关闭固定时间模式
-
/**
- * 获取UTC+8时间戳
- * @param {Date|number} date - 可选,日期对象或时间戳
- * @returns {number} UTC+8时间戳
+ * 格式化日期时间为相对时间(如:刚刚、1分钟前、2小时前等)
+ * @param {string|Date} dateTime - 日期时间(可以是ISO字符串或Date对象)
+ * @returns {string} 格式化后的相对时间
*/
-function getUtc8Timestamp(date) {
- let baseTimestamp;
- if (isFixedTimeMode) {
- baseTimestamp = FIXED_UTC8_TIME;
- } else if (!date) {
- baseTimestamp = Date.now();
- } else if (typeof date === 'number') {
- baseTimestamp = date;
- } else {
- baseTimestamp = date.getTime();
+function formatRelativeTime(dateTime) {
+ if (!dateTime) {
+ return '刚刚';
}
- return baseTimestamp + 8 * 60 * 60 * 1000; // 手动加8小时
-}
-
-/**
- * 获取UTC+8 Date对象
- * @param {number} timestamp - 可选,时间戳
- * @returns {Date} UTC+8 Date对象
- */
-function getUtc8Date(timestamp) {
- const ts = isFixedTimeMode ? FIXED_UTC8_TIME : (timestamp || Date.now());
- const date = new Date(ts + 8 * 60 * 60 * 1000); // 手动加8小时
- return date;
-}
-
-/**
- * 将时间转换为UTC+8的ISO字符串(带时区信息)
- * @param {Date|number} date - 可选,日期对象或时间戳
- * @returns {string} ISO格式的UTC+8时间字符串
- */
-function toUtc8ISOString(date) {
- const timestamp = getUtc8Timestamp(date);
- const utcDate = new Date(timestamp);
- const year = utcDate.getUTCFullYear();
- const month = String(utcDate.getUTCMonth() + 1).padStart(2, '0');
- const day = String(utcDate.getUTCDate()).padStart(2, '0');
- const hours = String(utcDate.getUTCHours()).padStart(2, '0');
- const minutes = String(utcDate.getUTCMinutes()).padStart(2, '0');
- const seconds = String(utcDate.getUTCSeconds()).padStart(2, '0');
- const milliseconds = String(utcDate.getUTCMilliseconds()).padStart(3, '0');
+ // 确保输入是Date对象
+ const date = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
+ if (isNaN(date.getTime())) {
+ return '刚刚';
+ }
- return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}+08:00`;
-}
-
-/**
- * 将时间转换为UTC+8的本地字符串
- * @param {Date|number} date - 可选,日期对象或时间戳
- * @param {Object} options - 可选,格式化选项
- * @returns {string} 本地化的UTC+8时间字符串
- */
-function toUtc8String(date, options = {}) {
- const defaultOptions = {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: false
- // 移除timeZone设置,因为getUtc8Timestamp已经返回UTC+8时间戳
- };
+ // 计算时间差(毫秒)
+ const now = new Date();
+ const diff = now - date;
- const timestamp = getUtc8Timestamp(date);
- const utc8Date = new Date(timestamp);
+ // 转换为不同的时间单位
+ const seconds = Math.floor(diff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
- return utc8Date.toLocaleString('zh-CN', { ...defaultOptions, ...options });
-}
-
-/**
- * 设置固定时间模式
- * @param {boolean} mode - true启用,false禁用
- */
-function setFixedTimeMode(mode) {
- isFixedTimeMode = mode;
+ // 根据时间差返回不同的格式
+ if (seconds < 60) {
+ return '刚刚';
+ } else if (minutes < 60) {
+ return `${minutes}分钟前`;
+ } else if (hours < 24) {
+ return `${hours}小时前`;
+ } else if (days < 7) {
+ return `${days}天前`;
+ } else {
+ // 超过7天显示具体日期
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ }
}
/**
- * 获取固定时间模式状态
- * @returns {boolean} 当前固定时间模式状态
+ * 格式化日期为YYYY-MM-DD HH:MM:SS格式
+ * @param {string|Date} dateTime - 日期时间(可以是ISO字符串或Date对象)
+ * @returns {string} 格式化后的日期时间
*/
-function getFixedTimeMode() {
- return isFixedTimeMode;
+function formatDateTime(dateTime) {
+ if (!dateTime) {
+ return '';
+ }
+
+ // 确保输入是Date对象
+ const date = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
+ if (isNaN(date.getTime())) {
+ return '';
+ }
+
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
-// 导出函数
module.exports = {
- getUtc8Timestamp,
- getUtc8Date,
- toUtc8ISOString,
- toUtc8String,
- setFixedTimeMode,
- getFixedTimeMode,
- // 向后兼容别名
- getBeijingTimeTimestamp: getUtc8Timestamp,
- getBeijingTimeDate: getUtc8Date,
- toBeijingTimeISOString: toUtc8ISOString,
- toBeijingTimeString: toUtc8String
-};
\ No newline at end of file
+ formatRelativeTime,
+ formatDateTime
+};