feat: 评论支持回复(两级 + @某人)
不做无限层级——手机屏幕撑不住层层缩进,读到第四层就没法看了。
微信、小红书、B站清一色是两级:一级评论 + 其下的回复列表,回复里
用「回复 @某人」表达指向谁。
数据结构:comments 加 parent_id + reply_to_name。回复的回复会被
压平到同一条一级评论下(parent_id 取爷爷的),同时把被回复人的名字
记进 reply_to_name —— 这样既保住两级,又不丢「在跟谁说话」的信息。
几个容易做错的地方:
- 一次查完这一页所有一级评论的回复,不是每条一次查询(N+1)
- 一级评论默认只带 3 条回复,其余点「展开全部 N 条」再拉,避免热门
评论一次返回几百条
- 删一级评论要连它下面的回复一起删,否则回复变成挂在空处的孤儿;
帖子的 comment_count 也要按实际删除条数减,不是减 1
- total 统计含回复,和帖子上显示的数字对得上
- replies 为空时返回 [],不是 null
实测四条评论(含一条回复的回复):
▸ 甲:一级评论 (2 条回复)
└ 乙:多久了?
└ 甲 回复 乙:三天了
▸ 乙:另一条一级评论 (0 条回复)
删掉第一条一级评论后 total 4→1,comment_count 同步。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@ Component({
|
||||
posterSaving: false,
|
||||
centered: false,
|
||||
commentImages: [],
|
||||
replyTo: {},
|
||||
},
|
||||
observers: {
|
||||
show: function (show) {
|
||||
@@ -165,6 +166,7 @@ Component({
|
||||
patch.addBreed = '';
|
||||
patch.commentText = '';
|
||||
patch.commentImages = [];
|
||||
patch.replyTo = {};
|
||||
patch.riskData = null;
|
||||
patch.saving = false;
|
||||
}
|
||||
@@ -417,6 +419,7 @@ Component({
|
||||
// 没有头像字段,用昵称首字做个色块,比一律显示同一个 emoji 强
|
||||
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) })),
|
||||
})),
|
||||
}),
|
||||
)
|
||||
@@ -424,8 +427,9 @@ Component({
|
||||
},
|
||||
// 长按删除自己的评论
|
||||
onDeleteComment(e) {
|
||||
const c = this.data.comments[e.currentTarget.dataset.index];
|
||||
if (!c || !c.is_self) return;
|
||||
const { id, self } = e.currentTarget.dataset;
|
||||
if (!id || !self) return;
|
||||
const c = { id };
|
||||
wx.showModal({
|
||||
title: '删除评论',
|
||||
content: '确定删除这条评论?',
|
||||
@@ -934,8 +938,13 @@ Component({
|
||||
if (!text) return wx.showToast({ title: '写点什么再发', icon: 'none' });
|
||||
if (!this.data.postId) return this.close();
|
||||
try {
|
||||
await api.createComment(this.data.postId, text, this.data.commentImages.map((i) => i.url));
|
||||
this.setData({ commentText: '', commentImages: [] });
|
||||
await api.createComment(
|
||||
this.data.postId,
|
||||
text,
|
||||
this.data.commentImages.map((i) => i.url),
|
||||
this.data.replyTo.id || '',
|
||||
);
|
||||
this.setData({ commentText: '', commentImages: [], replyTo: {} });
|
||||
this.loadComments();
|
||||
this.triggerEvent('commented');
|
||||
} catch (e) {
|
||||
@@ -955,6 +964,26 @@ Component({
|
||||
wx.showToast({ title: (e && e.message) || '上传失败', icon: 'none' });
|
||||
});
|
||||
},
|
||||
// 回复某条评论:输入区顶部显示「回复 xxx」,发送时带上 parent_id
|
||||
onReplyTo(e) {
|
||||
const { id, name } = e.currentTarget.dataset;
|
||||
this.setData({ replyTo: { id, name } });
|
||||
},
|
||||
onCancelReply() {
|
||||
this.setData({ replyTo: {} });
|
||||
},
|
||||
// 一级评论默认只带 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) })),
|
||||
}),
|
||||
)
|
||||
.catch((err) => wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' }));
|
||||
},
|
||||
onPreviewCommentImage(e) {
|
||||
const { urls, cur } = e.currentTarget.dataset;
|
||||
if (urls && urls.length) wx.previewImage({ urls, current: cur });
|
||||
|
||||
Reference in New Issue
Block a user