Browse Source

feat: 实现完整的点赞功能和修复动态显示问题

pull/18/head
徐飞洋 1 month ago
parent
commit
91c872a1a0
  1. 59
      pages/eggbar/eggbar.js
  2. 6
      pages/eggbar/eggbar.wxml
  3. 255
      server-example/server-mysql.js
  4. 4
      utils/api.js

59
pages/eggbar/eggbar.js

@ -83,12 +83,25 @@ Page({
loading: true
});
API.getPosts({
// 获取用户电话号码
const userInfo = wx.getStorageSync('userInfo');
const phoneNumber = userInfo?.phoneNumber || wx.getStorageSync('phoneNumber');
// 只有当电话号码存在时才传递,避免传递空值
const params = {
page: this.data.page,
pageSize: this.data.pageSize
}).then(res => {
};
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 => {
@ -243,6 +256,20 @@ Page({
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 {
@ -256,10 +283,34 @@ Page({
this.setData({ posts });
API.likePost(postId).then(res => {
console.log('点赞成功');
// 调用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'
});
});
},

6
pages/eggbar/eggbar.wxml

@ -42,15 +42,15 @@
</view>
</view>
<view class="post-footer">
<view class="post-action" bindtap="likePost" data-id="{{item.id}}" catchtap="true">
<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}}" catchtap="true">
<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}}" catchtap="true">
<view class="post-action" bindtap="sharePost" data-id="{{item.id}}">
<text class="action-icon">📤</text>
<text class="action-text">分享</text>
</view>

255
server-example/server-mysql.js

@ -211,6 +211,9 @@ app.get('/api/eggbar/posts', async (req, res) => {
}
);
console.log('数据库查询结果数量:', posts.length);
console.log('数据库查询结果:', posts);
// 关闭临时连接
await tempSequelize.close();
@ -233,12 +236,32 @@ app.get('/api/eggbar/posts', async (req, res) => {
});
// 格式化响应数据
const formattedPosts = paginatedPosts.map(post => ({
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: post.images,
images: images,
topic: post.topic,
likes: post.likes || 0,
comments: post.comments || 0,
@ -246,7 +269,45 @@ app.get('/api/eggbar/posts', async (req, res) => {
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,
@ -422,16 +483,36 @@ app.post('/api/eggbar/upload', upload.single('image'), async (req, res) => {
});
}
const tempFilePath = req.file.path;
try {
// 使用OSS上传图片
const imageUrl = await OssUploader.uploadFile(req.file.path, 'eggbar', 'image');
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({
@ -441,6 +522,90 @@ app.post('/api/eggbar/upload', upload.single('image'), async (req, res) => {
}
});
// 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之后)
app.use((req, res, next) => {
// 使用统一的时间处理函数获取当前时间
@ -1642,6 +1807,37 @@ EggbarPost.init({
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
});
// 定义模型之间的关联关系
// 用户和商品的一对多关系 (卖家发布商品)
@ -2907,7 +3103,11 @@ app.post('/api/products/upload', upload.array('images', 10), async (req, res) =>
const uploadedFileUrls = new Set();
// 准备文件路径数组
const filePaths = uploadedFiles.map(file => file.path);
const filePaths = uploadedFiles.map(file => {
// 添加到临时文件清理列表
tempFilesToClean.push(file.path);
return file.path;
});
// 使用商品名称作为文件夹名,确保每个商品的图片独立存储
// 移除商品名称中的特殊字符,确保可以作为合法的文件夹名
@ -4081,15 +4281,30 @@ app.post('/api/products/upload', upload.array('images', 10), async (req, res) =>
code: 500,
error: err.message
});
} finally {
// 确保临时文件被清理
if (tempFilesToClean.length > 0) {
try {
cleanTempFiles(tempFilesToClean);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
}
});
// 【关键修复】在 handleAddImagesToExistingProduct 函数中加强图片合并逻辑
async function handleAddImagesToExistingProduct(req, res, existingProductId, uploadedFiles) {
let transaction;
let tempFilesToClean = [];
try {
console.log('【图片更新模式】开始处理图片上传到已存在商品,商品ID:', existingProductId);
// 收集需要清理的临时文件路径
for (const file of uploadedFiles) {
tempFilesToClean.push(file.path);
}
// 使用事务确保数据一致性
transaction = await sequelize.transaction();
@ -4247,6 +4462,15 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl
code: 500,
error: error.message
});
} finally {
// 确保临时文件被清理
if (tempFilesToClean.length > 0) {
try {
cleanTempFiles(tempFilesToClean);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
}
}
@ -7506,6 +7730,7 @@ app.post('/api/settlement/submit', async (req, res) => {
// 上传入驻文件
app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
let tempFilePath = null;
try {
const { openid, fileType } = req.body;
@ -7527,12 +7752,13 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
});
}
tempFilePath = req.file.path;
// 上传文件到OSS - 使用静态方法调用
// 注意: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是干净的字符串,移除可能存在的反引号和空格
const cleanFileUrl = String(fileUrl).replace(/[` ]/g, '');
@ -7549,16 +7775,21 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
} catch (error) {
console.error('入驻文件上传失败:', error);
// 清理临时文件
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
success: false,
code: 500,
message: '文件上传失败: ' + error.message
});
} finally {
// 确保临时文件被清理
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
console.log('临时文件已清理:', tempFilePath);
} catch (cleanupError) {
console.warn('清理临时文件时出错:', cleanupError);
}
}
}
});

4
utils/api.js

@ -4539,9 +4539,9 @@ module.exports = {
},
// 点赞动态
likePost: function(postId) {
likePost: function(postId, phone) {
return new Promise((resolve, reject) => {
request(`/api/eggbar/posts/${postId}/like`, 'POST')
request(`/api/eggbar/posts/${postId}/like`, 'POST', { phone: phone })
.then(response => {
console.log('点赞成功:', response);
resolve(response);

Loading…
Cancel
Save