const store = require('../../utils/store.js'); const api = require('../../utils/api.js'); const { toastErr } = require('../../utils/ui.js'); const { syncTabBar } = require('../../utils/tabbar.js'); // 相对时间。社区里关心的是「多久以前发的」,精确到秒没意义 function fmtAgo(iso) { if (!iso) return ''; const d = new Date(iso); if (isNaN(d.getTime())) return ''; const min = Math.floor((Date.now() - d.getTime()) / 60000); if (min < 1) return '刚刚'; if (min < 60) return min + ' 分钟前'; if (min < 60 * 24) return Math.floor(min / 60) + ' 小时前'; if (min < 60 * 24 * 7) return Math.floor(min / 1440) + ' 天前'; const p = (n) => (n < 10 ? '0' + n : '' + n); const sameYear = d.getFullYear() === new Date().getFullYear(); return (sameYear ? '' : d.getFullYear() + '-') + p(d.getMonth() + 1) + '-' + p(d.getDate()); } const TAG_CLASS = { 求助: 'warn', 经验: 'blue', 精选: 'purple', 避坑: 'red', 晒宠: '' }; function mapPost(p) { const tags = p.tags || []; const tag = tags[0] || ''; return { id: p.id, author_name: p.author_name, author_emoji: p.author_emoji || '🐾', content: p.content, images: p.images || [], like_count: p.like_count, comment_count: p.comment_count, liked: false, tag, tagClass: TAG_CLASS[tag] || '', is_ai: !!p.is_ai, user_id: p.user_id, followed: !!p.followed, is_self: !!p.is_self, timeText: fmtAgo(p.created_at), }; } Page({ data: { pet: {}, feedTabs: ['推荐', '关注', '新手求助', '晒宠', '经验'], feedIdx: 0, posts: [], sheetShow: false, sheetType: '', sheetPostId: '', loaded: false, loadErr: '', intoView: '', }, onLoad() { this._unsub = store.subscribe((pet) => this.setData({ pet })); }, onUnload() { if (this._unsub) this._unsub(); }, onShow() { syncTabBar(this); store .ready() .then(() => { this.setData({ pet: store.getPet() }); this.loadPosts(); }) .catch((e) => wx.showToast({ title: e.message || '加载失败', icon: 'none' })); }, loadPosts() { const tab = this.data.feedTabs[this.data.feedIdx]; this.setData({ loadErr: '' }); api .listPosts(tab, 1) .then((page) => this.setData({ posts: (page.list || []).map(mapPost), loaded: true })) .catch((e) => { // 不能让「加载失败」显示成「还没有帖子」,那是在骗用户 this.setData({ loadErr: e.message || '加载失败', loaded: true }); toastErr(e); }); }, // 来自 seg-tabs 的 change 事件 switchFeedTab(e) { // 切分类必须回到顶部:不然从「推荐」滚到一半切到「关注」, // 看到的是新列表的中间,会以为内容错乱了 this.setData({ feedIdx: Number(e.detail.index), intoView: 'feed-top' }); this.loadPosts(); }, // 滚动时清掉锚点,否则下次再设同一个值不会触发 onFeedScroll() { if (this.data.intoView) this.setData({ intoView: '' }); }, likePost(e) { const i = e.currentTarget.dataset.index; const post = this.data.posts[i]; api .likePost(post.id) .then((res) => { this.setData({ [`posts[${i}].like_count`]: res.like_count, [`posts[${i}].liked`]: true }); }) .catch((e) => toastErr(e, '点赞失败')); }, openComments(e) { const id = e.currentTarget.dataset.id; if (!id) return; // 评论改成独立二级页面。原来套在 fixed 弹层里,输入框被键盘遮挡且无法可靠避让; // 普通页面里的 fixed 输入条用 transform 平移就能稳稳浮在键盘上方 wx.navigateTo({ url: `/pages/comments/comments?postId=${id}`, events: { // 评论页把「已发布评论数」回传,卡片上的数字实时更新 commentChanged: (d) => { if (!d || !d.postId) return; const idx = this.data.posts.findIndex((p) => p.id === d.postId); if (idx >= 0) this.setData({ [`posts[${idx}].comment_count`]: d.count }); }, }, }); }, openSheet(e) { this.setData({ sheetType: e.currentTarget.dataset.type, sheetPostId: '', sheetShow: true }); }, closeSheet() { this.setData({ sheetShow: false }); }, onPosted() { this.setData({ sheetShow: false }); this.loadPosts(); }, onCommented() { this.loadPosts(); }, // 点头像或昵称进 TA 的主页。关注做了却没有主页,关注完就石沉大海 goUser(e) { const id = e.currentTarget.dataset.id; if (id) wx.navigateTo({ url: `/pages/user/user?id=${id}` }); }, goLearn() { wx.navigateTo({ url: '/pages/learn/learn' }); }, // 社区的悬浮键本来就该是发帖,不是记录(原来它跳 AI 聊天页) onCreatePost() { this.setData({ sheetType: 'createPost', sheetShow: true }); }, // 点缩略图放大预览。只取 http 图(机器人帖的 emoji 占位不参与) onPreviewImage(e) { const { urls, cur } = e.currentTarget.dataset; const list = (urls || []).filter((u) => typeof u === 'string' && u.indexOf('http') === 0); if (!list.length) return; wx.previewImage({ current: cur, urls: list }); }, // 长按自己的帖子可删除 onLongPressPost(e) { const p = this.data.posts[e.currentTarget.dataset.index]; if (!p || !p.is_self) return; wx.showModal({ title: '删除帖子', content: '确定删除这条帖子?删除后其他人将看不到。', confirmColor: '#D9534F', success: (res) => { if (!res.confirm) return; api.deletePost(p.id) .then(() => { wx.showToast({ title: '已删除', icon: 'success' }); this.loadPosts(); }) .catch((err) => wx.showToast({ title: err.message || '删除失败', icon: 'none' })); }, }); }, // 关注 / 取关帖子作者 toggleFollow(e) { const i = e.currentTarget.dataset.index; const p = this.data.posts[i]; if (!p || !p.user_id || p.is_self) return; const fn = p.followed ? api.unfollowUser : api.followUser; fn(p.user_id) .then(() => { // 同一作者的所有帖子一起更新状态 const posts = this.data.posts.map((x) => x.user_id === p.user_id ? { ...x, followed: !p.followed } : x, ); this.setData({ posts }); wx.showToast({ title: p.followed ? '已取消关注' : '已关注', icon: 'none' }); if (this.data.feedTabs[this.data.feedIdx] === '关注') this.loadPosts(); }) .catch((err) => wx.showToast({ title: err.message || '操作失败', icon: 'none' })); }, onShareAppMessage(e) { // 从帖子上的分享按钮转发:带上该帖内容做标题 if (e && e.from === 'button') { const c = (e.target.dataset.content || '').slice(0, 40); return { title: c ? '宠友圈:' + c : '来宠友圈看看大家的毛孩子', path: '/pages/community/community' }; } return { title: '宠友圈 · 和铲屎官们交流养宠经验', path: '/pages/community/community' }; }, onShareTimeline() { return { title: '宠友圈 · 和铲屎官们交流养宠经验' }; }, });