feat(聊天页面): 添加新消息提醒功能并优化自动滚动逻辑

实现智能滚动行为,当用户不在底部时显示新消息提醒气泡
添加动画效果和样式,点击气泡可滚动到最新消息
优化滚动检测逻辑,保持轮询始终运行
This commit is contained in:
邹方成 2025-10-31 00:40:32 +08:00
parent 7ce96a57e5
commit 0af7fc4ec5
4 changed files with 245 additions and 11 deletions

View File

@ -24,6 +24,10 @@ Page({
isAtBottom: true,
// 正在加载更多(顶部分页)
loadingMore: false,
// 新消息提醒相关状态
showNewMessageTip: false, // 是否显示新消息提醒
newMessageCount: 0, // 新消息数量
lastMessageId: '', // 最后一条消息的ID用于检测新消息
},
/**
@ -68,12 +72,27 @@ Page({
// 根据ID进行排序确保ID最大的最新的消息在最后
const sortedList = that.sortMessagesByID(list);
that.setData({
// 智能滚动逻辑:用户在底部时自动滚动,在上方时保持位置
const updateData = {
messages: sortedList,
total: res.total,
// only auto-scroll if user is at bottom
scrollToId: that.data.isAtBottom ? (sortedList[sortedList.length - 1] ? sortedList[sortedList.length - 1].id : '') : that.data.scrollToId
});
total: res.total
};
that.setData(updateData);
// 如果用户在底部延迟设置滚动ID确保滚动生效
if (that.data.isAtBottom && sortedList.length > 0) {
const lastMessageId = sortedList[sortedList.length - 1].id;
console.log('用户在底部准备自动滚动到消息ID:', lastMessageId);
setTimeout(() => {
that.setData({
scrollToId: lastMessageId
});
console.log('已设置scrollToId:', lastMessageId);
}, 50);
} else {
console.log('用户不在底部或无消息保持当前位置。isAtBottom:', that.data.isAtBottom, '消息数量:', sortedList.length);
}
return res; // 返回结果以支持链式调用
});
@ -130,7 +149,7 @@ Page({
// 统一的消息处理函数
processMessages(messageList) {
return messageList.map((item, index) => {
const processedMessages = messageList.map((item, index) => {
// 优先使用服务器返回的ID如果没有则使用创建时间戳
if (item.id && !item.id.toString().startsWith('m') && !item.id.toString().startsWith('u')) {
item.id = 'm' + item.id;
@ -154,6 +173,44 @@ Page({
return item;
});
// 检测新消息并显示提醒
this.checkNewMessages(processedMessages);
return processedMessages;
},
// 检测新消息的函数
checkNewMessages(newMessages) {
if (newMessages.length === 0) return;
const currentMessages = this.data.messages;
const currentLastMessageId = this.data.lastMessageId;
const sortedNewMessages = this.sortMessagesByID(newMessages);
const latestNewMessage = sortedNewMessages[sortedNewMessages.length - 1];
// 如果不在底部且有新消息
if (!this.data.isAtBottom && currentMessages.length > 0) {
// 找出真正的新消息(在当前消息列表中不存在的消息)
const currentMessageIds = new Set(currentMessages.map(msg => msg.id));
const realNewMessages = sortedNewMessages.filter(msg => !currentMessageIds.has(msg.id));
if (realNewMessages.length > 0) {
// 累加新消息数量(而不是重置)
const currentCount = this.data.newMessageCount || 0;
this.setData({
showNewMessageTip: true,
newMessageCount: currentCount + realNewMessages.length
});
}
}
// 更新最后一条消息ID
if (latestNewMessage) {
this.setData({
lastMessageId: latestNewMessage.id
});
}
},
// 消息排序函数
@ -246,13 +303,15 @@ Page({
// e.detail.scrollTop increases when scrolling down. We want to detect upward scroll.
const scrollTop = e.detail.scrollTop || 0;
const last = this._lastScrollTop || 0;
// If user scrolls up (new scrollTop < last), pause polling
// If user scrolls up (new scrollTop < last), mark as not at bottom but keep polling
if (scrollTop < last) {
// user scrolled up
if (this.data.isAtBottom) {
// only act if we were previously at bottom
// only update state if we were previously at bottom, but keep polling active
console.log('用户向上滚动设置isAtBottom为false');
this.setData({ isAtBottom: false });
this.stopPolling();
// 注释掉停止轮询的逻辑,确保始终获取新消息
// this.stopPolling();
}
}
// detect reaching near top to load more
@ -267,14 +326,31 @@ Page({
},
onScrollToLower() {
// user scrolled to bottom (or very near). resume polling if needed
// user scrolled to bottom (or very near). update state and ensure polling is active
console.log('用户滚动到底部当前isAtBottom状态:', this.data.isAtBottom);
if (!this.data.isAtBottom) {
this.setData({ isAtBottom: true });
this.setData({
isAtBottom: true,
showNewMessageTip: false,
newMessageCount: 0
});
console.log('已设置isAtBottom为true');
// 确保轮询始终运行(即使之前已经在运行)
this.startPolling();
// also ensure we scroll to latest message next render
const lastId = this.data.messages[this.data.messages.length - 1] ? this.data.messages[this.data.messages.length - 1].id : '';
if (lastId) {
this.setData({ scrollToId: lastId });
console.log('设置scrollToId到最新消息:', lastId);
}
} else {
// 即使已经在底部,也要隐藏新消息提醒
if (this.data.showNewMessageTip) {
this.setData({
showNewMessageTip: false,
newMessageCount: 0
});
console.log('隐藏新消息提醒');
}
}
},
@ -507,6 +583,30 @@ Page({
wx.stopPullDownRefresh();
}
},
// 点击新消息提醒,滚动到底部
scrollToBottom() {
const messages = this.data.messages;
if (messages.length > 0) {
const sortedMessages = this.sortMessagesByID(messages);
const latestMessage = sortedMessages[sortedMessages.length - 1];
this.setData({
scrollToId: latestMessage.id,
showNewMessageTip: false,
newMessageCount: 0,
isAtBottom: true
});
// 延迟一下确保滚动完成
setTimeout(() => {
this.setData({
scrollToId: ''
});
}, 100);
}
},
onReachBottom() { },
onShareAppMessage() { }
});

View File

@ -33,6 +33,14 @@
</block>
</scroll-view>
<!-- 新消息提醒 -->
<view wx:if="{{showNewMessageTip}}" class="new-message-tip" bindtap="scrollToBottom">
<view class="tip-content">
<text class="tip-text">{{newMessageCount}}条新消息</text>
<view class="tip-arrow">↓</view>
</view>
</view>
<view class="input-area">
<image class="btn-image-img" src="/static/upload.png" bindtap="chooseImage" mode="aspectFit" />
<input class="input" placeholder="请输入内容" value="{{inputText}}" bindinput="onInput" confirm-type="send" bindconfirm="sendText" />

View File

@ -314,4 +314,57 @@
order: 2; /* same as previous .bubble order for service */
margin-left: 8rpx; /* match bubble spacing */
margin-right: 0;
}
/* 新消息提醒样式 */
.new-message-tip {
position: fixed;
bottom: 200rpx;
right: 30rpx;
z-index: 1000;
animation: tipFadeIn 0.3s ease-in-out;
}
.tip-content {
background: #07c160;
color: white;
padding: 16rpx 24rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
font-size: 28rpx;
}
.tip-text {
margin-right: 8rpx;
}
.tip-arrow {
font-size: 24rpx;
font-weight: bold;
animation: bounce 1s infinite;
}
@keyframes tipFadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-6rpx);
}
60% {
transform: translateY(-3rpx);
}
}

