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:
@@ -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: {} });
|
||||
|
||||
@@ -7,7 +7,11 @@ module.exports.sel = function (map, key, index, def) {
|
||||
</wxs>
|
||||
|
||||
<view class="overlay {{show ? 'show' : ''}}" bindtap="onMaskTap">
|
||||
<view class="sheet {{innerType === 'comments' ? 'sheet-cmp' : ''}}" catchtap="noop">
|
||||
<!-- 键盘避让:只用 transform 平移整张弹层(合成层操作,不触发 reflow,
|
||||
所以不会把聚焦中的 textarea 抖失焦 → 不再「弹一下就收」的死循环)。
|
||||
show/hide 的滑入滑出也并进这一个 transform,避免和 CSS 里的 transform 打架。 -->
|
||||
<view class="sheet {{innerType === 'comments' ? 'sheet-cmp' : ''}}" catchtap="noop"
|
||||
style="transform:translateY({{show ? (innerType === 'comments' && kbHeight ? ('-' + kbHeight + 'px') : '0px') : '106%'}})">
|
||||
<view class="sheetbar"></view>
|
||||
|
||||
<!-- 评论:头 + 滚动列表 + 常驻输入栏。输入栏不能放进滚动区,
|
||||
@@ -68,23 +72,20 @@ module.exports.sel = function (map, key, index, def) {
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 收起态:一条细条,点了才展开成大写评论面板(小红书那套) -->
|
||||
<view wx:if="{{!composing}}" class="cm-collapsed" catchtap="onStartCompose">
|
||||
<view class="cm-collapsed-pill {{commentText ? 'has' : ''}}">{{commentText || '说点什么…'}}</view>
|
||||
<view class="cm-collapsed-ic"><pt-icon name="photo" size="{{40}}"></pt-icon></view>
|
||||
</view>
|
||||
|
||||
<!-- 展开态:大输入面板,浮在键盘上方 -->
|
||||
<view wx:else class="cmp-dock" style="padding-bottom:{{kbHeight ? kbHeight + 'px' : 'calc(20rpx + env(safe-area-inset-bottom))'}}">
|
||||
<view class="cm-dock-head">
|
||||
<text class="cm-dock-title">{{replyTo.name ? '回复 ' + replyTo.name : '写评论'}}</text>
|
||||
<view class="cm-dock-close" catchtap="onCloseCompose"><pt-icon name="close" size="{{30}}"></pt-icon></view>
|
||||
<!-- 常驻评论条:textarea 的 class / auto-height / 尺寸全程不变,回复时只置 focus。
|
||||
之前真机「闪 + 不聚焦」是因为聚焦同帧还改了 textarea 自身属性(mini→full、
|
||||
auto-height 切换),被微信当成 reflow 把焦点抖掉了。现在什么都不切。 -->
|
||||
<view class="cmp-dock">
|
||||
<view wx:if="{{replyTo.name}}" class="cm-replying">
|
||||
回复 {{replyTo.name}}
|
||||
<view class="cm-cancel" catchtap="onCancelReply"><pt-icon name="close" size="{{24}}"></pt-icon></view>
|
||||
</view>
|
||||
<textarea class="cm-ta" placeholder="{{replyTo.name ? '回复 @' + replyTo.name + '…' : '有爱评论,说点好听的~'}}"
|
||||
<textarea class="cm-ta" placeholder="{{replyTo.name ? '回复 @' + replyTo.name + '…' : '说点什么…'}}"
|
||||
placeholder-class="placeholder" value="{{commentText}}" bindinput="onCommentInput"
|
||||
focus="{{cmFocus}}" bindfocus="onCommentFocus" bindblur="onCommentBlur"
|
||||
bindkeyboardheightchange="onKbChange"
|
||||
maxlength="500" auto-height="{{true}}" show-confirm-bar="{{false}}"
|
||||
adjust-position="{{false}}" cursor-spacing="12"></textarea>
|
||||
adjust-position="{{false}}" cursor-spacing="16"></textarea>
|
||||
<view wx:if="{{commentImages.length}}" class="img-picker" style="margin-top:12rpx">
|
||||
<view wx:for="{{commentImages}}" wx:key="id" class="img-thumb">
|
||||
<image src="{{item.url}}" mode="aspectFill"></image>
|
||||
@@ -92,10 +93,11 @@ module.exports.sel = function (map, key, index, def) {
|
||||
</view>
|
||||
</view>
|
||||
<view class="cm-toolbar">
|
||||
<view class="cm-pic" catchtap="onPickCommentImages"><pt-icon name="photo" size="{{44}}"></pt-icon></view>
|
||||
<view class="cm-pic {{commentImages.length ? 'disabled' : ''}}" catchtap="onPickCommentImages"><pt-icon name="photo" size="{{44}}"></pt-icon></view>
|
||||
<text class="cm-count">{{commentText.length}}/500</text>
|
||||
<view class="cm-send {{commentText ? 'on' : ''}}" catchtap="onSendComment">发送</view>
|
||||
<view class="cm-send {{commentText && !sendingComment ? 'on' : ''}}" catchtap="onSendComment">{{sendingComment ? '审核中…' : '发送'}}</view>
|
||||
</view>
|
||||
<view class="cm-tip">评论通过安全审核后才会公开显示,配图最多 1 张</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
padding:var(--sp-3) var(--sp-5) calc(20rpx + env(safe-area-inset-bottom));
|
||||
transition:padding-bottom .2s ease;
|
||||
}
|
||||
/* 展开态:给顶部一点投影,和列表分层 */
|
||||
.cmp-dock.open{box-shadow:0 -8rpx 24rpx rgba(0,0,0,.05)}
|
||||
|
||||
.cm{display:flex;gap:var(--sp-3);padding:var(--sp-3) 0}
|
||||
.cm-av{
|
||||
@@ -155,9 +157,9 @@
|
||||
.cm-dock-title{font-size:var(--fs-md);font-weight:var(--fw-b);color:var(--text)}
|
||||
.cm-dock-close{display:flex;align-items:center;color:var(--muted);padding:var(--sp-1)}
|
||||
.cm-ta{
|
||||
width:100%;box-sizing:border-box;min-height:200rpx;max-height:520rpx;height:auto;
|
||||
padding:var(--sp-4);background:var(--surface-2);border-radius:var(--r-md);
|
||||
font-size:var(--fs-lg);line-height:1.5;
|
||||
width:100%;box-sizing:border-box;min-height:96rpx;max-height:360rpx;height:auto;
|
||||
padding:var(--sp-3) var(--sp-4);background:var(--surface-2);border-radius:var(--r-md);
|
||||
font-size:var(--fs-md);line-height:1.5;
|
||||
}
|
||||
.cm-toolbar{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-3)}
|
||||
.cm-pic{flex:none;display:flex;align-items:center;color:var(--muted)}
|
||||
@@ -168,3 +170,5 @@
|
||||
background:var(--line);color:#fff;font-size:var(--fs-md);font-weight:var(--fw-b);transition:.16s ease;
|
||||
}
|
||||
.cm-send.on{background:var(--cta)}
|
||||
.cm-pic.disabled{opacity:.35}
|
||||
.cm-tip{margin-top:var(--sp-2);color:var(--muted2);font-size:var(--fs-cap);line-height:1.5}
|
||||
|
||||
Reference in New Issue
Block a user