Browse Source

更新聊天详情页

pull/1/head
徐飞洋 3 months ago
parent
commit
6aa92c4325
  1. 514
      pages/chat-detail/index.js
  2. 18
      pages/chat-detail/index.wxml
  3. 83
      pages/chat-detail/index.wxss

514
pages/chat-detail/index.js

@ -10,52 +10,42 @@ Page({
chatTitle: '聊天对象',
managerPhone: null,
timer: null,
lastLoadTime: 0 // 用于节流的时间戳
scrollTop: 0, // 用于控制scroll-view的滚动位置
scrollWithAnimation: true, // 控制滚动是否使用动画
isAtBottom: true, // 标记用户是否在聊天记录底部
hasNewMessages: false, // 标记是否有未读的新消息
newMessageCount: 0, // 未读新消息数量
lastMessageCount: 0 // 上一次消息数量,用于检测新消息
},
onLoad: function (options) {
// 支持两种参数传递方式:
// 1. 从聊天列表页传递的 id 参数
// 2. 从客服列表页传递的 userId、userName、phone 参数
let chatId = options.id || options.userId;
let managerPhone = options.phone;
if (chatId) {
if (options.id) {
this.setData({
chatId: chatId,
managerPhone: managerPhone || options.id // 如果没有直接传递phone,则使用chatId作为fallback
chatId: options.id,
managerPhone: options.id
});
// 如果有传递name或userName参数,使用该名称作为聊天标题
let chatTitle = options.name || options.userName;
if (chatTitle) {
if (options.name) {
this.setData({
chatTitle: decodeURIComponent(chatTitle)
chatTitle: decodeURIComponent(options.name)
});
} else {
// 如果没有传递名称参数,加载聊天标题
this.loadChatTitle();
}
}
this.loadMessages();
// 设置定时器,每5秒查询一次数据库同步内容
this.loadMessages(); // 使用默认值true,首次进入页面时自动滚动到底部
this.startTimer();
},
// 页面显示时重新启动定时器
onShow: function () {
if (!this.data.timer) {
this.startTimer();
}
},
// 加载聊天标题
loadChatTitle: function () {
// 直接使用已有的managerPhone获取业务员信息
const managerPhone = this.data.managerPhone;
if (managerPhone) {
// 获取业务员信息
API.getSalesPersonnelInfo(managerPhone).then(personnelInfo => {
if (personnelInfo && personnelInfo.alias) {
this.setData({ chatTitle: personnelInfo.alias });
@ -73,14 +63,14 @@ Page({
// 格式化时间显示
formatDateTime: function (dateString) {
if (!dateString) return '刚刚';
const now = new Date();
const msgDate = new Date(dateString);
const diffMs = now - msgDate;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) {
return '刚刚';
} else if (diffMins < 60) {
@ -90,7 +80,6 @@ Page({
} else if (diffDays < 7) {
return `${diffDays}天前`;
} else {
// 超过一周显示具体日期
const year = msgDate.getFullYear();
const month = (msgDate.getMonth() + 1).toString().padStart(2, '0');
const day = msgDate.getDate().toString().padStart(2, '0');
@ -100,216 +89,155 @@ Page({
}
},
loadMessages: function () {
// 节流逻辑:避免短时间内多次调用
const currentTime = Date.now();
if (currentTime - this.data.lastLoadTime < 2000) { // 2秒内不重复调用
console.log('API调用节流中,距离上次调用仅' + (currentTime - this.data.lastLoadTime) + 'ms');
return Promise.resolve();
}
// 更新上次加载时间
this.setData({
lastLoadTime: currentTime
});
return new Promise((resolve, reject) => {
this.setData({ loading: true });
// 获取当前用户的手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
// 尝试从users中获取手机号
if (userId && users[userId]) {
if (users[userId].phoneNumber) {
userPhone = users[userId].phoneNumber;
} else if (users[userId].phone) {
userPhone = users[userId].phone;
}
}
// 如果还没有获取到,尝试从全局用户信息获取
if (!userPhone) {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
if (userInfo.phoneNumber) {
userPhone = userInfo.phoneNumber;
} else if (userInfo.phone) {
userPhone = userInfo.phone;
}
}
}
// 如果还没有获取到,尝试从直接存储获取
if (!userPhone) {
if (wx.getStorageSync('phoneNumber')) {
userPhone = wx.getStorageSync('phoneNumber');
} else if (wx.getStorageSync('phone')) {
userPhone = wx.getStorageSync('phone');
}
}
// 重要修复:当有managerPhone时,应该使用managerPhone作为chatId传递给服务器
// 因为服务器端查询需要的是手机号,而不是客服人员的ID
const chatIdForApi = this.data.managerPhone || this.data.chatId;
// 使用新添加的API获取聊天记录
API.getChatMessages(chatIdForApi, userPhone).then(res => {
if (Array.isArray(res)) {
// 处理每条消息,确定发送者和格式化时间
const processedMessages = res.map(message => {
// 判断消息是发送还是接收
const sender = (message.sender_phone === userPhone) ? 'me' : 'other';
// 格式化时间
const time = this.formatDateTime(message.created_at);
return {
id: message.id,
content: message.content,
sender: sender,
time: time,
originalTime: message.created_at,
sender_phone: message.sender_phone,
receiver_phone: message.receiver_phone
};
});
// 按时间顺序排序(升序)
processedMessages.sort((a, b) => {
return new Date(a.originalTime) - new Date(b.originalTime);
});
this.setData({
messages: processedMessages,
loading: false,
scrollToView: processedMessages.length > 0 ? 'latest-message' : ''
});
// 如果managerPhone尚未设置,尝试从聊天记录中获取
if (!this.data.managerPhone && processedMessages.length > 0) {
const firstMessage = processedMessages[0];
const managerPhone = (firstMessage.sender_phone === userPhone) ? firstMessage.receiver_phone : firstMessage.sender_phone;
this.setData({ managerPhone: managerPhone });
}
} else {
this.setData({
messages: [],
loading: false
});
}
resolve();
}).catch(error => {
console.error('加载聊天记录失败:', error);
this.setData({
loading: false
});
wx.showToast({
title: '加载聊天记录失败',
icon: 'none'
});
reject(error);
});
});
},
loadMoreMessages: function () {
// 加载更多历史消息
if (this.data.loading) return;
loadMessages: function (shouldScrollToBottom = true) {
this.setData({ loading: true });
// 获取当前用户的手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
// 尝试从users中获取手机号
if (userId && users[userId]) {
if (users[userId].phoneNumber) {
userPhone = users[userId].phoneNumber;
} else if (users[userId].phone) {
userPhone = users[userId].phone;
}
userPhone = users[userId].phoneNumber || users[userId].phone;
}
// 如果还没有获取到,尝试从全局用户信息获取
if (!userPhone) {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
if (userInfo.phoneNumber) {
userPhone = userInfo.phoneNumber;
} else if (userInfo.phone) {
userPhone = userInfo.phone;
}
}
userPhone = userInfo ? (userInfo.phoneNumber || userInfo.phone) : null;
}
// 如果还没有获取到,尝试从直接存储获取
if (!userPhone) {
if (wx.getStorageSync('phoneNumber')) {
userPhone = wx.getStorageSync('phoneNumber');
} else if (wx.getStorageSync('phone')) {
userPhone = wx.getStorageSync('phone');
}
userPhone = wx.getStorageSync('phoneNumber') || wx.getStorageSync('phone');
}
// 获取最早的消息时间,用于分页加载
const earliestTime = this.data.messages.length > 0 ? this.data.messages[0].originalTime : null;
// 使用API获取更多聊天记录(带分页参数)
const chatIdForApi = this.data.managerPhone || this.data.chatId;
API.getChatMessages(chatIdForApi, userPhone, { before: earliestTime }).then(res => {
if (Array.isArray(res) && res.length > 0) {
// 处理每条消息,确定发送者和格式化时间
if (!userPhone) {
this.setData({ loading: false });
wx.showToast({
title: '请先登录',
icon: 'none'
});
return;
}
// 获取聊天消息
API.getChatMessages(this.data.chatId, userPhone).then(res => {
if (Array.isArray(res)) {
// 处理消息并排序
const processedMessages = res.map(message => {
// 判断消息是发送还是接收
const sender = (message.sender_phone === userPhone) ? 'me' : 'other';
// 格式化时间
const time = this.formatDateTime(message.created_at);
return {
id: message.id,
content: message.content,
sender: sender,
time: time,
originalTime: message.created_at
originalTime: message.created_at,
isNew: false // 服务器返回的消息不显示新消息动画
};
});
// 按时间顺序排序(升序
// 按创建时间升序排序(旧消息在前,新消息在后)
processedMessages.sort((a, b) => {
return new Date(a.originalTime) - new Date(b.originalTime);
});
// 在更新数据之前,保存当前的最后一条消息,用于检测新消息
const currentLastMessage = this.data.messages[this.data.messages.length - 1];
const newLastMessage = processedMessages[processedMessages.length - 1];
// 更新消息列表
this.setData({
messages: [...processedMessages, ...this.data.messages],
messages: processedMessages,
loading: false
}, () => {
// 在数据更新完成的回调中执行逻辑,确保DOM已更新
// 首次加载消息或明确需要滚动时,直接滚动到底部
if (shouldScrollToBottom) {
this.scrollToBottom(false);
}
else {
// 检查是否有新消息
// 比较最新消息的ID是否与当前显示的最后一条消息ID不同
// 有新消息的条件:
// 1. 消息列表不为空
// 2. 新消息列表的最后一条消息与当前显示的最后一条消息ID不同
const hasNewMessages = newLastMessage && currentLastMessage &&
newLastMessage.id !== currentLastMessage.id;
console.log('新消息检测:', { hasNewMessages, isAtBottom: this.data.isAtBottom });
if (hasNewMessages) {
if (this.data.isAtBottom) {
// 如果用户当前在底部,自动滚动到底部
console.log('用户在底部,自动滚动到新消息');
this.scrollToBottom(true);
} else {
// 如果用户不在底部,显示新消息提示按钮
console.log('用户不在底部,显示新消息提示');
this.setData({
hasNewMessages: true,
newMessageCount: this.data.newMessageCount + 1
});
}
}
}
});
} else {
this.setData({
messages: [],
loading: false
});
wx.showToast({
title: '没有更多消息了',
icon: 'none'
});
}
}).catch(error => {
console.error('加载更多聊天记录失败:', error);
this.setData({
loading: false
});
console.error('加载聊天记录失败:', error);
this.setData({ loading: false });
wx.showToast({
title: '加载更多消息失败',
title: '加载聊天记录失败',
icon: 'none'
});
});
},
// 滚动到底部
scrollToBottom: function (withAnimation = true) {
console.log('执行滚动到底部操作,是否使用动画:', withAnimation);
// 使用wx.nextTick确保在DOM更新后执行
wx.nextTick(() => {
this.setData({
scrollWithAnimation: withAnimation,
scrollTop: 999999
}, () => {
// 滚动完成后清除新消息提示
setTimeout(() => {
this.setData({
hasNewMessages: false,
newMessageCount: 0
});
console.log('滚动完成,清除新消息提示');
}, 100);
});
});
},
// 监听滚动事件,检测是否在底部
onScroll: function (e) {
const { scrollTop, scrollHeight, clientHeight } = e.detail;
// 计算滚动位置与底部的距离
const distanceToBottom = scrollHeight - scrollTop - clientHeight;
// 当距离底部小于100rpx(约50px)时,标记为在底部
const isAtBottom = distanceToBottom < 100;
console.log('滚动检测:', { scrollTop, scrollHeight, clientHeight, distanceToBottom, isAtBottom });
// 直接更新状态,确保滚动位置实时反映
this.setData({
isAtBottom: isAtBottom
});
},
onInputChange: function (e) {
this.setData({
inputValue: e.detail.value
@ -319,209 +247,109 @@ Page({
sendMessage: function () {
const content = this.data.inputValue.trim();
if (!content) return;
// 获取当前用户的手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
// 尝试从users中获取手机号
if (userId && users[userId]) {
if (users[userId].phoneNumber) {
userPhone = users[userId].phoneNumber;
} else if (users[userId].phone) {
userPhone = users[userId].phone;
}
userPhone = users[userId].phoneNumber || users[userId].phone;
}
// 如果还没有获取到,尝试从全局用户信息获取
if (!userPhone) {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
if (userInfo.phoneNumber) {
userPhone = userInfo.phoneNumber;
} else if (userInfo.phone) {
userPhone = userInfo.phone;
}
}
userPhone = userInfo ? (userInfo.phoneNumber || userInfo.phone) : null;
}
// 如果还没有获取到,尝试从直接存储获取
if (!userPhone) {
if (wx.getStorageSync('phoneNumber')) {
userPhone = wx.getStorageSync('phoneNumber');
} else if (wx.getStorageSync('phone')) {
userPhone = wx.getStorageSync('phone');
}
userPhone = wx.getStorageSync('phoneNumber') || wx.getStorageSync('phone');
}
// 获取客服手机号
let managerPhone = this.data.managerPhone;
if (!userPhone) {
wx.showToast({
title: '获取用户手机号失败,请重新登录',
title: '请先登录',
icon: 'none'
});
return;
}
// 如果managerPhone尚未设置,尝试从聊天记录中获取
if (!managerPhone && this.data.messages && this.data.messages.length > 0) {
// 从第一条消息中提取与当前用户不同的手机号作为managerPhone
const firstMessage = this.data.messages[0];
if (firstMessage && firstMessage.sender_phone) {
managerPhone = (firstMessage.sender_phone === userPhone) ? firstMessage.receiver_phone : firstMessage.sender_phone;
this.setData({ managerPhone: managerPhone });
}
}
// 如果managerPhone仍然不存在,尝试直接从API获取
const managerPhone = this.data.managerPhone;
if (!managerPhone) {
// 显示加载提示
wx.showLoading({
title: '获取客服信息中...',
});
// 先尝试从聊天列表获取
API.getChatList(userPhone).then(chatList => {
if (Array.isArray(chatList)) {
// 找到当前聊天项
const currentChat = chatList.find(item => item.id === this.data.chatId);
if (currentChat && currentChat.manager_phone) {
managerPhone = currentChat.manager_phone;
this.setData({ managerPhone: managerPhone });
// 获取到managerPhone后立即发送消息
this.sendMessageAfterGetManagerPhone(userPhone, managerPhone, content);
} else {
// 如果聊天列表中没有找到,尝试直接从聊天记录获取
this.loadMessages().then(() => {
if (this.data.messages.length > 0) {
const firstMessage = this.data.messages[0];
managerPhone = (firstMessage.sender_phone === userPhone) ? firstMessage.receiver_phone : firstMessage.sender_phone;
this.setData({ managerPhone: managerPhone });
this.sendMessageAfterGetManagerPhone(userPhone, managerPhone, content);
} else {
wx.hideLoading();
wx.showToast({
title: '获取客服信息失败,请稍候重试',
icon: 'none'
});
}
}).catch(() => {
wx.hideLoading();
wx.showToast({
title: '获取客服信息失败,请稍候重试',
icon: 'none'
});
});
}
} else {
wx.hideLoading();
wx.showToast({
title: '获取客服信息失败,请稍候重试',
icon: 'none'
});
}
}).catch(() => {
wx.hideLoading();
wx.showToast({
title: '获取客服信息失败,请稍候重试',
icon: 'none'
});
wx.showToast({
title: '获取聊天对象失败',
icon: 'none'
});
return;
}
// 正常发送消息
this.sendMessageAfterGetManagerPhone(userPhone, managerPhone, content);
},
// 当获取到managerPhone后发送消息
sendMessageAfterGetManagerPhone: function (userPhone, managerPhone, content) {
// 隐藏加载提示
wx.hideLoading();
// 创建临时消息
const tempId = Date.now();
const newMessage = {
id: tempId,
const tempMessage = {
id: Date.now(),
content: content,
sender: 'me',
time: '刚刚'
time: '刚刚',
originalTime: new Date().toISOString(),
isNew: true // 标识新消息
};
// 先在本地显示消息
// 更新消息列表
const newMessages = [...this.data.messages, tempMessage];
this.setData({
messages: [...this.data.messages, newMessage],
inputValue: '',
scrollToView: 'latest-message'
messages: newMessages,
inputValue: ''
}, () => {
// 发送消息时无论用户在哪个位置,都自动滚动到底部
this.scrollToBottom(true);
// 短暂延迟后移除isNew标记,确保动画完成
setTimeout(() => {
const updatedMessages = this.data.messages.map(msg => ({
...msg,
isNew: false
}));
this.setData({ messages: updatedMessages });
}, 300);
});
// 调用API发送消息到数据库
// 发送到服务器
API.sendMessage(userPhone, managerPhone, content).then(res => {
console.log('消息发送成功:', res);
// 如果需要,可以更新消息的id为服务器返回的id
if (res && res.data && res.data.id) {
const updatedMessages = this.data.messages.map(msg => {
if (msg.id === tempId) {
return {
...msg,
id: res.data.id
};
}
return msg;
});
this.setData({
messages: updatedMessages
});
}
}).catch(error => {
console.error('消息发送失败:', error);
wx.showToast({
title: '消息发送失败,请稍后重试',
title: '消息发送失败',
icon: 'none'
});
});
// 移除模拟对方回复的代码,改为定时器同步数据库内容
},
// 启动定时器
startTimer: function () {
// 清除之前的定时器(如果有)
if (this.data.timer) {
clearInterval(this.data.timer);
}
// 设置新的定时器,每5秒调用一次loadMessages方法
// 每5秒同步一次消息,但不自动滚动到底部
const timer = setInterval(() => {
this.loadMessages();
this.loadMessages(false);
}, 5000);
this.setData({
timer: timer
});
},
// 页面隐藏时清除定时器
onHide: function () {
if (this.data.timer) {
clearInterval(this.data.timer);
this.setData({
timer: null
});
this.setData({ timer: null });
}
},
// 页面卸载时清除定时器
onUnload: function () {
if (this.data.timer) {
clearInterval(this.data.timer);
this.setData({
timer: null
});
this.setData({ timer: null });
}
},
}
});

18
pages/chat-detail/index.wxml

@ -1,4 +1,4 @@
<view class="container">
<view class="chat-page-container">
<view class="chat-header">
<view class="header-back" bindtap="onBack">
<text>←</text>
@ -11,7 +11,14 @@
</view>
</view>
<scroll-view class="chat-content" scroll-y bindscrolltolower="loadMoreMessages" scroll-into-view="{{scrollToView}}" scroll-with-animation="true">
<scroll-view
id="chatScrollView"
scroll-y
scroll-top="{{scrollTop}}"
scroll-with-animation="{{scrollWithAnimation}}"
class="chat-content"
bindscroll="onScroll"
>
<view class="chat-messages">
<block wx:if="{{loading && messages.length === 0}}">
<view class="loading">加载中...</view>
@ -21,7 +28,7 @@
</block>
<block wx:else>
<!-- 动态渲染聊天消息 -->
<view wx:for="{{messages}}" wx:key="id" class="message-item {{item.sender}}" id="{{index === messages.length - 1 ? 'latest-message' : 'message-' + item.id}}">
<view wx:for="{{messages}}" wx:key="id" class="message-item {{item.sender}} {{item.isNew ? 'new' : ''}}">
<!-- 对方消息 -->
<view wx:if="{{item.sender === 'other'}}" class="avatar">
<image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
@ -39,6 +46,11 @@
</view>
</scroll-view>
<!-- 新消息提示按钮 -->
<view wx:if="{{hasNewMessages}}" class="new-message-tip" bindtap="scrollToBottom">
<text>有{{newMessageCount}}条新消息,点击查看</text>
</view>
<view class="chat-input-area">
<input class="message-input" placeholder="请输入消息" bindinput="onInputChange" value="{{inputValue}}"></input>
<button class="send-button" bindtap="sendMessage">发送</button>

83
pages/chat-detail/index.wxss

@ -1,8 +1,11 @@
.container {
background-color: #f0f0f0;
min-height: 100vh;
.chat-page-container {
height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #f0f0f0;
position: relative;
overflow: hidden;
}
.chat-header {
@ -39,9 +42,10 @@
color: #333;
}
.chat-scroll-area,
.chat-content {
flex: 1;
padding: 20rpx 20rpx 120rpx 20rpx; /* 增加底部padding,避免被输入框遮挡 */
padding: 20rpx 20rpx 140rpx 20rpx; /* 增加底部padding,避免被固定输入框遮挡 */
overflow-y: auto;
}
@ -56,6 +60,16 @@
align-items: flex-end;
gap: 16rpx;
max-width: 100%;
opacity: 1;
transform: translateY(0) scale(1);
transition: all 0.3s ease-out;
animation-fill-mode: forwards;
}
/* 新消息初始状态 */
.message-item.new {
opacity: 0;
transform: translateY(20rpx) scale(0.95);
}
.message-item.other {
@ -63,7 +77,12 @@
}
.message-item.me {
flex-direction: row-reverse;
flex-direction: row;
justify-content: flex-end;
}
.message-item.me .avatar {
margin-right: 16rpx;
}
.avatar {
@ -86,16 +105,44 @@
position: relative;
}
/* 对方消息气泡 */
.message-item.other .message-bubble {
background-color: #fff;
border-bottom-left-radius: 8rpx;
}
/* 对方消息气泡尖角 */
.message-item.other .message-bubble::before {
content: '';
position: absolute;
left: -16rpx;
bottom: 16rpx;
width: 0;
height: 0;
border-top: 16rpx solid transparent;
border-bottom: 16rpx solid transparent;
border-right: 16rpx solid #fff;
}
/* 自己消息气泡 */
.message-item.me .message-bubble {
background-color: #92E3A9;
border-bottom-right-radius: 8rpx;
}
/* 自己消息气泡尖角 */
.message-item.me .message-bubble::before {
content: '';
position: absolute;
right: -16rpx;
bottom: 16rpx;
width: 0;
height: 0;
border-top: 16rpx solid transparent;
border-bottom: 16rpx solid transparent;
border-left: 16rpx solid #92E3A9;
}
.message-content {
display: block;
font-size: 28rpx;
@ -123,11 +170,35 @@
padding: 16rpx 24rpx;
border-top: 1rpx solid #e8e8e8;
gap: 20rpx;
z-index: 100;
height: 100rpx;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
box-sizing: border-box;
}
/* 新消息提示按钮样式 */
.new-message-tip {
background-color: #FF6B81;
color: white;
font-size: 26rpx;
padding: 12rpx 24rpx;
border-radius: 28rpx;
position: fixed;
bottom: 120rpx;
right: 30rpx;
z-index: 99;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.15);
opacity: 0.9;
transition: all 0.3s ease;
}
.new-message-tip:hover {
opacity: 1;
transform: translateY(-2rpx);
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.2);
}
.message-input {

Loading…
Cancel
Save