Files
Blizzard 8d0269cbce fix(fe): 评论页键盘避让改用 bindkeyboardheightchange 移动固定输入条
列表完全不动(adjust-position=false + 页面 overflow:hidden),只有底部 fixed
输入条按键盘高度抬 bottom,聚焦期间不碰输入框其它属性,避免原生 textarea 抖失焦。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:10:53 +08:00

179 lines
5.9 KiB
JavaScript

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,
kbHeight: 0,
},
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: {} });
},
// 键盘高度变化:微信为「自定义输入条跟随键盘」提供的事件。只改输入条的 bottom(inline),
// 评论列表完全不动。注意:聚焦期间除了这个 bottom,绝不 setData 碰输入框本身
// (不设 cmFocus:false、不改它的布局),否则原生 textarea 会被抖失焦、键盘弹一下就收。
onKbChange(e) {
this.setData({ kbHeight: (e.detail && e.detail.height) || 0 });
},
onCommentBlur() {
// 失焦:收起键盘时把输入条放回底部;cmFocus 复位好让下次回复能重新拉键盘
this.setData({ cmFocus: false, kbHeight: 0 });
},
// 配图:评论只允许 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' }));
},
});