const config = require('../../config/config.js'); const apiClient = require('../../utils/api-client.js'); const { getListImageUrl, getPreviewImageUrl, getOriginalImageUrl } = require('../../utils/image-url-optimizer.js'); Page({ data: { feedList: [], // 动态列表数据 userList: [], // 用户列表(从头像模块显示) page: 1, // 当前页码 pageSize: 5, // 每页加载数量 loading: false, // 是否正在加载 noMore: false, // 是否没有更多数据 currentYear: new Date().getFullYear(), // 当前年份 // 定位相关数据 latitude: null, longitude: null, radius: 30, modeFrom: '', feedUuid: '', pendingFeedUuid: '', scrollIntoFeedId: '', highlightFeedUuid: '', lastRedirectToken: '', // 评论弹窗相关 showCommentModal: false, // 是否显示评论弹窗 currentFeedUuid: '', // 当前评论的动态UUID currentFeedIndex: -1, // 当前评论的动态索引 currentComments: [], // 当前显示的评论列表 commentInputValue: '', // 评论输入内容 // 回复相关 replyingCommentId: null, // 当前正在回复的评论ID replyingCommentIndex: null, // 当前正在回复的评论索引(一级评论) replyingToCommentId: null, // 回复的目标评论ID(二级评论的父评论) replyInputValue: '', // 回复输入内容 showReplyInput: {}, // 控制每个评论的回复输入框显示状态 {commentId: true/false} submittingReply: false // 是否正在提交回复,防止重复点击 }, onLoad(options = {}) { this._skipNextOnShowReload = true; this.highlightTimer = null; const mapLatitude = parseFloat(options.latitude); const mapLongitude = parseFloat(options.longitude); const hasMapParams = !Number.isNaN(mapLatitude) && !Number.isNaN(mapLongitude); if (hasMapParams) { const parsedRadius = parseInt(options.radius, 10); const safeRadius = Number.isNaN(parsedRadius) ? this.data.radius : Math.max(10, parsedRadius); this._skipNextOnShowReload = true; this.setData({ latitude: mapLatitude, longitude: mapLongitude, radius: safeRadius, modeFrom: options.mode || 'map', feedUuid: options.feedUuid || '', pendingFeedUuid: options.feedUuid || '' }, () => { this.resetAndLoadFeedList(); }); return; } // 先获取定位,再加载数据 this.getLocation().then(() => { this.loadFeedList(); }).catch(() => {}); this.apiClient = apiClient; }, // 获取用户地理位置 getLocation() { return new Promise((resolve, reject) => { wx.getLocation({ type: 'gcj02', success: (res) => { this.setData({ latitude: res.latitude, longitude: res.longitude }); resolve(); // 定位成功,允许加载数据 }, fail: (err) => { wx.showToast({ title: '获取位置失败,无法加载附近动态', icon: 'none', duration: 2000 }); reject(err); } }); }); }, resetAndLoadFeedList() { if (!this.data.latitude || !this.data.longitude) { return; } this.setData({ page: 1, feedList: [], noMore: false, scrollIntoFeedId: '' }, () => { this.loadFeedList(); }); }, onShow() { // 🔥 设置tabBar选中状态为"圈子"(索引1) try { if (typeof this.getTabBar === 'function' && this.getTabBar()) { this.getTabBar().setData({ selected: 1 }); } } catch (_) {} const appInstance = getApp(); const redirectPayload = appInstance?.globalData?.mapFeedRedirect; if (redirectPayload && redirectPayload.token && redirectPayload.token !== this.data.lastRedirectToken) { appInstance.globalData.mapFeedRedirect = null; this.applyRedirectPayload(redirectPayload); return; } if (this._skipNextOnShowReload) { // 延迟清除标志,确保能捕获到预览关闭事件 setTimeout(() => { this._skipNextOnShowReload = false; }, 500); return; } // 页面显示时刷新数据 if (this.data.latitude && this.data.longitude) { this.resetAndLoadFeedList(); } }, // 加载动态列表 loadFeedList() { // 检查是否有定位信息 if (!this.data.latitude || !this.data.longitude) return; if (this.data.loading || this.data.noMore) return; this.setData({ loading: true }); // 通过字符串模板拼接带参数的URL const { page, pageSize, latitude, longitude } = this.data; // 查看所有动态 const fullRequestUrl = `${config.api.baseUrl}/api/v1/feeds?` + `type=timeline&` + `page=${page}&` + `pageSize=${pageSize}&` + `latitude=${latitude}&` + `longitude=${longitude}&` + `radius=30`; // 先检查URL是否有效 if (!fullRequestUrl || !fullRequestUrl.startsWith('http')) { console.error('无效的请求URL:', fullRequestUrl); this.setData({ loading: false }); wx.showToast({ title: '加载失败,请重试', icon: 'none' }); return; } // 调用接口 wx.request({ url: fullRequestUrl, method: 'GET', header: { 'Authorization': `Bearer ${apiClient.getToken() || ''}` }, success: (res) => { if (res.data.data?.feeds && res.data.data.feeds.length > 0) { if (res.data.data.feeds[0].media && res.data.data.feeds[0].media.length > 0) { } } this.setData({ loading: false }); // 🔥 统一处理401 - 使用apiClient的统一处理 if (apiClient.is401Error(res)) { const app = getApp(); const isLoggedIn = app?.globalData?.isLoggedIn || false; apiClient.handle401Error(isLoggedIn); return; } if (res.data.code !== 200) { wx.showToast({ title: res.data.message || '加载失败', icon: 'none' }); return; } const newFeeds = res.data.data.feeds || []; const processedFeeds = newFeeds.map((feed, feedIdx) => { // 时间格式化 let formattedTime = '未知时间'; try { formattedTime = this.formatTime(feed.createdAt || ''); } catch (error) { console.error('时间格式化失败:', error); } // 用户信息 const feedUser = feed.user || {}; console.log("当前动态的用户信息", feedUser) // 处理用户头像URL let validAvatar = feedUser.avatar || ''; if (validAvatar && !validAvatar.startsWith('http') && validAvatar.startsWith('/')) { try { const baseDomain = config.api.baseUrl.replace(/\/api\/v1$/, ''); validAvatar = `${baseDomain}${validAvatar}`; } catch (e) { console.error(`头像路径转换失败:`, e); } } if (validAvatar && validAvatar.startsWith('http://')) { validAvatar = validAvatar.replace('http://', 'https://'); } if (!validAvatar || (!validAvatar.startsWith('http://') && !validAvatar.startsWith('https://'))) { console.warn(`无效的头像URL,使用占位图:`, validAvatar); validAvatar = '/images/findme-logo.png'; } // 处理动态图片 let processedMedia = []; if (feed.media && Array.isArray(feed.media)) { processedMedia = feed.media.map((mediaItem, index) => { if (!mediaItem || typeof mediaItem !== 'object') { console.warn(`动态${feed.id || index}的图片无效:`, mediaItem); return { url: '/images/placeholder.svg', thumbnailUrl: '/images/placeholder.svg', type: 'image' }; } // 确保图片URL有效 let validUrl = mediaItem.url || mediaItem.src || ''; let validThumbnailUrl = mediaItem.thumbnailUrl || mediaItem.thumbnail || validUrl; if (validUrl && !validUrl.startsWith('http') && validUrl.startsWith('/')) { try { const baseDomain = config.api.baseUrl.replace(/\/api\/v1$/, ''); validUrl = `${baseDomain}${validUrl}`; } catch (e) { console.error(`图片路径转换失败:`, e); } } if (validUrl && validUrl.startsWith('http://')) { validUrl = validUrl.replace('http://', 'https://'); } if (!validUrl || (!validUrl.startsWith('http://') && !validUrl.startsWith('https://'))) { console.warn(`无效的图片URL,使用占位图:`, validUrl); validUrl = '/images/placeholder.svg'; } if (validThumbnailUrl && !validThumbnailUrl.startsWith('http') && validThumbnailUrl.startsWith('/')) { try { const baseDomain = config.api.baseUrl.replace(/\/api\/v1$/, ''); validThumbnailUrl = `${baseDomain}${validThumbnailUrl}`; } catch (e) {} } if (validThumbnailUrl && validThumbnailUrl.startsWith('http://')) { validThumbnailUrl = validThumbnailUrl.replace('http://', 'https://'); } if (!validThumbnailUrl || (!validThumbnailUrl.startsWith('http://') && !validThumbnailUrl.startsWith('https://'))) { validThumbnailUrl = validUrl; } // 优化图片URL,提高清晰度 // 列表显示使用中等质量,预览使用高质量 const optimizedUrl = getListImageUrl(validUrl, 750); const optimizedThumbnailUrl = getListImageUrl(validThumbnailUrl, 400); const originalUrl = getOriginalImageUrl(validUrl); // 保存原图URL用于预览 return { ...mediaItem, url: optimizedUrl, // 列表显示用优化后的URL thumbnailUrl: optimizedThumbnailUrl, originalUrl: originalUrl, // 保存原图URL type: mediaItem.type || 'image', loading: true // 初始状态为加载中 }; }).filter(Boolean); } else if (feed.images && Array.isArray(feed.images)) { processedMedia = feed.images.map(imageUrl => { // 优化图片URL,提高清晰度 const optimizedUrl = getListImageUrl(imageUrl, 750); const originalUrl = getOriginalImageUrl(imageUrl); return { url: optimizedUrl, thumbnailUrl: optimizedUrl, originalUrl: originalUrl, type: 'image', loading: true // 初始状态为加载中 }; }); } // 处理评论数据,格式化评论时间,组织嵌套结构 let processedComments = []; if (feed.comments && Array.isArray(feed.comments) && feed.comments.length > 0) { // 获取当前用户ID,用于判断是否是自己的评论 const app = getApp(); const currentUser = app.globalData.userInfo?.user || {}; const currentUserId = currentUser.id || currentUser.userId || currentUser.customId || ''; // 分离一级评论和回复 const topLevelComments = []; // 一级评论(没有replyToId的) const repliesList = []; // 所有回复(有replyToId的) feed.comments.forEach((item, idx) => { // 判断是否是当前用户的评论 const itemUserId = item.userId || item.user?.id || item.user?.userId || ''; const isOwn = currentUserId && itemUserId && currentUserId.toString() === itemUserId.toString(); // 确保user对象存在,如果不存在则创建默认值 const user = item.user || {}; if (!user.nickname && item.userName) { user.nickname = item.userName; } if (!user.nickname) { user.nickname = '未知用户'; } if (!item.replyToId) { // 一级评论 topLevelComments.push({ ...item, id: item.id || item.uuid || `comment_${feedIdx}_${idx}`, formattedTime: item.formattedTime || this.formatCommentTime(item.createdAt || ''), replies: [], visibleReplyCount: 5, // 默认显示5条回复 isOwn: isOwn, user: user // 确保user对象被绑定 }); } else { // 回复 repliesList.push({ ...item, id: item.id || item.uuid || `reply_${feedIdx}_${idx}`, formattedTime: item.formattedTime || this.formatCommentTime(item.createdAt || ''), isOwn: isOwn, user: user // 确保user对象被绑定 }); } }); // 将回复组织到对应的一级评论下(支持多级嵌套) // 使用多次遍历的方式,确保所有回复都能正确找到父节点 let remainingReplies = [...repliesList]; let maxIterations = 10; // 防止无限循环 let iteration = 0; while (remainingReplies.length > 0 && iteration < maxIterations) { iteration++; const newRemainingReplies = []; remainingReplies.forEach(reply => { // 先尝试在一级评论中查找 const parentComment = topLevelComments.find(c => c.id === reply.replyToId); if (parentComment) { // 回复一级评论 if (!parentComment.replies) { parentComment.replies = []; } // 设置 replyToUser(如果父评论有 user 对象) if (parentComment.user) { reply.replyToUser = parentComment.user; } parentComment.replies.push(reply); } else { // 尝试在所有已处理的回复中查找父回复 let found = false; for (let comment of topLevelComments) { if (comment.replies && comment.replies.length > 0) { const targetReply = comment.replies.find(r => r.id === reply.replyToId); if (targetReply) { // 找到父回复,设置 replyToUser 并添加到同一评论的 replies 中 if (targetReply.user) { reply.replyToUser = targetReply.user; } // 在父回复后插入 const targetIndex = comment.replies.findIndex(r => r.id === reply.replyToId); if (targetIndex >= 0) { comment.replies.splice(targetIndex + 1, 0, reply); } else { comment.replies.push(reply); } found = true; break; } } } if (!found) { // 如果找不到父节点,可能是父节点还未处理,留到下一轮 newRemainingReplies.push(reply); } } }); remainingReplies = newRemainingReplies; // 如果这一轮没有处理任何回复,说明有循环依赖或找不到父节点,跳出 if (remainingReplies.length === repliesList.length && iteration > 1) { break; } } // 如果还有剩余回复(找不到父节点),作为第一个评论的回复(兜底处理) if (remainingReplies.length > 0 && topLevelComments.length > 0) { if (!topLevelComments[0].replies) { topLevelComments[0].replies = []; } topLevelComments[0].replies.push(...remainingReplies); } // 对一级评论按时间倒序排序(最新的在前),确保与本地添加评论的顺序一致 topLevelComments.sort((a, b) => { const timeA = new Date(a.createdAt || 0).getTime(); const timeB = new Date(b.createdAt || 0).getTime(); return timeB - timeA; // 倒序:最新在前 }); // 对每个评论的回复也按时间倒序排序 topLevelComments.forEach(comment => { if (comment.replies && comment.replies.length > 0) { comment.replies.sort((a, b) => { const timeA = new Date(a.createdAt || 0).getTime(); const timeB = new Date(b.createdAt || 0).getTime(); return timeB - timeA; // 倒序:最新在前 }); } }); processedComments = topLevelComments; } return { ...feed, user: { ...feedUser, avatar: validAvatar, nickname: feedUser.nickname || feedUser.customId || feedUser.phone || '未知用户', }, formattedTime: formattedTime, media: processedMedia, comments: processedComments || [], // 确保是数组 visibleCommentCount: feed.visibleCommentCount || 20 // 默认显示20条评论,如果已存在则保留 }; }); // 排序动态 const sortedFeeds = this.sortFeeds(processedFeeds); // 去重处理 let finalFeeds = sortedFeeds; if (this.data.page !== 1 && this.data.feedList && this.data.feedList.length > 0) { // 获取现有动态ID的集合 const existingIds = new Set(this.data.feedList.map(item => item.id || item.uuid || item.dynamicId)); // 过滤掉已经存在的动态 finalFeeds = sortedFeeds.filter(feed => { const feedId = feed.id || feed.uuid || feed.dynamicId; // 如果没有ID或ID不存在于现有集合中保留动态 return !feedId || !existingIds.has(feedId); }); } const feedsToSet = finalFeeds.filter(feed => feed && typeof feed === 'object'); const updatedFeedList = this.data.page === 1 ? feedsToSet : [...this.data.feedList, ...feedsToSet]; const pendingFeedUuid = this.data.pendingFeedUuid; const foundTarget = pendingFeedUuid ? updatedFeedList.some(feed => { const feedId = feed.uuid || feed.id || feed.dynamicId; return feedId && feedId === pendingFeedUuid; }) : false; // 🔥 提取唯一用户列表 const uniqueUsers = this.extractUniqueUsers(updatedFeedList); this.setData({ feedList: updatedFeedList, userList: uniqueUsers, page: this.data.page + 1, noMore: !res.data.data.hasMore, loading: false }, () => { this.afterFeedListUpdate(foundTarget, !res.data.data.hasMore); }); }, fail: (err) => { console.error(`=== 接口请求失败(${fullRequestUrl}) ===`, err); this.setData({ loading: false }); wx.showToast({ title: '加载失败,请检查网络', icon: 'none' }); } }); }, // 🔥 从动态列表中提取唯一用户 extractUniqueUsers(feedList) { const userMap = new Map(); if (!feedList || !Array.isArray(feedList)) { return []; } feedList.forEach(feed => { if (feed.user && feed.user.customId) { const customId = feed.user.customId; // 如果用户不存在或者当前动态更新的用户信息更完整,则更新 if (!userMap.has(customId) || (!userMap.get(customId).avatar && feed.user.avatar) || (!userMap.get(customId).nickname && feed.user.nickname)) { userMap.set(customId, { customId: customId, avatar: feed.user.avatar || '/images/default-avatar.png', nickname: feed.user.nickname || feed.user.customId || '用户' }); } } }); return Array.from(userMap.values()); }, afterFeedListUpdate(foundTarget, noMore) { const pendingUuid = this.data.pendingFeedUuid; if (!pendingUuid) { return; } if (foundTarget) { const targetFeed = this.data.feedList.find(feed => { const feedId = feed.uuid || feed.id || feed.dynamicId; return feedId && feedId === pendingUuid; }); if (targetFeed) { const targetId = targetFeed.uuid || targetFeed.id || targetFeed.dynamicId; const anchorId = `feed-item-${targetId}`; this.setData({ scrollIntoFeedId: anchorId, highlightFeedUuid: targetId, pendingFeedUuid: '' }); if (this.highlightTimer) { clearTimeout(this.highlightTimer); } this.highlightTimer = setTimeout(() => { this.setData({ highlightFeedUuid: '', scrollIntoFeedId: '' }); }, 4000); } return; } if (!noMore) { this.loadFeedList(); } else { this.setData({ pendingFeedUuid: '' }); } }, applyRedirectPayload(payload) { const safeLatitude = parseFloat(payload.latitude); const safeLongitude = parseFloat(payload.longitude); if (Number.isNaN(safeLatitude) || Number.isNaN(safeLongitude)) { return; } const safeRadius = Math.max(10, parseInt(payload.radius, 10) || this.data.radius); const feedUuid = payload.feedUuid || ''; const redirectToken = payload.token || `${Date.now()}`; this.setData({ latitude: safeLatitude, longitude: safeLongitude, radius: safeRadius, modeFrom: payload.mode || 'map', feedUuid, pendingFeedUuid: feedUuid, lastRedirectToken: redirectToken }, () => { this.resetAndLoadFeedList(); }); }, // 排序动态 sortFeeds(feeds) { const currentUser = getApp().globalData.userInfo || {}; const currentGender = currentUser.gender !== undefined ? currentUser.gender : 0; return [...feeds].sort((a, b) => { // 确保用户信息存在 const aUser = a.user || {}; const bUser = b.user || {}; const isAFriend = aUser.isFriend || false; const isBFriend = bUser.isFriend || false; // 好友优先级高于非好友 if (isAFriend && !isBFriend) return -1; if (!isAFriend && isBFriend) return 1; // 好友/非好友分组内排序 const aCreateTime = new Date(a.createdAt || 0).getTime(); const bCreateTime = new Date(b.createdAt || 0).getTime(); if (isAFriend && isBFriend) { // 好友:按发布时间倒序 return bCreateTime - aCreateTime; } else { // 非好友:优先异性,再按时间倒序 const aGender = aUser.gender !== undefined ? aUser.gender : currentGender; const bGender = bUser.gender !== undefined ? bUser.gender : currentGender; const isAOpposite = aGender !== currentGender; const isBOpposite = bGender !== currentGender; if (isAOpposite && !isBOpposite) return -1; if (!isAOpposite && isBOpposite) return 1; // 同性别:按时间倒序 return bCreateTime - aCreateTime; } }); }, // 格式化时间 formatTime(timeStr) { if (!timeStr) return '未知时间'; const createTime = new Date(timeStr); if (isNaN(createTime.getTime())) return '未知时间'; const now = new Date(); const diffMinutes = Math.floor((now - createTime) / (1000 * 60)); // 5分钟内:刚刚 if (diffMinutes < 5) return '刚刚'; // 格式化日期时间 const year = createTime.getFullYear(); const month = String(createTime.getMonth() + 1).padStart(2, '0'); const day = String(createTime.getDate()).padStart(2, '0'); const hour = String(createTime.getHours()).padStart(2, '0'); const minute = String(createTime.getMinutes()).padStart(2, '0'); // 跨年份显示完整日期,同年份省略年份 return year === this.data.currentYear ? `${month}月${day}日 ${hour}:${minute}` : `${year}年${month}月${day}日 ${hour}:${minute}`; }, // 格式化评论时间 formatCommentTime(timeStr) { if (!timeStr) return ''; const commentTime = new Date(timeStr); if (isNaN(commentTime.getTime())) return ''; const now = new Date(); const diffMinutes = Math.floor((now - commentTime) / (1000 * 60)); // 5分钟内:显示"刚刚" if (diffMinutes < 5) return '刚刚'; // 格式化日期时间 const year = commentTime.getFullYear(); const month = commentTime.getMonth() + 1; // 不加前导0,直接显示月份 const day = commentTime.getDate(); const hour = String(commentTime.getHours()).padStart(2, '0'); const minute = String(commentTime.getMinutes()).padStart(2, '0'); // 当前年份:显示不带年份的日期和时间,例如:8月24日 17:11 // 非当前年份:显示带年份的日期和时间,例如:2024年 8月24日 17:11 const currentYear = now.getFullYear(); return year === currentYear ? `${month}月${day}日 ${hour}:${minute}` : `${year}年 ${month}月${day}日 ${hour}:${minute}`; }, // 滚动到底部加载更多 onReachBottom() { this.loadFeedList(); }, // 返回首页功能 navigateBackHome() { wx.switchTab({ url: '/pages/map/map' }); }, handlePost() { // 跳转到发布页面 wx.navigateTo({ url: '/subpackages/media/edits/edits', fail: (err) => { console.error('跳转失败:', err); wx.showToast({ title: '跳转发布页面失败', icon: 'none' }); } }); }, onHide() { if (this.highlightTimer) { clearTimeout(this.highlightTimer); this.highlightTimer = null; } }, onUnload() { if (this.highlightTimer) { clearTimeout(this.highlightTimer); this.highlightTimer = null; } }, // 图片预览 previewImage(e) { try { // 获取当前点击的图片索引和动态索引 const { index, feedIndex } = e.currentTarget.dataset; // 获取当前动态的图片列表 const feed = this.data.feedList[feedIndex]; if (!feed || !feed.media || !feed.media.length) { console.error('没有找到媒体数据'); return; } // 优先使用原始图片URL或高质量图片,确保预览完整清晰的图片 const imageUrls = feed.media .filter(item => item.type === 'image' && (item.originalUrl || item.url)) .map(item => { // 如果有原图URL,使用原图;否则使用高质量优化URL if (item.originalUrl) { return getPreviewImageUrl(item.originalUrl); } return getPreviewImageUrl(item.url); }); if (!imageUrls.length) { console.error('没有有效的图片URL'); return; } // 🔥 设置标志,防止预览关闭后触发页面刷新 this._skipNextOnShowReload = true; // 调用微信小程序的图片预览API wx.previewImage({ current: imageUrls[index], // 当前显示图片的URL urls: imageUrls, // 需要预览的图片URL列表 success: () => { // 预览打开成功,标志已设置,关闭时会触发 onShow }, fail: (err) => { console.error('图片预览失败:', err); // 预览失败,清除标志 this._skipNextOnShowReload = false; wx.showToast({ title: '预览图片失败', icon: 'none' }); } }); } catch (error) { console.error('图片预览过程中出错:', error); // 出错时清除标志 this._skipNextOnShowReload = false; wx.showToast({ title: '预览图片失败', icon: 'none' }); } }, // 图片加载成功 onImageLoad(e) { const { index, feedIndex } = e.currentTarget.dataset; const feedList = this.data.feedList; if (feedList[feedIndex] && feedList[feedIndex].media && feedList[feedIndex].media[index]) { // 更新对应图片的加载状态 const updatePath = `feedList[${feedIndex}].media[${index}].loading`; this.setData({ [updatePath]: false }); } }, // 图片加载失败 onImageError(e) { const { index, feedIndex } = e.currentTarget.dataset; const feedList = this.data.feedList; if (feedList[feedIndex] && feedList[feedIndex].media && feedList[feedIndex].media[index]) { // 加载失败也隐藏 loading const updatePath = `feedList[${feedIndex}].media[${index}].loading`; this.setData({ [updatePath]: false }); } }, // 点击头像进入个人资料 navigateToUserProfile(e) { const customId = e.currentTarget.dataset.customId; if (!customId) return; // 获取当前用户信息 const currentUser = getApp().globalData.userInfo || {}; const currentUserId = currentUser.user.customId || ''; // 检查是否是当前用户自己 if (customId === currentUserId) { this.navigateToSelfProfile(customId); return; } // 使用friendAPI判断是否是好友关系 this.checkFriendRelation(customId); }, // 跳转到个人主页 navigateToSelfProfile(customId) { const targetUrl = `/subpackages/profile/profile/profile?customId=${customId}`; wx.navigateTo({ url: targetUrl, fail: (err) => { console.error('跳转个人主页失败:', err); wx.showToast({ title: '跳转失败,请重试', icon: 'none' }); } }); }, // 检查好友关系 async checkFriendRelation(customId) { try { const friendAPI = require('../../utils/friend-api.js'); // 显示加载提示 wx.showLoading({ title: '加载中...', mask: true }); // 获取好友详情 const friendDetailResponse = await friendAPI.getFriendDetail(customId); wx.hideLoading(); if (friendDetailResponse && friendDetailResponse.code === 0 && friendDetailResponse.data){ // 成功获取好友详情,说明是好友关系 this.navigateToFriendProfile(customId); return; } // 跳转到陌生人主页 this.navigateToStrangerProfile(customId); } catch (error) { wx.hideLoading(); console.error('检查好友关系失败:', error); // 跳转到陌生人主页 this.navigateToStrangerProfile(customId); } }, // 跳转到好友主页 navigateToFriendProfile(customId) { const targetUrl = `/subpackages/social/friend-detail/friend-detail?customId=${customId}`; wx.navigateTo({ url: targetUrl, fail: (err) => { console.error('跳转好友主页失败:', err); wx.showToast({ title: '跳转失败,请重试', icon: 'none' }); } }); }, // 跳转到陌生人主页 navigateToStrangerProfile(customId) { const targetUrl = `/subpackages/social/user-preview/user-preview?customId=${customId}`; wx.navigateTo({ url: targetUrl, fail: (err) => { console.error('跳转陌生人主页失败:', err); wx.showToast({ title: '跳转失败,请重试', icon: 'none' }); } }); }, // 处理点赞 async handleLike(e) { const feedUuid = e.currentTarget.dataset.feedUuid; const isLiked = e.currentTarget.dataset.isliked; const feedIndex = e.currentTarget.dataset.feedIndex; // 获取当前动态索引 if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } // 检查登录状态 const app = getApp(); if (!app.globalData.isLoggedIn) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.navigateTo({ url: '/pages/login/login' }); }, 1500); return; } // TODO: 调用点赞API console.log('点赞动态:', feedUuid); if(!isLiked){ const response = await this.apiClient.addLikeDynamic(feedUuid); if(response){ if(response.code==200){ wx.showToast({ title: response.data.message, icon: 'success', duration: 1000 }); }else{ wx.showToast({ title: response.message, icon: 'none', duration: 1000 }); } } // 更新点赞状态和数量 this.updateLikeStatus(feedIndex, true); }else{ const responseDelete= await this.apiClient.deleteLikeDynamic(feedUuid); if(responseDelete){ if(responseDelete.code==200){ wx.showToast({ title: responseDelete.data.message, icon: 'success', duration: 1000 }); }else{ wx.showToast({ title: responseDelete.message, icon: 'none', duration: 1000 }); } } // 更新点赞状态和数量 this.updateLikeStatus(feedIndex, false); } }, // 新增更新点赞状态的方法 updateLikeStatus(feedIndex, isLiked) { // 复制当前的动态列表 const feedList = [...this.data.feedList]; // 检查当前动态是否存在 if (feedList[feedIndex]) { // 初始化interactions对象(防止undefined错误) if (!feedList[feedIndex].interactions) { feedList[feedIndex].interactions = { likeCount: 0 }; } // 更新点赞状态 feedList[feedIndex].isLiked = isLiked; // 更新点赞数量(点赞+1,取消点赞-1) feedList[feedIndex].interactions.likeCount = (feedList[feedIndex].interactions.likeCount || 0) + (isLiked ? 1 : -1); // 确保数量不会小于0 if (feedList[feedIndex].interactions.likeCount < 0) { feedList[feedIndex].interactions.likeCount = 0; } // 更新数据 this.setData({ feedList: feedList }); } }, // 处理评论 - 显示评论弹窗 async handleComment(e) { const feedUuid = e.currentTarget.dataset.feedUuid; const feedIndex = e.currentTarget.dataset.feedIndex; if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } // 检查登录状态 const app = getApp(); if (!app.globalData.isLoggedIn) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.navigateTo({ url: '/pages/login/login' }); }, 1500); return; } // 显示弹窗并加载评论 this.setData({ showCommentModal: true, currentFeedUuid: feedUuid, currentFeedIndex: feedIndex, commentInputValue: '' }); // 加载评论列表 await this.loadComments(feedUuid, feedIndex); }, // 加载评论列表 async loadComments(feedUuid, feedIndex) { try { // 先尝试从feed数据中获取评论 const feed = this.data.feedList[feedIndex]; let comments = feed?.comments || []; // 如果有评论数据,格式化时间 if (comments && comments.length > 0) { comments = comments.map(comment => { return { ...comment, formattedTime: comment.formattedTime || this.formatCommentTime(comment.createdAt || '') }; }); } // 如果没有评论数据,尝试从API获取(如果有获取评论的API) // TODO: 如果需要从API获取评论列表,在这里添加 this.setData({ currentComments: comments }); } catch (error) { console.error('加载评论失败:', error); this.setData({ currentComments: [] }); } }, // 关闭评论弹窗 closeCommentModal() { this.setData({ showCommentModal: false, currentFeedUuid: '', currentFeedIndex: -1, currentComments: [], commentInputValue: '' }); }, // 防止点击内容区域关闭弹窗 preventClose() { // 空函数,阻止事件冒泡 }, // 评论输入 onCommentInput(e) { this.setData({ commentInputValue: e.detail.value }); }, // 提交评论 async submitComment() { const { currentFeedUuid, currentFeedIndex, commentInputValue } = this.data; if (!commentInputValue || !commentInputValue.trim()) { wx.showToast({ title: '请输入评论内容', icon: 'none', duration: 1500 }); return; } // 获取当前用户信息 const app = getApp(); const currentUser = app.globalData.userInfo?.user || {}; const nickname = currentUser.nickname || currentUser.customId || '未知用户'; try { const response = await this.apiClient.addCommentDynamic(currentFeedUuid, commentInputValue.trim(), null, nickname); if (response && response.code === 200) { wx.showToast({ title: '评论成功', icon: 'success', duration: 1000 }); // 创建新评论对象(新发布的评论默认是自己的) const newComment = { id: response.data?.id || `comment_${Date.now()}`, content: commentInputValue.trim(), createdAt: response.data?.createdAt || new Date().toISOString(), user: { nickname: currentUser.nickname || currentUser.customId || '未知用户', avatar: currentUser.avatar || '/images/findme-logo.png', customId: currentUser.customId || '' }, formattedTime: this.formatCommentTime(response.data?.createdAt || new Date().toISOString()), replies: [], isOwn: true // 新发布的评论默认是自己的 }; // 清空输入框 this.setData({ commentInputValue: '' }); // 更新动态的评论列表和数量 - 使用路径更新方式避免渲染错误 const feed = this.data.feedList[currentFeedIndex]; if (feed) { // 初始化 comments 数组(如果不存在) const currentComments = feed.comments || []; const updatedComments = [newComment, ...currentComments]; // 更新评论数量 const currentCommentCount = feed.interactions?.commentCount || 0; const visibleCommentCount = feed.visibleCommentCount || 5; // 使用路径更新方式,确保小程序能正确检测数据变化 const updatePath = {}; updatePath[`feedList[${currentFeedIndex}].comments`] = updatedComments; updatePath[`feedList[${currentFeedIndex}].interactions.commentCount`] = currentCommentCount + 1; // 确保 visibleCommentCount 存在且至少为20 if (!feed.visibleCommentCount || feed.visibleCommentCount < 20) { updatePath[`feedList[${currentFeedIndex}].visibleCommentCount`] = 20; } this.setData(updatePath); } // 更新弹窗中的评论列表 await this.loadComments(currentFeedUuid, currentFeedIndex); // 延迟关闭评论弹窗,让用户看到成功提示 setTimeout(() => { this.closeCommentModal(); }, 800); } else { wx.showToast({ title: response?.message || '评论失败', icon: 'none', duration: 1500 }); } } catch (error) { console.error('提交评论失败:', error); wx.showToast({ title: '评论失败,请重试', icon: 'none', duration: 1500 }); } }, // 删除评论 async deleteComment(e) { const feedUuid = e.currentTarget.dataset.feedUuid; const commentId = e.currentTarget.dataset.feedCommentId; if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } // 检查登录状态 const app = getApp(); if (!app.globalData.isLoggedIn) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.navigateTo({ url: '/pages/login/login' }); }, 1500); return; } const response = await this.apiClient.deleteCommentDynamic(feedUuid,commentId); if(response){ if(response.code==200){ wx.showToast({ title: "删除成功", icon: 'success', duration: 1000 }); }else{ wx.showToast({ title: "删除失败", icon: 'none', duration: 1000 }); } } }, // 展开更多评论(每次增加20条,懒加载模式) expandComments(e) { const feedIndex = e.currentTarget.dataset.feedIndex; this.loadMoreComments(feedIndex); }, // 加载更多评论(懒加载,每次加载20条) loadMoreComments(feedIndex) { const feed = this.data.feedList[feedIndex]; if (feed && feed.comments) { const currentCount = feed.visibleCommentCount || 20; // 当前显示的评论数量 const totalCount = feed.comments.length; // 评论总数 // 如果已经显示全部,不继续加载 if (currentCount >= totalCount) { return; } // 每次增加20条,但不超过总数 // 例如:初始20条 -> 滚动后40条 -> 再滚动60条 -> 以此类推 const newCount = Math.min(currentCount + 20, totalCount); if (newCount > currentCount) { // 使用路径更新方式 this.setData({ [`feedList[${feedIndex}].visibleCommentCount`]: newCount }); } } }, // 评论区域滚动到底部时触发(懒加载评论和回复) onCommentScrollToLower(e) { const feedIndex = e.currentTarget.dataset.feedIndex; if (feedIndex !== undefined && feedIndex !== null) { const feed = this.data.feedList[feedIndex]; // 先尝试加载更多回复(优先处理回复) if (feed && feed.comments) { let hasMoreReplies = false; feed.comments.forEach((comment, commentIndex) => { if (comment.replies && comment.replies.length > 0) { const currentCount = comment.visibleReplyCount || 5; const totalCount = comment.replies.length; // 如果有未显示的回复,自动加载更多 if (currentCount < totalCount) { const newCount = Math.min(currentCount + 20, totalCount); if (newCount > currentCount) { this.setData({ [`feedList[${feedIndex}].comments[${commentIndex}].visibleReplyCount`]: newCount }); hasMoreReplies = true; } } } }); // 如果有加载了回复,不继续加载评论(避免一次性加载太多) if (hasMoreReplies) { return; } } // 如果没有更多回复需要加载,则加载更多评论 this.loadMoreComments(feedIndex); } }, // 展开更多回复(每次增加20条) expandReplies(e) { const feedIndex = e.currentTarget.dataset.feedIndex; const commentIndex = e.currentTarget.dataset.commentIndex; const feed = this.data.feedList[feedIndex]; if (feed && feed.comments && feed.comments[commentIndex]) { const comment = feed.comments[commentIndex]; const currentCount = comment.visibleReplyCount || 5; // 当前显示的回复数量(默认5条) const totalCount = comment.replies ? comment.replies.length : 0; // 回复总数 // 每次增加20条,但不超过总数 // 例如:初始5条 -> 点击后25条 -> 再点击45条 -> 以此类推 const newCount = Math.min(currentCount + 20, totalCount); if (newCount > currentCount) { // 使用路径更新方式 this.setData({ [`feedList[${feedIndex}].comments[${commentIndex}].visibleReplyCount`]: newCount }); } } }, // 点击回复按钮 handleReplyClick(e) { const feedIndex = e.currentTarget.dataset.feedIndex; const commentId = e.currentTarget.dataset.commentId; const commentIndex = e.currentTarget.dataset.commentIndex; const replyId = e.currentTarget.dataset.replyId; // 如果是回复二级评论,这是被回复的二级评论ID const replyToUserName = e.currentTarget.dataset.replyToUser || ''; // 生成唯一key:如果有replyId,说明是回复二级回复,key应该包含replyId const replyKey = replyId ? `feed_${feedIndex}_comment_${commentId}_reply_${replyId}` : `feed_${feedIndex}_comment_${commentId}`; // 切换回复输入框显示状态 const showReplyInput = { ...this.data.showReplyInput }; // 如果当前输入框已显示,则关闭;否则打开 if (showReplyInput[replyKey]) { showReplyInput[replyKey] = false; this.setData({ showReplyInput: showReplyInput, replyInputValue: '', replyingCommentId: null, replyingCommentIndex: null, replyingToCommentId: null }); } else { // 关闭其他所有输入框 Object.keys(showReplyInput).forEach(key => { showReplyInput[key] = false; }); // 打开当前输入框 showReplyInput[replyKey] = true; // 获取feedUuid const feed = this.data.feedList[feedIndex]; const feedUuid = feed ? (feed.uuid || feed.id || feed.dynamicId) : ''; // 如果有replyId,说明是回复二级评论,需要设置replyToId this.setData({ showReplyInput: showReplyInput, replyInputValue: replyToUserName ? `@${replyToUserName} ` : '', replyingCommentId: commentId, replyingCommentIndex: commentIndex, replyingToCommentId: replyId || null, currentFeedIndex: feedIndex, currentFeedUuid: feedUuid }); } }, // 回复输入 onReplyInput(e) { this.setData({ replyInputValue: e.detail.value }); }, // 提交回复 async submitReply(e) { // 防止重复点击 if (this.data.submittingReply) { return; } const feedIndex = e.currentTarget.dataset.feedIndex; const commentId = e.currentTarget.dataset.commentId; const commentIndex = e.currentTarget.dataset.commentIndex; const { replyInputValue, replyingToCommentId } = this.data; // 移除 @用户名 前缀后,检查剩余内容是否为空 const contentWithoutMention = replyInputValue ? replyInputValue.trim().replace(/^@[\S]+\s+/, '') : ''; if (!replyInputValue || !replyInputValue.trim() || !contentWithoutMention) { wx.showToast({ title: '请输入回复内容', icon: 'none', duration: 1500 }); return; } // 设置提交状态 this.setData({ submittingReply: true }); // 获取feedUuid const feed = this.data.feedList[feedIndex]; if (!feed) { wx.showToast({ title: '动态不存在', icon: 'none' }); return; } const feedUuid = feed.uuid || feed.id || feed.dynamicId; if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } // 检查登录状态并获取当前用户信息 const app = getApp(); if (!app.globalData.isLoggedIn) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.navigateTo({ url: '/pages/login/login' }); }, 1500); return; } // 获取当前用户信息 const currentUser = app.globalData.userInfo?.user || {}; const nickname = currentUser.nickname || currentUser.customId || '未知用户'; try { // 调用API提交回复(如果replyingToCommentId存在,说明是回复二级评论) const replyToId = replyingToCommentId || commentId; const response = await this.apiClient.addCommentDynamic(feedUuid, replyInputValue.trim(), replyToId, nickname); if (response && response.code === 200) { wx.showToast({ title: '回复成功', icon: 'success', duration: 1000 }); // 创建新回复对象(新发布的回复默认是自己的) const newReply = { id: response.data?.id || `reply_${Date.now()}`, content: replyInputValue.trim().replace(/^@[\S]+\s+/, ''), // 移除@用户名前缀 createdAt: response.data?.createdAt || new Date().toISOString(), user: { nickname: currentUser.nickname || currentUser.customId || '未知用户', avatar: currentUser.avatar || '/images/findme-logo.png', customId: currentUser.customId || '' }, formattedTime: this.formatCommentTime(response.data?.createdAt || new Date().toISOString()), replyToId: replyToId, replyToUser: replyingToCommentId ? this.findReplyUser(this.data.feedList[feedIndex], commentId, replyingToCommentId) : null, isOwn: true // 新发布的回复默认是自己的 }; // 更新动态的评论列表 - 使用路径更新方式 const feed = this.data.feedList[feedIndex]; if (feed && feed.comments && feed.comments[commentIndex]) { const comment = feed.comments[commentIndex]; // 初始化 replies 数组(如果不存在) const currentReplies = comment.replies || []; const updatedReplies = [newReply, ...currentReplies]; // 更新评论数量 const currentCommentCount = feed.interactions?.commentCount || 0; // 使用路径更新方式,确保小程序能正确检测数据变化 const updatePath = {}; updatePath[`feedList[${feedIndex}].comments[${commentIndex}].replies`] = updatedReplies; updatePath[`feedList[${feedIndex}].interactions.commentCount`] = currentCommentCount + 1; // 如果当前显示的回复数少于5条,增加到5条以确保能看到新回复 const currentVisibleReplyCount = comment.visibleReplyCount || 5; if (currentVisibleReplyCount < 5) { updatePath[`feedList[${feedIndex}].comments[${commentIndex}].visibleReplyCount`] = 5; } // 清空回复输入框和状态 // 根据是否有replyingToCommentId来生成正确的key const currentReplyingToCommentId = this.data.replyingToCommentId; const replyKey = currentReplyingToCommentId ? `feed_${feedIndex}_comment_${commentId}_reply_${currentReplyingToCommentId}` : `feed_${feedIndex}_comment_${commentId}`; const showReplyInput = { ...this.data.showReplyInput }; showReplyInput[replyKey] = false; updatePath.showReplyInput = showReplyInput; updatePath.replyInputValue = ''; updatePath.replyingCommentId = null; updatePath.replyingCommentIndex = null; updatePath.replyingToCommentId = null; updatePath.submittingReply = false; this.setData(updatePath); } else { // 如果找不到对应数据,只更新状态 this.setData({ submittingReply: false }); } } else { this.setData({ submittingReply: false // 恢复提交状态 }); wx.showToast({ title: response?.message || '回复失败', icon: 'none', duration: 1500 }); } } catch (error) { console.error('提交回复失败:', error); this.setData({ submittingReply: false // 恢复提交状态 }); wx.showToast({ title: '回复失败,请重试', icon: 'none', duration: 1500 }); } }, // 查找被回复的用户信息 findReplyUser(feed, commentId, replyId) { if (!feed || !feed.comments) return null; const comment = feed.comments.find(c => c.id === commentId); if (!comment || !comment.replies) return null; const reply = comment.replies.find(r => r.id === replyId); return reply ? reply.user : null; }, // 删除评论 async deleteComment(e) { const { feedIndex, commentId, commentIndex } = e.currentTarget.dataset; // 检查参数(feedIndex可能是0,所以不能用!feedIndex判断) if (feedIndex === undefined || feedIndex === null || commentId === undefined || commentId === null || commentIndex === undefined || commentIndex === null) { console.error('删除评论参数错误:', { feedIndex, commentId, commentIndex, dataset: e.currentTarget.dataset }); wx.showToast({ title: '参数错误', icon: 'none' }); return; } console.log('删除评论,参数:', { feedIndex, commentId, commentIndex }); // 确认删除 wx.showModal({ title: '删除评论', content: '确定要删除这条评论吗?', confirmText: '删除', confirmColor: '#ff4757', success: async (res) => { if (!res.confirm) return; const feed = this.data.feedList[feedIndex]; if (!feed) { wx.showToast({ title: '动态不存在', icon: 'none' }); return; } const feedUuid = feed.uuid || feed.id || feed.dynamicId; if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } try { wx.showLoading({ title: '删除中...', mask: true }); const response = await this.apiClient.deleteCommentDynamic(feedUuid, commentId); if (response && response.code === 200) { wx.hideLoading(); wx.showToast({ title: '删除成功', icon: 'success', duration: 1000 }); // 从本地列表中删除评论(通过ID匹配,更安全) const currentComments = this.data.feedList[feedIndex].comments || []; const updatedComments = currentComments.filter((c) => c.id !== commentId); const currentCommentCount = (this.data.feedList[feedIndex].interactions.commentCount || 0) - 1; // 使用路径更新方式 const updatePath = {}; updatePath[`feedList[${feedIndex}].comments`] = updatedComments; updatePath[`feedList[${feedIndex}].interactions.commentCount`] = Math.max(0, currentCommentCount); this.setData(updatePath); } else { wx.hideLoading(); wx.showToast({ title: response?.message || '删除失败', icon: 'none', duration: 1500 }); } } catch (error) { console.error('删除评论失败:', error); wx.hideLoading(); wx.showToast({ title: '删除失败,请重试', icon: 'none', duration: 1500 }); } } }); }, // 删除回复 async deleteReply(e) { const { feedIndex, commentId, commentIndex, replyId, replyIndex } = e.currentTarget.dataset; // 检查参数(feedIndex可能是0,所以不能用!feedIndex判断) if (feedIndex === undefined || feedIndex === null || commentId === undefined || commentId === null || commentIndex === undefined || commentIndex === null || replyId === undefined || replyId === null || replyIndex === undefined || replyIndex === null) { console.error('删除回复参数错误:', { feedIndex, commentId, commentIndex, replyId, replyIndex, dataset: e.currentTarget.dataset }); wx.showToast({ title: '参数错误', icon: 'none' }); return; } console.log('删除回复,参数:', { feedIndex, commentId, commentIndex, replyId, replyIndex }); // 确认删除 wx.showModal({ title: '删除回复', content: '确定要删除这条回复吗?', confirmText: '删除', confirmColor: '#ff4757', success: async (res) => { if (!res.confirm) return; const feed = this.data.feedList[feedIndex]; if (!feed) { wx.showToast({ title: '动态不存在', icon: 'none' }); return; } const feedUuid = feed.uuid || feed.id || feed.dynamicId; if (!feedUuid) { wx.showToast({ title: '动态ID不存在', icon: 'none' }); return; } try { wx.showLoading({ title: '删除中...', mask: true }); const response = await this.apiClient.deleteCommentDynamic(feedUuid, replyId); if (response && response.code === 200) { wx.hideLoading(); wx.showToast({ title: '删除成功', icon: 'success', duration: 1000 }); // 从本地列表中删除回复(通过ID匹配,更安全) const currentComments = this.data.feedList[feedIndex].comments || []; // 通过commentId查找评论,而不是使用commentIndex(更安全) const targetCommentIndex = currentComments.findIndex(c => c.id === commentId); if (targetCommentIndex >= 0) { const targetComment = currentComments[targetCommentIndex]; if (targetComment && targetComment.replies) { const updatedReplies = targetComment.replies.filter((r) => r.id !== replyId); const currentCommentCount = (this.data.feedList[feedIndex].interactions.commentCount || 0) - 1; // 使用路径更新方式 const updatePath = {}; updatePath[`feedList[${feedIndex}].comments[${targetCommentIndex}].replies`] = updatedReplies; updatePath[`feedList[${feedIndex}].interactions.commentCount`] = Math.max(0, currentCommentCount); this.setData(updatePath); } } } else { wx.hideLoading(); wx.showToast({ title: response?.message || '删除失败', icon: 'none', duration: 1500 }); } } catch (error) { console.error('删除回复失败:', error); wx.hideLoading(); wx.showToast({ title: '删除失败,请重试', icon: 'none', duration: 1500 }); } } }); }, });