From 7a55ee0f2ed8f4d6dc38c7f9d2f9ec28b9f46073 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 3 Aug 2026 16:04:21 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(community):=20=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B=E4=BA=8C=E7=BA=A7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=20+=20=E8=AF=84=E8=AE=BA=E5=86=85=E5=AE=B9=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=AE=A1=E6=A0=B8=20+=20=E8=AF=84=E8=AE=BA=E6=95=B0?= =?UTF-8?q?=E6=8C=89=E5=AE=9E=E5=8F=91=E5=AE=9E=E6=97=B6=E7=BA=A0=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - 新增 pages/comments 独立评论页(列表 + 底部输入条 + 回复),社区卡片点评论改为 navigateTo - 输入条用普通流式布局 + 原生 adjust-position 做键盘避让,聚焦期间不 setData 碰输入框 - 评论只能配 1 张图;底部常驻「审核后公开」提示;发送按钮在途「审核中…」防连点 - 发布结果按状态区分:过审即时展示,其余提示「已提交,审核通过后展示」 - 评论页把已发布评论数回传社区列表,卡片数字实时更新 后端: - CreateComment 文本判为违规时直接拒收(ErrContentRejected),不入库 - 社区列表/用户帖子列表用「已发布评论实时条数」覆盖 comment_count, 修正异步过审/删待审/后台改状态导致的计数漂移 - IDCard 手机号返回完整号码(上一批遗留) Co-Authored-By: Claude Opus 5 --- pets-be/internal/service/community.go | 34 ++++ pets-be/internal/service/service.go | 3 + pets-be/internal/service/user_profile.go | 1 + pets-fe/app.json | 1 + .../components/bottom-sheet/bottom-sheet.js | 65 ++++--- .../components/bottom-sheet/bottom-sheet.wxml | 34 ++-- .../components/bottom-sheet/bottom-sheet.wxss | 10 +- pets-fe/pages/comments/comments.js | 173 ++++++++++++++++++ pets-fe/pages/comments/comments.json | 6 + pets-fe/pages/comments/comments.wxml | 82 +++++++++ pets-fe/pages/comments/comments.wxss | 82 +++++++++ pets-fe/pages/community/community.js | 16 +- 12 files changed, 461 insertions(+), 46 deletions(-) create mode 100644 pets-fe/pages/comments/comments.js create mode 100644 pets-fe/pages/comments/comments.json create mode 100644 pets-fe/pages/comments/comments.wxml create mode 100644 pets-fe/pages/comments/comments.wxss diff --git a/pets-be/internal/service/community.go b/pets-be/internal/service/community.go index 38d750e..597b741 100644 --- a/pets-be/internal/service/community.go +++ b/pets-be/internal/service/community.go @@ -44,6 +44,35 @@ func (s *Service) attachPostImages(posts []model.Post) { } } +// attachCommentCounts 用「已发布评论」的实时条数覆盖 comment_count。 +// 维护型计数器在这些路径上会漂移:图片评论异步过审后没补计数、删除待审评论时误减、 +// 后台审核改状态没同步。直接按实际已发布条数算,永远和评论弹层「共 X 条」一致。 +func (s *Service) attachCommentCounts(posts []model.Post) { + if len(posts) == 0 { + return + } + ids := make([]string, len(posts)) + for i := range posts { + ids[i] = posts[i].ID + } + type row struct { + PostID string + N int + } + var rows []row + s.db.Model(&model.Comment{}). + Select("post_id, count(*) as n"). + Where("post_id IN ? AND status = ?", ids, model.PostPublished). + Group("post_id").Scan(&rows) + cnt := make(map[string]int, len(rows)) + for _, r := range rows { + cnt[r.PostID] = r.N + } + for i := range posts { + posts[i].CommentCount = cnt[posts[i].ID] + } +} + // tabTag 将 feed tab 映射为标签过滤(空表示不过滤) func tabTag(tab string) string { switch tab { @@ -81,6 +110,7 @@ func (s *Service) ListPosts(userID, tab string, offset, limit int) ([]model.Post return nil, 0, err } s.attachPostImages(posts) + s.attachCommentCounts(posts) s.markFollowed(userID, posts) return posts, total, nil } @@ -299,6 +329,10 @@ func (s *Service) CreateComment(userID, postID string, content string, images [] // 评论也过内容安全(scene=2 评论)。图片这里已经是可访问 URL status, _ := s.moderateInitial(content, user.OpenID, 2, len(images) > 0) + // 文本被判违规:直接拒绝、不入库,让用户知道发不出去 + if status == model.PostRejected { + return nil, ErrContentRejected + } comment := model.Comment{ PostID: postID, UserID: userID, AuthorName: name, Content: content, diff --git a/pets-be/internal/service/service.go b/pets-be/internal/service/service.go index 3a37440..83b92c8 100644 --- a/pets-be/internal/service/service.go +++ b/pets-be/internal/service/service.go @@ -24,6 +24,9 @@ var ErrForbidden = errors.New("无权操作") // ErrInvalidParam 参数不合法(handler 据此返回 40000) var ErrInvalidParam = errors.New("参数不合法") +// ErrContentRejected 内容安全判定为违规,直接拒绝发送(不入库) +var ErrContentRejected = errors.New("内容包含违规信息,无法发布") + // Service 业务逻辑聚合,方法按领域分散在各文件 type Service struct { db *gorm.DB diff --git a/pets-be/internal/service/user_profile.go b/pets-be/internal/service/user_profile.go index e330f2f..e75b418 100644 --- a/pets-be/internal/service/user_profile.go +++ b/pets-be/internal/service/user_profile.go @@ -215,6 +215,7 @@ func (s *Service) ListUserPosts(viewerID, userID string, offset, limit int) ([]m return nil, 0, err } s.attachPostImages(posts) + s.attachCommentCounts(posts) s.markFollowed(viewerID, posts) return posts, total, nil } diff --git a/pets-fe/app.json b/pets-fe/app.json index 678ee94..50ed886 100644 --- a/pets-fe/app.json +++ b/pets-fe/app.json @@ -11,6 +11,7 @@ "pages/idcard/idcard", "pages/report/report", "pages/community/community", + "pages/comments/comments", "pages/learn/learn", "pages/article/article", "pages/mine/mine", diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.js b/pets-fe/components/bottom-sheet/bottom-sheet.js index 9fe6f36..6541fd1 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.js +++ b/pets-fe/components/bottom-sheet/bottom-sheet.js @@ -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: {} }); diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.wxml b/pets-fe/components/bottom-sheet/bottom-sheet.wxml index 96e5314..efbf0c5 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.wxml +++ b/pets-fe/components/bottom-sheet/bottom-sheet.wxml @@ -7,7 +7,11 @@ module.exports.sel = function (map, key, index, def) { - + + - - {{commentText || '说点什么…'}} - - - - - - - {{replyTo.name ? '回复 ' + replyTo.name : '写评论'}} - + + + + 回复 {{replyTo.name}} + - + adjust-position="{{false}}" cursor-spacing="16"> @@ -92,10 +93,11 @@ module.exports.sel = function (map, key, index, def) { - + {{commentText.length}}/500 - 发送 + {{sendingComment ? '审核中…' : '发送'}} + 评论通过安全审核后才会公开显示,配图最多 1 张 diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.wxss b/pets-fe/components/bottom-sheet/bottom-sheet.wxss index 547e849..e63f16a 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.wxss +++ b/pets-fe/components/bottom-sheet/bottom-sheet.wxss @@ -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} diff --git a/pets-fe/pages/comments/comments.js b/pets-fe/pages/comments/comments.js new file mode 100644 index 0000000..a0477ef --- /dev/null +++ b/pets-fe/pages/comments/comments.js @@ -0,0 +1,173 @@ +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, + }, + + 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: {} }); + }, + + // 关键:聚焦期间 JS 绝不 setData 碰输入条(连 cmFocus:false 都不设), + // 否则原生 textarea 会被抖失焦、键盘弹一下就自己收。cmFocus 只在失焦时复位, + // 这样下次点回复 false→true 还能重新拉起键盘。键盘避让全交给原生 adjust-position。 + onCommentBlur() { + this.setData({ cmFocus: false }); + }, + + // 配图:评论只允许 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' })); + }, +}); diff --git a/pets-fe/pages/comments/comments.json b/pets-fe/pages/comments/comments.json new file mode 100644 index 0000000..6594cd8 --- /dev/null +++ b/pets-fe/pages/comments/comments.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + "nav-bar": "/components/nav-bar/nav-bar", + "pt-icon": "/components/pt-icon/index" + } +} diff --git a/pets-fe/pages/comments/comments.wxml b/pets-fe/pages/comments/comments.wxml new file mode 100644 index 0000000..c682909 --- /dev/null +++ b/pets-fe/pages/comments/comments.wxml @@ -0,0 +1,82 @@ + + + + 共 {{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}} + + + + + + + + + + + + {{sendingComment ? '审核中…' : '发送'}} + + 评论通过安全审核后才会公开显示,配图最多 1 张 + diff --git a/pets-fe/pages/comments/comments.wxss b/pets-fe/pages/comments/comments.wxss new file mode 100644 index 0000000..65741f8 --- /dev/null +++ b/pets-fe/pages/comments/comments.wxss @@ -0,0 +1,82 @@ +/* flex 列 + 整页 overflow:hidden:页面本身不滚,列表在内层 scroll-view 里滚。 + 这样聚焦时 adjust-position 只能均匀上移整屏,不会过度上滚露出大空白 */ +page{display:flex;flex-direction:column;height:100vh;overflow:hidden;background:var(--bg)} + +.cm-scroll{flex:1;min-height:0;padding:0 var(--sp-5)} +.cm-count-head{ + padding:var(--sp-3) 0;font-size:var(--fs-md);font-weight:var(--fw-b); + color:var(--text);border-bottom:1rpx solid var(--line); +} + +/* 空态 */ +.cmp-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:160rpx 0;color:var(--muted)} +.cmp-empty-ic{ + width:140rpx;height:140rpx;border-radius:50%;margin-bottom:var(--sp-4); + display:flex;align-items:center;justify-content:center; + background:var(--primary-soft);color:var(--primary); +} +.cmp-empty-t{font-size:var(--fs-lg);font-weight:var(--fw-b);color:var(--text-2)} +.cmp-empty-p{margin-top:var(--sp-1);font-size:var(--fs-sm);color:var(--muted2)} + +/* 评论行 */ +.cm{display:flex;gap:var(--sp-3);padding:var(--sp-3) 0} +.cm-av{ + 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-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-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)} +.mine-tag{ + margin-left:var(--sp-1);font-size:var(--fs-cap);font-weight:var(--fw-b); + color:var(--primary-ink);background:var(--primary-soft);padding:2rpx var(--sp-1);border-radius:var(--r-full); +} + +/* 底部输入条:flex 列里最后一行,不 fixed。整屏靠 adjust-position 均匀上移避让键盘 */ +.cm-bar{ + flex:none;background:#fff;border-top:1rpx solid var(--line); + padding:var(--sp-3) var(--sp-5) calc(16rpx + env(safe-area-inset-bottom)); +} +.cm-replying{ + 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-bar-row{display:flex;align-items:flex-end;gap:var(--sp-3)} +.cm-ta{ + flex:1;min-width:0;box-sizing:border-box;min-height:72rpx;max-height:300rpx;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-pic{flex:none;display:flex;align-items:center;color:var(--muted);height:72rpx} +.cm-pic.disabled{opacity:.35} +.cm-send{ + flex:none;height:72rpx;padding:0 var(--sp-6);border-radius:var(--r-full); + 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)} +.cm-tip{margin-top:var(--sp-2);color:var(--muted2);font-size:var(--fs-cap);line-height:1.5} + +/* 配图预览 */ +.img-picker{display:flex;flex-wrap:wrap;gap:var(--sp-3);margin-bottom:var(--sp-2)} +.img-thumb{position:relative;width:160rpx;height:160rpx;border-radius:var(--r-sm);overflow:hidden} +.img-thumb image{width:100%;height:100%} +.img-del{ + position:absolute;top:0;right:0;width:44rpx;height:44rpx; + display:flex;align-items:center;justify-content:center; + background:rgba(0,0,0,.55);color:#fff;border-radius:0 var(--r-sm) 0 var(--r-sm); +} diff --git a/pets-fe/pages/community/community.js b/pets-fe/pages/community/community.js index c487a37..08c755a 100644 --- a/pets-fe/pages/community/community.js +++ b/pets-fe/pages/community/community.js @@ -105,7 +105,21 @@ Page({ .catch((e) => toastErr(e, '点赞失败')); }, openComments(e) { - this.setData({ sheetPostId: e.currentTarget.dataset.id, sheetType: 'comments', sheetShow: true }); + const id = e.currentTarget.dataset.id; + if (!id) return; + // 评论改成独立二级页面。原来套在 fixed 弹层里,输入框被键盘遮挡且无法可靠避让; + // 普通页面里的 fixed 输入条用 transform 平移就能稳稳浮在键盘上方 + wx.navigateTo({ + url: `/pages/comments/comments?postId=${id}`, + events: { + // 评论页把「已发布评论数」回传,卡片上的数字实时更新 + commentChanged: (d) => { + if (!d || !d.postId) return; + const idx = this.data.posts.findIndex((p) => p.id === d.postId); + if (idx >= 0) this.setData({ [`posts[${idx}].comment_count`]: d.count }); + }, + }, + }); }, openSheet(e) { this.setData({ sheetType: e.currentTarget.dataset.type, sheetPostId: '', sheetShow: true }); From 8d0269cbcea46fe2d36ff3ed6cec007a01015f92 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 3 Aug 2026 16:10:53 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(fe):=20=E8=AF=84=E8=AE=BA=E9=A1=B5?= =?UTF-8?q?=E9=94=AE=E7=9B=98=E9=81=BF=E8=AE=A9=E6=94=B9=E7=94=A8=20bindke?= =?UTF-8?q?yboardheightchange=20=E7=A7=BB=E5=8A=A8=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 列表完全不动(adjust-position=false + 页面 overflow:hidden),只有底部 fixed 输入条按键盘高度抬 bottom,聚焦期间不碰输入框其它属性,避免原生 textarea 抖失焦。 Co-Authored-By: Claude Opus 5 --- pets-fe/pages/comments/comments.js | 13 +++++++++---- pets-fe/pages/comments/comments.wxml | 12 ++++++------ pets-fe/pages/comments/comments.wxss | 13 ++++++++----- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pets-fe/pages/comments/comments.js b/pets-fe/pages/comments/comments.js index a0477ef..0bb237f 100644 --- a/pets-fe/pages/comments/comments.js +++ b/pets-fe/pages/comments/comments.js @@ -26,6 +26,7 @@ Page({ replyTo: {}, cmFocus: false, sendingComment: false, + kbHeight: 0, }, onLoad(query) { @@ -77,11 +78,15 @@ Page({ this.setData({ replyTo: {} }); }, - // 关键:聚焦期间 JS 绝不 setData 碰输入条(连 cmFocus:false 都不设), - // 否则原生 textarea 会被抖失焦、键盘弹一下就自己收。cmFocus 只在失焦时复位, - // 这样下次点回复 false→true 还能重新拉起键盘。键盘避让全交给原生 adjust-position。 + // 键盘高度变化:微信为「自定义输入条跟随键盘」提供的事件。只改输入条的 bottom(inline), + // 评论列表完全不动。注意:聚焦期间除了这个 bottom,绝不 setData 碰输入框本身 + // (不设 cmFocus:false、不改它的布局),否则原生 textarea 会被抖失焦、键盘弹一下就收。 + onKbChange(e) { + this.setData({ kbHeight: (e.detail && e.detail.height) || 0 }); + }, onCommentBlur() { - this.setData({ cmFocus: false }); + // 失焦:收起键盘时把输入条放回底部;cmFocus 复位好让下次回复能重新拉键盘 + this.setData({ cmFocus: false, kbHeight: 0 }); }, // 配图:评论只允许 1 张 diff --git a/pets-fe/pages/comments/comments.wxml b/pets-fe/pages/comments/comments.wxml index c682909..7473224 100644 --- a/pets-fe/pages/comments/comments.wxml +++ b/pets-fe/pages/comments/comments.wxml @@ -55,10 +55,10 @@ - - + + 回复 {{replyTo.name}} @@ -72,9 +72,9 @@ + adjust-position="{{false}}" cursor-spacing="16"> {{sendingComment ? '审核中…' : '发送'}} diff --git a/pets-fe/pages/comments/comments.wxss b/pets-fe/pages/comments/comments.wxss index 65741f8..559aa42 100644 --- a/pets-fe/pages/comments/comments.wxss +++ b/pets-fe/pages/comments/comments.wxss @@ -1,8 +1,9 @@ -/* flex 列 + 整页 overflow:hidden:页面本身不滚,列表在内层 scroll-view 里滚。 - 这样聚焦时 adjust-position 只能均匀上移整屏,不会过度上滚露出大空白 */ +/* flex 列 + 整页 overflow:hidden:页面本身不滚(adjust-position=false 时列表纹丝不动), + 列表在内层 scroll-view 里滚,底部输入条 fixed 覆在最下面 */ page{display:flex;flex-direction:column;height:100vh;overflow:hidden;background:var(--bg)} -.cm-scroll{flex:1;min-height:0;padding:0 var(--sp-5)} +/* 底部留白 = 输入条高度,最后几条评论不被固定输入条盖住 */ +.cm-scroll{flex:1;min-height:0;padding:0 var(--sp-5) 200rpx} .cm-count-head{ padding:var(--sp-3) 0;font-size:var(--fs-md);font-weight:var(--fw-b); color:var(--text);border-bottom:1rpx solid var(--line); @@ -44,10 +45,12 @@ page{display:flex;flex-direction:column;height:100vh;overflow:hidden;background: color:var(--primary-ink);background:var(--primary-soft);padding:2rpx var(--sp-1);border-radius:var(--r-full); } -/* 底部输入条:flex 列里最后一行,不 fixed。整屏靠 adjust-position 均匀上移避让键盘 */ +/* 底部输入条:固定在底部,键盘弹起时 inline 的 bottom 会被抬到键盘上方(列表不动) */ .cm-bar{ - flex:none;background:#fff;border-top:1rpx solid var(--line); + position:fixed;left:0;right:0;bottom:0;z-index:50; + background:#fff;border-top:1rpx solid var(--line); padding:var(--sp-3) var(--sp-5) calc(16rpx + env(safe-area-inset-bottom)); + transition:bottom .15s ease; } .cm-replying{ display:inline-flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-2); From 81d96f774c73b469a461981a38c2226f761f7b43 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 3 Aug 2026 16:20:23 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(fe):=20=E5=86=B7=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=BF=87=E6=B8=A1=E5=8A=A8=E7=94=BB=20=E2=80=94=20=E7=88=AA?= =?UTF-8?q?=E5=8D=B0=E4=B8=80=E6=AD=A5=E6=AD=A5=E8=B8=A9=E4=B8=8A=E5=B1=8F?= =?UTF-8?q?=E5=B9=95=20+=20=E6=94=B6=E5=B0=BE=E6=B8=90=E9=9A=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 启动登录已在 app.js onLaunch + store.ready 单例里最早触发,进首页不再等登录 - 闪屏把原来的单个 logo 换成左右交替的爪印小径,逐个延迟「踩」上屏走向 logo - 登录/拉宠物完成后:先保证闪屏至少 900ms 让爪印走几步,再整屏渐隐放大切走, 不再硬切到首页/建档向导 Co-Authored-By: Claude Opus 5 --- pets-fe/pages/onboarding/onboarding.js | 21 +++++++++++--- pets-fe/pages/onboarding/onboarding.wxml | 16 +++++----- pets-fe/pages/onboarding/onboarding.wxss | 37 +++++++++++++++++------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/pets-fe/pages/onboarding/onboarding.js b/pets-fe/pages/onboarding/onboarding.js index 593c988..6ff27b4 100644 --- a/pets-fe/pages/onboarding/onboarding.js +++ b/pets-fe/pages/onboarding/onboarding.js @@ -45,6 +45,7 @@ function ageFromBirthday(bdayStr) { Page({ data: { booting: true, // 冷启动先显示闪屏,静默登录 + 拉宠物期间不闪建档向导 + bootOut: false, // 闪屏淡出中:加一层渐隐动画再切走,避免硬切 step: 0, petType: '猫猫', petEmoji: '🐱', @@ -68,20 +69,32 @@ Page({ submitting: false, }, onLoad() { + this._bootStart = Date.now(); this.setData({ today: today(), arrivedAt: today() }); // 闪屏期间就把登录 + 宠物拉完,保证进首页时数据已经到位。 // 有宠物直接进首页;没有才落到建档向导。登录失败也别卡在闪屏, // 放用户进向导(提交时会再 store.ready 重试) store .ready() - .then(() => { - if (store.getPets().length > 0) { + .then(() => this._leaveBoot(store.getPets().length > 0 ? 'home' : 'wizard')) + .catch(() => this._leaveBoot('wizard')); + }, + // 冷启动闪屏收尾:先保证闪屏至少露够一小会(否则秒开会「闪一下」), + // 再加一层渐隐动画,最后才真正切到首页 / 建档向导,避免硬切 + _leaveBoot(target) { + const MIN_MS = 900; // 至少显示这么久,让爪印踩几步走完 + const FADE_MS = 340; // 与 .boot--out 的 CSS 过渡时长一致 + const wait = Math.max(0, MIN_MS - (Date.now() - (this._bootStart || 0))); + setTimeout(() => { + this.setData({ bootOut: true }); + setTimeout(() => { + if (target === 'home') { wx.switchTab({ url: '/pages/home/home' }); } else { this.setData({ booting: false }); } - }) - .catch(() => this.setData({ booting: false })); + }, FADE_MS); + }, wait); }, nextStep() { this.setData({ step: Math.min(this.data.step + 1, 5) }); diff --git a/pets-fe/pages/onboarding/onboarding.wxml b/pets-fe/pages/onboarding/onboarding.wxml index d9e9f93..ae64752 100644 --- a/pets-fe/pages/onboarding/onboarding.wxml +++ b/pets-fe/pages/onboarding/onboarding.wxml @@ -1,10 +1,12 @@ - - - - - - - + + + + + + + + + 肉垫计划 diff --git a/pets-fe/pages/onboarding/onboarding.wxss b/pets-fe/pages/onboarding/onboarding.wxss index 907135d..4d1a827 100644 --- a/pets-fe/pages/onboarding/onboarding.wxss +++ b/pets-fe/pages/onboarding/onboarding.wxss @@ -16,18 +16,35 @@ background: radial-gradient(circle at 50% 32%, rgba(253,179,92,.28), transparent 55%), var(--bg); + transition:opacity .34s ease, transform .34s ease; } -.boot-badge{ - width:184rpx;height:184rpx;border-radius:44rpx; - background:linear-gradient(150deg,#FFB865,#F5943E); - display:flex;align-items:center;justify-content:center; - box-shadow:0 18rpx 42rpx rgba(214,138,52,.4); - animation:boot-pop .5s ease both, boot-breathe 2.4s ease-in-out .5s infinite; -} -.boot-name{margin-top:var(--sp-5);font-size:var(--fs-2xl);font-weight:var(--fw-b);letter-spacing:-1rpx} +/* 收尾渐隐:整屏淡出 + 轻微放大,像走进 App,而不是硬切 */ +.boot--out{opacity:0;transform:scale(1.06);pointer-events:none} + +/* 爪印小径:一串爪印从下往上、左右交替地一步步踩出来,走向 logo */ +.paw-trail{position:relative;width:320rpx;height:540rpx;margin:0 auto} +.paw-trail .pw{position:absolute;animation:paw-step 2.4s ease-in-out infinite both} +.paw-trail .paw{width:100rpx;height:92rpx} +.paw-trail .foot-l{transform:rotate(-13deg) scale(.6)} +.paw-trail .foot-r{transform:rotate(13deg) scale(-.6,.6)} /* 负 X 缩放 = 镜像成另一只脚 */ +/* 之字形落点(bottom 越大越靠上)+ 逐个延迟,形成「一步步走」 */ +.paw-trail .pw-0{left:96rpx;bottom:0;animation-delay:0s} +.paw-trail .pw-1{left:184rpx;bottom:96rpx;animation-delay:.2s} +.paw-trail .pw-2{left:96rpx;bottom:192rpx;animation-delay:.4s} +.paw-trail .pw-3{left:184rpx;bottom:288rpx;animation-delay:.6s} +.paw-trail .pw-4{left:96rpx;bottom:384rpx;animation-delay:.8s} +.paw-trail .pw-5{left:184rpx;bottom:480rpx;animation-delay:1s} + +.boot-name{margin-top:var(--sp-4);font-size:var(--fs-2xl);font-weight:var(--fw-b);letter-spacing:-1rpx} .boot-tip{margin-top:var(--sp-2);color:var(--muted);font-size:var(--fs-md)} -@keyframes boot-pop{from{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}} -@keyframes boot-breathe{0%,100%{transform:translateY(0)}50%{transform:translateY(-10rpx)}} +/* 每个爪印:从上方一点、缩着落下 → 踩实(轻微过冲)→ 停一下 → 淡出,循环 */ +@keyframes paw-step{ + 0%{opacity:0;transform:translateY(-14rpx) scale(.7)} + 10%{opacity:1;transform:translateY(0) scale(1.12)} + 20%{opacity:1;transform:translateY(0) scale(1)} + 68%{opacity:1;transform:translateY(0) scale(1)} + 86%,100%{opacity:0;transform:translateY(0) scale(1)} +} .hero{text-align:center;padding:60rpx 0 var(--sp-5)} .pet-logo{ From 1033931d7a6ee44dfce3b4ec2c4ea387e3c67184 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 3 Aug 2026 16:26:02 +0800 Subject: [PATCH 4/5] =?UTF-8?q?style(fe):=20=E5=86=B7=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E7=88=AA=E5=8D=B0=E5=8A=A8=E7=94=BB=E5=8A=A0=E9=95=BF=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=20=E2=80=94=20=E8=B5=B0=E5=AE=8C=E6=95=B4=E6=9D=A1?= =?UTF-8?q?=E8=B7=AF=E5=86=8D=E6=B7=A1=E5=85=A5=20logo=EF=BC=88=E7=BA=A6?= =?UTF-8?q?=201.9s=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 爪印改成一次性踩完并留屏(不再闪烁循环),步距 .26s、带 squash 过冲,更有踩感 - logo/文案等爪印快到顶再淡入,形成「走过来→到家」的收束 - 闪屏最短时长 900ms→1900ms,避免一闪而过 Co-Authored-By: Claude Opus 5 --- pets-fe/pages/onboarding/onboarding.js | 2 +- pets-fe/pages/onboarding/onboarding.wxss | 47 ++++++++++++++---------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/pets-fe/pages/onboarding/onboarding.js b/pets-fe/pages/onboarding/onboarding.js index 6ff27b4..6356ad5 100644 --- a/pets-fe/pages/onboarding/onboarding.js +++ b/pets-fe/pages/onboarding/onboarding.js @@ -82,7 +82,7 @@ Page({ // 冷启动闪屏收尾:先保证闪屏至少露够一小会(否则秒开会「闪一下」), // 再加一层渐隐动画,最后才真正切到首页 / 建档向导,避免硬切 _leaveBoot(target) { - const MIN_MS = 900; // 至少显示这么久,让爪印踩几步走完 + const MIN_MS = 1900; // 至少显示这么久,让整条爪印走完 + logo 淡入,别一闪而过 const FADE_MS = 340; // 与 .boot--out 的 CSS 过渡时长一致 const wait = Math.max(0, MIN_MS - (Date.now() - (this._bootStart || 0))); setTimeout(() => { diff --git a/pets-fe/pages/onboarding/onboarding.wxss b/pets-fe/pages/onboarding/onboarding.wxss index 4d1a827..6a99f8a 100644 --- a/pets-fe/pages/onboarding/onboarding.wxss +++ b/pets-fe/pages/onboarding/onboarding.wxss @@ -21,29 +21,36 @@ /* 收尾渐隐:整屏淡出 + 轻微放大,像走进 App,而不是硬切 */ .boot--out{opacity:0;transform:scale(1.06);pointer-events:none} -/* 爪印小径:一串爪印从下往上、左右交替地一步步踩出来,走向 logo */ -.paw-trail{position:relative;width:320rpx;height:540rpx;margin:0 auto} -.paw-trail .pw{position:absolute;animation:paw-step 2.4s ease-in-out infinite both} +/* 爪印小径:一串爪印从下往上、左右交替地一步步「踩」出来,走向 logo。 + 一次性踩完并留在屏上(不闪烁循环),走完 logo 再淡入 */ +.paw-trail{position:relative;width:340rpx;height:560rpx;margin:0 auto} +.paw-trail .pw{position:absolute;animation:paw-stamp .56s cubic-bezier(.2,.85,.3,1) both} .paw-trail .paw{width:100rpx;height:92rpx} -.paw-trail .foot-l{transform:rotate(-13deg) scale(.6)} -.paw-trail .foot-r{transform:rotate(13deg) scale(-.6,.6)} /* 负 X 缩放 = 镜像成另一只脚 */ -/* 之字形落点(bottom 越大越靠上)+ 逐个延迟,形成「一步步走」 */ +.paw-trail .foot-l{transform:rotate(-13deg) scale(.68)} +.paw-trail .foot-r{transform:rotate(13deg) scale(-.68,.68)} /* 负 X 缩放 = 镜像成另一只脚 */ +/* 之字形落点(bottom 越大越靠上)+ 逐个延迟 .26s,节奏放慢一点,像一步一步走 */ .paw-trail .pw-0{left:96rpx;bottom:0;animation-delay:0s} -.paw-trail .pw-1{left:184rpx;bottom:96rpx;animation-delay:.2s} -.paw-trail .pw-2{left:96rpx;bottom:192rpx;animation-delay:.4s} -.paw-trail .pw-3{left:184rpx;bottom:288rpx;animation-delay:.6s} -.paw-trail .pw-4{left:96rpx;bottom:384rpx;animation-delay:.8s} -.paw-trail .pw-5{left:184rpx;bottom:480rpx;animation-delay:1s} +.paw-trail .pw-1{left:196rpx;bottom:104rpx;animation-delay:.26s} +.paw-trail .pw-2{left:96rpx;bottom:208rpx;animation-delay:.52s} +.paw-trail .pw-3{left:196rpx;bottom:312rpx;animation-delay:.78s} +.paw-trail .pw-4{left:96rpx;bottom:416rpx;animation-delay:1.04s} +.paw-trail .pw-5{left:196rpx;bottom:520rpx;animation-delay:1.3s} -.boot-name{margin-top:var(--sp-4);font-size:var(--fs-2xl);font-weight:var(--fw-b);letter-spacing:-1rpx} -.boot-tip{margin-top:var(--sp-2);color:var(--muted);font-size:var(--fs-md)} -/* 每个爪印:从上方一点、缩着落下 → 踩实(轻微过冲)→ 停一下 → 淡出,循环 */ -@keyframes paw-step{ - 0%{opacity:0;transform:translateY(-14rpx) scale(.7)} - 10%{opacity:1;transform:translateY(0) scale(1.12)} - 20%{opacity:1;transform:translateY(0) scale(1)} - 68%{opacity:1;transform:translateY(0) scale(1)} - 86%,100%{opacity:0;transform:translateY(0) scale(1)} +/* logo 和文案等爪印快走到了再淡入,形成「走过来 → 到家」的收束感 */ +.boot-name{ + margin-top:var(--sp-4);font-size:var(--fs-2xl);font-weight:var(--fw-b);letter-spacing:-1rpx; + animation:fade .5s ease both;animation-delay:1.5s; +} +.boot-tip{ + margin-top:var(--sp-2);color:var(--muted);font-size:var(--fs-md); + animation:fade .5s ease both;animation-delay:1.66s; +} +/* 每个爪印:从上方一点、缩着落下 → 踩实(轻微过冲、squash)→ 稳住并留在屏上 */ +@keyframes paw-stamp{ + 0%{opacity:0;transform:translateY(-20rpx) scale(.5)} + 55%{opacity:1;transform:translateY(3rpx) scale(1.18)} + 74%{transform:translateY(0) scale(.95)} + 100%{opacity:1;transform:translateY(0) scale(1)} } .hero{text-align:center;padding:60rpx 0 var(--sp-5)} From d1f4755dc6e3a17029bd8de2c612448e05611abe Mon Sep 17 00:00:00 2001 From: Blizzard Date: Mon, 3 Aug 2026 16:43:24 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(fe):=20=E8=AF=84=E8=AE=BA=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E8=B5=B7=E5=A7=8B=E5=B0=B1=E6=98=AF=E5=8D=95?= =?UTF-8?q?=E8=A1=8C=E9=AB=98=E5=BA=A6=EF=BC=8C=E6=B6=88=E9=99=A4=E3=80=8C?= =?UTF-8?q?=E5=85=88=E9=AB=98=E4=B8=80=E4=B8=8B=E5=86=8D=E7=BC=A9=E3=80=8D?= =?UTF-8?q?=E7=9A=84=E6=8A=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来 .cm-ta 是 height:auto,首帧按 textarea 默认约两行渲染成高的,auto-height 再测量缩回单行 → 抖动。改为显式单行 height(盖掉全局 textarea 的 88rpx), 文字换行时才由 auto-height 顶高,超过上限内部滚动。 Co-Authored-By: Claude Opus 5 --- pets-fe/pages/comments/comments.wxss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pets-fe/pages/comments/comments.wxss b/pets-fe/pages/comments/comments.wxss index 559aa42..1d5d1e0 100644 --- a/pets-fe/pages/comments/comments.wxss +++ b/pets-fe/pages/comments/comments.wxss @@ -59,10 +59,13 @@ page{display:flex;flex-direction:column;height:100vh;overflow:hidden;background: } .cm-cancel{display:flex;align-items:center} .cm-bar-row{display:flex;align-items:flex-end;gap:var(--sp-3)} +/* 起始就是单行高度(显式 height 盖掉全局 textarea 的 88rpx,避免「先高一下再缩成单行」的抖动), + auto-height 只在文字换行时把它顶高;超过 max 就内部滚动 */ .cm-ta{ - flex:1;min-width:0;box-sizing:border-box;min-height:72rpx;max-height:300rpx;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; + flex:1;min-width:0;box-sizing:border-box; + height:72rpx;min-height:72rpx;max-height:280rpx; + padding:16rpx var(--sp-4);background:var(--surface-2);border-radius:var(--r-md); + font-size:var(--fs-md);line-height:1.4; } .cm-pic{flex:none;display:flex;align-items:center;color:var(--muted);height:72rpx} .cm-pic.disabled{opacity:.35}