feat(聊天页面): 添加新消息提醒功能并优化自动滚动逻辑
实现智能滚动行为,当用户不在底部时显示新消息提醒气泡 添加动画效果和样式,点击气泡可滚动到最新消息 优化滚动检测逻辑,保持轮询始终运行
This commit is contained in:
parent
7ce96a57e5
commit
0af7fc4ec5
@ -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() { }
|
||||
});
|
||||
@ -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" />
|
||||
|
||||
@ -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
73
测试说明.md
Normal 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更新后再滚动
|
||||
Loading…
x
Reference in New Issue
Block a user