You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1440 lines
44 KiB
1440 lines
44 KiB
|
3 months ago
|
// pages/chat-detail/index.js
|
||
|
|
import socketManager from '../../utils/websocket';
|
||
|
|
|
||
|
|
Page({
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 页面的初始数据
|
||
|
|
*/
|
||
|
|
data: {
|
||
|
|
userId: '',
|
||
|
|
userName: '',
|
||
|
|
avatar: '',
|
||
|
|
messages: [],
|
||
|
|
inputValue: '',
|
||
|
|
goodsInfo: null, // 商品信息
|
||
|
|
connectionStatus: 'disconnected', // 连接状态: disconnected, connecting, connected, error
|
||
|
|
connectionMessage: '', // 连接状态提示消息
|
||
|
|
isMockMode: false, // 默认为真实WebSocket通信模式
|
||
|
|
isManager: false // 是否是与客服的聊天
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 从本地存储加载历史消息
|
||
|
|
*/
|
||
|
|
loadMessagesFromStorage: function() {
|
||
|
|
try {
|
||
|
|
const storedMessages = wx.getStorageSync(`chat_messages_${this.data.userId}`);
|
||
|
|
if (storedMessages && storedMessages.length > 0) {
|
||
|
|
console.log('从本地存储加载了', storedMessages.length, '条历史消息');
|
||
|
|
|
||
|
|
// 处理消息数据,确保包含必要的字段
|
||
|
|
const messagesWithRequiredFields = storedMessages.map(msg => ({
|
||
|
|
...msg,
|
||
|
|
// 重新生成shortTime字段
|
||
|
|
shortTime: msg.time ? this.extractShortTime(msg.time) : this.getShortTime()
|
||
|
|
}));
|
||
|
|
|
||
|
|
// 重新处理时间显示逻辑
|
||
|
|
const processedMessages = this.processMessageTimes(messagesWithRequiredFields);
|
||
|
|
this.setData({
|
||
|
|
messages: processedMessages
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('从本地存储加载消息失败:', e);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 根据用户ID获取用户名
|
||
|
|
*/
|
||
|
|
getUserNameById: function(userId) {
|
||
|
|
try {
|
||
|
|
// 参数有效性检查
|
||
|
|
if (!userId || typeof userId === 'undefined') {
|
||
|
|
return '未知用户';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 确保userId是字符串类型
|
||
|
|
const safeUserId = String(userId);
|
||
|
|
|
||
|
|
// 首先从客服列表缓存中查找
|
||
|
|
const app = getApp();
|
||
|
|
if (app.globalData.customerServiceList) {
|
||
|
|
const service = app.globalData.customerServiceList.find(item =>
|
||
|
|
item.id === safeUserId || item.managerId === safeUserId ||
|
||
|
|
String(item.id) === safeUserId || String(item.managerId) === safeUserId
|
||
|
|
);
|
||
|
|
if (service) {
|
||
|
|
return service.alias || service.name || service.id || '未知客服';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 尝试从本地存储获取客服列表
|
||
|
|
const cachedCustomerServices = wx.getStorageSync('cached_customer_services') || [];
|
||
|
|
const service = cachedCustomerServices.find(item =>
|
||
|
|
item.id === safeUserId || item.managerId === safeUserId ||
|
||
|
|
String(item.id) === safeUserId || String(item.managerId) === safeUserId
|
||
|
|
);
|
||
|
|
if (service) {
|
||
|
|
return service.alias || service.name || service.id || '未知客服';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 从固定映射中查找
|
||
|
|
const userNameMap = {
|
||
|
|
'1001': '刘海',
|
||
|
|
'1002': '李明',
|
||
|
|
'1003': '王刚',
|
||
|
|
'1004': '张琳'
|
||
|
|
};
|
||
|
|
|
||
|
|
if (userNameMap[safeUserId]) {
|
||
|
|
return userNameMap[safeUserId];
|
||
|
|
}
|
||
|
|
|
||
|
|
// 对于manager_开头的ID,显示为客服
|
||
|
|
if (safeUserId.startsWith('manager_') || safeUserId.includes('manager')) {
|
||
|
|
return '客服-' + (safeUserId.length >= 4 ? safeUserId.slice(-4) : safeUserId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果都没有找到,返回默认名称,避免出现undefined
|
||
|
|
if (safeUserId.length >= 4) {
|
||
|
|
return '用户' + safeUserId.slice(-4);
|
||
|
|
} else {
|
||
|
|
return '用户' + safeUserId;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('获取用户名出错:', e);
|
||
|
|
return '未知用户';
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 生命周期函数--监听页面加载
|
||
|
|
*/
|
||
|
|
onLoad: function (options) {
|
||
|
|
// 接收从上一个页面传递的参数
|
||
|
|
if (options) {
|
||
|
|
const userId = options.userId || '';
|
||
|
|
// 优先使用传递的userName参数,并确保正确解码
|
||
|
|
let userName = options.userName ? decodeURIComponent(options.userName) : '';
|
||
|
|
|
||
|
|
// 如果没有传递userName或userName为空,则使用getUserNameById获取默认名称
|
||
|
|
if (!userName || userName.trim() === '') {
|
||
|
|
userName = this.getUserNameById(userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查是否传递了测试模式参数和是否是客服
|
||
|
|
const isTestMode = options.isTestMode === 'true';
|
||
|
|
const isManager = options.isManager === 'true';
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
userId: userId,
|
||
|
|
userName: userName,
|
||
|
|
avatar: options.avatar || '',
|
||
|
|
isMockMode: isTestMode, // 默认为false,只有明确指定才启用测试模式
|
||
|
|
isManager: isManager // 设置是否是与客服的聊天
|
||
|
|
});
|
||
|
|
|
||
|
|
// 更新导航栏标题
|
||
|
|
wx.setNavigationBarTitle({
|
||
|
|
title: userName || '在线联系'
|
||
|
|
});
|
||
|
|
|
||
|
|
// 首先尝试从本地存储加载历史消息
|
||
|
|
const hasLoadedStoredMessages = this.loadMessagesFromStorage();
|
||
|
|
|
||
|
|
// 如果没有历史消息,初始化模拟消息数据
|
||
|
|
if (!hasLoadedStoredMessages) {
|
||
|
|
this.initMockMessages();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化WebSocket连接(无论是否模拟模式都初始化,但模拟模式下不发送真实消息)
|
||
|
|
this.initWebSocket();
|
||
|
|
|
||
|
|
// 显示当前模式提示
|
||
|
|
wx.showToast({
|
||
|
|
title: isTestMode ? '开发测试模式' : '真实通信模式',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 初始化WebSocket连接
|
||
|
|
*/
|
||
|
|
initWebSocket: function() {
|
||
|
|
// 获取当前用户ID(实际项目中应该从全局或本地存储获取)
|
||
|
|
const currentUserId = wx.getStorageSync('userId') || 'user_' + Date.now();
|
||
|
|
|
||
|
|
// 保存当前用户ID到本地存储
|
||
|
|
wx.setStorageSync('userId', currentUserId);
|
||
|
|
|
||
|
|
// 构建WebSocket连接地址
|
||
|
|
// 注意:在实际部署时,需要替换为真实的WebSocket服务地址
|
||
|
|
// 这里使用本地服务器地址作为示例
|
||
|
|
const app = getApp();
|
||
|
|
const globalUserInfo = app.globalData.userInfo || {};
|
||
|
|
const isManager = this.data.isManager || (globalUserInfo.userType === 'manager' || globalUserInfo.type === 'manager');
|
||
|
|
|
||
|
|
const wsUrl = `ws://localhost:3003/ws?userId=${currentUserId}&targetId=${this.data.userId}&type=chat&userType=${isManager ? 'manager' : 'user'}`;
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'connecting',
|
||
|
|
connectionMessage: '正在连接服务器...'
|
||
|
|
});
|
||
|
|
|
||
|
|
// 设置WebSocket事件监听
|
||
|
|
this.setupWebSocketListeners();
|
||
|
|
|
||
|
|
// 连接WebSocket服务器
|
||
|
|
socketManager.connect(wsUrl, {
|
||
|
|
maxReconnectAttempts: 5,
|
||
|
|
reconnectInterval: 3000,
|
||
|
|
heartbeatTime: 30000
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监听网络状态变化
|
||
|
|
this.startNetworkListener();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 手动重新连接
|
||
|
|
*/
|
||
|
|
reconnect: function() {
|
||
|
|
wx.showLoading({
|
||
|
|
title: '正在重连...',
|
||
|
|
});
|
||
|
|
|
||
|
|
// 重置重连计数
|
||
|
|
socketManager.reconnectAttempts = 0;
|
||
|
|
|
||
|
|
// 重新初始化WebSocket连接
|
||
|
|
this.initWebSocket();
|
||
|
|
|
||
|
|
setTimeout(() => {
|
||
|
|
wx.hideLoading();
|
||
|
|
}, 1000);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 开始网络状态监听
|
||
|
|
*/
|
||
|
|
startNetworkListener: function() {
|
||
|
|
// 监听网络状态变化
|
||
|
|
wx.onNetworkStatusChange((res) => {
|
||
|
|
console.log('网络状态变化:', res);
|
||
|
|
|
||
|
|
if (!res.isConnected) {
|
||
|
|
// 网络断开
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'error',
|
||
|
|
connectionMessage: '网络已断开'
|
||
|
|
});
|
||
|
|
|
||
|
|
// 显示网络断开提示
|
||
|
|
wx.showToast({
|
||
|
|
title: '网络连接已断开',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
} else if (res.isConnected && this.data.connectionStatus === 'error') {
|
||
|
|
// 网络恢复且之前连接错误,尝试重连
|
||
|
|
wx.showToast({
|
||
|
|
title: '网络已恢复,正在重连',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
this.reconnect();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置WebSocket事件监听器
|
||
|
|
*/
|
||
|
|
setupWebSocketListeners: function() {
|
||
|
|
// 连接成功
|
||
|
|
socketManager.on('open', () => {
|
||
|
|
const app = getApp();
|
||
|
|
const isManager = this.data.isManager ||
|
||
|
|
(app.globalData.userInfo?.userType === 'manager' ||
|
||
|
|
app.globalData.userInfo?.type === 'manager');
|
||
|
|
|
||
|
|
// 如果是客服,更新全局用户信息
|
||
|
|
if (isManager && app.globalData.userInfo) {
|
||
|
|
app.globalData.userInfo.userType = 'manager';
|
||
|
|
app.globalData.userInfo.type = 'manager';
|
||
|
|
}
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'connected',
|
||
|
|
connectionMessage: '已连接'
|
||
|
|
});
|
||
|
|
console.log('WebSocket连接成功', { isManager });
|
||
|
|
|
||
|
|
// 发送认证消息
|
||
|
|
this.sendAuthMessage();
|
||
|
|
|
||
|
|
wx.showToast({
|
||
|
|
title: '连接成功',
|
||
|
|
icon: 'success',
|
||
|
|
duration: 1500
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// 接收消息
|
||
|
|
socketManager.on('message', (data) => {
|
||
|
|
console.log('收到WebSocket消息:', data);
|
||
|
|
this.handleReceivedMessage(data);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 连接关闭
|
||
|
|
socketManager.on('close', (res) => {
|
||
|
|
if (this.data.connectionStatus !== 'disconnected') {
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'disconnected',
|
||
|
|
connectionMessage: `连接已断开(${res.code || ''})`
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 连接错误
|
||
|
|
socketManager.on('error', (error) => {
|
||
|
|
console.error('WebSocket错误:', error);
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'error',
|
||
|
|
connectionMessage: '连接错误'
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// 重连中
|
||
|
|
socketManager.on('reconnecting', (attempts) => {
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'connecting',
|
||
|
|
connectionMessage: `重连中(${attempts}/5)...`
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// 重连失败
|
||
|
|
socketManager.on('reconnectFailed', () => {
|
||
|
|
this.setData({
|
||
|
|
connectionStatus: 'error',
|
||
|
|
connectionMessage: '重连失败,请检查网络'
|
||
|
|
});
|
||
|
|
|
||
|
|
wx.showModal({
|
||
|
|
title: '连接失败',
|
||
|
|
content: '无法连接到服务器,请检查网络设置后重试。',
|
||
|
|
showCancel: false,
|
||
|
|
confirmText: '确定'
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监听WebSocket状态更新事件
|
||
|
|
socketManager.on('status', (status) => {
|
||
|
|
console.log('WebSocket状态更新:', status);
|
||
|
|
this.setData({
|
||
|
|
connectionMessage: status.message || '状态更新'
|
||
|
|
});
|
||
|
|
|
||
|
|
// 根据状态类型更新连接状态
|
||
|
|
switch(status.type) {
|
||
|
|
case 'connecting':
|
||
|
|
this.setData({ connectionStatus: 'connecting' });
|
||
|
|
break;
|
||
|
|
case 'connected':
|
||
|
|
this.setData({ connectionStatus: 'connected' });
|
||
|
|
break;
|
||
|
|
case 'disconnected':
|
||
|
|
this.setData({ connectionStatus: 'disconnected' });
|
||
|
|
break;
|
||
|
|
case 'error':
|
||
|
|
this.setData({ connectionStatus: 'error' });
|
||
|
|
if (status.isWarning) {
|
||
|
|
wx.showToast({
|
||
|
|
title: status.message,
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'reconnecting':
|
||
|
|
this.setData({ connectionStatus: 'reconnecting' });
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监听消息发送成功事件
|
||
|
|
socketManager.on('sendSuccess', (data) => {
|
||
|
|
console.log('消息发送成功:', data);
|
||
|
|
// 可以在这里更新UI状态,如显示已发送标记等
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监听消息发送失败事件
|
||
|
|
socketManager.on('sendError', (error) => {
|
||
|
|
console.error('消息发送失败:', error);
|
||
|
|
wx.showToast({
|
||
|
|
title: '消息发送失败,请重试',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 发送认证消息
|
||
|
|
*/
|
||
|
|
sendAuthMessage: function() {
|
||
|
|
const currentUserId = wx.getStorageSync('userId') || 'user_' + Date.now();
|
||
|
|
const app = getApp();
|
||
|
|
const globalUserInfo = app.globalData.userInfo || {};
|
||
|
|
|
||
|
|
// 确定用户类型:优先使用页面参数,然后是全局用户信息,最后默认为普通用户
|
||
|
|
const isManager = this.data.isManager || false;
|
||
|
|
const userType = isManager ? 'manager' : (globalUserInfo.userType || globalUserInfo.type || 'user');
|
||
|
|
|
||
|
|
console.log('认证信息:', { userId: currentUserId, userType, isManager: this.data.isManager });
|
||
|
|
|
||
|
|
const authMessage = {
|
||
|
|
type: 'auth',
|
||
|
|
data: {
|
||
|
|
userId: currentUserId,
|
||
|
|
type: userType, // 用户类型:普通用户或客服
|
||
|
|
name: isManager ? '客服' + currentUserId.slice(-4) : '用户' + currentUserId.slice(-4)
|
||
|
|
},
|
||
|
|
timestamp: Date.now()
|
||
|
|
};
|
||
|
|
|
||
|
|
console.log('发送认证消息:', authMessage);
|
||
|
|
socketManager.send(authMessage);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 处理接收到的消息
|
||
|
|
*/
|
||
|
|
handleReceivedMessage: function(data) {
|
||
|
|
console.log('收到消息:', data);
|
||
|
|
|
||
|
|
// 确保数据格式正确
|
||
|
|
if (!data || typeof data !== 'object') {
|
||
|
|
console.error('收到无效消息:', data);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 添加消息数据格式验证
|
||
|
|
if (!data.type) {
|
||
|
|
console.error('消息缺少必要字段:', data);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 根据消息类型处理
|
||
|
|
if (data.type === 'new_message' && data.payload) {
|
||
|
|
// 处理服务器推送的新消息
|
||
|
|
this.processServerMessage(data.payload);
|
||
|
|
} else if (data.type === 'message') {
|
||
|
|
// 处理旧格式的普通消息(向后兼容)
|
||
|
|
this.processChatMessage(data);
|
||
|
|
} else if (data.type === 'system') {
|
||
|
|
// 处理系统消息
|
||
|
|
const systemMessage = {
|
||
|
|
type: 'system',
|
||
|
|
content: data.content || '',
|
||
|
|
time: this.getFormattedTime(),
|
||
|
|
showTime: true
|
||
|
|
};
|
||
|
|
|
||
|
|
// 如果是警告消息,添加警告标记
|
||
|
|
if (data.isWarning) {
|
||
|
|
systemMessage.isWarning = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
const messages = [...this.data.messages];
|
||
|
|
messages.push(systemMessage);
|
||
|
|
|
||
|
|
this.setData({ messages });
|
||
|
|
|
||
|
|
// 保存消息到本地存储
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
|
||
|
|
this.scrollToBottom();
|
||
|
|
} else if (data.type === 'status') {
|
||
|
|
// 处理状态更新消息
|
||
|
|
this.setData({
|
||
|
|
connectionMessage: data.message || '连接状态更新'
|
||
|
|
});
|
||
|
|
|
||
|
|
wx.showToast({
|
||
|
|
title: data.message || '状态更新',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
} else if (data.type === 'message_sent') {
|
||
|
|
// 处理服务器确认消息
|
||
|
|
console.log('服务器确认消息已送达:', data.payload);
|
||
|
|
} else if (data.type === 'error') {
|
||
|
|
// 处理错误消息
|
||
|
|
console.error('收到服务器错误:', data.message);
|
||
|
|
wx.showToast({
|
||
|
|
title: '消息发送失败: ' + data.message,
|
||
|
|
icon: 'none',
|
||
|
|
duration: 2000
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 处理聊天消息(旧格式,向后兼容)
|
||
|
|
processChatMessage: function(message) {
|
||
|
|
// 确保消息对象包含必要的字段
|
||
|
|
const content = message.content || '';
|
||
|
|
const isImage = message.isImage || false;
|
||
|
|
const app = getApp();
|
||
|
|
const currentUser = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || '');
|
||
|
|
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer';
|
||
|
|
|
||
|
|
console.log('处理聊天消息 - 用户类型:', currentUserType, '当前用户ID:', currentUser, '消息来源:', message.from || message.senderId);
|
||
|
|
|
||
|
|
// 确定消息发送方(me或other)
|
||
|
|
let sender = 'other';
|
||
|
|
if (message.from === currentUser || message.senderId === currentUser) {
|
||
|
|
sender = 'me';
|
||
|
|
} else if (currentUserType === 'customer_service' && message.receiverId && message.receiverId === this.data.userId) {
|
||
|
|
// 客服接收到的发送给自己的消息
|
||
|
|
sender = 'other';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建新消息对象
|
||
|
|
const newMessage = {
|
||
|
|
id: message.id || 'msg_' + Date.now(),
|
||
|
|
type: 'message',
|
||
|
|
content: content,
|
||
|
|
isImage: isImage,
|
||
|
|
sender: sender,
|
||
|
|
time: message.timestamp ? this.formatTimestampToTime(message.timestamp) : this.getFormattedTime(),
|
||
|
|
showTime: this.shouldShowTime(),
|
||
|
|
shortTime: this.getShortTime(),
|
||
|
|
status: 'sent', // 接收的消息默认已发送
|
||
|
|
senderType: message.senderType || 'unknown', // 保存发送方类型
|
||
|
|
serverData: message // 保存完整的服务器数据
|
||
|
|
};
|
||
|
|
|
||
|
|
this.addReceivedMessage(newMessage);
|
||
|
|
},
|
||
|
|
|
||
|
|
// 处理服务器推送的新消息
|
||
|
|
processServerMessage: function(messageData) {
|
||
|
|
console.log('处理服务器推送的新消息:', messageData);
|
||
|
|
|
||
|
|
// 获取当前用户信息
|
||
|
|
const app = getApp();
|
||
|
|
const currentUser = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || '');
|
||
|
|
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer';
|
||
|
|
|
||
|
|
console.log('处理服务器消息 - 用户类型:', currentUserType, '当前用户ID:', currentUser, '消息数据:', messageData);
|
||
|
|
|
||
|
|
// 确定消息发送方(me或other)
|
||
|
|
let sender = 'other';
|
||
|
|
|
||
|
|
// 检查是否是当前用户发送的消息
|
||
|
|
if (messageData.senderId === currentUser) {
|
||
|
|
sender = 'me';
|
||
|
|
} else if (currentUserType === 'customer_service') {
|
||
|
|
// 对于客服,还需要考虑其他情况
|
||
|
|
if (messageData.direction === 'customer_to_service' && messageData.receiverId === currentUser) {
|
||
|
|
// 客户发送给当前客服的消息
|
||
|
|
sender = 'other';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 处理消息内容,支持嵌套的data字段
|
||
|
|
const messageContent = messageData.content || (messageData.data && messageData.data.content) || '';
|
||
|
|
const contentType = messageData.contentType || (messageData.data && messageData.data.contentType) || 1;
|
||
|
|
const isImage = contentType === 2 || (messageData.fileUrl && contentType !== 1);
|
||
|
|
|
||
|
|
// 创建符合前端UI要求的消息对象
|
||
|
|
const newMessage = {
|
||
|
|
id: messageData.messageId || messageData.id || 'msg_' + Date.now(),
|
||
|
|
type: 'message',
|
||
|
|
content: isImage && messageData.fileUrl ? messageData.fileUrl : messageContent,
|
||
|
|
isImage: isImage,
|
||
|
|
sender: sender,
|
||
|
|
senderType: messageData.senderType || 'unknown', // 保存发送方类型
|
||
|
|
direction: messageData.direction || 'unknown', // 保存消息方向
|
||
|
|
time: messageData.createdAt ? this.formatServerTime(messageData.createdAt) : this.getFormattedTime(),
|
||
|
|
status: 'sent',
|
||
|
|
serverData: messageData // 保存完整的服务器数据
|
||
|
|
};
|
||
|
|
|
||
|
|
// 特殊处理客服消息显示逻辑
|
||
|
|
if (currentUserType === 'customer_service' && sender === 'other') {
|
||
|
|
console.log('客服收到客户消息:', messageContent);
|
||
|
|
// 确保客服消息可见性
|
||
|
|
this.ensureManagerMessageVisibility(newMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.addReceivedMessage(newMessage);
|
||
|
|
},
|
||
|
|
|
||
|
|
// 通用的添加接收消息方法
|
||
|
|
addReceivedMessage: function(newMessage) {
|
||
|
|
// 检查是否已经存在相同的消息,避免重复添加
|
||
|
|
const existingMessage = this.data.messages.find(msg =>
|
||
|
|
msg.id === newMessage.id ||
|
||
|
|
(newMessage.serverData && msg.serverData && msg.serverData.messageId === newMessage.serverData.messageId)
|
||
|
|
);
|
||
|
|
|
||
|
|
if (existingMessage) {
|
||
|
|
console.log('消息已存在,跳过添加:', newMessage);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
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(newMessage.time);
|
||
|
|
const lastTime = this.parseMessageTime(lastMessage.time);
|
||
|
|
|
||
|
|
// 如果时间相差超过5分钟,则显示时间
|
||
|
|
if (Math.abs(currentTime - lastTime) > 5 * 60 * 1000) {
|
||
|
|
showTime = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
newMessage.showTime = showTime;
|
||
|
|
newMessage.shortTime = this.extractShortTime(newMessage.time);
|
||
|
|
|
||
|
|
// 添加到消息列表
|
||
|
|
messages.push(newMessage);
|
||
|
|
|
||
|
|
// 保存到本地存储
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
|
||
|
|
// 更新页面数据
|
||
|
|
this.setData({ messages });
|
||
|
|
|
||
|
|
// 滚动到底部
|
||
|
|
this.scrollToBottom();
|
||
|
|
|
||
|
|
// 如果是新消息,触发通知更新消息列表
|
||
|
|
if (newMessage.sender === 'other') {
|
||
|
|
try {
|
||
|
|
wx.vibrateShort(); // 轻微震动提示
|
||
|
|
const app = getApp();
|
||
|
|
if (app.globalData.onNewMessage) {
|
||
|
|
app.globalData.onNewMessage(newMessage);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('触发新消息事件失败:', e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 强制更新消息列表,确保聊天记录能在消息中心显示
|
||
|
|
this.updateMessageListGlobal();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 强制更新全局消息列表
|
||
|
|
*/
|
||
|
|
updateMessageListGlobal: function() {
|
||
|
|
try {
|
||
|
|
console.log('强制更新全局消息列表');
|
||
|
|
// 触发全局事件,通知消息列表页面更新
|
||
|
|
const app = getApp();
|
||
|
|
if (app.globalData.onNewMessage) {
|
||
|
|
app.globalData.onNewMessage({ userId: this.data.userId, userName: this.data.userName });
|
||
|
|
}
|
||
|
|
|
||
|
|
// 直接调用消息列表页面的loadChatList方法(如果能访问到)
|
||
|
|
// 这里我们通过重新加载存储来确保数据同步
|
||
|
|
console.log('消息已保存到存储,消息列表页面下次显示时会自动刷新');
|
||
|
|
} catch (e) {
|
||
|
|
console.error('更新全局消息列表失败:', e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 格式化服务器时间
|
||
|
|
formatServerTime: function(serverTime) {
|
||
|
|
const date = new Date(serverTime);
|
||
|
|
const month = date.getMonth() + 1;
|
||
|
|
const day = date.getDate();
|
||
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
||
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
|
|
return `${month}-${day} ${hours}:${minutes}`;
|
||
|
|
},
|
||
|
|
|
||
|
|
// 解析消息时间字符串为时间戳
|
||
|
|
parseMessageTime: function(timeStr) {
|
||
|
|
if (!timeStr) return Date.now();
|
||
|
|
return new Date(timeStr).getTime();
|
||
|
|
},
|
||
|
|
|
||
|
|
// 提取短时间格式 (HH:MM)
|
||
|
|
extractShortTime: function(timeStr) {
|
||
|
|
if (!timeStr) return this.getShortTime();
|
||
|
|
|
||
|
|
// 如果时间格式包含小时分钟,直接提取
|
||
|
|
const timeMatch = timeStr.match(/(\d{1,2}):(\d{2})/);
|
||
|
|
if (timeMatch) {
|
||
|
|
return `${timeMatch[1].padStart(2, '0')}:${timeMatch[2]}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.getShortTime();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 将时间戳格式化为时间字符串
|
||
|
|
*/
|
||
|
|
formatTimestampToTime: function(timestamp) {
|
||
|
|
const date = new Date(timestamp);
|
||
|
|
const month = date.getMonth() + 1;
|
||
|
|
const day = date.getDate();
|
||
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
||
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
|
|
return `${month}-${day} ${hours}:${minutes}`;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 判断是否应该显示消息时间
|
||
|
|
*/
|
||
|
|
shouldShowTime: function() {
|
||
|
|
const messages = this.data.messages;
|
||
|
|
if (messages.length === 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
const lastMessage = messages[messages.length - 1];
|
||
|
|
const currentTime = this.parseMessageTime(this.getFormattedTime());
|
||
|
|
const lastTime = this.parseMessageTime(lastMessage.time);
|
||
|
|
|
||
|
|
// 如果时间差超过5分钟,显示时间
|
||
|
|
return currentTime - lastTime > 5 * 60 * 1000;
|
||
|
|
},
|
||
|
|
|
||
|
|
// 初始化模拟消息数据
|
||
|
|
initMockMessages: function() {
|
||
|
|
// 只有从客服列表页面进入(isManager=true)且没有历史消息时,才显示开场白
|
||
|
|
if (this.data.isManager) {
|
||
|
|
// 获取当前时间作为消息时间
|
||
|
|
const currentTime = this.getFormattedTime();
|
||
|
|
|
||
|
|
// 模拟历史消息,适用于鸡蛋交易平台
|
||
|
|
const mockMessages = [
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
sender: 'other',
|
||
|
|
content: '您好!欢迎来到鸡蛋交易平台,我是' + this.data.userName + ',请问有什么可以帮助您的吗?',
|
||
|
|
time: currentTime
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
// 处理消息时间显示逻辑
|
||
|
|
const processedMessages = this.processMessageTimes(mockMessages);
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
messages: processedMessages
|
||
|
|
});
|
||
|
|
|
||
|
|
// 滚动到底部
|
||
|
|
this.scrollToBottom();
|
||
|
|
} else {
|
||
|
|
// 从消息中心进入时,不显示开场白,设置空消息列表
|
||
|
|
this.setData({
|
||
|
|
messages: []
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 输入框内容变化
|
||
|
|
onInput: function(e) {
|
||
|
|
this.setData({
|
||
|
|
inputValue: e.detail.value
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 优化消息数据结构,移除不必要的字段
|
||
|
|
*/
|
||
|
|
optimizeMessagesForStorage: function(messages) {
|
||
|
|
// 限制每条聊天最多保存1000条消息
|
||
|
|
const MAX_MESSAGES_PER_CHAT = 1000;
|
||
|
|
let optimizedMessages = messages.slice(-MAX_MESSAGES_PER_CHAT);
|
||
|
|
|
||
|
|
// 移除不必要的字段,只保留核心数据
|
||
|
|
return optimizedMessages.map(msg => ({
|
||
|
|
type: msg.type,
|
||
|
|
sender: msg.sender,
|
||
|
|
content: msg.content,
|
||
|
|
time: msg.time,
|
||
|
|
showTime: msg.showTime
|
||
|
|
// 移除shortTime字段,因为可以从time字段中提取
|
||
|
|
}));
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 保存消息到本地存储
|
||
|
|
*/
|
||
|
|
saveMessagesToStorage: function(messages) {
|
||
|
|
try {
|
||
|
|
// 优化消息数据结构
|
||
|
|
const optimizedMessages = this.optimizeMessagesForStorage(messages);
|
||
|
|
|
||
|
|
// 获取当前聊天对象的标识
|
||
|
|
const chatIdentifier = this.data.userId;
|
||
|
|
const chatKey = `chat_messages_${chatIdentifier}`;
|
||
|
|
|
||
|
|
// 保存消息到当前聊天的存储
|
||
|
|
wx.setStorageSync(chatKey, optimizedMessages);
|
||
|
|
console.log('消息已优化并保存到本地存储,当前消息数量:', optimizedMessages.length);
|
||
|
|
console.log('存储键名:', chatKey);
|
||
|
|
|
||
|
|
// 获取应用实例和用户信息
|
||
|
|
const app = getApp();
|
||
|
|
const currentUserId = wx.getStorageSync('userId') || '';
|
||
|
|
const isCurrentUserManager = app.globalData.userInfo?.userType === 'manager' ||
|
||
|
|
app.globalData.userInfo?.type === 'manager';
|
||
|
|
const currentManagerId = app.globalData.userInfo?.managerId || '';
|
||
|
|
|
||
|
|
// 确保双向消息同步
|
||
|
|
// 1. 对于客服发送的消息,确保创建反向记录
|
||
|
|
if (isCurrentUserManager && currentManagerId && currentManagerId !== chatIdentifier) {
|
||
|
|
this.ensureManagerMessageVisibility(chatIdentifier, currentManagerId, messages);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 对于普通用户发送给客服的消息,创建特殊的反向记录
|
||
|
|
if (!isCurrentUserManager && currentUserId && currentUserId !== chatIdentifier) {
|
||
|
|
// 创建反向消息记录(客服视角)
|
||
|
|
const reversedKey = `chat_messages_${currentUserId}`;
|
||
|
|
const reversedMessages = wx.getStorageSync(reversedKey) || [];
|
||
|
|
|
||
|
|
// 获取用户发送的最新消息
|
||
|
|
const userMessages = messages.filter(msg => msg.sender === 'me');
|
||
|
|
if (userMessages.length > 0) {
|
||
|
|
const latestUserMessage = userMessages[userMessages.length - 1];
|
||
|
|
|
||
|
|
// 检查消息是否已存在
|
||
|
|
const isMessageExists = reversedMessages.some(msg =>
|
||
|
|
msg.time === latestUserMessage.time && msg.sender === 'other'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!isMessageExists) {
|
||
|
|
// 创建反向消息(从客服视角看是对方发送的)
|
||
|
|
const reversedMessage = {
|
||
|
|
type: 'message',
|
||
|
|
sender: 'other',
|
||
|
|
content: latestUserMessage.content,
|
||
|
|
time: latestUserMessage.time,
|
||
|
|
showTime: reversedMessages.length === 0,
|
||
|
|
shortTime: latestUserMessage.shortTime
|
||
|
|
};
|
||
|
|
|
||
|
|
reversedMessages.push(reversedMessage);
|
||
|
|
const optimizedReversed = this.optimizeMessagesForStorage(reversedMessages);
|
||
|
|
wx.setStorageSync(reversedKey, optimizedReversed);
|
||
|
|
console.log('已为用户创建反向消息记录,确保客服能看到');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 强制更新全局消息列表
|
||
|
|
this.updateMessageListGlobal();
|
||
|
|
} catch (e) {
|
||
|
|
console.error('保存消息到本地存储失败:', e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 确保客服能看到自己发送的消息
|
||
|
|
* 当客服向其他用户发送消息时,确保消息也能在自己的消息列表中显示
|
||
|
|
*/
|
||
|
|
ensureManagerMessageVisibility: function(targetId, currentManagerId, messages) {
|
||
|
|
try {
|
||
|
|
// 从消息中提取该客服发送的最新消息
|
||
|
|
const managerMessages = messages.filter(msg => msg.sender === 'me');
|
||
|
|
if (managerMessages.length > 0) {
|
||
|
|
// 获取最新的客服发送消息
|
||
|
|
const latestMessage = managerMessages[managerMessages.length - 1];
|
||
|
|
|
||
|
|
// 创建反向的消息记录(客服视角)
|
||
|
|
const reversedKey = `chat_messages_${targetId}`;
|
||
|
|
let reversedMessages = wx.getStorageSync(reversedKey) || [];
|
||
|
|
|
||
|
|
// 检查这条消息是否已经存在,避免重复添加
|
||
|
|
const isMessageExists = reversedMessages.some(msg =>
|
||
|
|
msg.time === latestMessage.time && msg.sender === 'me'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!isMessageExists) {
|
||
|
|
// 添加新消息到反向消息列表
|
||
|
|
const newReversedMessage = { ...latestMessage };
|
||
|
|
// 设置是否显示时间
|
||
|
|
newReversedMessage.showTime = reversedMessages.length === 0;
|
||
|
|
reversedMessages.push(newReversedMessage);
|
||
|
|
|
||
|
|
// 保存反向消息记录
|
||
|
|
const optimizedReversedMessages = this.optimizeMessagesForStorage(reversedMessages);
|
||
|
|
wx.setStorageSync(reversedKey, optimizedReversedMessages);
|
||
|
|
console.log('已为客服创建反向消息记录,确保消息可见性');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新消息列表
|
||
|
|
this.updateMessageListGlobal();
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('确保客服消息可见性失败:', e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 清理指定聊天的历史消息
|
||
|
|
*/
|
||
|
|
clearChatHistory: function() {
|
||
|
|
try {
|
||
|
|
wx.removeStorageSync(`chat_messages_${this.data.userId}`);
|
||
|
|
this.setData({
|
||
|
|
messages: []
|
||
|
|
});
|
||
|
|
console.log('聊天历史已清空');
|
||
|
|
wx.showToast({
|
||
|
|
title: '聊天历史已清空',
|
||
|
|
icon: 'success'
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
console.error('清空聊天历史失败:', e);
|
||
|
|
wx.showToast({
|
||
|
|
title: '清空失败',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// 发送消息
|
||
|
|
sendMessage: function() {
|
||
|
|
if (!this.data.inputValue.trim()) return;
|
||
|
|
|
||
|
|
// 创建消息对象
|
||
|
|
const newMessage = {
|
||
|
|
type: 'message',
|
||
|
|
sender: 'me',
|
||
|
|
content: this.data.inputValue.trim(),
|
||
|
|
time: this.getFormattedTime(),
|
||
|
|
status: 'sending' // 初始状态为发送中
|
||
|
|
};
|
||
|
|
|
||
|
|
// 添加新消息到消息列表
|
||
|
|
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);
|
||
|
|
|
||
|
|
// 立即更新UI
|
||
|
|
this.setData({
|
||
|
|
messages: messages,
|
||
|
|
inputValue: ''
|
||
|
|
});
|
||
|
|
|
||
|
|
// 保存消息到本地存储
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
|
||
|
|
this.scrollToBottom();
|
||
|
|
|
||
|
|
// 根据模式决定发送方式
|
||
|
|
if (this.data.isMockMode) {
|
||
|
|
// 模拟模式:仅用于开发测试,消息不会真正发送
|
||
|
|
console.log('模拟模式:消息未通过WebSocket发送', newMessage);
|
||
|
|
|
||
|
|
// 更新消息状态为已发送(模拟)
|
||
|
|
setTimeout(() => {
|
||
|
|
this.updateMessageStatus(messages.length - 1, 'sent');
|
||
|
|
}, 500);
|
||
|
|
|
||
|
|
wx.showToast({
|
||
|
|
title: '测试模式:消息仅在本地显示',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 1500
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// 真实模式:通过WebSocket发送消息
|
||
|
|
const sent = this.sendWebSocketMessage(newMessage);
|
||
|
|
|
||
|
|
// 更新消息状态为已发送
|
||
|
|
setTimeout(() => {
|
||
|
|
this.updateMessageStatus(messages.length - 1, sent ? 'sent' : 'failed');
|
||
|
|
}, 500);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 更新消息状态
|
||
|
|
* @param {number} index - 消息索引
|
||
|
|
* @param {string} status - 状态值(sending/sent/failed)
|
||
|
|
*/
|
||
|
|
updateMessageStatus: function(index, status) {
|
||
|
|
const messages = [...this.data.messages];
|
||
|
|
if (messages[index]) {
|
||
|
|
messages[index].status = status;
|
||
|
|
this.setData({ messages });
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 通过WebSocket发送消息
|
||
|
|
*/
|
||
|
|
sendWebSocketMessage: function(message) {
|
||
|
|
const app = getApp();
|
||
|
|
// 获取当前用户信息
|
||
|
|
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer';
|
||
|
|
const currentUserId = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || 'user_' + Date.now());
|
||
|
|
const chatTargetId = this.data.userId; // 聊天对象ID
|
||
|
|
const isCurrentUserService = currentUserType === 'customer_service' ||
|
||
|
|
app.globalData.userInfo?.userType === 'customer_service' ||
|
||
|
|
app.globalData.userInfo?.type === 'customer_service';
|
||
|
|
|
||
|
|
console.log('发送消息 - 用户类型:', currentUserType, '是否客服:', isCurrentUserService, '聊天对象ID:', chatTargetId);
|
||
|
|
|
||
|
|
if (socketManager.getConnectionStatus()) {
|
||
|
|
// 构建符合服务器要求的消息格式
|
||
|
|
const wsMessage = {
|
||
|
|
type: 'chat_message', // 服务器期望的类型
|
||
|
|
direction: isCurrentUserService ? 'service_to_customer' : 'customer_to_service', // 消息方向
|
||
|
|
data: {
|
||
|
|
// 根据用户类型设置正确的接收方ID
|
||
|
|
receiverId: chatTargetId,
|
||
|
|
senderId: currentUserId,
|
||
|
|
senderType: isCurrentUserService ? 'customer_service' : 'customer',
|
||
|
|
content: message.content,
|
||
|
|
contentType: message.isImage ? 2 : 1, // 1: 文本消息, 2: 图片消息
|
||
|
|
timestamp: Date.now()
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// 如果是图片消息,添加URL
|
||
|
|
if (message.isImage && message.content.startsWith('http')) {
|
||
|
|
wsMessage.data.fileUrl = message.content;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('通过WebSocket发送符合服务器格式的消息:', wsMessage);
|
||
|
|
const sent = socketManager.send(wsMessage);
|
||
|
|
|
||
|
|
if (sent) {
|
||
|
|
// 添加发送成功的提示
|
||
|
|
wx.showToast({
|
||
|
|
title: '消息已发送',
|
||
|
|
icon: 'success',
|
||
|
|
duration: 1000
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
console.error('消息发送失败,可能是队列已满或连接断开');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.error('WebSocket未连接,无法发送消息');
|
||
|
|
wx.showToast({
|
||
|
|
title: '连接未建立,发送失败',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 1500
|
||
|
|
});
|
||
|
|
// 尝试重新连接
|
||
|
|
this.initWebSocket();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 模拟对方回复(用于开发测试)
|
||
|
|
*/
|
||
|
|
simulateReply: function() {
|
||
|
|
setTimeout(() => {
|
||
|
|
const replyMessage = {
|
||
|
|
type: 'message',
|
||
|
|
sender: 'other',
|
||
|
|
content: this.getRandomReply(),
|
||
|
|
time: this.getFormattedTime(),
|
||
|
|
showTime: false, // 回复消息通常不显示时间
|
||
|
|
shortTime: this.getShortTime()
|
||
|
|
};
|
||
|
|
|
||
|
|
const messages = [...this.data.messages];
|
||
|
|
messages.push(replyMessage);
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
messages: messages
|
||
|
|
});
|
||
|
|
|
||
|
|
// 保存消息到本地存储
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
|
||
|
|
this.scrollToBottom();
|
||
|
|
}, 1000);
|
||
|
|
},
|
||
|
|
|
||
|
|
// 获取格式化时间(带日期)
|
||
|
|
getFormattedTime: function() {
|
||
|
|
const now = new Date();
|
||
|
|
const month = now.getMonth() + 1;
|
||
|
|
const date = now.getDate();
|
||
|
|
const hours = now.getHours().toString().padStart(2, '0');
|
||
|
|
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||
|
|
return `${month}-${date} ${hours}:${minutes}`;
|
||
|
|
},
|
||
|
|
|
||
|
|
// 获取仅包含时分的时间格式
|
||
|
|
getShortTime: function() {
|
||
|
|
const now = new Date();
|
||
|
|
const hours = now.getHours().toString().padStart(2, '0');
|
||
|
|
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||
|
|
return `${hours}:${minutes}`;
|
||
|
|
},
|
||
|
|
|
||
|
|
// 处理消息时间显示逻辑
|
||
|
|
processMessageTimes: function(messages) {
|
||
|
|
return messages.map((message, index) => {
|
||
|
|
// 复制消息对象,避免直接修改原数据
|
||
|
|
const processedMessage = { ...message };
|
||
|
|
|
||
|
|
// 设置是否显示消息时间
|
||
|
|
processedMessage.showTime = false;
|
||
|
|
|
||
|
|
// 第一条消息总是显示时间
|
||
|
|
if (index === 0) {
|
||
|
|
processedMessage.showTime = true;
|
||
|
|
} else {
|
||
|
|
// 与上一条消息比较时间差
|
||
|
|
const currentTime = this.parseMessageTime(message.time);
|
||
|
|
const previousTime = this.parseMessageTime(messages[index - 1].time);
|
||
|
|
|
||
|
|
// 如果时间差超过5分钟,显示时间
|
||
|
|
if (currentTime - previousTime > 5 * 60 * 1000) {
|
||
|
|
processedMessage.showTime = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 为每条消息添加短时间格式(用于消息气泡旁显示)
|
||
|
|
processedMessage.shortTime = this.extractShortTime(message.time);
|
||
|
|
|
||
|
|
return processedMessage;
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// 获取随机回复
|
||
|
|
getRandomReply: function() {
|
||
|
|
const replies = [
|
||
|
|
'好的,了解了',
|
||
|
|
'稍等,我马上回复',
|
||
|
|
'这个没问题',
|
||
|
|
'您还有其他问题吗?',
|
||
|
|
'好的,我知道了'
|
||
|
|
];
|
||
|
|
return replies[Math.floor(Math.random() * replies.length)];
|
||
|
|
},
|
||
|
|
|
||
|
|
// 预览图片 - 使用微信插件进行预览
|
||
|
|
previewImage: function(e) {
|
||
|
|
let imageUrl = e.currentTarget.dataset.src;
|
||
|
|
|
||
|
|
// 处理URL格式,确保能正确预览
|
||
|
|
if (typeof imageUrl === 'string') {
|
||
|
|
// 移除可能的引号
|
||
|
|
imageUrl = imageUrl.replace(/^["']|["']$/g, '');
|
||
|
|
|
||
|
|
// 检查URL协议
|
||
|
|
const isHttpProtocol = imageUrl.startsWith('http://') || imageUrl.startsWith('https://');
|
||
|
|
const isWxfileProtocol = imageUrl.startsWith('wxfile://');
|
||
|
|
|
||
|
|
// 如果URL缺少协议且不是wxfile协议,添加https前缀
|
||
|
|
if (!isHttpProtocol && !isWxfileProtocol) {
|
||
|
|
imageUrl = 'https://' + imageUrl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用微信原生预览功能
|
||
|
|
wx.previewImage({
|
||
|
|
current: imageUrl, // 当前显示图片的链接
|
||
|
|
urls: [imageUrl], // 需要预览的图片链接列表
|
||
|
|
showmenu: true, // 显示菜单,允许用户保存图片等操作
|
||
|
|
success: function() {
|
||
|
|
console.log('图片预览成功');
|
||
|
|
},
|
||
|
|
fail: function(error) {
|
||
|
|
console.error('图片预览失败:', error);
|
||
|
|
wx.showToast({
|
||
|
|
title: '图片预览失败',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// 滚动到底部
|
||
|
|
scrollToBottom: function() {
|
||
|
|
setTimeout(() => {
|
||
|
|
wx.createSelectorQuery().select('#message-container').boundingClientRect(res => {
|
||
|
|
if (res) {
|
||
|
|
wx.createSelectorQuery().select('#message-list').scrollOffset(res => {
|
||
|
|
if (res) {
|
||
|
|
wx.createSelectorQuery().select('#message-list').context(context => {
|
||
|
|
context.scrollTo({ scrollTop: res.scrollHeight, animated: true });
|
||
|
|
}).exec();
|
||
|
|
}
|
||
|
|
}).exec();
|
||
|
|
}
|
||
|
|
}).exec();
|
||
|
|
}, 100);
|
||
|
|
},
|
||
|
|
|
||
|
|
// 显示表情
|
||
|
|
showEmoji: function() {
|
||
|
|
// 表情功能占位符
|
||
|
|
wx.showToast({
|
||
|
|
title: '表情功能开发中',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// 切换语音模式
|
||
|
|
toggleVoiceMode: function() {
|
||
|
|
// 语音模式切换功能占位符
|
||
|
|
wx.showToast({
|
||
|
|
title: '语音功能开发中',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// 显示更多操作
|
||
|
|
showMoreOptions: function() {
|
||
|
|
wx.showActionSheet({
|
||
|
|
itemList: ['发送图片', '发送位置'],
|
||
|
|
success: (res) => {
|
||
|
|
if (!res.cancel) {
|
||
|
|
if (res.tapIndex === 0) {
|
||
|
|
// 发送图片
|
||
|
|
wx.chooseImage({
|
||
|
|
count: 1,
|
||
|
|
success: (res) => {
|
||
|
|
console.log('选择的图片路径:', res.tempFilePaths[0]);
|
||
|
|
this.sendImageMessage(res.tempFilePaths[0]);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else if (res.tapIndex === 1) {
|
||
|
|
// 发送位置
|
||
|
|
wx.chooseLocation({
|
||
|
|
success: function(res) {
|
||
|
|
console.log('选择的位置:', res);
|
||
|
|
// 这里可以添加发送位置的逻辑
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// 发送图片消息
|
||
|
|
sendImageMessage: function(imagePath) {
|
||
|
|
const newMessage = {
|
||
|
|
type: 'message',
|
||
|
|
sender: 'me',
|
||
|
|
content: imagePath,
|
||
|
|
time: this.getFormattedTime(),
|
||
|
|
isImage: true // 标记为图片消息
|
||
|
|
};
|
||
|
|
|
||
|
|
// 添加新消息到消息列表
|
||
|
|
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);
|
||
|
|
|
||
|
|
// 立即更新UI
|
||
|
|
this.setData({
|
||
|
|
messages: messages
|
||
|
|
});
|
||
|
|
|
||
|
|
// 保存消息到本地存储
|
||
|
|
this.saveMessagesToStorage(messages);
|
||
|
|
|
||
|
|
this.scrollToBottom();
|
||
|
|
|
||
|
|
// 根据模式决定发送方式
|
||
|
|
if (this.data.isMockMode) {
|
||
|
|
// 模拟模式:不再自动回复,让用户和客服可以真正沟通
|
||
|
|
// 移除了 this.simulateReply() 调用
|
||
|
|
} else {
|
||
|
|
// WebSocket模式:通过WebSocket发送消息(实际项目中需要先上传图片获取URL)
|
||
|
|
this.sendWebSocketMessage(newMessage);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 生命周期函数--监听页面初次渲染完成
|
||
|
|
*/
|
||
|
|
onReady: function () {
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 生命周期函数--监听页面显示
|
||
|
|
*/
|
||
|
|
onShow: function () {
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 生命周期函数--监听页面隐藏
|
||
|
|
*/
|
||
|
|
onHide: function () {
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 切换模拟模式
|
||
|
|
*/
|
||
|
|
toggleMockMode: function(e) {
|
||
|
|
const checked = e.detail.value;
|
||
|
|
|
||
|
|
wx.showLoading({
|
||
|
|
title: checked ? '切换到WebSocket模式...' : '切换到模拟模式...',
|
||
|
|
});
|
||
|
|
|
||
|
|
// 如果是切换到模拟模式
|
||
|
|
if (!checked) {
|
||
|
|
// 清理WebSocket连接和监听器
|
||
|
|
socketManager.off('open');
|
||
|
|
socketManager.off('message');
|
||
|
|
socketManager.off('close');
|
||
|
|
socketManager.off('error');
|
||
|
|
socketManager.off('reconnecting');
|
||
|
|
socketManager.off('reconnectFailed');
|
||
|
|
|
||
|
|
// 移除网络状态监听
|
||
|
|
wx.offNetworkStatusChange();
|
||
|
|
|
||
|
|
// 可选:关闭连接
|
||
|
|
socketManager.close();
|
||
|
|
|
||
|
|
this.setData({
|
||
|
|
isMockMode: true,
|
||
|
|
connectionStatus: '',
|
||
|
|
connectionMessage: ''
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// 切换到WebSocket模式
|
||
|
|
this.setData({
|
||
|
|
isMockMode: false
|
||
|
|
});
|
||
|
|
|
||
|
|
// 初始化WebSocket连接
|
||
|
|
this.initWebSocket();
|
||
|
|
}
|
||
|
|
|
||
|
|
setTimeout(() => {
|
||
|
|
wx.hideLoading();
|
||
|
|
wx.showToast({
|
||
|
|
title: checked ? '已切换到WebSocket模式' : '已切换到模拟模式',
|
||
|
|
icon: 'none',
|
||
|
|
duration: 1500
|
||
|
|
});
|
||
|
|
}, 1000);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 生命周期函数--监听页面卸载
|
||
|
|
*/
|
||
|
|
onUnload: function () {
|
||
|
|
// 清理WebSocket连接和监听器
|
||
|
|
if (!this.data.isMockMode) {
|
||
|
|
socketManager.off('open');
|
||
|
|
socketManager.off('message');
|
||
|
|
socketManager.off('close');
|
||
|
|
socketManager.off('error');
|
||
|
|
socketManager.off('reconnecting');
|
||
|
|
socketManager.off('reconnectFailed');
|
||
|
|
|
||
|
|
// 移除网络状态监听
|
||
|
|
wx.offNetworkStatusChange();
|
||
|
|
|
||
|
|
// 关闭WebSocket连接
|
||
|
|
// 注意:这里可以根据实际需求决定是否关闭连接
|
||
|
|
// 如果是多页面共用一个连接,可以不在这里关闭
|
||
|
|
// socketManager.close();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 页面相关事件处理函数--监听用户下拉动作
|
||
|
|
*/
|
||
|
|
onPullDownRefresh: function () {
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 页面上拉触底事件的处理函数
|
||
|
|
*/
|
||
|
|
onReachBottom: function () {
|
||
|
|
this.loadMoreMessages();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 加载更多消息
|
||
|
|
*/
|
||
|
|
loadMoreMessages: function() {
|
||
|
|
// 加载历史消息功能占位符
|
||
|
|
console.log('加载更多历史消息');
|
||
|
|
// 实际项目中,这里应该从服务器加载更多历史消息
|
||
|
|
wx.showToast({
|
||
|
|
title: '已加载全部历史消息',
|
||
|
|
icon: 'none'
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 用户点击右上角分享
|
||
|
|
*/
|
||
|
|
onShareAppMessage: function () {
|
||
|
|
|
||
|
|
}
|
||
|
|
});
|