邹方成 7209e5a815 refactor(消息列表): 重构消息处理逻辑,统一ID生成和排序
将消息处理逻辑提取为独立函数,统一处理ID生成和内容解析
添加消息排序功能,确保消息按ID正确排序
移除硬编码的ID生成方式,优先使用服务器返回的ID
2025-10-31 00:07:08 +08:00

495 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/contact/index.js
const request = require('../../api/request.js');
Page({
/**
* 页面的初始数据
*/
data: {
messages: [],
inputText: '',
scrollToId: '',
// 使用本地头像
userAvatar: '/static/user.png',
serviceAvatar: '/static/contact.png',
showGetUser: false,
userInfo: {},
appid: '',
page: 1,
page_size: 100,
openid: '',
total: 0,
// 是否滚动在底部
isAtBottom: true,
// 正在加载更多(顶部分页)
loadingMore: false,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const accountInfo = wx.getAccountInfoSync();
this.setData({
appid: accountInfo.miniProgram.appId,
});
// 检查是否已有用户信息
if (wx.getStorageSync('user_info')) {
this.setData({
showGetUser: false,
userInfo: wx.getStorageSync('user_info'),
});
this.getMessages()
// start polling when page loads and user is present
this.startPolling();
} else {
// 直接进行静默登录,不显示授权弹窗
this.silentLogin();
}
},
// onShow() {
// this.getMessages()
// },
getMessages() {
const userInfo = wx.getStorageSync('user_info');
const that = this;
request('app/messages', 'get', {
app_id: that.data.appid,
user_id: userInfo.openid,
page: that.data.page,
page_size: that.data.page_size,
}).then(res => {
// 处理消息列表,使用统一的消息处理函数
const list = that.processMessages(res.list);
// 根据ID进行排序确保ID最大的最新的消息在最后
const sortedList = that.sortMessagesByID(list);
that.setData({
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
});
})
},
// 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 => {
// 处理新加载的消息,使用统一的消息处理函数
const newList = that.processMessages(res.list);
// 合并新消息和现有消息
const combined = newList.concat(that.data.messages);
// 根据ID进行排序确保ID最大的最新的消息在最后
const sortedCombined = that.sortMessagesByID(combined);
that.setData({
messages: sortedCombined,
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 });
});
},
// 统一的消息处理函数
processMessages(messageList) {
return messageList.map((item, index) => {
// 优先使用服务器返回的ID如果没有则使用创建时间戳
if (item.id && !item.id.toString().startsWith('m') && !item.id.toString().startsWith('u')) {
item.id = 'm' + item.id;
} else if (item.create_time) {
// 使用创建时间的时间戳作为ID
item.id = 'm' + new Date(item.create_time).getTime();
} else if (!item.id) {
// 兜底方案:使用当前时间戳 + 索引
item.id = 'm' + (Date.now() + index);
}
// 解析消息内容
if (typeof item.content === 'string') {
try {
item.content = JSON.parse(item.content);
} catch (e) {
console.warn('消息内容解析失败:', item.content);
item.content = { messages: item.content };
}
}
return item;
});
},
// 消息排序函数
sortMessagesByID(messages) {
return messages.sort((a, b) => {
// 提取ID的数字部分
const getNumericId = (id) => {
if (id.startsWith('u')) {
// 用户消息:使用时间戳
return parseInt(id.replace('u', '')) || 0;
} else if (id.startsWith('m')) {
// 服务器消息:如果是时间戳格式(长度>10直接使用否则转换为时间戳格式
const numId = parseInt(id.replace('m', '')) || 0;
// 如果是小数字比如1,2,3可能是服务器的序号ID需要转换为可比较的时间戳
// 这里假设小于1000000000000约2001年的都是序号ID
if (numId < 1000000000000) {
// 将小的序号ID映射到一个较早的时间范围确保它们排在用户消息之前
return numId + 1000000000; // 加上一个基数,但仍然小于用户消息的时间戳
}
return numId;
}
return 0;
};
const idA = getNumericId(a.id);
const idB = getNumericId(b.id);
return idA - idB;
});
},
// 静默登录方法只需要appid和js_code
silentLogin() {
const that = this;
const accountInfo = wx.getAccountInfoSync();
wx.login({
success: function (res) {
request('wechat/miniprogram/login', 'post', {
"app_id": accountInfo.miniProgram.appId,
"js_code": res.code
}).then(resp => {
wx.setStorageSync('user_info', resp);
that.setData({
showGetUser: false,
userInfo: resp
});
request('app/user/create', 'post', {
"app_id": accountInfo.miniProgram.appId,
"user_avatar": resp.user_avatar || '/static/user.png',
"user_id": resp.openid,
"user_name": resp.user_name || '微信用户'
}).then(resp => {
that.getMessages()
// start polling once user info exists
that.startPolling();
});
}).catch(err => {
console.error('静默登录失败:', err);
// 如果静默登录失败,可以选择显示授权弹窗或其他处理
that.setData({
showGetUser: true
});
});
},
fail: function(err) {
console.error('wx.login失败:', err);
}
});
},
// 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 });
}
}
},
onReady() {
// 初始化欢迎消息,带时间分割线
const now = new Date();
const timeStr = this.formatTime(now);
this.setData({
messages: [],
scrollToId: 'm1'
});
},
onInput(e) {
this.setData({
inputText: e.detail.value
});
},
sendText() {
const text = this.data.inputText && this.data.inputText.trim();
if (!text) return;
const now = new Date();
const id = 'u' + Date.now();
// const timeStr = this.formatTime(now);
// // 判断是否需要显示时间分割线
// let showTime = false;
// const lastMsg = this.data.messages.length ? this.data.messages[this.data.messages.length - 1] : null;
// if (!lastMsg || now - (lastMsg._ts || now) > 5 * 60 * 1000) showTime = true;
const msg = {
id,
content: {
messages: text
},
"msg_type": 1,
receiver_id: '',
send_time: "刚刚",
sender_id: this.data.userInfo.openid,
sender_name: this.data.userInfo.user_name
};
// 添加新消息并重新排序
const newMessages = this.data.messages.concat(msg);
const sortedMessages = this.sortMessagesByID(newMessages);
this.setData({
messages: sortedMessages,
inputText: ''
});
const accountInfo = wx.getAccountInfoSync();
const userInfo = wx.getStorageSync('user_info')
request('app/send_message', 'POST', {
"app_id": accountInfo.miniProgram.appId,
"content": JSON.stringify({
messages: text
}),
"from_user_id": userInfo.openid,
"from_user_name": userInfo.user_name,
"msg_type": 1
})
// 等待渲染,确保 scroll-into-view 生效
setTimeout(() => {
// 滚动到最新的消息(排序后的最后一条)
const latestMessage = sortedMessages[sortedMessages.length - 1];
this.setData({
scrollToId: latestMessage ? latestMessage.id : id
});
}, 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);
},
chooseImage() {
const that = this;
wx.chooseImage({
count: 1, // 选择图片的数量
mediaType: ['image'], // 仅选择图片
sizeType: ['compressed', 'original'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths;
console.log('选择的图片路径:', res);
if (tempFilePaths && tempFilePaths.length) {
const userInfo = wx.getStorageSync('user_info')
// const now = new Date();
const id = 'u' + Date.now();
// const timeStr = that.formatTime(now);
// let showTime = false;
// const lastMsg = that.data.messages.length ? that.data.messages[that.data.messages.length - 1] : null;
// if (!lastMsg || now - (lastMsg._ts || now) > 5 * 60 * 1000) showTime = true;
const msg = {
id,
msg_type: '2',
receiver_id: '',
content: {
messages: tempFilePaths[0]
},
sender_id: userInfo.openid,
sender_name: userInfo.user_name,
receiver_id: '',
send_time: "刚刚",
};
const list = that.data.messages
list.push(msg);
that.setData({
messages: list
});
setTimeout(() => {
that.setData({
scrollToId: id
});
}, 50);
wx.uploadFile({
filePath: tempFilePaths[0], // 图片临时文件路径
name: 'file', // 服务器接收文件的字段名,需与后端对应
url: 'https://mini-chat.1024tool.vip/admin/upload/image', // 服务器接收图片的接口地址
success: (res) => {
const data = JSON.parse(res.data);
console.log('上传成功', data);
const accountInfo = wx.getAccountInfoSync();
request('app/send_message', 'POST', {
"app_id": accountInfo.miniProgram.appId,
"content": JSON.stringify({
messages: 'https://mini-chat.1024tool.vip/'+ data.preview_image_url
}),
"from_user_id": userInfo.openid,
"from_user_name": userInfo.user_name,
"msg_type": 2
})
},
fail: (err) => {
console.error('上传失败', err);
}
});
// 模拟客服返回图片确认
// setTimeout(() => {
// const replyNow = new Date();
// const replyTimeStr = that.formatTime(replyNow);
// const reply = {
// id: 's' + Date.now(),
// from: 'service',
// type: 'text',
// content: '已收到图片,感谢!',
// showTime: false,
// timeStr: replyTimeStr,
// _ts: replyNow
// };
// that.setData({
// messages: that.data.messages.concat(reply)
// });
// setTimeout(() => {
// that.setData({
// scrollToId: reply.id
// });
// }, 50);
// }, 1200);
}
}
});
},
// 时间格式化
formatTime(date) {
const y = date.getFullYear();
const m = (date.getMonth() + 1).toString().padStart(2, '0');
const d = date.getDate().toString().padStart(2, '0');
const h = date.getHours().toString().padStart(2, '0');
const min = date.getMinutes().toString().padStart(2, '0');
return `${y}${m}${d}${h}:${min}`;
},
previewImage(e) {
const src = e.currentTarget.dataset.src;
if (!src) return;
wx.previewImage({
urls: [src],
current: src
});
},
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() { }
});