Browse Source

修复聊天功能系列问题:修复白屏、登录失败、WebSocket连接、消息显示等问题

pull/1/head
Default User 3 months ago
parent
commit
2bd5a18640
  1. 176
      app.js
  2. 1822
      pages/chat-detail/index.js
  3. 3
      pages/chat-detail/index.json
  4. 613
      pages/chat/index.js
  5. 2
      pages/chat/index.json
  6. 91
      pages/chat/index.wxml
  7. 270
      pages/chat/index.wxss
  8. 86
      pages/message-list/index.js
  9. 171
      pages/profile/index.js
  10. 2
      server-example/.env
  11. 2
      server-example/port-conflict-fix.js
  12. 276
      server-example/server-mysql.js
  13. 539
      utils/api.js
  14. 286
      utils/websocket.js

176
app.js

@ -3,14 +3,18 @@ App({
// 初始化应用
console.log('App Launch')
// 初始化WebSocket管理器
const wsManager = require('./utils/websocket').default;
this.globalData.webSocketManager = wsManager;
// 连接WebSocket服务器
wsManager.connect('ws://localhost:3003', {
maxReconnectAttempts: 5,
reconnectInterval: 3000,
heartbeatTime: 30000
});
try {
const wsManager = require('./utils/websocket');
this.globalData.webSocketManager = wsManager;
// 连接WebSocket服务器
wsManager.connect('ws://localhost:3003', {
maxReconnectAttempts: 5,
reconnectInterval: 3000,
heartbeatTime: 30000
});
} catch (e) {
console.error('初始化WebSocket管理器失败:', e);
}
// 初始化本地存储的标签和用户数据
if (!wx.getStorageSync('users')) {
wx.setStorageSync('users', {})
@ -71,6 +75,9 @@ App({
console.log('App初始化 - 用户类型:', this.globalData.userType);
console.log('App初始化 - 用户信息:', this.globalData.userInfo);
// 异步获取客服列表并缓存
this.fetchAndCacheCustomerServices();
// 获取用户信息
wx.getSetting({
success: res => {
@ -78,24 +85,96 @@ App({
// 已经授权,可以直接调用 getUserInfo 获取头像昵称
wx.getUserInfo({
success: res => {
this.globalData.userInfo = res.userInfo
// 存储用户ID(实际项目中使用openid)
if (!wx.getStorageSync('userId')) {
const userId = 'user_' + Date.now()
wx.setStorageSync('userId', userId)
// 初始化用户数据
const users = wx.getStorageSync('users')
users[userId] = {
info: res.userInfo,
type: null
// 调用API获取服务器users表中的真实userId
const API = require('./utils/api.js');
API.login().then(serverUserInfo => {
// 确保获取到服务器返回的真实userId
if (serverUserInfo && serverUserInfo.data && serverUserInfo.data.userId) {
const userId = String(serverUserInfo.data.userId);
console.log('从服务器获取到真实用户ID:', userId);
// 存储服务器返回的真实用户ID
wx.setStorageSync('userId', userId);
// 获取用户类型,默认customer
const userType = serverUserInfo.data.userType || 'customer';
// 更新全局用户信息,确保使用服务器返回的ID
const userInfoWithId = {
...res.userInfo,
...serverUserInfo.data.userInfo, // 确保包含服务器返回的所有用户信息
userId: userId
};
this.globalData.userInfo = userInfoWithId;
this.globalData.userType = userType;
// 更新本地存储的用户信息
wx.setStorageSync('userInfo', userInfoWithId);
wx.setStorageSync('userType', userType);
// 更新用户数据
const users = wx.getStorageSync('users');
users[userId] = {
info: userInfoWithId,
type: userType
};
wx.setStorageSync('users', users);
// 用户授权登录后重新认证WebSocket连接,使用服务器返回的真实userId
if (this.globalData.webSocketManager) {
console.log('用户授权后重新认证WebSocket,使用服务器返回的真实用户ID:', userId);
this.globalData.webSocketManager.authenticate(userType, userId);
}
} else {
// 从本地存储获取备用userId
const localUserId = wx.getStorageSync('userId');
if (localUserId) {
console.log('使用本地存储的备用用户ID:', localUserId);
const userId = String(localUserId);
// 更新全局用户信息,使用本地userId
const userInfoWithId = {
...res.userInfo,
userId: userId
};
this.globalData.userInfo = userInfoWithId;
this.globalData.userType = wx.getStorageSync('userType') || 'customer';
// 更新本地存储的用户信息
wx.setStorageSync('userInfo', userInfoWithId);
// 更新用户数据
const users = wx.getStorageSync('users');
users[userId] = {
info: userInfoWithId,
type: this.globalData.userType
};
wx.setStorageSync('users', users);
} else {
console.error('登录失败:未获取到有效的用户ID');
}
}
wx.setStorageSync('users', users)
}
}).catch(error => {
console.error('登录API调用失败:', error);
// 登录失败时提示用户
wx.showToast({
title: '登录失败,请重试',
icon: 'none'
});
});
},
fail: error => {
console.error('获取用户信息失败:', error);
}
})
});
}
},
fail: error => {
console.error('获取用户设置失败:', error);
}
})
});
},
onShow: function () {
console.log('App Show')
@ -138,6 +217,57 @@ App({
wsConnectionState: 'disconnected', // disconnected, connecting, connected, error
// 客服相关状态
isServiceOnline: false,
onlineServiceCount: 0
onlineServiceCount: 0,
// 客服列表,从服务器获取并缓存
customerServiceList: []
},
// 获取客服列表并存储到globalData和本地存储
async fetchAndCacheCustomerServices() {
try {
console.log('开始获取客服列表...');
// 使用wx.request直接获取客服列表
const res = await new Promise((resolve, reject) => {
wx.request({
url: 'http://localhost:3003/api/managers',
method: 'GET',
timeout: 15000,
header: {
'content-type': 'application/json'
},
success: resolve,
fail: reject
});
});
if (res && res.statusCode === 200 && res.data) {
const dataSource = res.data.data || res.data;
if (Array.isArray(dataSource)) {
// 处理客服数据,确保包含必要字段
const processedData = dataSource.map(item => ({
id: item.id || `id_${Date.now()}_${Math.random()}`,
managerId: item.managerId || '',
name: item.name || '未知',
alias: item.alias || item.name || '未知',
phoneNumber: item.phoneNumber || '',
avatarUrl: item.avatar || item.avatarUrl || '',
isOnline: !!item.online
}));
// 更新全局客服列表
this.globalData.customerServiceList = processedData;
// 存储到本地存储
wx.setStorageSync('cached_customer_services', processedData);
console.log('客服列表获取成功,共', processedData.length, '条数据');
return processedData;
}
}
} catch (error) {
console.error('获取客服列表失败:', error);
}
return [];
}
})

1822
pages/chat-detail/index.js

File diff suppressed because it is too large

3
pages/chat-detail/index.json

@ -3,6 +3,5 @@
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"usingComponents": {},
"enablePullDownRefresh": false,
"navigationBarBackButtonText": "返回"
"enablePullDownRefresh": false
}

613
pages/chat/index.js

@ -1,5 +1,109 @@
// pages/chat/index.js
import socketManager from '../../utils/websocket.js';
const AuthManager = require('../../utils/auth.js');
// 获取用户名的辅助函数,支持手机号格式的ID - 增强版本
function getUserNameById(userId, isCustomerServiceMode = false) {
// 严格验证用户ID的有效性
if (!userId || userId === null || userId === undefined) {
// 返回默认名称而不是null,避免unknown显示
return '系统用户';
}
// 确保userId是字符串类型,并去除前后空格
const safeUserId = String(userId).trim();
// 检查是否是空字符串
if (safeUserId === '' || safeUserId === '0') {
return '系统用户';
}
// 检查是否是手机号格式 (中国手机号格式)
if (/^1[3-9]\d{9}$/.test(safeUserId)) {
// 关键修复:将"客户-"改为"用户-",统一用户显示格式
return `用户-${safeUserId.slice(-4)}`;
}
// 纯数字ID - 关键修复:默认显示为客服
// 客服ID通常是纯数字,用户ID通常是字符串格式
if (/^\d+$/.test(safeUserId)) {
return `客服${safeUserId}`;
}
// 增强的用户ID格式处理
if (safeUserId.startsWith('manager_')) {
// 提取客服ID数字部分
const managerNum = safeUserId.replace('manager_', '');
if (!isNaN(managerNum) && managerNum > 0) {
return `客服${managerNum}`;
}
return '客服' + safeUserId.substring(8, 12);
} else if (safeUserId.startsWith('user_')) {
// 用户ID格式处理
const parts = safeUserId.split('_');
if (parts.length >= 3) {
// 使用最后一部分生成更友好的名称
const randomPart = parts[parts.length - 1];
return `用户${randomPart.substring(0, 4).toUpperCase()}`;
}
return '用户' + safeUserId.substring(5, 9);
} else if (safeUserId.startsWith('test_')) {
return '测试用户';
} else if (safeUserId.includes('_customer_')) {
// 提取客户标识部分
const parts = safeUserId.split('_customer_');
// 关键修复:将"客户-"改为"用户-"
return parts.length > 1 ? `用户-${parts[1].substring(0, 4)}` : '用户';
}
// 安全的本地存储访问
try {
if (isCustomerServiceMode) {
// 客服模式下尝试从本地存储获取用户信息
const userInfo = wx.getStorageSync('userInfo');
// 检查userInfo类型,避免重复JSON解析
if (userInfo && typeof userInfo === 'object') {
// 如果用户ID匹配,返回用户名或手机号后四位
if (userInfo.userId === safeUserId || userInfo.id === safeUserId) {
// 关键修复:将"客户-"改为"用户-"
return userInfo.name || (userInfo.phone && `用户-${userInfo.phone.slice(-4)}`) || '用户';
}
} else if (userInfo && typeof userInfo === 'string') {
try {
const parsedUserInfo = JSON.parse(userInfo);
if (parsedUserInfo.userId === safeUserId || parsedUserInfo.id === safeUserId) {
// 关键修复:将"客户-"改为"用户-"
return parsedUserInfo.name || (parsedUserInfo.phone && `用户-${parsedUserInfo.phone.slice(-4)}`) || '用户';
}
} catch (parseError) {
console.warn('解析userInfo字符串失败,忽略此步骤:', parseError);
}
}
}
} catch (e) {
console.error('获取本地用户信息失败:', e);
}
// 增强的固定用户名映射表
const userNameMap = {
'user_1765610275027_qqkb12ws3': '测试用户',
'17780155537': '客服-5537',
'17844569862': '用户-862', // 关键修复:将"客户-"改为"用户-"
// 添加已知用户ID的映射
'user_1763452685075_rea007flq': '用户REA0' // 添加特定用户ID的映射
};
if (userNameMap[safeUserId]) {
return userNameMap[safeUserId];
}
// 对于未识别的有效用户ID,返回更友好的用户ID标识
const displayId = safeUserId.length > 8 ? safeUserId.substring(0, 8) : safeUserId;
// 客服模式下默认显示为用户,除非是自己
const isSelf = isCustomerServiceMode && String(displayId) === String(wx.getStorageSync('managerId'));
return isSelf ? `客服-${displayId}` : `用户-${displayId}`;
}
Page({
@ -32,9 +136,29 @@ Page({
console.log('更新消息列表:', newMessage);
const messages = [...this.data.messages];
// 确定消息发送者ID - 处理服务器返回的数据格式
const senderId = newMessage.senderId;
const existingIndex = messages.findIndex(item => item.id === senderId);
// 获取当前用户信息
const app = getApp();
const currentUserId = app.globalData.userInfo?.userId || wx.getStorageSync('userId') || 'unknown';
const userType = app.globalData.userInfo?.userType || wx.getStorageSync('userType');
const managerId = app.globalData.userInfo?.managerId || wx.getStorageSync('managerId');
// 关键修复:处理不同格式的消息数据
// 提取消息的发送者、接收者和会话ID
const senderId = newMessage.senderId || newMessage.from || newMessage.sender || newMessage.userId;
const receiverId = newMessage.receiverId || newMessage.to || newMessage.receiver || newMessage.managerId;
const conversationId = newMessage.conversationId || newMessage.conversation_id;
// 关键修复:对于客服端,只处理用户发送的消息,忽略自己发送的消息
if (userType === 'manager' && managerId) {
// 客服只处理发送者是用户且接收者是当前客服的消息
const isFromUser = senderId && String(senderId).includes('user_') || !String(senderId).includes('manager_') && senderId !== managerId;
const isToCurrentManager = String(receiverId) === String(managerId) || String(receiverId).includes('manager_') || !receiverId;
if (!isFromUser || !isToCurrentManager) {
console.log('客服端过滤掉非用户消息:', { senderId, receiverId, managerId });
return; // 不处理这条消息
}
}
// 格式化消息时间 - 服务器使用createdAt字段
const now = new Date();
@ -53,35 +177,59 @@ Page({
displayTime = (messageDate.getMonth() + 1) + '月' + messageDate.getDate() + '日';
}
// 获取当前用户ID,确定消息方向
const app = getApp();
const currentUserId = app.globalData.userInfo?.userId || wx.getStorageSync('userId') || 'unknown';
// 关键修复:确定显示的用户ID
// 对于客服端,总是显示用户ID
let displayUserId;
if (userType === 'manager') {
displayUserId = senderId; // 客服端显示发送者(用户)ID
} else {
// 用户端根据消息方向确定显示ID
displayUserId = senderId === currentUserId ? receiverId : senderId;
}
// 确定显示的用户ID(如果是自己发送的消息,显示接收者ID)
const displayUserId = senderId === currentUserId ? newMessage.receiverId : senderId;
// 使用会话ID作为唯一标识,避免重复问题
const uniqueId = conversationId || displayUserId;
const existingIndex = messages.findIndex(item =>
item.conversationId === conversationId || item.id === uniqueId
);
// 使用增强的用户名显示函数
const displayName = getUserNameById(displayUserId, userType === 'manager');
// 消息类型处理
let messageContent = newMessage.content || '';
if (newMessage.content_type === 2 || newMessage.type === 'image') {
messageContent = '[图片]';
} else if (newMessage.content_type === 3 || newMessage.type === 'voice') {
messageContent = '[语音]';
} else if (newMessage.content_type === 4 || newMessage.type === 'video') {
messageContent = '[视频]';
}
if (existingIndex >= 0) {
// 存在该用户的消息,更新内容和时间
// 存在该会话的消息,更新内容和时间
messages[existingIndex] = {
...messages[existingIndex],
content: newMessage.content || '',
content: messageContent,
time: displayTime,
isRead: false
isRead: false,
// 确保有会话ID
conversationId: conversationId || messages[existingIndex].conversationId
};
// 将更新的消息移到列表顶部
const [updatedMessage] = messages.splice(existingIndex, 1);
messages.unshift(updatedMessage);
} else {
// 新用户消息,添加到列表顶部
// 这里暂时使用ID作为用户名,实际应用中应该从用户信息中获取
const displayName = `用户${displayUserId}`;
// 新会话消息,添加到列表顶部
messages.unshift({
id: displayUserId,
id: uniqueId,
name: displayName,
avatar: displayName.charAt(0),
content: newMessage.content || '',
content: messageContent,
time: displayTime,
isRead: false
isRead: false,
unreadCount: 1,
conversationId: conversationId
});
}
@ -226,7 +374,7 @@ Page({
socketManager.off('message', this.handleWebSocketMessage);
},
// 加载聊天列表数据
// 加载聊天列表数据 - 优化版本
loadChatList: function() {
wx.showLoading({ title: '加载中' });
@ -241,9 +389,22 @@ Page({
console.log('使用API地址:', baseUrl);
console.log('当前用户ID:', currentUserId);
// 使用正确的API端点 - /api/conversations/user/:userId
// 根据用户类型选择正确的API端点
const userType = app.globalData.userInfo?.userType || wx.getStorageSync('userType');
const managerId = app.globalData.userInfo?.managerId || wx.getStorageSync('managerId');
// 构建API路径:客服使用manager端点,普通用户使用user端点
let apiPath;
if (userType === 'manager' && managerId) {
apiPath = `/api/conversations/manager/${managerId}`;
console.log('客服身份,使用manager API端点');
} else {
apiPath = `/api/conversations/user/${currentUserId}`;
console.log('普通用户身份,使用user API端点');
}
wx.request({
url: `${baseUrl}/api/conversations/user/${currentUserId}`,
url: `${baseUrl}${apiPath}`,
method: 'GET',
header: {
'Authorization': token ? `Bearer ${token}` : '',
@ -254,7 +415,12 @@ Page({
// 处理不同的API响应格式
let chatData = [];
if (res.data.code === 0 && res.data.data) {
// 更灵活的响应格式处理
if (res.data && res.data.success && res.data.code === 200 && res.data.data) {
// 标准API响应格式
chatData = res.data.data;
} else if (res.data && res.data.code === 0 && res.data.data) {
// 备用API响应格式
chatData = res.data.data;
} else if (Array.isArray(res.data)) {
// 如果直接返回数组
@ -264,71 +430,231 @@ Page({
chatData = [res.data];
}
if (chatData.length > 0) {
// 保存原始数据到日志,便于调试
console.log('处理前的聊天数据:', chatData);
if (Array.isArray(chatData) && chatData.length > 0) {
// 格式化聊天列表数据
const formattedMessages = chatData.map(item => {
// 格式化时间
const now = new Date();
const messageDate = new Date(item.lastMessageTime || item.createdAt || Date.now());
let displayTime = '';
if (messageDate.toDateString() === now.toDateString()) {
// 今天的消息只显示时间
displayTime = messageDate.getHours().toString().padStart(2, '0') + ':' +
messageDate.getMinutes().toString().padStart(2, '0');
} else if (messageDate.toDateString() === new Date(now.getTime() - 86400000).toDateString()) {
// 昨天的消息
displayTime = '昨天';
const formattedMessages = chatData
.filter(item => {
// 跳过无效项
if (!item) return false;
// 关键修复:对于客服端,只显示用户的消息,不显示自己的消息
if (userType === 'manager' && managerId) {
// 获取会话中的用户ID和客服ID
const conversationUserId = item.userId || item.user_id;
const conversationManagerId = item.managerId || item.manager_id || '';
// 确保只显示用户发送给当前客服的消息,且不显示自己的消息
// 1. 确保是当前客服的会话
// 2. 确保不是客服自己的会话(避免显示客服自己)
// 3. 确保有有效的用户ID
const isCurrentManagerConversation = String(conversationManagerId) === String(managerId);
const isNotSelfConversation = conversationUserId !== currentUserId;
const hasValidUserId = conversationUserId && conversationUserId !== 'unknown';
console.log('客服消息过滤:', {
conversationUserId,
conversationManagerId,
currentUserId,
managerId,
keep: isCurrentManagerConversation && isNotSelfConversation && hasValidUserId
});
return isCurrentManagerConversation && isNotSelfConversation && hasValidUserId;
}
return true;
})
.map(item => {
// 格式化时间
const now = new Date();
const messageDate = new Date(item.lastMessageTime || item.createdAt || item.timestamp || Date.now());
let displayTime = '';
if (messageDate.toDateString() === now.toDateString()) {
// 今天的消息只显示时间
displayTime = messageDate.getHours().toString().padStart(2, '0') + ':' +
messageDate.getMinutes().toString().padStart(2, '0');
} else if (messageDate.toDateString() === new Date(now.getTime() - 86400000).toDateString()) {
// 昨天的消息
displayTime = '昨天';
} else {
// 其他日期显示月日
displayTime = (messageDate.getMonth() + 1) + '月' + messageDate.getDate() + '日';
}
// 获取当前用户ID,确定显示哪个用户 - 重点支持数据库驼峰命名
const userIdFromData = item.userId || item.user_id;
const managerIdFromData = item.managerId || item.manager_id;
// 关键修复:对于客服端,总是显示用户ID,确保正确显示用户消息
let displayUserId;
if (userType === 'manager') {
// 客服端应该显示用户ID
displayUserId = userIdFromData || 'unknown';
} else {
// 用户端正常处理
displayUserId = (userIdFromData === currentUserId) ?
(managerIdFromData || 'unknown') :
(userIdFromData || 'unknown');
}
// 添加调试信息
console.log('处理会话项:', {
originalItem: item,
userId: userIdFromData,
managerId: managerIdFromData,
conversationId: item.conversation_id,
selectedUserId: displayUserId,
userType: userType
});
// 使用增强的用户名显示函数
const displayName = getUserNameById(displayUserId, userType === 'manager');
// 获取消息内容,支持多种格式
let lastMessage = item.lastMessage || item.content || '';
if (item.lastMessage && typeof item.lastMessage === 'object') {
// 如果lastMessage是对象,获取content字段
lastMessage = item.lastMessage.content || '';
}
// 消息类型处理 - 支持数据库字段格式
let messageContent = lastMessage;
// 检查content_type字段(数据库使用)
if (item.content_type === 2 || item.content_type === 'image') {
messageContent = '[图片]';
} else if (item.content_type === 3 || item.content_type === 'voice') {
messageContent = '[语音]';
} else if (item.content_type === 4 || item.content_type === 'video') {
messageContent = '[视频]';
} else if (item.messageType === 'image' || (item.lastMessage && item.lastMessage.type === 'image')) {
messageContent = '[图片]';
} else if (item.messageType === 'voice' || (item.lastMessage && item.lastMessage.type === 'voice')) {
messageContent = '[语音]';
} else if (item.messageType === 'video' || (item.lastMessage && item.lastMessage.type === 'video')) {
messageContent = '[视频]';
}
// 确定未读消息数量 - 根据用户类型使用不同字段
let unreadCount = 0;
if (userType === 'manager') {
// 客服端使用cs_unread_count
unreadCount = item.cs_unread_count || 0;
} else {
// 用户端使用unread_count
unreadCount = typeof item.unreadCount === 'number' ? item.unreadCount : (item.unread_count || 0);
}
return {
// 使用conversation_id作为唯一标识,避免重复ID问题
id: item.conversation_id || displayUserId,
name: item.userName || item.name || displayName,
avatar: item.avatar || (item.userName || displayName).charAt(0) || '用',
content: messageContent,
time: displayTime,
isRead: unreadCount === 0, // 有未读数就是未读状态
unreadCount: unreadCount,
// 添加原始数据引用,便于调试
_raw: item,
// 保留会话ID用于导航
conversationId: item.conversation_id || item.id
};
});
// 去重 - 基于conversation_id或userId
const uniqueMessages = [];
const seenIds = new Set();
formattedMessages.forEach(msg => {
const uniqueId = msg.conversationId || msg.id;
if (!seenIds.has(uniqueId)) {
seenIds.add(uniqueId);
uniqueMessages.push(msg);
} else {
// 其他日期显示月日
displayTime = (messageDate.getMonth() + 1) + '月' + messageDate.getDate() + '日';
console.warn(`发现重复的会话ID: ${uniqueId},已过滤`);
}
});
// 获取当前用户ID,确定显示哪个用户
const displayUserId = item.userId === currentUserId ? item.managerId : item.userId || item.id;
const displayName = `用户${displayUserId}`;
return {
id: displayUserId,
name: item.userName || item.name || displayName,
avatar: item.avatar || (item.userName || displayName).charAt(0) || '用',
content: item.lastMessage || item.content || '',
time: displayTime,
isRead: item.isRead || false,
unreadCount: item.unreadCount || 0
};
// 按时间和未读状态排序
uniqueMessages.sort((a, b) => {
// 未读消息优先
if (a.isRead !== b.isRead) {
return a.isRead ? 1 : -1;
}
// 其他按时间排序
return 0;
});
this.setData({ messages: formattedMessages });
// 保存到本地存储,便于调试和离线访问
wx.setStorageSync('lastChatList', formattedMessages);
// 同时为chatList和messages赋值,确保页面模板能正确获取数据
const processedChatList = uniqueMessages;
this.setData({
messages: formattedMessages,
chatList: processedChatList,
noChats: processedChatList.length === 0
});
// 更新TabBar未读状态
this.updateTabBarBadge();
} else {
console.log('暂无聊天消息');
this.setData({ messages: [] });
console.log('暂无聊天消息,但数据在数据库中存在,可能是API请求路径或参数问题');
console.log('API请求详情:', {
baseUrl,
apiPath,
currentUserId,
userType,
managerId
});
// 即使API返回空数组,也要显示空状态,而不是白屏
this.setData({
messages: [],
noChats: true
});
}
},
fail: (err) => {
console.error('网络请求失败:', err);
wx.showToast({
title: '网络请求失败,使用本地数据',
title: '获取消息失败,重试中...',
icon: 'none',
duration: 3000
});
// 失败时使用模拟数据,确保页面能够正常显示
this.setData({
messages: [
// 尝试使用本地缓存数据
const cachedList = wx.getStorageSync('lastChatList') || [];
if (cachedList.length > 0) {
console.log('使用本地缓存数据:', cachedList.length);
this.setData({ messages: cachedList });
this.updateTabBarBadge();
} else {
// 缓存也没有,使用模拟数据
console.log('使用模拟数据');
const mockMessages = [
{
id: '1',
name: '系统消息',
avatar: '系',
content: '欢迎使用聊天功能',
time: '刚刚',
isRead: false
isRead: false,
unreadCount: 0,
conversationId: 'conv_1' // 添加conversationId,确保导航正常
}
]
});
];
this.setData({ messages: mockMessages });
}
// 确保设置了noChats状态,避免白屏
this.setData({ noChats: this.data.messages.length === 0 });
},
complete: () => {
wx.hideLoading();
// 确保设置了noChats状态,避免白屏
this.setData({ noChats: this.data.messages.length === 0 });
}
});
} catch (error) {
@ -349,7 +675,32 @@ Page({
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.loadChatList();
console.log('聊天列表页面onLoad,准备加载数据');
try {
// 页面加载时执行身份验证
AuthManager.authenticate((authResult) => {
console.log('身份验证成功,开始加载聊天列表:', authResult);
this.loadChatList();
}, (error) => {
console.warn('身份验证失败,使用访客模式:', error);
// 即使身份验证失败,也加载聊天列表,避免白屏
this.loadChatList();
});
} catch (error) {
console.error('身份验证过程中发生错误:', error);
// 即使身份验证过程中发生错误,也加载聊天列表
this.loadChatList();
}
// 初始化WebSocket连接
setTimeout(() => {
try {
this.initWebSocket();
} catch (error) {
console.error('初始化WebSocket过程中发生错误:', error);
}
}, 1000);
},
/**
@ -377,8 +728,23 @@ Page({
* 生命周期函数--监听页面显示
*/
onShow() {
// 页面显示时初始化WebSocket连接
this.initWebSocket();
console.log('聊天列表页面onShow');
// 检查是否已登录
if (AuthManager.isLoggedIn()) {
// 已登录,初始化WebSocket连接
try {
this.initWebSocket();
} catch (error) {
console.error('初始化WebSocket过程中发生错误:', error);
}
}
// 确保消息列表有数据,避免白屏
if (!this.data.messages || this.data.messages.length === 0) {
console.log('消息列表为空,尝试重新加载数据');
this.loadChatList();
}
},
/**
@ -401,7 +767,11 @@ Page({
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
console.log('下拉刷新触发');
// 重新加载聊天列表数据
this.loadChatList();
// 停止下拉刷新动画
wx.stopPullDownRefresh();
},
/**
@ -424,51 +794,98 @@ Page({
// 跳转到对话详情页面
navigateToChatDetail: function(e) {
// 关键修复:获取userId和userName
const userId = e.currentTarget.dataset.userId;
const userName = e.currentTarget.dataset.userName;
const conversationId = e.currentTarget.dataset.conversationId;
// 将该聊天标记为已读
const messages = [...this.data.messages];
const messageIndex = messages.findIndex(item => item.id === userId);
console.log('导航到聊天详情:', { userId, userName, conversationId });
if (messageIndex >= 0) {
messages[messageIndex].isRead = true;
messages[messageIndex].unreadCount = 0;
this.setData({ messages });
// 执行身份验证
AuthManager.authenticate(() => {
// 将该聊天标记为已读
const messages = [...this.data.messages];
// 查找会话时同时考虑id和conversationId
const messageIndex = messages.findIndex(item =>
item.id === userId || item.conversationId === conversationId
);
// 更新TabBar未读消息数
this.updateTabBarBadge();
if (messageIndex >= 0) {
messages[messageIndex].isRead = true;
messages[messageIndex].unreadCount = 0;
this.setData({ messages });
// 通知服务器已读状态(可选)
this.markAsRead(userId);
}
// 更新TabBar未读消息数
this.updateTabBarBadge();
// 通知服务器已读状态(使用userId)
this.markAsRead(userId);
}
wx.navigateTo({
url: `/pages/chat-detail/index?userId=${userId}&userName=${encodeURIComponent(userName)}`
// 关键修复:在URL中同时传递userId和conversationId,确保正确打开会话
wx.navigateTo({
url: `/pages/chat-detail/index?userId=${userId}&userName=${encodeURIComponent(userName)}${conversationId ? `&conversationId=${conversationId}` : ''}`
});
});
},
// 通知服务器消息已读
markAsRead: function(userId) {
const app = getApp();
const token = app.globalData.token || wx.getStorageSync('token');
wx.request({
url: `${app.globalData.baseUrl || 'https://your-server.com'}/api/chat/read`,
method: 'POST',
header: {
'Authorization': `Bearer ${token}`,
'content-type': 'application/json'
},
data: {
userId: userId
},
success: (res) => {
console.log('标记消息已读成功:', res.data);
},
fail: (err) => {
console.error('标记消息已读失败:', err);
}
});
// 只执行本地标记已读,不再调用服务器API
console.log('执行本地标记已读,不再调用服务器API:', userId);
// 在本地标记消息已读,确保应用功能正常
this.localMarkAsRead(userId);
},
// 本地标记消息已读
localMarkAsRead: function(userId) {
try {
// 获取当前的消息列表 (使用messages而不是chatList)
const messages = this.data.messages || [];
// 更新指定用户的未读状态
const updatedMessages = messages.map(chat => {
if (chat.userId === userId || chat.id === userId || chat.conversationId === userId) {
return {
...chat,
unreadCount: 0,
lastReadTime: Date.now()
};
}
return chat;
});
// 更新数据和本地存储 (使用messages而不是chatList)
this.setData({ messages: updatedMessages });
wx.setStorageSync('lastChatList', updatedMessages);
// 更新TabBar的未读提示
this.updateTabBarBadge();
console.log(`本地标记用户 ${userId} 的消息已读成功`);
} catch (error) {
console.error('本地标记已读失败:', error);
}
},
// 切换消息标签页
switchTab: function(e) {
const tab = e.currentTarget.dataset.tab;
this.setData({ activeTab: tab });
// 根据选中的标签页过滤消息列表
if (tab === 'unread') {
this.filterUnreadMessages();
} else {
// 显示所有消息 - 从本地存储恢复原始列表
const cachedList = wx.getStorageSync('lastChatList') || [];
this.setData({ messages: cachedList });
}
},
// 过滤未读消息
filterUnreadMessages: function() {
const unreadMessages = this.data.messages.filter(item => !item.isRead || item.unreadCount > 0);
this.setData({ messages: unreadMessages });
}
})

2
pages/chat/index.json

@ -2,7 +2,5 @@
"navigationBarTitleText": "消息",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarLeftButtonText": "返回",
"navigationBarRightButtonText": "管理",
"usingComponents": {}
}

91
pages/chat/index.wxml

@ -1,43 +1,76 @@
<view class="chat-container">
<!-- 页面顶部导航已在index.json中配置 -->
<view class="container">
<!-- 头部导航栏 - 现代填充风格 -->
<view class="header-full">
<!-- 左侧返回按钮 -->
<view class="header-left">
<view class="header-btn back-btn" bindtap="onBack">
<text class="icon-back">‹</text>
</view>
</view>
<!-- 中间标题区域 -->
<view class="header-center">
<view class="title-large">消息</view>
</view>
<!-- 消息类型切换 -->
<view class="message-tabs">
<view class="tab-item active">全部</view>
<view class="tab-item">未读</view>
<!-- 右侧操作按钮区域 -->
<view class="header-right">
<view class="header-btn action-btn" bindtap="onNavigationBarButtonTap" data-type="search">
<text class="icon-search">🔍</text>
</view>
<view class="header-btn action-btn" bindtap="onNavigationBarButtonTap" data-type="more">
<text class="icon-more">···</text>
</view>
</view>
</view>
<!-- 清除未读提示 -->
<view class="clear-unread">
<text class="clear-btn" bindtap="clearAllUnreadStatus">清除未读</text>
<!-- 微信风格的消息类型切换 -->
<view class="tabs-wechat">
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="switchTab" data-tab="all">全部消息</view>
<view class="tab-item {{activeTab === 'unread' ? 'active' : ''}}" bindtap="switchTab" data-tab="unread">未读消息</view>
<view class="clear-unread" bindtap="clearAllUnreadStatus">清除未读</view>
</view>
<!-- 消息列表 -->
<view class="message-list">
<!-- 消息列表 - 微信风格 -->
<scroll-view class="message-list-wechat" scroll-y="true">
<!-- 提示消息 -->
<view class="message-tips">
<view class="message-tips" wx:if="{{messages.length > 0 && showOldMessageTips}}">
以下为3天前的消息,提示将弱化
</view>
<!-- 动态消息列表 -->
<block wx:if="{{messages.length > 0}}">
<view wx:for="{{messages}}" wx:key="id" class="message-item" bindtap="navigateToChatDetail" data-user-id="{{item.id}}" data-user-name="{{item.name}}">
<view class="message-avatar">
<text class="avatar-icon">{{item.avatar}}</text>
<view wx:if="{{!item.isRead}}" class="unread-dot"></view>
</view>
<view class="message-content">
<view class="message-header">
<text class="message-name">{{item.name}}</text>
<text class="message-time">{{item.time}}</text>
</view>
<text class="message-text {{!item.isRead ? 'unread' : ''}}">{{item.content}}</text>
<!-- 消息项 -->
<view class="message-item-wechat" wx:for="{{messages}}" wx:key="id" bindtap="navigateToChatDetail" data-user-id="{{item.id}}" data-user-name="{{item.name}}" data-conversation-id="{{item.conversationId || item.id}}">
<!-- 头像 -->
<view class="avatar-container-wechat">
<view class="avatar-wechat">
{{item.avatar || (item.name && item.name.charAt(0)) || '用'}}
</view>
<!-- 未读红点 -->
<view class="unread-dot" wx:if="{{!item.isRead && !item.unreadCount}}"></view>
<!-- 未读数字角标 -->
<view class="unread-badge-wechat" wx:if="{{item.unreadCount > 0}}">{{item.unreadCount > 99 ? '99+' : item.unreadCount}}</view>
</view>
</block>
<!-- 消息内容区域 -->
<view class="message-content-wechat">
<!-- 消息头部:名称和时间 -->
<view class="message-header-wechat">
<view class="name-wechat">{{item.name}}</view>
<view class="time-wechat">{{item.time}}</view>
</view>
<!-- 消息文本 -->
<view class="message-text-wechat">
<text wx:if="{{!item.isRead && !item.unreadCount}}" class="unread-dot-small">●</text>
<text class="message-content-text">{{item.content || '[图片]'}}</text>
</view>
</view>
</view>
<!-- 空状态提示 -->
<view wx:else class="empty-state">
<text class="empty-text">暂无消息</text>
<view class="empty-state-wechat" wx:if="{{messages.length === 0}}">
<view class="empty-icon-wechat">💬</view>
<view class="empty-text-wechat">暂无消息记录</view>
<view class="empty-subtext">您可以开始与客户或客服的对话</view>
</view>
</view>
</scroll-view>
</view>

270
pages/chat/index.wxss

@ -1,26 +1,114 @@
.chat-container {
/* 微信风格聊天页面样式 */
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
padding-top: 0;
position: relative;
overflow: hidden;
}
/* 消息类型切换 */
.message-tabs {
/* 头部导航栏 - 现代填充风格 */
.header-full {
display: flex;
background-color: #ffffff;
border-bottom: 1rpx solid #eeeeee;
justify-content: space-between;
align-items: center;
width: 100%;
box-sizing: border-box;
height: 92rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #e0e0e0;
position: relative;
z-index: 10;
}
.tab-item {
.header-left,
.header-right {
width: 100rpx;
display: flex;
align-items: center;
}
.header-left {
justify-content: flex-start;
padding-left: 20rpx;
}
.header-right {
justify-content: flex-end;
padding-right: 20rpx;
}
.header-center {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 28rpx 0;
}
.title-large {
font-size: 38rpx;
font-weight: 600;
color: #000000;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header-btn {
display: flex;
align-items: center;
justify-content: center;
width: 70rpx;
height: 70rpx;
border-radius: 35rpx;
transition: background-color 0.2s;
}
.header-btn:active {
background-color: #f0f0f0;
}
.back-btn .icon-back {
font-size: 44rpx;
color: #333;
font-weight: bold;
}
.action-btn {
margin-left: 10rpx;
}
.action-btn .icon-search {
font-size: 40rpx;
color: #333;
}
.action-btn .icon-more {
font-size: 40rpx;
color: #333;
font-weight: bold;
}
/* 微信风格的消息类型切换 */
.tabs-wechat {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #ffffff;
padding: 0 20rpx;
height: 76rpx;
border-bottom: 1rpx solid #e0e0e0;
}
.tab-item {
font-size: 32rpx;
color: #666666;
padding: 0 20rpx;
height: 100%;
display: flex;
align-items: center;
position: relative;
}
@ -33,77 +121,65 @@
content: '';
position: absolute;
bottom: 0;
left: 40%;
width: 20%;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background-color: #07c160;
}
/* 清除未读提示 */
.clear-unread {
display: flex;
justify-content: flex-end;
padding: 20rpx 30rpx;
background-color: #ffffff;
box-sizing: border-box;
width: 100%;
}
.clear-btn {
font-size: 28rpx;
color: #1677ff;
padding: 0 10rpx;
}
/* 消息列表 */
.message-list {
/* 消息列表 - 微信风格 */
.message-list-wechat {
flex: 1;
padding-bottom: 30rpx;
overflow-y: auto;
background-color: #f5f5f5;
overflow: auto;
}
/* 提示消息 */
.message-tips {
text-align: center;
padding: 20rpx 0;
margin-bottom: 20rpx;
margin-bottom: 10rpx;
color: #999999;
font-size: 28rpx;
font-size: 26rpx;
background-color: #f5f5f5;
}
/* 消息项 */
.message-item {
/* 消息项 - 微信风格 */
.message-item-wechat {
display: flex;
padding: 24rpx 30rpx;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
position: relative;
}
/* 添加底部边框线,模仿微信分隔线 */
.message-item::after {
content: '';
position: absolute;
left: 150rpx;
right: 0;
bottom: 0;
height: 1rpx;
background-color: #f0f0f0;
/* 头像容器 */
.avatar-container-wechat {
position: relative;
margin-right: 24rpx;
}
/* 头像 */
.message-avatar {
/* 头像 - 微信风格 */
.avatar-wechat {
width: 100rpx;
height: 100rpx;
border-radius: 8rpx;
border-radius: 12rpx;
background-color: #07c160;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
position: relative;
overflow: hidden;
color: #ffffff;
font-size: 40rpx;
font-weight: 500;
}
/* 未读消息红点 */
/* 未读红点 */
.unread-dot {
position: absolute;
top: -6rpx;
@ -112,71 +188,125 @@
height: 28rpx;
border-radius: 14rpx;
background-color: #ff3b30;
border: 2rpx solid #ffffff;
border: 3rpx solid #ffffff;
}
.avatar-icon {
font-size: 40rpx;
/* 未读数字角标 - 微信风格 */
.unread-badge-wechat {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 36rpx;
height: 36rpx;
padding: 0 10rpx;
border-radius: 18rpx;
background-color: #ff3b30;
border: 3rpx solid #ffffff;
color: #ffffff;
font-size: 24rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
/* 消息内容 */
.message-content {
/* 消息内容区域 */
.message-content-wechat {
flex: 1;
display: flex;
flex-direction: column;
padding: 6rpx 0;
justify-content: space-between;
min-height: 100rpx;
}
.message-header {
/* 消息头部:名称和时间 */
.message-header-wechat {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
margin-bottom: 10rpx;
}
.message-name {
.name-wechat {
font-size: 32rpx;
color: #000000;
font-weight: 500;
color: #000000;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-time {
.time-wechat {
font-size: 24rpx;
color: #999999;
margin-left: 12rpx;
margin-left: 15rpx;
}
.message-text {
/* 消息文本 */
.message-text-wechat {
display: flex;
align-items: center;
font-size: 28rpx;
color: #999999;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 未读消息文本样式 */
.message-text.unread {
color: #000000;
font-weight: 500;
.unread-dot-small {
color: #ff3b30;
font-size: 20rpx;
margin-right: 8rpx;
}
/* 空状态样式 */
.empty-state {
.message-content-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 空状态提示 - 微信风格 */
.empty-state-wechat {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
padding: 100rpx 0;
justify-content: center;
padding: 150rpx 0;
color: #999999;
}
.empty-text {
.empty-icon-wechat {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text-wechat {
font-size: 32rpx;
color: #666666;
margin-bottom: 15rpx;
}
.empty-subtext {
font-size: 28rpx;
color: #999999;
}
/* 滚动条样式优化 */
.message-list-wechat::-webkit-scrollbar {
width: 6rpx;
}
.message-list-wechat::-webkit-scrollbar-track {
background: transparent;
}
.message-list-wechat::-webkit-scrollbar-thumb {
background: #cccccc;
border-radius: 3rpx;
}
.message-list-wechat::-webkit-scrollbar-thumb:hover {
background: #999999;
}

86
pages/message-list/index.js

@ -143,20 +143,37 @@ Page({
return false;
}
// 过滤掉临时用户ID(以temp_开头的ID)
const safeUserId = String(displayUserId).trim();
if (safeUserId.startsWith('temp_')) {
console.log('过滤掉临时用户ID:', safeUserId, '的会话');
return false;
}
return true;
});
if (validChatData.length > 0) {
// 格式化聊天列表数据
const formattedMessages = validChatData.map(item => {
// 获取显示的用户ID
const displayUserId = item.userId === currentUserId ? item.managerId : item.userId || item.id;
const displayName = this.getUserNameById(displayUserId);
// 获取显示的用户ID和名称
let displayUserId, displayName;
// 关键修复:根据用户类型和聊天对象类型确定显示的ID和名称
if (isManager) {
// 客服模式:显示的是客户信息
displayUserId = item.userId;
displayName = this.getUserNameById(displayUserId);
} else {
// 普通用户模式:显示的是客服信息
displayUserId = item.managerId || item.userId || item.id;
displayName = this.getUserNameById(displayUserId);
}
return {
userId: displayUserId,
userName: item.userName || item.name || displayName,
avatar: item.avatar || (displayName).charAt(0) || '用',
userName: displayName || item.userName || item.name || '未知用户',
avatar: item.avatar || (displayName && displayName.charAt(0)) || '用',
lastMessage: item.lastMessage || item.content || '',
lastMessageTime: this.formatMessageTime(item.lastMessageTime || item.createdAt || Date.now()),
messageCount: item.messageCount || 0,
@ -244,6 +261,12 @@ Page({
return;
}
// 过滤掉临时用户ID(以temp_开头的ID)
if (chatUserId.trim().startsWith('temp_')) {
console.log('跳过临时用户ID:', chatUserId, '的会话');
return;
}
// 获取消息列表
const messages = wx.getStorageSync(key);
@ -392,6 +415,39 @@ Page({
const app = getApp();
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer';
// 过滤掉临时用户ID(以temp_开头的ID)
if (safeUserId.startsWith('temp_')) {
console.log('过滤临时用户ID:', safeUserId);
return null;
}
// 尝试从全局客服列表中获取用户名
let customerServiceList = app.globalData.customerServiceList || [];
// 如果全局客服列表为空,尝试从本地存储获取
if (customerServiceList.length === 0) {
customerServiceList = wx.getStorageSync('cached_customer_services') || [];
// 如果本地存储也没有,尝试从服务器获取
if (customerServiceList.length === 0) {
console.log('客服列表为空,尝试从服务器获取');
// 这里可以添加异步获取客服列表的逻辑,但由于getUserNameById是同步函数,暂时不实现
}
}
// 从客服列表中查找匹配的客服
const service = customerServiceList.find(item => {
// 安全地访问对象属性,避免undefined属性错误
const itemId = item?.id ? String(item.id).trim() : '';
const itemManagerId = item?.managerId ? String(item.managerId).trim() : '';
return itemId === safeUserId || itemManagerId === safeUserId;
});
if (service) {
return service.alias || service.name || '客服';
}
// 尝试从用户信息缓存中获取更详细的用户信息
// 客服模式下,尝试获取客户的手机号或其他标识信息
if (currentUserType === 'manager' || currentUserType === 'customer_service') {
@ -427,31 +483,13 @@ Page({
}
}
// 尝试从全局客服列表中获取用户名
const cachedCustomerServices = wx.getStorageSync('cached_customer_services') || [];
const service = cachedCustomerServices.find(item => {
// 安全地访问对象属性,避免undefined属性错误
const itemId = item?.id ? String(item.id).trim() : '';
const itemManagerId = item?.managerId ? String(item.managerId).trim() : '';
return itemId === safeUserId || itemManagerId === safeUserId;
});
if (service) {
return service.alias || service.name || '客服';
}
// 固定用户名映射
const userMap = {
'user1': '客服专员',
'user2': '商家客服',
'user_1': '张三',
'user_2': '李四',
'user_3': '王五',
'user_4': '赵六',
'user_5': '钱七',
'customer_service_1': '客服小王',
'customer_service_2': '客服小李',
'customer_service_3': '客服小张'
'user_5': '钱七'
};
if (userMap[safeUserId]) {

171
pages/profile/index.js

@ -107,11 +107,17 @@ Page({
// 始终根据当前用户类型显示对应的身份标签
if (userType && userType !== '') {
let identityLabel = '身份:not_set'
switch (userType) {
case 'buyer': identityLabel = '身份:买家'; break
case 'seller': identityLabel = '身份:卖家'; break
case 'both': identityLabel = '身份:买卖家'; break
case 'buyer+seller': identityLabel = '身份:买卖家'; break
// 优先处理包含manager的类型
if (userType.includes('manager')) {
identityLabel = '身份:客服';
} else {
switch (userType) {
case 'buyer': identityLabel = '身份:买家'; break
case 'seller': identityLabel = '身份:卖家'; break
case 'both': identityLabel = '身份:买卖家'; break
case 'buyer+seller': identityLabel = '身份:买卖家'; break
}
}
filteredTags.push(identityLabel)
console.log('加载用户信息 - 根据当前用户类型显示身份标签:', identityLabel)
@ -198,31 +204,153 @@ Page({
users[userId] = {}
}
// 只有当服务器返回的身份与本地不同时才更新
if (users[userId].type !== serverType) {
users[userId].type = serverType
// 获取当前的用户类型,检查是否包含manager
const currentUserType = wx.getStorageSync('userType') || ''
let isManager = currentUserType.includes('manager')
// 移除serverType中的customer(如果存在)
let processedServerType = serverType.replace(/,?customer/g, '').replace(/^,|,$/g, '')
// 当服务器返回customer类型时,额外检查用户是否为客服
if (serverType === 'customer' && !isManager) {
console.log('服务器返回customer类型,检查用户是否为客服...')
const app = getApp()
// 从多个可能的位置获取手机号,增加获取成功率
let phoneNumber = ''
// 1. 尝试从storage获取
phoneNumber = wx.getStorageSync('phoneNumber') || ''
console.log('1. 从storage获取手机号:', phoneNumber)
// 2. 尝试从globalData获取
if (!phoneNumber && app.globalData.userInfo && app.globalData.userInfo.phoneNumber) {
phoneNumber = app.globalData.userInfo.phoneNumber
console.log('2. 从globalData获取手机号:', phoneNumber)
}
// 3. 尝试从userInfo storage获取
if (!phoneNumber) {
const userInfo = wx.getStorageSync('userInfo') || {}
phoneNumber = userInfo.phoneNumber || ''
console.log('3. 从userInfo storage获取手机号:', phoneNumber)
}
// 调用客服检查函数
const api = require('../../utils/api')
api.checkIfUserIsCustomerService(phoneNumber).then(isCS => {
let newUserType = ''
if (isCS) {
console.log('确认用户为客服,设置为manager类型')
isManager = true
// 构建新的用户类型,设置为manager
newUserType = 'manager'
// 更新本地存储
users[userId].type = newUserType
wx.setStorageSync('users', users)
// 更新全局用户类型
app.globalData.userType = newUserType
// 更新页面显示的用户类型
this.setData({
userType: this.formatUserType(newUserType)
})
console.log('用户身份已更新为客服:', newUserType)
// 调用API将用户类型更新同步到服务器
// 注意:传入空字符串以触发强制同步manager类型的逻辑
api.updateUserType('').then(res => {
console.log('客服身份已成功同步到服务器:', res)
}).catch(error => {
console.error('同步客服身份到服务器失败:', error)
})
} else {
console.log('确认用户非客服,使用处理后的类型')
newUserType = processedServerType || ''
// 对于非客服用户,更新为处理后的类型
if (users[userId].type !== newUserType) {
users[userId].type = newUserType
wx.setStorageSync('users', users)
// 更新全局用户类型
app.globalData.userType = newUserType
// 更新页面显示的用户类型
this.setData({
userType: this.formatUserType(newUserType)
})
}
}
// 更新用户标签
this.updateUserTags(userId, newUserType)
}).catch(error => {
console.error('检查客服身份失败:', error)
// 继续执行后续操作
let newUserType = processedServerType || ''
if (users[userId].type !== newUserType) {
users[userId].type = newUserType
wx.setStorageSync('users', users)
// 更新全局用户类型
app.globalData.userType = newUserType
// 更新页面显示的用户类型
this.setData({
userType: this.formatUserType(newUserType)
})
}
this.updateUserTags(userId, newUserType)
})
// 异步处理已启动,此处返回
return
}
// 构建新的用户类型,如果是manager则保留manager标识
let newUserType = processedServerType
if (isManager) {
console.log('检测到用户为客服,保留manager标识')
if (!processedServerType.includes('manager')) {
newUserType = processedServerType ? processedServerType + ',manager' : 'manager'
}
}
// 只有当新构建的用户类型与本地不同时才更新
if (users[userId].type !== newUserType) {
users[userId].type = newUserType
wx.setStorageSync('users', users)
// 更新全局用户类型
const app = getApp()
app.globalData.userType = serverType
app.globalData.userType = newUserType
// 更新页面显示的用户类型
this.setData({
userType: this.formatUserType(serverType)
userType: this.formatUserType(newUserType)
})
console.log('用户身份已从服务器同步:', serverType)
console.log('用户身份已从服务器同步并保留客服标识:', newUserType)
} else {
console.log('用户身份与服务器一致,无需更新:', serverType)
console.log('用户身份与服务器一致,无需更新:', newUserType)
}
// 更新用户标签
this.updateUserTags(userId, serverType)
// 更新用户标签,确保传入正确的参数
this.updateUserTags(userId, newUserType)
},
// 格式化用户类型显示
formatUserType(type) {
// 优先处理包含manager的类型
if (type && type.includes('manager')) {
return '客服';
}
switch (type) {
case 'buyer': return '买家';
case 'seller': return '卖家';
@ -388,14 +516,19 @@ Page({
wx.setStorageSync('sessionKey', sessionKey)
}
// 如果有userId,也存储起来
// 确保始终使用从服务器获取的正式用户ID,不再生成临时ID
if (userId) {
wx.setStorageSync('userId', userId)
} else {
// 生成临时userId
const tempUserId = 'user_' + Date.now()
wx.setStorageSync('userId', tempUserId)
userId = tempUserId
const app = getApp();
if (app.globalData.userInfo && app.globalData.userInfo.userId) {
const serverUserId = String(app.globalData.userInfo.userId);
wx.setStorageSync('userId', serverUserId);
userId = serverUserId;
console.log('使用从全局获取的正式用户ID:', serverUserId);
} else {
console.warn('未找到有效的用户ID,请确保用户已授权登录');
}
}
console.log('获取openid成功并存储:', openid)

2
server-example/.env

@ -19,6 +19,6 @@ DB_PASSWORD=schl@2025
PORT=3003
# 日志配置
LOG_LEVEL=debug
NODE_ENV=development
NODE_ENV=production
# 详细日志记录,用于问题排查
ENABLE_DETAILED_LOGGING=true

2
server-example/port-conflict-fix.js

@ -199,7 +199,7 @@ function provideNonInteractiveFix() {
}
break;
case '2':
if (updatePM2ConfigFile(3004)) {
if (updatePM2ConfigFile(3003)) {
console.log('正在重启应用...');
restartPM2App();
}

276
server-example/server-mysql.js

@ -15,8 +15,12 @@ function validateUserId(userId) {
}
function validateManagerId(managerId) {
if (!managerId || managerId === 0 || managerId === '0') {
throw new Error('无效的managerId: 不能为空或为0');
if (!managerId || managerId === 0 || managerId === '0' || managerId === 'user') {
throw new Error('无效的managerId: 不能为空、为0或为"user"');
}
// 只允许数字类型的客服ID
if (!/^\d+$/.test(String(managerId))) {
throw new Error('无效的managerId: 必须是数字类型');
}
// 确保managerId也是字符串类型
return String(managerId).trim();
@ -6861,79 +6865,155 @@ function getOnlineStats() {
// 创建或获取现有会话
async function createOrGetConversation(userId, managerId) {
// 修复: 确保ID类型一致
userId = validateUserId(userId);
managerId = validateManagerId(managerId);
try {
// 尝试查找已存在的会话
const [existingConversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ? LIMIT 1',
{ replacements: [userId, managerId] }
);
// 关键修复:明确区分userId和managerId,确保userId是普通用户ID,managerId是客服ID
let finalUserId = validateUserId(userId);
let finalManagerId = validateManagerId(managerId);
// 关键修复:验证managerId不是无效值(如"user")
if (finalManagerId === 'user' || finalManagerId === '0' || !finalManagerId) {
console.error('严重错误: 尝试使用无效的managerId创建会话:', managerId);
throw new Error('无效的客服ID');
}
if (existingConversations && existingConversations.length > 0) {
const conversation = existingConversations[0];
// 如果会话已结束,重新激活
if (conversation.status !== 1) {
await sequelize.query(
'UPDATE chat_conversations SET status = 1 WHERE conversation_id = ?',
{ replacements: [conversation.conversation_id] }
);
conversation.status = 1;
// 关键修复:验证userId不是无效值
if (!finalUserId || finalUserId === '0') {
console.error('严重错误: 尝试使用无效的userId创建会话:', userId);
throw new Error('无效的用户ID');
}
// 关键修复:确保userId和managerId不会被错误交换
// 如果userId是数字而managerId不是数字,说明参数顺序可能错误
if (/^\d+$/.test(finalUserId) && !/^\d+$/.test(finalManagerId)) {
console.error('严重错误: 检测到userId和managerId可能被错误交换,userId:', finalUserId, 'managerId:', finalManagerId);
throw new Error('无效的用户ID或客服ID,可能存在参数顺序错误');
}
// 设置重试参数,防止竞态条件导致的重复创建
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
// 尝试查找已存在的会话 - 双向检查,避免重复创建
const [existingConversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE (userId = ? AND managerId = ?) OR (userId = ? AND managerId = ?) LIMIT 1',
{ replacements: [finalUserId, finalManagerId, finalManagerId, finalUserId] }
);
if (existingConversations && existingConversations.length > 0) {
const conversation = existingConversations[0];
// 如果会话已结束,重新激活
if (conversation.status !== 1) {
await sequelize.query(
'UPDATE chat_conversations SET status = 1 WHERE conversation_id = ?',
{ replacements: [conversation.conversation_id] }
);
conversation.status = 1;
}
return conversation;
}
return conversation;
}
// 创建新会话
const conversationId = crypto.randomUUID();
const now = new Date();
// 创建新会话
const conversationId = crypto.randomUUID();
const now = new Date();
await sequelize.query(
`INSERT INTO chat_conversations
(conversation_id, userId, managerId, status, user_online, cs_online, created_at, updated_at)
VALUES (?, ?, ?, 1, ?, ?, ?, ?)`,
{
replacements: [
conversationId,
userId,
managerId,
onlineUsers.has(userId) ? 1 : 0,
onlineManagers.has(managerId) ? 1 : 0,
now,
now
]
await sequelize.query(
`INSERT INTO chat_conversations
(conversation_id, userId, managerId, status, user_online, cs_online, created_at, updated_at)
VALUES (?, ?, ?, 1, ?, ?, ?, ?)`,
{
replacements: [
conversationId,
finalUserId,
finalManagerId,
onlineUsers.has(finalUserId) ? 1 : 0,
onlineManagers.has(finalManagerId) ? 1 : 0,
now,
now
]
}
);
// 返回新创建的会话
return {
conversation_id: conversationId,
userId: finalUserId,
managerId: finalManagerId,
status: 1,
user_online: onlineUsers.has(finalUserId) ? 1 : 0,
cs_online: onlineManagers.has(finalManagerId) ? 1 : 0,
created_at: now,
updated_at: now
};
} catch (error) {
console.error(`创建或获取会话失败 (尝试 ${attempts + 1}/${maxAttempts}):`, error);
attempts++;
// 如果不是最后一次尝试,短暂延迟后重试
if (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 100 * attempts));
} else {
// 最后一次尝试也失败了,抛出错误
throw error;
}
);
}
}
// 返回新创建的会话
return {
conversation_id: conversationId,
userId,
managerId,
status: 1,
user_online: onlineUsers.has(userId) ? 1 : 0,
cs_online: onlineManagers.has(managerId) ? 1 : 0,
created_at: now,
updated_at: now
};
} catch (error) {
console.error('创建或获取会话失败:', error);
throw error;
// 所有尝试都失败后,再次查询以获取可能已存在的会话
const [finalConversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ? LIMIT 1',
{ replacements: [finalUserId, finalManagerId] }
);
if (finalConversations && finalConversations.length > 0) {
return finalConversations[0];
}
throw new Error('无法创建或获取会话,所有尝试均失败');
}
// 获取用户的所有会话
async function getUserConversations(userId) {
try {
// 第一步:获取所有会话
const [conversations] = await sequelize.query(
`SELECT c.*, u.nickName as userNickName, u.avatarUrl as userAvatar,
p.name as managerName
FROM chat_conversations c
INNER JOIN users u ON c.userId = u.userId
INNER JOIN userlogin.personnel p ON c.managerId = p.id
WHERE c.userId = ?
ORDER BY c.last_message_time DESC, c.created_at DESC`,
`SELECT * FROM chat_conversations
WHERE userId = ?
ORDER BY last_message_time DESC, created_at DESC`,
{ replacements: [userId] }
);
// 第二步:对于每个会话,单独获取用户和客服信息
for (let i = 0; i < conversations.length; i++) {
const conversation = conversations[i];
// 获取用户信息
const [users] = await sequelize.query(
'SELECT nickName, avatarUrl FROM users WHERE userId = ?',
{ replacements: [conversation.userId] }
);
if (users && users.length > 0) {
conversation.userNickName = users[0].nickName;
conversation.userAvatar = users[0].avatarUrl;
}
// 获取客服信息
try {
const [personnel] = await sequelize.query(
'SELECT name FROM userlogin.personnel WHERE id = ?',
{ replacements: [conversation.managerId] }
);
if (personnel && personnel.length > 0) {
conversation.managerName = personnel[0].name;
} else {
// 客服信息不存在时,使用默认名称
conversation.managerName = `客服${conversation.managerId}`;
}
} catch (error) {
console.warn('获取客服信息失败,使用默认名称:', error.message);
// 客服信息获取失败时,使用默认名称
conversation.managerName = `客服${conversation.managerId}`;
}
}
return conversations;
} catch (error) {
console.error('获取用户会话失败:', error);
@ -6944,16 +7024,37 @@ async function getUserConversations(userId) {
// 获取客服的所有会话
async function getManagerConversations(managerId) {
try {
// 分两步查询,避免跨数据库表连接时的排序规则问题
// 第一步:获取所有会话
const [conversations] = await sequelize.query(
`SELECT c.*, u.nickName as userNickName, u.avatarUrl as userAvatar,
p.name as managerName
FROM chat_conversations c
INNER JOIN users u ON c.userId = u.userId
INNER JOIN userlogin.personnel p ON c.managerId = p.id
WHERE c.managerId = ?
ORDER BY c.last_message_time DESC, c.created_at DESC`,
`SELECT * FROM chat_conversations WHERE managerId = ?
ORDER BY last_message_time DESC, created_at DESC`,
{ replacements: [managerId] }
);
// 第二步:对于每个会话,单独获取用户和客服信息
for (let i = 0; i < conversations.length; i++) {
const conversation = conversations[i];
// 获取用户信息
const [users] = await sequelize.query(
'SELECT nickName, avatarUrl FROM users WHERE userId = ?',
{ replacements: [conversation.userId] }
);
if (users && users.length > 0) {
conversation.userNickName = users[0].nickName;
conversation.userAvatar = users[0].avatarUrl;
}
// 获取客服信息
const [personnel] = await sequelize.query(
'SELECT name FROM userlogin.personnel WHERE id = ?',
{ replacements: [conversation.managerId] }
);
if (personnel && personnel.length > 0) {
conversation.managerName = personnel[0].name;
}
}
return conversations;
} catch (error) {
console.error('获取客服会话失败:', error);
@ -7141,12 +7242,23 @@ async function handleChatMessage(ws, payload) {
} else {
conversation = conversations[0];
console.log('查询到的会话详情:', conversation);
// 关键修复:确保receiverId是有效的客服ID,不是"user"或其他无效值
receiverId = conversation.managerId;
if (receiverId === 'user') {
console.error('错误: 会话的managerId是"user",使用payload中的managerId替代');
if (payload.managerId) {
receiverId = validateManagerId(payload.managerId);
} else {
throw new Error('会话的managerId无效,且payload中没有提供有效的managerId');
}
}
}
}
// 验证会话的userId是否与当前用户匹配,不匹配则修复
if (conversation.userId !== senderId) {
// 关键修复:只有当会话的userId不是当前用户ID,并且会话的managerId不是当前用户ID时,才更新会话的userId
// 避免将用户ID和客服ID错误地交换
if (conversation.userId !== senderId && conversation.managerId !== senderId) {
console.error(`错误: 会话userId(${conversation.userId})与当前用户ID(${senderId})不匹配`);
// 更新会话的userId为当前用户ID
await sequelize.query(
@ -7159,8 +7271,8 @@ async function handleChatMessage(ws, payload) {
}
} else if (connection.isManager) {
// 客服发送消息给用户
senderId = validateManagerId(connection.managerId);
senderType = 2;
senderId = validateManagerId(connection.managerId);
senderType = 2;
console.log('处理客服消息 - 详细信息:');
console.log('- managerId:', senderId);
@ -7243,9 +7355,10 @@ async function handleChatMessage(ws, payload) {
}
// 统一会话信息格式,强制使用正确的字段名
// 关键修复:保持原始会话的userId和managerId不变,只统一字段名
conversation = {
conversation_id: convId,
userId: senderId, // 使用传入的senderId
userId: conversation.userId,
managerId: conversation.managerId,
...conversation
};
@ -7463,13 +7576,26 @@ async function storeMessage(messageData) {
// 更新会话最后消息
async function updateConversationLastMessage(conversationId, lastMessage, timestamp) {
try {
await sequelize.query(
'UPDATE chat_conversations SET last_message = ?, last_message_time = ?, updated_at = ? WHERE conversation_id = ?',
{ replacements: [lastMessage, timestamp, timestamp, conversationId] }
// 关键修复:在更新最后消息前,先检查会话是否存在
const [conversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE conversation_id = ? LIMIT 1',
{ replacements: [conversationId] }
);
if (conversations && conversations.length > 0) {
// 只有当会话存在时,才更新最后消息
await sequelize.query(
'UPDATE chat_conversations SET last_message = ?, last_message_time = ?, updated_at = ? WHERE conversation_id = ?',
{ replacements: [lastMessage, timestamp, timestamp, conversationId] }
);
console.log(`更新会话最后消息成功: conversationId=${conversationId}, lastMessage=${lastMessage}`);
} else {
// 如果会话不存在,不创建新会话,只记录警告
console.warn(`警告: 尝试更新不存在的会话最后消息: conversationId=${conversationId}`);
}
} catch (error) {
console.error('更新会话最后消息失败:', error);
throw error;
// 不抛出错误,避免影响消息存储
}
}

539
utils/api.js

@ -1403,6 +1403,170 @@ module.exports = {
return request('/api/wechat/getOpenid', 'POST', { code: code });
},
// 获取personnel表数据
getPersonnelData: function() {
console.log('获取personnel表数据...');
return new Promise((resolve) => {
request('/api/managers', 'GET', {}) // 使用现有的managers接口查询采购员数据
.then(res => {
console.log('获取personnel表数据成功:', res);
// 适配不同的数据返回格式
const data = res && res.data && Array.isArray(res.data) ? res.data :
res && Array.isArray(res) ? res : [];
resolve(data);
})
.catch(err => {
console.error('获取personnel表数据失败:', err);
resolve([]);
});
});
},
// 根据手机号获取客服的managerId - 优化版
getManagerIdByPhone: function(phoneNumber) {
console.log('根据手机号获取managerId:', phoneNumber);
return new Promise(async (resolve) => {
// 严格验证手机号参数
if (!phoneNumber || typeof phoneNumber !== 'string' || phoneNumber.trim() === '') {
console.log('无效的手机号参数');
resolve(null);
return;
}
try {
// 获取personnel表数据
const data = await this.getPersonnelData();
// 精确匹配手机号,确保找到正确的客服记录
const personnel = data.find(item => {
// 严格检查phoneNumber字段,不依赖其他工作岗位字段
return item && item.phoneNumber === phoneNumber;
});
if (personnel) {
// 优先使用userId字段,其次使用id字段
const managerId = personnel.userId || personnel.id || null;
console.log(`找到客服记录,managerId: ${managerId}`);
resolve(managerId);
} else {
console.log(`在personnel表中未找到手机号为${phoneNumber}的客服记录`);
resolve(null);
}
} catch (error) {
console.error('查询personnel表失败:', error);
resolve(null);
}
});
},
// 检查用户是否为客服(通过查询personnel表)
checkIfUserIsCustomerService: function (phoneNumber = null) {
console.log('API.checkIfUserIsCustomerService - phoneNumber:', phoneNumber);
return new Promise(async (resolve) => {
// 如果没有电话号码,直接返回false
if (!phoneNumber) {
console.log('没有电话号码,不是客服');
resolve(false);
return;
}
// 使用getManagerIdByPhone来判断是否为客服
const managerId = await this.getManagerIdByPhone(phoneNumber);
const isCustomerService = !!managerId;
console.log(`用户手机号 ${phoneNumber} 客服检查结果: ${isCustomerService}`);
resolve(isCustomerService);
});
},
// 统一的用户类型设置函数
setUserType: function(userId, phoneNumber, currentType = '') {
console.log('API.setUserType - userId:', userId, 'phoneNumber:', phoneNumber, 'currentType:', currentType);
return new Promise(async (resolve) => {
try {
// 初始化用户类型
let userType = currentType || '';
let managerId = null;
// 获取本地存储的users信息
const users = wx.getStorageSync('users') || {};
// 检查是否为客服并获取managerId
if (phoneNumber) {
managerId = await this.getManagerIdByPhone(phoneNumber);
}
// 明确的用户类型判断逻辑
const isManager = !!managerId;
const isRegularUser = !isManager && (currentType.includes('buyer') || currentType.includes('seller') || currentType.includes('both'));
// 设置用户类型
if (isManager) {
// 客服身份:仅设置为manager
userType = 'manager';
// 保存managerId到本地存储
wx.setStorageSync('managerId', managerId);
console.log(`用户被识别为客服,managerId: ${managerId}`);
} else {
// 普通用户:确保只包含buyer、seller或both
if (currentType.includes('both')) {
userType = 'both';
} else if (currentType.includes('seller')) {
userType = 'seller';
} else if (currentType.includes('buyer')) {
userType = 'buyer';
} else {
userType = ''; // 默认为空类型,不设置默认值
}
// 清除managerId
wx.removeStorageSync('managerId');
console.log('用户被识别为普通用户,类型:', userType);
}
// 更新users存储
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].type = userType;
if (managerId) {
users[userId].managerId = managerId;
} else {
delete users[userId].managerId;
}
wx.setStorageSync('users', users);
}
// 存储用户类型信息
wx.setStorageSync('userType', userType);
// 更新全局数据
if (getApp && getApp().globalData) {
getApp().globalData.userType = userType;
getApp().globalData.isManager = isManager;
// 添加isCustomer标识,用于聊天功能判断
getApp().globalData.isCustomer = !isManager && (userType.includes('buyer') || userType.includes('seller') || userType.includes('both'));
}
// 确定是否为customer身份
const isCustomer = !isManager && (userType.includes('buyer') || userType.includes('seller') || userType.includes('both'));
resolve({
success: true,
userType: userType,
isManager: isManager,
isCustomer: isCustomer,
managerId: managerId
});
} catch (error) {
console.error('设置用户类型失败:', error);
resolve({
success: false,
error: error.message
});
}
});
},
// 微信登录函数 - 增强版,支持手机号一键登录
login: function (encryptedData = null, iv = null) {
return new Promise((resolve, reject) => {
@ -1470,44 +1634,186 @@ module.exports = {
userId = phoneRes.data.userId;
}
// 获取用户信息以判断是否为客服
// 获取解密后的手机号
let phoneNumber = null;
if (phoneRes.data && phoneRes.data.phoneNumber) {
phoneNumber = phoneRes.data.phoneNumber;
}
// 获取用户信息
this.getUserInfo(openid).then(userInfoRes => {
console.log('获取用户信息成功:', userInfoRes);
let userInfo = null;
let userType = 'customer'; // 默认客户类型
let userType = ''; // 默认类型
// 处理不同格式的响应
if (userInfoRes && userInfoRes.data) {
userInfo = userInfoRes.data;
// 判断用户类型 - 支持多种格式
if (userInfo.userType === 'customer_service' ||
userInfo.type === 'customer_service' ||
userInfo.isService === true ||
userInfo.isManager === true) {
userType = 'customer_service';
}
}
// 存储用户类型信息
wx.setStorageSync('userType', userType);
// 获取本地存储的users信息
const users = wx.getStorageSync('users') || {};
let currentType = '';
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userInfo = userInfo;
getApp().globalData.userType = userType;
// 如果有userId,从users中获取当前类型
if (userId && users[userId] && users[userId].type) {
currentType = users[userId].type;
console.log('从本地存储获取用户类型:', currentType);
}
// 先设置基础用户类型
userType = currentType || '';
// 使用客服身份判断逻辑:查询personnel表
Promise.all([
this.checkIfUserIsCustomerService(phoneNumber),
this.getManagerIdByPhone(phoneNumber)
]).then(([isCustomerService, managerId]) => {
if (isCustomerService && managerId) {
// 如果是客服,确保userType包含manager
if (!userType.includes('manager')) {
userType = userType ? userType + ',manager' : 'manager';
}
// 保存managerId到本地存储,用于WebSocket认证
console.log(`保存客服的managerId: ${managerId} 到本地存储`);
wx.setStorageSync('managerId', managerId);
// 更新users存储,包含managerId
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].managerId = managerId;
}
console.log('用户被识别为客服:', phoneNumber, '用户类型:', userType);
} else {
// 如果不是客服,移除manager标识
userType = userType.replace(/,?manager/g, '').replace(/^,|,$/g, '');
// 清除managerId
wx.removeStorageSync('managerId');
console.log('用户被识别为普通用户:', phoneNumber, '用户类型:', userType);
}
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userInfo, userType }
// 更新users存储中的类型信息
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].type = userType; // 直接存储用户类型
wx.setStorageSync('users', users);
}
// 存储用户类型信息
wx.setStorageSync('userType', userType);
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userInfo = userInfo;
getApp().globalData.userType = userType;
}
// 将用户类型更新到服务器数据库
if (isCustomerService) {
console.log('将客服身份更新到服务器数据库');
this.updateUserType('').catch(err => {
console.error('更新客服身份到服务器失败:', err);
// 失败不影响登录流程
});
}
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userInfo, userType, phoneNumber }
});
}).catch(checkErr => {
console.error('检查客服身份失败:', checkErr);
// 检查失败时默认为空类型
wx.setStorageSync('userType', '');
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userInfo, userType: '', phoneNumber }
});
});
}).catch(userInfoErr => {
console.warn('获取用户信息失败(不影响登录):', userInfoErr);
// 如果获取用户信息失败,仍然返回登录成功,但用户类型默认为客户
wx.setStorageSync('userType', 'customer');
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userType: 'customer' }
// 获取本地存储的users信息
const users = wx.getStorageSync('users') || {};
let currentType = '';
// 如果有userId,从users中获取当前类型
if (userId && users[userId] && users[userId].type) {
currentType = users[userId].type;
}
// 先设置基础用户类型
userType = currentType || '';
// 检查客服身份并获取managerId
Promise.all([
this.checkIfUserIsCustomerService(phoneNumber),
this.getManagerIdByPhone(phoneNumber)
]).then(([isCustomerService, managerId]) => {
if (isCustomerService && managerId) {
// 如果是客服,确保userType包含manager
if (!userType.includes('manager')) {
userType = userType ? userType + ',manager' : 'manager';
}
// 保存managerId到本地存储,用于WebSocket认证
console.log(`保存客服的managerId: ${managerId} 到本地存储`);
wx.setStorageSync('managerId', managerId);
// 更新users存储,包含managerId
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].managerId = managerId;
}
} else {
// 如果不是客服,移除manager标识
userType = userType.replace(/,?manager/g, '').replace(/^,|,$/g, '');
// 清除managerId
wx.removeStorageSync('managerId');
}
// 更新users存储
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].type = userType;
wx.setStorageSync('users', users);
}
wx.setStorageSync('userType', userType);
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userType = userType;
}
// 将用户类型更新到服务器数据库
if (isCustomerService) {
console.log('将客服身份更新到服务器数据库');
this.updateUserType('').catch(err => {
console.error('更新客服身份到服务器失败:', err);
// 失败不影响登录流程
});
}
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userType, phoneNumber }
});
}).catch(() => {
// 如果检查也失败,默认为空类型
wx.setStorageSync('userType', '');
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes, userType: '', phoneNumber }
});
});
});
}).catch(phoneErr => {
@ -1520,41 +1826,104 @@ module.exports = {
});
} else {
// 没有手机号信息,直接返回登录成功
// 获取用户信息以判断是否为客服
// 获取用户信息
this.getUserInfo(openid).then(userInfoRes => {
console.log('获取用户信息成功:', userInfoRes);
let userInfo = null;
let userType = 'customer'; // 默认客户类型
let phoneNumber = null;
// 处理不同格式的响应
if (userInfoRes && userInfoRes.data) {
userInfo = userInfoRes.data;
// 判断用户类型 - 支持多种格式
if (userInfo.userType === 'customer_service' ||
userInfo.type === 'customer_service' ||
userInfo.isService === true ||
userInfo.isManager === true) {
userType = 'customer_service';
// 尝试从用户信息中获取手机号
if (userInfo.phoneNumber) {
phoneNumber = userInfo.phoneNumber;
}
}
// 存储用户类型信息
wx.setStorageSync('userType', userType);
// 获取本地存储的users信息
const users = wx.getStorageSync('users') || {};
let currentType = '';
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userInfo = userInfo;
getApp().globalData.userType = userType;
// 如果有userId,从users中获取当前类型
if (userId && users[userId] && users[userId].type) {
currentType = users[userId].type;
}
resolve({
success: true,
data: { openid, userId, sessionKey, userInfo, userType }
});
// 先设置基础用户类型
userType = currentType || 'customer';
// 如果有手机号,尝试检查是否为客服
if (phoneNumber) {
this.checkIfUserIsCustomerService(phoneNumber).then(isCustomerService => {
if (isCustomerService) {
// 如果是客服,确保userType包含manager
if (!userType.includes('manager')) {
userType = userType === 'customer' ? 'manager' : userType + ',manager';
}
console.log('用户被识别为客服:', phoneNumber, '用户类型:', userType);
} else {
// 如果不是客服,移除manager标识
userType = userType.replace(/,?manager/g, '').replace(/^,|,$/g, '') || 'customer';
console.log('用户被识别为普通用户:', phoneNumber, '用户类型:', userType);
}
// 更新users存储
if (userId) {
if (!users[userId]) {
users[userId] = {};
}
users[userId].type = userType.replace('customer', '');
wx.setStorageSync('users', users);
}
// 存储用户类型信息
wx.setStorageSync('userType', userType);
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userInfo = userInfo;
getApp().globalData.userType = userType;
}
resolve({
success: true,
data: { openid, userId, sessionKey, userInfo, userType, phoneNumber }
});
}).catch(() => {
// 检查失败时默认为普通用户
wx.setStorageSync('userType', 'customer');
resolve({
success: true,
data: { openid, userId, sessionKey, userInfo, userType: 'customer', phoneNumber }
});
});
} else {
// 没有手机号,默认为普通用户
wx.setStorageSync('userType', 'customer');
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userInfo = userInfo;
getApp().globalData.userType = userType;
}
resolve({
success: true,
data: { openid, userId, sessionKey, userInfo, userType }
});
}
}).catch(userInfoErr => {
console.warn('获取用户信息失败(不影响登录):', userInfoErr);
// 如果获取用户信息失败,仍然返回登录成功,但用户类型默认为客户
wx.setStorageSync('userType', 'customer');
// 更新全局用户信息
if (getApp && getApp().globalData) {
getApp().globalData.userType = 'customer';
}
resolve({
success: true,
data: { openid, userId, sessionKey, userType: 'customer' }
@ -1667,8 +2036,8 @@ module.exports = {
return request('/api/user/upload', 'POST', data);
},
// 更新用户类型
updateUserType: async (typeToAdd) => {
// 更新用户类型 - 恢复原有的类型转换功能
updateUserType: async function(typeToAdd) {
try {
const userId = wx.getStorageSync('userId');
const openid = wx.getStorageSync('openid');
@ -1683,59 +2052,95 @@ module.exports = {
let users = wx.getStorageSync('users') || {};
let currentType = users[userId] && users[userId].type ? users[userId].type : '';
// 计算新的用户类型
// 计算新的用户类型(支持buyer、seller、both、manager)
let newType = currentType;
// 如果要添加的类型与当前类型相同,则不改变
if (newType === typeToAdd) {
return;
}
// 如果当前类型为空,则直接设置为要添加的类型
if (!newType) {
newType = typeToAdd;
} else {
// 如果当前类型是buyer,要添加seller,则变为both
if (newType === 'buyer' && typeToAdd === 'seller') {
newType = 'both';
// 标记是否需要强制更新到服务器(即使类型没有变化)
let forceUpdate = false;
// 处理空字符串参数 - 可能是从客服登录流程调用的
if (typeToAdd === '') {
// 检查当前类型是否包含manager
if (currentType.includes('manager')) {
forceUpdate = true; // 客服类型需要强制同步到服务器
// 确保type设置为manager,而不是空字符串
newType = 'manager';
console.log('检测到客服类型,将强制同步manager类型到服务器');
}
// 如果当前类型是seller,要添加buyer,则变为both
else if (newType === 'seller' && typeToAdd === 'buyer') {
newType = 'both';
}
// 只处理buyer、seller类型转换,manager由系统自动设置
else if (typeToAdd === 'buyer' || typeToAdd === 'seller') {
// 如果当前类型为空,则直接设置为要添加的类型
if (!newType) {
newType = typeToAdd;
} else {
// 如果当前类型是buyer,要添加seller,则变为both
if (newType === 'buyer' && typeToAdd === 'seller') {
newType = 'both';
}
// 如果当前类型是seller,要添加buyer,则变为both
else if (newType === 'seller' && typeToAdd === 'buyer') {
newType = 'both';
}
// 如果当前类型是both,不做改变
else if (newType === 'both') {
// 但仍然需要检查是否是客服,是的话需要强制更新
if (currentType.includes('manager')) {
forceUpdate = true;
}
}
}
}
// 如果类型没有变化,不需要更新
if (newType === currentType) {
// 正常情况下,如果类型没有变化且不强制更新,不需要继续
if (newType === currentType && !forceUpdate) {
return;
}
// 保留manager标识(如果有)
const hasManager = currentType.includes('manager');
if (hasManager && !newType.includes('manager')) {
newType = newType ? newType + ',manager' : 'manager';
}
// 更新本地存储
if (!users[userId]) {
users[userId] = {};
}
users[userId].type = newType;
users[userId].type = newType.replace('customer', ''); // 不存储customer类型
wx.setStorageSync('users', users);
// 更新全局数据
const app = getApp();
app.globalData.userType = newType;
// 同时更新本地的userType存储
wx.setStorageSync('userType', newType);
console.log('用户类型更新成功:', newType);
// 上传到服务器
const uploadData = {
userId,
openid,
...userInfo,
type: newType,
timestamp: Date.now()
};
// 如果有userInfo,合并其属性但不覆盖已有的关键属性
if (userInfo) {
const userInfoCopy = {...userInfo};
delete userInfoCopy.userId; // 不覆盖userId
delete userInfoCopy.openid; // 不覆盖openid
delete userInfoCopy.type; // 不覆盖type
delete userInfoCopy.timestamp; // 不覆盖timestamp
Object.assign(uploadData, userInfoCopy);
}
console.log('准备上传用户类型到服务器:', uploadData);
// 调用用户信息更新API
return API.request({
url: '/api/user/update',
method: 'POST',
data: uploadData
});
return request('/api/user/update', 'POST', uploadData);
} catch (error) {
console.error('更新用户类型失败:', error);

286
utils/websocket.js

@ -111,10 +111,10 @@ class WebSocketManager {
return;
}
// 处理认证响应
// 处理认证响应(兼容auth_response和auth_success两种消息格式)
if (data.type === 'auth_response') {
if (data.success) {
console.log('WebSocket认证成功');
console.log('WebSocket认证成功(auth_response)');
this.isAuthenticated = true;
// 触发认证成功事件,并传递用户类型信息
this._trigger('authenticated', { userType: data.userType || 'customer' });
@ -128,6 +128,19 @@ class WebSocketManager {
return;
}
// 处理auth_success格式的认证成功消息(与后端实际返回格式匹配)
if (data.type === 'auth_success') {
console.log('WebSocket认证成功(auth_success)');
this.isAuthenticated = true;
// 从payload中提取用户类型信息
const userType = data.payload && data.payload.type ? data.payload.type : 'customer';
// 触发认证成功事件
this._trigger('authenticated', { userType: userType });
// 认证成功后发送队列中的消息 - 关键修复!
this._flushMessageQueue();
return;
}
// 处理客服状态更新消息
if (data.type === 'customerServiceStatusUpdate') {
console.log('处理客服状态更新:', data);
@ -166,8 +179,8 @@ class WebSocketManager {
/**
* 发送认证消息
* @param {string} userType - 用户类型'user''customerService'
* @param {string} userId - 用户ID
* @param {string} userType - 用户类型'user''manager'
* @param {string} userId - 用户ID或managerId
*/
authenticate(userType = null, userId = null) {
try {
@ -176,21 +189,83 @@ class WebSocketManager {
const globalUserInfo = app.globalData.userInfo || {};
// 如果传入了参数,优先使用传入的参数
const finalUserType = userType || globalUserInfo.userType || globalUserInfo.type || 'customer';
const finalUserId = userId || globalUserInfo.userId || wx.getStorageSync('userId') || `temp_${Date.now()}`;
let finalUserType = userType || globalUserInfo.userType || globalUserInfo.type || 'customer';
console.log('发送WebSocket认证消息:', { userId: finalUserId, userType: finalUserType });
// 构建认证消息 - 严格区分用户类型和认证信息
let authMessage;
// 构建认证消息
const authMessage = {
type: 'auth',
timestamp: Date.now(),
data: {
userId: finalUserId,
userType: finalUserType,
// 可以根据实际需求添加更多认证信息
// 检查是否为客服身份
const storedManagerId = wx.getStorageSync('managerId');
const isManager = finalUserType === 'manager' || storedManagerId;
if (isManager) {
// 客服认证:必须使用有效的managerId,不允许使用普通userId作为容错
if (!storedManagerId) {
console.error('客服认证失败:未找到有效的managerId');
this._trigger('authFailed', { message: '客服认证失败:未找到有效的managerId' });
return;
}
authMessage = {
type: 'auth',
managerId: storedManagerId,
userType: 'manager',
timestamp: Date.now()
};
console.log('客服用户认证:', { managerId: storedManagerId, userType: 'manager' });
} else {
// 普通用户认证:必须使用users表中的正式userId
// 聊天功能必须在用户授权登录后使用,因此必须有有效的userId
let finalUserId = null;
// 优先级1:使用传入的userId(应该是从服务器获取的正式ID)
if (userId) {
finalUserId = String(userId);
console.log('使用传入的用户ID:', finalUserId);
}
// 优先级2:从全局用户信息获取(应该包含服务器返回的userId)
else if (globalUserInfo && globalUserInfo.userId) {
finalUserId = String(globalUserInfo.userId);
console.log('从globalData获取用户ID:', finalUserId);
}
// 优先级3:从本地存储获取(应该存储了服务器返回的userId)
else {
finalUserId = wx.getStorageSync('userId');
if (finalUserId) {
finalUserId = String(finalUserId);
console.log('从本地存储获取用户ID:', finalUserId);
}
}
// 验证是否有有效的用户ID
if (!finalUserId || finalUserId === 'undefined' || finalUserId === 'null') {
console.error('认证失败:未获取到有效的用户ID');
this._trigger('authFailed', {
message: '用户未授权登录,请先完成登录',
code: 'NO_VALID_USER_ID'
});
return;
}
// 确保使用正确的用户类型
// 根据用户在users表中的类型设置,支持customer、buyer、seller、both
let authUserType = finalUserType;
if (!authUserType || authUserType === 'user') {
authUserType = 'customer';
}
};
console.log('准备认证 - 用户ID:', finalUserId, '用户类型:', authUserType);
authMessage = {
type: 'auth',
userId: finalUserId,
userType: authUserType,
timestamp: Date.now()
};
console.log('普通用户认证:', { userId: finalUserId, userType: authUserType });
}
console.log('发送WebSocket认证消息:', authMessage);
// 直接发送认证消息,不经过常规消息队列
if (this.isConnected && this.socket) {
@ -201,6 +276,7 @@ class WebSocketManager {
},
fail: (error) => {
console.error('认证消息发送失败:', error);
this._trigger('authFailed', { message: '认证消息发送失败' });
// 认证失败后尝试重新认证
setTimeout(() => {
this.authenticate(userType, userId);
@ -210,6 +286,7 @@ class WebSocketManager {
}
} catch (error) {
console.error('发送认证消息异常:', error);
this._trigger('authFailed', { message: '认证处理异常' });
}
}
@ -230,6 +307,62 @@ class WebSocketManager {
data.timestamp = Date.now();
}
// 确保消息使用正式用户ID,严格区分sender_id和receiver_id
try {
const app = getApp();
const globalUserInfo = app.globalData.userInfo || {};
let currentUserId = String(globalUserInfo.userId || wx.getStorageSync('userId') || '');
// 重要:确保使用的是正式用户ID,不允许使用临时ID
if (currentUserId.startsWith('temp_') || currentUserId.includes('temp') || currentUserId.includes('test_')) {
console.error('严重错误:消息对象中检测到临时或测试用户ID,聊天功能必须使用正式userId');
// 尝试从本地存储获取正式用户ID
const userInfo = wx.getStorageSync('userInfo');
if (userInfo && userInfo.userId && !String(userInfo.userId).includes('temp') && !String(userInfo.userId).includes('test_')) {
currentUserId = String(userInfo.userId);
console.log('已更新为正式用户ID:', currentUserId);
} else {
console.error('无法获取有效的正式用户ID,消息发送失败');
return false;
}
}
// 确保消息中包含正确的sender_id,并保留userId字段供服务器使用
if (data.userId && !data.sender_id) {
console.warn('消息使用了userId字段,应改为使用sender_id字段');
data.sender_id = data.userId;
// 关键修复:保留userId字段,因为服务器需要它来处理消息
// delete data.userId; // 不再删除userId字段
}
// 如果没有指定sender_id,则设置为当前用户ID
if (!data.sender_id) {
data.sender_id = currentUserId;
}
// 确保接收者ID使用receiver_id字段
if (data.targetUserId && !data.receiver_id) {
console.warn('消息使用了targetUserId字段,应改为使用receiver_id字段');
data.receiver_id = data.targetUserId;
delete data.targetUserId;
}
// 【修复】确保聊天消息使用正确的数据库字段名
// 数据库使用下划线命名法,前端代码中可能使用驼峰命名法
if (data.receiverId) {
console.warn('检测到使用了驼峰命名的receiverId,将转换为下划线命名的receiver_id');
data.receiver_id = data.receiver_id || data.receiverId;
// 保留原始字段以保持兼容性
}
if (data.senderId) {
console.warn('检测到使用了驼峰命名的senderId,将转换为下划线命名的sender_id');
data.sender_id = data.sender_id || data.senderId;
// 保留原始字段以保持兼容性
}
} catch (e) {
console.error('处理消息用户ID时出错:', e);
}
// 如果是认证消息或连接未建立,直接处理
if (data.type === 'auth' || data.type === 'ping') {
// 认证消息和心跳消息不需要等待认证
@ -488,5 +621,122 @@ class WebSocketManager {
}
}
// 导出单例
export default new WebSocketManager();
// 消息发送状态管理 - 全局作用域
const messageStatus = new Map();
const MESSAGE_SENDING = 'sending';
const MESSAGE_SENT = 'sent';
const MESSAGE_FAILED = 'failed';
// 消息去重函数 - 防止重复发送
function shouldSendMessage(messageId) {
const status = messageStatus.get(messageId);
// 如果消息正在发送中,不重复发送
if (status === MESSAGE_SENDING) {
console.log(`[WebSocket] 消息 ${messageId} 正在发送中,跳过重复发送`);
return false;
}
// 设置消息状态为发送中
messageStatus.set(messageId, MESSAGE_SENDING);
return true;
}
// 更新消息发送状态
function updateMessageStatus(messageId, status) {
messageStatus.set(messageId, status);
// 定期清理已完成的消息状态
if (messageStatus.size > 100) {
cleanupMessageStatus();
}
}
// 清理已完成的消息状态
function cleanupMessageStatus() {
for (const [messageId, status] of messageStatus.entries()) {
if (status === MESSAGE_SENT || status === MESSAGE_FAILED) {
messageStatus.delete(messageId);
}
}
}
// 创建单例实例
const websocketManager = new WebSocketManager();
// 增强的消息发送函数
function sendEnhancedMessage(messageData) {
// 确保消息数据有效
if (!messageData || typeof messageData !== 'object') {
console.error('[WebSocket] 无效的消息数据:', messageData);
return false;
}
// 确保消息有唯一ID
const actualMessageData = messageData;
if (!actualMessageData.messageId) {
// 如果是create_conversation消息,使用userId+managerId+timestamp生成临时ID
if (actualMessageData.type === 'create_conversation') {
actualMessageData.messageId = `create_${actualMessageData.userId}_${actualMessageData.managerId}_${actualMessageData.timestamp}`;
} else {
// 其他消息类型生成随机ID
actualMessageData.messageId = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// 消息去重检查
if (!shouldSendMessage(actualMessageData.messageId)) {
return false;
}
try {
// 确保包含必要字段
if (actualMessageData.type === 'chat_message') {
if (!actualMessageData.userId || !actualMessageData.managerId) {
console.error('[WebSocket] 聊天消息缺少必要的userId或managerId字段');
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
return false;
}
}
// 设置消息状态为发送中
updateMessageStatus(actualMessageData.messageId, 'sending');
// 发送消息 - 使用WebSocketManager实例
const socketManager = websocketManager;
if (socketManager.send) {
socketManager.send(actualMessageData);
console.log(`[WebSocket] 消息 ${actualMessageData.messageId} 发送成功`);
} else {
console.error('[WebSocket] 无法访问send方法');
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
return false;
}
// 设置消息发送超时检测
setTimeout(() => {
if (messageStatus.get(actualMessageData.messageId) === MESSAGE_SENDING) {
console.warn(`[WebSocket] 消息 ${actualMessageData.messageId} 发送超时,可能需要重试`);
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
}
}, 5000); // 5秒超时
return true;
} catch (error) {
console.error(`[WebSocket] 发送消息失败: ${error.message}`);
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
return false;
}
}
// 修复导出问题,确保正确支持ES6默认导入
// CommonJS导出方式
module.exports = websocketManager;
// 添加消息处理相关函数到导出对象
module.exports.sendEnhancedMessage = sendEnhancedMessage;
module.exports.updateMessageStatus = updateMessageStatus;
module.exports.MESSAGE_SENT = MESSAGE_SENT;
module.exports.MESSAGE_FAILED = MESSAGE_FAILED;
// ES6模块导出
export default websocketManager;
export { sendEnhancedMessage, updateMessageStatus, MESSAGE_SENT, MESSAGE_FAILED };

Loading…
Cancel
Save