feat(community): 评论改为独立二级页面 + 评论内容安全审核 + 评论数按实发实时纠正

前端:
- 新增 pages/comments 独立评论页(列表 + 底部输入条 + 回复),社区卡片点评论改为 navigateTo
- 输入条用普通流式布局 + 原生 adjust-position 做键盘避让,聚焦期间不 setData 碰输入框
- 评论只能配 1 张图;底部常驻「审核后公开」提示;发送按钮在途「审核中…」防连点
- 发布结果按状态区分:过审即时展示,其余提示「已提交,审核通过后展示」
- 评论页把已发布评论数回传社区列表,卡片数字实时更新

后端:
- CreateComment 文本判为违规时直接拒收(ErrContentRejected),不入库
- 社区列表/用户帖子列表用「已发布评论实时条数」覆盖 comment_count,
  修正异步过审/删待审/后台改状态导致的计数漂移
- IDCard 手机号返回完整号码(上一批遗留)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-08-03 16:04:21 +08:00
parent 51ead8f411
commit 7a55ee0f2e
12 changed files with 461 additions and 46 deletions
+39 -26
View File
@@ -82,6 +82,7 @@ Component({
replyTo: {},
cmFocus: false,
composing: false, // 评论输入面板是否展开(收起时只是一条细条)
sendingComment: false, // 发送+审核在途,防连点
kbHeight: 0,
total: 0,
},
@@ -120,6 +121,7 @@ Component({
patch.replyTo = {};
patch.cmFocus = false;
patch.composing = false;
patch.sendingComment = false;
patch.kbHeight = 0;
patch.saving = false;
}
@@ -655,32 +657,49 @@ Component({
// 评论
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.close();
this.setData({ sendingComment: true });
wx.showLoading({ title: '审核中…', mask: true });
try {
await api.createComment(
const res = await api.createComment(
this.data.postId,
text,
this.data.commentImages.map((i) => i.url),
this.data.replyTo.id || '',
);
this.setData({ commentText: '', commentImages: [], replyTo: {}, composing: false, cmFocus: false });
this.loadComments();
this.triggerEvent('commented');
wx.hideLoading();
this.setData({
commentText: '', commentImages: [], replyTo: {},
composing: false, cmFocus: false, kbHeight: 0, sendingComment: false,
});
// 过审即时展示;否则挂在人工/异步图片审核,通过后才出现
if (res && res.status === 'published') {
this.loadComments();
this.triggerEvent('commented');
wx.showToast({ title: '评论成功', icon: 'success' });
} else {
wx.showToast({ title: '已提交,审核通过后展示', icon: 'none' });
}
} catch (e) {
wx.showToast({ title: e.message || '评论失败', icon: 'none' });
wx.hideLoading();
this.setData({ sendingComment: false });
// 违规内容后端直接拒收,这里把原因透出来
wx.showToast({ title: (e && e.message) || '评论失败', icon: 'none' });
}
},
// 评论配图,最多 3
// 评论配图:只允许 1
onPickCommentImages() {
const left = 3 - this.data.commentImages.length;
if (left <= 0) return wx.showToast({ title: '最多 3 张', icon: 'none' });
if (this.data.commentImages.length >= 1) {
return wx.showToast({ title: '评论只能配 1', icon: 'none' });
}
upload
.chooseAndUploadImages(left)
.then((files) => this.setData({ commentImages: this.data.commentImages.concat(files) }))
.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' });
@@ -692,27 +711,21 @@ Component({
onReplyTo(e) {
const { id, name } = e.currentTarget.dataset;
if (!id) return;
// 点任意评论直接展开面板并预填回复对象
this.setData({ replyTo: { id, name }, composing: true, cmFocus: true });
// 输入框常驻、属性不变,直接置 focus 拉键盘即可;只填「回复对象」这一个横幅
this.setData({ replyTo: { id, name }, cmFocus: true });
},
// 点细条:展开大输入面板并聚焦(拉起键盘)
onStartCompose() {
this.setData({ composing: true, cmFocus: true });
onCommentFocus() {
// 只复位一次性 focus 开关。键盘高度交给 onKbChange,用 transform 平移弹层来避让
this.setData({ cmFocus: false });
},
// 收起面板,草稿(文字/图片)留着,下次点开接着写
onCloseCompose() {
this.setData({ composing: false, cmFocus: false, kbHeight: 0 });
},
onCommentFocus(e) {
// adjust-position=false,键盘高度自己接管:弹层是 fixed 定位,
// 交给系统顶会把整块推出屏幕
// 键盘高度变化(真机可靠来源)。把高度写进 data,
// wxml 里只用它算 transform 平移量 —— 纯合成层操作,不 reflow、不抖焦点
onKbChange(e) {
this.setData({ kbHeight: (e.detail && e.detail.height) || 0 });
},
onCommentBlur() {
// 失焦先放下键盘。有草稿就保持展开(否则点「发送」会因面板收起而丢掉这一下点击);
// 空内容才收回细条
const empty = !(this.data.commentText || '').trim() && !this.data.commentImages.length;
this.setData({ kbHeight: 0, cmFocus: false, composing: empty ? false : this.data.composing });
// 失焦不主动清 kbHeight:键盘真正收起时 onKbChange 会回调 0,避免和收键盘动画抢
this.setData({ cmFocus: false });
},
onCancelReply() {
this.setData({ replyTo: {} });