From fe420ba47c3aed569375fb75c7f48576ce9b8b44 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Wed, 29 Jul 2026 16:39:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(fe):=20=E8=AF=84=E8=AE=BA=E6=94=B9?= =?UTF-8?q?=E6=88=90=E5=B0=8F=E7=BA=A2=E4=B9=A6=E9=82=A3=E5=A5=97=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E2=80=94=E2=80=94=E7=82=B9=E8=AF=84=E8=AE=BA=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=94=A4=E8=B5=B7=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版做成了居中弹窗,参考图给的是另一种范式:输入栏常驻底部,点任意 一条评论就把它激活并填上「回复 @xxx」。按参考图重做,居中那套一并删掉, 不留死代码。 结构从「一个滚动容器装所有东西」改成三段式: 固定头(共 N 条评论)+ 滚动列表 + 常驻输入栏 输入栏必须在滚动区外——放进去的话列表一滚它就跟着走,点完评论还得再 把输入框找回来,正是这次要解决的问题。 交互: - 点评论或回复的任意位置 → 输入框获得焦点,placeholder 变「回复 @xxx」, 上方出现可取消的橙色提示条。不用再单独点一个「回复」按钮 - 删除从长按改成显式的「删除」文字,只在自己的评论上出现——长按是 隐藏交互,没人会发现 - 回复带首字头像并缩进一层,「甲 回复 乙」的指向关系显示在昵称行 键盘处理用 adjust-position=false + bindfocus 拿键盘高度自己顶:弹层是 fixed 定位,交给系统调整会把整块推出屏幕。 Co-Authored-By: Claude Opus 5 --- .../components/bottom-sheet/bottom-sheet.js | 35 ++++- .../components/bottom-sheet/bottom-sheet.wxml | 148 ++++++++++-------- .../components/bottom-sheet/bottom-sheet.wxss | 82 +++++----- 3 files changed, 150 insertions(+), 115 deletions(-) diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.js b/pets-fe/components/bottom-sheet/bottom-sheet.js index f9a8dbf..bded3c9 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.js +++ b/pets-fe/components/bottom-sheet/bottom-sheet.js @@ -117,9 +117,11 @@ Component({ proInfo: null, saving: false, posterSaving: false, - centered: false, commentImages: [], replyTo: {}, + cmFocus: false, + kbHeight: 0, + total: 0, }, observers: { show: function (show) { @@ -139,8 +141,7 @@ Component({ methods: { setType(type, fresh) { const pet = store.getPet(); - // 评论以输入为主,贴底弹层会被键盘顶掉大半屏,改成居中 - const patch = { innerType: type, pet, centered: type === 'comments' }; + const patch = { innerType: type, pet }; if (fresh) { patch.segSel = {}; patch.optSel = {}; @@ -167,6 +168,8 @@ Component({ patch.commentText = ''; patch.commentImages = []; patch.replyTo = {}; + patch.cmFocus = false; + patch.kbHeight = 0; patch.riskData = null; patch.saving = false; } @@ -413,13 +416,18 @@ Component({ api.listComments(this.data.postId) .then((page) => this.setData({ + total: page.total || 0, comments: (page.list || []).map((c) => ({ ...c, timeText: fmtAgo(c.created_at), // 没有头像字段,用昵称首字做个色块,比一律显示同一个 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) })), + replies: (c.replies || []).map((r) => ({ + ...r, + timeText: fmtAgo(r.created_at), + initial: (r.author_name || '?').slice(0, 1), + })), })), }), ) @@ -965,9 +973,20 @@ Component({ }); }, // 回复某条评论:输入区顶部显示「回复 xxx」,发送时带上 parent_id + // 小红书那套:点任意一条评论/回复,直接把输入框激活并填上「回复 @xxx」, + // 不用再去底部找输入框、也不用单独点一个「回复」按钮 onReplyTo(e) { const { id, name } = e.currentTarget.dataset; - this.setData({ replyTo: { id, name } }); + if (!id) return; + this.setData({ replyTo: { id, name }, cmFocus: true }); + }, + onCommentFocus(e) { + // adjust-position=false,键盘高度自己接管:弹层是 fixed 定位, + // 交给系统顶会把整块推出屏幕 + this.setData({ kbHeight: (e.detail && e.detail.height) || 0 }); + }, + onCommentBlur() { + this.setData({ kbHeight: 0, cmFocus: false }); }, onCancelReply() { this.setData({ replyTo: {} }); @@ -979,7 +998,11 @@ Component({ .listReplies(id) .then((list) => this.setData({ - [`comments[${index}].replies`]: (list || []).map((r) => ({ ...r, timeText: fmtAgo(r.created_at) })), + [`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' })); diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.wxml b/pets-fe/components/bottom-sheet/bottom-sheet.wxml index 47a02d7..adb50f4 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.wxml +++ b/pets-fe/components/bottom-sheet/bottom-sheet.wxml @@ -7,10 +7,91 @@ module.exports.sel = function (map, key, index, def) { - + - + + + 共 {{total}} 条评论 + + + + {{item.initial}} + + {{item.author_name}} + {{item.content}} + + + + + {{item.timeText}} + 回复 + 删除 + + + + + + {{r.initial}} + + + {{r.author_name}} + 回复 {{r.reply_to_name}} + + {{r.content}} + + {{r.timeText}} + 回复 + 删除 + + + + + — 展开全部 {{item.reply_n}} 条回复 + + + + + + 还没有评论,来抢个沙发 + + + + + 回复 {{replyTo.name}} + + + + + + + + + + + + + + {{commentText.length}}/500 + 发送 + + + + + + 记录体重 @@ -318,69 +399,6 @@ module.exports.sel = function (map, key, index, def) { - - 评论 {{comments.length ? comments.length : ''}} - - - {{item.initial}} - - - {{item.author_name}} - - {{item.timeText}} - - {{item.content}} - - - - - - - - {{r.author_name}} - 回复 {{r.reply_to_name}} - {{r.content}} - - - 展开全部 {{item.reply_n}} 条回复 - - - - 回复 - - - - 还没有评论,来抢个沙发 - 长按自己的评论可删除 - - - - 回复 {{replyTo.name}} - - - - - - - - - - - - - 配图 {{commentImages.length}}/3 - - {{commentText.length}}/500 - 发送 - - - diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.wxss b/pets-fe/components/bottom-sheet/bottom-sheet.wxss index 8f05b32..d9aa008 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.wxss +++ b/pets-fe/components/bottom-sheet/bottom-sheet.wxss @@ -83,60 +83,54 @@ /* 离屏画布:挪到可视区外而不是 display:none,否则 selectorQuery 取不到节点 */ .poster-canvas{position:fixed;left:-9999rpx;top:0;width:600rpx;height:840rpx} -/* 评论。原来拿 .record-row(左右两端对齐)当评论用,昵称在左内容在右, - 内容一长就被挤扁。改成头像 + 昵称时间 + 内容的正常结构。 */ -.cm{display:flex;gap:var(--sp-3);padding:var(--sp-3) 0;border-bottom:1rpx solid var(--line)} -.cm:last-of-type{border-bottom:0} +/* ── 评论:头 + 滚动列表 + 常驻输入栏 ── */ +.sheet.sheet-cmp{display:flex;flex-direction:column;height:82vh;padding-bottom:0} +.cmp-head{ + flex:none;text-align:center;padding:0 0 var(--sp-3); + font-size:var(--fs-sm);color:var(--muted);border-bottom:1rpx solid var(--line); +} +.cmp-list{flex:1;min-height:0;padding:var(--sp-3) var(--sp-5) 0} +.cmp-dock{ + flex:none;background:#fff;border-top:1rpx solid var(--line); + padding:var(--sp-3) var(--sp-5) calc(20rpx + env(safe-area-inset-bottom)); + transition:padding-bottom .2s ease; +} + +.cm{display:flex;gap:var(--sp-3);padding:var(--sp-3) 0} .cm-av{ - width:64rpx;height:64rpx;border-radius:var(--r-sm);flex:none; + width:64rpx;height:64rpx;border-radius:50%;flex:none; display:flex;align-items:center;justify-content:center; background:var(--primary-soft);color:var(--primary-ink); font-size:var(--fs-md);font-weight:var(--fw-b); } +.cm-av-sm{width:48rpx;height:48rpx;font-size:var(--fs-sm)} .cm-body{flex:1;min-width:0} -.cm-head{display:flex;align-items:center;gap:var(--sp-1)} -.cm-name{font-size:var(--fs-sm);font-weight:var(--fw-b);color:var(--text-2)} -.cm-time{margin-left:auto;font-size:var(--fs-cap);color:var(--muted2);flex:none} +.cm-name{font-size:var(--fs-sm);color:var(--muted)} +.cm-at{color:var(--muted2)} .cm-text{margin-top:6rpx;font-size:var(--fs-md);line-height:1.55;word-break:break-word} -.cm-tip{margin-top:var(--sp-3);text-align:center;color:var(--muted2);font-size:var(--fs-cap)} - -.cm-input{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-4)} -.cm-input .input{flex:1} -.cm-send{ - flex:none;height:88rpx;padding:0 var(--sp-5);border-radius:var(--r-md); - display:flex;align-items:center;justify-content:center; - background:var(--line);color:#fff;font-size:var(--fs-md);font-weight:var(--fw-b);transition:.16s ease; -} -.cm-send.on{background:var(--cta)} - -/* 居中弹层:评论这类以输入为主的,贴底会被键盘顶掉大半屏 */ -.sheet.sheet-center{ - left:var(--sp-5);right:var(--sp-5);bottom:auto;top:50%; - border-radius:var(--r-lg); - transform:translateY(-50%) scale(.94);opacity:0; -} -.overlay.show .sheet.sheet-center{transform:translateY(-50%) scale(1);opacity:1} -.sheet-center .sheet-scroll{max-height:62vh;padding:0 var(--sp-5) var(--sp-5)} -.sheet-center .sheetbar{display:none} - -/* 评论编辑区 */ -.cm-editor{margin-top:var(--sp-4);border-top:1rpx solid var(--line);padding-top:var(--sp-4)} -.cm-ta{min-height:160rpx;height:auto} -.cm-bar{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-3)} -.cm-pic{display:flex;align-items:center;gap:var(--sp-1);color:var(--muted);font-size:var(--fs-sm)} -.cm-count{margin-left:auto;color:var(--muted2);font-size:var(--fs-cap)} .cm-imgs{display:flex;flex-wrap:wrap;gap:var(--sp-1);margin-top:var(--sp-2)} .cm-img{width:150rpx;height:150rpx;border-radius:var(--r-sm)} +.cm-meta{display:flex;align-items:center;gap:var(--sp-4);margin-top:var(--sp-2);font-size:var(--fs-cap);color:var(--muted2)} +.cm-reply-btn{color:var(--muted)} +.cm-del{color:var(--red-ink)} +.cm-replies{margin-top:var(--sp-2)} +.cm-reply{display:flex;gap:var(--sp-2);padding:var(--sp-2) 0} +.cm-more{margin-top:4rpx;font-size:var(--fs-cap);color:var(--muted)} -/* 回复:只缩进一层,再深手机上就没法读了 */ -.cm-replies{margin-top:var(--sp-2);padding:var(--sp-2) var(--sp-3);background:var(--surface-2);border-radius:var(--r-sm)} -.cm-reply{font-size:var(--fs-sm);line-height:1.6;color:var(--text);padding:4rpx 0} -.cm-at{color:var(--muted)} -.cm-more{margin-top:6rpx;font-size:var(--fs-cap);color:var(--primary-dark)} -.cm-act{margin-top:var(--sp-2);font-size:var(--fs-cap);color:var(--muted)} +/* 「回复 @xxx」的提示条 */ .cm-replying{ - display:flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-2); - padding:var(--sp-1) var(--sp-3);border-radius:var(--r-full); - background:var(--primary-soft);color:var(--primary-ink);font-size:var(--fs-cap);align-self:flex-start; + display:inline-flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-2); + padding:4rpx var(--sp-3);border-radius:var(--r-full); + background:var(--primary-soft);color:var(--primary-ink);font-size:var(--fs-cap); } .cm-cancel{display:flex;align-items:center} +.cm-ta{min-height:120rpx;height:auto;background:var(--surface-2)} +.cm-bar{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-2)} +.cm-pic{display:flex;align-items:center;color:var(--muted)} +.cm-count{margin-left:auto;color:var(--muted2);font-size:var(--fs-cap)} +.cm-send{ + flex:none;height:72rpx;padding:0 var(--sp-5);border-radius:var(--r-full); + display:flex;align-items:center;justify-content:center; + background:var(--line);color:#fff;font-size:var(--fs-sm);font-weight:var(--fw-b);transition:.16s ease; +} +.cm-send.on{background:var(--cta)}