From 98fab39000e6af5a6a339f129ca0fcf86acc3ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?= <15778543+xufeiyang6017@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 16:38:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=81=8A=E5=A4=A9=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=8A=9F=E8=83=BD=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E9=97=AE=E9=A2=98=E3=80=81=E5=88=9B=E5=BB=BA=E5=B7=A6?= =?UTF-8?q?=E4=BE=A7=E8=81=8A=E5=A4=A9=E5=88=97=E8=A1=A8=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E3=80=81=E5=AE=9E=E7=8E=B0=E7=9C=9F=E5=AE=9E=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/chat/index.js | 146 +++++++++++----- server-example/server-mysql.js | 300 --------------------------------- utils/api.js | 84 +++++++++ 3 files changed, 184 insertions(+), 346 deletions(-) diff --git a/pages/chat/index.js b/pages/chat/index.js index e53dc06..c6c3dcc 100644 --- a/pages/chat/index.js +++ b/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; - this.setData({ - chatList: chatList, - filteredChatList: chatList + // 尝试从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: 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' + }); }); }, diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index a708899..9782ea3 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -6385,308 +6385,8 @@ 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) { diff --git a/utils/api.js b/utils/api.js index 7f6fe0c..8924e84 100644 --- a/utils/api.js +++ b/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 || '获取业务员信息失败,请稍后重试')); + }); + }); } }; \ No newline at end of file