const api = require('../../utils/api.js'); const upload = require('../../utils/upload.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()); } Page({ data: { postId: '', total: 0, comments: [], commentText: '', commentImages: [], replyTo: {}, cmFocus: false, sendingComment: false, }, onLoad(query) { this.setData({ postId: query.postId || '' }); this.loadComments(); }, // 把当前已发布评论数回传给社区列表,卡片上的评论数实时跟着变 syncCountBack() { const ch = this.getOpenerEventChannel && this.getOpenerEventChannel(); if (ch && ch.emit) ch.emit('commentChanged', { postId: this.data.postId, count: this.data.total }); }, loadComments() { if (!this.data.postId) return; api .listComments(this.data.postId) .then((page) => { this.setData({ total: page.total || 0, comments: (page.list || []).map((c) => ({ ...c, timeText: fmtAgo(c.created_at), initial: (c.author_name || '?').slice(0, 1), imgs: Array.isArray(c.images) ? c.images : [], replies: (c.replies || []).map((r) => ({ ...r, timeText: fmtAgo(r.created_at), initial: (r.author_name || '?').slice(0, 1), })), })), }); this.syncCountBack(); }) .catch(() => {}); }, onCommentInput(e) { this.setData({ commentText: e.detail.value }); }, // 点任意评论/回复:底部输入条切到「回复 xxx」并聚焦 onReplyTo(e) { const { id, name } = e.currentTarget.dataset; if (!id) return; this.setData({ replyTo: { id, name }, cmFocus: true }); }, onCancelReply() { this.setData({ replyTo: {} }); }, // 关键:聚焦期间 JS 绝不 setData 碰输入条(连 cmFocus:false 都不设), // 否则原生 textarea 会被抖失焦、键盘弹一下就自己收。cmFocus 只在失焦时复位, // 这样下次点回复 false→true 还能重新拉起键盘。键盘避让全交给原生 adjust-position。 onCommentBlur() { this.setData({ cmFocus: false }); }, // 配图:评论只允许 1 张 onPickCommentImages() { if (this.data.commentImages.length >= 1) { return wx.showToast({ title: '评论只能配 1 张图', icon: 'none' }); } upload .chooseAndUploadImages(1) .then((files) => this.setData({ commentImages: (files || []).slice(0, 1) })) .catch((e) => { if (e && e.canceled) return; wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' }); }); }, onRemoveCommentImage(e) { const list = this.data.commentImages.slice(); list.splice(e.currentTarget.dataset.index, 1); this.setData({ commentImages: list }); }, onPreviewCommentImage(e) { const { urls, cur } = e.currentTarget.dataset; if (urls && urls.length) wx.previewImage({ urls, current: cur }); }, async onSendComment() { if (this.data.sendingComment) return; // 防连点 const text = (this.data.commentText || '').trim(); if (!text) return wx.showToast({ title: '写点什么再发', icon: 'none' }); if (!this.data.postId) return; this.setData({ sendingComment: true }); wx.showLoading({ title: '审核中…', mask: true }); try { const res = await api.createComment( this.data.postId, text, this.data.commentImages.map((i) => i.url), this.data.replyTo.id || '', ); wx.hideLoading(); this.setData({ commentText: '', commentImages: [], replyTo: {}, cmFocus: false, sendingComment: false }); if (res && res.status === 'published') { this.loadComments(); wx.showToast({ title: '评论成功', icon: 'success' }); } else { wx.showToast({ title: '已提交,审核通过后展示', icon: 'none' }); } } catch (e) { wx.hideLoading(); this.setData({ sendingComment: false }); wx.showToast({ title: (e && e.message) || '评论失败', icon: 'none' }); } }, // 长按删除自己的评论 onDeleteComment(e) { const { id, self } = e.currentTarget.dataset; if (!id || !self) return; wx.showModal({ title: '删除评论', content: '确定删除这条评论?', confirmColor: '#D9534F', success: (r) => { if (!r.confirm) return; api .deleteComment(id) .then(() => this.loadComments()) .catch((err) => wx.showToast({ title: (err && err.message) || '删除失败', icon: 'none' })); }, }); }, // 一级评论默认只带 3 条回复,点开拉全量 onExpandReplies(e) { const { id, index } = e.currentTarget.dataset; api .listReplies(id) .then((list) => this.setData({ [`comments[${index}].replies`]: (list || []).map((r) => ({ ...r, timeText: fmtAgo(r.created_at), initial: (r.author_name || '?').slice(0, 1), })), }), ) .catch((err) => wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })); }, });