Browse Source

Fix chat functionality: correct time display, message sync, and role-based UI

pull/1/head
Default User 3 months ago
parent
commit
29ddaf4f62
  1. 498
      pages/chat-detail/index.js
  2. 14
      pages/chat-detail/index.wxml
  3. 16
      pages/customer-service/index.js
  4. 2
      pages/customer-service/index.wxml
  5. 63
      pages/message-list/index.js

498
pages/chat-detail/index.js

@ -17,7 +17,14 @@ Page({
connectionStatus: 'disconnected', // 连接状态: disconnected, connecting, connected, error connectionStatus: 'disconnected', // 连接状态: disconnected, connecting, connected, error
connectionMessage: '', // 连接状态提示消息 connectionMessage: '', // 连接状态提示消息
isMockMode: false, // 默认为真实WebSocket通信模式 isMockMode: false, // 默认为真实WebSocket通信模式
isManager: false // 是否是与客服的聊天 isManager: false, // 是否是与客服的聊天
scrollToMessage: '', // 用于滚动到指定消息的ID
scrollTop: 0, // 用于scroll-top属性
showScrollToBottomBtn: false, // 是否显示"到达最底部"按钮
isNearBottom: true, // 用户是否接近底部
lastScrollTop: 0, // 上次滚动位置
scrollHeight: 0, // 滚动区域高度
contentHeight: 0 // 内容高度
}, },
/** /**
@ -50,31 +57,78 @@ Page({
console.log('从本地存储加载了', storedMessages.length, '条历史消息,存储键:', successKey); console.log('从本地存储加载了', storedMessages.length, '条历史消息,存储键:', successKey);
// 处理消息数据,确保包含必要的字段 // 处理消息数据,确保包含必要的字段
const messagesWithRequiredFields = storedMessages.map((msg, index) => ({ const messagesWithRequiredFields = storedMessages.map((msg, index) => {
...msg, // 获取原始时间
// 确保消息有id字段,避免被稳定性检查过滤 const originalTime = msg.time;
id: msg.id || `local_msg_${Date.now()}_${index}`, let formattedTime;
// 确保消息类型正确
type: msg.type || 'message', // 调试:记录原始时间
// 确保有sender字段 console.log('=== 历史消息时间调试 ===');
sender: msg.sender || (msg.isSelf ? 'me' : 'other'), console.log('原始时间:', originalTime);
// 确保有content字段 console.log('原始时间类型:', typeof originalTime);
content: msg.content || '',
// 确保有time字段 // 无论是否有原始时间,都重新格式化,确保正确的北京时间显示
time: msg.time || this.getFormattedTime(), if (originalTime) {
// 重新生成shortTime字段 // 重新格式化所有时间,确保正确的北京时间
shortTime: msg.time ? this.extractShortTime(msg.time) : this.getShortTime(), if (typeof originalTime === 'number') {
// 确保有status字段 // 数字时间戳,重新格式化
status: msg.status || 'sent', formattedTime = this.formatTimestampToTime(originalTime);
// 确保有senderType字段 console.log('数字时间戳重新格式化后:', formattedTime);
senderType: msg.senderType || 'unknown', } else if (typeof originalTime === 'string') {
// 确保有isRead字段 if (originalTime.includes('T')) {
isRead: msg.isRead || true, // ISO格式字符串,重新格式化
// 确保有timestamp字段 formattedTime = this.formatServerTime(originalTime);
timestamp: msg.timestamp || Date.now(), console.log('ISO字符串重新格式化后:', formattedTime);
// 确保有showTime字段 } else {
showTime: msg.showTime || false // 其他字符串格式,检查是否是"月-日 时:分"格式
})); const dateTimeMatch = originalTime.match(/^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})$/);
if (dateTimeMatch) {
// 已经是正确的北京时间格式,直接使用
formattedTime = originalTime;
console.log('直接使用正确格式的时间:', formattedTime);
} else {
// 其他格式,使用当前时间
formattedTime = this.getFormattedTime();
console.log('格式不正确,使用当前时间:', formattedTime);
}
}
} else {
// 其他类型,使用当前时间
formattedTime = this.getFormattedTime();
console.log('其他类型,使用当前时间:', formattedTime);
}
} else {
// 没有时间,使用当前时间
formattedTime = this.getFormattedTime();
console.log('没有原始时间,使用当前时间:', formattedTime);
}
return {
...msg,
// 确保消息有id字段,避免被稳定性检查过滤
id: msg.id || `local_msg_${Date.now()}_${index}`,
// 确保消息类型正确
type: msg.type || 'message',
// 确保有sender字段
sender: msg.sender || (msg.isSelf ? 'me' : 'other'),
// 确保有content字段
content: msg.content || '',
// 确保有正确格式的time字段(北京时间)
time: formattedTime,
// 重新生成shortTime字段
shortTime: this.extractShortTime(formattedTime),
// 确保有status字段
status: msg.status || 'sent',
// 确保有senderType字段
senderType: msg.senderType || 'unknown',
// 确保有isRead字段
isRead: msg.isRead || true,
// 确保有timestamp字段
timestamp: msg.timestamp || Date.now(),
// 确保有showTime字段
showTime: msg.showTime || false
};
});
// 重新处理时间显示逻辑 // 重新处理时间显示逻辑
const processedMessages = this.processMessageTimes(messagesWithRequiredFields); const processedMessages = this.processMessageTimes(messagesWithRequiredFields);
@ -658,12 +712,28 @@ Page({
// 设置WebSocket事件监听 // 设置WebSocket事件监听
this.setupWebSocketListeners(); this.setupWebSocketListeners();
// 连接WebSocket服务器 // 关键修复:使用应用级别的WebSocket连接,不再重新创建连接
socketManager.connect(wsUrl, { // 检查应用级别连接状态
maxReconnectAttempts: 5, const app = getApp();
reconnectInterval: 3000, if (app.globalData.webSocketManager && app.globalData.webSocketManager.isConnected) {
heartbeatTime: 30000 // 应用级别连接已存在,使用它
}); console.log('使用应用级别WebSocket连接');
this.setData({
connectionStatus: 'connected',
connectionMessage: '已连接'
});
// 发送认证消息
this.sendAuthMessage();
} else {
// 应用级别连接不存在,创建新连接
console.log('创建新的WebSocket连接');
// 连接WebSocket服务器
socketManager.connect(wsUrl, {
maxReconnectAttempts: 5,
reconnectInterval: 3000,
heartbeatTime: 30000
});
}
// 监听网络状态变化 // 监听网络状态变化
this.startNetworkListener(); this.startNetworkListener();
@ -677,6 +747,27 @@ Page({
console.log('延迟获取历史消息,确保WebSocket连接完全建立'); console.log('延迟获取历史消息,确保WebSocket连接完全建立');
this.fetchHistoryMessages(); this.fetchHistoryMessages();
}, 1500); }, 1500);
// 关键修复:注册全局新消息处理函数,确保在聊天页面时能收到新消息
this.originalGlobalMessageHandler = app.globalData.onNewMessage;
app.globalData.onNewMessage = (newMessage) => {
console.log('全局新消息处理函数被调用,消息:', newMessage);
// 检查消息是否与当前聊天相关
const isCurrentChatMessage = newMessage.serverData &&
(newMessage.serverData.conversationId === this.data.conversationId ||
newMessage.serverData.senderId === this.data.userId ||
newMessage.serverData.receiverId === this.data.userId);
if (isCurrentChatMessage) {
// 如果是当前聊天的消息,直接添加到消息列表
this.processServerMessage(newMessage.serverData);
}
// 调用原始处理函数
if (this.originalGlobalMessageHandler && typeof this.originalGlobalMessageHandler === 'function') {
this.originalGlobalMessageHandler(newMessage);
}
};
}, },
/** /**
@ -1245,6 +1336,22 @@ Page({
const contentType = messageData.contentType || (messageData.data && messageData.data.contentType) || 1; const contentType = messageData.contentType || (messageData.data && messageData.data.contentType) || 1;
const isImage = contentType === 2 || (messageData.fileUrl && contentType !== 1); const isImage = contentType === 2 || (messageData.fileUrl && contentType !== 1);
// 调试:记录服务器返回的原始时间数据
console.log('=== 时间调试 ===');
console.log('原始服务器时间:', messageData.createdAt);
console.log('原始服务器时间类型:', typeof messageData.createdAt);
// 格式化时间,添加详细调试
let formattedTime;
if (messageData.createdAt) {
console.log('调用formatServerTime处理时间:', messageData.createdAt);
formattedTime = this.formatServerTime(messageData.createdAt);
console.log('格式化后的时间:', formattedTime);
} else {
formattedTime = this.getFormattedTime();
console.log('使用当前时间:', formattedTime);
}
// 创建符合前端UI要求的消息对象 // 创建符合前端UI要求的消息对象
const newMessage = { const newMessage = {
id: messageData.messageId || messageData.id || 'msg_' + Date.now(), id: messageData.messageId || messageData.id || 'msg_' + Date.now(),
@ -1254,7 +1361,7 @@ Page({
sender: sender, sender: sender,
senderType: messageData.senderType || 'unknown', // 保存发送方类型 senderType: messageData.senderType || 'unknown', // 保存发送方类型
direction: messageData.direction || 'unknown', // 保存消息方向 direction: messageData.direction || 'unknown', // 保存消息方向
time: messageData.createdAt ? this.formatServerTime(messageData.createdAt) : this.getFormattedTime(), time: formattedTime,
status: 'sent', status: 'sent',
serverData: messageData, // 保存完整的服务器数据 serverData: messageData, // 保存完整的服务器数据
// 【关键修复】添加真实用户消息标记 // 【关键修复】添加真实用户消息标记
@ -1264,7 +1371,9 @@ Page({
userId: this.data.userId, userId: this.data.userId,
isRealUserId: this.data.isRealUserId, isRealUserId: this.data.isRealUserId,
matchedUser: isRealUserMessage, matchedUser: isRealUserMessage,
messageTimestamp: messageData.createdAt || Date.now() messageTimestamp: messageData.createdAt || Date.now(),
rawCreatedAt: messageData.createdAt,
formattedTime: formattedTime
} }
}; };
@ -1348,11 +1457,19 @@ Page({
// 保存到本地存储 // 保存到本地存储
this.saveMessagesToStorage(messages); this.saveMessagesToStorage(messages);
// 更新页面数据 // 更新页面数据,确保滚动在数据更新后执行
this.setData({ messages }); this.setData({ messages }, () => {
// 根据当前滚动位置决定是否自动滚动到底部
// 滚动到底部 if (this.data.isNearBottom) {
this.scrollToBottom(); // 如果接近底部,自动滚动到底部
this.scrollToBottom();
} else {
// 如果不在底部,显示"到达最底部"按钮
this.setData({
showScrollToBottomBtn: true
});
}
});
// 如果是新消息,触发通知更新消息列表 // 如果是新消息,触发通知更新消息列表
if (newMessage.sender === 'other') { if (newMessage.sender === 'other') {
@ -1638,35 +1755,103 @@ Page({
} }
}, },
// 格式化服务器时间 // 格式化服务器时间 - 统一使用北京时间
formatServerTime: function(serverTime) { formatServerTime: function(serverTime) {
const date = new Date(serverTime); console.log('=== formatServerTime 调试 ===');
console.log('输入serverTime:', serverTime);
console.log('输入类型:', typeof serverTime);
// 处理不同类型的时间输入
let date;
if (typeof serverTime === 'string' && serverTime.includes('T')) {
// ISO 8601 格式的时间字符串,如 "2025-12-17T14:48:00.000Z"
console.log('ISO 8601格式时间字符串');
// 解析ISO字符串为Date对象
const utcDate = new Date(serverTime);
console.log('UTC Date对象:', utcDate);
// 转换为北京时间(UTC+8)
const beijingTime = new Date(utcDate.getTime() + (8 * 60 * 60 * 1000));
console.log('转换为北京时间:', beijingTime);
date = beijingTime;
console.log('Date对象时间戳:', date.getTime());
} else if (typeof serverTime === 'number') {
// 处理时间戳
console.log('数字时间戳');
let adjustedTimestamp = serverTime;
console.log('原始时间戳:', adjustedTimestamp);
// 检查是否是秒级时间戳(小于1e12,毫秒级时间戳通常大于1e12)
if (adjustedTimestamp < 1e12) {
console.log('秒级时间戳,转换为毫秒级');
adjustedTimestamp *= 1000;
console.log('转换后时间戳:', adjustedTimestamp);
} else {
console.log('已为毫秒级时间戳');
}
// 假设时间戳是UTC时间,转换为北京时间
const utcDate = new Date(adjustedTimestamp);
const beijingTime = new Date(utcDate.getTime() + (8 * 60 * 60 * 1000));
date = beijingTime;
console.log('转换为北京时间对象:', date);
} else {
console.log('其他格式,使用当前时间');
// 其他格式,直接使用当前北京时间
date = new Date();
console.log('当前时间Date对象:', date);
}
// 获取北京时间的各个组成部分
const year = date.getFullYear();
const month = date.getMonth() + 1; const month = date.getMonth() + 1;
const day = date.getDate(); const day = date.getDate();
const hours = date.getHours().toString().padStart(2, '0'); const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0');
return `${month}-${day} ${hours}:${minutes}`; const seconds = date.getSeconds().toString().padStart(2, '0');
const formattedTime = `${month}-${day} ${hours}:${minutes}`;
console.log('格式化后的完整时间:', `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`);
console.log('返回的格式化时间:', formattedTime);
return formattedTime;
}, },
// 解析消息时间字符串为时间戳 // 解析消息时间字符串为时间戳 - 统一使用北京时间
parseMessageTime: function(timeStr) { parseMessageTime: function(timeStr) {
if (!timeStr) return Date.now(); if (!timeStr) return Date.now(); // 返回当前时间戳
// 处理iOS不支持的"月-日 时:分"格式 console.log('=== parseMessageTime 调试 ===');
console.log('输入时间字符串:', timeStr);
// 处理"月-日 时:分"格式
const dateTimeMatch = timeStr.match(/^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})$/); const dateTimeMatch = timeStr.match(/^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})$/);
if (dateTimeMatch) { if (dateTimeMatch) {
console.log('匹配到月-日 时:分格式');
const month = dateTimeMatch[1].padStart(2, '0'); const month = dateTimeMatch[1].padStart(2, '0');
const day = dateTimeMatch[2].padStart(2, '0'); const day = dateTimeMatch[2].padStart(2, '0');
const hours = dateTimeMatch[3].padStart(2, '0'); const hours = dateTimeMatch[3].padStart(2, '0');
const minutes = dateTimeMatch[4]; const minutes = dateTimeMatch[4];
const year = new Date().getFullYear(); // 使用当前年份 const year = new Date().getFullYear(); // 使用当前年份
// 转换为iOS支持的格式
const iosCompatibleDate = `${year}-${month}-${day}T${hours}:${minutes}:00`; console.log('解析结果:', { year, month, day, hours, minutes });
return new Date(iosCompatibleDate).getTime();
// 直接构建Date对象,使用北京时间(不添加T和Z,避免时区问题)
const date = new Date(year, month - 1, day, hours, minutes, 0);
const timestamp = date.getTime();
console.log('转换为Date对象:', date);
console.log('返回时间戳:', timestamp);
return timestamp;
} }
// 对于其他格式,尝试直接解析 // 对于其他格式,尝试直接解析
return new Date(timeStr).getTime(); const date = new Date(timeStr);
console.log('直接解析为Date对象:', date);
console.log('返回时间戳:', date.getTime());
return date.getTime();
}, },
// 提取短时间格式 (HH:MM) // 提取短时间格式 (HH:MM)
@ -1683,15 +1868,59 @@ Page({
}, },
/** /**
* 将时间戳格式化为时间字符串 * 将时间戳格式化为时间字符串 - 统一使用北京时间
*/ */
formatTimestampToTime: function(timestamp) { formatTimestampToTime: function(timestamp) {
const date = new Date(timestamp); console.log('=== formatTimestampToTime 调试 ===');
console.log('输入timestamp:', timestamp);
console.log('输入类型:', typeof timestamp);
// 处理不同类型的时间输入
let date;
if (typeof timestamp === 'string' && timestamp.includes('T')) {
// ISO 8601 格式的时间字符串,如 "2025-12-17T14:48:00.000Z"
console.log('ISO 8601格式时间字符串');
// 解析ISO字符串为Date对象
const utcDate = new Date(timestamp);
console.log('UTC Date对象:', utcDate);
// 转换为北京时间(UTC+8)
const beijingTime = new Date(utcDate.getTime() + (8 * 60 * 60 * 1000));
console.log('转换为北京时间:', beijingTime);
date = beijingTime;
} else if (typeof timestamp === 'number') {
// 处理时间戳
console.log('数字时间戳');
let adjustedTimestamp = timestamp;
// 检查是否是秒级时间戳(小于1e12,毫秒级时间戳通常大于1e12)
if (adjustedTimestamp < 1e12) {
// 秒级时间戳转换为毫秒级
adjustedTimestamp *= 1000;
console.log('转换后时间戳:', adjustedTimestamp);
}
// 假设时间戳是UTC时间,转换为北京时间
const utcDate = new Date(adjustedTimestamp);
const beijingTime = new Date(utcDate.getTime() + (8 * 60 * 60 * 1000));
date = beijingTime;
console.log('转换为北京时间对象:', date);
} else {
// 其他格式,直接使用当前北京时间
date = new Date();
console.log('其他格式,使用当前时间:', date);
}
// 获取北京时间的各个组成部分
const month = date.getMonth() + 1; const month = date.getMonth() + 1;
const day = date.getDate(); const day = date.getDate();
const hours = date.getHours().toString().padStart(2, '0'); const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0');
return `${month}-${day} ${hours}:${minutes}`;
const formattedTime = `${month}-${day} ${hours}:${minutes}`;
console.log('返回的格式化时间:', formattedTime);
return formattedTime;
}, },
/** /**
@ -1920,51 +2149,56 @@ Page({
return; return;
} }
// 创建消息对象 // 调试:记录发送消息时的时间
console.log('=== 发送消息时间调试 ===');
console.log('当前时间(Date对象):', new Date());
// 获取当前北京时间,确保时间准确
const currentTime = this.getFormattedTime();
const shortTime = this.getShortTime();
console.log('getFormattedTime返回:', currentTime);
console.log('getShortTime返回:', shortTime);
// 创建消息对象,立即设置正确的时间
const newMessage = { const newMessage = {
type: 'message', type: 'message',
sender: 'me', sender: 'me',
content: this.data.inputValue.trim(), content: this.data.inputValue.trim(),
time: this.getFormattedTime(), time: currentTime,
shortTime: shortTime,
showTime: this.shouldShowTime(),
status: 'sending', // 初始状态为发送中 status: 'sending', // 初始状态为发送中
// 【关键修复】添加真实用户ID相关字段 // 【关键修复】添加真实用户ID相关字段
targetUserId: this.data.userId targetUserId: this.data.userId,
// 调试:添加发送时间详情
sendDebugInfo: {
rawDate: new Date(),
rawTimestamp: Date.now(),
formattedTime: currentTime
}
}; };
console.log('开始发送消息,目标用户ID:', this.data.userId, '会话ID:', this.data.conversationId); console.log('开始发送消息,目标用户ID:', this.data.userId, '会话ID:', this.data.conversationId);
// 添加新消息到消息列表 // 添加新消息到消息列表
const messages = [...this.data.messages]; const messages = [...this.data.messages];
// 设置是否显示新消息的时间
let showTime = false;
if (messages.length === 0) {
showTime = true;
} else {
const lastMessage = messages[messages.length - 1];
const currentTime = this.parseMessageTime(this.getFormattedTime());
const lastTime = this.parseMessageTime(lastMessage.time);
// 如果时间差超过5分钟,显示时间
if (currentTime - lastTime > 5 * 60 * 1000) {
showTime = true;
}
}
newMessage.showTime = showTime;
newMessage.shortTime = this.getShortTime();
messages.push(newMessage); messages.push(newMessage);
// 立即更新UI // 重新处理所有消息的时间显示,确保时间显示正确
const processedMessages = this.processMessageTimes(messages);
// 立即更新UI,确保滚动在数据更新后执行
this.setData({ this.setData({
messages: messages, messages: processedMessages,
inputValue: '' inputValue: ''
}, () => {
// 数据更新完成后滚动到底部
this.scrollToBottom();
}); });
// 保存消息到本地存储 // 保存消息到本地存储
this.saveMessagesToStorage(messages); this.saveMessagesToStorage(processedMessages);
this.scrollToBottom();
// 会话ID检查和处理 // 会话ID检查和处理
if (!this.data.conversationId || this.data.conversationId.startsWith('temp_')) { if (!this.data.conversationId || this.data.conversationId.startsWith('temp_')) {
@ -2427,18 +2661,31 @@ Page({
}, 1000); }, 1000);
}, },
// 获取格式化时间(带日期) // 获取格式化时间(带日期)- 统一使用北京时间
getFormattedTime: function() { getFormattedTime: function() {
// 调试:记录当前时间
console.log('=== getFormattedTime 调试 ===');
console.log('当前Date对象:', new Date());
// 获取当前本地时间(设备的本地时间已经是北京时间)
const now = new Date(); const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1; const month = now.getMonth() + 1;
const date = now.getDate(); const date = now.getDate();
const hours = now.getHours().toString().padStart(2, '0'); const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0');
return `${month}-${date} ${hours}:${minutes}`; const seconds = now.getSeconds().toString().padStart(2, '0');
const formattedTime = `${month}-${date} ${hours}:${minutes}`;
console.log('格式化后时间:', formattedTime);
console.log('完整时间:', `${year}-${month}-${date} ${hours}:${minutes}:${seconds}`);
return formattedTime;
}, },
// 获取仅包含时分的时间格式 // 获取仅包含时分的时间格式 - 统一使用北京时间
getShortTime: function() { getShortTime: function() {
// 获取当前本地时间(设备的本地时间已经是北京时间)
const now = new Date(); const now = new Date();
const hours = now.getHours().toString().padStart(2, '0'); const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0');
@ -2529,18 +2776,12 @@ Page({
// 滚动到底部 // 滚动到底部
scrollToBottom: function() { scrollToBottom: function() {
setTimeout(() => { setTimeout(() => {
wx.createSelectorQuery().select('#message-container').boundingClientRect(res => { // 使用scroll-top属性滚动到底部,这是更可靠的方式
if (res) { this.setData({
wx.createSelectorQuery().select('#message-list').scrollOffset(res => { scrollTop: 999999,
if (res) { showScrollToBottomBtn: false
wx.createSelectorQuery().select('#message-list').context(context => { });
context.scrollTo({ scrollTop: res.scrollHeight, animated: true }); }, 50);
}).exec();
}
}).exec();
}
}).exec();
}, 100);
}, },
// 显示表情 // 显示表情
@ -2653,7 +2894,55 @@ Page({
* 生命周期函数--监听页面显示 * 生命周期函数--监听页面显示
*/ */
onShow: function () { onShow: function () {
// 页面显示时,初始化滚动区域高度
this.initScrollHeight();
// 页面显示时,滚动到最新消息
console.log('聊天页面显示,滚动到最新消息');
// 只在页面首次显示时滚动到底部,避免频繁跳转
if (!this.hasScrolledOnShow) {
this.scrollToBottom();
this.hasScrolledOnShow = true;
}
},
/**
* 初始化滚动区域高度
*/
initScrollHeight: function() {
wx.createSelectorQuery().select('#message-list').boundingClientRect(res => {
if (res) {
this.setData({
scrollHeight: res.height
});
}
}).exec();
},
/**
* 滚动事件处理函数
*/
onScroll: function(e) {
const { scrollTop, scrollHeight, contentHeight } = e.detail;
// 更新滚动位置
this.setData({
lastScrollTop: scrollTop,
contentHeight: contentHeight
});
// 计算是否接近底部(距离底部50px以内)
const distanceToBottom = contentHeight - scrollTop - scrollHeight;
const isNearBottom = distanceToBottom < 50 && distanceToBottom > -100;
this.setData({
isNearBottom: isNearBottom
});
// 如果接近底部,隐藏"到达最底部"按钮
if (isNearBottom) {
this.setData({
showScrollToBottomBtn: false
});
}
}, },
/** /**
@ -2736,6 +3025,13 @@ Page({
console.log('稳定性检查定时器已清理'); console.log('稳定性检查定时器已清理');
} }
// 关键修复:恢复原始的全局消息处理函数
const app = getApp();
if (this.originalGlobalMessageHandler) {
app.globalData.onNewMessage = this.originalGlobalMessageHandler;
console.log('已恢复原始的全局消息处理函数');
}
// 关闭WebSocket连接 // 关闭WebSocket连接
// 注意:这里可以根据实际需求决定是否关闭连接 // 注意:这里可以根据实际需求决定是否关闭连接
// 如果是多页面共用一个连接,可以不在这里关闭 // 如果是多页面共用一个连接,可以不在这里关闭
@ -3274,8 +3570,15 @@ Page({
}, () => { }, () => {
// 数据更新完成后的回调 // 数据更新完成后的回调
console.log('消息列表已成功更新到UI'); console.log('消息列表已成功更新到UI');
// 滚动到底部
this.scrollToBottom(); // 关键修复:只在页面首次加载或用户接近底部时才滚动到底部,避免频繁跳转
// 如果是首次加载历史消息,或者用户当前已经接近底部,才自动滚动到底部
if (this.data.isNearBottom || this.data.messages.length === 0) {
console.log('首次加载历史消息或用户接近底部,自动滚动到底部');
this.scrollToBottom();
} else {
console.log('用户不在底部,不自动滚动,保持当前位置');
}
// 【关键修复】根据消息情况显示不同的提示 // 【关键修复】根据消息情况显示不同的提示
const realUserMessages = processedMessages.filter(msg => msg.isRealUserMessage); const realUserMessages = processedMessages.filter(msg => msg.isRealUserMessage);
@ -3549,11 +3852,12 @@ Page({
title: '加载更多消息...', title: '加载更多消息...',
}); });
// 简单实现,实际可以添加分页逻辑 // 实现真正的分页加载,根据已有消息数量计算页码
const page = Math.floor(this.data.messages.length / 50) + 1;
socketManager.send({ socketManager.send({
type: 'get_messages', type: 'get_messages',
conversationId: conversationId, conversationId: conversationId,
page: 1, // 实际应用中应该根据已有消息数量计算页码 page: page, // 根据已有消息数量计算页码
limit: 50 limit: 50
}); });
}, },

14
pages/chat-detail/index.wxml

@ -21,8 +21,9 @@
id="message-list" id="message-list"
class="wechat-message-list" class="wechat-message-list"
scroll-y scroll-y
bindscrolltolower="loadMoreMessages" bindscrolltoupper="loadMoreMessages"
scroll-into-view="{{scrollToMessage}}" bindscroll="onScroll"
scroll-top="{{scrollTop}}"
scroll-with-animation scroll-with-animation
> >
<!-- 消息容器 --> <!-- 消息容器 -->
@ -43,7 +44,7 @@
</view> </view>
<!-- 对方消息 --> <!-- 对方消息 -->
<view wx:elif="{{item.type === 'message' && item.sender === 'other'}}" class="wechat-message-item wechat-other-message"> <view wx:elif="{{item.type === 'message' && item.sender === 'other'}}" class="wechat-message-item wechat-other-message" id="msg_{{index}}">
<view class="wechat-avatar"> <view class="wechat-avatar">
<text class="wechat-avatar-text">{{userName ? userName.charAt(0) : (avatar || '用')}}</text> <text class="wechat-avatar-text">{{userName ? userName.charAt(0) : (avatar || '用')}}</text>
</view> </view>
@ -58,7 +59,7 @@
</view> </view>
</view> </view>
<!-- 我的消息 --> <!-- 我的消息 -->
<view wx:elif="{{item.type === 'message' && item.sender === 'me'}}" class="wechat-message-item wechat-my-message"> <view wx:elif="{{item.type === 'message' && item.sender === 'me'}}" class="wechat-message-item wechat-my-message" id="msg_{{index}}">
<!-- 图片消息直接显示 --> <!-- 图片消息直接显示 -->
<image wx:if="{{item.isImage}}" src="{{item.content}}" class="wechat-message-image wechat-my-image" mode="aspectFit" bindtap="previewImage" data-src="{{item.content}}" /> <image wx:if="{{item.isImage}}" src="{{item.content}}" class="wechat-message-image wechat-my-image" mode="aspectFit" bindtap="previewImage" data-src="{{item.content}}" />
<!-- 文本消息仍然显示在气泡中 --> <!-- 文本消息仍然显示在气泡中 -->
@ -75,6 +76,11 @@
</view> </view>
</scroll-view> </scroll-view>
<!-- 到达最底部按钮 -->
<view wx:if="{{showScrollToBottomBtn}}" class="scroll-to-bottom-btn" bindtap="scrollToBottom">
<text class="scroll-btn-text">到达最底部</text>
</view>
<!-- 输入区域 - 微信风格 --> <!-- 输入区域 - 微信风格 -->
<view class="wechat-input-area"> <view class="wechat-input-area">
<view class="wechat-input-toolbar"> <view class="wechat-input-toolbar">

16
pages/customer-service/index.js

@ -248,6 +248,22 @@ Page({
// 启动定期刷新 // 启动定期刷新
this.startPeriodicRefresh(); this.startPeriodicRefresh();
// 检查当前用户身份
this.checkUserType();
},
/**
* 检查当前用户身份
*/
checkUserType: function() {
const app = getApp();
const userInfo = app.globalData.userInfo || {};
const isManager = userInfo.userType === 'manager' || userInfo.type === 'manager';
console.log('当前用户身份检查:', { isManager, userType: userInfo.userType });
this.setData({
isCurrentUserManager: isManager
});
}, },
onShow() { onShow() {

2
pages/customer-service/index.wxml

@ -72,7 +72,7 @@
</view> </view>
</view> </view>
<view class="action-buttons"> <view class="action-buttons">
<view class="button-chat" bindtap="onChat" data-id="{{item.id}}"> <view wx:if="{{!isCurrentUserManager}}" class="button-chat" bindtap="onChat" data-id="{{item.id}}">
<text class="button-icon">💬</text> <text class="button-icon">💬</text>
</view> </view>
<view class="button-call" bindtap="onCall" data-phone="{{item.phoneNumber}}"> <view class="button-call" bindtap="onCall" data-phone="{{item.phoneNumber}}">

63
pages/message-list/index.js

@ -573,27 +573,48 @@ Page({
}, },
/** /**
* 格式化消息时间 - 增强版支持多种时间格式 * 格式化消息时间 - 增强版支持多种时间格式 - 统一使用北京时间
*/ */
formatMessageTime: function(timeStr) { formatMessageTime: function(timeStr) {
try { try {
if (!timeStr) return ''; if (!timeStr) return '';
// 获取当前北京时间
const now = new Date(); const now = new Date();
let messageDate; const nowYear = now.getFullYear();
const nowMonth = now.getMonth();
const nowDate = now.getDate();
let date;
// 尝试不同的时间格式解析 // 尝试不同的时间格式解析
if (typeof timeStr === 'number') { if (typeof timeStr === 'number') {
// 处理时间戳 // 处理时间戳
messageDate = new Date(timeStr); let adjustedTimestamp = timeStr;
// 检查是否是秒级时间戳(小于1e12,毫秒级时间戳通常大于1e12)
if (adjustedTimestamp < 1e12) {
// 秒级时间戳转换为毫秒级
adjustedTimestamp *= 1000;
}
date = new Date(adjustedTimestamp);
} else if (typeof timeStr === 'string') { } else if (typeof timeStr === 'string') {
// 处理字符串格式
if (/^\d+$/.test(timeStr)) { if (/^\d+$/.test(timeStr)) {
// 数字字符串,可能是时间戳 // 数字字符串,可能是时间戳
messageDate = new Date(parseInt(timeStr)); const timestamp = parseInt(timeStr);
let adjustedTimestamp = timestamp;
// 检查是否是秒级时间戳
if (adjustedTimestamp < 1e12) {
// 秒级时间戳转换为毫秒级
adjustedTimestamp *= 1000;
}
date = new Date(adjustedTimestamp);
} else if (timeStr.includes('T')) {
// ISO 8601格式的时间字符串,如 "2025-12-18T07:03:00.000Z"
// 先将字符串解析为Date对象(自动转换为本地时间)
date = new Date(timeStr);
} else { } else {
// 其他字符串格式 // 其他格式的时间字符串,直接解析
messageDate = new Date(timeStr); date = new Date(timeStr);
} }
} else { } else {
// 其他类型,返回空 // 其他类型,返回空
@ -601,17 +622,25 @@ Page({
} }
// 验证日期是否有效 // 验证日期是否有效
if (isNaN(messageDate.getTime())) { if (isNaN(date.getTime())) {
return ''; return '';
} }
const diffTime = Math.abs(now - messageDate); // 获取北京时间的各组成部分
const beijingYear = date.getFullYear();
const beijingMonth = date.getMonth();
const beijingDate = date.getDate();
const beijingHours = date.getHours();
const beijingMinutes = date.getMinutes();
// 计算天数差
const diffTime = Math.abs(now.getTime() - date.getTime());
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (diffDays === 0) { if (diffDays === 0) {
// 今天的消息只显示时间,格式化为 HH:mm // 今天的消息只显示时间,格式化为 HH:mm
const hours = messageDate.getHours().toString().padStart(2, '0'); const hours = beijingHours.toString().padStart(2, '0');
const minutes = messageDate.getMinutes().toString().padStart(2, '0'); const minutes = beijingMinutes.toString().padStart(2, '0');
return `${hours}:${minutes}`; return `${hours}:${minutes}`;
} else if (diffDays === 1) { } else if (diffDays === 1) {
// 昨天的消息 // 昨天的消息
@ -619,17 +648,17 @@ Page({
} else if (diffDays < 7) { } else if (diffDays < 7) {
// 一周内的消息显示星期 // 一周内的消息显示星期
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return weekdays[messageDate.getDay()]; return weekdays[date.getDay()];
} else if (diffDays < 365) { } else if (diffDays < 365) {
// 今年内的消息显示月日 // 今年内的消息显示月日
const month = (messageDate.getMonth() + 1).toString().padStart(2, '0'); const month = (beijingMonth + 1).toString().padStart(2, '0');
const day = messageDate.getDate().toString().padStart(2, '0'); const day = beijingDate.toString().padStart(2, '0');
return `${month}-${day}`; return `${month}-${day}`;
} else { } else {
// 超过一年的消息显示完整日期 // 超过一年的消息显示完整日期
const year = messageDate.getFullYear(); const year = beijingYear;
const month = (messageDate.getMonth() + 1).toString().padStart(2, '0'); const month = (beijingMonth + 1).toString().padStart(2, '0');
const day = messageDate.getDate().toString().padStart(2, '0'); const day = beijingDate.toString().padStart(2, '0');
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
} }
} catch (e) { } catch (e) {

Loading…
Cancel
Save