73
测试说明.md Normal file
View File

@ -0,0 +1,73 @@
# 自动滚动修复测试说明
## 修复内容
已修复聊天页面自动刷新后自动滚动到底部的问题,现在实现智能滚动:
### 修复的问题
- ✅ 轮询刷新时不再强制滚动到底部
- ✅ 用户在底部时,新消息会自动滚动显示
- ✅ 用户在上方浏览历史消息时,保持当前位置
- ✅ 添加了延迟处理确保滚动生效
### 修改的文件
- `pages/contact/index.js` - 修改了 `getMessages()` 函数的滚动逻辑
## 测试步骤
### 1. 打开项目
1. 使用微信开发者工具打开项目目录:`/Users/win/code2025/wx-chant`
2. 确保项目配置正确appid 已设置
### 2. 测试场景
#### 场景1用户在底部时的自动滚动
1. 进入聊天页面
2. 滚动到最底部
3. 等待新消息到达(轮询刷新)
4. **预期结果**:新消息自动滚动到视图中
#### 场景2用户在上方时保持位置
1. 进入聊天页面
2. 向上滚动查看历史消息
3. 等待新消息到达(轮询刷新)
4. **预期结果**
- 滚动位置保持不变
- 显示新消息提醒气泡
- 点击气泡可滚动到最新消息
#### 场景3用户发送消息时的自动滚动
1. 在聊天页面发送文本或图片消息
2. **预期结果**:自动滚动到刚发送的消息
### 3. 调试信息
已添加控制台日志,可在微信开发者工具的控制台中查看:
- 滚动状态变化
- 自动滚动触发情况
- scrollToId 设置情况
### 4. 关键日志信息
- `用户在底部准备自动滚动到消息ID: xxx` - 触发自动滚动
- `用户不在底部或无消息,保持当前位置` - 保持位置
- `用户向上滚动设置isAtBottom为false` - 状态变化
- `用户滚动到底部当前isAtBottom状态: xxx` - 到达底部
## 技术实现
### 核心逻辑
```javascript
// 智能滚动:先更新数据,再延迟设置滚动
that.setData(updateData);
if (that.data.isAtBottom && sortedList.length > 0) {
setTimeout(() => {
that.setData({
scrollToId: lastMessageId
});
}, 50);
}
```
### 状态管理
- `isAtBottom`: 标记用户是否在聊天底部
- `scrollToId`: 控制滚动到指定消息
- 延迟50ms确保DOM更新后再滚动