This commit is contained in:
左哥 2025-10-19 21:27:53 +08:00
parent 7e88dbb16c
commit 7477992bc7
4 changed files with 226 additions and 46 deletions

View File

@ -17,8 +17,13 @@ Page({
userInfo: {},
appid: '',
page: 1,
page_size: 100,
page_size: 50,
openid: '',
total: 0,
// 是否滚动在底部
isAtBottom: true,
// 正在加载更多(顶部分页)
loadingMore: false,
},
/**
@ -39,6 +44,8 @@ Page({
userInfo: wx.getStorageSync('user_info'),
});
this.getMessages()
// start polling when page loads and user is present
this.startPolling();
} else {
this.setData({
showGetUser: true,
@ -64,15 +71,73 @@ Page({
page_size: this.data.page_size,
}).then(res => {
console.log('res', res);
const list = res.list.map(item => {
// generate stable ids across pages so we can prepend without collisions
const base = (this.data.page - 1) * this.data.page_size;
const list = res.list.map((item, index) => {
item.id = 'm' + (base + index);
item.content = JSON.parse(item.content);
return item;
});
this.setData({
messages: list,
total: res.total,
// only auto-scroll if user is at bottom
scrollToId: this.data.isAtBottom ? (list[list.length - 1] ? list[list.length - 1].id : '') : this.data.scrollToId
});
console.log('messages',list);
})
},
// Load older messages (previous pages) and prepend them to the top
loadMoreAtTop() {
if (this.data.loadingMore) return;
// if we've loaded all messages, skip
const already = this.data.messages.length || 0;
if (already >= this.data.total) return;
// compute next page to fetch
const nextPage = this.data.page + 1;
this.setData({ loadingMore: true });
const userInfo = wx.getStorageSync('user_info');
const that = this;
// capture current top-most visible message id to preserve scroll position
const prevTopId = this.data.messages[0] ? this.data.messages[0].id : null;
request('app/messages', 'get', {
app_id: this.data.appid,
user_id: userInfo.openid,
page: nextPage,
page_size: this.data.page_size,
}).then(res => {
// map with stable ids based on nextPage
const base = (nextPage - 1) * that.data.page_size;
const newList = res.list.map((item, index) => {
item.id = 'm' + (base + index);
item.content = JSON.parse(item.content);
return item;
});
// prepend older messages
const combined = newList.concat(that.data.messages);
that.setData({
messages: combined,
page: nextPage,
total: res.total,
}, () => {
// after render, keep the previous top message in view
if (prevTopId) {
// scroll-into-view to the previous top id so viewport doesn't jump
that.setData({ scrollToId: prevTopId });
}
that.setData({ loadingMore: false });
});
}).catch(() => {
that.setData({ loadingMore: false });
});
},
requestUserProfile(e) {
console.log('user', e.detail);
const user = e.detail;
@ -102,6 +167,8 @@ Page({
"user_name": resp.user_name
}).then(resp => {
that.getMessages()
// start polling once user info exists
that.startPolling();
});
});
@ -110,6 +177,62 @@ Page({
},
// Start polling messages every 1 second. Ensures only one interval exists.
startPolling() {
if (this.poller) return; // already polling
// immediate fetch then periodic
this.getMessages();
this.poller = setInterval(() => {
this.getMessages();
}, 1000);
},
// Stop polling and clear interval
stopPolling() {
if (this.poller) {
clearInterval(this.poller);
this.poller = null;
}
},
// Scroll handlers: stop polling when user scrolls up, resume when reach bottom
onScroll(e) {
// 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 (scrollTop < last) {
// user scrolled up
if (this.data.isAtBottom) {
// only act if we were previously at bottom
this.setData({ isAtBottom: false });
this.stopPolling();
}
}
// detect reaching near top to load more
if (scrollTop <= 2) {
// if there are more messages to load and not already loading
if (!this.data.loadingMore && this.data.messages.length < this.data.total) {
this.loadMoreAtTop();
}
}
// update lastScrollTop for next event
this._lastScrollTop = scrollTop;
},
onScrollToLower() {
// user scrolled to bottom (or very near). resume polling if needed
if (!this.data.isAtBottom) {
this.setData({ 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 });
}
}
},
dismissGetUser() {
this.setData({
showGetUser: false
@ -121,14 +244,7 @@ Page({
const now = new Date();
const timeStr = this.formatTime(now);
this.setData({
messages: [{
id: 'm1',
from: 'service',
type: 'text',
content: '您好!请问有什么可以帮您?',
showTime: true,
timeStr
}],
messages: [],
scrollToId: 'm1'
});
},
@ -189,23 +305,23 @@ Page({
}, 50);
// 模拟客服回复(延迟)
setTimeout(() => {
const replyNow = new Date();
const replyTimeStr = this.formatTime(replyNow);
const reply = {
id: 's' + Date.now(),
from: 'service',
type: 'text',
content: '收到,客服正在处理中...',
showTime: false,
timeStr: replyTimeStr,
_ts: replyNow
};
this.setData({
messages: this.data.messages.concat(reply),
scrollToId: reply.id
});
}, 800);
// setTimeout(() => {
// const replyNow = new Date();
// const replyTimeStr = this.formatTime(replyNow);
// const reply = {
// id: 's' + Date.now(),
// from: 'service',
// type: 'text',
// content: '收到,客服正在处理中...',
// showTime: false,
// timeStr: replyTimeStr,
// _ts: replyNow
// };
// this.setData({
// messages: this.data.messages.concat(reply),
// scrollToId: reply.id
// });
// }, 800);
},
chooseImage() {
@ -291,9 +407,20 @@ Page({
});
},
onShow() {},
onHide() {},
onUnload() {},
onShow() {
// resume polling when page becomes visible
if (wx.getStorageSync('user_info')) {
this.startPolling();
}
},
onHide() {
// stop polling when leaving page
this.stopPolling();
},
onUnload() {
// cleanup
this.stopPolling();
},
onPullDownRefresh() {},
onReachBottom() {},
onShareAppMessage() {}

View File

@ -6,7 +6,7 @@
<text class="header-title">租号客服</text>
</view> -->
<scroll-view class="messages" scroll-y="true" scroll-into-view="{{scrollToId}}">
<scroll-view class="messages" scroll-y="true" scroll-into-view="{{scrollToId}}" bindscroll="onScroll" bindscrolltolower="onScrollToLower" lower-threshold="20">
<block wx:for="{{messages}}" wx:key="id">
<!-- 时间分割线 -->
<block wx:if="{{item.showTime}}">
@ -17,16 +17,18 @@
<image class="avatar" src="{{item.sender_id == userInfo.openid ? userAvatar : serviceAvatar}}" />
</view>
<view class="bubble">
<block wx:if="{{item.msg_type == 1}}">
<text class="msg-text">{{item.content.messages}}</text>
</block>
<block wx:elif="{{item.msg_type == 2}}">
<image src="{{item.content.messages}}" bindtap="previewImage" data-src="{{item.content.messages}}" class="msg-image" mode="aspectFill" />
</block>
<view class="bubble-wrap">
<view class="bubble">
<block wx:if="{{item.msg_type == 1}}">
<text class="msg-text">{{item.content.messages}}</text>
</block>
<block wx:elif="{{item.msg_type == 2}}">
<image src="{{item.content.messages}}" bindtap="previewImage" data-src="{{item.content.messages}}" class="msg-image" mode="aspectFill" />
</block>
</view>
<view class="send-time">{{item.send_time}}</view>
</view>
</view>
</block>
</scroll-view>

View File

@ -112,6 +112,23 @@
justify-content: center;
}
/* bubble-wrap stacks bubble above the timestamp */
.bubble-wrap {
display: flex;
flex-direction: column;
align-items: flex-start; /* will be overridden for .user */
}
/* For user messages, bubble-wrap should align to the right */
.message.user .bubble-wrap {
align-items: flex-end;
}
/* For service messages, bubble-wrap aligns to the left */
.message.service .bubble-wrap {
align-items: flex-start;
}
.avatar {
width: 80rpx;
height: 80rpx;
@ -149,6 +166,25 @@
display: block;
}
/* 发送时间样式(气泡下方) */
.send-time {
font-size: 22rpx;
color: #9aa0a6;
margin-top: 6rpx;
/* keep the timestamp small and unobtrusive */
}
/* 不同发送方时间对齐 */
.message.user .send-time {
text-align: right;
margin-right: 8rpx;
}
.message.service .send-time {
text-align: left;
margin-left: 8rpx;
}
/* 输入栏 */
.input-area {
background: #fff;
@ -264,3 +300,18 @@
border-radius: 8rpx;
border: 1px solid #eee
}
/* Keep bubble-wrap in the same order/place as the original bubble
so we don't change the horizontal layout. Only adjust order and
tiny margins - do not change existing bubble/avatar styles. */
.message.user .bubble-wrap {
order: 1; /* same as previous .bubble order for user */
margin-right: 8rpx; /* match bubble spacing */
margin-left: 0;
}
.message.service .bubble-wrap {
order: 2; /* same as previous .bubble order for service */
margin-left: 8rpx; /* match bubble spacing */
margin-right: 0;
}

View File

@ -23,21 +23,21 @@
"miniprogram": {
"list": [
{
"name": "pages/contact/index",
"pathName": "pages/contact/index",
"name": "pages/index/detail",
"pathName": "pages/index/detail",
"query": "url=1",
"scene": null,
"launchMode": "default"
},
{
"name": "pages/index/detail",
"pathName": "pages/index/detail",
"query": "url=1&app_id=wx26ad074017e1e63f",
"name": "pages/contact/index",
"pathName": "pages/contact/index",
"query": "",
"launchMode": "default",
"scene": null
}
]
}
},
"libVersion": "3.9.2"
"libVersion": "3.10.3"
}