Browse Source

实现聊天列表功能:修复导航问题、创建左侧聊天列表界面、实现真实聊天数据加载逻辑

pull/1/head
徐飞洋 3 months ago
parent
commit
98fab39000
  1. 142
      pages/chat/index.js
  2. 300
      server-example/server-mysql.js
  3. 84
      utils/api.js

142
pages/chat/index.js

@ -1,4 +1,6 @@
// pages/chat/index.js
const API = require('../../utils/api.js');
Page({
data: {
chatList: [],
@ -11,53 +13,105 @@ Page({
},
loadChatList: function () {
// 模拟加载消息列表
const chatList = [
{
id: 1,
name: '系统消息',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '欢迎使用消息中心功能',
time: '刚刚',
unread: true
},
{
id: 2,
name: '客服小王',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '您好,有什么可以帮助您的吗?',
time: '10分钟前',
unread: false
},
{
id: 3,
name: '供应商小李',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '您的订单已经发货,请注意查收',
time: '1小时前',
unread: true
},
{
id: 4,
name: '采购部门',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '关于下个月的采购计划,请查收附件',
time: '昨天',
unread: false
},
{
id: 5,
name: '技术支持',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '系统升级已完成,新增了多项功能',
time: '2天前',
unread: true
// 显示加载提示
wx.showLoading({
title: '加载中...',
});
// 获取用户手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
// 尝试从users中获取手机号
if (userId && users[userId] && users[userId].phoneNumber) {
userPhone = users[userId].phoneNumber;
} else {
// 尝试从全局用户信息获取
const userInfo = wx.getStorageSync('userInfo');
if (userInfo && userInfo.phoneNumber) {
userPhone = userInfo.phoneNumber;
} else {
// 尝试从直接存储的phoneNumber获取
userPhone = wx.getStorageSync('phoneNumber');
}
}
// 如果没有手机号,显示错误提示
if (!userPhone) {
wx.hideLoading();
wx.showToast({
title: '请先登录并绑定手机号',
icon: 'none'
});
return;
}
// 调用API获取聊天列表
API.getChatList(userPhone).then(res => {
if (res && Array.isArray(res)) {
// 处理每个聊天项,获取业务员信息
const chatListPromises = res.map(async (chatItem) => {
if (chatItem.manager_phone) {
try {
// 获取业务员信息
const personnelInfo = await API.getSalesPersonnelInfo(chatItem.manager_phone);
if (personnelInfo) {
// 使用业务员的alias作为显示名称
chatItem.name = personnelInfo.alias || chatItem.manager_phone;
chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';
}
];
} catch (error) {
console.error('获取业务员信息失败:', error);
// 如果获取失败,使用默认名称
chatItem.name = chatItem.manager_phone;
chatItem.avatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';
}
} else {
// 系统消息或其他没有manager_phone的消息
chatItem.name = chatItem.name || '系统消息';
chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';
}
// 确保消息内容存在
chatItem.content = chatItem.content || '暂无消息内容';
// 确保时间存在
chatItem.time = chatItem.time || '刚刚';
// 确保unread字段存在
chatItem.unread = chatItem.unread || false;
return chatItem;
});
// 等待所有聊天项处理完成
Promise.all(chatListPromises).then(processedChatList => {
this.setData({
chatList: chatList,
filteredChatList: chatList
chatList: processedChatList,
filteredChatList: processedChatList
});
wx.hideLoading();
}).catch(error => {
console.error('处理聊天列表失败:', error);
wx.hideLoading();
wx.showToast({
title: '加载聊天列表失败',
icon: 'none'
});
});
} else {
wx.hideLoading();
wx.showToast({
title: '加载聊天列表失败',
icon: 'none'
});
}
}).catch(error => {
console.error('获取聊天列表失败:', error);
wx.hideLoading();
wx.showToast({
title: error.message || '加载聊天列表失败',
icon: 'none'
});
});
},

300
server-example/server-mysql.js

@ -6385,309 +6385,9 @@ app.get('/api/online-stats', async (req, res) => {
}
});
// REST API接口 - 获取客服列表 - 修改为按照用户需求:工位名为采购员且有电话号码
app.get('/api/managers', async (req, res) => {
try {
// 查询userlogin数据库中的personnel表,获取工位为采购员且有电话号码的用户
// 根据表结构选择所有需要的字段
const [personnelData] = await sequelize.query(
'SELECT id, managerId, managercompany, managerdepartment, organization, projectName, name, alias, phoneNumber, avatarUrl FROM userlogin.personnel WHERE projectName = ? AND phoneNumber IS NOT NULL AND phoneNumber != "" ORDER BY id ASC',
{ replacements: ['采购员'] }
);
// 其次从数据库查询结果检查
const dbStatus = onlineStatusMap[stringId] || (stringManagerId && onlineStatusMap[stringManagerId]) || false;
console.log(`客服在线状态(数据库): id=${id}, status=${dbStatus}`);
return dbStatus;
};
const managers = personnelData.map((person, index) => ({
id: person.id,
managerId: person.managerId || `PM${String(index + 1).padStart(3, '0')}`,
managercompany: person.managercompany || '未知公司',
managerdepartment: person.managerdepartment || '采购部',
organization: person.organization || '采购组',
projectName: person.projectName || '采购员',
name: person.name || '未知',
alias: person.alias || person.name || '未知',
phoneNumber: person.phoneNumber || '',
avatar: person.avatarUrl || '', // 使用表中的avatarUrl字段
online: isManagerOnline(person.id, person.managerId) // 综合检查在线状态
}));
res.status(200).json({
success: true,
code: 200,
data: managers
});
} catch (error) {
console.error('获取客服列表失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '获取客服列表失败: ' + error.message
});
}
});
// WebSocket服务器事件处理
wss.on('connection', (ws, req) => {
console.log('新的WebSocket连接建立');
// 生成连接ID
const connectionId = crypto.randomUUID();
ws.connectionId = connectionId;
// 存储连接信息
connections.set(connectionId, {
ws,
userId: null,
managerId: null,
isUser: false,
isManager: false,
connectedAt: getBeijingTime()
});
// 连接认证处理
ws.on('message', async (message) => {
try {
// 更新连接活动时间
updateConnectionActivity(ws.connectionId);
const data = JSON.parse(message.toString());
// 处理认证消息
if (data.type === 'auth' || data.action === 'auth') {
await handleAuth(ws, data);
return;
}
// 处理心跳消息
if (data.type === 'ping') {
// 更新连接活动时间
updateConnectionActivity(ws.connectionId);
// 更新心跳时间到chat_online_status表
const connection = connections.get(ws.connectionId);
if (connection) {
const userId = connection.isUser ? connection.userId : connection.managerId;
const type = connection.isUser ? 1 : 2;
updateChatOnlineStatusHeartbeat(userId, type);
}
ws.send(JSON.stringify({ type: 'pong' }));
return;
}
// 处理创建会话请求
if (data.type === 'create_conversation') {
console.log('接收到创建会话请求:', JSON.stringify(data));
try {
const { userId, managerId, timestamp } = data;
// 验证必要参数
if (!userId || !managerId) {
console.error('错误: 创建会话缺少必要参数');
ws.send(JSON.stringify({
type: 'error',
message: '创建会话失败: 缺少用户ID或客服ID'
}));
return;
}
// 创建或获取会话
const conversation = await createOrGetConversation(userId, managerId);
console.log('会话创建成功:', conversation);
// 返回会话创建成功响应
ws.send(JSON.stringify({
type: 'conversation_created',
conversationId: conversation.conversation_id,
userId: conversation.userId,
managerId: conversation.managerId,
status: conversation.status,
timestamp: timestamp || Date.now()
}));
} catch (error) {
console.error('创建会话失败:', error);
ws.send(JSON.stringify({
type: 'error',
message: '创建会话失败: ' + error.message
}));
}
return;
}
// 处理聊天消息
if (data.type === 'chat_message') {
console.log('接收到聊天消息:', JSON.stringify(data));
// 直接使用接收到的数据作为payload,确保格式正确
const payload = data;
// 确保必要字段都已设置
if (!payload.senderId && !payload.userId) {
console.error('错误: 缺少用户ID字段');
ws.send(JSON.stringify({
type: 'error',
message: '消息缺少用户ID'
}));
return;
}
// 确保senderType是数字类型
if (payload.senderType && typeof payload.senderType === 'string') {
if (payload.senderType.includes('customer') || payload.senderType.includes('user')) {
payload.senderType = 1; // 普通用户
} else if (payload.senderType.includes('service') || payload.senderType.includes('manager')) {
payload.senderType = 2; // 客服
}
} else if (!payload.senderType) {
// 设置默认值
const connection = connections.get(ws.connectionId);
if (connection && connection.isManager) {
payload.senderType = 2;
} else {
payload.senderType = 1;
}
}
// 确保所有数字字段都是数字类型
payload.senderType = Number(payload.senderType);
payload.contentType = Number(payload.contentType || 1);
console.log('处理聊天消息 - 准备传递的payload:', JSON.stringify(payload));
// 调用handleChatMessage处理消息
try {
await handleChatMessage(ws, payload);
console.log('消息处理完成');
} catch (error) {
console.error('处理聊天消息时出错:', error);
ws.send(JSON.stringify({
type: 'error',
message: '消息处理失败',
error: error.message
}));
}
return;
}
// 处理会话相关消息
if (data.type === 'session') {
// 直接传递整个data对象给handleSessionMessage,因为action可能在data根级别
await handleSessionMessage(ws, data);
return;
}
// 直接处理get_messages请求
if (data.type === 'get_messages') {
// 直接传递整个data对象给handleSessionMessage,因为action可能在data根级别
await handleSessionMessage(ws, data);
return;
}
// 处理未读消息标记
if (data.type === 'mark_read') {
const payload = data.data || data.payload || data;
await handleMarkRead(ws, payload);
return;
}
} catch (error) {
console.error('处理WebSocket消息错误:', error);
ws.send(JSON.stringify({
type: 'error',
message: '消息处理失败'
}));
}
});
// 连接关闭处理
ws.on('close', async () => {
console.log('WebSocket连接关闭');
const connection = connections.get(connectionId);
if (connection) {
// 更新在线状态
if (connection.isUser && connection.userId) {
onlineUsers.delete(connection.userId);
await updateUserOnlineStatus(connection.userId, 0);
// 更新chat_online_status表为离线
await updateChatOnlineStatusOffline(connection.userId, 1);
} else if (connection.isManager && connection.managerId) {
onlineManagers.delete(connection.managerId);
await updateManagerOnlineStatus(connection.managerId, 0);
// 更新chat_online_status表为离线
await updateChatOnlineStatusOffline(connection.managerId, 2);
}
// 从连接池中移除
connections.delete(connectionId);
}
});
// 更新chat_online_status表的心跳时间
async function updateChatOnlineStatusHeartbeat(userId, type) {
try {
const now = getBeijingTime();
// 根据userId是否为null使用不同的SQL语句
let sql;
let replacements;
if (userId === null || userId === undefined) {
sql = `UPDATE chat_online_status
SET last_heartbeat = ?, updated_at = ?
WHERE userId IS NULL AND type = ? AND is_online = 1`;
replacements = [now, now, type];
} else {
sql = `UPDATE chat_online_status
SET last_heartbeat = ?, updated_at = ?
WHERE userId = ? AND type = ? AND is_online = 1`;
replacements = [now, now, userId, type];
}
await sequelize.query(sql, { replacements });
} catch (error) {
// 心跳更新失败不影响主流程,仅记录日志
console.error('更新chat_online_status心跳时间失败:', error);
}
}
// 更新chat_online_status表为离线状态
async function updateChatOnlineStatusOffline(userId, type) {
try {
const now = getBeijingTime();
// 根据userId是否为null使用不同的SQL语句
let sql;
let replacements;
if (userId === null || userId === undefined) {
sql = `UPDATE chat_online_status
SET is_online = 0, updated_at = ?
WHERE userId IS NULL AND type = ?`;
replacements = [now, type];
} else {
sql = `UPDATE chat_online_status
SET is_online = 0, updated_at = ?
WHERE userId = ? AND type = ?`;
replacements = [now, userId, type];
}
await sequelize.query(sql, { replacements });
console.log(`更新chat_online_status离线状态成功: userId=${userId}, type=${type}`);
} catch (error) {
console.error('更新chat_online_status离线状态失败:', error);
}
}
// 连接错误处理
ws.on('error', (error) => {
console.error('WebSocket连接错误:', error);
});
});
// 认证处理函数
async function handleAuth(ws, data) {
// 详细日志记录原始认证数据

84
utils/api.js

@ -3506,6 +3506,90 @@ module.exports = {
reject(new Error('取消收藏失败,请稍后重试'));
});
});
},
// 获取聊天列表数据
getChatList: function (userPhone) {
console.log('API.getChatList - userPhone:', userPhone);
return new Promise((resolve, reject) => {
// 如果没有传入手机号,尝试从本地存储获取
if (!userPhone) {
// 获取用户信息,包含手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
// 尝试从users中获取手机号
if (userId && users[userId] && users[userId].phoneNumber) {
userPhone = users[userId].phoneNumber;
} else {
// 尝试从全局用户信息获取
const userInfo = wx.getStorageSync('userInfo');
if (userInfo && userInfo.phoneNumber) {
userPhone = userInfo.phoneNumber;
} else {
// 尝试从直接存储的phoneNumber获取
userPhone = wx.getStorageSync('phoneNumber');
}
}
}
// 如果没有手机号,直接返回错误
if (!userPhone) {
reject(new Error('请先登录并绑定手机号'));
return;
}
const requestData = {
user_phone: userPhone
};
console.log('获取聊天列表请求数据:', requestData);
request('/api/chat/list', 'POST', requestData).then(res => {
console.log('获取聊天列表成功:', res);
// 标准化响应格式
if (res && Array.isArray(res)) {
resolve(res);
} else if (res && res.data) {
resolve(Array.isArray(res.data) ? res.data : []);
} else {
resolve([]);
}
}).catch(error => {
console.error('获取聊天列表失败:', error);
reject(new Error(error.message || '获取聊天列表失败,请稍后重试'));
});
});
},
// 获取业务员详细信息
getSalesPersonnelInfo: function (phone) {
console.log('API.getSalesPersonnelInfo - phone:', phone);
return new Promise((resolve, reject) => {
if (!phone) {
reject(new Error('手机号不能为空'));
return;
}
const requestData = {
phone: phone
};
console.log('获取业务员信息请求数据:', requestData);
request('/api/personnel/get', 'POST', requestData).then(res => {
console.log('获取业务员信息成功:', res);
// 标准化响应格式
if (res && res.data) {
resolve(res.data);
} else {
resolve(res);
}
}).catch(error => {
console.error('获取业务员信息失败:', error);
reject(new Error(error.message || '获取业务员信息失败,请稍后重试'));
});
});
}
};
Loading…
Cancel
Save