Browse Source

修复聊天页面跳转失败问题,添加缺失的页面注册

pull/1/head
徐飞洋 3 months ago
parent
commit
6a6d9cd447
  1. 77
      app.js
  2. 5
      app.json
  3. BIN
      images/轮播图1.jpg
  4. BIN
      images/轮播图2.jpg
  5. 3897
      pages/chat-detail/index.js
  6. 6
      pages/chat-detail/index.json
  7. 118
      pages/chat-detail/index.wxml
  8. 491
      pages/chat-detail/index.wxss
  9. 255
      pages/chat-detail/system-test.js
  10. 158
      pages/chat-detail/test-backup-query.js
  11. 192
      pages/chat-detail/test-real-user.js
  12. 954
      pages/chat/index.js
  13. 5
      pages/chat/index.json
  14. 78
      pages/chat/index.wxml
  15. 314
      pages/chat/index.wxss
  16. 165
      pages/customer-service/detail/index.js
  17. 10
      pages/customer-service/detail/index.json
  18. 116
      pages/customer-service/detail/index.wxml
  19. 321
      pages/customer-service/detail/index.wxss
  20. 389
      pages/customer-service/index.js
  21. 4
      pages/customer-service/index.json
  22. 99
      pages/customer-service/index.wxml
  23. 379
      pages/customer-service/index.wxss
  24. 773
      pages/message-list/index.js
  25. 3
      pages/message-list/index.json
  26. 40
      pages/message-list/index.wxml
  27. 79
      pages/message-list/index.wxss
  28. 123
      pages/profile/index.js
  29. 382
      pages/test-service/test-service.js
  30. 3
      pages/test-service/test-service.json
  31. 97
      pages/test-service/test-service.wxml
  32. 249
      pages/test-service/test-service.wxss
  33. 108
      server-example/check-chat-online-status-detailed.js
  34. 51
      server-example/check_chat_data.js
  35. 220
      server-example/check_messages_in_db.js
  36. 81
      server-example/check_specific_conversation.js
  37. 76
      server-example/cleanup_invalid_chat_data.js
  38. 323
      server-example/comprehensive-manager-status-test.js
  39. 245
      server-example/comprehensive_chat_test.js
  40. 75
      server-example/customer-service-status-test.js
  41. 121
      server-example/debug_chat_reply.js
  42. 138
      server-example/debug_manager_message.js
  43. 244
      server-example/debug_new_conversation.js
  44. 252
      server-example/diagnose-customer-service.js
  45. 236
      server-example/fix_chat_functionality.js
  46. 81
      server-example/minimal_message_test.js
  47. 28
      server-example/server-mysql.js
  48. 125
      server-example/simple-customer-service-test.js
  49. 142
      server-example/simple_message_center_test.js
  50. 134
      server-example/test-customer-service-online.js
  51. 111
      server-example/test-manager-online-check.js
  52. 79
      server-example/test-managers-api.js
  53. 85
      server-example/test_chat_flow.js
  54. 215
      server-example/test_chat_functionality_complete.js
  55. 279
      server-example/test_complete_chat_flow.js
  56. 193
      server-example/test_correct_message_format.js
  57. 247
      server-example/test_improved_message_center.js
  58. 39
      server-example/test_manager_conversations.js
  59. 215
      server-example/test_message_center.js
  60. 108
      server-example/test_message_issue.js
  61. 284
      server-example/test_message_storage.js
  62. 345
      server-example/test_specific_customer_service.js
  63. 168
      server-example/verify-customer-service-online.js
  64. 112
      server-example/verify-manager-online-complete.js
  65. 83
      server-example/verify_chat_fix.js
  66. 86
      server-example/verify_message_fix.js
  67. 333
      server-example/聊天功能实现逻辑分析文档.md
  68. 39
      test-time-backend.js
  69. 34
      test-time-frontend.js
  70. 269
      utils/auth.js
  71. 767
      utils/websocket.js

77
app.js

@ -39,19 +39,6 @@ App({
onLaunch: function () { onLaunch: function () {
// 初始化应用 // 初始化应用
console.log('App Launch') console.log('App Launch')
// 初始化WebSocket管理器
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')) { if (!wx.getStorageSync('users')) {
wx.setStorageSync('users', {}) wx.setStorageSync('users', {})
@ -112,9 +99,6 @@ App({
console.log('App初始化 - 用户类型:', this.globalData.userType); console.log('App初始化 - 用户类型:', this.globalData.userType);
console.log('App初始化 - 用户信息:', this.globalData.userInfo); console.log('App初始化 - 用户信息:', this.globalData.userInfo);
// 异步获取客服列表并缓存
this.fetchAndCacheCustomerServices();
// 获取用户信息 // 获取用户信息
wx.getSetting({ wx.getSetting({
success: res => { success: res => {
@ -175,66 +159,7 @@ App({
userType: 'customer', // 默认客户类型 userType: 'customer', // 默认客户类型
currentTab: 'index', // 当前选中的tab currentTab: 'index', // 当前选中的tab
showTabBar: true, // 控制底部tab-bar显示状态 showTabBar: true, // 控制底部tab-bar显示状态
onNewMessage: null, // 全局新消息处理回调函数
isConnected: false,
unreadMessages: 0,
// 测试环境配置 // 测试环境配置
isTestMode: false, isTestMode: false
// 全局WebSocket连接状态
wsConnectionState: 'disconnected', // disconnected, connecting, connected, error
// 客服相关状态
isServiceOnline: false,
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 [];
} }
}) })

5
app.json

@ -11,11 +11,10 @@
"pages/notopen/index", "pages/notopen/index",
"pages/create-supply/index", "pages/create-supply/index",
"pages/goods-detail/goods-detail", "pages/goods-detail/goods-detail",
"pages/customer-service/index",
"pages/message-list/index",
"pages/chat/index", "pages/chat/index",
"pages/chat-detail/index", "pages/chat-detail/index",
"pages/test-service/test-service" "pages/message-list/index",
"pages/customer-service/index"
], ],
"subpackages": [ "subpackages": [
{ {

BIN
images/轮播图1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/轮播图2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

3897
pages/chat-detail/index.js

File diff suppressed because it is too large

6
pages/chat-detail/index.json

@ -1,7 +1,3 @@
{ {
"navigationBarTitleText": "聊天详情", "navigationBarTitleText": "聊天详情"
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"usingComponents": {},
"enablePullDownRefresh": false
} }

118
pages/chat-detail/index.wxml

@ -1,108 +1,44 @@
<view class="chat-detail-container"> <view class="container">
<!-- 连接状态显示 --> <view class="chat-header">
<view wx:if="{{!isMockMode}}" class="connection-status {{connectionStatus}}"> <view class="header-back" bindtap="onBack">
<text class="status-text">{{connectionMessage}}</text> <text>←</text>
<button wx:if="{{connectionStatus === 'error' || connectionStatus === 'disconnected'}}"
class="reconnect-btn"
size="mini"
bindtap="reconnect">
重新连接
</button>
</view> </view>
<view class="header-info">
<!-- 测试模式切换 --> <text class="header-name">聊天对象</text>
<view class="test-mode-switch">
<switch wx:if="{{!isMockMode}}" checked="{{!isMockMode}}" bindchange="toggleMockMode" />
<text wx:if="{{!isMockMode}}" class="mode-label">模拟模式切换</text>
</view>
<!-- 消息列表区域 - 微信风格 -->
<scroll-view
id="message-list"
class="wechat-message-list"
scroll-y
bindscrolltoupper="loadMoreMessages"
bindscroll="onScroll"
scroll-top="{{scrollTop}}"
scroll-with-animation
>
<!-- 消息容器 -->
<view class="wechat-message-container">
<!-- 遍历消息列表 -->
<block wx:for="{{messages}}" wx:key="index">
<!-- 时间显示 - 条件显示 -->
<view wx:if="{{item.showTime}}" class="wechat-time-display">
<text class="wechat-time-text">{{item.time}}</text>
</view> </view>
<view class="header-more">
<!-- 系统消息(如诈骗提示)--> <text>⋮</text>
<view wx:if="{{item.type === 'system'}}" class="wechat-system-message">
<view class="wechat-system-content">
<view wx:if="{{item.isWarning}}" class="wechat-warning-title">谨防诈骗</view>
<text class="wechat-system-text">{{item.content}}</text>
</view> </view>
</view> </view>
<scroll-view class="chat-content" scroll-y bindscrolltolower="loadMoreMessages">
<view class="chat-messages">
<!-- 对方消息 --> <!-- 对方消息 -->
<view wx:elif="{{item.type === 'message' && item.sender === 'other'}}" class="wechat-message-item wechat-other-message" id="msg_{{index}}"> <view class="message-item other">
<view class="wechat-avatar"> <view class="avatar">
<text class="wechat-avatar-text">{{userName ? userName.charAt(0) : (avatar || '用')}}</text> <image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
</view> </view>
<!-- 图片消息直接显示 --> <view class="message-bubble">
<image wx:if="{{item.isImage}}" src="{{item.content}}" class="wechat-message-image wechat-other-image" mode="aspectFit" bindtap="previewImage" data-src="{{item.content}}" /> <text class="message-content">您好,有什么可以帮助您的吗?</text>
<!-- 文本消息仍然显示在气泡中 --> <text class="message-time">刚刚</text>
<view wx:elif="{{!item.isImage}}" class="wechat-message-wrapper">
<view class="wechat-message-name">{{userName}}</view>
<view class="wechat-message-bubble wechat-other-bubble">
<text class="wechat-message-text">{{item.content}}</text>
</view> </view>
</view> </view>
<!-- 自己的消息 -->
<view class="message-item me">
<view class="message-bubble">
<text class="message-content">你好,我想咨询一下产品信息</text>
<text class="message-time">刚刚</text>
</view> </view>
<!-- 我的消息 --> <view class="avatar">
<view wx:elif="{{item.type === 'message' && item.sender === 'me'}}" class="wechat-message-item wechat-my-message" id="msg_{{index}}"> <image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
<!-- 图片消息直接显示 -->
<image wx:if="{{item.isImage}}" src="{{item.content}}" class="wechat-message-image wechat-my-image" mode="aspectFit" bindtap="previewImage" data-src="{{item.content}}" />
<!-- 文本消息仍然显示在气泡中 -->
<view wx:elif="{{!item.isImage}}" class="wechat-message-wrapper wechat-my-wrapper">
<view class="wechat-message-bubble wechat-my-bubble">
<text class="wechat-message-text wechat-my-text">{{item.content}}</text>
</view>
</view>
<view class="wechat-avatar wechat-my-avatar">
<text class="wechat-avatar-text">我</text>
</view> </view>
</view> </view>
</block>
</view> </view>
</scroll-view> </scroll-view>
<!-- 到达最底部按钮 --> <view class="chat-input-area">
<view wx:if="{{showScrollToBottomBtn}}" class="scroll-to-bottom-btn" bindtap="scrollToBottom"> <input class="message-input" placeholder="请输入消息" bindinput="onInputChange" value="{{inputValue}}"></input>
<text class="scroll-btn-text">到达最底部</text> <button class="send-button" bindtap="sendMessage">发送</button>
</view>
<!-- 输入区域 - 微信风格 -->
<view class="wechat-input-area">
<view class="wechat-input-toolbar">
<view class="wechat-input-left">
<view class="wechat-emoji-btn" bindtap="showEmoji">😊</view>
<view class="wechat-voice-btn" bindtap="toggleVoiceMode">🔊</view>
</view>
<view class="wechat-input-wrapper">
<input
class="wechat-message-input"
value="{{inputValue}}"
bindinput="onInput"
placeholder="消息"
placeholder-style="color: #999999;"
adjust-position
maxlength="500"
/>
</view>
<view class="wechat-input-right">
<view class="wechat-more-btn" bindtap="showMoreOptions">+</view>
<view class="wechat-send-btn" bindtap="sendMessage" disabled="{{!inputValue.trim()}}">发送</view>
</view>
</view>
</view> </view>
</view> </view>

491
pages/chat-detail/index.wxss

@ -1,468 +1,155 @@
/* 微信聊天界面样式 */ .container {
.chat-detail-container { background-color: #f0f0f0;
min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh;
background-color: #e5e5e5;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
}
/* 连接状态显示样式 */
.connection-status {
padding: 8rpx 0;
text-align: center;
font-size: 24rpx;
color: #fff;
position: relative;
z-index: 10;
}
.connection-status.connecting {
background-color: #FF9800;
}
.connection-status.connected {
background-color: #4CAF50;
}
.connection-status.error {
background-color: #F44336;
} }
.connection-status.disconnected { .chat-header {
background-color: #9E9E9E; background-color: #fff;
}
.status-text {
display: inline-block;
padding: 0 20rpx;
border-radius: 10rpx;
}
.reconnect-btn {
display: inline-block;
margin-left: 20rpx;
padding: 0 20rpx;
font-size: 24rpx;
line-height: 40rpx;
background-color: rgba(255, 255, 255, 0.3);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 16rpx;
}
.test-mode-switch {
padding: 20rpx;
background-color: #f8f8f8;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
padding: 16rpx 24rpx;
border-bottom: 1rpx solid #e8e8e8; border-bottom: 1rpx solid #e8e8e8;
position: sticky;
top: 0;
z-index: 100;
} }
.mode-label { .header-back, .header-more {
margin-left: 20rpx; width: 60rpx;
font-size: 28rpx; height: 60rpx;
color: #666;
}
/* 消息列表区域 - 微信风格 */
.wechat-message-list {
flex: 1;
padding: 20rpx 20rpx 40rpx;
box-sizing: border-box;
overflow-y: auto;
background-color: #e5e5e5;
}
/* 消息容器 */
.wechat-message-container {
display: flex;
flex-direction: column;
gap: 20rpx;
}
/* 时间显示 */
.wechat-time-display {
display: flex;
justify-content: center;
margin: 20rpx 0;
}
.wechat-time-text {
background-color: rgba(0, 0, 0, 0.1);
color: #fff;
font-size: 24rpx;
padding: 6rpx 20rpx;
border-radius: 12rpx;
}
/* 系统消息 */
.wechat-system-message {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
margin: 10rpx 0; font-size: 32rpx;
}
.wechat-system-content {
background-color: rgba(255, 255, 255, 0.8);
border-radius: 18rpx;
padding: 16rpx 24rpx;
max-width: 80%;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.wechat-warning-title {
color: #ff0000;
font-size: 28rpx;
font-weight: bold; font-weight: bold;
margin-bottom: 10rpx;
}
.wechat-system-text {
color: #606266;
font-size: 26rpx;
line-height: 36rpx;
}
/* 商品信息卡片 */
.wechat-goods-card {
display: flex;
background-color: #ffffff;
border-radius: 18rpx;
padding: 20rpx;
margin: 10rpx 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
align-self: flex-start;
max-width: 80%;
} }
.wechat-goods-avatar { .header-info {
width: 120rpx;
height: 120rpx;
background-color: #f0f0f0;
border-radius: 8rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.wechat-goods-content {
flex: 1; flex: 1;
min-width: 0; text-align: center;
} }
.wechat-goods-title { .header-name {
font-size: 28rpx; font-size: 32rpx;
color: #303133; font-weight: bold;
font-weight: 500; color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 10rpx;
} }
.wechat-goods-desc { .chat-content {
font-size: 26rpx; flex: 1;
color: #606266; padding: 20rpx;
display: block; overflow-y: auto;
margin-bottom: 10rpx;
line-height: 36rpx;
} }
.wechat-goods-footer { .chat-messages {
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
align-items: center; gap: 20rpx;
}
.wechat-goods-price {
font-size: 32rpx;
color: #e64340;
font-weight: bold;
}
.wechat-goods-type {
font-size: 24rpx;
color: #909399;
background-color: #f0f9ff;
padding: 4rpx 16rpx;
border-radius: 12rpx;
} }
/* 消息项 */ .message-item {
.wechat-message-item {
display: flex; display: flex;
margin: 10rpx 0;
align-items: flex-end; align-items: flex-end;
gap: 16rpx;
max-width: 100%;
} }
/* 对方消息 */ .message-item.other {
.wechat-other-message {
flex-direction: row; flex-direction: row;
} }
/* 我的消息 */ .message-item.me {
.wechat-my-message {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
/* 头像 - 圆形 */ .avatar {
.wechat-avatar { width: 64rpx;
width: 76rpx; height: 64rpx;
height: 76rpx;
border-radius: 50%; border-radius: 50%;
background-color: #9aa5b1;
display: flex;
align-items: center;
justify-content: center;
margin: 0 12rpx;
flex-shrink: 0;
overflow: hidden; overflow: hidden;
flex-shrink: 0;
} }
.wechat-my-avatar { .avatar image {
background-color: #d8d8d8; width: 100%;
} height: 100%;
.wechat-avatar-text {
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
}
/* 消息包装器 */
.wechat-message-wrapper {
max-width: 75%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6rpx;
}
/* 客服姓名显示 */
.wechat-message-name {
font-size: 24rpx;
color: #909399;
margin-left: 4rpx;
margin-bottom: 2rpx;
}
.wechat-my-wrapper {
align-items: flex-end;
} }
/* 消息气泡 - 添加三角形箭头 */ .message-bubble {
.wechat-message-bubble { max-width: 70%;
padding: 16rpx 24rpx;
border-radius: 20rpx;
position: relative; position: relative;
padding: 14rpx 20rpx;
max-width: 100%;
word-wrap: break-word;
word-break: break-all;
}
.wechat-other-bubble {
background-color: #ffffff;
border-radius: 18rpx;
}
.wechat-my-bubble {
background-color: #07c160;
border-radius: 18rpx;
}
/* 对方消息气泡三角形 */
.wechat-other-bubble::before {
content: '';
position: absolute;
left: -10rpx;
bottom: 18rpx;
width: 0;
height: 0;
border-style: solid;
border-width: 10rpx 10rpx 10rpx 0;
border-color: transparent #ffffff transparent transparent;
}
/* 我的消息气泡三角形 */
.wechat-my-bubble::before {
content: '';
position: absolute;
right: -10rpx;
bottom: 18rpx;
width: 0;
height: 0;
border-style: solid;
border-width: 10rpx 0 10rpx 10rpx;
border-color: transparent transparent transparent #07c160;
} }
/* 消息文本 */ .message-item.other .message-bubble {
.wechat-message-text { background-color: #fff;
font-size: 32rpx; border-bottom-left-radius: 8rpx;
line-height: 44rpx;
color: #303133;
}
.wechat-my-text {
color: #ffffff;
}
/* 图片消息样式 - 使用微信插件风格 */
.wechat-message-image {
width: 280rpx;
height: 280rpx;
max-width: 75%;
max-height: 500rpx;
border-radius: 16rpx;
margin: 8rpx 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
background-color: #f5f5f5;
} }
/* 对方图片消息样式 - 左侧缩进 */ .message-item.me .message-bubble {
.wechat-other-image { background-color: #92E3A9;
margin-left: 16rpx; border-bottom-right-radius: 8rpx;
align-self: flex-start;
} }
/* 我的图片消息样式 - 右侧缩进 */ .message-content {
.wechat-my-image { display: block;
margin-right: 16rpx; font-size: 28rpx;
align-self: flex-end; color: #333;
line-height: 1.5;
word-wrap: break-word;
} }
/* 消息项调整,确保图片和文本消息对齐 */ .message-item.me .message-content {
.wechat-message-item { color: #000;
align-items: flex-start;
padding: 4rpx 0;
} }
/* 消息时间 */ .message-time {
.wechat-message-time { display: block;
font-size: 22rpx; font-size: 22rpx;
color: #909399; color: #999;
padding: 0 4rpx;
}
.wechat-my-time {
text-align: right; text-align: right;
margin-top: 8rpx;
} }
/* 输入区域 - 微信风格 */ .chat-input-area {
.wechat-input-area { background-color: #fff;
background-color: #f5f5f5;
border-top: 1rpx solid #d8d8d8;
padding: 12rpx 20rpx;
box-sizing: border-box;
}
/* 输入工具栏 */
.wechat-input-toolbar {
display: flex;
align-items: center;
gap: 16rpx;
}
/* 底部安全区域适配 */
@media screen and (min-height: 812px) {
.wechat-input-area {
padding-bottom: calc(12rpx + env(safe-area-inset-bottom, 0rpx));
}
}
/* 左侧按钮 */
.wechat-input-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10rpx; padding: 16rpx 24rpx;
} border-top: 1rpx solid #e8e8e8;
gap: 20rpx;
/* 右侧按钮 */
.wechat-input-right {
display: flex;
align-items: center;
gap: 10rpx;
}
/* 表情按钮 */
.wechat-emoji-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
}
/* 语音按钮 */
.wechat-voice-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
} }
/* 更多按钮 */ .message-input {
.wechat-more-btn { flex: 1;
width: 64rpx; background-color: #f5f5f5;
height: 64rpx; border-radius: 28rpx;
display: flex; padding: 16rpx 24rpx;
align-items: center; font-size: 28rpx;
justify-content: center; color: #333;
font-size: 44rpx; min-height: 56rpx;
color: #606266; max-height: 200rpx;
overflow-y: auto;
} }
/* 发送按钮 */ .send-button {
.wechat-send-btn { background-color: #FF6B81;
background-color: #07c160; color: #fff;
color: #ffffff;
font-size: 28rpx; font-size: 28rpx;
padding: 0 28rpx; font-weight: bold;
height: 64rpx; padding: 16rpx 32rpx;
line-height: 64rpx; border-radius: 28rpx;
border-radius: 32rpx; border: none;
min-width: 120rpx;
text-align: center; text-align: center;
line-height: normal;
height: auto;
} }
/* 发送按钮禁用状态 */ .send-button:hover {
.wechat-send-btn:disabled { background-color: #FF526D;
background-color: #c8c8c8;
color: #ffffff;
cursor: not-allowed;
}
/* 发送按钮点击状态 */
.wechat-send-btn:active:not(:disabled) {
background-color: #06b354;
}
/* 输入框包装器 */
.wechat-input-wrapper {
flex: 1;
background-color: #ffffff;
border: 1rpx solid #d8d8d8;
border-radius: 32rpx;
padding: 0 20rpx;
min-height: 64rpx;
max-height: 180rpx;
display: flex;
align-items: center;
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
}
/* 消息输入框 */
.wechat-message-input {
flex: 1;
font-size: 32rpx;
color: #303133;
min-height: 44rpx;
max-height: 160rpx;
line-height: 44rpx;
padding: 0;
margin: 10rpx 0;
} }

255
pages/chat-detail/system-test.js

@ -1,255 +0,0 @@
// 完整系统测试脚本 - 模拟真实用户消息显示功能
console.log('========= 开始真实用户消息功能的完整系统测试 =========');
// 模拟环境变量和配置
const CONFIG = {
API_BASE_URL: 'http://localhost:3003',
TEST_USER_ID: 'user_1763452685075_rea007flq',
TEST_MANAGER_ID: 22,
TEST_CONVERSATION_ID: '6257d0f1-9cc3-4e1b-836b-048e9b0ac217'
};
// 模拟请求函数
async function mockRequest(url, options = {}) {
console.log(`\n模拟发送请求到: ${url}`);
console.log('请求参数:', options);
// 模拟响应
const mockResponses = {
'/api/chat/history': {
success: true,
messages: [
{
id: 'msg_001',
sender_id: CONFIG.TEST_USER_ID,
receiver_id: CONFIG.TEST_MANAGER_ID,
content: '你好,我是真实用户,这是我的第一条消息',
created_at: Date.now() - 3600000,
content_type: 1
},
{
id: 'msg_002',
sender_id: CONFIG.TEST_USER_ID,
receiver_id: CONFIG.TEST_MANAGER_ID,
content: '这是第二条来自真实用户的消息,应该能正确显示',
created_at: Date.now() - 1800000,
content_type: 1
}
],
total: 2,
hasMore: false
},
'/api/chat/direct_query': {
success: true,
messages: [
{
id: 'direct_msg_001',
sender_id: CONFIG.TEST_USER_ID,
receiver_id: CONFIG.TEST_MANAGER_ID,
content: '这是通过备用查询获取的消息',
created_at: Date.now() - 3600000,
content_type: 1
}
]
}
};
// 延迟模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 500));
// 返回模拟响应
for (const path in mockResponses) {
if (url.includes(path)) {
return { data: mockResponses[path] };
}
}
return { data: { success: false, message: '接口不存在' } };
}
// 模拟fetchHistoryMessages函数
async function simulateFetchHistoryMessages(userId, isRealUserId = false) {
console.log(`\n1. 模拟fetchHistoryMessages调用`);
console.log(` - 用户ID: ${userId}`);
console.log(` - 是否真实用户ID: ${isRealUserId}`);
// 构建请求参数
const requestData = {
userId: CONFIG.TEST_MANAGER_ID,
targetUserId: userId,
conversationId: CONFIG.TEST_CONVERSATION_ID,
pageSize: 50,
lastMessageId: null,
isRealUser: isRealUserId,
include_all_messages: true
};
console.log(` - 构建的请求参数:`, requestData);
// 模拟发送请求
try {
const response = await mockRequest(`${CONFIG.API_BASE_URL}/api/chat/history`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
console.log(` - 请求成功,响应数据:`, response);
return response.data;
} catch (error) {
console.error(` - 请求失败:`, error);
return null;
}
}
// 模拟handleHistoryMessages函数
function simulateHandleHistoryMessages(response, userId) {
console.log(`\n2. 模拟handleHistoryMessages处理`);
if (!response || !response.success) {
console.error(` - 响应无效或请求失败`);
return { success: false, messages: [] };
}
// 处理消息数据
const messages = response.messages || [];
console.log(` - 接收到 ${messages.length} 条消息`);
// 格式化消息
const formattedMessages = messages.map(msg => {
const isSelf = msg.sender_id === CONFIG.TEST_MANAGER_ID.toString();
const isRealUserMessage = msg.sender_id === userId;
return {
id: msg.id,
content: msg.content,
time: new Date(msg.created_at).toLocaleString(),
isSelf,
isRealUserMessage,
senderId: msg.sender_id,
receiverId: msg.receiver_id
};
});
// 按时间排序
formattedMessages.sort((a, b) => {
return new Date(a.time) - new Date(b.time);
});
console.log(` - 格式化后的消息:`);
formattedMessages.forEach(msg => {
const type = msg.isRealUserMessage ? '真实用户消息' : msg.isSelf ? '自己发送' : '其他消息';
console.log(` - [${type}] ${msg.content} (${msg.time})`);
});
// 统计真实用户消息数量
const realUserMessagesCount = formattedMessages.filter(msg => msg.isRealUserMessage).length;
console.log(` - 识别出的真实用户消息数量: ${realUserMessagesCount}`);
return {
success: true,
messages: formattedMessages,
realUserMessagesCount
};
}
// 模拟备用查询方法
async function simulateDirectDatabaseQuery(userId) {
console.log(`\n3. 模拟备用数据库查询方法tryDirectDatabaseQuery`);
const queryData = {
type: 'direct_db_query',
action: 'get_chat_messages',
targetUserId: userId,
conversationId: CONFIG.TEST_CONVERSATION_ID,
userId: CONFIG.TEST_MANAGER_ID,
isManager: true,
limit: 100
};
console.log(` - 构建的查询数据:`, queryData);
try {
const response = await mockRequest(`${CONFIG.API_BASE_URL}/api/chat/direct_query`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(queryData)
});
console.log(` - 备用查询成功,响应数据:`, response);
return response.data;
} catch (error) {
console.error(` - 备用查询失败:`, error);
return null;
}
}
// 主测试流程
async function runSystemTest() {
try {
console.log('\n=== 测试1: 使用真实用户ID模拟正常消息加载 ===');
// 步骤1: 模拟fetchHistoryMessages
const historyResponse = await simulateFetchHistoryMessages(
CONFIG.TEST_USER_ID,
true // 标记为真实用户ID
);
if (!historyResponse) {
console.error('\n❌ 测试失败: 无法获取历史消息');
return;
}
// 步骤2: 模拟handleHistoryMessages
const handleResult = simulateHandleHistoryMessages(
historyResponse,
CONFIG.TEST_USER_ID
);
if (!handleResult.success) {
console.error('\n❌ 测试失败: 处理历史消息失败');
return;
}
// 步骤3: 验证结果
if (handleResult.realUserMessagesCount > 0) {
console.log(`\n✅ 测试成功: 成功识别并处理了 ${handleResult.realUserMessagesCount} 条真实用户消息`);
} else {
console.warn('\n⚠️ 警告: 未识别到真实用户消息,启动备用查询');
// 启动备用查询
const backupResponse = await simulateDirectDatabaseQuery(CONFIG.TEST_USER_ID);
if (backupResponse && backupResponse.success) {
console.log(`\n✅ 备用查询成功: 获取到 ${backupResponse.messages.length} 条消息`);
} else {
console.error('\n❌ 备用查询也失败');
}
}
// 最终验证
console.log('\n=== 系统测试总结 ===');
console.log(`✅ fetchHistoryMessages函数成功识别真实用户ID: ${CONFIG.TEST_USER_ID}`);
console.log(`✅ handleHistoryMessages函数成功处理消息数据`);
console.log(`✅ 系统具备备用查询机制以确保数据可用性`);
console.log(`✅ 所有关键功能模块测试通过`);
console.log('\n========= 系统测试完成 =========');
return {
success: true,
realUserMessagesDisplayed: handleResult.realUserMessagesCount > 0
};
} catch (error) {
console.error('\n❌ 系统测试失败:', error);
return { success: false, error: error.message };
}
}
// 执行测试
runSystemTest().then(result => {
if (result.success) {
console.log('\n🎉 测试结果: 成功!系统可以正确显示真实用户消息。');
} else {
console.log('\n❌ 测试结果: 失败!请检查代码实现。');
}
});

158
pages/chat-detail/test-backup-query.js

@ -1,158 +0,0 @@
/**
* 备用数据库查询方法测试脚本
* 用于验证tryDirectDatabaseQuery函数的功能
*/
console.log('\n=== 备用数据库查询方法测试 ===');
// 模拟测试数据
const mockRealUserData = {
userId: "user_1763452685075_rea007flq",
isRealUserId: true,
isManager: true,
managerId: 22,
conversationId: "test_conversation_123"
};
// 模拟wx.request函数
function mockWxRequest(options) {
console.log('\n模拟wx.request调用:');
console.log('- URL:', options.url);
console.log('- 方法:', options.method);
console.log('- 请求数据:', options.data);
// 模拟成功响应
setTimeout(() => {
const mockSuccessResponse = {
data: {
success: true,
messages: [
{
message_id: "direct_msg_001",
sender_id: mockRealUserData.userId,
receiver_id: "manager_22",
content: "这是通过备用查询获取的消息",
created_at: Date.now() - 3600000,
content_type: 1
},
{
message_id: "direct_msg_002",
sender_id: mockRealUserData.userId,
receiver_id: "manager_22",
content: "这是第二条备用查询消息",
created_at: Date.now() - 3500000,
content_type: 1
}
]
}
};
console.log('模拟成功响应:', mockSuccessResponse);
options.success(mockSuccessResponse);
}, 500);
}
// 模拟tryDirectDatabaseQuery函数
function simulateTryDirectDatabaseQuery(data) {
console.log('\n1. 测试备用查询函数初始化...');
try {
console.log('- 正在准备备用数据库查询...');
console.log('- 目标用户ID:', data.userId);
console.log('- 会话ID:', data.conversationId);
console.log('- 是否真实用户:', data.isRealUserId);
// 构建查询数据
const queryData = {
type: 'direct_db_query',
action: 'get_chat_messages',
targetUserId: data.userId,
conversationId: data.conversationId,
userId: "manager_22",
isManager: data.isManager,
limit: 100,
timestamp: Date.now()
};
console.log('- 构建的查询数据:', queryData);
// 模拟API请求
mockWxRequest({
url: 'https://api.example.com/direct_query',
method: 'POST',
data: queryData,
success: (res) => {
console.log('\n2. 测试备用查询成功处理...');
console.log('- 备用查询成功接收到响应');
if (res.data && res.data.success && res.data.messages) {
console.log(`- 成功获取到 ${res.data.messages.length} 条消息`);
console.log('- 消息详情:', res.data.messages);
// 模拟处理这些消息
console.log('\n3. 测试消息处理流程...');
const messages = res.data.messages;
const realUserMessages = messages.filter(msg =>
msg.sender_id === data.userId ||
(msg.sender_id && msg.sender_id.startsWith('user_') && msg.sender_id.includes('_rea'))
);
console.log(`- 识别出的真实用户消息数量: ${realUserMessages.length}`);
console.log('- 最终测试结果: ✅ 备用查询方法工作正常');
} else {
console.log('- 备用查询没有返回消息');
}
},
fail: (error) => {
console.log('\n2. 测试备用查询失败处理...');
console.log('- 备用查询失败:', error.message);
console.log('- 测试重试逻辑...');
// 模拟重试逻辑
setTimeout(() => {
console.log('- 开始重试常规WebSocket查询...');
console.log('- 重试逻辑测试通过');
}, 1000);
}
});
return true;
} catch (error) {
console.error('\n❌ 备用查询过程中出错:', error.message);
return false;
}
}
// 运行完整测试
function runBackupQueryTest() {
console.log('\n🚀 开始执行备用查询方法测试...');
// 执行模拟的备用查询
const testStarted = simulateTryDirectDatabaseQuery(mockRealUserData);
// 测试启动结果
if (testStarted) {
console.log('\n✅ 备用查询测试已成功启动');
console.log('=== 测试正在进行中... ===');
} else {
console.log('\n❌ 备用查询测试启动失败');
}
// 模拟延迟完成测试
setTimeout(() => {
console.log('\n=== 备用查询方法测试总结 ===');
console.log('✅ 备用查询方法的主要功能测试完成');
console.log('✅ 请求数据构建正确');
console.log('✅ 响应处理逻辑正常');
console.log('✅ 真实用户消息识别功能正常');
console.log('\n备用查询方法可以在常规查询失败时作为有效的备用方案');
}, 2000);
}
// 执行测试
runBackupQueryTest();
// 导出测试函数
module.exports = {
runBackupTest: runBackupQueryTest
};

192
pages/chat-detail/test-real-user.js

@ -1,192 +0,0 @@
/**
* 真实用户消息功能测试脚本
* 用于验证fetchHistoryMessages和handleHistoryMessages函数对真实用户ID的处理
*/
// 模拟测试数据
const mockRealUserData = {
userId: "user_1763452685075_rea007flq",
isRealUserId: true,
isManager: true,
managerId: 22
};
// 模拟消息数据
const mockMessages = [
{
message_id: "msg_001",
sender_id: "user_1763452685075_rea007flq",
receiver_id: "manager_22",
content: "这是一条来自真实用户的消息",
created_at: Date.now() - 3600000,
content_type: 1
},
{
message_id: "msg_002",
sender_id: "manager_22",
receiver_id: "user_1763452685075_rea007flq",
content: "这是客服回复的消息",
created_at: Date.now() - 3500000,
content_type: 1
},
{
message_id: "msg_003",
sender_id: "user_1763452685075_rea007flq",
receiver_id: "manager_22",
content: "这是真实用户的第二条消息",
created_at: Date.now() - 3400000,
content_type: 1
}
];
console.log('\n=== 真实用户消息功能测试 ===');
console.log('测试数据:', mockRealUserData);
console.log('模拟消息数量:', mockMessages.length);
// 模拟fetchHistoryMessages中对真实用户ID的处理
function simulateRealUserIdProcessing(data) {
console.log('\n1. 测试真实用户ID识别...');
// 模拟识别逻辑
const isRealUserId = data.isRealUserId ||
(data.userId &&
data.userId.startsWith('user_') &&
data.userId.includes('_rea'));
console.log('- 识别结果:', isRealUserId ? '✅ 成功识别为真实用户ID' : '❌ 未能识别为真实用户ID');
return isRealUserId;
}
// 模拟构建请求数据
function simulateRequestData(isRealUserId, data) {
console.log('\n2. 测试请求数据构建...');
const requestData = {
type: 'get_messages',
conversationId: "test_conversation",
page: 1,
limit: 100,
userId: "manager_22",
targetUserId: data.userId,
userType: data.isManager ? 'manager' : 'user',
managerId: data.isManager ? data.managerId : undefined,
timestamp: Date.now(),
isRealUser: isRealUserId,
queryByUserId: true
};
// 真实用户特殊处理
if (isRealUserId) {
requestData.real_user_id = data.userId;
}
console.log('- 构建的请求数据:');
console.log(' - 是否包含isRealUser标志:', requestData.isRealUser !== undefined);
console.log(' - 是否包含real_user_id:', requestData.real_user_id !== undefined);
console.log(' - 是否包含queryByUserId:', requestData.queryByUserId !== undefined);
return requestData;
}
// 模拟消息格式化和处理
function simulateMessageProcessing(messages, userId) {
console.log('\n3. 测试消息格式化和处理...');
// 模拟格式化逻辑
const formattedMessages = messages.map(msg => {
const senderId = msg.sender_id || msg.senderId || '';
const isRealUserMessage = senderId === userId ||
(senderId && senderId.startsWith('user_') && senderId.includes('_rea'));
return {
id: msg.message_id,
content: msg.content,
senderId: senderId,
receiverId: msg.receiver_id || msg.receiverId || '',
isRealUserMessage: isRealUserMessage,
time: new Date(msg.created_at).toLocaleString(),
timestamp: msg.created_at
};
});
// 模拟排序
formattedMessages.sort((a, b) => a.timestamp - b.timestamp);
// 统计真实用户消息
const realUserMessages = formattedMessages.filter(msg => msg.isRealUserMessage);
console.log('- 格式化后的消息总数:', formattedMessages.length);
console.log('- 识别出的真实用户消息数量:', realUserMessages.length);
console.log('- 真实用户消息详情:');
realUserMessages.forEach(msg => {
console.log(` - 消息ID: ${msg.id}, 内容: ${msg.content.substring(0, 20)}...`);
});
return {
allMessages: formattedMessages,
realUserMessages: realUserMessages
};
}
// 运行完整测试
function runFullTest() {
try {
console.log('\n🚀 开始执行完整测试流程...');
// 1. 测试真实用户ID识别
const isRealUserId = simulateRealUserIdProcessing(mockRealUserData);
// 2. 测试请求数据构建
const requestData = simulateRequestData(isRealUserId, mockRealUserData);
// 3. 测试消息格式化和处理
const processedData = simulateMessageProcessing(mockMessages, mockRealUserData.userId);
// 4. 验证结果
console.log('\n4. 验证最终结果...');
let allTestsPassed = true;
// 验证真实用户ID识别
if (!isRealUserId) {
console.log('❌ 测试失败: 未能正确识别真实用户ID');
allTestsPassed = false;
}
// 验证请求数据包含必要字段
if (!requestData.isRealUser || !requestData.real_user_id) {
console.log('❌ 测试失败: 请求数据缺少必要的真实用户字段');
allTestsPassed = false;
}
// 验证真实用户消息识别
if (processedData.realUserMessages.length !== 2) {
console.log(`❌ 测试失败: 预期识别2条真实用户消息,实际识别${processedData.realUserMessages.length}`);
allTestsPassed = false;
}
// 输出最终结果
console.log('\n=== 测试结果汇总 ===');
if (allTestsPassed) {
console.log('✅ 所有测试通过!真实用户消息功能正常工作');
} else {
console.log('❌ 部分测试失败,请检查代码实现');
}
return allTestsPassed;
} catch (error) {
console.error('\n❌ 测试执行过程中出错:', error.message);
return false;
}
}
// 执行测试
const testResult = runFullTest();
console.log('\n=== 测试完成 ===');
// 导出测试函数供其他模块使用
module.exports = {
runTest: runFullTest,
mockRealUserData,
mockMessages
};

954
pages/chat/index.js

@ -1,953 +1,49 @@
// pages/chat/index.js // 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({ Page({
/**
* 页面的初始数据
*/
data: { data: {
messages: [], // 初始为空数组,后续通过数据获取和WebSocket更新 chatList: []
activeTab: 'all',
webSocketUrl: '' // WebSocket服务器地址
},
// WebSocket消息处理函数
handleWebSocketMessage: function(message) {
console.log('聊天列表页面接收到消息:', message);
// 判断是否为新消息 - 支持'new_message'类型(服务器实际发送的类型)
if (message.type === 'new_message') {
const newMessage = message.payload;
this.updateMessageList(newMessage);
} else if (message.type === 'chat_message') {
// 保留原有的'chat_message'类型支持,兼容不同的消息格式
const newMessage = message.data;
this.updateMessageList(newMessage);
}
},
// 更新消息列表
updateMessageList: function(newMessage) {
console.log('更新消息列表:', newMessage);
const messages = [...this.data.messages];
// 获取当前用户信息
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();
const messageDate = new Date(newMessage.createdAt || newMessage.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
// 对于客服端,总是显示用户ID
let displayUserId;
if (userType === 'manager') {
displayUserId = senderId; // 客服端显示发送者(用户)ID
} else {
// 用户端根据消息方向确定显示ID
displayUserId = senderId === currentUserId ? 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: messageContent,
time: displayTime,
isRead: false,
// 确保有会话ID
conversationId: conversationId || messages[existingIndex].conversationId
};
// 将更新的消息移到列表顶部
const [updatedMessage] = messages.splice(existingIndex, 1);
messages.unshift(updatedMessage);
} else {
// 新会话消息,添加到列表顶部
messages.unshift({
id: uniqueId,
name: displayName,
avatar: displayName.charAt(0),
content: messageContent,
time: displayTime,
isRead: false,
unreadCount: 1,
conversationId: conversationId
});
}
// 使用setData更新视图
this.setData({ messages });
// 触发消息提示振动(可选)
wx.vibrateShort();
// 更新TabBar未读消息数(如果需要)
this.updateTabBarBadge();
},
// 更新TabBar未读消息提示 - 使用自定义TabBar兼容方式
updateTabBarBadge: function() {
console.log('更新TabBar未读提示,当前消息数:', this.data.messages.length);
// 检查是否有未读消息
const hasUnread = this.data.messages.some(msg => !msg.isRead);
console.log('是否有未读消息:', hasUnread);
// 对于自定义TabBar,使用全局状态来管理未读标记
const app = getApp();
if (app && app.globalData) {
app.globalData.tabBarBadge = {
chat: hasUnread ? ' ' : ''
};
console.log('已更新全局TabBar未读标记状态:', hasUnread ? '显示' : '隐藏');
// 尝试通过getTabBar方法通知自定义TabBar更新
try {
const tabBar = this.getTabBar();
if (tabBar) {
tabBar.setData({
selected: 'buyer', // 假设聊天页是buyer tab
badge: hasUnread ? ' ' : ''
});
}
} catch (e) {
console.log('TabBar更新失败,将在下一次页面显示时自动更新');
}
}
}, },
// 清理所有未读消息状态 onLoad: function (options) {
clearAllUnreadStatus: function() { this.loadChatList();
console.log('清理所有未读消息状态');
try {
// 1. 更新本地消息列表中的未读状态
const updatedMessages = this.data.messages.map(msg => ({
...msg,
isRead: true
}));
this.setData({
messages: updatedMessages
});
// 2. 对于自定义TabBar,使用全局状态来管理未读标记
const app = getApp();
if (app && app.globalData) {
app.globalData.tabBarBadge = {
chat: ''
};
console.log('已清理全局TabBar未读标记');
// 尝试通过getTabBar方法通知自定义TabBar更新
try {
const tabBar = this.getTabBar();
if (tabBar) {
tabBar.setData({
selected: 'buyer', // 假设聊天页是buyer tab
badge: ''
});
}
} catch (e) {
console.log('TabBar更新失败,将在下一次页面显示时自动更新');
}
}
// 3. 显示成功提示
wx.showToast({
title: '已清除所有未读提示',
icon: 'success',
duration: 2000
});
} catch (error) {
console.error('清理未读状态失败:', error);
wx.showToast({
title: '清理失败',
icon: 'none',
duration: 2000
});
}
},
// 初始化WebSocket连接
initWebSocket: function() {
try {
const app = getApp();
// 使用正确的WebSocket服务器地址 - 开发环境通常是ws://localhost:3003
// 动态构建WebSocket URL,基于全局配置或环境
let wsProtocol = 'ws://';
let wsHost = app.globalData.webSocketUrl || 'localhost:3003';
let wsUrl;
// 如果wsHost已经包含协议,直接使用
if (wsHost.startsWith('ws://') || wsHost.startsWith('wss://')) {
wsUrl = wsHost;
} else {
// 否则添加协议前缀
wsUrl = `${wsProtocol}${wsHost}`;
}
this.setData({ webSocketUrl: wsUrl });
console.log('WebSocket连接初始化,使用地址:', wsUrl);
// 连接WebSocket
socketManager.connect(wsUrl);
// 添加消息监听
socketManager.on('message', this.handleWebSocketMessage.bind(this));
// 添加状态监听,以便调试
socketManager.on('status', (status) => {
console.log('WebSocket状态更新:', status);
});
console.log('聊天列表页面WebSocket已初始化');
} catch (error) {
console.error('初始化WebSocket失败:', error);
wx.showToast({
title: 'WebSocket初始化失败',
icon: 'none'
});
}
},
// 清理WebSocket连接
cleanupWebSocket: function() {
socketManager.off('message', this.handleWebSocketMessage);
},
// 加载聊天列表数据 - 优化版本
loadChatList: function() {
wx.showLoading({ title: '加载中' });
try {
// 从服务器获取真实的聊天列表数据
const app = getApp();
const token = app.globalData.token || wx.getStorageSync('token');
// 使用正确的API配置,兼容开发环境
const baseUrl = app.globalData.baseUrl || 'http://localhost:3003';
const currentUserId = app.globalData.userInfo?.userId || wx.getStorageSync('userId') || 'unknown';
console.log('使用API地址:', baseUrl);
console.log('当前用户ID:', currentUserId);
// 根据用户类型选择正确的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}${apiPath}`,
method: 'GET',
header: {
'Authorization': token ? `Bearer ${token}` : '',
'content-type': 'application/json'
},
success: (res) => {
console.log('获取聊天列表成功:', res.data);
// 处理不同的API响应格式
let chatData = [];
// 更灵活的响应格式处理
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)) {
// 如果直接返回数组
chatData = res.data;
} else if (res.data) {
// 如果返回的是对象但不是标准格式,尝试直接使用
chatData = [res.data];
}
// 保存原始数据到日志,便于调试
console.log('处理前的聊天数据:', chatData);
if (Array.isArray(chatData) && chatData.length > 0) {
// 格式化聊天列表数据
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 {
console.warn(`发现重复的会话ID: ${uniqueId},已过滤`);
}
});
// 按时间和未读状态排序
uniqueMessages.sort((a, b) => {
// 未读消息优先
if (a.isRead !== b.isRead) {
return a.isRead ? 1 : -1;
}
// 其他按时间排序
return 0;
});
// 保存到本地存储,便于调试和离线访问
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('暂无聊天消息,但数据在数据库中存在,可能是API请求路径或参数问题');
console.log('API请求详情:', {
baseUrl,
apiPath,
currentUserId,
userType,
managerId
});
// 即使API返回空数组,也要显示空状态,而不是白屏
this.setData({
messages: [],
noChats: true
});
}
}, },
fail: (err) => {
console.error('网络请求失败:', err);
wx.showToast({
title: '获取消息失败,重试中...',
icon: 'none',
duration: 3000
});
// 尝试使用本地缓存数据 loadChatList: function () {
const cachedList = wx.getStorageSync('lastChatList') || []; // 模拟加载消息列表
if (cachedList.length > 0) { const chatList = [
console.log('使用本地缓存数据:', cachedList.length);
this.setData({ messages: cachedList });
this.updateTabBarBadge();
} else {
// 缓存也没有,使用模拟数据
console.log('使用模拟数据');
const mockMessages = [
{ {
id: '1', id: 1,
name: '系统消息', name: '系统消息',
avatar: '', avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
content: '欢迎使用聊天功能', content: '欢迎使用消息中心功能',
time: '刚刚', time: '刚刚',
isRead: false, unread: true
unreadCount: 0,
conversationId: 'conv_1' // 添加conversationId,确保导航正常
}
];
this.setData({ messages: mockMessages });
}
// 确保设置了noChats状态,避免白屏
this.setData({ noChats: this.data.messages.length === 0 });
}, },
complete: () => { {
wx.hideLoading(); id: 2,
// 确保设置了noChats状态,避免白屏 name: '客服小王',
this.setData({ noChats: this.data.messages.length === 0 }); avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
} content: '您好,有什么可以帮助您的吗?',
}); time: '10分钟前',
} catch (error) { unread: false
console.error('加载聊天列表异常:', error);
wx.hideLoading();
wx.showToast({ title: '加载异常', icon: 'none' });
} }
}, ];
// 返回上一页
onBack: function() {
wx.navigateBack({
delta: 1
});
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log('聊天列表页面onLoad,准备加载数据');
try { this.setData({
// 页面加载时执行身份验证 chatList: chatList
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);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
// 设置导航栏右侧按钮的点击事件
wx.showNavigationBarLoading();
}, },
// 导航栏左侧按钮点击事件 onChatItemTap: function (e) {
onNavigationBarButtonTap(e) { // 跳转到聊天详情页
if (e.type === 'left') {
this.onBack();
} else if (e.type === 'right') {
// 处理管理按钮点击
wx.showToast({ wx.showToast({
title: '管理功能待开发', title: '聊天功能开发中',
icon: 'none' icon: 'none'
}); });
}
}, },
/** onPullDownRefresh: function () {
* 生命周期函数--监听页面显示
*/
onShow() {
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(); this.loadChatList();
}
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
// 页面隐藏时清理WebSocket连接
this.cleanupWebSocket();
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
// 页面卸载时清理WebSocket连接
this.cleanupWebSocket();
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
console.log('下拉刷新触发');
// 重新加载聊天列表数据
this.loadChatList();
// 停止下拉刷新动画
wx.stopPullDownRefresh(); wx.stopPullDownRefresh();
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
// 页面分享配置
return {
title: '聊天列表',
path: '/pages/chat/index'
}
},
// 跳转到对话详情页面
navigateToChatDetail: function(e) {
// 关键修复:获取userId和userName
const userId = e.currentTarget.dataset.userId;
const userName = e.currentTarget.dataset.userName;
const conversationId = e.currentTarget.dataset.conversationId;
// 关键修复:获取原始数据
const message = this.data.messages.find(item =>
item.id === userId || item.conversationId === userId
);
// 关键修复:总是从原始数据中获取真实的用户ID
let realUserId = userId;
let isManager = false;
// 获取当前用户信息,判断自己是否是客服
const app = getApp();
const currentUserType = app.globalData.userInfo?.userType || wx.getStorageSync('userType');
const currentManagerId = app.globalData.userInfo?.managerId || wx.getStorageSync('managerId');
const isCurrentManager = currentUserType === 'manager' && currentManagerId;
if (message && message._raw) {
// 从原始数据中获取真实的用户ID或客服ID
if (isCurrentManager) {
// 客服点击会话,应该获取用户ID
if (message._raw.user_id) {
realUserId = message._raw.user_id;
console.log('客服模式:从原始数据中提取真实用户ID:', realUserId);
} else if (message._raw.manager_id) {
realUserId = message._raw.manager_id;
isManager = true;
console.log('客服模式:从原始数据中提取真实客服ID:', realUserId);
}
} else {
// 用户点击会话,应该获取客服ID
if (message._raw.manager_id) {
realUserId = message._raw.manager_id;
isManager = true;
console.log('用户模式:从原始数据中提取真实客服ID:', realUserId);
} else if (message._raw.user_id) {
realUserId = message._raw.user_id;
console.log('用户模式:从原始数据中提取真实用户ID:', realUserId);
}
}
} else {
// 直接检查userId格式
if (/^\d+$/.test(userId)) {
// 如果userId是纯数字,很可能是客服ID
isManager = true;
console.log('检测到纯数字客服ID:', userId);
} else if (userId.startsWith('user_')) {
// 如果是用户ID格式,直接使用
realUserId = userId;
console.log('检测到用户ID格式,直接使用:', realUserId);
} else if (userId.includes('-')) {
// 会话ID格式,尝试从原始数据中获取
console.warn('无法从会话ID中提取真实用户ID,使用原始会话ID:', userId);
}
}
console.log('导航到聊天详情:', {
originalUserId: userId,
realUserId: realUserId,
userName: userName,
conversationId: conversationId,
isManager: isManager,
currentUserType: currentUserType,
isCurrentManager: isCurrentManager
});
// 执行身份验证
AuthManager.authenticate(() => {
// 将该聊天标记为已读
const messages = [...this.data.messages];
// 查找会话时同时考虑id和conversationId
const messageIndex = messages.findIndex(item =>
item.id === userId || item.conversationId === conversationId
);
if (messageIndex >= 0) {
messages[messageIndex].isRead = true;
messages[messageIndex].unreadCount = 0;
this.setData({ messages });
// 更新TabBar未读消息数
this.updateTabBarBadge();
// 通知服务器已读状态(使用userId)
this.markAsRead(userId);
}
// 关键修复:在URL中同时传递realUserId和conversationId,确保正确打开会话
wx.navigateTo({
url: `/pages/chat-detail/index?userId=${realUserId}&userName=${encodeURIComponent(userName)}&conversationId=${conversationId}&isManager=${isManager}`
});
});
},
// 通知服务器消息已读
markAsRead: function(userId) {
// 只执行本地标记已读,不再调用服务器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 });
} }
}) });

5
pages/chat/index.json

@ -1,6 +1,3 @@
{ {
"navigationBarTitleText": "消息", "navigationBarTitleText": "消息中心"
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"usingComponents": {}
} }

78
pages/chat/index.wxml

@ -1,76 +1,18 @@
<view class="container"> <view class="container">
<!-- 头部导航栏 - 现代填充风格 --> <view class="chat-header">
<view class="header-full"> <text class="chat-title">消息中心</text>
<!-- 左侧返回按钮 -->
<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>
<!-- 右侧操作按钮区域 --> <view class="chat-list">
<view class="header-right"> <view class="chat-item" bindtap="onChatItemTap">
<view class="header-btn action-btn" bindtap="onNavigationBarButtonTap" data-type="search"> <view class="avatar">
<text class="icon-search">🔍</text> <image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
</view> </view>
<view class="header-btn action-btn" bindtap="onNavigationBarButtonTap" data-type="more"> <view class="chat-info">
<text class="icon-more">···</text> <view class="chat-name">系统消息</view>
<view class="chat-content">欢迎使用消息中心功能</view>
<view class="chat-time">刚刚</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 微信风格的消息类型切换 -->
<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>
<!-- 消息列表 - 微信风格 -->
<scroll-view class="message-list-wechat" scroll-y="true">
<!-- 提示消息 -->
<view class="message-tips" wx:if="{{messages.length > 0 && showOldMessageTips}}">
以下为3天前的消息,提示将弱化
</view>
<!-- 消息项 -->
<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>
<!-- 消息内容区域 -->
<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 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>
</scroll-view>
</view> </view>

314
pages/chat/index.wxss

@ -1,312 +1,72 @@
/* 微信风格聊天页面样式 */
.container { .container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
position: relative; min-height: 100vh;
overflow: hidden;
}
/* 头部导航栏 - 现代填充风格 */
.header-full {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 92rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #e0e0e0;
position: relative;
z-index: 10;
}
.header-left,
.header-right {
width: 100rpx;
display: flex;
align-items: center;
}
.header-left {
justify-content: flex-start;
padding-left: 20rpx;
} }
.header-right { .chat-header {
justify-content: flex-end; background-color: #fff;
padding-right: 20rpx; padding: 20rpx;
} border-bottom: 1rpx solid #e8e8e8;
.header-center {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 70rpx;
height: 70rpx;
border-radius: 35rpx;
transition: background-color 0.2s;
} }
.header-btn:active { .chat-title {
background-color: #f0f0f0; font-size: 36rpx;
}
.back-btn .icon-back {
font-size: 44rpx;
color: #333;
font-weight: bold; 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; 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;
}
.tab-item.active {
color: #07c160;
font-weight: 500;
} }
.tab-item.active::after { .chat-list {
content: ''; padding: 20rpx;
position: absolute;
bottom: 0;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background-color: #07c160;
} }
.clear-unread { .chat-item {
font-size: 28rpx; background-color: #fff;
color: #1677ff;
padding: 0 10rpx;
}
/* 消息列表 - 微信风格 */
.message-list-wechat {
flex: 1;
background-color: #f5f5f5;
overflow: auto;
}
/* 提示消息 */
.message-tips {
text-align: center;
padding: 20rpx 0;
margin-bottom: 10rpx;
color: #999999;
font-size: 26rpx;
background-color: #f5f5f5;
}
/* 消息项 - 微信风格 */
.message-item-wechat {
display: flex;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
position: relative;
}
/* 头像容器 */
.avatar-container-wechat {
position: relative;
margin-right: 24rpx;
}
/* 头像 - 微信风格 */
.avatar-wechat {
width: 100rpx;
height: 100rpx;
border-radius: 12rpx; border-radius: 12rpx;
background-color: #07c160; padding: 20rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 40rpx;
font-weight: 500;
}
/* 未读红点 */
.unread-dot {
position: absolute;
top: -6rpx;
right: -6rpx;
width: 28rpx;
height: 28rpx;
border-radius: 14rpx;
background-color: #ff3b30;
border: 3rpx solid #ffffff;
}
/* 未读数字角标 - 微信风格 */
.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; display: flex;
align-items: center; align-items: center;
justify-content: center; margin-bottom: 20rpx;
line-height: 1; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 消息内容区域 */
.message-content-wechat {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 100rpx;
} }
/* 消息头部:名称和时间 */ .avatar {
.message-header-wechat { width: 80rpx;
display: flex; height: 80rpx;
justify-content: space-between; border-radius: 50%;
align-items: center; margin-right: 20rpx;
margin-bottom: 10rpx;
}
.name-wechat {
font-size: 32rpx;
font-weight: 500;
color: #000000;
flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.time-wechat { .avatar image {
font-size: 24rpx; width: 100%;
color: #999999; height: 100%;
margin-left: 15rpx;
}
/* 消息文本 */
.message-text-wechat {
display: flex;
align-items: center;
font-size: 28rpx;
color: #999999;
line-height: 40rpx;
overflow: hidden;
}
.unread-dot-small {
color: #ff3b30;
font-size: 20rpx;
margin-right: 8rpx;
} }
.message-content-text { .chat-info {
flex: 1; flex: 1;
overflow: hidden; position: relative;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 空状态提示 - 微信风格 */
.empty-state-wechat {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 150rpx 0;
color: #999999;
}
.empty-icon-wechat {
font-size: 120rpx;
margin-bottom: 30rpx;
} }
.empty-text-wechat { .chat-name {
font-size: 32rpx; font-size: 32rpx;
color: #666666; font-weight: bold;
margin-bottom: 15rpx; color: #333;
margin-bottom: 8rpx;
} }
.empty-subtext { .chat-content {
font-size: 28rpx; font-size: 28rpx;
color: #999999; color: #666;
} margin-bottom: 8rpx;
/* 滚动条样式优化 */
.message-list-wechat::-webkit-scrollbar {
width: 6rpx;
}
.message-list-wechat::-webkit-scrollbar-track {
background: transparent;
} }
.message-list-wechat::-webkit-scrollbar-thumb { .chat-time {
background: #cccccc; font-size: 24rpx;
border-radius: 3rpx; color: #999;
} position: absolute;
top: 0;
.message-list-wechat::-webkit-scrollbar-thumb:hover { right: 0;
background: #999999;
} }

165
pages/customer-service/detail/index.js

@ -1,165 +0,0 @@
// pages/customer-service/detail/index.js
Page({
data: {
customerData: null,
customerServices: [
{
id: 1,
managerId: 'PM001',
managercompany: '鸡蛋贸易有限公司',
managerdepartment: '采购部',
organization: '鸡蛋采购组',
projectName: '高级采购经理',
name: '张三',
alias: '张经理',
phoneNumber: '13800138001',
avatarUrl: '',
score: 999,
isOnline: true,
responsibleArea: '华北区鸡蛋采购',
experience: '1-3年',
serviceCount: 200,
purchaseCount: 15000,
profitIncreaseRate: 15,
profitFarmCount: 120,
skills: ['渠道拓展', '供应商维护', '质量把控', '精准把控市场价格']
},
{
id: 2,
managerId: 'PM002',
managercompany: '鸡蛋贸易有限公司',
managerdepartment: '采购部',
organization: '全国采购组',
projectName: '采购经理',
name: '李四',
alias: '李经理',
phoneNumber: '13900139002',
avatarUrl: '',
score: 998,
isOnline: true,
responsibleArea: '全国鸡蛋采购',
experience: '2-3年',
serviceCount: 200,
purchaseCount: 20000,
profitIncreaseRate: 18,
profitFarmCount: 150,
skills: ['精准把控市场价格', '渠道拓展', '供应商维护']
},
{
id: 3,
managerId: 'PM003',
managercompany: '鸡蛋贸易有限公司',
managerdepartment: '采购部',
organization: '华东采购组',
projectName: '采购专员',
name: '王五',
alias: '王专员',
phoneNumber: '13700137003',
avatarUrl: '',
score: 997,
isOnline: false,
responsibleArea: '华东区鸡蛋采购',
experience: '1-2年',
serviceCount: 150,
purchaseCount: 12000,
profitIncreaseRate: 12,
profitFarmCount: 80,
skills: ['质量把控', '供应商维护']
},
{
id: 4,
managerId: 'PM004',
managercompany: '鸡蛋贸易有限公司',
managerdepartment: '采购部',
organization: '华南采购组',
projectName: '高级采购经理',
name: '赵六',
alias: '赵经理',
phoneNumber: '13600136004',
avatarUrl: '',
score: 996,
isOnline: true,
responsibleArea: '华南区鸡蛋采购',
experience: '3-5年',
serviceCount: 250,
purchaseCount: 25000,
profitIncreaseRate: 20,
profitFarmCount: 180,
skills: ['精准把控市场价格', '渠道拓展', '质量把控', '供应商维护']
}
]
},
onLoad: function (options) {
// 获取传递过来的客服ID
const { id } = options;
// 根据ID查找客服数据
const customerData = this.data.customerServices.find(item => item.id === parseInt(id));
if (customerData) {
this.setData({
customerData: customerData
});
// 设置导航栏标题
wx.setNavigationBarTitle({
title: `${customerData.alias} - 客服详情`,
});
} else {
// 如果找不到对应ID的客服,显示错误提示
wx.showToast({
title: '未找到客服信息',
icon: 'none'
});
}
},
onShow() {
// 更新自定义tabBar状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: -1 // 不选中任何tab
});
}
},
// 返回上一页
onBack: function () {
wx.navigateBack();
},
// 在线沟通
onChat: function () {
const { customerData } = this.data;
if (customerData) {
wx.navigateTo({
url: `/pages/chat/index?id=${customerData.id}&name=${customerData.alias}&phone=${customerData.phoneNumber}`
});
}
},
// 拨打电话
onCall: function () {
const { customerData } = this.data;
if (customerData && customerData.phoneNumber) {
wx.makePhoneCall({
phoneNumber: customerData.phoneNumber,
success: function () {
console.log('拨打电话成功');
},
fail: function () {
console.log('拨打电话失败');
}
});
}
},
// 分享功能
onShareAppMessage: function () {
const { customerData } = this.data;
return {
title: `${customerData?.alias || '优秀客服'} - 鸡蛋贸易平台`,
path: `/pages/customer-service/detail/index?id=${customerData?.id}`,
imageUrl: ''
};
}
});

10
pages/customer-service/detail/index.json

@ -1,7 +1,9 @@
{ {
"navigationBarTitleText": "客服详情", "navigationBarTitleText": "客服详情",
"navigationBarBackgroundColor": "#ffffff", "navigationStyle": "custom",
"navigationBarTextStyle": "black", "usingComponents": {
"backgroundColor": "#f8f8f8", "custom-header": "../../components/custom-header/custom-header",
"usingComponents": {} "service-card": "../../components/service-card/service-card",
"faq-item": "../../components/faq-item/faq-item"
}
} }

116
pages/customer-service/detail/index.wxml

@ -1,93 +1,65 @@
<!-- pages/customer-service/detail/index.wxml -->
<view class="container"> <view class="container">
<!-- 顶部导航栏 --> <view class="service-detail-header">
<view class="nav-bar"> <view class="header-back" bindtap="onBack">
<view class="nav-left" bindtap="onBack"> <text>←</text>
<text class="back-icon">返回</text>
</view>
<view class="nav-title">客服详情</view>
<view class="nav-right">
<text class="share-icon">📤</text>
</view> </view>
<text class="header-title">客服详情</text>
<view class="header-placeholder"></view>
</view> </view>
<!-- 客服基本信息 --> <view class="service-detail-content">
<view class="info-section"> <view class="service-info-section">
<view class="header-info"> <view class="service-avatar">
<view class="avatar-container"> <text>💁</text>
<image class="avatar" src="{{customerData.avatarUrl || '/images/default-avatar.png'}}" mode="aspectFill" />
<view wx:if="{{customerData.isOnline}}" class="online-indicator-large">在线</view>
<view wx:else class="offline-indicator-large">离线</view>
</view>
<view class="header-details">
<view class="name-score-row">
<text class="name-large">{{customerData.alias}}</text>
<text class="score-large">{{customerData.score}} 鸡蛋分</text>
</view>
<text class="position">{{customerData.projectName}}</text>
<text class="company-large">{{customerData.managercompany}}</text>
</view> </view>
<view class="service-basic-info">
<text class="service-name">客服小王</text>
<text class="service-status">在线</text>
</view> </view>
</view> </view>
<!-- 核心信息卡片 --> <view class="service-details">
<view class="core-info-section"> <view class="detail-item">
<view class="info-card"> <text class="detail-label">服务类型</text>
<view class="info-item"> <text class="detail-value">在线客服</text>
<text class="info-label">负责区域</text>
<text class="info-value">{{customerData.responsibleArea}}</text>
</view> </view>
<view class="info-item"> <view class="detail-item">
<text class="info-label">联系电话</text> <text class="detail-label">服务时间</text>
<text class="info-value phone-number" bindtap="onCall">{{customerData.phoneNumber}}</text> <text class="detail-value">24小时在线</text>
</view>
<view class="info-item">
<text class="info-label">工作经验</text>
<text class="info-value">服务平台{{customerData.experience}}</text>
</view>
<view class="info-item">
<text class="info-label">服务规模</text>
<text class="info-value">服务{{customerData.serviceCount}}家鸡场</text>
</view> </view>
<view class="detail-item">
<text class="detail-label">专业领域</text>
<text class="detail-value">产品咨询、订单处理、售后支持</text>
</view> </view>
</view> </view>
<!-- 专业技能标签 --> <view class="contact-section">
<view class="skills-section"> <text class="section-title">联系方式</text>
<view class="section-title">专业技能</view> <button class="contact-button" bindtap="startChat">
<view class="skills-container"> <text>💬 开始聊天</text>
<view wx:for="{{customerData.skills}}" wx:key="index" class="skill-tag"> </button>
{{item}} <button class="contact-button phone-button" open-type="makePhoneCall" phone-number="400-123-4567">
</view> <text>📞 电话联系</text>
</view> </button>
<button class="contact-button email-button" bindtap="sendEmail">
<text>📧 发送邮件</text>
</button>
</view> </view>
<!-- 业绩数据 --> <view class="faq-section">
<view class="performance-section"> <text class="section-title">常见问题</text>
<view class="section-title">业绩数据</view> <view class="faq-item" bindtap="onFaqItemTap">
<view class="performance-cards"> <text class="faq-question">如何查询订单状态?</text>
<view class="performance-card"> <text class="faq-arrow">></text>
<view class="performance-number">{{customerData.purchaseCount}}</view>
<view class="performance-label">累计采购鸡蛋(件)</view>
</view>
<view class="performance-card">
<view class="performance-number">{{customerData.profitFarmCount}}</view>
<view class="performance-label">累计服务鸡场(家)</view>
</view>
<view class="performance-card">
<view class="performance-number profit-rate">{{customerData.profitIncreaseRate}}%</view>
<view class="performance-label">平均盈利增长</view>
</view> </view>
<view class="faq-item" bindtap="onFaqItemTap">
<text class="faq-question">如何申请退款?</text>
<text class="faq-arrow">></text>
</view> </view>
<view class="faq-item" bindtap="onFaqItemTap">
<text class="faq-question">物流配送时间是多久?</text>
<text class="faq-arrow">></text>
</view> </view>
<!-- 底部操作按钮 -->
<view class="bottom-actions">
<view class="action-button chat-button" bindtap="onChat">
<text class="button-text">💬 在线沟通</text>
</view>
<view class="action-button call-button" bindtap="onCall">
<text class="button-text">📞 电话联系</text>
</view> </view>
</view> </view>
</view> </view>

321
pages/customer-service/detail/index.wxss

@ -1,317 +1,180 @@
/* pages/customer-service/detail/index.wxss */
.container { .container {
padding-bottom: 100rpx; background-color: #f5f5f5;
background-color: #f8f8f8;
min-height: 100vh; min-height: 100vh;
} }
/* 顶部导航栏 */ .service-detail-header {
.nav-bar { background-color: #fff;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 44rpx 30rpx 20rpx; justify-content: space-between;
background-color: #fff; padding: 16rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #e8e8e8;
position: fixed; position: sticky;
top: 0; top: 0;
left: 0; z-index: 100;
right: 0;
z-index: 1000;
width: 100%;
box-sizing: border-box;
} }
.nav-left { .header-back, .header-placeholder {
width: 80rpx; width: 60rpx;
height: 60rpx;
display: flex; display: flex;
justify-content: flex-start; align-items: center;
} justify-content: center;
.back-icon {
font-size: 32rpx; font-size: 32rpx;
color: #333; font-weight: bold;
font-weight: normal;
} }
.nav-title { .header-title {
font-size: 36rpx; font-size: 32rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
flex: 1;
text-align: center;
} }
.nav-right { .service-detail-content {
display: flex; padding: 20rpx;
align-items: center;
gap: 30rpx;
} }
.share-icon { .service-info-section {
font-size: 32rpx;
color: #333;
}
/* 客服基本信息 */
.info-section {
background-color: #fff; background-color: #fff;
padding: 120rpx 30rpx 30rpx; border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
} box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.header-info {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.avatar-container { .service-avatar {
width: 160rpx; width: 120rpx;
height: 160rpx; height: 120rpx;
margin-right: 30rpx;
position: relative;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #f0f0f0; background-color: #f0f0f0;
border: 4rpx solid #f0f0f0; border-radius: 50%;
} display: flex;
align-items: center;
.online-indicator-large { justify-content: center;
position: absolute; font-size: 64rpx;
bottom: 0; margin-right: 32rpx;
right: 0;
background-color: #52c41a;
color: white;
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 20rpx;
border: 4rpx solid white;
}
.offline-indicator-large {
position: absolute;
bottom: 0;
right: 0;
background-color: #999;
color: white;
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 20rpx;
border: 4rpx solid white;
} }
.header-details { .service-basic-info {
flex: 1; flex: 1;
} }
.name-score-row { .service-name {
display: flex; display: block;
align-items: center; font-size: 36rpx;
margin-bottom: 12rpx;
}
.name-large {
font-size: 40rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-right: 16rpx;
}
.score-large {
font-size: 28rpx;
color: #fff;
background: linear-gradient(135deg, #ffb800, #ff7700);
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.position {
font-size: 32rpx;
color: #666;
margin-bottom: 8rpx; margin-bottom: 8rpx;
} }
.company-large { .service-status {
display: block;
font-size: 28rpx; font-size: 28rpx;
color: #999; color: #07C160;
} }
/* 核心信息卡片 */ .service-details {
.core-info-section {
padding: 0 30rpx;
margin-bottom: 20rpx;
}
.info-card {
background-color: #fff; background-color: #fff;
border-radius: 24rpx; border-radius: 16rpx;
padding: 30rpx; padding: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
} }
.info-item { .detail-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20rpx 0; padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
} }
.info-item:last-child { .detail-item:last-child {
border-bottom: none; border-bottom: none;
} }
.info-label { .detail-label {
font-size: 30rpx; font-size: 28rpx;
color: #666; color: #666;
} }
.info-value { .detail-value {
font-size: 30rpx; font-size: 28rpx;
color: #333; color: #333;
text-align: right; text-align: right;
flex: 1; flex: 1;
margin-left: 30rpx; margin-left: 40rpx;
}
.phone-number {
color: #1890ff;
} }
/* 专业技能标签 */ .contact-section, .faq-section {
.skills-section {
background-color: #fff; background-color: #fff;
padding: 30rpx; border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
} }
.section-title { .section-title {
display: block;
font-size: 32rpx; font-size: 32rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-bottom: 20rpx; margin-bottom: 24rpx;
}
.skills-container {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding-bottom: 10rpx;
}
.skill-tag {
background-color: #f0f9ff;
color: #1890ff;
font-size: 26rpx;
padding: 12rpx 24rpx;
border-radius: 20rpx;
border: 1rpx solid #bae7ff;
transition: all 0.2s ease;
}
.skill-tag:active {
background-color: #bae7ff;
transform: scale(0.98);
}
/* 业绩数据 */
.performance-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 120rpx;
}
.performance-cards {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
.performance-card {
flex: 1;
background-color: #f9f9f9;
padding: 24rpx;
border-radius: 16rpx;
text-align: center;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.performance-card:active {
transform: translateY(2rpx);
background-color: #f0f0f0;
} }
.performance-number { .contact-button {
font-size: 36rpx; display: block;
width: 100%;
background-color: #FF6B81;
color: #fff;
font-size: 32rpx;
font-weight: bold; font-weight: bold;
color: #333; padding: 24rpx;
margin-bottom: 12rpx; border-radius: 12rpx;
border: none;
margin-bottom: 16rpx;
line-height: normal;
height: auto;
} }
.performance-number.profit-rate { .contact-button:last-child {
color: #52c41a; margin-bottom: 0;
} }
.performance-label { .contact-button.phone-button {
font-size: 24rpx; background-color: #07C160;
color: #666;
line-height: 1.4;
} }
/* 底部操作按钮 */ .contact-button.email-button {
.bottom-actions { background-color: #1989fa;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
background-color: #fff;
padding: 20rpx 30rpx;
border-top: 1rpx solid #f0f0f0;
gap: 20rpx;
} }
.action-button { .contact-button:hover {
flex: 1;
padding: 24rpx;
border-radius: 40rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.action-button:active {
transform: scale(0.98);
opacity: 0.9; opacity: 0.9;
} }
.chat-button:active { .faq-item {
background-color: #096dd9; display: flex;
} justify-content: space-between;
align-items: center;
.call-button:active { padding: 24rpx 0;
background-color: #389e0d; border-bottom: 1rpx solid #f0f0f0;
} }
.chat-button { .faq-item:last-child {
background-color: #1890ff; border-bottom: none;
color: white;
} }
.call-button { .faq-question {
background-color: #52c41a; font-size: 28rpx;
color: white; color: #333;
flex: 1;
} }
.button-text { .faq-arrow {
font-size: 30rpx; font-size: 28rpx;
color: #999;
margin-left: 20rpx;
} }

389
pages/customer-service/index.js

@ -1,378 +1,55 @@
// pages/customer-service/index.js // pages/customer-service/index.js
Page({ Page({
// 客服数据,将从后端动态获取
data: { data: {
customerServices: [], serviceList: []
filteredServices: [],
searchKeyword: '',
selectedArea: '全部',
totalCount: 0,
onlineCount: 0
}, },
// 获取客服列表的方法 onLoad: function (options) {
async fetchCustomerServices() { this.loadServiceInfo();
try {
console.log('开始请求客服列表...');
// 导入API工具并使用正确的请求方法
const api = require('../../utils/api');
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: (error) => {
console.error('网络请求失败:', error);
reject(error);
}
});
});
console.log('API响应状态码:', res?.statusCode);
console.log('API响应数据:', res?.data ? JSON.stringify(res.data) : 'undefined');
// 更宽松的响应检查,确保能处理各种有效的响应格式
if (res && res.statusCode === 200 && res.data) {
// 无论success字段是否存在,只要有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()}`, // 确保有id
managerId: item.managerId || '',
managercompany: item.managercompany || '',
managerdepartment: item.managerdepartment || '',
organization: item.organization || '',
projectName: item.projectName || '',
name: item.name || '未知',
alias: item.alias || item.name || '未知',
phoneNumber: item.phoneNumber || '',
avatarUrl: item.avatar || item.avatarUrl || '', // 兼容avatar和avatarUrl
score: Math.floor(Math.random() * 20) + 980, // 随机生成分数
isOnline: !!item.online, // 转换为布尔值
responsibleArea: `${this.getRandomArea()}鸡蛋采购`,
experience: this.getRandomExperience(),
serviceCount: this.getRandomNumber(100, 300),
purchaseCount: this.getRandomNumber(10000, 30000),
profitIncreaseRate: this.getRandomNumber(10, 25),
profitFarmCount: this.getRandomNumber(50, 200),
skills: this.getRandomSkills()
}));
console.log('处理后的数据数量:', processedData.length);
return processedData;
} else {
console.error('响应数据格式错误,不是预期的数组格式:', dataSource);
return [];
}
} else {
console.error('获取客服列表失败,状态码:', res?.statusCode, '响应数据:', res?.data);
return [];
}
} catch (error) {
console.error('请求客服列表出错:', error);
// 网络错误时显示提示
wx.showToast({
title: '网络请求失败,请检查网络连接',
icon: 'none'
});
return [];
}
}, },
// 辅助函数:生成随机区域 loadServiceInfo: function () {
getRandomArea() { // 模拟加载客服信息
const areas = ['华北区', '华东区', '华南区', '全国', '西南区', '西北区', '东北区']; const serviceList = [
return areas[Math.floor(Math.random() * areas.length)]; {
id: 1,
type: 'phone',
name: '电话客服',
desc: '周一至周日 9:00-18:00',
number: '400-123-4567'
}, },
{
// 辅助函数:生成随机工作经验 id: 2,
getRandomExperience() { type: 'online',
const experiences = ['1-2年', '1-3年', '2-3年', '3-5年', '5年以上']; name: '在线客服',
return experiences[Math.floor(Math.random() * experiences.length)]; desc: '24小时在线为您服务'
},
// 辅助函数:生成随机数字
getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}, },
{
// 辅助函数:生成随机技能 id: 3,
getRandomSkills() { type: 'email',
const allSkills = ['渠道拓展', '供应商维护', '质量把控', '精准把控市场价格', '谈判技巧', '库存管理']; name: '邮箱客服',
const skillCount = Math.floor(Math.random() * 3) + 2; // 2-4个技能 desc: 'service@example.com'
const selectedSkills = [];
while (selectedSkills.length < skillCount) {
const skill = allSkills[Math.floor(Math.random() * allSkills.length)];
if (!selectedSkills.includes(skill)) {
selectedSkills.push(skill);
}
} }
];
return selectedSkills;
},
// 设置WebSocket监听客服状态变化
setupWebSocketListener() {
const ws = getApp().globalData.webSocketManager;
const app = getApp();
const userInfo = app.globalData.userInfo || {};
const isManager = userInfo.userType === 'manager' || userInfo.type === 'manager';
if (ws) {
// 监听客服状态更新消息
ws.on('customerServiceStatusUpdate', (data) => {
console.log('收到客服状态更新:', data);
// 更新对应客服的在线状态
const customerServiceList = this.data.customerServices;
const updatedList = customerServiceList.map(item => {
if (item.id === data.id || item.managerId === data.managerId) {
return { ...item, isOnline: data.isOnline };
}
return item;
});
const onlineCount = updatedList.filter(item => item.isOnline).length;
this.setData({ this.setData({
customerServices: updatedList, serviceList: serviceList
onlineCount: onlineCount
}); });
// 重新应用筛选
this.filterServices();
});
// 如果当前用户是客服,立即进行认证
if (isManager && userInfo.userId) {
console.log('客服用户登录,进行WebSocket认证:', userInfo.userId);
// 使用userId作为managerId进行认证,确保与服务器端onlineManagers中的键匹配
ws.authenticate();
}
}
},
// 定期刷新客服列表(每30秒)
startPeriodicRefresh() {
this.refreshTimer = setInterval(() => {
console.log('定期刷新客服列表...');
this.loadCustomerServices();
}, 30000);
}, },
// 停止定期刷新 startOnlineChat: function () {
stopPeriodicRefresh() { // 跳转到在线聊天页面
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
},
// 加载客服列表
async loadCustomerServices() {
console.log('开始加载客服列表...');
// 确保在开始时调用hideLoading,防止重复调用showLoading
try {
wx.hideLoading();
} catch (e) {
console.log('没有正在显示的loading');
}
wx.showLoading({ title: '加载中...' });
try {
const services = await this.fetchCustomerServices();
console.log('获取到的客服数量:', services.length);
// 计算在线数量
const onlineCount = services.filter(item => item.isOnline).length;
console.log('在线客服数量:', onlineCount);
// 更新数据
this.setData({
customerServices: services,
totalCount: services.length,
onlineCount: onlineCount
});
console.log('数据更新成功');
// 应用当前的筛选条件
this.filterServices();
console.log('筛选条件应用完成');
// 如果没有数据,显示提示(确保在hideLoading后显示)
if (services.length === 0) {
setTimeout(() => {
wx.showToast({ wx.showToast({
title: '暂无客服数据', title: '在线客服功能开发中',
icon: 'none', icon: 'none'
duration: 2000
});
}, 100);
}
} catch (error) {
console.error('加载客服列表失败:', error);
// 确保在hideLoading后显示错误提示
setTimeout(() => {
wx.showToast({
title: '加载失败,请重试',
icon: 'none',
duration: 2000
});
}, 100);
} finally {
wx.hideLoading();
}
},
onLoad: function () {
// 初始化WebSocket连接
const app = getApp();
if (!app.globalData.webSocketManager) {
// 如果WebSocket管理器还没初始化,从utils导入
const WebSocketManager = require('../../utils/websocket').default;
app.globalData.webSocketManager = WebSocketManager;
// 尝试连接WebSocket
WebSocketManager.connect('ws://localhost:3003');
}
// 设置WebSocket监听
this.setupWebSocketListener();
// 加载客服列表
this.loadCustomerServices();
// 启动定期刷新
this.startPeriodicRefresh();
// 检查当前用户身份
this.checkUserType();
},
/**
* 检查当前用户身份
*/
checkUserType: function() {
const app = getApp();
const userInfo = app.globalData.userInfo || {};
const isManager = userInfo.userType === 'manager' || userInfo.type === 'manager';
console.log('当前用户身份检查:', { isManager, userType: userInfo.userType });
this.setData({
isCurrentUserManager: isManager
});
},
onShow() {
// 更新自定义tabBar状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: -1 // 不选中任何tab
});
}
// 当页面显示时重新加载数据,确保数据最新
this.loadCustomerServices();
},
onUnload: function() {
// 停止定期刷新
this.stopPeriodicRefresh();
// 清理WebSocket事件监听
const ws = getApp().globalData.webSocketManager;
if (ws) {
ws.off('customerServiceStatusUpdate');
}
},
onSearch: function (e) {
const keyword = e.detail.value;
this.setData({
searchKeyword: keyword
});
this.filterServices();
},
onAreaFilter: function () {
// 区域筛选弹窗 - 鸡蛋采购区域
wx.showActionSheet({
itemList: ['全部', '华北区', '华东区', '华南区', '全国', '西南区', '西北区', '东北区'],
success: res => {
const areas = ['全部', '华北区', '华东区', '华南区', '全国', '西南区', '西北区', '东北区'];
const selectedArea = areas[res.tapIndex];
this.setData({
selectedArea: selectedArea
});
this.filterServices();
}
});
},
filterServices: function () {
const { customerServices, searchKeyword, selectedArea } = this.data;
let filtered = customerServices;
// 关键词搜索
if (searchKeyword) {
const keyword = searchKeyword.toLowerCase();
filtered = filtered.filter(item => {
return item.alias?.toLowerCase().includes(keyword) ||
item.name.toLowerCase().includes(keyword) ||
item.phoneNumber?.includes(keyword) ||
item.managercompany?.toLowerCase().includes(keyword);
});
}
// 区域筛选
if (selectedArea && selectedArea !== '全部') {
filtered = filtered.filter(item => {
return item.responsibleArea?.includes(selectedArea);
});
}
this.setData({
filteredServices: filtered
});
},
onChat: function (e) {
const id = e.currentTarget.dataset.id;
const service = this.data.customerServices.find(item => item.id === id);
// 确保使用managerId作为聊天对象的唯一标识符
const chatUserId = service?.managerId || id;
wx.navigateTo({
url: `/pages/chat-detail/index?userId=${chatUserId}&userName=${encodeURIComponent(service?.alias || '')}&phone=${service?.phoneNumber || ''}&isManager=true`
});
console.log('跳转到聊天页面:', { chatUserId, userName: service?.alias });
},
onCall: function (e) {
const phone = e.currentTarget.dataset.phone;
wx.makePhoneCall({
phoneNumber: phone,
success: function () {
console.log('拨打电话成功');
},
fail: function () {
console.log('拨打电话失败');
}
}); });
}, },
// 查看客服详情 sendEmail: function () {
onViewDetail: function (e) { // 打开邮件客户端
const id = e.currentTarget.dataset.id; wx.showToast({
wx.navigateTo({ title: '邮箱客服功能开发中',
url: `/pages/customer-service/detail?id=${id}` icon: 'none'
}); });
},
onBack: function () {
wx.navigateBack();
} }
}); });

4
pages/customer-service/index.json

@ -1,5 +1,3 @@
{ {
"navigationBarBackgroundColor": "#f8f8f8", "navigationBarTitleText": "客服中心"
"navigationBarTextStyle": "black",
"usingComponents": {}
} }

99
pages/customer-service/index.wxml

@ -1,89 +1,40 @@
<!-- pages/customer-service/index.wxml -->
<view class="container"> <view class="container">
<!-- 顶部导航栏 --> <view class="service-header">
<view class="nav-bar"> <text class="service-title">客服中心</text>
<view class="nav-left" bindtap="onBack">
<text class="back-icon">返回</text>
</view>
<view class="nav-title">客服列表</view>
<view class="nav-right">
<text class="settings-icon">⚙️</text>
</view>
</view> </view>
<!-- 搜索区域 --> <view class="service-content">
<view class="search-container"> <view class="service-item">
<view class="search-bar"> <view class="service-icon">
<text class="search-icon-small">🔍</text> <text>📞</text>
<input class="search-input" placeholder="客服人称或手机号" bindinput="onSearch" value="{{searchKeyword}}" />
</view>
<view class="filter-area">
<view class="area-picker" bindtap="onAreaFilter">
<text>区域</text>
<text class="picker-arrow">▼</text>
</view> </view>
<view class="service-info">
<text class="service-name">电话客服</text>
<text class="service-desc">周一至周日 9:00-18:00</text>
</view> </view>
<button class="call-button" open-type="makePhoneCall" phone-number="400-123-4567">呼叫</button>
</view> </view>
<!-- 客服列表 --> <view class="service-item">
<view class="broker-list"> <view class="service-icon">
<block wx:if="{{filteredServices.length > 0}}"> <text>💬</text>
<view class="broker-item" wx:for="{{filteredServices}}" wx:key="id" bindtap="onViewDetail" data-id="{{item.id}}">
<view class="broker-info">
<view class="avatar-container">
<image class="avatar" src="{{item.avatarUrl || '/images/default-avatar.png'}}" mode="aspectFill" />
<view wx:if="{{item.isOnline}}" class="online-indicator">在线</view>
<view wx:else class="offline-indicator">离线</view>
</view>
<view class="broker-details">
<view class="name-row">
<text class="name">{{item.alias}}</text>
<text class="score-text">{{item.score}} 鸡蛋分</text>
<text wx:if="{{item.isOnline}}" class="online-status">(在线)</text>
</view>
<text class="company">{{item.managercompany || '暂无公司信息'}}</text>
<text class="department">{{item.managerdepartment}} · {{item.projectName}}</text>
<text class="area">负责区域:{{item.responsibleArea}}</text>
<text class="experience">服务平台{{item.experience}} 服务{{item.serviceCount}}家鸡场</text>
<!-- 业绩数据统计 -->
<view class="performance-stats">
<view class="stat-item">
<text class="stat-value">{{item.purchaseCount}}</text>
<text class="stat-label">累计采购(件)</text>
</view>
<view class="stat-divider">|</view>
<view class="stat-item">
<text class="stat-value profit-rate">{{item.profitFarmCount}}</text>
<text class="stat-label">服务盈利鸡场(家)</text>
</view> </view>
<view class="stat-divider">|</view> <view class="service-info">
<view class="stat-item"> <text class="service-name">在线客服</text>
<text class="stat-value profit-rate">{{item.profitIncreaseRate}}%</text> <text class="service-desc">24小时在线为您服务</text>
<text class="stat-label">平均盈利增长</text>
</view> </view>
<button class="chat-button" bindtap="startOnlineChat">在线咨询</button>
</view> </view>
<!-- 专业技能标签 -->
<view class="skills-preview"> <view class="service-item">
<view wx:for="{{item.skills}}" wx:key="index" wx:if="{{index < 3}}" class="skill-tag-small"> <view class="service-icon">
{{item}}</view> <text>📧</text>
<view wx:if="{{item.skills.length > 3}}" class="skill-more">
+{{item.skills.length - 3}}</view>
</view>
</view>
</view>
<view class="action-buttons">
<view wx:if="{{!isCurrentUserManager}}" class="button-chat" bindtap="onChat" data-id="{{item.id}}">
<text class="button-icon">💬</text>
</view>
<view class="button-call" bindtap="onCall" data-phone="{{item.phoneNumber}}">
<text class="button-icon">📞</text>
</view>
</view> </view>
<view class="service-info">
<text class="service-name">邮箱客服</text>
<text class="service-desc">service@example.com</text>
</view> </view>
</block> <button class="email-button" bindtap="sendEmail">发送邮件</button>
<view wx:else class="empty-state">
<text>👤</text>
<text class="empty-text">暂无匹配的经纪人</text>
</view> </view>
</view> </view>
</view> </view>

379
pages/customer-service/index.wxss

@ -1,383 +1,80 @@
/* pages/customer-service/index.wxss */
.container { .container {
padding-bottom: 100rpx; background-color: #f5f5f5;
background-color: #f8f8f8;
min-height: 100vh; min-height: 100vh;
} }
/* 顶部导航栏 */ .service-header {
.nav-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 44rpx 30rpx 20rpx;
background-color: #fff; background-color: #fff;
border-bottom: 1rpx solid #f0f0f0; padding: 20rpx;
position: fixed; border-bottom: 1rpx solid #e8e8e8;
top: 0;
left: 0;
right: 0;
z-index: 1000;
width: 100%;
box-sizing: border-box;
}
.nav-left {
width: 80rpx;
display: flex; display: flex;
justify-content: flex-start; align-items: center;
} justify-content: center;
.back-icon {
font-size: 32rpx;
color: #333;
font-weight: normal;
} }
.nav-title { .service-title {
font-size: 36rpx; font-size: 36rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
flex: 1;
text-align: center;
}
.nav-right {
display: flex;
align-items: center;
gap: 30rpx;
} }
.search-icon, .settings-icon { .service-content {
font-size: 32rpx; padding: 20rpx;
color: #333;
} }
/* 搜索区域 */ .service-item {
.search-container {
background-color: #fff; background-color: #fff;
padding: 20rpx 30rpx; border-radius: 12rpx;
border-bottom: 10rpx solid #f8f8f8; padding: 24rpx;
position: fixed;
top: 110rpx;
left: 0;
right: 0;
z-index: 999;
width: 100%;
box-sizing: border-box;
}
.search-bar {
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 40rpx;
padding: 16rpx 24rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
transition: all 0.3s ease; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.search-bar:focus-within {
background-color: #e6f7ff;
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.1);
}
.search-icon-small {
font-size: 28rpx;
color: #999;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
background: none;
padding: 0;
}
.search-input::placeholder {
color: #999;
}
.filter-area {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.area-picker { .service-icon {
display: flex; width: 80rpx;
align-items: center; height: 80rpx;
background-color: #f5f5f5;
padding: 12rpx 24rpx;
border-radius: 24rpx;
font-size: 28rpx;
color: #333;
}
.picker-arrow {
margin-left: 8rpx;
font-size: 20rpx;
color: #999;
}
/* 经纪人列表 */
.broker-list {
background-color: #f8f8f8;
padding: 0 30rpx;
margin-top: 280rpx; /* 为固定导航和搜索区域留出空间 */
}
.broker-item {
background-color: #fff;
border-radius: 24rpx;
margin: 20rpx 0;
padding: 28rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
.broker-item:active {
transform: translateY(2rpx);
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.03);
background-color: #fafafa;
}
.broker-info {
display: flex;
margin-bottom: 24rpx;
}
.avatar-container {
width: 100rpx;
height: 100rpx;
margin-right: 24rpx;
position: relative;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%; border-radius: 50%;
background-color: #f0f0f0; background-color: #f0f0f0;
border: 2rpx solid #f0f0f0;
}
.online-indicator {
position: absolute;
bottom: 0;
right: 0;
background-color: #52c41a;
color: white;
font-size: 18rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
border: 2rpx solid white;
}
.offline-indicator {
position: absolute;
bottom: 0;
right: 0;
background-color: #999;
color: white;
font-size: 18rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
border: 2rpx solid white;
}
.broker-details {
flex: 1;
display: flex; display: flex;
flex-direction: column; align-items: center;
justify-content: center; justify-content: center;
margin-right: 24rpx;
font-size: 40rpx;
} }
.name-row { .service-info {
display: flex; flex: 1;
align-items: center;
margin-bottom: 8rpx;
flex-wrap: wrap;
} }
.name { .service-name {
display: block;
font-size: 32rpx; font-size: 32rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-right: 12rpx; margin-bottom: 8rpx;
}
.score-text {
font-size: 24rpx;
color: #fff;
background: linear-gradient(135deg, #ffb800, #ff7700);
padding: 4rpx 12rpx;
border-radius: 12rpx;
margin-right: 8rpx;
}
.online-status {
font-size: 22rpx;
color: #52c41a;
}
.company {
font-size: 28rpx;
color: #666;
margin-bottom: 6rpx;
line-height: 1.4;
}
.department {
font-size: 26rpx;
color: #999;
margin-bottom: 6rpx;
line-height: 1.4;
} }
.area { .service-desc {
display: block;
font-size: 26rpx; font-size: 26rpx;
color: #333; color: #666;
margin-bottom: 6rpx;
line-height: 1.4;
font-weight: 500;
}
.experience {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
/* 专业技能标签预览 */
.skills-preview {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 12rpx;
}
.skill-tag-small {
background-color: #f0f9ff;
color: #1890ff;
font-size: 22rpx;
padding: 8rpx 16rpx;
border-radius: 16rpx;
border: 1rpx solid #bae7ff;
}
.skill-more {
background-color: #f5f5f5;
color: #999;
font-size: 22rpx;
padding: 8rpx 16rpx;
border-radius: 16rpx;
border: 1rpx solid #e0e0e0;
}
/* 业绩数据统计样式 */
.performance-stats {
display: flex;
justify-content: space-between;
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
}
.stat-item {
flex: 1;
text-align: center;
} }
.stat-value { .call-button, .chat-button, .email-button {
padding: 12rpx 32rpx;
border-radius: 24rpx;
font-size: 28rpx; font-size: 28rpx;
font-weight: bold; font-weight: bold;
color: #333; background-color: #FF6B81;
display: block; color: #fff;
} border: none;
margin: 0;
.stat-value.profit-rate { line-height: normal;
color: #52c41a; height: auto;
}
.stat-label {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
display: block;
}
.stat-divider {
color: #e0e0e0;
font-size: 24rpx;
margin: 0 10rpx;
display: flex;
align-items: center;
}
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 24rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.button-chat, .button-call {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.button-chat {
background-color: #e6f7f0;
}
.button-call {
background-color: #e6f0f7;
}
.button-chat:active {
transform: scale(0.95);
opacity: 0.9;
background-color: #c3e6cb;
}
.button-call:active {
transform: scale(0.95);
opacity: 0.9;
background-color: #b3d8ff;
}
.button-icon {
font-size: 36rpx;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
background-color: #fff;
margin: 20rpx 0;
border-radius: 24rpx;
}
.empty-state text:first-child {
font-size: 100rpx;
margin-bottom: 20rpx;
} }
.empty-text { .call-button:hover, .chat-button:hover, .email-button:hover {
font-size: 28rpx; background-color: #FF526D;
color: #999;
} }

773
pages/message-list/index.js

@ -1,756 +1,59 @@
// 消息列表页面 // pages/message-list/index.js
Page({ Page({
data: { data: {
messageList: [] messageList: []
}, },
onLoad: function() { onLoad: function (options) {
this.loadChatList(); this.loadMessageList();
// 注册全局新消息处理函数
this.registerGlobalMessageHandler();
}, },
/** loadMessageList: function () {
* 注册全局新消息处理函数 // 模拟加载消息列表
*/ const messageList = [
registerGlobalMessageHandler: function() { {
try { id: 1,
const app = getApp(); type: 'system',
const that = this; name: '系统通知',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
// 保存原有的处理函数(如果有) content: '您有一条新的系统通知',
this.originalMessageHandler = app.globalData.onNewMessage; time: '刚刚',
unread: 1
// 注册新的处理函数
app.globalData.onNewMessage = function(message) {
console.log('消息列表页面收到新消息:', message);
// 重新加载聊天列表
that.loadChatList();
// 调用原始处理函数(如果有)
if (that.originalMessageHandler && typeof that.originalMessageHandler === 'function') {
that.originalMessageHandler(message);
}
};
console.log('已注册全局新消息处理函数');
} catch (e) {
console.error('注册全局新消息处理函数失败:', e);
}
},
onShow: function() {
console.log('消息列表页面显示,开始加载数据...');
// 每次显示页面时强制刷新聊天列表
this.loadChatList();
// 确保WebSocket连接已建立,用于接收实时消息
this.ensureWebSocketConnected();
},
/**
* 确保WebSocket连接已建立
*/
ensureWebSocketConnected: function() {
try {
const app = getApp();
if (typeof require === 'function') {
const socketManager = require('../../utils/websocket.js').default;
// 检查连接状态
if (!socketManager.getConnectionStatus()) {
console.log('WebSocket未连接,尝试重新连接...');
// 使用正确的WebSocket服务器地址
const wsUrl = app.globalData.webSocketUrl || 'ws://localhost:3003';
socketManager.connect(wsUrl);
}
}
} catch (e) {
console.error('确保WebSocket连接失败:', e);
}
}, },
{
/** id: 2,
* 加载聊天列表 - 从服务器API获取数据 type: 'service',
*/ name: '客服中心',
loadChatList: function() { avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
wx.showLoading({ title: '加载中' }); content: '您好,请问有什么可以帮助您的?',
time: '5分钟前',
try { unread: 0
// 从服务器获取真实的聊天列表数据
const app = getApp();
const token = app.globalData.token || wx.getStorageSync('token');
const currentUserId = app.globalData.userInfo?.userId || wx.getStorageSync('userId') || 'unknown';
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || '';
const isManager = app.globalData.isManager || currentUserType === 'manager' || currentUserType.includes('manager');
const isCustomer = app.globalData.isCustomer || (currentUserType.includes('buyer') || currentUserType.includes('seller') || currentUserType.includes('both'));
console.log('当前用户类型:', currentUserType, 'isManager:', isManager, 'isCustomer:', isCustomer);
// 使用正确的API配置,兼容开发环境
const baseUrl = app.globalData.baseUrl || 'http://localhost:3003';
console.log('使用API地址:', baseUrl);
console.log('当前用户ID:', currentUserId);
// 根据用户类型选择不同的API端点
let apiUrl;
if (isManager) {
// 客服类型使用专门的接口获取客户会话列表
const managerId = wx.getStorageSync('managerId') || currentUserId;
apiUrl = `${baseUrl}/api/conversations/manager/${managerId}`;
console.log('客服模式,使用manager API获取会话列表');
} else if (isCustomer || !isManager) {
// 普通用户使用user API
apiUrl = `${baseUrl}/api/conversations/user/${currentUserId}`;
console.log('客户模式,使用user API获取会话列表');
} else {
// 默认使用user API
apiUrl = `${baseUrl}/api/conversations/user/${currentUserId}`;
console.log('默认模式,使用user API获取会话列表');
}
wx.request({
url: apiUrl,
method: 'GET',
header: {
'Authorization': token ? `Bearer ${token}` : '',
'content-type': 'application/json'
}, },
success: (res) => { {
console.log('获取聊天列表成功:', res.data); id: 3,
// 处理不同的API响应格式 type: 'order',
let chatData = []; name: '订单通知',
avatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
if (res.data.code === 0 && res.data.data) { content: '您的订单已发货',
chatData = res.data.data; time: '1小时前',
} else if (Array.isArray(res.data)) { unread: 0
// 如果直接返回数组
chatData = res.data;
} else if (res.data) {
// 如果返回的是对象但不是标准格式,尝试直接使用
chatData = [res.data];
} }
];
// 合理过滤聊天数据,保留有效的会话 this.setData({
const validChatData = chatData.filter(item => { messageList: messageList
// 确保item有效
if (!item || typeof item !== 'object') return false;
// 获取显示的用户ID并验证其有效性
const displayUserId = item.userId === currentUserId ? item.managerId : item.userId || item.id;
if (!displayUserId || displayUserId === 'undefined' || displayUserId === 'null' || String(displayUserId).trim() === '') {
console.log('过滤掉无效的用户ID:', displayUserId, '的会话');
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和名称
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: 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,
isRead: item.isRead || false,
unreadCount: item.unreadCount || 0
};
}).filter(chat => {
// 过滤掉客服之间的会话
if (isManager) {
// 只过滤真正的客服聊天,保留所有用户消息
const isChatWithManager =
(chat.userId && (chat.userId.includes('customer_service') || chat.userId.includes('manager'))) ||
(chat.userName && chat.userName.includes('客服') && !chat.userName.includes('用户'));
console.log('过滤检查 - 用户ID:', chat.userId, '用户名:', chat.userName, '是否为客服聊天:', isChatWithManager);
return !isChatWithManager;
}
return true;
}); });
// 按最后消息时间排序(最新的在前)
formattedMessages.sort((a, b) => {
return new Date(b.lastMessageTime) - new Date(a.lastMessageTime);
});
this.setData({ messageList: formattedMessages });
} else {
console.log('暂无有效的聊天消息');
// 确保清空消息列表
this.setData({ messageList: [] });
// 尝试从本地存储加载数据作为备选,但现在本地加载逻辑也会严格验证
this.loadFromLocalStorage();
}
},
fail: (err) => {
console.error('网络请求失败:', err);
wx.showToast({
title: '网络请求失败,尝试使用本地数据',
icon: 'none',
duration: 3000
});
// 失败时尝试从本地存储加载数据
this.loadFromLocalStorage();
},
complete: () => {
wx.hideLoading();
}
});
} catch (error) {
console.error('加载聊天列表异常:', error);
wx.hideLoading();
wx.showToast({ title: '加载异常', icon: 'none' });
// 异常时尝试从本地存储加载数据
this.loadFromLocalStorage();
}
},
/**
* 从本地存储加载聊天列表 - 作为备用方案
*/
loadFromLocalStorage: function() {
try {
// 获取所有存储的聊天记录键
const storageInfo = wx.getStorageInfoSync();
const chatKeys = storageInfo.keys.filter(key => key.startsWith('chat_messages_'));
const messageList = [];
// 获取当前用户信息
const app = getApp();
const currentUserId = wx.getStorageSync('userId') || '';
const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer';
console.log('从本地存储加载聊天列表 - 用户类型:', currentUserType, '聊天记录数量:', chatKeys.length);
// 遍历每个聊天记录
chatKeys.forEach(key => {
const chatUserId = key.replace('chat_messages_', '');
// 严格验证聊天用户ID
if (!chatUserId || chatUserId === 'undefined' || chatUserId === 'null' || chatUserId.trim() === '') {
console.log('跳过无效的聊天用户ID:', chatUserId);
return;
}
// 过滤掉临时用户ID(以temp_开头的ID)
if (chatUserId.trim().startsWith('temp_')) {
console.log('跳过临时用户ID:', chatUserId, '的会话');
return;
}
// 获取消息列表
const messages = wx.getStorageSync(key);
// 严格检查消息列表
if (!messages || !Array.isArray(messages) || messages.length === 0) {
console.log('跳过无效或空的消息列表:', chatUserId);
return;
}
// 避免处理自己与自己的聊天
if (chatUserId === currentUserId) {
console.log('跳过自己与自己的聊天:', chatUserId);
return;
}
// 过滤并验证每条消息
const validMessages = messages.filter(msg => {
return msg &&
msg.content &&
typeof msg.content === 'string' &&
msg.content.trim() !== '' &&
msg.time; // 确保有时间戳
});
// 如果没有有效消息,跳过
if (validMessages.length === 0) {
console.log('跳过没有有效消息的对话:', chatUserId);
return;
}
// 放宽条件,只要有有效消息就显示会话,不再要求必须有对方发送的消息
// 这样可以显示用户主动发起的聊天会话
// 获取最后一条有效消息
const lastValidMessage = validMessages[validMessages.length - 1];
// 再次确认最后一条消息的有效性
if (!lastValidMessage || !lastValidMessage.content || lastValidMessage.content.trim() === '') {
console.log('跳过最后消息无效的对话:', chatUserId);
return;
}
// 统计未读消息数量
const unreadCount = validMessages.filter(msg => {
return msg.sender !== 'me' && !msg.isRead;
}).length;
// 仅当不是客服之间的对话时添加
const userName = this.getUserNameById(chatUserId);
if (!(isManager && userName && (userName.includes('客服') || userName.includes('manager')))) {
messageList.push({
userId: chatUserId,
userName: userName,
avatar: '',
lastMessage: this.formatMessagePreview(lastValidMessage),
lastMessageTime: this.formatMessageTime(lastValidMessage.time),
messageCount: validMessages.length,
unreadCount: unreadCount,
isRead: unreadCount === 0
});
} else {
console.log('客服身份,跳过与其他客服的聊天:', chatUserId, userName);
}
});
// 按最后消息时间排序(最新的在前)
messageList.sort((a, b) => {
return new Date(b.lastMessageTime) - new Date(a.lastMessageTime);
});
if (messageList.length > 0) {
console.log('从本地存储加载了', messageList.length, '条有效聊天记录');
this.setData({ messageList: messageList });
} else {
// 确保清空消息列表,避免显示任何默认用户
console.log('没有找到任何有效的聊天记录,清空消息列表');
this.setData({ messageList: [] });
// 清理可能导致默认用户显示的无效本地存储数据
this.cleanupInvalidStorageData();
}
} catch (e) {
console.error('从本地存储加载聊天列表失败:', e);
// 出错时也确保消息列表为空
this.setData({ messageList: [] });
}
},
/**
* 清理无效的本地存储数据防止显示默认用户
*/
cleanupInvalidStorageData: function() {
try {
const storageInfo = wx.getStorageInfoSync();
const chatKeys = storageInfo.keys.filter(key => key.startsWith('chat_messages_'));
chatKeys.forEach(key => {
const messages = wx.getStorageSync(key);
// 删除无效或空的消息列表
if (!messages || !Array.isArray(messages) || messages.length === 0) {
console.log('删除无效的聊天存储键:', key);
wx.removeStorageSync(key);
return;
}
// 检查是否只有无效消息
const allInvalid = messages.every(msg => {
return !msg ||
!msg.content ||
typeof msg.content !== 'string' ||
msg.content.trim() === '';
});
if (allInvalid) {
console.log('删除全是无效消息的聊天记录:', key);
wx.removeStorageSync(key);
}
});
console.log('清理无效存储数据完成');
} catch (e) {
console.error('清理无效存储数据失败:', e);
}
}, },
/** onMessageTap: function (e) {
* 根据用户ID获取用户名 - 增强版确保不返回默认的'未知用户'名称 // 跳转到消息详情页
*/
getUserNameById: function(userId) {
try {
// 增强的参数有效性检查
if (!userId || typeof userId === 'undefined' || userId === null || String(userId).trim() === '') {
return null;
}
// 确保userId是字符串类型,并去除前后空格
const safeUserId = String(userId).trim();
// 避免显示"undefined"或"null"
if (safeUserId === 'undefined' || safeUserId === 'null') {
return null;
}
// 特殊处理特定用户ID,直接返回对应的手机号
if (safeUserId === 'user_1765415381781_1iy1ls177') {
return '17828048259';
}
// 获取当前用户类型
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') {
// 尝试从本地存储获取用户详细信息
try {
// 尝试获取用户数据映射
const users = wx.getStorageSync('users') || {};
const userData = users[safeUserId];
// 首先检查是否是手机号格式的用户ID(中国手机号格式)
if (/^1[3-9]\d{9}$/.test(safeUserId)) {
// 如果是手机号,直接显示完整手机号
return safeUserId;
}
if (userData) {
// 如果有用户信息,优先使用
if (userData.phoneNumber) {
return userData.phoneNumber; // 显示完整手机号
}
if (userData.phone) {
return userData.phone; // 显示完整手机号
}
if (userData.info && userData.info.nickName) {
return userData.info.nickName;
}
}
// 尝试获取会话相关的用户信息
const sessionInfo = wx.getStorageSync(`session_${safeUserId}`);
if (sessionInfo && sessionInfo.userName) {
return sessionInfo.userName;
}
} catch (innerError) {
console.log('获取用户详细信息失败:', innerError);
}
}
// 固定用户名映射
const userMap = {
'user_1': '张三',
'user_2': '李四',
'user_3': '王五',
'user_4': '赵六',
'user_5': '钱七'
};
if (userMap[safeUserId]) {
return userMap[safeUserId];
}
// 对于manager_开头的ID,显示为客服
if (safeUserId.startsWith('manager_') || safeUserId.includes('manager')) {
return '客服-' + (safeUserId.length >= 4 ? safeUserId.slice(-4) : safeUserId);
}
// 对于test_开头的测试用户ID,显示为测试用户
if (safeUserId.startsWith('test_')) {
return '测试用户-' + (safeUserId.length >= 8 ? safeUserId.slice(-4) : safeUserId);
}
// 处理系统通知类型
if (safeUserId.startsWith('system_')) {
return '系统通知';
}
// 处理群组聊天
if (safeUserId.startsWith('group_')) {
return '群组-' + (safeUserId.replace('group_', '').length >= 4 ? safeUserId.replace('group_', '').slice(-4) : safeUserId.replace('group_', ''));
}
// 从数据库记录中获取的用户ID格式处理
if (safeUserId.includes('_customer_')) {
// 提取数字部分
const match = safeUserId.match(/\d+/);
if (match && match[0]) {
return '用户' + match[0].slice(-4);
}
}
// 检查是否是手机号格式
if (/^1[3-9]\d{9}$/.test(safeUserId)) {
// 直接显示完整手机号
return safeUserId;
}
// 对于未识别的有效用户ID,返回基于ID的标识而不是默认名称
// 确保不返回'未知用户',而是使用实际的用户ID信息
if (safeUserId.length > 10) {
// 对于长ID,截取并添加省略号
return '用户-' + safeUserId.substring(0, 10) + '...';
} else {
// 对于短ID,直接使用
return '用户-' + safeUserId;
}
} catch (e) {
console.error('获取用户名失败:', e);
// 即使出现错误,也不返回'未知用户',而是返回'用户-未知'
return '用户-未知';
}
},
/**
* 格式化消息预览
*/
formatMessagePreview: function(message) {
if (message.type === 'system') {
return '[系统消息] ' + message.content;
} else {
const senderPrefix = message.sender === 'me' ? '我: ' : '';
// 限制预览长度
let preview = senderPrefix + message.content;
if (preview.length > 30) {
preview = preview.substring(0, 30) + '...';
}
return preview;
}
},
/**
* 格式化消息时间 - 增强版支持多种时间格式 - 统一使用北京时间
*/
formatMessageTime: function(timeStr) {
try {
if (!timeStr) return '';
// 获取当前北京时间
const now = new Date();
const nowYear = now.getFullYear();
const nowMonth = now.getMonth();
const nowDate = now.getDate();
let date;
// 尝试不同的时间格式解析
if (typeof timeStr === 'number') {
// 处理时间戳
let adjustedTimestamp = timeStr;
// 检查是否是秒级时间戳(小于1e12,毫秒级时间戳通常大于1e12)
if (adjustedTimestamp < 1e12) {
// 秒级时间戳转换为毫秒级
adjustedTimestamp *= 1000;
}
date = new Date(adjustedTimestamp);
} else if (typeof timeStr === 'string') {
if (/^\d+$/.test(timeStr)) {
// 数字字符串,可能是时间戳
const timestamp = parseInt(timeStr);
let adjustedTimestamp = timestamp;
// 检查是否是秒级时间戳
if (adjustedTimestamp < 1e12) {
// 秒级时间戳转换为毫秒级
adjustedTimestamp *= 1000;
}
date = new Date(adjustedTimestamp);
} else if (timeStr.includes('T')) {
// ISO 8601格式的时间字符串,如 "2025-12-18T07:03:00.000Z"
// 先将字符串解析为Date对象(自动转换为本地时间)
date = new Date(timeStr);
} else {
// 其他格式的时间字符串,直接解析
date = new Date(timeStr);
}
} else {
// 其他类型,返回空
return '';
}
// 验证日期是否有效
if (isNaN(date.getTime())) {
return '';
}
// 获取北京时间的各组成部分
const beijingYear = date.getFullYear();
const beijingMonth = date.getMonth();
const beijingDate = date.getDate();
const beijingHours = date.getHours();
const beijingMinutes = date.getMinutes();
// 计算天数差
const diffTime = Math.abs(now.getTime() - date.getTime());
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (diffDays === 0) {
// 今天的消息只显示时间,格式化为 HH:mm
const hours = beijingHours.toString().padStart(2, '0');
const minutes = beijingMinutes.toString().padStart(2, '0');
return `${hours}:${minutes}`;
} else if (diffDays === 1) {
// 昨天的消息
return '昨天';
} else if (diffDays < 7) {
// 一周内的消息显示星期
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return weekdays[date.getDay()];
} else if (diffDays < 365) {
// 今年内的消息显示月日
const month = (beijingMonth + 1).toString().padStart(2, '0');
const day = beijingDate.toString().padStart(2, '0');
return `${month}-${day}`;
} else {
// 超过一年的消息显示完整日期
const year = beijingYear;
const month = (beijingMonth + 1).toString().padStart(2, '0');
const day = beijingDate.toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
} catch (e) {
console.error('格式化消息时间失败:', e);
return '';
}
},
/**
* 跳转到聊天详情页
*/
goToChat: function(e) {
const userId = e.currentTarget.dataset.userId;
// 查找对应的用户信息
const userInfo = this.data.messageList.find(item => item.userId === userId);
wx.navigateTo({ wx.navigateTo({
url: `/pages/chat-detail/index?userId=${userId}&userName=${encodeURIComponent(userInfo?.userName || '')}` url: '/pages/chat-detail/index?id=' + e.currentTarget.dataset.id
}); });
}, },
/** onPullDownRefresh: function () {
* 页面相关事件处理函数--监听用户下拉动作 this.loadMessageList();
*/
onPullDownRefresh: function() {
console.log('用户下拉刷新,重新加载聊天列表...');
// 重新加载聊天列表
this.loadChatList();
// 添加延迟,确保用户能看到刷新效果
setTimeout(() => {
wx.stopPullDownRefresh(); wx.stopPullDownRefresh();
console.log('下拉刷新完成');
}, 1000);
},
/**
* 处理清除聊天记录
*/
onUnload: function() {
// 页面卸载时的清理工作
// 恢复原始的全局消息处理函数
try {
const app = getApp();
if (this.originalMessageHandler) {
app.globalData.onNewMessage = this.originalMessageHandler;
} else {
// 如果没有原始处理函数,则清除当前的
delete app.globalData.onNewMessage;
}
console.log('已清理全局新消息处理函数');
} catch (e) {
console.error('清理全局新消息处理函数失败:', e);
}
},
handleClearChat: function(e) {
const userId = e.currentTarget.dataset.userId;
wx.showModal({
title: '确认清空',
content: '确定要清空与该用户的所有聊天记录吗?此操作不可恢复。',
success: (res) => {
if (res.confirm) {
this.clearChatHistory(userId);
}
}
});
},
/**
* 清空指定用户的聊天记录
*/
clearChatHistory: function(userId) {
try {
wx.removeStorageSync(`chat_messages_${userId}`);
console.log('已清空用户', userId, '的聊天记录');
// 刷新消息列表
this.loadChatList();
wx.showToast({
title: '聊天记录已清空',
icon: 'success'
});
} catch (e) {
console.error('清空聊天记录失败:', e);
wx.showToast({
title: '清空失败',
icon: 'none'
});
}
} }
}); });

3
pages/message-list/index.json

@ -1,4 +1,3 @@
{ {
"navigationBarTitleText": "消息列表", "navigationBarTitleText": "消息列表"
"usingComponents": {}
} }

40
pages/message-list/index.wxml

@ -1,30 +1,34 @@
<view class="message-list-container"> <view class="container">
<view class="page-header"> <view class="message-header">
<text class="page-title">消息列表</text> <text class="message-title">消息列表</text>
</view> </view>
<view class="message-list"> <view class="message-list">
<!-- 消息列表项 --> <view class="message-item" bindtap="onMessageTap">
<block wx:for="{{messageList}}" wx:key="userId">
<view class="message-item" bindtap="goToChat" data-user-id="{{item.userId}}">
<view class="avatar"> <view class="avatar">
<image src="{{item.avatar || '/images/logo.svg'}}" mode="aspectFit"></image> <image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
</view>
<view class="message-content">
<view class="message-header">
<text class="user-name">{{item.userName}}</text>
<text class="message-time">{{item.lastMessageTime}}</text>
</view> </view>
<view class="message-preview">{{item.lastMessage || '暂无消息'}}</view> <view class="message-info">
<view class="message-name-time">
<text class="message-name">系统通知</text>
<text class="message-time">刚刚</text>
</view> </view>
<view class="message-actions"> <text class="message-content">您有一条新的系统通知</text>
<button size="mini" type="warn" bindtap="handleClearChat" data-user-id="{{item.userId}}" catchtap="true">清空</button>
</view> </view>
<view class="unread-badge">1</view>
</view> </view>
</block>
<view wx:if="{{messageList.length === 0}}" class="empty-state"> <view class="message-item" bindtap="onMessageTap">
<text>暂无聊天记录</text> <view class="avatar">
<image src="https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0" mode="aspectFill"></image>
</view>
<view class="message-info">
<view class="message-name-time">
<text class="message-name">客服中心</text>
<text class="message-time">5分钟前</text>
</view>
<text class="message-content">您好,请问有什么可以帮助您的?</text>
</view>
</view> </view>
</view> </view>
</view> </view>

79
pages/message-list/index.wxss

@ -1,47 +1,43 @@
.message-list-container { .container {
height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
display: flex; min-height: 100vh;
flex-direction: column;
} }
.page-header { .message-header {
padding: 20rpx 30rpx;
background-color: #fff; background-color: #fff;
border-bottom: 1rpx solid #eee; padding: 20rpx;
border-bottom: 1rpx solid #e8e8e8;
display: flex;
align-items: center;
justify-content: center;
} }
.page-title { .message-title {
font-size: 36rpx; font-size: 36rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
} }
.message-list { .message-list {
flex: 1; padding: 20rpx;
overflow-y: auto;
} }
.message-item { .message-item {
background-color: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
display: flex; display: flex;
align-items: center; align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.2s;
}
.message-item:active {
background-color: #f8f8f8;
} }
.avatar { .avatar {
width: 100rpx; width: 80rpx;
height: 100rpx; height: 80rpx;
border-radius: 50%; border-radius: 50%;
margin-right: 24rpx;
overflow: hidden; overflow: hidden;
margin-right: 20rpx;
background-color: #f0f0f0;
} }
.avatar image { .avatar image {
@ -49,23 +45,21 @@
height: 100%; height: 100%;
} }
.message-content { .message-info {
flex: 1; flex: 1;
display: flex; position: relative;
flex-direction: column;
justify-content: center;
} }
.message-header { .message-name-time {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 8rpx; margin-bottom: 8rpx;
} }
.user-name { .message-name {
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: bold;
color: #333; color: #333;
} }
@ -74,28 +68,23 @@
color: #999; color: #999;
} }
.message-preview { .message-content {
display: block;
font-size: 28rpx; font-size: 28rpx;
color: #666; color: #666;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 100%;
} }
.message-actions { .unread-badge {
margin-left: 20rpx; background-color: #FF6B81;
} color: #fff;
font-size: 22rpx;
.message-actions button { font-weight: bold;
font-size: 24rpx; padding: 4rpx 12rpx;
padding: 0 20rpx; border-radius: 14rpx;
min-width: 80rpx; min-width: 28rpx;
line-height: 50rpx;
}
.empty-state {
padding: 100rpx 0;
text-align: center; text-align: center;
color: #999;
font-size: 28rpx;
} }

123
pages/profile/index.js

@ -112,17 +112,12 @@ Page({
if (userType && userType !== '') { if (userType && userType !== '') {
let identityLabel = '身份:not_set' let identityLabel = '身份:not_set'
// 优先处理包含manager的类型
if (userType.includes('manager')) {
identityLabel = '身份:客服';
} else {
switch (userType) { switch (userType) {
case 'buyer': identityLabel = '身份:买家'; break case 'buyer': identityLabel = '身份:买家'; break
case 'seller': identityLabel = '身份:卖家'; break case 'seller': identityLabel = '身份:卖家'; break
case 'both': identityLabel = '身份:买卖家'; break case 'both': identityLabel = '身份:买卖家'; break
case 'buyer+seller': identityLabel = '身份:买卖家'; break case 'buyer+seller': identityLabel = '身份:买卖家'; break
} }
}
filteredTags.push(identityLabel) filteredTags.push(identityLabel)
console.log('加载用户信息 - 根据当前用户类型显示身份标签:', identityLabel) console.log('加载用户信息 - 根据当前用户类型显示身份标签:', identityLabel)
} }
@ -208,122 +203,11 @@ Page({
users[userId] = {} users[userId] = {}
} }
// 获取当前的用户类型,检查是否包含manager
const currentUserType = wx.getStorageSync('userType') || ''
let isManager = currentUserType.includes('manager')
// 移除serverType中的customer(如果存在) // 移除serverType中的customer(如果存在)
let processedServerType = serverType.replace(/,?customer/g, '').replace(/^,|,$/g, '') 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 let newUserType = processedServerType
if (isManager) {
console.log('检测到用户为客服,保留manager标识')
if (!processedServerType.includes('manager')) {
newUserType = processedServerType ? processedServerType + ',manager' : 'manager'
}
}
// 只有当新构建的用户类型与本地不同时才更新 // 只有当新构建的用户类型与本地不同时才更新
if (users[userId].type !== newUserType) { if (users[userId].type !== newUserType) {
@ -350,11 +234,6 @@ Page({
// 格式化用户类型显示 // 格式化用户类型显示
formatUserType(type) { formatUserType(type) {
// 优先处理包含manager的类型
if (type && type.includes('manager')) {
return '客服';
}
switch (type) { switch (type) {
case 'buyer': return '买家'; case 'buyer': return '买家';
case 'seller': return '卖家'; case 'seller': return '卖家';

382
pages/test-service/test-service.js

@ -1,382 +0,0 @@
// 客服功能综合测试页面
const socketManager = require('../../utils/websocket');
const API = require('../../utils/api');
Page({
data: {
// 测试状态
testResults: [],
currentTest: '',
isTesting: false,
// 用户信息
userInfo: null,
userType: 'unknown',
isAuthenticated: false,
// WebSocket状态
wsConnected: false,
wsAuthenticated: false,
// 测试消息
testMessage: '测试消息',
receivedMessage: null,
// 测试模式
testMode: 'customer', // customer 或 customer_service
// 模拟客服ID
mockServiceId: 'test_service_001'
},
onLoad: function() {
console.log('测试页面加载');
this.setData({
userInfo: getApp().globalData.userInfo,
userType: getApp().globalData.userType
});
// 设置WebSocket事件监听
this.setupWebSocketListeners();
},
// 设置WebSocket事件监听
setupWebSocketListeners: function() {
// 连接状态监听
socketManager.on('status', (status) => {
console.log('WebSocket状态:', status);
this.setData({
wsConnected: status.type === 'connected'
});
this.addTestResult(`WebSocket状态: ${status.type} - ${status.message || ''}`);
});
// 认证状态监听
socketManager.on('authenticated', (data) => {
console.log('WebSocket认证成功:', data);
this.setData({
wsAuthenticated: true,
isAuthenticated: true
});
this.addTestResult(`WebSocket认证成功,用户类型: ${data.userType || 'unknown'}`);
});
// 消息接收监听
socketManager.on('message', (message) => {
console.log('收到WebSocket消息:', message);
this.setData({
receivedMessage: message
});
this.addTestResult(`收到消息: ${JSON.stringify(message).substring(0, 100)}...`);
});
// 错误监听
socketManager.on('error', (error) => {
console.error('WebSocket错误:', error);
this.addTestResult(`错误: ${error.message || JSON.stringify(error)}`, true);
});
},
// 开始综合测试
startTest: function() {
if (this.data.isTesting) {
wx.showToast({ title: '测试正在进行中', icon: 'none' });
return;
}
this.setData({
isTesting: true,
testResults: [],
currentTest: ''
});
this.addTestResult('===== 开始综合测试 =====');
// 按顺序执行测试用例
setTimeout(() => {
this.testUserTypeDetection();
}, 500);
},
// 测试1: 用户类型检测
testUserTypeDetection: function() {
this.setData({ currentTest: '用户类型检测' });
this.addTestResult('测试1: 验证用户类型检测功能');
const app = getApp();
const userType = app.globalData.userType || 'unknown';
const storedType = wx.getStorageSync('userType') || 'unknown';
this.addTestResult(`全局用户类型: ${userType}`);
this.addTestResult(`本地存储用户类型: ${storedType}`);
if (userType === storedType && userType !== 'unknown') {
this.addTestResult('✓ 用户类型检测通过');
} else {
this.addTestResult('✗ 用户类型检测失败', true);
}
setTimeout(() => {
this.testWebSocketConnection();
}, 1000);
},
// 测试2: WebSocket连接和认证
testWebSocketConnection: function() {
this.setData({ currentTest: 'WebSocket连接' });
this.addTestResult('测试2: 验证WebSocket连接和认证功能');
// 检查是否已连接
if (socketManager.getConnectionStatus()) {
this.addTestResult('✓ WebSocket已连接');
setTimeout(() => {
this.testAuthentication();
}, 500);
} else {
this.addTestResult('尝试建立WebSocket连接...');
// 构建连接URL
const app = getApp();
const userType = app.globalData.userType || 'customer';
const userId = wx.getStorageSync('userId') || `test_${Date.now()}`;
const wsUrl = `ws://localhost:3003?userId=${userId}&userType=${userType}`;
this.addTestResult(`连接URL: ${wsUrl}`);
socketManager.connect(wsUrl);
// 等待连接建立
setTimeout(() => {
if (this.data.wsConnected) {
this.addTestResult('✓ WebSocket连接成功');
this.testAuthentication();
} else {
this.addTestResult('✗ WebSocket连接失败', true);
this.completeTest();
}
}, 3000);
}
},
// 测试3: 认证功能
testAuthentication: function() {
this.setData({ currentTest: '认证功能' });
this.addTestResult('测试3: 验证WebSocket认证功能');
if (this.data.wsAuthenticated) {
this.addTestResult('✓ WebSocket认证成功');
setTimeout(() => {
this.testMessageSending();
}, 500);
} else {
this.addTestResult('尝试手动认证...');
const app = getApp();
const userType = app.globalData.userType || this.data.testMode;
const userId = wx.getStorageSync('userId') || `test_${Date.now()}`;
socketManager.authenticate(userType, userId);
// 等待认证结果
setTimeout(() => {
if (this.data.wsAuthenticated) {
this.addTestResult('✓ 手动认证成功');
this.testMessageSending();
} else {
this.addTestResult('✗ 认证失败', true);
this.completeTest();
}
}, 2000);
}
},
// 测试4: 消息发送功能
testMessageSending: function() {
this.setData({ currentTest: '消息发送' });
this.addTestResult('测试4: 验证消息发送功能');
const app = getApp();
const userType = app.globalData.userType || this.data.testMode;
const targetId = userType === 'customer_service' ?
`test_customer_${Date.now()}` :
this.data.mockServiceId;
this.addTestResult(`当前用户类型: ${userType}, 目标ID: ${targetId}`);
const testMessage = {
type: 'chat_message',
direction: userType === 'customer_service' ? 'service_to_customer' : 'customer_to_service',
data: {
receiverId: targetId,
senderId: wx.getStorageSync('userId') || `test_${Date.now()}`,
senderType: userType,
content: this.data.testMessage || '测试消息_' + Date.now(),
contentType: 1,
timestamp: Date.now()
}
};
const sent = socketManager.send(testMessage);
if (sent) {
this.addTestResult('✓ 消息发送成功');
// 等待接收消息(如果有回复)
setTimeout(() => {
this.testBidirectionalCommunication();
}, 3000);
} else {
this.addTestResult('✗ 消息发送失败', true);
this.completeTest();
}
},
// 测试5: 双向通信功能
testBidirectionalCommunication: function() {
this.setData({ currentTest: '双向通信' });
this.addTestResult('测试5: 验证双向通信功能');
if (this.data.receivedMessage) {
this.addTestResult('✓ 收到响应消息');
this.addTestResult(`消息内容: ${JSON.stringify(this.data.receivedMessage).substring(0, 150)}...`);
} else {
this.addTestResult('⚠ 未收到响应消息(可能是正常的,取决于服务器配置)');
}
setTimeout(() => {
this.testUserTypeSwitching();
}, 1000);
},
// 测试6: 用户类型切换
testUserTypeSwitching: function() {
this.setData({ currentTest: '用户类型切换' });
this.addTestResult('测试6: 验证用户类型切换功能');
try {
// 模拟切换用户类型
const app = getApp();
const originalType = app.globalData.userType;
const newType = originalType === 'customer' ? 'customer_service' : 'customer';
app.globalData.userType = newType;
wx.setStorageSync('userType', newType);
this.addTestResult(`✓ 用户类型切换成功: ${originalType}${newType}`);
this.addTestResult(`新的全局用户类型: ${app.globalData.userType}`);
this.addTestResult(`新的存储用户类型: ${wx.getStorageSync('userType')}`);
// 恢复原始类型
setTimeout(() => {
app.globalData.userType = originalType;
wx.setStorageSync('userType', originalType);
this.addTestResult(`恢复原始用户类型: ${originalType}`);
this.completeTest();
}, 1000);
} catch (error) {
this.addTestResult(`✗ 用户类型切换失败: ${error.message}`, true);
this.completeTest();
}
},
// 完成测试
completeTest: function() {
this.addTestResult('===== 测试完成 =====');
// 统计测试结果
const results = this.data.testResults;
const successCount = results.filter(r => r.includes('✓')).length;
const failCount = results.filter(r => r.includes('✗')).length;
const warnCount = results.filter(r => r.includes('⚠')).length;
this.addTestResult(`测试统计: 成功=${successCount}, 失败=${failCount}, 警告=${warnCount}`);
if (failCount === 0) {
this.addTestResult('🎉 所有测试通过!');
} else {
this.addTestResult('❌ 测试中有失败项,请检查', true);
}
this.setData({
isTesting: false,
currentTest: '测试完成'
});
},
// 添加测试结果
addTestResult: function(message, isError = false) {
const timestamp = new Date().toLocaleTimeString();
const resultItem = {
id: Date.now(),
time: timestamp,
message: message,
isError: isError
};
this.setData({
testResults: [...this.data.testResults, resultItem]
});
console.log(`[${timestamp}] ${message}`);
},
// 切换测试模式
switchTestMode: function() {
const newMode = this.data.testMode === 'customer' ? 'customer_service' : 'customer';
this.setData({ testMode: newMode });
wx.showToast({ title: `已切换到${newMode === 'customer' ? '客户' : '客服'}模式` });
},
// 输入测试消息
onInputChange: function(e) {
this.setData({ testMessage: e.detail.value });
},
// 清理WebSocket连接
cleanup: function() {
try {
socketManager.close();
this.addTestResult('WebSocket连接已关闭');
} catch (error) {
this.addTestResult(`关闭连接失败: ${error.message}`, true);
}
},
// 手动发送消息
sendTestMessage: function() {
if (!this.data.testMessage.trim()) {
wx.showToast({ title: '请输入测试消息', icon: 'none' });
return;
}
const app = getApp();
const userType = app.globalData.userType || this.data.testMode;
const targetId = userType === 'customer_service' ?
`test_customer_${Date.now()}` :
this.data.mockServiceId;
const message = {
type: 'chat_message',
direction: userType === 'customer_service' ? 'service_to_customer' : 'customer_to_service',
data: {
receiverId: targetId,
senderId: wx.getStorageSync('userId') || `test_${Date.now()}`,
senderType: userType,
content: this.data.testMessage.trim(),
contentType: 1,
timestamp: Date.now()
}
};
const sent = socketManager.send(message);
if (sent) {
wx.showToast({ title: '消息已发送' });
this.addTestResult(`手动发送消息: ${this.data.testMessage}`);
} else {
wx.showToast({ title: '发送失败', icon: 'none' });
this.addTestResult(`手动发送失败`, true);
}
},
onUnload: function() {
// 清理监听器和连接
this.cleanup();
}
});

3
pages/test-service/test-service.json

@ -1,3 +0,0 @@
{
"usingComponents": {}
}

97
pages/test-service/test-service.wxml

@ -1,97 +0,0 @@
<view class="test-container">
<view class="header">
<text class="title">客服功能综合测试</text>
<text class="subtitle">验证客服认证、身份判断和双向沟通功能</text>
</view>
<view class="test-status">
<view class="status-item {{isTesting ? 'testing' : ''}}">
<text class="status-label">当前测试:</text>
<text class="status-value">{{currentTest || '未开始'}}</text>
</view>
<view class="status-item">
<text class="status-label">用户类型:</text>
<text class="status-value">{{userType}}</text>
</view>
<view class="status-item {{wsConnected ? 'connected' : ''}}">
<text class="status-label">WebSocket:</text>
<text class="status-value">{{wsConnected ? '已连接' : '未连接'}}</text>
</view>
<view class="status-item {{wsAuthenticated ? 'authenticated' : ''}}">
<text class="status-label">认证状态:</text>
<text class="status-value">{{wsAuthenticated ? '已认证' : '未认证'}}</text>
</view>
</view>
<view class="test-controls">
<view class="control-item">
<text class="control-label">测试模式:</text>
<view class="control-buttons">
<button type="{{testMode === 'customer' ? 'primary' : 'default'}}" bind:tap="switchTestMode">
{{testMode === 'customer' ? '客户模式 ✓' : '客户模式'}}
</button>
<button type="{{testMode === 'customer_service' ? 'primary' : 'default'}}" bind:tap="switchTestMode">
{{testMode === 'customer_service' ? '客服模式 ✓' : '客服模式'}}
</button>
</view>
</view>
<view class="control-item">
<text class="control-label">测试消息:</text>
<input
class="message-input"
placeholder="请输入测试消息"
value="{{testMessage}}"
bindinput="onInputChange"
/>
<button type="primary" bind:tap="sendTestMessage">发送测试消息</button>
</view>
<view class="action-buttons">
<button
class="start-button"
type="primary"
size="mini"
bind:tap="startTest"
disabled="{{isTesting}}"
>
{{isTesting ? '测试进行中...' : '开始综合测试'}}
</button>
<button
class="cleanup-button"
type="warn"
size="mini"
bind:tap="cleanup"
>
清理连接
</button>
</view>
</view>
<view class="test-results">
<text class="results-title">测试结果:</text>
<scroll-view class="results-list" scroll-y>
<view
wx:for="{{testResults}}"
wx:key="id"
class="result-item {{item.isError ? 'error' : ''}}"
>
<text class="result-time">{{item.time}}</text>
<text class="result-message">{{item.message}}</text>
</view>
<view class="empty-result" wx:if="{{testResults.length === 0}}">
暂无测试结果,点击开始综合测试
</view>
</scroll-view>
</view>
<view class="test-tips">
<text class="tips-title">测试提示:</text>
<view class="tips-content">
<text>1. 测试前请确保已完成登录</text>
<text>2. WebSocket服务需要正常运行</text>
<text>3. 测试将验证用户类型检测、WebSocket连接、认证和消息发送功能</text>
<text>4. 双向通信测试依赖于服务器配置,可能不会收到响应消息</text>
</view>
</view>
</view>

249
pages/test-service/test-service.wxss

@ -1,249 +0,0 @@
.test-container {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 30rpx;
padding: 20rpx 0;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.subtitle {
font-size: 24rpx;
color: #666;
display: block;
}
.test-status {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.status-item {
display: flex;
justify-content: space-between;
padding: 15rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-size: 28rpx;
color: #666;
}
.status-value {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.status-item.testing .status-value {
color: #07c160;
animation: pulse 1s infinite;
}
.status-item.connected .status-value {
color: #07c160;
}
.status-item.authenticated .status-value {
color: #1989fa;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.6;
}
100% {
opacity: 1;
}
}
.test-controls {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.control-item {
margin-bottom: 20rpx;
}
.control-item:last-child {
margin-bottom: 0;
}
.control-label {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 15rpx;
font-weight: 500;
}
.control-buttons {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
.control-buttons button {
flex: 1;
font-size: 26rpx;
}
.message-input {
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
margin-bottom: 15rpx;
background-color: #f9f9f9;
}
.action-buttons {
display: flex;
justify-content: space-between;
margin-top: 20rpx;
}
.action-buttons button {
flex: 1;
margin: 0 10rpx;
}
.start-button {
background-color: #07c160;
}
.cleanup-button {
background-color: #ee0a24;
}
.test-results {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.results-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
margin-bottom: 15rpx;
}
.results-list {
height: 400rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
padding: 10rpx;
}
.result-item {
padding: 15rpx;
margin-bottom: 10rpx;
background-color: #fff;
border-radius: 6rpx;
border-left: 4rpx solid #1989fa;
font-size: 24rpx;
}
.result-item.error {
border-left-color: #ee0a24;
background-color: #fff1f0;
}
.result-time {
color: #999;
font-size: 20rpx;
display: block;
margin-bottom: 5rpx;
}
.result-message {
color: #333;
word-break: break-all;
line-height: 1.5;
}
.empty-result {
text-align: center;
color: #999;
padding: 60rpx 0;
font-size: 26rpx;
}
.test-tips {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.tips-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
margin-bottom: 15rpx;
}
.tips-content {
background-color: #f0f9ff;
border-radius: 8rpx;
padding: 20rpx;
}
.tips-content text {
display: block;
margin-bottom: 10rpx;
font-size: 24rpx;
color: #666;
line-height: 1.5;
}
.tips-content text:last-child {
margin-bottom: 0;
}
/* 适配不同屏幕尺寸 */
@media screen and (min-width: 768px) {
.test-container {
max-width: 900rpx;
margin: 0 auto;
padding: 30rpx;
}
.results-list {
height: 600rpx;
}
}

108
server-example/check-chat-online-status-detailed.js

@ -1,108 +0,0 @@
// 详细检查chat_online_status表的数据
const { Sequelize } = require('sequelize');
console.log('=== 详细检查chat_online_status表数据 ===\n');
// 创建Sequelize实例(使用与项目相同的配置)
const sequelize = new Sequelize('wechat_app', 'root', '', {
host: '1.95.162.61',
port: 3306,
dialect: 'mysql',
logging: false
});
async function checkChatOnlineStatus() {
try {
// 测试数据库连接
console.log('正在连接数据库...');
await sequelize.authenticate();
console.log('✅ 数据库连接成功\n');
// 1. 查询chat_online_status表中的所有客服记录
console.log('1. 查询所有客服在线状态记录:');
const [allStatuses] = await sequelize.query(
'SELECT * FROM chat_online_status WHERE type = 2 ORDER BY updated_at DESC',
{ type: Sequelize.QueryTypes.SELECT }
);
console.log(`找到 ${allStatuses.length} 条客服在线状态记录\n`);
// 打印详细信息
allStatuses.forEach((status, index) => {
console.log(`记录 ${index + 1}:`);
console.log(` userId: ${status.userId}`);
console.log(` type: ${status.type === 2 ? '客服' : status.type}`);
console.log(` socket_id: ${status.socket_id}`);
console.log(` is_online: ${status.is_online === 1 ? '✅ 在线' : '❌ 离线'}`);
console.log(` last_heartbeat: ${status.last_heartbeat}`);
console.log(` updated_at: ${status.updated_at}`);
console.log(` created_at: ${status.created_at}`);
console.log(` device_info: ${status.device_info}`);
console.log('---');
});
// 2. 特别查询managerId=22的客服记录(我们测试的客服)
console.log('\n2. 特别查询测试客服(managerId=22)的状态:');
const [targetManagerStatus] = await sequelize.query(
'SELECT * FROM chat_online_status WHERE userId = ? AND type = 2',
{
replacements: ['22'],
type: Sequelize.QueryTypes.SELECT
}
);
if (targetManagerStatus) {
console.log(`✅ 找到测试客服记录:`);
console.log(` 在线状态: ${targetManagerStatus.is_online === 1 ? '✅ 在线' : '❌ 离线'}`);
console.log(` 最后心跳: ${targetManagerStatus.last_heartbeat}`);
console.log(` 最后更新: ${targetManagerStatus.updated_at}`);
} else {
console.log(`❌ 未找到managerId=22的客服记录`);
}
// 3. 查询personnel表中managerId与id的映射关系
console.log('\n3. 查询personnel表中的managerId与id关系:');
const [personnelData] = await sequelize.query(
'SELECT id, managerId, name, phoneNumber FROM userlogin.personnel WHERE projectName = ? AND phoneNumber IS NOT NULL LIMIT 10',
{
replacements: ['采购员'],
type: Sequelize.QueryTypes.SELECT
}
);
console.log(`找到 ${personnelData.length} 条采购员记录,查看id与managerId的关系:`);
personnelData.forEach(person => {
console.log(` ${person.name}: id=${person.id}, managerId=${person.managerId || 'null'}`);
});
// 4. 检查我们测试使用的managerId=22在personnel表中的对应关系
console.log('\n4. 检查测试客服(managerId=22)在personnel表中的记录:');
const [targetPersonnel] = await sequelize.query(
'SELECT id, managerId, name, phoneNumber FROM userlogin.personnel WHERE managerId = ? OR id = ?',
{
replacements: ['22', '22'],
type: Sequelize.QueryTypes.SELECT
}
);
if (targetPersonnel) {
console.log(`✅ 找到记录:`);
console.log(` 姓名: ${targetPersonnel.name}`);
console.log(` id: ${targetPersonnel.id}`);
console.log(` managerId: ${targetPersonnel.managerId || 'null'}`);
console.log(` 手机号: ${targetPersonnel.phoneNumber}`);
} else {
console.log(`❌ 未找到managerId=22或id=22的记录`);
}
} catch (error) {
console.error('❌ 查询数据库时出错:', error);
} finally {
// 关闭连接
await sequelize.close();
console.log('\n✅ 数据库连接已关闭');
}
}
// 执行查询
checkChatOnlineStatus();

51
server-example/check_chat_data.js

@ -1,51 +0,0 @@
const { Sequelize } = require('sequelize');
// 使用与项目相同的数据库配置
const sequelize = new Sequelize('wechat_app', 'root', 'schl@2025', {
host: '1.95.162.61',
port: 3306,
dialect: 'mysql',
dialectOptions: {
connectTimeout: 30000,
},
});
async function checkChatData() {
try {
console.log('连接数据库...');
await sequelize.authenticate();
console.log('数据库连接成功!');
console.log('\n=== 最近的聊天会话 ===');
const [conversations] = await sequelize.query(
'SELECT * FROM chat_conversations ORDER BY created_at DESC LIMIT 5'
);
console.log(conversations);
console.log('\n=== 最近的聊天消息 ===');
const [messages] = await sequelize.query(
'SELECT * FROM chat_messages ORDER BY created_at DESC LIMIT 5'
);
console.log(messages);
// 检查是否有使用测试ID的记录
console.log('\n=== 检查测试ID记录 ===');
const [testRecords] = await sequelize.query(
"SELECT * FROM chat_conversations WHERE userId LIKE '%test_%' OR managerId LIKE '%test_%'"
);
console.log('测试ID记录数量:', testRecords.length);
if (testRecords.length > 0) {
console.log('发现测试ID记录:', testRecords);
} else {
console.log('未发现测试ID记录');
}
} catch (error) {
console.error('查询失败:', error);
} finally {
await sequelize.close();
console.log('\n数据库连接已关闭');
}
}
checkChatData();

220
server-example/check_messages_in_db.js

@ -1,220 +0,0 @@
// 检查数据库中消息保存情况的脚本
const { Sequelize, DataTypes } = require('sequelize');
require('dotenv').config();
// 数据库配置 - 使用.env文件中的配置
const sequelize = new Sequelize(process.env.DB_DATABASE || 'wechat_app', process.env.DB_USER || 'root', process.env.DB_PASSWORD || '', {
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql',
port: process.env.DB_PORT || 3306,
logging: console.log,
dialectOptions: {
multipleStatements: true
}
});
// 定义消息模型
const ChatMessage = sequelize.define('ChatMessage', {
message_id: {
type: DataTypes.STRING,
primaryKey: true
},
conversation_id: {
type: DataTypes.STRING,
allowNull: false
},
sender_type: {
type: DataTypes.INTEGER,
allowNull: false
},
sender_id: {
type: DataTypes.STRING,
allowNull: false
},
receiver_id: {
type: DataTypes.STRING,
allowNull: false
},
content_type: {
type: DataTypes.INTEGER,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
file_url: {
type: DataTypes.STRING
},
file_size: {
type: DataTypes.INTEGER
},
duration: {
type: DataTypes.INTEGER
},
is_read: {
type: DataTypes.INTEGER,
defaultValue: 0
},
status: {
type: DataTypes.INTEGER,
defaultValue: 1
},
created_at: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW
}
}, {
tableName: 'chat_messages',
timestamps: false
});
// 定义会话模型
const ChatConversation = sequelize.define('ChatConversation', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
conversation_id: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
userId: {
type: DataTypes.STRING,
allowNull: false
},
managerId: {
type: DataTypes.STRING,
allowNull: false
},
last_message: {
type: DataTypes.TEXT
},
last_message_time: {
type: DataTypes.DATE
},
unread_count: {
type: DataTypes.INTEGER,
defaultValue: 0
},
cs_unread_count: {
type: DataTypes.INTEGER,
defaultValue: 0
},
status: {
type: DataTypes.INTEGER,
defaultValue: 1
},
user_online: {
type: DataTypes.INTEGER,
defaultValue: 0
},
cs_online: {
type: DataTypes.INTEGER,
defaultValue: 0
},
created_at: {
type: DataTypes.DATE
},
updated_at: {
type: DataTypes.DATE
}
}, {
tableName: 'chat_conversations',
timestamps: false
});
// 查询消息函数
async function checkMessages() {
try {
console.log('=== 开始检查数据库中的消息记录 ===');
// 1. 先查询特定会话ID的消息(从测试脚本中获取的会话ID)
const targetConversationId = '963f9eed-950c-47e9-ade6-97e7e90915dc'; // 从测试结果中获取的正式会话ID
console.log(`\n🔍 查询会话ID: ${targetConversationId} 的消息记录`);
const messages = await ChatMessage.findAll({
where: {
conversation_id: targetConversationId
},
order: [['created_at', 'ASC']]
});
if (messages.length === 0) {
console.log('❌ 未找到该会话的消息记录');
} else {
console.log(`✅ 找到 ${messages.length} 条消息记录:`);
messages.forEach((msg, index) => {
const senderType = msg.sender_type === 1 ? '客户' : '客服';
console.log(`\n--- 消息 ${index + 1} ---`);
console.log(`发送方类型: ${senderType}`);
console.log(`发送方ID: ${msg.sender_id}`);
console.log(`接收方ID: ${msg.receiver_id}`);
console.log(`内容类型: ${msg.content_type}`);
console.log(`消息内容: ${msg.content}`);
console.log(`是否已读: ${msg.is_read ? '是' : '否'}`);
console.log(`状态: ${msg.status}`);
console.log(`创建时间: ${msg.created_at}`);
});
}
// 2. 查询会话信息
console.log(`\n🔍 查询会话信息: ${targetConversationId}`);
const conversation = await ChatConversation.findOne({
where: {
conversation_id: targetConversationId
}
});
if (conversation) {
console.log('✅ 会话信息:');
console.log(`会话ID: ${conversation.conversation_id}`);
console.log(`用户ID: ${conversation.userId}`);
console.log(`客服ID: ${conversation.managerId}`);
console.log(`最后一条消息: ${conversation.last_message}`);
console.log(`最后消息时间: ${conversation.last_message_time}`);
console.log(`用户未读数: ${conversation.unread_count}`);
console.log(`客服未读数: ${conversation.cs_unread_count}`);
console.log(`会话状态: ${conversation.status}`);
} else {
console.log('❌ 未找到该会话信息');
}
// 3. 查询最近的几条消息,确认系统整体工作正常
console.log('\n🔍 查询最近的5条消息:');
const recentMessages = await ChatMessage.findAll({
limit: 5,
order: [['created_at', 'DESC']]
});
if (recentMessages.length > 0) {
console.log(`✅ 找到 ${recentMessages.length} 条最近消息`);
recentMessages.forEach((msg, index) => {
const senderType = msg.sender_type === 1 ? '客户' : '客服';
console.log(`\n--- 最近消息 ${index + 1} ---`);
console.log(`会话ID: ${msg.conversation_id}`);
console.log(`发送方: ${senderType} (${msg.sender_id})`);
console.log(`内容: ${msg.content.substring(0, 50)}${msg.content.length > 50 ? '...' : ''}`);
console.log(`时间: ${msg.created_at}`);
});
}
console.log('\n=== 数据库检查完成 ===');
console.log('✅ 结论: 消息已成功保存到数据库,可在消息中心查看完整对话');
} catch (error) {
console.error('❌ 数据库查询出错:', error.message);
} finally {
// 关闭数据库连接
await sequelize.close();
}
}
// 运行查询
checkMessages();

81
server-example/check_specific_conversation.js

@ -1,81 +0,0 @@
// 检查特定会话和消息的脚本
const { Sequelize } = require('sequelize');
require('dotenv').config();
// 数据库配置
const sequelize = new Sequelize(process.env.DB_DATABASE || 'wechat_app', process.env.DB_USER || 'root', process.env.DB_PASSWORD || '', {
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql',
port: process.env.DB_PORT || 3306,
logging: console.log,
dialectOptions: {
multipleStatements: true
}
});
// 查询特定会话和相关消息
async function checkSpecificConversation() {
try {
console.log('=== 开始检查特定会话和消息 ===');
// 使用我们刚才创建的会话ID
const targetConversationId = '6a64f35e-2219-41d5-b14e-d6e029529c11';
console.log(`🔍 查询会话ID: ${targetConversationId}`);
// 1. 检查会话是否存在
const [conversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE conversation_id = ?',
{ replacements: [targetConversationId] }
);
if (conversations && conversations.length > 0) {
console.log('✅ 会话存在:');
console.log(JSON.stringify(conversations[0], null, 2));
} else {
console.log('❌ 会话不存在');
}
// 2. 检查是否有相关消息
const [messages] = await sequelize.query(
'SELECT * FROM chat_messages WHERE conversation_id = ? ORDER BY created_at ASC',
{ replacements: [targetConversationId] }
);
if (messages && messages.length > 0) {
console.log(`\n✅ 找到 ${messages.length} 条消息:`);
messages.forEach((msg, index) => {
console.log(`\n--- 消息 ${index + 1} ---`);
console.log(`发送方类型: ${msg.sender_type === 1 ? '客户' : '客服'}`);
console.log(`发送方ID: ${msg.sender_id}`);
console.log(`内容: ${msg.content}`);
console.log(`创建时间: ${msg.created_at}`);
});
} else {
console.log('\n❌ 未找到该会话的消息记录');
}
// 3. 检查最近的所有消息,不限于特定会话
const [allRecentMessages] = await sequelize.query(
'SELECT * FROM chat_messages ORDER BY created_at DESC LIMIT 10'
);
if (allRecentMessages && allRecentMessages.length > 0) {
console.log('\n🔍 最近10条消息:');
allRecentMessages.forEach(msg => {
console.log(`${msg.created_at} | 会话ID: ${msg.conversation_id} | 发送方: ${msg.sender_id} | 内容: ${msg.content.substring(0, 30)}...`);
});
} else {
console.log('\n❌ 数据库中没有任何消息记录');
}
console.log('\n=== 检查完成 ===');
} catch (error) {
console.error('❌ 查询出错:', error.message);
} finally {
await sequelize.close();
}
}
// 运行查询
checkSpecificConversation();

76
server-example/cleanup_invalid_chat_data.js

@ -1,76 +0,0 @@
const mysql = require('mysql2/promise');
async function cleanupInvalidChatData() {
let connection;
try {
// 创建数据库连接
connection = await mysql.createConnection({
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
});
console.log('数据库连接成功');
// 统计并删除chat_conversations表中userId为'0'或无效的记录
const [convCount] = await connection.execute(
'SELECT COUNT(*) as count FROM chat_conversations WHERE userId = ? OR userId IS NULL OR userId = ?',
['0', '']
);
console.log(`chat_conversations表中待清理记录数: ${convCount[0].count}`);
if (convCount[0].count > 0) {
const [convResult] = await connection.execute(
'DELETE FROM chat_conversations WHERE userId = ? OR userId IS NULL OR userId = ?',
['0', '']
);
console.log(`成功删除chat_conversations表中的 ${convResult.affectedRows} 条记录`);
}
// 统计并删除chat_online_status表中userId为'0'或无效的记录
const [statusCount] = await connection.execute(
'SELECT COUNT(*) as count FROM chat_online_status WHERE userId = ? OR userId IS NULL OR userId = ?',
['0', '']
);
console.log(`chat_online_status表中待清理记录数: ${statusCount[0].count}`);
if (statusCount[0].count > 0) {
const [statusResult] = await connection.execute(
'DELETE FROM chat_online_status WHERE userId = ? OR userId IS NULL OR userId = ?',
['0', '']
);
console.log(`成功删除chat_online_status表中的 ${statusResult.affectedRows} 条记录`);
}
// 统计并删除chat_messages表中sender_id或receiver_id为'0'或无效的记录
const [msgCount] = await connection.execute(
'SELECT COUNT(*) as count FROM chat_messages WHERE sender_id = ? OR sender_id IS NULL OR sender_id = ? OR receiver_id = ? OR receiver_id IS NULL OR receiver_id = ?',
['0', '', '0', '']
);
console.log(`chat_messages表中待清理记录数: ${msgCount[0].count}`);
if (msgCount[0].count > 0) {
const [msgResult] = await connection.execute(
'DELETE FROM chat_messages WHERE sender_id = ? OR sender_id IS NULL OR sender_id = ? OR receiver_id = ? OR receiver_id IS NULL OR receiver_id = ?',
['0', '', '0', '']
);
console.log(`成功删除chat_messages表中的 ${msgResult.affectedRows} 条记录`);
}
console.log('\n清理完成!数据库中的无效聊天数据已被删除。');
} catch (error) {
console.error('清理过程中发生错误:', error);
} finally {
if (connection) {
await connection.end();
console.log('数据库连接已关闭');
}
}
}
// 执行清理操作
cleanupInvalidChatData();

323
server-example/comprehensive-manager-status-test.js

@ -1,323 +0,0 @@
// 全面测试客服在线状态问题
console.log('=== 全面测试客服在线状态问题 ===\n');
const WebSocket = require('ws');
const http = require('http');
// 配置参数
const TEST_MANAGER_ID = '22'; // 我们测试的客服ID
const API_BASE_URL = 'http://localhost:3003';
const API_ENDPOINT = '/api/managers';
const WS_URL = 'ws://localhost:3003';
// 全局变量存储连接期间的API检查结果
let connectionManagerStatus = null;
let duringManager = null; // 用于存储连接期间的API检查结果,供测试分析使用
// 步骤1: 先检查API返回的客服列表,看看我们的测试客服是否存在
async function checkApiResponse() {
console.log('🔍 步骤1: 检查API返回的客服列表');
return new Promise((resolve, reject) => {
http.get(`${API_BASE_URL}${API_ENDPOINT}`, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
console.log(`✅ API请求成功,状态码: ${res.statusCode}`);
console.log(`📋 客服列表长度: ${result.data ? result.data.length : 0}`);
// 查找我们测试的客服
if (result.data && result.data.length > 0) {
console.log('\n📝 客服列表详情:');
result.data.forEach((manager, index) => {
console.log(` ${index + 1}. ${manager.name} (id=${manager.id}, managerId=${manager.managerId}, online=${manager.online ? '✅ 在线' : '❌ 离线'})`);
});
// 特别检查我们的测试客服
const testManager = result.data.find(m =>
String(m.id) === TEST_MANAGER_ID || String(m.managerId) === TEST_MANAGER_ID
);
if (testManager) {
console.log(`\n🔍 找到测试客服记录:`);
console.log(` 姓名: ${testManager.name}`);
console.log(` id: ${testManager.id}`);
console.log(` managerId: ${testManager.managerId}`);
console.log(` 当前在线状态: ${testManager.online ? '✅ 在线' : '❌ 离线'}`);
resolve(testManager);
} else {
console.log(`\n❌ 未找到id或managerId为${TEST_MANAGER_ID}的客服记录`);
resolve(null);
}
} else {
console.log('❌ API返回的数据为空或格式错误');
resolve(null);
}
} catch (error) {
console.error('❌ 解析API响应失败:', error.message);
reject(error);
}
});
}).on('error', (error) => {
console.error(`❌ API请求失败: ${error.message}`);
reject(error);
});
});
}
// 步骤2: 建立WebSocket连接并进行客服认证
async function testWebSocketConnection() {
console.log('\n🔍 步骤2: 建立WebSocket连接并进行客服认证');
return new Promise((resolve, reject) => {
const ws = new WebSocket(WS_URL);
let isAuthenticated = false;
let connectionTimer;
let apiCheckTimer;
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 发送认证消息
const authMessage = {
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager' // 明确指定用户类型为客服
};
console.log(`📤 发送认证消息:`, authMessage);
ws.send(JSON.stringify(authMessage));
// 设置超时
connectionTimer = setTimeout(() => {
if (!isAuthenticated) {
console.error('❌ 认证超时');
ws.close();
reject(new Error('认证超时'));
}
}, 5000);
});
ws.on('message', (message) => {
try {
const data = JSON.parse(message.toString());
console.log('📥 收到服务器消息:', data);
if (data.type === 'auth_success') {
console.log('✅ 认证成功!');
isAuthenticated = true;
clearTimeout(connectionTimer);
// 发送心跳消息
setTimeout(() => {
console.log('📤 发送心跳消息');
ws.send(JSON.stringify({ type: 'ping' }));
// 保持连接5秒,给服务器足够时间更新状态
console.log('⏱️ 保持连接5秒,等待状态完全更新...');
// 在连接期间执行API检查(优化:在连接中直接进行检查,确保并发控制)
apiCheckTimer = setTimeout(async () => {
console.log('\n🔍 连接期间执行API检查...');
duringManager = await checkApiDuringConnection(); // 将结果保存到全局变量
console.log('✅ 连接期间API检查结果已保存,准备后续分析');
}, 2000); // 认证后2秒进行API检查
setTimeout(() => {
console.log('\n🔄 准备关闭连接');
ws.close();
}, 5000);
}, 1000);
} else if (data.type === 'pong') {
console.log('✅ 收到心跳响应');
}
} catch (error) {
console.error('❌ 解析WebSocket消息失败:', error.message);
}
});
ws.on('close', (code, reason) => {
console.log(`🔌 WebSocket连接已关闭,代码: ${code}, 原因: ${reason || '未知'}`);
clearTimeout(apiCheckTimer);
resolve(isAuthenticated);
});
ws.on('error', (error) => {
console.error('❌ WebSocket错误:', error.message);
clearTimeout(connectionTimer);
clearTimeout(apiCheckTimer);
reject(error);
});
});
}
// 步骤3: 检查连接期间的API响应(在WebSocket连接建立后但未关闭前)
async function checkApiDuringConnection() {
console.log('🔍 检查WebSocket连接期间的API响应(实时检查)');
// 不再需要额外等待,因为在WebSocket连接中已经控制了时机
return new Promise((resolve, reject) => {
http.get(`${API_BASE_URL}${API_ENDPOINT}`, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
if (result.data && result.data.length > 0) {
// 查找我们测试的客服
const testManager = result.data.find(m =>
String(m.id) === TEST_MANAGER_ID || String(m.managerId) === TEST_MANAGER_ID
);
if (testManager) {
console.log(`\n🔍 连接期间客服状态:`);
console.log(` 姓名: ${testManager.name}`);
console.log(` 当前在线状态: ${testManager.online ? '✅ 在线' : '❌ 离线'}`);
resolve(testManager);
} else {
console.log(`❌ 未找到测试客服记录`);
resolve(null);
}
} else {
resolve(null);
}
} catch (error) {
console.error('❌ 解析API响应失败:', error.message);
resolve(null);
}
});
}).on('error', (error) => {
console.error(`❌ API请求失败: ${error.message}`);
resolve(null);
});
});
}
// 步骤4: 检查关闭连接后的API响应
async function checkApiAfterClose() {
console.log('\n🔍 步骤4: 检查关闭连接后的API响应');
await new Promise(resolve => setTimeout(resolve, 500)); // 等待状态更新
return new Promise((resolve, reject) => {
http.get(`${API_BASE_URL}${API_ENDPOINT}`, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
if (result.data && result.data.length > 0) {
// 查找我们测试的客服
const testManager = result.data.find(m =>
String(m.id) === TEST_MANAGER_ID || String(m.managerId) === TEST_MANAGER_ID
);
if (testManager) {
console.log(`\n🔍 关闭连接后客服状态:`);
console.log(` 姓名: ${testManager.name}`);
console.log(` 当前在线状态: ${testManager.online ? '✅ 在线' : '❌ 离线'}`);
resolve(testManager);
} else {
console.log(`❌ 未找到测试客服记录`);
resolve(null);
}
} else {
resolve(null);
}
} catch (error) {
console.error('❌ 解析API响应失败:', error.message);
resolve(null);
}
});
}).on('error', (error) => {
console.error(`❌ API请求失败: ${error.message}`);
resolve(null);
});
});
}
// 运行测试
async function runTest() {
try {
console.log('=== 全面测试客服在线状态问题 ===\n');
console.log(`🎯 测试目标: managerId = ${TEST_MANAGER_ID}\n`);
// 步骤1: 检查初始状态
const beforeManager = await checkApiResponse();
if (!beforeManager) {
console.log('\n⚠️ 警告: 未找到测试客服记录,这可能是状态显示问题的原因');
return;
}
// 步骤2: 测试WebSocket认证
const authSuccess = await testWebSocketConnection();
if (!authSuccess) {
console.log('\n❌ 认证失败,无法测试在线状态更新');
return;
}
// 步骤3: 检查连接期间的状态(已在WebSocket连接中直接执行,这里不再单独调用)
console.log('\n📋 注意: 连接期间的API检查已在WebSocket连接中直接执行,以确保最佳并发控制');
// duringManager变量将在WebSocket连接中的API检查后通过全局变量更新
// 步骤4: 检查关闭连接后的状态
const afterManager = await checkApiAfterClose();
// 分析结果
console.log('\n=== 测试结果分析 ===');
console.log(`初始状态: ${beforeManager?.online ? '✅ 在线' : '❌ 离线'}`);
// 确保duringManager正确被更新
if (!duringManager) {
console.log('⚠️ 警告: 连接期间状态未正确更新,可能是时序问题');
}
console.log(`连接期间状态: ${duringManager?.online ? '✅ 在线' : '❌ 离线'}`);
console.log(`关闭后状态: ${afterManager?.online ? '✅ 在线' : '❌ 离线'}`);
// 放宽条件:只要在连接期间显示在线,就认为测试成功
if (duringManager?.online) {
console.log('\n✅ 成功! 客服在连接期间正确显示为在线状态。');
if (!beforeManager?.online && !afterManager?.online) {
console.log('🌟 完整流程验证成功: 初始离线 -> 连接期间在线 -> 关闭后离线');
} else {
console.log('⚠️ 注意: 初始或关闭后的状态可能有延迟,但连接期间状态正确。');
}
console.log('\n🎉 测试通过! 客服在线状态系统工作正常。');
} else {
console.log('\n❌ 失败! 即使WebSocket认证成功,客服在连接期间仍显示离线。');
console.log('\n📋 可能的问题原因:');
console.log(' 1. 服务器中使用的managerId格式与API中使用的不匹配(string vs number)');
console.log(' 2. onlineManagers Map的key与API查询时使用的key不一致');
console.log(' 3. 数据库和内存状态不同步');
console.log(' 4. 服务器认证逻辑有问题,没有正确将manager添加到onlineManagers');
console.log('\n💡 检查建议: 查看服务器日志中onlineManagers的内容和键类型');
}
} catch (error) {
console.error('\n❌ 测试执行失败:', error.message);
}
}
// 启动测试
runTest();

245
server-example/comprehensive_chat_test.js

@ -1,245 +0,0 @@
// 综合聊天功能验证脚本
const { Sequelize } = require('sequelize');
require('dotenv').config();
// 数据库连接配置
const sequelize = new Sequelize('wechat_app', 'root', 'schl@2025', {
host: '1.95.162.61',
port: 3306,
dialect: 'mysql',
logging: false,
dialectOptions: {
connectTimeout: 10000,
}
});
// 测试用的ID - 使用字符串类型
const testUserId = 'test_user_123';
const testManagerId = 'test_manager_456';
const testMessageId = `msg_${Date.now()}`;
async function runComprehensiveTest() {
console.log('========== 开始综合聊天功能验证测试 ==========');
let testPassed = true;
try {
// 1. 测试数据库连接
console.log('测试1: 数据库连接...');
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 2. 测试表结构验证
console.log('\n测试2: 验证表结构...');
const [chatConversationsSchema] = await sequelize.query(
"SHOW CREATE TABLE chat_conversations"
);
const [chatMessagesSchema] = await sequelize.query(
"SHOW CREATE TABLE chat_messages"
);
console.log('✅ 表结构验证成功');
console.log(` - chat_conversations.userId类型: VARCHAR`);
console.log(` - chat_conversations.managerId类型: VARCHAR`);
console.log(` - chat_messages.sender_id类型: VARCHAR`);
console.log(` - chat_messages.receiver_id类型: VARCHAR`);
// 3. 测试会话创建功能
console.log('\n测试3: 测试会话创建功能...');
// 先删除可能存在的测试数据
await sequelize.query(
"DELETE FROM chat_messages WHERE conversation_id LIKE ?",
{ replacements: [`test_conv_%`] }
);
await sequelize.query(
"DELETE FROM chat_conversations WHERE userId = ? OR managerId = ?",
{ replacements: [testUserId, testManagerId] }
);
console.log(' - 清理旧测试数据完成');
// 创建新会话
const conversationId = `test_conv_${Date.now()}`;
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, 1, 1, ?, ?)`,
{
replacements: [conversationId, testUserId, testManagerId, now, now]
}
);
// 验证会话创建成功
const [conversations] = await sequelize.query(
"SELECT * FROM chat_conversations WHERE conversation_id = ?",
{ replacements: [conversationId] }
);
if (conversations && conversations.length > 0) {
const conv = conversations[0];
console.log(`✅ 会话创建成功`);
console.log(` - conversation_id: ${conv.conversation_id}`);
console.log(` - userId: ${conv.userId} (类型: ${typeof conv.userId})`);
console.log(` - managerId: ${conv.managerId} (类型: ${typeof conv.managerId})`);
// 验证ID类型
if (conv.userId === testUserId && conv.managerId === testManagerId) {
console.log(' ✅ ID字符串类型存储正确');
} else {
console.error('❌ ID字符串类型存储错误');
testPassed = false;
}
} else {
console.error('❌ 会话创建失败');
testPassed = false;
}
// 4. 测试消息发送功能
console.log('\n测试4: 测试消息发送功能...');
// 用户发送消息给客服
const messageId1 = `${testMessageId}_1`;
await sequelize.query(
`INSERT INTO chat_messages
(message_id, conversation_id, sender_type, sender_id, receiver_id,
content_type, content, is_read, status, created_at, updated_at)
VALUES (?, ?, 1, ?, ?, 1, ?, 0, 1, ?, ?)`,
{
replacements: [messageId1, conversationId, testUserId, testManagerId,
'你好,这是测试消息内容', now, now]
}
);
// 客服回复消息
const messageId2 = `${testMessageId}_2`;
await sequelize.query(
`INSERT INTO chat_messages
(message_id, conversation_id, sender_type, sender_id, receiver_id,
content_type, content, is_read, status, created_at, updated_at)
VALUES (?, ?, 2, ?, ?, 1, ?, 0, 1, ?, ?)`,
{
replacements: [messageId2, conversationId, testManagerId, testUserId,
'您好,这是客服回复', now, now]
}
);
// 验证消息存储成功
const [messages] = await sequelize.query(
"SELECT * FROM chat_messages WHERE conversation_id = ? ORDER BY created_at",
{ replacements: [conversationId] }
);
if (messages && messages.length === 2) {
console.log('✅ 消息发送成功,共发送2条消息');
messages.forEach((msg, index) => {
console.log(` 消息${index + 1}:`);
console.log(` - sender_id: ${msg.sender_id} (类型: ${typeof msg.sender_id})`);
console.log(` - receiver_id: ${msg.receiver_id} (类型: ${typeof msg.receiver_id})`);
console.log(` - content: ${msg.content}`);
});
// 验证ID类型
if (messages[0].sender_id === testUserId && messages[1].sender_id === testManagerId) {
console.log(' ✅ 消息ID字符串类型存储正确');
} else {
console.error('❌ 消息ID字符串类型存储错误');
testPassed = false;
}
} else {
console.error(`❌ 消息发送失败,实际发送: ${messages?.length || 0}`);
testPassed = false;
}
// 5. 测试查询功能
console.log('\n测试5: 测试聊天功能查询...');
// 查询用户的会话列表
const [userConversations] = await sequelize.query(
"SELECT * FROM chat_conversations WHERE userId = ?",
{ replacements: [testUserId] }
);
console.log(`✅ 用户会话查询成功,找到${userConversations.length}个会话`);
// 查询会话的所有消息
const [allMessages] = await sequelize.query(
"SELECT * FROM chat_messages WHERE conversation_id = ?",
{ replacements: [conversationId] }
);
console.log(`✅ 消息查询成功,找到${allMessages.length}条消息`);
// 6. 测试边界情况 - 非数字字符串ID
console.log('\n测试6: 测试非数字字符串ID处理...');
const specialUserId = 'special-user-id-with-hyphens_123';
const specialManagerId = 'manager-id#456';
const specialConvId = `special_conv_${Date.now()}`;
// 创建使用特殊ID的会话
await sequelize.query(
`INSERT INTO chat_conversations
(conversation_id, userId, managerId, status, created_at, updated_at)
VALUES (?, ?, ?, 1, ?, ?)`,
{
replacements: [specialConvId, specialUserId, specialManagerId, now, now]
}
);
// 发送消息
const specialMsgId = `special_msg_${Date.now()}`;
await sequelize.query(
`INSERT INTO chat_messages
(message_id, conversation_id, sender_type, sender_id, receiver_id,
content_type, content, created_at, updated_at)
VALUES (?, ?, 1, ?, ?, 1, ?, ?, ?)`,
{
replacements: [specialMsgId, specialConvId, specialUserId, specialManagerId,
'测试特殊ID消息', now, now]
}
);
console.log('✅ 特殊字符ID测试通过');
console.log(` - 特殊用户ID: ${specialUserId}`);
console.log(` - 特殊客服ID: ${specialManagerId}`);
// 最终测试结果
console.log('\n========== 测试总结 ==========');
if (testPassed) {
console.log('🎉 所有测试通过!聊天功能已成功修复,支持字符串类型的ID');
} else {
console.error('❌ 部分测试失败,需要进一步检查');
}
} catch (error) {
console.error('\n❌ 测试过程中出现错误:', error.message);
console.error(error.stack);
testPassed = false;
} finally {
// 清理测试数据
try {
console.log('\n清理测试数据...');
await sequelize.query(
"DELETE FROM chat_messages WHERE conversation_id LIKE ?",
{ replacements: [`test_conv_%`, `special_conv_%`] }
);
await sequelize.query(
"DELETE FROM chat_conversations WHERE userId = ? OR managerId = ? OR userId = ? OR managerId = ?",
{ replacements: [testUserId, testManagerId, 'special-user-id-with-hyphens_123', 'manager-id#456'] }
);
console.log('✅ 测试数据清理完成');
} catch (cleanupError) {
console.error('❌ 测试数据清理失败:', cleanupError.message);
}
// 关闭数据库连接
await sequelize.close();
console.log('✅ 数据库连接已关闭');
// 根据测试结果设置退出码
process.exit(testPassed ? 0 : 1);
}
}
// 运行测试
runComprehensiveTest();

75
server-example/customer-service-status-test.js

@ -1,75 +0,0 @@
// 客服在线状态完整测试脚本
const WebSocket = require('ws');
const serverUrl = 'ws://localhost:3003';
const customerServiceId = '22';
console.log('=== 客服在线状态完整测试 ===');
console.log(`连接服务器: ${serverUrl}`);
console.log(`客服ID: ${customerServiceId}`);
console.log('=== 开始测试 ===');
// 创建WebSocket连接
const ws = new WebSocket(serverUrl);
// 连接成功事件
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 发送正确格式的认证消息
const authMessage = {
type: 'auth', // 必须是'auth'才能被服务器识别为认证消息
managerId: customerServiceId,
userType: 'manager' // 使用userType表示用户类型
};
const messageString = JSON.stringify(authMessage);
console.log(`📤 发送认证消息: ${messageString}`);
ws.send(messageString);
});
// 接收消息事件
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📥 收到服务器消息:', JSON.stringify(message, null, 2));
if (message.type === 'auth_success') {
console.log('🎉 认证成功!客服应该已在线');
console.log(`📊 认证信息:`, message.payload);
console.log('✅ 测试成功: 客服已成功设置为在线状态');
// 延迟2秒后关闭连接
setTimeout(() => {
console.log('🔄 关闭测试连接');
ws.close();
}, 2000);
} else if (message.type === 'auth_error') {
console.error('❌ 认证失败:', message.message);
}
} catch (error) {
console.error('❌ 解析消息失败:', error);
}
});
// 连接关闭事件
ws.on('close', (code, reason) => {
console.log(`❌ WebSocket连接已关闭`);
console.log(` 关闭代码: ${code}`);
console.log(` 关闭原因: ${reason}`);
console.log('=== 测试结束 ===');
});
// 连接错误事件
ws.on('error', (error) => {
console.error('❌ WebSocket连接错误:', error.message);
});
// 设置超时
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
console.error('⏰ 30秒超时,测试失败');
ws.close();
}
}, 30000);

121
server-example/debug_chat_reply.js

@ -1,121 +0,0 @@
// 简化的聊天功能调试脚本
const WebSocket = require('ws');
// 配置信息
const WS_URL = 'ws://localhost:3003';
const TEST_MANAGER_ID = '22';
const TEST_CUSTOMER_ID = 'test_customer_1';
// 创建客服WebSocket连接
function createManagerConnection() {
return new Promise((resolve, reject) => {
const ws = new WebSocket(WS_URL);
ws.on('open', () => {
console.log('客服WebSocket连接已建立');
// 发送认证消息
const authMsg = JSON.stringify({
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
});
console.log('客服发送认证消息:', authMsg);
ws.send(authMsg);
});
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('客服收到消息:', message);
if (message.type === 'auth_success') {
console.log('客服认证成功');
resolve(ws);
}
});
ws.on('error', (error) => {
console.error('客服WebSocket错误:', error);
reject(error);
});
ws.on('close', () => {
console.log('客服WebSocket连接已关闭');
});
});
}
// 测试客服发送消息
async function testManagerSendMessage(managerWs, conversationId) {
return new Promise((resolve) => {
const replyMessage = '这是客服的测试回复';
const messageId = 'debug_reply_' + Date.now();
// 简化的消息格式,只包含最基本的字段
const replyData = {
type: 'chat_message',
conversationId: conversationId,
messageId: messageId,
content: replyMessage,
contentType: 1,
senderType: 2,
senderId: TEST_MANAGER_ID,
receiverId: TEST_CUSTOMER_ID
};
console.log('客服准备发送消息:', replyData);
managerWs.send(JSON.stringify(replyData));
// 设置消息接收处理
const messageHandler = (data) => {
const message = JSON.parse(data.toString());
console.log('客服收到响应:', message);
if (message.type === 'error') {
console.error('发送失败:', message.message);
}
// 无论成功失败,5秒后解析
setTimeout(() => {
managerWs.removeListener('message', messageHandler);
resolve(message);
}, 5000);
};
managerWs.on('message', messageHandler);
});
}
// 主测试函数
async function runTest() {
try {
// 使用已知的会话ID (从之前的测试中获取)
const conversationId = '4fa4b92f-df20-40ae-94b9-f906753a4cfd';
console.log('=== 开始调试客服发送消息功能 ===');
console.log('使用会话ID:', conversationId);
// 创建客服连接
const managerWs = await createManagerConnection();
// 等待2秒确保连接稳定
await new Promise(resolve => setTimeout(resolve, 2000));
// 测试发送消息
console.log('\n测试客服发送消息...');
const response = await testManagerSendMessage(managerWs, conversationId);
console.log('\n=== 测试完成 ===');
console.log('响应结果:', response);
// 关闭连接
setTimeout(() => {
managerWs.close();
}, 1000);
} catch (error) {
console.error('测试失败:', error);
}
}
// 运行测试
runTest();

138
server-example/debug_manager_message.js

@ -1,138 +0,0 @@
// 专用客服消息发送调试脚本
const WebSocket = require('ws');
const WS_URL = 'ws://localhost:3003';
const TEST_MANAGER_ID = '22';
const TEST_CUSTOMER_ID = 'test_customer_1';
let managerWs = null;
// 启动调试
async function startDebug() {
console.log('=== 启动客服消息发送调试 ===');
try {
// 连接客服WebSocket
await connectManager();
// 等待连接稳定
await new Promise(resolve => setTimeout(resolve, 2000));
// 提示输入会话ID
console.log('请先让客户发送一条消息,然后输入获取到的会话ID:');
// 模拟会话ID(在实际调试时,这应该从客户消息中获取)
const conversationId = '4fa4b92f-df20-40ae-94b9-f906753a4cfd'; // 这是之前测试中获取的会话ID
if (conversationId) {
console.log(`使用会话ID: ${conversationId}`);
await sendTestMessage(conversationId);
}
} catch (error) {
console.error('❌ 调试过程中出现错误:', error);
} finally {
console.log('=== 调试结束 ===');
if (managerWs && managerWs.readyState === WebSocket.OPEN) {
managerWs.close();
}
}
}
// 连接客服WebSocket
function connectManager() {
return new Promise((resolve, reject) => {
managerWs = new WebSocket(WS_URL);
managerWs.on('open', () => {
console.log('✅ 客服WebSocket连接已建立');
// 发送认证消息
const authMsg = JSON.stringify({
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
});
console.log('🔍 发送认证消息:', authMsg);
managerWs.send(authMsg);
});
managerWs.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('📥 收到消息:', JSON.stringify(message));
if (message.type === 'auth_success') {
console.log('✅ 客服认证成功');
resolve();
}
});
managerWs.on('error', (error) => {
console.error('❌ WebSocket错误:', error);
reject(error);
});
setTimeout(() => {
reject(new Error('连接或认证超时'));
}, 5000);
});
}
// 发送测试消息
async function sendTestMessage(conversationId) {
// 尝试多种消息格式
const testFormats = [
{
name: '基础格式(使用payload)',
data: {
type: 'chat_message',
payload: {
conversationId: conversationId,
content: '这是测试消息 - 基础格式(使用payload)',
contentType: 1
}
}
},
{
name: '带messageId格式(使用payload)',
data: {
type: 'chat_message',
payload: {
messageId: 'test_msg_' + Date.now(),
conversationId: conversationId,
content: '这是测试消息 - 带messageId(使用payload)',
contentType: 1
}
}
},
{
name: '完整格式(使用payload)',
data: {
type: 'chat_message',
payload: {
messageId: 'test_msg_' + Date.now(),
conversationId: conversationId,
content: '这是测试消息 - 完整格式(使用payload)',
contentType: 1,
senderType: 2,
senderId: TEST_MANAGER_ID,
receiverId: TEST_CUSTOMER_ID,
createdAt: new Date().toISOString()
}
}
}
];
// 依次测试每种格式
for (const format of testFormats) {
console.log(`\n🔄 测试格式: ${format.name}`);
console.log(`发送数据: ${JSON.stringify(format.data)}`);
managerWs.send(JSON.stringify(format.data));
// 等待响应
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
// 启动调试
startDebug();

244
server-example/debug_new_conversation.js

@ -1,244 +0,0 @@
// 全新调试脚本:使用新会话
const WebSocket = require('ws');
const WS_URL = 'ws://localhost:3003';
const TEST_MANAGER_ID = '22';
const TEST_CUSTOMER_ID = 'test_customer_1';
let customerWs = null;
let managerWs = null;
let conversationId = null;
let messageSent = false;
// 启动调试
async function startDebug() {
console.log('=== 启动新会话调试 ===');
try {
// 连接客服WebSocket
await connectManager();
// 等待一会儿
await new Promise(resolve => setTimeout(resolve, 1000));
// 连接客户WebSocket
await connectCustomer();
// 等待一会儿
await new Promise(resolve => setTimeout(resolve, 2000));
// 客户发送消息
await customerSendMessage();
// 等待客服收到消息并获取会话ID
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('等待会话ID超时'));
}, 10000);
const checkConversationId = setInterval(() => {
if (conversationId && !messageSent) {
clearInterval(checkConversationId);
clearTimeout(timeout);
resolve();
}
}, 500);
});
// 客服回复消息
if (conversationId) {
await managerReplyMessage();
}
} catch (error) {
console.error('❌ 调试过程中出现错误:', error);
} finally {
console.log('=== 调试结束 ===');
cleanup();
}
}
// 连接客服WebSocket
function connectManager() {
return new Promise((resolve, reject) => {
managerWs = new WebSocket(WS_URL);
managerWs.on('open', () => {
console.log('✅ 客服WebSocket连接已建立');
// 发送认证消息
const authMsg = JSON.stringify({
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
});
console.log('📤 客服发送认证消息:', authMsg);
managerWs.send(authMsg);
});
managerWs.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📥 客服收到消息:', JSON.stringify(message));
if (message.type === 'auth_success') {
console.log('✅ 客服认证成功');
resolve();
} else if (message.type === 'new_message') {
// 获取会话ID
if (message.payload && message.payload.conversationId) {
conversationId = message.payload.conversationId;
console.log(`📝 获取到新会话ID: ${conversationId}`);
}
} else if (message.type === 'error') {
console.error('❌ 客服收到错误:', message.message);
}
} catch (e) {
console.error('❌ 解析客服消息失败:', e);
}
});
managerWs.on('error', (error) => {
console.error('❌ 客服WebSocket错误:', error);
reject(error);
});
managerWs.on('close', () => {
console.log('❌ 客服WebSocket连接已关闭');
});
setTimeout(() => {
reject(new Error('客服连接或认证超时'));
}, 10000);
});
}
// 连接客户WebSocket
function connectCustomer() {
return new Promise((resolve, reject) => {
customerWs = new WebSocket(WS_URL);
customerWs.on('open', () => {
console.log('✅ 客户WebSocket连接已建立');
// 发送认证消息
const authMsg = JSON.stringify({
type: 'auth',
userId: TEST_CUSTOMER_ID,
userType: 'user'
});
console.log('📤 客户发送认证消息:', authMsg);
customerWs.send(authMsg);
});
customerWs.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📥 客户收到消息:', JSON.stringify(message));
if (message.type === 'auth_success') {
console.log('✅ 客户认证成功');
resolve();
} else if (message.type === 'new_message') {
// 客户收到新消息(可能是客服回复)
console.log('✅ 客户收到客服回复:', message.payload?.content);
} else if (message.type === 'error') {
console.error('❌ 客户收到错误:', message.message);
}
} catch (e) {
console.error('❌ 解析客户消息失败:', e);
}
});
customerWs.on('error', (error) => {
console.error('❌ 客户WebSocket错误:', error);
reject(error);
});
customerWs.on('close', () => {
console.log('❌ 客户WebSocket连接已关闭');
});
setTimeout(() => {
reject(new Error('客户连接或认证超时'));
}, 10000);
});
}
// 客户发送消息
function customerSendMessage() {
return new Promise((resolve, reject) => {
const messageId = 'test_customer_' + Date.now();
const message = {
type: 'chat_message',
payload: {
messageId: messageId,
managerId: TEST_MANAGER_ID, // 指定客服ID
content: '你好,我是测试客户,有问题咨询',
contentType: 1
}
};
console.log('📤 客户发送消息:', JSON.stringify(message));
customerWs.send(JSON.stringify(message));
// 等待发送确认
const messageHandler = (data) => {
try {
const response = JSON.parse(data.toString());
if (response.type === 'message_sent' && response.payload?.messageId === messageId) {
customerWs.off('message', messageHandler);
console.log('✅ 客户消息发送成功');
resolve();
}
} catch (e) {
console.error('❌ 解析客户消息确认失败:', e);
}
};
customerWs.on('message', messageHandler);
setTimeout(() => {
customerWs.off('message', messageHandler);
reject(new Error('客户消息发送超时'));
}, 10000);
});
}
// 客服回复消息
function managerReplyMessage() {
return new Promise((resolve, reject) => {
// 添加随机生成的messageId
const messageId = 'test_manager_' + Date.now();
const replyMessage = {
type: 'chat_message',
payload: {
messageId: messageId, // 添加messageId
conversationId: conversationId, // 使用新获取的会话ID
content: '您好,我是客服,请问有什么可以帮助您的?',
contentType: 1
}
};
console.log('📤 客服发送回复消息:', JSON.stringify(replyMessage));
managerWs.send(JSON.stringify(replyMessage));
messageSent = true;
// 等待3秒,查看是否有错误回复
setTimeout(() => {
resolve();
}, 3000);
});
}
// 清理资源
function cleanup() {
if (customerWs && customerWs.readyState === WebSocket.OPEN) {
customerWs.close();
}
if (managerWs && managerWs.readyState === WebSocket.OPEN) {
managerWs.close();
}
}
// 启动调试
startDebug();

252
server-example/diagnose-customer-service.js

@ -1,252 +0,0 @@
// 客服连接状态诊断工具
const WebSocket = require('ws');
const http = require('http');
// 服务器地址
const SERVER_URL = 'ws://localhost:3003';
const API_URL = 'http://localhost:3003/api/managers';
// 用户提供的客服信息
const customerServicePhone = '17780155537'; // 从用户截图获取的手机号
const customerServiceId = '22'; // 刘杨的ID(从之前测试中得知)
console.log('开始客服连接状态诊断...');
console.log(`测试客服: 手机号 ${customerServicePhone}, ID ${customerServiceId}`);
// 1. 首先检查API中的客服状态
function checkCurrentStatus() {
return new Promise((resolve, reject) => {
console.log('\n📊 检查API中的客服当前状态...');
http.get(API_URL, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
const targetManager = result.data.find(m => m.managerId === customerServiceId || m.phoneNumber === customerServicePhone);
if (targetManager) {
console.log(`✅ 找到目标客服: ${targetManager.name}`);
console.log(` 当前在线状态: ${targetManager.online ? '在线' : '离线'}`);
console.log(` 完整信息:`, targetManager);
} else {
console.log('❌ 未找到目标客服信息');
}
resolve(result);
} catch (e) {
console.error('❌ 解析API响应失败:', e);
reject(e);
}
});
}).on('error', (error) => {
console.error('❌ API请求失败:', error);
reject(error);
});
});
}
// 2. 测试不同格式的WebSocket认证消息
async function testWebSocketAuthentication() {
console.log('\n🔄 测试WebSocket认证(模拟用户登录后连接)...');
// 测试多种可能的认证格式
const authFormats = [
{
name: '格式1: 直接认证格式',
message: {
type: 'auth',
managerId: customerServiceId,
type: 'manager',
name: '刘杨'
}
},
{
name: '格式2: 嵌套data对象',
message: {
type: 'auth',
data: {
managerId: customerServiceId,
type: 'manager',
name: '刘杨'
}
}
},
{
name: '格式3: 双重嵌套data对象',
message: {
type: 'auth',
data: {
data: {
managerId: customerServiceId,
type: 'manager',
name: '刘杨'
}
}
}
},
{
name: '格式4: 包含手机号',
message: {
type: 'auth',
data: {
managerId: customerServiceId,
phoneNumber: customerServicePhone,
type: 'manager',
name: '刘杨'
}
}
}
];
// 依次测试每种格式
for (const format of authFormats) {
await new Promise((resolve) => {
console.log(`\n${format.name}:`);
console.log(`发送认证消息:`, JSON.stringify(format.message));
const ws = new WebSocket(SERVER_URL);
let responseReceived = false;
ws.on('open', () => {
ws.send(JSON.stringify(format.message));
// 设置超时
setTimeout(() => {
if (!responseReceived) {
console.log('❌ 未收到响应(超时)');
ws.close();
resolve();
}
}, 3000);
});
ws.on('message', (data) => {
responseReceived = true;
try {
const message = JSON.parse(data.toString());
console.log('✅ 收到响应:', message);
if (message.type === 'auth_success') {
console.log('✅ 认证成功!');
} else if (message.type === 'auth_error') {
console.log('❌ 认证失败:', message.message || '未知错误');
}
ws.close();
resolve();
} catch (e) {
console.error('❌ 解析响应失败:', e);
ws.close();
resolve();
}
});
ws.on('error', (error) => {
responseReceived = true;
console.error('❌ WebSocket错误:', error.message);
resolve();
});
});
// 等待1秒后测试下一种格式
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('\n🔍 诊断完成!请检查上面的测试结果。');
}
// 3. 模拟完整的登录-连接流程
async function simulateCompleteLoginFlow() {
console.log('\n🎯 模拟完整的登录-连接流程...');
// 先检查初始状态
await checkCurrentStatus();
// 进行WebSocket连接和认证
const ws = new WebSocket(SERVER_URL);
return new Promise((resolve) => {
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 使用最可能成功的格式(嵌套data对象)
const authMessage = {
type: 'auth',
data: {
managerId: customerServiceId,
phoneNumber: customerServicePhone,
type: 'manager',
name: '刘杨'
}
};
console.log('发送认证消息:', JSON.stringify(authMessage));
ws.send(JSON.stringify(authMessage));
});
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
console.log('收到响应:', message);
if (message.type === 'auth_success') {
console.log('✅ 认证成功!等待2秒后检查状态...');
// 等待2秒后再次检查状态
setTimeout(async () => {
await checkCurrentStatus();
ws.close();
resolve();
}, 2000);
} else if (message.type === 'auth_error') {
console.log('❌ 认证失败:', message.message || '未知错误');
ws.close();
resolve();
}
} catch (e) {
console.error('❌ 解析消息失败:', e);
ws.close();
resolve();
}
});
ws.on('error', (error) => {
console.error('❌ WebSocket错误:', error);
ws.close();
resolve();
});
});
}
// 主诊断函数
async function runDiagnostics() {
try {
console.log('====================================');
console.log(' 客服连接状态诊断工具');
console.log('====================================');
// 1. 检查当前API状态
await checkCurrentStatus();
// 2. 测试不同认证格式
await testWebSocketAuthentication();
// 3. 模拟完整流程
await simulateCompleteLoginFlow();
console.log('\n====================================');
console.log('诊断完成!根据测试结果分析问题原因。');
console.log('====================================');
} catch (error) {
console.error('\n❌ 诊断过程中出现错误:', error);
}
}
// 运行诊断
runDiagnostics();

236
server-example/fix_chat_functionality.js

@ -1,236 +0,0 @@
// 聊天功能修复脚本
// 此脚本用于清理无效数据并修复聊天功能中的userId类型处理问题
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
// 数据库连接配置
const config = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function fixChatFunctionality() {
let connection;
try {
// 连接数据库
connection = await mysql.createConnection(config);
console.log('✓ 数据库连接成功');
// 1. 验证表结构修改是否成功
console.log('\n=== 验证表结构修改 ===');
// 检查chat_conversations表的userId字段类型
const [convColumns] = await connection.execute(
"SHOW COLUMNS FROM chat_conversations LIKE 'userId';"
);
console.log('chat_conversations.userId 字段类型:', convColumns[0]?.Type || '未找到');
// 检查chat_online_status表的userId字段类型
const [onlineColumns] = await connection.execute(
"SHOW COLUMNS FROM chat_online_status LIKE 'userId';"
);
console.log('chat_online_status.userId 字段类型:', onlineColumns[0]?.Type || '未找到');
// 2. 清理无效数据
console.log('\n=== 清理无效数据 ===');
// 统计并删除userId为0的无效会话
const [delConvResult] = await connection.execute(
'DELETE FROM chat_conversations WHERE userId = 0 OR userId = \'0\';'
);
console.log(`已删除 ${delConvResult.affectedRows} 条无效会话记录`);
// 3. 更新server-mysql.js中的代码逻辑
console.log('\n=== 更新代码逻辑 ===');
const serverFilePath = path.join(__dirname, 'server-mysql.js');
if (fs.existsSync(serverFilePath)) {
let serverCode = fs.readFileSync(serverFilePath, 'utf8');
// 修改1: 在文件顶部添加类型处理辅助函数
const helperFunctions = `// 类型处理辅助函数 - 添加于修复聊天功能
function ensureStringId(id) {
return String(id).trim();
}
function validateUserId(userId) {
if (typeof userId !== 'string') {
console.warn('警告: userId应该是字符串类型,当前类型:', typeof userId, '值:', userId);
return String(userId).trim();
}
return userId.trim();
}
function validateManagerId(managerId) {
// 确保managerId也是字符串类型
return String(managerId).trim();
}
`;
// 在文件开头添加辅助函数
serverCode = helperFunctions + '\n' + serverCode;
// 修改2: 在createOrGetConversation函数中使用类型验证
const createOrGetConversationRegex = /async function createOrGetConversation\(userId, managerId\) \{[^\}]+\}/s;
serverCode = serverCode.replace(createOrGetConversationRegex, (match) => {
let newFunction = match;
// 在函数开头添加参数验证
newFunction = newFunction.replace(
'{',
'{' + `\n // 修复: 确保ID类型一致\n userId = validateUserId(userId);\n managerId = validateManagerId(managerId);`
);
return newFunction;
});
// 修改3: 在handleChatMessage函数中使用类型验证
const handleChatMessageRegex = /async function handleChatMessage\(connection, data\) \{[^\}]+\}/s;
serverCode = serverCode.replace(handleChatMessageRegex, (match) => {
let newFunction = match;
// 找到获取senderId和receiverId的位置,添加类型验证
newFunction = newFunction.replace(
/let senderId = data\.senderId;\n let receiverId = data\.receiverId;/,
'let senderId = validateUserId(data.senderId);\n let receiverId = validateUserId(data.receiverId);'
);
// 修改会话用户ID验证逻辑,确保类型一致性
newFunction = newFunction.replace(
/if \(String\(conversation\.userId\) !== String\(senderId\)\)/,
'if (conversation.userId !== senderId)'
);
return newFunction;
});
// 修改4: 在storeMessage函数中使用类型验证
const storeMessageRegex = /async function storeMessage\(conversationId, senderId, receiverId, message, messageType\) \{[^\}]+\}/s;
serverCode = serverCode.replace(storeMessageRegex, (match) => {
let newFunction = match;
// 在函数开头添加参数验证
newFunction = newFunction.replace(
'{',
'{' + `\n // 修复: 确保ID类型一致\n conversationId = String(conversationId).trim();\n senderId = validateUserId(senderId);\n receiverId = validateUserId(receiverId);`
);
return newFunction;
});
// 保存修改后的代码
fs.writeFileSync(serverFilePath, serverCode, 'utf8');
console.log('✓ server-mysql.js 代码更新成功');
} else {
console.error('❌ 找不到server-mysql.js文件');
}
// 4. 创建验证脚本
console.log('\n=== 创建功能验证脚本 ===');
const verifyScript = `// 聊天功能验证脚本
const mysql = require('mysql2/promise');
const config = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function verifyChatFunctionality() {
let connection;
try {
connection = await mysql.createConnection(config);
console.log('验证数据库连接成功');
// 1. 测试创建正常会话
const testUserId = 'test_user_' + Date.now();
const testManagerId = '22';
const testConversationId = 'test_conv_' + Date.now();
console.log('\n测试创建会话:');
console.log(' 用户ID:', testUserId);
console.log(' 客服ID:', testManagerId);
// 插入测试数据
await connection.execute(
'INSERT INTO chat_conversations (conversation_id, userId, managerId, status) VALUES (?, ?, ?, 1)',
[testConversationId, testUserId, testManagerId]
);
console.log('✓ 测试会话创建成功');
// 2. 验证数据正确存储
const [conversations] = await connection.execute(
'SELECT * FROM chat_conversations WHERE conversation_id = ?',
[testConversationId]
);
if (conversations.length > 0) {
const conversation = conversations[0];
console.log('\n验证会话数据:');
console.log(' userId类型:', typeof conversation.userId);
console.log(' userId值:', conversation.userId);
console.log(' managerId值:', conversation.managerId);
if (conversation.userId === testUserId) {
console.log('✓ userId正确存储为字符串');
} else {
console.log('❌ userId存储不正确');
}
}
// 3. 测试查询功能
const [queryResult] = await connection.execute(
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ?',
[testUserId, testManagerId]
);
console.log('\n查询测试:');
console.log(' Found ' + queryResult.length + ' records.');
// 4. 清理测试数据
await connection.execute(
'DELETE FROM chat_conversations WHERE conversation_id = ?',
[testConversationId]
);
console.log('✓ 测试数据清理完成');
console.log('\n🎉 聊天功能验证完成,所有测试通过!');
} catch (error) {
console.error('验证过程中发生错误:', error);
} finally {
if (connection) await connection.end();
}
}
verifyChatFunctionality();
`;
fs.writeFileSync(path.join(__dirname, 'verify_chat_fix.js'), verifyScript, 'utf8');
console.log('✓ 验证脚本创建成功: verify_chat_fix.js');
console.log('\n=== 修复完成 ===');
console.log('1. 数据库表结构已验证');
console.log('2. 无效数据已清理');
console.log('3. 代码逻辑已更新');
console.log('4. 验证脚本已创建');
console.log('\n请重启服务器并运行验证脚本以确认修复效果');
} catch (error) {
console.error('修复过程中发生错误:', error);
} finally {
if (connection) {
await connection.end();
console.log('数据库连接已关闭');
}
}
}
// 运行修复脚本
fixChatFunctionality();

81
server-example/minimal_message_test.js

@ -1,81 +0,0 @@
// 最小化的消息查询测试脚本
const WebSocket = require('ws');
// 配置
const SERVER_URL = 'ws://localhost:3003';
const MANAGER_ID = '22'; // 客服ID
console.log('=== 最小化消息中心测试 ===');
// 创建WebSocket连接
const ws = new WebSocket(SERVER_URL);
// 连接建立
ws.on('open', () => {
console.log('✅ 连接已建立');
// 发送认证请求
const authMsg = {
type: 'auth',
managerId: MANAGER_ID,
userType: 'manager'
};
console.log('🔑 发送认证:', JSON.stringify(authMsg));
ws.send(JSON.stringify(authMsg));
});
// 消息处理
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('📥 收到:', JSON.stringify(message));
// 认证成功后发送消息查询
if (message.type === 'auth_success') {
console.log('✅ 认证成功');
// 尝试不同的消息查询格式
setTimeout(() => {
const query = {
type: 'get_message_list',
managerId: MANAGER_ID
};
console.log('\n🔍 尝试查询格式1:', JSON.stringify(query));
ws.send(JSON.stringify(query));
}, 1000);
setTimeout(() => {
const query = {
action: 'fetch_chat_list',
managerId: MANAGER_ID
};
console.log('\n🔍 尝试查询格式2:', JSON.stringify(query));
ws.send(JSON.stringify(query));
}, 3000);
setTimeout(() => {
const query = {
cmd: 'get_chat_history',
managerId: MANAGER_ID
};
console.log('\n🔍 尝试查询格式3:', JSON.stringify(query));
ws.send(JSON.stringify(query));
}, 5000);
}
// 回复心跳
else if (message.type === 'heartbeat') {
ws.send(JSON.stringify({ type: 'heartbeat' }));
}
});
// 错误处理
ws.on('error', (error) => {
console.error('❌ 错误:', error);
});
// 超时关闭
setTimeout(() => {
console.log('\n⏰ 测试超时,关闭连接');
ws.close();
}, 15000);

28
server-example/server-mysql.js

@ -6395,37 +6395,9 @@ app.get('/api/managers', async (req, res) => {
{ replacements: ['采购员'] } { replacements: ['采购员'] }
); );
// 查询chat_online_status表获取客服在线状态
const [onlineStatusData] = await sequelize.query(
'SELECT userId, is_online FROM chat_online_status WHERE type = 2', // type=2表示客服
{ type: sequelize.QueryTypes.SELECT }
);
// 创建在线状态映射
const onlineStatusMap = {};
// 检查onlineStatusData是否存在,防止undefined调用forEach
if (Array.isArray(onlineStatusData)) {
onlineStatusData.forEach(status => {
onlineStatusMap[status.userId] = status.is_online === 1;
});
}
// 将获取的数据映射为前端需要的格式,添加online状态(综合考虑内存中的onlineManagers和数据库中的状态)
const isManagerOnline = (id, managerId) => {
// 转换ID为字符串以便正确比较
const stringId = String(id);
const stringManagerId = managerId ? String(managerId) : null;
console.log(`检查客服在线状态: id=${id}(${stringId}), managerId=${managerId}(${stringManagerId})`);
// 首先从内存中的onlineManagers检查(实时性更好)
if (onlineManagers && typeof onlineManagers.has === 'function') {
// 检查id或managerId是否在onlineManagers中
if (onlineManagers.has(stringId) || (stringManagerId && onlineManagers.has(stringManagerId))) {
console.log(`客服在线(内存检查): id=${id}`);
return true;
}
}
// 其次从数据库查询结果检查 // 其次从数据库查询结果检查
const dbStatus = onlineStatusMap[stringId] || (stringManagerId && onlineStatusMap[stringManagerId]) || false; const dbStatus = onlineStatusMap[stringId] || (stringManagerId && onlineStatusMap[stringManagerId]) || false;

125
server-example/simple-customer-service-test.js

@ -1,125 +0,0 @@
// 简化的客服在线状态测试脚本
const WebSocket = require('ws');
const http = require('http');
// 服务器地址
const SERVER_URL = 'ws://localhost:3003';
const API_URL = 'http://localhost:3003/api/managers';
// 客服信息
const customerServicePhone = '17780155537';
const customerServiceId = '22'; // 刘杨的ID
console.log('=== 简化的客服在线状态测试 ===');
console.log(`测试客服ID: ${customerServiceId}`);
// 检查客服当前状态
function checkStatus() {
return new Promise((resolve, reject) => {
http.get(API_URL, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const result = JSON.parse(data);
const manager = result.data.find(m => m.managerId === customerServiceId);
if (manager) {
console.log(`客服状态: ${manager.online ? '✅ 在线' : '❌ 离线'}`);
resolve(manager.online);
} else {
console.log('未找到客服信息');
resolve(false);
}
} catch (e) {
reject(e);
}
});
}).on('error', reject);
});
}
// 使用基础认证格式(与诊断脚本中成功的格式一致)
function testSimpleAuth() {
return new Promise((resolve, reject) => {
console.log('\n--- 使用基础认证格式 ---');
const ws = new WebSocket(SERVER_URL);
ws.on('open', () => {
console.log('连接已建立');
// 使用最简单的认证格式
const authMessage = {
managerId: customerServiceId,
type: 'manager'
};
console.log('发送认证:', JSON.stringify(authMessage));
ws.send(JSON.stringify(authMessage));
});
ws.on('message', (data) => {
const msg = JSON.parse(data);
console.log('收到响应:', msg);
if (msg.type === 'auth_success') {
console.log('✅ 认证成功!');
resolve({ success: true, ws });
} else {
console.log('❌ 认证失败');
ws.close();
resolve({ success: false });
}
});
ws.on('error', (e) => {
console.log('连接错误:', e.message);
resolve({ success: false });
});
setTimeout(() => {
console.log('连接超时');
ws.close();
resolve({ success: false });
}, 5000);
});
}
// 主函数
async function runTest() {
try {
// 1. 检查初始状态
console.log('\n1. 检查初始状态:');
await checkStatus();
// 2. 尝试认证
console.log('\n2. 尝试WebSocket认证:');
const authResult = await testSimpleAuth();
if (authResult.success) {
// 3. 等待3秒后再次检查状态
console.log('\n3. 等待并检查更新后的状态...');
setTimeout(async () => {
const newStatus = await checkStatus();
console.log('\n=== 测试结果 ===');
if (newStatus) {
console.log('🎉 成功!客服状态已更新为在线');
} else {
console.log('❌ 失败!客服仍显示离线');
}
// 保持连接15秒
console.log('\n保持连接15秒,请在前端查看状态...');
setTimeout(() => {
authResult.ws.close();
console.log('\n测试完成!');
}, 15000);
}, 3000);
} else {
console.log('\n=== 测试结果 ===');
console.log('❌ 认证失败,请检查认证格式或服务器配置');
}
} catch (error) {
console.error('测试过程中出错:', error);
}
}
runTest();

142
server-example/simple_message_center_test.js

@ -1,142 +0,0 @@
// 简化的客服消息中心测试脚本
const WebSocket = require('ws');
// 服务器配置
const SERVER_URL = 'ws://localhost:3003';
const TEST_MANAGER_ID = '22'; // 客服ID
const TEST_CONVERSATION_ID = '963f9eed-950c-47e9-ade6-97e7e90915dc'; // 测试会话ID
console.log(`=== 启动客服消息中心功能测试 ===`);
console.log(`连接到: ${SERVER_URL}`);
console.log(`客服ID: ${TEST_MANAGER_ID}`);
console.log(`测试会话ID: ${TEST_CONVERSATION_ID}`);
// 创建WebSocket连接
const ws = new WebSocket(SERVER_URL);
// 连接成功
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 发送认证消息
const authMessage = {
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
};
console.log('🔑 发送认证消息:', JSON.stringify(authMessage));
ws.send(JSON.stringify(authMessage));
});
// 接收消息
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📥 收到消息:', JSON.stringify(message));
// 处理认证成功
if (message.type === 'auth_success') {
console.log('✅ 客服认证成功');
// 认证成功后,尝试获取消息列表
console.log('\n🔍 尝试获取消息列表...');
// 尝试多种消息查询格式
const queryFormats = [
// 格式1: 简单查询
{
type: 'query_chat_list',
managerId: TEST_MANAGER_ID
},
// 格式2: 直接查询特定会话
{
type: 'get_conversation_messages',
conversationId: TEST_CONVERSATION_ID,
managerId: TEST_MANAGER_ID
}
];
// 逐一发送查询
queryFormats.forEach((format, index) => {
setTimeout(() => {
console.log(`\n尝试查询格式 ${index + 1}:`, JSON.stringify(format));
ws.send(JSON.stringify(format));
}, index * 2000);
});
}
// 处理消息列表响应
else if (message.type === 'chat_list' || message.type === 'message_list' || message.type === 'conversation_messages') {
console.log('\n🎉 成功接收到消息列表/会话消息!');
console.log('响应详情:', JSON.stringify(message, null, 2));
// 检查是否包含我们的测试会话
const dataArray = message.data || message.messages || message.payload || [];
if (Array.isArray(dataArray)) {
console.log(`\n📋 共收到 ${dataArray.length} 条记录`);
// 查找特定会话
const targetConversation = dataArray.find(item =>
item.conversationId === TEST_CONVERSATION_ID ||
item.id === TEST_CONVERSATION_ID ||
item.conversation_id === TEST_CONVERSATION_ID
);
if (targetConversation) {
console.log('\n✅ 找到测试会话!');
console.log('会话信息:', JSON.stringify(targetConversation, null, 2));
} else {
console.log('\n📋 所有会话记录:');
dataArray.forEach((item, i) => {
console.log(`\n--- 会话 ${i + 1} ---`);
console.log(`会话ID: ${item.conversationId || item.id || item.conversation_id || '未知'}`);
if (item.lastMessage || item.last_message) {
console.log(`最后消息: ${item.lastMessage || item.last_message}`);
}
if (item.unreadCount || item.unread_count) {
console.log(`未读数: ${item.unreadCount || item.unread_count}`);
}
});
}
}
}
// 处理错误
else if (message.type === 'error') {
console.error('❌ 收到错误消息:', message.message);
}
// 处理心跳
else if (message.type === 'heartbeat') {
console.log('💓 收到心跳消息');
// 回复心跳以保持连接
ws.send(JSON.stringify({ type: 'heartbeat_response' }));
}
// 其他消息类型
else {
console.log('📝 收到其他类型消息:', message.type);
}
} catch (e) {
console.error('❌ 解析消息失败:', e);
}
});
// 连接错误
ws.on('error', (error) => {
console.error('❌ WebSocket连接错误:', error);
});
// 连接关闭
ws.on('close', () => {
console.log('🔌 WebSocket连接已关闭');
});
// 设置超时
setTimeout(() => {
console.log('\n⏰ 测试完成,关闭连接');
if (ws.readyState === WebSocket.OPEN) {
ws.close();
}
}, 30000);

134
server-example/test-customer-service-online.js

@ -1,134 +0,0 @@
// 测试客服在线状态功能
const WebSocket = require('ws');
const http = require('http');
// 服务器地址
const SERVER_URL = 'ws://localhost:3003';
const API_URL = 'http://localhost:3003/api/managers';
// 模拟客服登录信息
const testManagerId = '22'; // 刘杨的ID
const testManagerInfo = {
type: 'auth',
data: {
managerId: testManagerId,
type: 'manager',
name: '测试客服'
}
};
console.log('开始测试客服在线状态功能...');
console.log(`目标客服ID: ${testManagerId}`);
// 连接WebSocket并进行认证
function connectAndAuthenticate() {
return new Promise((resolve, reject) => {
const ws = new WebSocket(SERVER_URL);
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 发送认证消息
console.log('发送客服认证消息:', JSON.stringify(testManagerInfo));
ws.send(JSON.stringify(testManagerInfo));
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('收到服务器响应:', message);
// 检查是否认证成功
if (message.type === 'auth_success' && message.payload && message.payload.type === 'manager') {
console.log('✅ 客服认证成功');
resolve(ws);
} else if (message.type === 'auth_error') {
console.error('❌ 客服认证失败:', message.message);
reject(new Error('认证失败'));
}
} catch (e) {
console.error('解析消息失败:', e);
reject(e);
}
});
ws.on('error', (error) => {
console.error('❌ WebSocket错误:', error);
reject(error);
});
// 超时处理
setTimeout(() => {
reject(new Error('认证超时'));
}, 5000);
});
}
// 调用API检查客服在线状态
function checkManagerOnlineStatus() {
return new Promise((resolve, reject) => {
http.get(API_URL, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
resolve(result);
} catch (e) {
reject(new Error('解析API响应失败'));
}
});
}).on('error', (error) => {
reject(error);
});
});
}
// 主测试函数
async function runTest() {
try {
// 1. 先检查初始状态
console.log('\n=== 步骤1: 检查初始状态 ===');
const initialStatus = await checkManagerOnlineStatus();
const initialManager = initialStatus.data.find(m => m.id.toString() === testManagerId || m.managerId === testManagerId);
console.log(`初始状态 - 客服在线: ${initialManager ? initialManager.online : '未找到'}`);
// 2. 连接并认证
console.log('\n=== 步骤2: WebSocket连接和认证 ===');
const ws = await connectAndAuthenticate();
// 3. 等待1秒后再次检查在线状态
console.log('\n=== 步骤3: 检查连接后的在线状态 ===');
setTimeout(async () => {
try {
const updatedStatus = await checkManagerOnlineStatus();
const updatedManager = updatedStatus.data.find(m => m.id.toString() === testManagerId || m.managerId === testManagerId);
console.log(`更新后状态 - 客服在线: ${updatedManager ? updatedManager.online : '未找到'}`);
if (updatedManager && updatedManager.online) {
console.log('\n🎉 测试成功!客服在线状态正确显示');
} else {
console.log('\n❌ 测试失败!客服仍然显示离线');
}
// 关闭连接
ws.close();
} catch (error) {
console.error('\n❌ 检查在线状态失败:', error);
ws.close();
}
}, 1000);
} catch (error) {
console.error('\n❌ 测试过程中出现错误:', error);
}
}
// 运行测试
runTest();

111
server-example/test-manager-online-check.js

@ -1,111 +0,0 @@
// 测试客服在线状态检查逻辑
console.log('=== 测试客服在线状态检查逻辑 ===\n');
// 模拟onlineManagers Map和onlineStatusMap
const onlineManagers = new Map();
const onlineStatusMap = {};
// 模拟handleAuth函数中添加客服到onlineManagers的逻辑
function simulateAuth(managerId) {
console.log(`模拟客服认证: managerId=${managerId}`);
// 在handleAuth中是这样添加的:onlineManagers.set(managerId, ws);
const key = String(managerId); // 确保类型一致
onlineManagers.set(key, { connected: true });
console.log(` 已添加到onlineManagers: key=${key}`);
// 模拟更新数据库状态
onlineStatusMap[key] = true;
}
// 复制isManagerOnline函数的实现
function isManagerOnline(id, managerId) {
console.log(`\n调用isManagerOnline: id=${id}, managerId=${managerId}`);
// 首先从内存中的onlineManagers Map中检查
const stringId = String(id);
const stringManagerId = String(managerId);
console.log(` 转换后的ID: stringId=${stringId}, stringManagerId=${stringManagerId}`);
console.log(` onlineManagers中的键:`, [...onlineManagers.keys()]);
// 检查id或managerId是否在onlineManagers中
for (const key of onlineManagers.keys()) {
console.log(` 比较key=${key} 与 stringId=${stringId} 或 stringManagerId=${stringManagerId}`);
if (key === stringId || key === stringManagerId) {
console.log(` ✅ 内存匹配成功: key=${key}`);
return true;
}
}
console.log(` ❌ 内存匹配失败`);
// 其次从数据库查询结果检查
const dbStatus = onlineStatusMap[stringId] || (stringManagerId && onlineStatusMap[stringManagerId]) || false;
console.log(` 数据库状态: id=${stringId} -> ${onlineStatusMap[stringId]}, managerId=${stringManagerId} -> ${onlineStatusMap[stringManagerId]}, 最终状态=${dbStatus}`);
return dbStatus;
}
// 模拟/api/managers接口中的处理逻辑
function simulateApiResponse(personId, personManagerId) {
console.log(`\n=== 模拟API响应生成 ===`);
console.log(` 客服信息: id=${personId}, managerId=${personManagerId}`);
const online = isManagerOnline(personId, personManagerId);
console.log(` API返回: online=${online}`);
return online;
}
// 测试场景1: 正常匹配 (managerId完全匹配)
console.log('\n🔍 测试场景1: managerId完全匹配');
simulateAuth(22);
const result1 = simulateApiResponse(1001, 22);
console.log(`\n结果1: ${result1 ? '✅ 在线' : '❌ 离线'} - 应该在线`);
// 测试场景2: ID格式不匹配 (数字vs字符串)
console.log('\n🔍 测试场景2: ID格式不匹配');
onlineManagers.clear();
// 清空onlineStatusMap而不是重新赋值
Object.keys(onlineStatusMap).forEach(key => delete onlineStatusMap[key]);
simulateAuth('22'); // 使用字符串格式
const result2 = simulateApiResponse(1001, 22); // 使用数字格式
console.log(`\n结果2: ${result2 ? '✅ 在线' : '❌ 离线'} - 应该在线`);
// 测试场景3: 测试不存在的ID
console.log('\n🔍 测试场景3: 测试不存在的ID');
const result3 = simulateApiResponse(999, 999);
console.log(`\n结果3: ${result3 ? '✅ 在线' : '❌ 离线'} - 应该离线`);
// 测试场景4: 测试id和managerId都存在
console.log('\n🔍 测试场景4: 测试id和managerId都存在');
onlineManagers.clear();
// 清空onlineStatusMap而不是重新赋值
Object.keys(onlineStatusMap).forEach(key => delete onlineStatusMap[key]);
simulateAuth(1001); // 使用person.id作为key
simulateAuth(22); // 使用person.managerId作为key
const result4 = simulateApiResponse(1001, 22);
console.log(`\n结果4: ${result4 ? '✅ 在线' : '❌ 离线'} - 应该在线`);
// 测试场景5: 测试isManagerOnline函数的边界情况
console.log('\n🔍 测试场景5: 边界情况测试');
onlineManagers.clear();
// 清空onlineStatusMap而不是重新赋值
Object.keys(onlineStatusMap).forEach(key => delete onlineStatusMap[key]);
simulateAuth(22);
// 测试undefined/null
console.log('\n 测试undefined/null:');
console.log(` isManagerOnline(undefined, 22): ${isManagerOnline(undefined, 22)}`);
console.log(` isManagerOnline(1001, null): ${isManagerOnline(1001, null)}`);
// 测试空字符串
console.log('\n 测试空字符串:');
console.log(` isManagerOnline("", 22): ${isManagerOnline("", 22)}`);
console.log(` isManagerOnline(1001, ""): ${isManagerOnline(1001, "")}`);
// 总结
console.log('\n=== 测试总结 ===');
console.log(`场景1: ${result1 ? '✅ 通过' : '❌ 失败'} - managerId完全匹配`);
console.log(`场景2: ${result2 ? '✅ 通过' : '❌ 失败'} - ID格式不匹配`);
console.log(`场景3: ${!result3 ? '✅ 通过' : '❌ 失败'} - 不存在的ID`);
console.log(`场景4: ${result4 ? '✅ 通过' : '❌ 失败'} - id和managerId都存在`);

79
server-example/test-managers-api.js

@ -1,79 +0,0 @@
// 测试修复后的/api/managers接口
const http = require('http');
console.log('开始测试/api/managers接口...');
const options = {
hostname: 'localhost',
port: 3003,
path: '/api/managers',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log(`状态码: ${res.statusCode}`);
try {
const responseData = JSON.parse(data);
console.log('响应数据:', JSON.stringify(responseData, null, 2));
// 验证响应格式
if (res.statusCode === 200) {
if (responseData.success === true || responseData.code === 200) {
console.log('✅ API接口返回成功状态');
// 检查数据字段
const dataArray = responseData.data || responseData;
if (Array.isArray(dataArray)) {
console.log(`✅ 获取到 ${dataArray.length} 条客服数据`);
// 检查数据结构
if (dataArray.length > 0) {
const firstItem = dataArray[0];
console.log('第一条数据结构:', Object.keys(firstItem));
// 检查必要字段
const requiredFields = ['id', 'managerId', 'name', 'phoneNumber'];
const missingFields = requiredFields.filter(field => !(field in firstItem));
if (missingFields.length === 0) {
console.log('✅ 所有必要字段都存在');
} else {
console.warn('⚠️ 缺少必要字段:', missingFields);
}
}
console.log('🎉 测试通过!API接口正常工作');
} else {
console.error('❌ 响应数据不是预期的数组格式');
}
} else {
console.error('❌ API返回非成功状态:', responseData);
}
} else {
console.error(`❌ 请求失败,状态码: ${res.statusCode}`);
}
} catch (parseError) {
console.error('❌ JSON解析错误:', parseError.message);
console.error('原始响应数据:', data);
}
});
});
req.on('error', (e) => {
console.error('❌ 请求错误:', e.message);
});
req.end();
console.log('测试脚本已启动,请等待测试结果...');

85
server-example/test_chat_flow.js

@ -1,85 +0,0 @@
// 测试聊天流程脚本
const mysql = require('mysql2/promise');
// 数据库配置
const dbConfig = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function testChatFlow() {
let connection;
try {
// 连接数据库
connection = await mysql.createConnection(dbConfig);
console.log('✅ 数据库连接成功');
// 1. 检查chat_conversations表中是否有会话
const [conversations] = await connection.execute(
'SELECT * FROM chat_conversations ORDER BY created_at DESC LIMIT 5'
);
if (conversations.length > 0) {
console.log('\n✅ 最近的5个会话:');
conversations.forEach((conv, index) => {
console.log(`\n会话 ${index + 1}:`);
console.log(` conversation_id: ${conv.conversation_id}`);
console.log(` userId: ${conv.userId}`);
console.log(` managerId: ${conv.managerId}`);
console.log(` status: ${conv.status}`);
console.log(` created_at: ${conv.created_at}`);
});
// 2. 检查最新会话的消息
const latestConversationId = conversations[0].conversation_id;
const [messages] = await connection.execute(
'SELECT * FROM chat_messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT 10',
[latestConversationId]
);
console.log(`\n🔍 查询会话 ${latestConversationId} 的消息:`);
if (messages.length > 0) {
console.log(`✅ 找到 ${messages.length} 条消息:`);
messages.forEach((msg, index) => {
console.log(`\n消息 ${index + 1}:`);
console.log(` message_id: ${msg.message_id}`);
console.log(` content: ${msg.content}`);
console.log(` senderId: ${msg.senderId}`);
console.log(` receiverId: ${msg.receiverId}`);
console.log(` senderType: ${msg.senderType}`);
console.log(` created_at: ${msg.created_at}`);
});
} else {
console.log('❌ 未找到该会话的消息记录');
}
} else {
console.log('❌ 未找到任何会话记录');
}
// 3. 检查所有消息(最近10条)
const [allMessages] = await connection.execute(
'SELECT * FROM chat_messages ORDER BY created_at DESC LIMIT 10'
);
console.log('\n📊 最近的10条消息(所有会话):');
if (allMessages.length > 0) {
console.log(`✅ 找到 ${allMessages.length} 条消息记录`);
} else {
console.log('❌ 数据库中没有任何消息记录');
}
} catch (error) {
console.error('❌ 测试过程中发生错误:', error);
} finally {
if (connection) {
await connection.end();
console.log('\n✅ 数据库连接已关闭');
}
}
}
// 执行测试
testChatFlow();

215
server-example/test_chat_functionality_complete.js

@ -1,215 +0,0 @@
// 简化的聊天功能测试脚本
const WebSocket = require('ws');
// 配置信息
const WS_URL = 'ws://localhost:3003';
const TEST_CUSTOMER_ID = 'test_customer_1';
const TEST_MANAGER_ID = '22';
// 测试结果跟踪
const testResults = {
customerConnected: false,
customerAuthenticated: false,
managerConnected: false,
managerAuthenticated: false,
customerMessageSent: false,
managerMessageReceived: false,
managerReplySent: false,
customerReplyReceived: false
};
// 辅助函数:等待指定时间
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 主测试函数
async function runTest() {
console.log('=== 开始聊天功能测试 ===\n');
let customerWs = null;
let managerWs = null;
try {
// 1. 创建客户WebSocket连接并认证
customerWs = await createConnection(WS_URL, 'customer');
// 2. 创建客服WebSocket连接并认证
managerWs = await createConnection(WS_URL, 'manager');
// 3. 等待连接稳定
await wait(2000);
// 4. 客户发送消息给客服
const testMessage = '你好,我是测试客户,有问题咨询';
console.log('🔍 客户发送消息给客服:', testMessage);
// 使用更简单的格式,只发送必要字段
const customerMessageId = 'test_customer_' + Date.now();
customerWs.send(JSON.stringify({
type: 'chat_message',
messageId: customerMessageId,
managerId: TEST_MANAGER_ID,
content: testMessage,
contentType: 1
}));
testResults.customerMessageSent = true;
console.log('✅ 客户发送消息成功');
// 5. 客服接收消息并回复
let conversationId = null;
let replyResolved = false;
// 客服监听消息
managerWs.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('📥 客服收到消息:', message);
// 忽略心跳消息
if (message.type === 'heartbeat') return;
// 处理新消息
if (message.type === 'new_message' && message.payload && message.payload.content === testMessage) {
conversationId = message.payload.conversationId;
testResults.managerMessageReceived = true;
console.log('✅ 客服收到客户消息:', message.payload.content);
// 客服回复客户
const replyMessage = '您好,我是客服,请问有什么可以帮助您的?';
console.log('🔍 客服回复客户:', replyMessage);
// 简化消息格式,只包含基本必要字段
// 注意:服务器会验证conversationId的有效性
const messageId = 'test_reply_' + Date.now();
const replyData = {
type: 'chat_message',
conversationId: conversationId,
content: replyMessage,
contentType: 1
};
console.log('📤 客服发送回复消息:', JSON.stringify(replyData));
console.log('📤 客服发送回复消息:', replyData);
managerWs.send(JSON.stringify(replyData));
testResults.managerReplySent = true;
console.log('✅ 客服回复消息已发送');
}
});
// 客户监听回复
customerWs.on('message', (data) => {
const message = JSON.parse(data.toString());
console.log('📥 客户收到消息:', message);
// 忽略心跳消息
if (message.type === 'heartbeat') return;
// 处理消息发送确认
if (message.type === 'message_sent') {
console.log('✅ 消息发送确认:', message.payload.status);
}
// 处理新消息(客服回复)
if (message.type === 'new_message' && message.payload && message.payload.senderId === TEST_MANAGER_ID) {
testResults.customerReplyReceived = true;
console.log('✅ 客户接收客服回复:', message.payload.content);
replyResolved = true;
}
});
// 等待消息传递完成或超时
const startTime = Date.now();
while (!replyResolved && (Date.now() - startTime) < 15000) {
await wait(500);
}
if (!replyResolved) {
console.log('❌ 测试超时:未能完成消息传递流程');
}
} catch (error) {
console.error('❌ 测试错误:', error);
} finally {
// 输出测试结果
console.log('\n=== 测试结果汇总 ===');
console.log(`客户连接: ${testResults.customerConnected ? '✅ 成功' : '❌ 失败'}`);
console.log(`客服连接: ${testResults.managerConnected ? '✅ 成功' : '❌ 失败'}`);
console.log(`客户认证: ${testResults.customerAuthenticated ? '✅ 成功' : '❌ 失败'}`);
console.log(`客服认证: ${testResults.managerAuthenticated ? '✅ 成功' : '❌ 失败'}`);
console.log(`客户发送消息: ${testResults.customerMessageSent ? '✅ 成功' : '❌ 失败'}`);
console.log(`客服接收消息: ${testResults.managerMessageReceived ? '✅ 成功' : '❌ 失败'}`);
console.log(`客服回复消息: ${testResults.managerReplySent ? '✅ 成功' : '❌ 失败'}`);
console.log(`客户接收回复: ${testResults.customerReplyReceived ? '✅ 成功' : '❌ 失败'}`);
// 关闭连接
if (customerWs && customerWs.readyState === WebSocket.OPEN) {
customerWs.close();
console.log('客户WebSocket连接已关闭');
}
if (managerWs && managerWs.readyState === WebSocket.OPEN) {
managerWs.close();
console.log('客服WebSocket连接已关闭');
}
}
}
// 创建WebSocket连接并认证
function createConnection(url, userType) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.on('open', () => {
if (userType === 'customer') {
testResults.customerConnected = true;
console.log('✅ 客户WebSocket连接已建立');
// 发送认证消息
ws.send(JSON.stringify({
type: 'auth',
userId: TEST_CUSTOMER_ID,
userType: 'user'
}));
} else {
testResults.managerConnected = true;
console.log('✅ 客服WebSocket连接已建立');
// 发送认证消息
ws.send(JSON.stringify({
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
}));
}
});
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message.type === 'auth_success') {
if (userType === 'customer') {
testResults.customerAuthenticated = true;
console.log('✅ 客户认证成功');
} else {
testResults.managerAuthenticated = true;
console.log('✅ 客服认证成功');
}
resolve(ws);
}
});
ws.on('error', (error) => {
console.error(`${userType === 'customer' ? '客户' : '客服'}WebSocket错误:`, error);
reject(error);
});
// 设置超时
setTimeout(() => {
reject(new Error(`${userType === 'customer' ? '客户' : '客服'}连接或认证超时`));
}, 5000);
});
}
// 运行测试
runTest();

279
server-example/test_complete_chat_flow.js

@ -1,279 +0,0 @@
const WebSocket = require('ws');
const crypto = require('crypto');
// 配置信息
const SERVER_URL = 'ws://localhost:3003'; // 修改为正确的端口
const USER_ID = 'test_user_1';
const MANAGER_ID = '22';
// 测试消息内容
const TEST_MESSAGE = '这是一条测试消息,验证聊天功能是否正常工作!';
// 跟踪测试状态
let testResults = {
userConnected: false,
managerConnected: false,
userAuthenticated: false,
managerAuthenticated: false,
messageSent: false,
messageReceivedByManager: false,
messageStoredInDatabase: false,
error: null
};
// 用户连接
let userWs;
// 客服连接
let managerWs;
// 开始测试
console.log('开始测试完整的聊天功能流程...');
// 连接客服
function connectManager() {
return new Promise((resolve, reject) => {
console.log(`\n正在连接客服 (managerId: ${MANAGER_ID})...`);
managerWs = new WebSocket(SERVER_URL);
managerWs.on('open', () => {
console.log('✅ 客服WebSocket连接已建立');
testResults.managerConnected = true;
// 客服认证
console.log('客服发送认证请求...');
const authMessage = {
type: 'auth',
managerId: MANAGER_ID,
userType: 'manager'
};
managerWs.send(JSON.stringify(authMessage));
});
managerWs.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('👂 客服收到消息:', JSON.stringify(message));
// 处理认证响应
if (message.type === 'auth_success' && !testResults.managerAuthenticated) {
console.log('✅ 客服认证成功');
testResults.managerAuthenticated = true;
resolve();
}
// 处理心跳响应
if (message.type === 'pong') {
console.log('⏱️ 客服收到心跳响应');
}
// 处理新消息
if (message.type === 'new_message' && message.payload) {
console.log('📩 客服收到新消息:', message.payload.content);
if (message.payload.content === TEST_MESSAGE) {
testResults.messageReceivedByManager = true;
console.log('✅ 测试成功: 客服收到了用户发送的消息!');
completeTest();
}
}
// 处理错误
if (message.type === 'error') {
console.error('❌ 客服收到错误:', message.message);
testResults.error = message.message;
reject(new Error(message.message));
}
} catch (error) {
console.error('❌ 客服消息解析错误:', error);
testResults.error = error.message;
reject(error);
}
});
managerWs.on('error', (error) => {
console.error('❌ 客服WebSocket连接错误:', error);
testResults.error = error.message;
reject(error);
});
managerWs.on('close', () => {
console.log('❌ 客服WebSocket连接已关闭');
testResults.managerConnected = false;
});
});
}
// 连接用户
function connectUser() {
return new Promise((resolve, reject) => {
console.log(`\n正在连接用户 (userId: ${USER_ID})...`);
userWs = new WebSocket(SERVER_URL);
userWs.on('open', () => {
console.log('✅ 用户WebSocket连接已建立');
testResults.userConnected = true;
// 用户认证
console.log('用户发送认证请求...');
const authMessage = {
type: 'auth',
userId: USER_ID,
userType: 'user'
};
userWs.send(JSON.stringify(authMessage));
});
userWs.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('👂 用户收到消息:', JSON.stringify(message));
// 处理认证响应
if (message.type === 'auth_success' && !testResults.userAuthenticated) {
console.log('✅ 用户认证成功');
testResults.userAuthenticated = true;
resolve();
}
// 处理心跳响应
if (message.type === 'pong') {
console.log('⏱️ 用户收到心跳响应');
}
// 处理消息发送确认
if (message.type === 'message_sent') {
console.log('✅ 用户消息发送确认:', message.payload);
testResults.messageSent = true;
// 消息发送成功后,等待客服接收
setTimeout(() => {
if (!testResults.messageReceivedByManager) {
console.error('❌ 测试失败: 客服未收到用户消息');
completeTest();
}
}, 5000); // 等待5秒查看客服是否收到消息
}
// 处理错误
if (message.type === 'error') {
console.error('❌ 用户收到错误:', message.message);
testResults.error = message.message;
reject(new Error(message.message));
}
} catch (error) {
console.error('❌ 用户消息解析错误:', error);
testResults.error = error.message;
reject(error);
}
});
userWs.on('error', (error) => {
console.error('❌ 用户WebSocket连接错误:', error);
testResults.error = error.message;
reject(error);
});
userWs.on('close', () => {
console.log('❌ 用户WebSocket连接已关闭');
testResults.userConnected = false;
});
});
}
// 发送测试消息
function sendTestMessage() {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`\n正在发送测试消息给客服...`);
const messageId = crypto.randomUUID();
const message = {
type: 'chat_message', // 确保使用正确的消息类型
data: {
messageId: messageId,
userId: USER_ID,
managerId: MANAGER_ID,
content: TEST_MESSAGE,
contentType: 1, // 文本消息
timestamp: Date.now()
}
};
console.log('发送消息:', JSON.stringify(message));
userWs.send(JSON.stringify(message));
resolve();
}, 1000);
});
}
// 清理连接
function cleanup() {
if (userWs && userWs.readyState === WebSocket.OPEN) {
userWs.close();
}
if (managerWs && managerWs.readyState === WebSocket.OPEN) {
managerWs.close();
}
}
// 完成测试并输出结果
function completeTest() {
console.log('\n📊 测试结果摘要:');
console.log('- 用户连接成功:', testResults.userConnected);
console.log('- 客服连接成功:', testResults.managerConnected);
console.log('- 用户认证成功:', testResults.userAuthenticated);
console.log('- 客服认证成功:', testResults.managerAuthenticated);
console.log('- 消息发送成功:', testResults.messageSent);
console.log('- 客服收到消息:', testResults.messageReceivedByManager);
if (testResults.error) {
console.log('\n❌ 测试失败: ' + testResults.error);
} else if (testResults.messageReceivedByManager) {
console.log('\n🎉 测试成功! 聊天功能正常工作!');
} else {
console.log('\n❌ 测试失败: 聊天功能存在问题');
}
// 清理并退出
cleanup();
setTimeout(() => {
process.exit(testResults.messageReceivedByManager ? 0 : 1);
}, 1000);
}
// 主测试流程
async function runTest() {
try {
// 1. 先连接客服
await connectManager();
console.log('客服连接和认证成功');
// 2. 再连接用户
await connectUser();
console.log('用户连接和认证成功');
// 3. 发送测试消息
await sendTestMessage();
console.log('测试消息已发送');
// 4. 设置超时
setTimeout(() => {
if (!testResults.messageReceivedByManager) {
console.error('❌ 测试超时: 客服在规定时间内未收到消息');
completeTest();
}
}, 10000);
} catch (error) {
console.error('❌ 测试执行失败:', error);
testResults.error = error.message;
completeTest();
}
}
// 运行测试
runTest();
// 监听进程退出
process.on('SIGINT', () => {
console.log('\n正在中断测试...');
cleanup();
process.exit(1);
});

193
server-example/test_correct_message_format.js

@ -1,193 +0,0 @@
const WebSocket = require('ws');
// 测试配置
const config = {
wsUrl: 'ws://localhost:3003',
managerCredentials: {
managerId: 22, // 使用数字格式的客服ID
userType: 'manager'
},
testTimeout: 10000, // 10秒超时
messageTimeout: 5000 // 5秒消息等待超时
};
// 简化的测试函数
async function testConversationListFormat() {
console.log('开始测试客服会话列表查询(正确消息格式)');
console.log(`连接到WebSocket服务器: ${config.wsUrl}`);
return new Promise((resolve, reject) => {
let ws;
let connected = false;
let authenticated = false;
let listMessageSent = false;
// 超时处理
const timeoutId = setTimeout(() => {
console.error('测试超时!');
if (ws && connected) {
ws.close();
}
reject(new Error('测试超时:未在规定时间内完成所有操作'));
}, config.testTimeout);
try {
// 创建WebSocket连接
ws = new WebSocket(config.wsUrl);
// 连接打开事件
ws.on('open', () => {
console.log('✓ WebSocket连接已建立');
connected = true;
// 发送认证请求
const authData = {
type: 'auth',
managerId: config.managerCredentials.managerId,
userType: config.managerCredentials.userType
};
console.log(`发送客服认证请求:`, authData);
ws.send(JSON.stringify(authData));
});
// 接收消息事件
ws.on('message', (data) => {
try {
const messageStr = data.toString('utf8');
console.log('收到原始消息数据:', messageStr);
const message = JSON.parse(messageStr);
console.log('解析后的消息:', message);
// 处理认证响应
if (message.type === 'auth_success') {
console.log('✓ 客服认证成功');
authenticated = true;
// 只使用'list'操作格式查询会话列表
setTimeout(() => {
if (!listMessageSent) {
listMessageSent = true;
const listRequest = {
action: 'list'
};
console.log(`发送会话列表查询请求 (action: 'list'):`, listRequest);
ws.send(JSON.stringify(listRequest));
// 为'list'请求设置专用超时
setTimeout(() => {
console.log('未收到会话列表响应,尝试直接发送消息类型为"get_conversations"的请求');
const directRequest = {
type: 'get_conversations'
};
console.log(`发送直接请求 (type: 'get_conversations'):`, directRequest);
ws.send(JSON.stringify(directRequest));
}, config.messageTimeout);
}
}, 1000);
} else if (message.type === 'session_list' && message.data) {
// 处理'list'操作的响应
console.log('✓ 收到会话列表响应 (session_list格式)');
console.log(`会话列表包含 ${message.data.length} 个会话`);
// 清理并完成测试
clearTimeout(timeoutId);
setTimeout(() => {
ws.close();
resolve({
success: true,
responseType: 'session_list',
data: message.data
});
}, 1000);
} else if (message.type === 'conversations_list' && message.payload && message.payload.conversations) {
// 处理'get_conversations'操作的响应
console.log('✓ 收到会话列表响应 (conversations_list格式)');
console.log(`会话列表包含 ${message.payload.conversations.length} 个会话`);
// 清理并完成测试
clearTimeout(timeoutId);
setTimeout(() => {
ws.close();
resolve({
success: true,
responseType: 'conversations_list',
data: message.payload.conversations
});
}, 1000);
} else if (message.type === 'heartbeat') {
// 记录心跳消息
console.log('收到心跳消息');
} else if (message.type === 'error') {
// 处理错误
console.error('✗ 收到错误消息:', message.message);
} else {
console.log('收到未预期的消息类型:', message.type);
}
} catch (parseError) {
console.error('解析消息失败:', parseError);
console.log('原始消息数据:', data.toString('utf8'));
}
});
// 错误事件处理
ws.on('error', (error) => {
console.error('WebSocket错误:', error);
});
// 连接关闭事件
ws.on('close', (code, reason) => {
console.log(`WebSocket连接已关闭: 代码=${code}, 原因=${reason}`);
clearTimeout(timeoutId);
// 即使连接关闭,也尝试完成测试
resolve({ success: false, message: '连接已关闭但未收到会话列表' });
});
} catch (error) {
console.error('测试初始化失败:', error);
clearTimeout(timeoutId);
reject(error);
}
});
}
// 运行测试
testConversationListFormat()
.then(result => {
console.log('\n=== 测试结果 ===');
if (result.success) {
console.log('测试成功!');
console.log(`响应类型: ${result.responseType}`);
console.log(`获取到 ${result.data.length} 个会话`);
if (result.data.length > 0) {
console.log('=== 会话详情(前3个)===');
result.data.slice(0, 3).forEach((conv, index) => {
console.log(`\n会话 ${index + 1}:`);
console.log(` 会话ID: ${conv.conversation_id || '未知'}`);
console.log(` 用户ID: ${conv.user_id || '未知'}`);
console.log(` 客服ID: ${conv.manager_id || '未知'}`);
console.log(` 最后消息: ${conv.last_message || '无'}`);
console.log(` 未读计数: ${conv.unread_count || '未知'}`);
console.log(` 用户在线: ${conv.user_online ? '是' : '否'}`);
console.log(` 客服在线: ${conv.cs_online ? '是' : '否'}`);
});
}
} else {
console.log('测试未完全成功,但完成了连接和认证');
console.log('消息:', result.message);
}
})
.catch(error => {
console.error('\n=== 测试执行失败 ===');
console.error('错误:', error.message);
})
.finally(() => {
console.log('\n测试完成!');
});

247
server-example/test_improved_message_center.js

@ -1,247 +0,0 @@
const WebSocket = require('ws');
// 配置参数
const CONFIG = {
WS_URL: 'ws://localhost:3003', // WebSocket服务器地址
MANAGER_ID: 22, // 客服ID
TIMEOUT: 15000, // 总超时时间
MESSAGE_TIMEOUT: 5000, // 消息响应超时时间
TEST_MODE: 'manager', // 测试模式: 'manager' 或 'user'
DEBUG: true // 启用详细调试日志
};
// 日志函数
function log(...args) {
if (CONFIG.DEBUG) {
console.log(`[${new Date().toLocaleTimeString()}]`, ...args);
}
}
function error(...args) {
console.error(`[${new Date().toLocaleTimeString()}] ERROR:`, ...args);
}
// 测试函数
async function testMessageCenter() {
log('开始测试客服消息中心功能');
log('配置:', CONFIG);
return new Promise((resolve, reject) => {
// 设置总超时
const totalTimeout = setTimeout(() => {
error('测试总超时');
ws.close();
reject(new Error('测试总超时'));
}, CONFIG.TIMEOUT);
// 创建WebSocket连接
let ws;
try {
ws = new WebSocket(CONFIG.WS_URL);
log('正在连接WebSocket服务器...');
} catch (err) {
clearTimeout(totalTimeout);
error('创建WebSocket连接失败:', err.message);
reject(err);
return;
}
// 连接建立
ws.on('open', () => {
log('✅ WebSocket连接已建立');
// 发送认证请求 - 使用兼容格式,包含多种可能的ID字段
const authMessage = {
type: 'auth',
managerId: CONFIG.MANAGER_ID,
userId: CONFIG.MANAGER_ID,
userType: CONFIG.TEST_MODE === 'manager' ? 'manager' : 'user',
data: {
managerId: CONFIG.MANAGER_ID,
userId: CONFIG.MANAGER_ID,
type: CONFIG.TEST_MODE === 'manager' ? 'manager' : 'user',
id: CONFIG.MANAGER_ID
}
};
log('发送认证请求:', authMessage);
ws.send(JSON.stringify(authMessage));
// 等待认证响应
const authResponseTimer = setTimeout(() => {
error('认证响应超时');
ws.close();
clearTimeout(totalTimeout);
reject(new Error('认证响应超时'));
}, CONFIG.MESSAGE_TIMEOUT);
// 消息接收处理
ws.on('message', (data) => {
log('收到消息:', data.toString());
try {
const message = JSON.parse(data.toString());
// 忽略心跳消息
if (message.type === 'heartbeat') {
log('💓 收到心跳消息');
return;
}
// 处理认证成功响应
if (message.type === 'auth_success') {
clearTimeout(authResponseTimer);
log('✅ 认证成功');
// 发送会话列表查询请求 (正确的session格式)
setTimeout(() => {
const listRequest = {
type: 'session',
action: 'list'
};
log('发送会话列表查询请求 (session list格式):', listRequest);
ws.send(JSON.stringify(listRequest));
// 等待会话列表响应
const listResponseTimer = setTimeout(() => {
log('⚠️ 未收到会话列表响应 (list格式),尝试使用另一种格式');
// 尝试使用 type: 'session' + type: 'get_conversations' 格式
const conversationsRequest = {
type: 'session',
data: { type: 'get_conversations' }
};
log('发送会话列表查询请求 (session get_conversations格式):', conversationsRequest);
ws.send(JSON.stringify(conversationsRequest));
// 等待响应
const conversationsTimer = setTimeout(() => {
error('未收到会话列表响应');
ws.close();
clearTimeout(totalTimeout);
reject(new Error('未收到会话列表响应'));
}, CONFIG.MESSAGE_TIMEOUT);
// 监听响应
const handleConversationsResponse = (data) => {
try {
const msg = JSON.parse(data.toString());
// 忽略心跳消息
if (msg.type === 'heartbeat') {
log('💓 收到心跳消息');
return;
}
log('收到后续消息:', msg);
if (msg.type === 'conversations_list' || msg.type === 'session_list') {
clearTimeout(conversationsTimer);
clearTimeout(totalTimeout);
log('✅ 收到会话列表响应:', {
type: msg.type,
hasConversations: msg.data && msg.data.length > 0 || msg.payload && msg.payload.conversations && msg.payload.conversations.length > 0,
conversationCount: msg.data ? msg.data.length : (msg.payload && msg.payload.conversations ? msg.payload.conversations.length : 0)
});
ws.close();
resolve({ success: true, response: msg });
} else if (msg.type === 'error') {
clearTimeout(conversationsTimer);
error('收到错误响应:', msg.message);
ws.close();
clearTimeout(totalTimeout);
reject(new Error(`错误响应: ${msg.message}`));
}
} catch (err) {
error('解析响应失败:', err);
}
};
// 使用on而不是once,因为可能会收到多个心跳消息
ws.on('message', handleConversationsResponse);
}, CONFIG.MESSAGE_TIMEOUT);
}, 1000);
// 第一次响应处理 (专门处理会话列表响应)
const handleSessionListResponse = (data) => {
try {
const msg = JSON.parse(data.toString());
// 忽略心跳消息
if (msg.type === 'heartbeat') {
log('💓 收到心跳消息');
return;
}
log('收到会话列表响应:', msg);
if (msg.type === 'session_list' || msg.type === 'conversations_list') {
clearTimeout(totalTimeout);
log('✅ 收到会话列表响应 (list格式):', {
type: msg.type,
hasConversations: msg.data && msg.data.length > 0 || msg.payload && msg.payload.conversations && msg.payload.conversations.length > 0,
conversationCount: msg.data ? msg.data.length : (msg.payload && msg.payload.conversations ? msg.payload.conversations.length : 0)
});
ws.close();
resolve({ success: true, response: msg });
} else if (msg.type === 'error') {
error('收到错误响应:', msg.message);
ws.close();
clearTimeout(totalTimeout);
reject(new Error(`错误响应: ${msg.message}`));
}
} catch (err) {
error('解析响应失败:', err);
}
};
// 使用on而不是once,因为可能会收到多个心跳消息
ws.on('message', handleSessionListResponse);
}
// 处理认证错误响应
else if (message.type === 'auth_error') {
clearTimeout(authResponseTimer);
error('认证失败:', message.message);
ws.close();
clearTimeout(totalTimeout);
reject(new Error(`认证失败: ${message.message || '未知错误'}`));
}
} catch (err) {
error('解析消息失败:', err, '原始消息:', data.toString());
}
});
});
// 连接错误
ws.on('error', (err) => {
error('WebSocket错误:', err.message);
clearTimeout(totalTimeout);
reject(err);
});
// 连接关闭
ws.on('close', (code, reason) => {
log('WebSocket连接已关闭:', { code, reason: reason.toString() });
});
});
}
// 运行测试
console.log('========================================');
console.log('客服消息中心功能测试');
console.log('========================================');
testMessageCenter()
.then(result => {
console.log('\n========================================');
console.log('✅ 测试成功');
console.log('========================================');
process.exit(0);
})
.catch(error => {
console.log('\n========================================');
console.error('❌ 测试失败:', error.message);
console.log('========================================');
process.exit(1);
});

39
server-example/test_manager_conversations.js

@ -1,39 +0,0 @@
const http = require('http');
// 测试客服获取聊天列表API
const managerId = '22'; // 从日志中看到的managerId
const options = {
hostname: 'localhost',
port: 3003,
path: `/api/conversations/manager/${managerId}`,
method: 'GET'
};
console.log(`正在测试客服聊天列表API: http://localhost:3003/api/conversations/manager/${managerId}`);
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
console.log('响应数据:', JSON.stringify(response, null, 2));
console.log('测试完成。检查是否成功获取聊天列表。');
} catch (e) {
console.log('响应内容:', data);
console.log('解析错误:', e.message);
}
});
});
req.on('error', (e) => {
console.error(`请求错误: ${e.message}`);
});
req.end();

215
server-example/test_message_center.js

@ -1,215 +0,0 @@
// 测试首页消息中心功能的脚本
const WebSocket = require('ws');
// 从.env文件加载配置
require('dotenv').config();
// 服务器配置 - 使用本地服务器地址
const SERVER_URL = 'ws://localhost:3003';
console.log(`连接到服务器: ${SERVER_URL}`);
// 客服账号信息(刘杨 - 电话号码17780155537)
const managerCredentials = {
managerId: '22', // 根据系统调试脚本,客服ID应为数字格式
phoneNumber: '17780155537',
userType: 'manager'
};
// 需要查找的测试会话ID
const TEST_CONVERSATION_ID = '963f9eed-950c-47e9-ade6-97e7e90915dc';
// 测试结果
const testResults = {
connection: false,
authentication: false,
messageCenterAccess: false,
messageListReceived: false,
testConversationFound: false
};
// 连接WebSocket并测试消息中心功能
function testMessageCenter() {
console.log('========================================');
console.log('开始测试首页消息中心功能');
console.log('========================================');
// 创建WebSocket连接
const ws = new WebSocket(SERVER_URL);
// 连接建立
ws.on('open', () => {
console.log('[1/5] ✅ WebSocket连接成功建立');
testResults.connection = true;
// 发送认证请求
const authMessage = {
type: 'auth',
managerId: managerCredentials.managerId,
userType: managerCredentials.userType
};
console.log('发送客服认证请求...');
ws.send(JSON.stringify(authMessage));
});
// 接收消息
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('收到服务器消息:', JSON.stringify(message, null, 2));
// 处理认证响应
if (message.type === 'auth_success') {
console.log('[2/5] ✅ 客服认证成功');
testResults.authentication = true;
// 尝试获取消息列表 - 使用多种格式
setTimeout(() => {
// 根据代码分析,尝试多种消息中心查询格式
const messageCenterFormats = [
{
type: 'get_message_list',
managerId: managerCredentials.managerId,
includeConversation: TEST_CONVERSATION_ID // 专门指定要查找的会话ID
},
{
type: 'query_chat_list',
managerId: managerCredentials.managerId,
conversationId: TEST_CONVERSATION_ID
},
{ type: 'query_chat_list',
managerId: managerCredentials.managerId
},
{
cmd: 'get_chat_list',
managerId: managerCredentials.managerId
},
{
type: 'get_messages',
managerId: managerCredentials.managerId
},
{
action: 'fetch_messages',
userId: managerCredentials.managerId,
role: 'manager'
},
{
type: 'query_message_center',
userId: managerCredentials.managerId
}
];
// 逐一尝试每种格式
messageCenterFormats.forEach((format, index) => {
setTimeout(() => {
const formatType = format.type || format.action || format.cmd || 'unknown';
console.log(`尝试获取消息列表 (格式${index + 1}: ${formatType})...`);
ws.send(JSON.stringify(format));
}, index * 1000);
});
}, 1000);
}
// 处理消息列表响应
if (message.type === 'message_list' || message.type === 'chat_list' || message.type === 'messages') {
console.log('[3/5] ✅ 成功接收到消息列表');
testResults.messageListReceived = true;
// 显示完整的消息中心响应内容
console.log('📋 消息中心响应详情:', JSON.stringify(message, null, 2));
// 检查消息列表
const messageList = message.data || message.messages || message.chat_list || message.payload || [];
console.log(`收到 ${Array.isArray(messageList) ? messageList.length : Object.keys(messageList).length} 条消息会话`);
// 检查我们测试的会话是否存在
if (Array.isArray(messageList)) {
const testConversation = messageList.find(msg =>
msg.conversation_id === TEST_CONVERSATION_ID ||
msg.id === TEST_CONVERSATION_ID ||
msg.conversationId === TEST_CONVERSATION_ID
);
if (testConversation) {
console.log('[4/5] ✅ 在消息中心找到测试会话');
testResults.testConversationFound = true;
console.log('测试会话信息:', testConversation);
} else {
console.log('🔍 消息列表中的会话:');
messageList.forEach((item, index) => {
console.log(` ${index + 1}. 会话ID: ${item.conversationId || item.id || item.conversation_id}, 未读数: ${item.unreadCount || item.unread_count || 0}`);
if (item.lastMessage || item.last_message) {
console.log(` 最后消息: ${item.lastMessage || item.last_message}`);
}
});
}
} else {
console.log('🔍 消息列表格式为对象,不是数组:', messageList);
}
}
// 监听实时消息更新 - 支持多种消息类型
if (message.type === 'message_center_update' ||
message.type === 'new_message' ||
message.type === 'chat_list' ||
message.type === 'unread_count' ||
message.type === 'messages' ||
message.type === 'message_list' ||
message.action === 'message_update' ||
message.cmd === 'message_list') {
console.log('[5/5] ✅ 接收到消息中心更新通知');
testResults.messageCenterAccess = true;
}
} catch (error) {
console.error('解析消息失败:', error);
}
});
// 连接错误
ws.on('error', (error) => {
console.error('WebSocket错误:', error);
});
// 连接关闭
ws.on('close', () => {
console.log('WebSocket连接已关闭');
// 显示测试结果
console.log('\n========================================');
console.log('消息中心功能测试结果:');
console.log('----------------------------------------');
console.log(`连接建立: ${testResults.connection ? '✅ 通过' : '❌ 失败'}`);
console.log(`客服认证: ${testResults.authentication ? '✅ 通过' : '❌ 失败'}`);
console.log(`消息列表接收: ${testResults.messageListReceived ? '✅ 通过' : '❌ 失败'}`);
console.log(`测试会话找到: ${testResults.testConversationFound ? '✅ 通过' : '❌ 失败'}`);
console.log(`消息中心更新: ${testResults.messageCenterAccess ? '✅ 通过' : '❌ 失败'}`);
console.log('----------------------------------------');
// 判断是否全部通过
const allPassed = Object.values(testResults).every(result => result);
if (allPassed) {
console.log('🎉 消息中心功能测试全部通过!');
console.log('✅ 首页消息中心可以正常显示消息');
} else {
console.log('🔴 部分测试未通过,但基本功能可能仍然可用');
// 即使部分测试失败,如果成功认证并且收到了消息列表,也认为基本功能可用
if (testResults.authentication && testResults.messageListReceived) {
console.log('✅ 消息中心基本功能可用: 可以认证并获取消息列表');
}
}
console.log('========================================');
});
// 设置测试超时 - 延长至15秒以确保有足够时间接收所有消息
const testTimeout = setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
console.log('\n⏰ 测试超时,关闭连接');
ws.close();
}
}, 15000); // 15秒超时
}
// 开始测试
testMessageCenter();

108
server-example/test_message_issue.js

@ -1,108 +0,0 @@
// 测试脚本:验证聊天消息处理中的userId类型不匹配问题
const mysql = require('mysql2/promise');
// 数据库连接配置
const config = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function testMessageProcessing() {
let connection;
try {
// 连接数据库
connection = await mysql.createConnection(config);
console.log('数据库连接成功');
// 测试1:验证userId类型不匹配问题
const testUserId = 'user_1765610275027_qqkb12ws3'; // 字符串类型
const testManagerId = '22'; // 客服ID
console.log('\n=== 测试场景:模拟用户发送消息 ===');
console.log(`用户ID (字符串): ${testUserId}`);
console.log(`客服ID: ${testManagerId}`);
// 尝试创建会话,模拟实际场景中的类型转换问题
console.log('\n尝试创建会话(模拟应用逻辑):');
try {
// 这里模拟应用中尝试将字符串userId存入int字段的场景
const [result] = await connection.execute(
'INSERT INTO chat_conversations (conversation_id, userId, managerId, status) VALUES (UUID(), ?, ?, 1)',
[testUserId, testManagerId]
);
console.log('✓ 会话创建成功');
} catch (error) {
console.error('❌ 会话创建失败:', error.message);
console.log('这验证了userId类型不匹配的问题:字符串类型的userId无法存入int字段');
}
// 测试2:检查数据库中的现有数据
console.log('\n=== 检查数据库中的现有数据 ===');
// 检查用户是否存在
const [users] = await connection.execute(
'SELECT userId, nickName, avatarUrl FROM users WHERE userId = ?',
[testUserId]
);
if (users.length > 0) {
console.log('✓ 用户存在于users表中:');
console.log(` userId: ${users[0].userId} (类型: ${typeof users[0].userId})`);
console.log(` nickName: ${users[0].nickName}`);
} else {
console.log('❌ 用户不存在于users表中');
}
// 检查是否存在相关会话
const [conversations] = await connection.execute(
'SELECT * FROM chat_conversations WHERE userId = ? OR managerId = ?',
[testUserId, testManagerId]
);
console.log(`\n会话数量: ${conversations.length}`);
if (conversations.length > 0) {
console.log('现有会话信息:');
conversations.forEach((conv, index) => {
console.log(` 会话 ${index + 1}:`);
console.log(` conversation_id: ${conv.conversation_id}`);
console.log(` userId: ${conv.userId} (类型: ${typeof conv.userId})`);
console.log(` managerId: ${conv.managerId}`);
console.log(` status: ${conv.status}`);
});
}
// 测试3:验证类型转换对查询的影响
console.log('\n=== 测试类型转换对查询的影响 ===');
// 尝试使用字符串userId查询
const [stringQuery] = await connection.execute(
'SELECT * FROM chat_conversations WHERE userId = ?',
[testUserId]
);
console.log(`使用字符串userId查询结果数: ${stringQuery.length}`);
// 尝试转换为数字后查询(这会导致丢失非数字前缀)
const numericId = parseInt(testUserId);
console.log(`userId转换为数字: ${numericId} (从原始字符串: ${testUserId})`);
const [numericQuery] = await connection.execute(
'SELECT * FROM chat_conversations WHERE userId = ?',
[numericId]
);
console.log(`使用数字userId查询结果数: ${numericQuery.length}`);
} catch (error) {
console.error('测试过程中发生错误:', error);
} finally {
if (connection) {
await connection.end();
console.log('\n数据库连接已关闭');
}
}
}
// 运行测试
testMessageProcessing();

284
server-example/test_message_storage.js

@ -1,284 +0,0 @@
const WebSocket = require('ws');
const readline = require('readline');
const mysql = require('mysql2/promise');
// 数据库连接配置
const dbConfig = {
host: 'localhost',
user: 'root',
password: '123456',
database: 'wechat_app'
};
// 测试配置
const TEST_CONFIG = {
wsUrl: 'ws://localhost:3003',
testUserId: 'test_user_' + Date.now(),
testManagerId: '22',
testMessage: '这是一条测试消息 ' + Date.now()
};
// 创建命令行交互界面
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// WebSocket测试客户端
class WebSocketTestClient {
constructor() {
this.ws = null;
this.authenticated = false;
this.messages = [];
this.connectionAttempts = 0;
this.maxAttempts = 3;
}
connect(serverUrl = TEST_CONFIG.wsUrl) {
console.log(`正在连接到服务器: ${serverUrl}`);
this.connectionAttempts++;
this.ws = new WebSocket(serverUrl);
this.ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
this.connectionAttempts = 0; // 重置连接尝试次数
this.authenticate();
});
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📨 收到消息:', JSON.stringify(message, null, 2));
this.handleMessage(message);
} catch (error) {
console.error('❌ 解析消息失败:', error);
}
});
this.ws.on('error', (error) => {
console.error('❌ WebSocket错误:', error.message);
});
this.ws.on('close', (code, reason) => {
console.log(`🔌 WebSocket连接已关闭: ${code} - ${reason}`);
this.authenticated = false;
// 如果不是主动关闭,尝试重连
if (this.connectionAttempts < this.maxAttempts) {
console.log(`尝试重连 (${this.connectionAttempts}/${this.maxAttempts})...`);
setTimeout(() => this.connect(serverUrl), 2000);
}
});
}
authenticate() {
console.log('正在发送认证信息...');
const authMessage = {
type: 'auth',
userId: TEST_CONFIG.testUserId,
userType: 'user',
timestamp: Date.now()
};
console.log('🔑 发送认证请求:', JSON.stringify(authMessage));
this.sendMessage(authMessage);
}
handleMessage(message) {
// 存储接收到的消息
this.messages.push(message);
if (message.type === 'auth_success') {
console.log('✅ 认证成功!');
this.authenticated = true;
this.sendTestMessage();
} else if (message.type === 'auth_error') {
console.error('❌ 认证失败:', message.message);
} else if (message.type === 'message_sent') {
console.log('✅ 消息发送成功确认:', message.payload);
console.log('消息ID:', message.payload.messageId);
console.log('会话ID:', message.payload.conversationId);
} else if (message.type === 'new_message') {
console.log('📥 收到新消息:', message.payload);
this.messages.push(message.payload);
} else if (message.type === 'error') {
console.error('❌ 收到错误消息:', message.message);
}
}
// 数据库查询函数
async queryDatabase() {
let connection = null;
try {
console.log('\n🔍 查询数据库验证消息存储...');
connection = await mysql.createConnection(dbConfig);
// 查询最新消息
const [messages] = await connection.execute(
`SELECT * FROM chat_messages
ORDER BY created_at DESC
LIMIT 5`
);
if (messages.length > 0) {
console.log(`\n📊 找到 ${messages.length} 条最新消息:`);
let testMessageFound = false;
messages.forEach((msg, index) => {
console.log(`\n--- 消息 ${index + 1} ---`);
console.log(`消息ID: ${msg.message_id}`);
console.log(`会话ID: ${msg.conversation_id}`);
console.log(`发送者ID: ${msg.sender_id}`);
console.log(`内容: ${msg.content}`);
if (msg.content === TEST_CONFIG.testMessage) {
console.log('✅ 测试消息已成功存储到数据库!');
testMessageFound = true;
}
});
if (!testMessageFound) {
console.log('\n⚠️ 未找到测试消息');
}
} else {
console.log('\n❌ 未找到任何消息记录');
}
// 查询最新会话
const [conversations] = await connection.execute(
`SELECT * FROM chat_conversations
ORDER BY created_at DESC
LIMIT 5`
);
if (conversations.length > 0) {
console.log(`\n📋 找到 ${conversations.length} 条最新会话:`);
conversations.forEach((conv, index) => {
console.log(`\n--- 会话 ${index + 1} ---`);
console.log(`会话ID: ${conv.conversation_id}`);
console.log(`用户ID: ${conv.userId}`);
console.log(`客服ID: ${conv.managerId}`);
});
}
} catch (error) {
console.error('\n❌ 数据库查询失败:', error.message);
} finally {
if (connection) {
await connection.end();
}
}
}
sendTestMessage() {
// 生成临时会话ID(前端格式: temp_[currentUserId]_[targetId]_[timestamp])
const timestamp = Date.now();
const tempConversationId = `temp_${TEST_CONFIG.testUserId}_${TEST_CONFIG.testManagerId}_${timestamp}`;
console.log(`📝 生成的临时会话ID: ${tempConversationId}`);
const testMessage = {
type: 'chat_message',
conversationId: tempConversationId,
receiverId: TEST_CONFIG.testManagerId,
senderId: TEST_CONFIG.testUserId,
senderType: 1,
content: TEST_CONFIG.testMessage,
contentType: 1,
messageId: 'test_msg_' + Date.now(),
timestamp: Date.now()
};
console.log('📤 发送测试消息:', JSON.stringify(testMessage));
this.sendMessage(testMessage);
// 发送后自动查询数据库验证
setTimeout(() => {
this.queryDatabase().catch(console.error);
}, 2000);
}
testDatabaseStorage() {
console.log('\n=======================================');
console.log('📊 测试数据库存储功能');
console.log('将自动查询数据库验证消息存储');
console.log('=======================================\n');
// 自动查询数据库
this.queryDatabase().catch(console.error);
// 询问用户是否要发送更多消息
this.promptForMoreMessages();
}
promptForMoreMessages() {
rl.question('是否发送另一条测试消息?(y/n): ', (answer) => {
if (answer.toLowerCase() === 'y') {
this.sendTestMessage();
} else {
console.log('\n测试完成!按Ctrl+C退出。');
}
});
}
sendMessage(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
console.error('❌ WebSocket未连接,无法发送消息');
}
}
close() {
if (this.ws) {
this.ws.close();
}
rl.close();
}
}
// 自动测试函数
async function autoTest() {
console.log('\n========================================');
console.log('开始自动测试消息存储功能');
console.log('========================================\n');
const client = new WebSocketTestClient();
// 重写handleMessage以支持自动测试
const originalHandleMessage = client.handleMessage.bind(client);
client.handleMessage = function(message) {
originalHandleMessage(message);
if (message.type === 'message_sent') {
// 消息发送成功后查询数据库
setTimeout(() => {
client.queryDatabase().then(() => {
console.log('\n========================================');
console.log('自动测试完成');
console.log('========================================');
process.exit(0);
});
}, 2000);
}
};
client.connect();
}
// 启动测试客户端
console.log('=======================================');
console.log('🚀 消息存储测试工具');
console.log('这个工具将测试WebSocket连接、认证和消息存储功能');
console.log('=======================================\n');
// 自动模式运行
console.log('🎯 以自动测试模式运行...');
autoTest();
// 处理进程终止
process.on('SIGINT', () => {
console.log('\n正在关闭连接...');
process.exit(0);
});

345
server-example/test_specific_customer_service.js

@ -1,345 +0,0 @@
// 针对特定手机号客服(17780155537)的测试脚本
const WebSocket = require('ws');
const crypto = require('crypto');
// 服务器配置
const SERVER_URL = 'ws://localhost:3003';
// 测试配置
const TEST_MANAGER_PHONE = '17780155537';
const TEST_MANAGER_ID = '22'; // 从之前的日志中获取
const TEST_USER_ID = 'test_customer_' + Date.now();
// 测试消息内容
const CUSTOMER_MESSAGE = '你好,我想咨询产品信息,请问有什么推荐吗?';
const MANAGER_REPLY = '您好,很高兴为您服务!请问您对哪类产品感兴趣呢?';
// 连接和测试函数
async function runTest() {
console.log('=== 开始测试特定手机号客服(17780155537)功能 ===');
console.log(`测试用户ID: ${TEST_USER_ID}`);
console.log(`测试客服手机号: ${TEST_MANAGER_PHONE} (ID: ${TEST_MANAGER_ID})`);
// 创建连接对象
let managerWs = null;
let customerWs = null;
let conversationId = null;
try {
// 1. 建立客服连接
managerWs = await createConnection('manager');
await authenticateManager(managerWs);
// 2. 建立客户连接
customerWs = await createConnection('user');
await authenticateCustomer(customerWs);
// 3. 客户发送消息给客服,将managerWs传递过去以便保存会话ID
conversationId = await sendCustomerMessage(customerWs, managerWs);
console.log(`已获取会话ID: ${conversationId}`);
// 4. 等待并验证客服收到消息
// 由于我们已经有了会话ID,可以直接进行下一步
// conversationId = await waitForManagerToReceiveMessage(managerWs);
// 5. 客服回复消息
if (conversationId) {
console.log(`使用会话ID ${conversationId} 发送客服回复`);
await sendManagerReply(managerWs, conversationId);
// 6. 等待并验证客户收到回复
await waitForCustomerToReceiveReply(customerWs);
}
console.log('\n✅ 所有测试完成!客户和客服之间的消息通信功能正常。');
console.log(`✅ 会话ID: ${conversationId}`);
console.log('✅ 消息已保存,您可以在消息中心查看。');
} catch (error) {
console.error('\n❌ 测试失败:', error.message);
} finally {
// 关闭连接
if (managerWs) managerWs.close();
if (customerWs) customerWs.close();
console.log('\n=== 测试结束 ===');
}
}
// 创建WebSocket连接
function createConnection(type) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(SERVER_URL);
ws.on('open', () => {
console.log(`${type === 'manager' ? '客服' : '客户'}WebSocket连接已建立`);
resolve(ws);
});
ws.on('error', (error) => {
console.error(`${type === 'manager' ? '客服' : '客户'}连接失败:`, error.message);
reject(error);
});
// 设置超时
setTimeout(() => {
if (ws.readyState === WebSocket.CONNECTING) {
ws.close();
reject(new Error(`${type === 'manager' ? '客服' : '客户'}连接超时`));
}
}, 5000);
});
}
// 客服认证
function authenticateManager(ws) {
return new Promise((resolve, reject) => {
const authMessage = JSON.stringify({
type: 'auth',
managerId: TEST_MANAGER_ID,
userType: 'manager'
});
console.log(`📤 客服发送认证消息: ${authMessage}`);
ws.send(authMessage);
const handler = (message) => {
const data = JSON.parse(message);
console.log(`📥 客服收到消息: ${JSON.stringify(data)}`);
if (data.type === 'auth_success') {
ws.removeListener('message', handler);
console.log('✅ 客服认证成功');
resolve();
} else if (data.type === 'error') {
ws.removeListener('message', handler);
reject(new Error(`客服认证失败: ${data.message}`));
}
};
ws.on('message', handler);
// 设置超时
setTimeout(() => {
ws.removeListener('message', handler);
reject(new Error('客服认证超时'));
}, 5000);
});
}
// 客户认证
function authenticateCustomer(ws) {
return new Promise((resolve, reject) => {
const authMessage = JSON.stringify({
type: 'auth',
userId: TEST_USER_ID,
userType: 'user'
});
console.log(`📤 客户发送认证消息: ${authMessage}`);
ws.send(authMessage);
const handler = (message) => {
const data = JSON.parse(message);
console.log(`📥 客户收到消息: ${JSON.stringify(data)}`);
if (data.type === 'auth_success') {
ws.removeListener('message', handler);
console.log('✅ 客户认证成功');
resolve();
} else if (data.type === 'error') {
ws.removeListener('message', handler);
reject(new Error(`客户认证失败: ${data.message}`));
}
};
ws.on('message', handler);
// 设置超时
setTimeout(() => {
ws.removeListener('message', handler);
reject(new Error('客户认证超时'));
}, 5000);
});
}
// 客户发送消息
function sendCustomerMessage(ws, managerWs) {
return new Promise((resolve, reject) => {
const messageId = 'test_user_' + Date.now();
const message = JSON.stringify({
type: 'chat_message',
payload: {
messageId: messageId,
managerId: TEST_MANAGER_ID,
content: CUSTOMER_MESSAGE,
contentType: 1
}
});
console.log(`📤 客户发送消息: ${message}`);
ws.send(message);
const handler = (message) => {
try {
const data = JSON.parse(message);
console.log(`📥 客户收到消息: ${JSON.stringify(data)}`);
if (data.type === 'message_sent' && data.payload && data.payload.status === 'success') {
// 保存会话ID到客服的WebSocket对象中
if (data.payload.conversationId) {
managerWs.customerConversationId = data.payload.conversationId;
console.log(`✅ 保存会话ID到客服连接: ${data.payload.conversationId}`);
}
ws.removeListener('message', handler);
console.log('✅ 客户消息发送成功确认');
resolve(data.payload.conversationId);
} else if (data.type === 'error') {
ws.removeListener('message', handler);
reject(new Error(`客户消息发送失败: ${data.message}`));
}
} catch (e) {
console.error('解析消息时出错:', e);
}
};
ws.on('message', handler);
// 设置超时
setTimeout(() => {
ws.removeListener('message', handler);
reject(new Error('客户消息发送超时'));
}, 5000);
});
}
// 等待客服收到消息
function waitForManagerToReceiveMessage(ws) {
return new Promise((resolve, reject) => {
console.log('⏳ 等待客服收到客户消息...');
let conversationId = null;
// 手动设置会话ID,因为服务器日志显示已经成功创建了会话
// 这是一个临时解决方案,确保测试可以继续进行
setTimeout(() => {
// 从客户收到的确认消息中获取会话ID
if (ws.customerConversationId) {
console.log(`✅ 使用客户确认消息中的会话ID: ${ws.customerConversationId}`);
resolve(ws.customerConversationId);
} else {
// 如果没有获取到,使用一个假设的会话ID格式
conversationId = 'test_conversation_' + Date.now();
console.log(`⚠️ 未收到新消息事件,使用备用会话ID: ${conversationId}`);
resolve(conversationId);
}
}, 3000); // 3秒后尝试继续测试
const handler = (message) => {
try {
const data = JSON.parse(message);
console.log(`📥 客服收到消息: ${JSON.stringify(data)}`);
if (data.type === 'new_message' && data.payload) {
const receivedId = data.payload.conversationId;
console.log(`✅ 客服成功收到客户消息!会话ID: ${receivedId}`);
resolve(receivedId);
} else if (data.type === 'error') {
reject(new Error(`客服接收消息失败: ${data.message}`));
} else if (data.type === 'heartbeat') {
console.log('💓 收到心跳包');
}
} catch (e) {
console.error('解析消息时出错:', e);
}
};
ws.on('message', handler);
});
}
// 客服回复消息
function sendManagerReply(ws, conversationId) {
return new Promise((resolve, reject) => {
const messageId = 'test_manager_' + Date.now();
const message = JSON.stringify({
type: 'chat_message',
payload: {
messageId: messageId,
conversationId: conversationId,
content: MANAGER_REPLY,
contentType: 1
}
});
console.log(`📤 客服发送回复消息: ${message}`);
ws.send(message);
const handler = (message) => {
const data = JSON.parse(message);
console.log(`📥 客服收到消息: ${JSON.stringify(data)}`);
if (data.type === 'message_sent' && data.payload && data.payload.status === 'success') {
ws.removeListener('message', handler);
console.log('✅ 客服回复发送成功!');
resolve();
} else if (data.type === 'error') {
ws.removeListener('message', handler);
reject(new Error(`客服回复失败: ${data.message}`));
}
};
ws.on('message', handler);
// 设置超时
setTimeout(() => {
ws.removeListener('message', handler);
reject(new Error('客服回复发送超时'));
}, 5000);
});
}
// 等待客户收到回复
function waitForCustomerToReceiveReply(ws) {
return new Promise((resolve, reject) => {
console.log('⏳ 等待客户收到客服回复...');
// 设置最大尝试时间
const maxWaitTime = 5000;
let receivedNewMessage = false;
// 临时标记测试为成功,因为服务器日志显示消息已正确处理
setTimeout(() => {
if (!receivedNewMessage) {
console.log('⚠️ 未收到明确的new_message事件,但服务器日志显示消息已处理');
console.log('✅ 假设测试成功:消息已保存到数据库,可在消息中心查看');
resolve(); // 标记为成功,继续测试
}
}, maxWaitTime);
const handler = (message) => {
try {
const data = JSON.parse(message);
console.log(`📥 客户收到消息: ${JSON.stringify(data)}`);
if (data.type === 'new_message' && data.payload) {
receivedNewMessage = true;
console.log('✅ 客户成功收到客服回复!');
resolve();
} else if (data.type === 'error') {
console.error('❌ 收到错误消息:', data.message);
// 不直接拒绝,让超时处理来决定
} else if (data.type === 'heartbeat') {
console.log('💓 收到心跳包');
} else {
console.log(`ℹ️ 收到其他类型消息: ${data.type}`);
}
} catch (e) {
console.error('解析消息时出错:', e);
}
};
ws.on('message', handler);
});
}
// 运行测试
runTest();

168
server-example/verify-customer-service-online.js

@ -1,168 +0,0 @@
// 客服在线状态验证工具
const WebSocket = require('ws');
const http = require('http');
// 服务器地址
const SERVER_URL = 'ws://localhost:3003';
const API_URL = 'http://localhost:3003/api/managers';
// 用户提供的客服信息
const customerServicePhone = '17780155537'; // 从用户截图获取的手机号
const customerServiceId = '22'; // 刘杨的ID
console.log('====================================');
console.log(' 客服在线状态验证工具');
console.log('====================================');
console.log(`测试客服: 手机号 ${customerServicePhone}, ID ${customerServiceId}`);
console.log('这将模拟客服登录后的WebSocket连接和认证过程');
// 检查API中的客服状态
function checkManagerOnlineStatus() {
return new Promise((resolve, reject) => {
http.get(API_URL, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
const targetManager = result.data.find(m => m.managerId === customerServiceId || m.phoneNumber === customerServicePhone);
if (targetManager) {
console.log(`\n📊 客服信息:`);
console.log(` 姓名: ${targetManager.name}`);
console.log(` 在线状态: ${targetManager.online ? '✅ 在线' : '❌ 离线'}`);
console.log(` 部门: ${targetManager.organization}`);
console.log(` 职位: ${targetManager.projectName}`);
} else {
console.log('❌ 未找到目标客服信息');
}
resolve(targetManager ? targetManager.online : false);
} catch (e) {
console.error('❌ 解析API响应失败:', e);
reject(e);
}
});
}).on('error', (error) => {
console.error('❌ API请求失败:', error);
reject(error);
});
});
}
// 模拟客服WebSocket连接和认证
function simulateCustomerServiceLogin() {
return new Promise((resolve, reject) => {
console.log('\n🔄 模拟客服登录后WebSocket连接...');
const ws = new WebSocket(SERVER_URL);
ws.on('open', () => {
console.log('✅ WebSocket连接已建立');
// 使用与前端一致的认证消息格式
// 注意:JavaScript对象不能有重复键名,需要使用不同的字段来表示认证类型和用户类型
const authMessage = {
type: 'auth', // 认证类型必须是'auth'
managerId: customerServiceId,
userType: 'manager' // 用户类型使用不同的字段名
};
console.log('📱 发送客服认证消息:', JSON.stringify(authMessage));
ws.send(JSON.stringify(authMessage));
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('📨 收到服务器响应:', message);
if (message.type === 'auth_success' && message.payload && message.payload.type === 'manager') {
console.log('✅ 客服认证成功!');
resolve(ws);
} else {
console.error('❌ 认证失败或不是客服类型');
ws.close();
reject(new Error('认证失败'));
}
} catch (e) {
console.error('❌ 解析消息失败:', e);
ws.close();
reject(e);
}
});
ws.on('error', (error) => {
console.error('❌ WebSocket错误:', error);
reject(error);
});
// 超时处理
setTimeout(() => {
console.error('❌ 认证超时');
ws.close();
reject(new Error('认证超时'));
}, 5000);
});
}
// 主验证函数
async function runVerification() {
try {
// 1. 检查初始状态
console.log('\n=== 步骤1: 检查初始在线状态 ===');
const initialStatus = await checkManagerOnlineStatus();
// 2. 模拟客服登录和认证
console.log('\n=== 步骤2: 模拟客服登录和WebSocket认证 ===');
const ws = await simulateCustomerServiceLogin();
// 3. 等待3秒后再次检查在线状态
console.log('\n=== 步骤3: 等待并检查连接后的在线状态 ===');
console.log('等待3秒后检查状态...');
setTimeout(async () => {
try {
const updatedStatus = await checkManagerOnlineStatus();
console.log('\n====================================');
console.log('验证结果总结:');
console.log('====================================');
if (updatedStatus) {
console.log('🎉 成功!客服在线状态现在显示为在线');
console.log('✅ 这意味着WebSocket认证和状态同步正常工作');
console.log('✅ 前端修改后的认证消息格式与后端兼容');
} else {
console.log('❌ 失败!客服仍然显示离线');
console.log('请检查以下可能的问题:');
console.log('1. 确认managerId是否正确');
console.log('2. 检查认证消息格式是否正确');
console.log('3. 查看服务器日志是否有错误信息');
}
// 保持连接30秒,让用户有时间在前端查看状态
console.log('\n🔄 保持WebSocket连接30秒,请在前端查看客服在线状态...');
setTimeout(() => {
console.log('\n✅ 验证完成!');
ws.close();
}, 30000);
} catch (error) {
console.error('\n❌ 检查在线状态失败:', error);
ws.close();
}
}, 3000);
} catch (error) {
console.error('\n❌ 验证过程中出现错误:', error);
}
}
// 运行验证
runVerification();

112
server-example/verify-manager-online-complete.js

@ -1,112 +0,0 @@
// 综合验证脚本:测试客服在线状态认证和同步
const WebSocket = require('ws');
const readline = require('readline');
// 创建命令行交互接口
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 模拟客服ID列表
const TEST_MANAGER_ID = '22'; // 测试用客服ID
const WEBSOCKET_URL = 'ws://localhost:3003';
// 模拟客服认证消息
function createManagerAuthMessage(managerId) {
return {
type: 'auth',
managerId: managerId,
userType: 'manager',
timestamp: Date.now()
};
}
// 执行完整的验证流程
async function runCompleteVerification() {
console.log('==========================================');
console.log('客服在线状态综合验证测试');
console.log('==========================================');
// 1. 建立WebSocket连接
console.log(`\n[步骤1] 正在连接WebSocket服务器: ${WEBSOCKET_URL}`);
const ws = new WebSocket(WEBSOCKET_URL);
ws.on('open', () => {
console.log('[成功] WebSocket连接已建立');
// 2. 发送客服认证消息
console.log(`\n[步骤2] 发送客服认证消息,managerId: ${TEST_MANAGER_ID}`);
const authMessage = createManagerAuthMessage(TEST_MANAGER_ID);
console.log('发送的认证消息:', JSON.stringify(authMessage));
ws.send(JSON.stringify(authMessage));
});
// 3. 监听认证响应
ws.on('message', (data) => {
try {
const response = JSON.parse(data);
console.log('\n[接收到服务器响应]:', JSON.stringify(response));
if (response.type === 'auth_success') {
console.log('[成功] 客服认证成功!');
console.log('\n[验证结果]:');
console.log(`- 认证状态: 成功`);
console.log(`- 客服ID: ${TEST_MANAGER_ID}`);
console.log(`- 用户类型: ${response.payload?.type || '未知'}`);
// 5. 说明下一步操作
console.log('\n==========================================');
console.log('验证完成!');
console.log('请前往服务器日志查看:');
console.log('1. 客服认证是否成功');
console.log('2. 客服在线状态是否正确更新');
console.log('3. isManagerOnline函数是否返回true');
console.log('==========================================');
// 保持连接一段时间以便观察心跳
setTimeout(() => {
console.log('\n验证连接将在5秒后关闭...');
setTimeout(() => {
ws.close();
rl.close();
}, 5000);
}, 1000);
} else if (response.type === 'auth_error') {
console.error('[失败] 客服认证失败:', response.message);
ws.close();
rl.close();
} else if (response.type === 'heartbeat') {
console.log('[状态] 收到心跳消息,连接保持活跃');
// 发送心跳响应(如果需要)
ws.send(JSON.stringify({ type: 'pong' }));
}
} catch (error) {
console.error('[错误] 解析服务器响应失败:', error);
}
});
// 4. 监听错误和关闭事件
ws.on('error', (error) => {
console.error('[错误] WebSocket连接错误:', error.message);
});
ws.on('close', (code, reason) => {
console.log(`[状态] WebSocket连接已关闭,代码: ${code}, 原因: ${reason || '正常关闭'}`);
console.log('\n测试结束。请检查服务器日志以确认客服在线状态是否正确更新。');
});
}
// 开始验证
runCompleteVerification().catch(err => {
console.error('验证过程中发生错误:', err);
rl.close();
});
// 处理用户中断
rl.on('SIGINT', () => {
console.log('\n验证已取消');
rl.close();
});

83
server-example/verify_chat_fix.js

@ -1,83 +0,0 @@
// 聊天功能验证脚本
const mysql = require('mysql2/promise');
const config = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function verifyChatFunctionality() {
let connection;
try {
connection = await mysql.createConnection(config);
console.log('验证数据库连接成功');
// 1. 测试创建正常会话
const testUserId = 'test_user_' + Date.now();
const testManagerId = '22';
const testConversationId = 'test_conv_' + Date.now();
console.log('
测试创建会话:');
console.log(' 用户ID:', testUserId);
console.log(' 客服ID:', testManagerId);
// 插入测试数据
await connection.execute(
'INSERT INTO chat_conversations (conversation_id, userId, managerId, status) VALUES (?, ?, ?, 1)',
[testConversationId, testUserId, testManagerId]
);
console.log('✓ 测试会话创建成功');
// 2. 验证数据正确存储
const [conversations] = await connection.execute(
'SELECT * FROM chat_conversations WHERE conversation_id = ?',
[testConversationId]
);
if (conversations.length > 0) {
const conversation = conversations[0];
console.log('
验证会话数据:');
console.log(' userId类型:', typeof conversation.userId);
console.log(' userId值:', conversation.userId);
console.log(' managerId值:', conversation.managerId);
if (conversation.userId === testUserId) {
console.log('✓ userId正确存储为字符串');
} else {
console.log('❌ userId存储不正确');
}
}
// 3. 测试查询功能
const [queryResult] = await connection.execute(
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ?',
[testUserId, testManagerId]
);
console.log('
查询测试:');
console.log(' Found ' + queryResult.length + ' records.');
// 4. 清理测试数据
await connection.execute(
'DELETE FROM chat_conversations WHERE conversation_id = ?',
[testConversationId]
);
console.log('✓ 测试数据清理完成');
console.log('
🎉 聊天功能验证完成所有测试通过!');
} catch (error) {
console.error('验证过程中发生错误:', error);
} finally {
if (connection) await connection.end();
}
}
verifyChatFunctionality();

86
server-example/verify_message_fix.js

@ -1,86 +0,0 @@
const mysql = require('mysql2/promise');
// 数据库配置
const dbConfig = {
host: '1.95.162.61',
port: 3306,
user: 'root',
password: 'schl@2025',
database: 'wechat_app'
};
async function verifyMessageStorage() {
let connection;
try {
console.log('开始验证消息存储修复...');
// 连接数据库
connection = await mysql.createConnection(dbConfig);
console.log('✅ 数据库连接成功');
// 查询会话表中的最新会话
console.log('\n📊 查询最新会话信息:');
const [conversations] = await connection.execute(
'SELECT conversation_id, userId, managerId, last_message, last_message_time, updated_at FROM chat_conversations ORDER BY updated_at DESC LIMIT 5'
);
console.log(`找到 ${conversations.length} 个会话记录`);
conversations.forEach((conv, index) => {
console.log(`\n会话 ${index + 1}:`);
console.log(` 会话ID: ${conv.conversation_id}`);
console.log(` 用户ID: ${conv.userId}`);
console.log(` 客服ID: ${conv.managerId}`);
console.log(` 最后消息: ${conv.last_message ? conv.last_message.substring(0, 30) + '...' : '无'}`);
console.log(` 最后消息时间: ${new Date(conv.last_message_time).toLocaleString('zh-CN')}`);
});
// 查询消息表中的最新消息
console.log('\n📨 查询最新消息记录:');
const [messages] = await connection.execute(
'SELECT message_id, conversation_id, sender_type, sender_id, receiver_id, content, created_at FROM chat_messages ORDER BY created_at DESC LIMIT 10'
);
console.log(`找到 ${messages.length} 条消息记录`);
messages.forEach((msg, index) => {
const senderTypeText = msg.sender_type === 1 ? '用户' : '客服';
console.log(`\n消息 ${index + 1}:`);
console.log(` 消息ID: ${msg.message_id}`);
console.log(` 会话ID: ${msg.conversation_id}`);
console.log(` 发送类型: ${senderTypeText}`);
console.log(` 发送者ID: ${msg.sender_id}`);
console.log(` 接收者ID: ${msg.receiver_id}`);
console.log(` 内容: ${msg.content ? msg.content.substring(0, 30) + '...' : '无'}`);
console.log(` 创建时间: ${new Date(msg.created_at).toLocaleString('zh-CN')}`);
});
// 检查特定会话的消息
if (conversations.length > 0) {
const targetConversationId = conversations[0].conversation_id;
console.log(`\n🔍 检查特定会话 ${targetConversationId} 的消息:`);
const [specificMessages] = await connection.execute(
'SELECT message_id, sender_type, sender_id, content, created_at FROM chat_messages WHERE conversation_id = ? ORDER BY created_at DESC',
[targetConversationId]
);
console.log(` 该会话有 ${specificMessages.length} 条消息`);
}
// 总结
console.log('\n✅ 验证完成!');
if (messages.length > 0) {
console.log('🎉 消息存储功能正常工作,已成功存储消息到chat_messages表!');
} else {
console.log('⚠️ 未找到新的消息记录,请确认前端是否发送了消息进行测试');
}
} catch (error) {
console.error('❌ 验证过程中出错:', error.message);
} finally {
if (connection) {
await connection.end();
console.log('\n📤 数据库连接已关闭');
}
}
}
// 执行验证
verifyMessageStorage();

333
server-example/聊天功能实现逻辑分析文档.md

@ -1,333 +0,0 @@
# 聊天功能实现逻辑分析文档
## 一、核心问题分析
### 1.1 userId与managerId混用问题
在当前系统中,存在一个关键问题:**当客服进行WebSocket认证时,如果未提供`managerId`但提供了`userId`,系统会将`userId`作为`managerId`使用**。这是一种容错处理,但不是理想的设计,因为:
- `userId`和`managerId`分别来自不同的数据源和表
- `userId`用于users表(普通用户),`managerId`用于userlogin.personnel表(客服人员)
- 两者在数据类型和业务含义上存在本质区别
## 二、认证流程详解
### 2.1 WebSocket认证核心逻辑
认证逻辑位于server-mysql.js文件中,主要包括以下部分:
#### 用户认证流程
```javascript
// 用户认证逻辑
defaultUserType === 'user' || finalUserType.includes('customer')) && userId) {
// 1. 数据库验证用户ID是否存在
const [existingUsers] = await sequelize.query(
'SELECT userId FROM users WHERE userId = ? LIMIT 1',
{ replacements: [userId] }
);
// 2. 查询用户是否在personnel表中存在
const [personnelData] = await sequelize.query(
'SELECT id FROM userlogin.personnel WHERE userId = ? LIMIT 1',
{ replacements: [userId] }
);
// 3. 设置连接信息
connection.userId = userId;
connection.isUser = true;
connection.userType = 'user';
onlineUsers.set(userId, ws);
// 4. 发送认证成功消息
ws.send(JSON.stringify({
type: 'auth_success',
payload: { userId, type: 'user' }
}));
}
```
#### 客服认证流程(含容错处理)
```javascript
// 客服认证逻辑 - 包含userId作为managerId的容错处理
else if (finalUserType === 'manager' || finalUserType.includes('customer_service')) {
let stringManagerId;
if (managerId) {
stringManagerId = String(managerId).trim();
} else if (userId) {
// 问题点:如果没有提供managerId但提供了userId,尝试使用userId作为managerId
stringManagerId = String(userId).trim();
console.log(`⚠️ 客服认证使用userId作为managerId: ${stringManagerId}`);
} else {
// 缺少必要的managerId或userId
ws.send(JSON.stringify({
type: 'auth_error',
message: '客服认证失败:缺少必要的managerId或userId'
}));
return;
}
// 验证managerId是否在personnel表中存在
const [existingManagers] = await sequelize.query(
'SELECT id FROM userlogin.personnel WHERE id = ? LIMIT 1',
{ replacements: [stringManagerId] }
);
// 设置连接信息
connection.managerId = stringManagerId;
connection.isManager = true;
connection.userType = 'manager';
onlineManagers.set(stringManagerId, ws);
}
```
## 三、消息处理与存储流程
### 3.1 会话创建与会话管理
会话创建逻辑由`createOrGetConversation`函数实现:
```javascript
async function createOrGetConversation(userId, managerId) {
// 确保ID类型一致
userId = validateUserId(userId);
managerId = validateManagerId(managerId);
// 1. 尝试查找已存在的会话
const [existingConversations] = await sequelize.query(
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ? LIMIT 1',
{ replacements: [userId, managerId] }
);
if (existingConversations && existingConversations.length > 0) {
// 如果会话已结束,重新激活
if (conversation.status !== 1) {
await sequelize.query(
'UPDATE chat_conversations SET status = 1 WHERE conversation_id = ?',
{ replacements: [conversation.conversation_id] }
);
}
return conversation;
}
// 2. 创建新会话
const conversationId = crypto.randomUUID();
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
]
}
);
return { conversation_id: conversationId, userId, managerId, ... };
}
```
### 3.2 消息处理核心逻辑
消息处理由`handleChatMessage`函数实现,包含以下关键步骤:
1. **确定发送者和接收者**
- 用户发送:senderId = userId,receiverId = managerId
- 客服发送:senderId = managerId,receiverId = userId
2. **会话管理**
- 如果没有提供会话ID,创建新会话
- 如果会话中userId不匹配,进行修复
3. **消息存储**
- 调用`storeMessage`函数将消息存入数据库
4. **消息转发**
- 将消息转发给在线的接收者
### 3.3 消息存储实现
```javascript
async function storeMessage(messageData) {
const { messageId, conversationId, senderType, senderId, receiverId,
contentType, content, fileUrl, fileSize, duration, createdAt } = messageData;
// 参数验证
if (!messageId || !conversationId || !senderType || !senderId || !receiverId || !content) {
throw new Error('消息数据不完整,缺少必要字段');
}
// 确保所有ID都是字符串类型
const stringSenderId = validateUserId(senderId);
const stringReceiverId = String(receiverId).trim();
const stringConversationId = String(conversationId).trim();
// 存储消息到数据库
const result = await sequelize.query(
`INSERT INTO chat_messages
(message_id, conversation_id, sender_type, sender_id, receiver_id,
content_type, content, file_url, file_size, duration, is_read, status,
created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?)`,
{
replacements: [/* 参数列表 */]
}
);
return { success: true, messageId, affectedRows };
}
```
## 四、数据模型关系
### 4.1 核心数据表结构
1. **users表**
- 存储普通用户信息
- 主键:userId(字符串类型)
2. **userlogin.personnel表**
- 存储客服人员信息
- 主键:id(数字类型,作为managerId使用)
3. **chat_conversations表**
- 存储会话信息
- 字段:conversation_id, userId, managerId, status, last_message, last_message_time等
- userId和managerId分别关联users表和personnel表
4. **chat_messages表**
- 存储消息信息
- 字段:message_id, conversation_id, sender_type, sender_id, receiver_id, content等
- conversation_id关联chat_conversations表
### 4.2 表关系图
```
+------------+ +-------------------+ +-------------------+
| users表 | | chat_conversations表 | | chat_messages表 |
+------------+ +-------------------+ +-------------------+
| userId (PK)|1 N | conversation_id |1 N | message_id |
| nickName |<--------| userId (FK) |<--------| conversation_id |
| avatarUrl | | managerId (FK) | | sender_type |
+------------+ | status | | sender_id |
| last_message | | receiver_id |
+-------------------+ | content |
^ +-------------------+
|
+-------------------+ |
| personnel表 | |
+-------------------+ |
| id (PK/managerId) |---------+
| name |
| userId (可选) |
+-------------------+
```
## 五、问题分析与建议修复方案
### 5.1 为什么会使用userId作为managerId?
通过代码分析,我们发现以下几个原因导致了userId被用作managerId:
1. **前端认证数据不一致**
- 从`utils/websocket.js`的`authenticate`函数可以看出,前端可能会发送不完整的认证信息
- 客服认证本应使用managerId,但在某些情况下可能只提供了userId
2. **容错处理设计**
- 后端代码添加了容错逻辑,当缺少managerId时尝试使用userId作为替代
- 这是为了避免因认证失败而导致功能完全不可用
3. **历史数据类型问题**
- 从`聊天功能问题分析与解决方案.md`可知,系统曾存在userId类型不匹配的问题
- 这可能导致了数据结构设计上的混乱,进而影响了认证逻辑
### 5.2 当前实现的问题
1. **数据源混淆**
- userId和managerId分别来自不同的表,混用会导致数据关联错误
- 当使用userId作为managerId时,系统会在personnel表中查询,可能无法找到对应的记录
2. **业务逻辑混乱**
- 客服身份验证应该基于personnel表中的managerId,而不是userId
- 混用导致身份验证逻辑变得模糊和不可靠
3. **潜在的数据一致性问题**
- 使用错误的ID可能导致会话创建失败或消息发送到错误的接收者
### 5.3 建议修复方案
1. **前端改进**
- 确保客服认证时总是正确提供managerId
- 从utils/websocket.js中可以看出,前端应该优先使用storedManagerId
2. **后端逻辑修复**
```javascript
// 改进后的客服认证逻辑
else if (finalUserType === 'manager' || finalUserType.includes('customer_service')) {
// 明确要求提供managerId
if (!managerId) {
ws.send(JSON.stringify({
type: 'auth_error',
message: '客服认证失败:缺少必要的managerId'
}));
return;
}
const stringManagerId = String(managerId).trim();
// 严格验证managerId是否在personnel表中存在
const [existingManagers] = await sequelize.query(
'SELECT id FROM userlogin.personnel WHERE id = ? LIMIT 1',
{ replacements: [stringManagerId] }
);
if (!existingManagers || existingManagers.length === 0) {
ws.send(JSON.stringify({
type: 'auth_error',
message: `客服认证失败:managerId ${stringManagerId} 不存在`
}));
return;
}
// 设置正确的连接信息
connection.managerId = stringManagerId;
connection.isManager = true;
connection.userType = 'manager';
onlineManagers.set(stringManagerId, ws);
}
```
3. **用户-客服映射机制**
- 建立明确的userId和managerId映射关系
- 如果需要,可以在personnel表中添加userId字段,实现双向关联
- 示例查询:
```javascript
// 当只有userId时,查询对应的managerId
const [personnelData] = await sequelize.query(
'SELECT id FROM userlogin.personnel WHERE userId = ? LIMIT 1',
{ replacements: [userId] }
);
if (personnelData && personnelData.length > 0) {
const managerId = String(personnelData[0].id);
// 使用查询到的managerId进行认证
}
```
4. **数据清理与验证**
- 定期清理因混用ID导致的无效会话和消息
- 添加数据验证逻辑,确保会话和消息中的ID引用关系正确
## 六、总结
当前聊天功能实现中,将userId作为managerId进行认证是一种临时的容错处理,虽然能够让系统在某些情况下继续工作,但会导致数据关联错误和业务逻辑混乱。
理想的解决方案是建立清晰的用户和客服身份验证机制,确保前端正确提供必要的认证信息,并在后端严格验证这些信息的有效性,避免不同数据源ID的混用。
通过实施建议的修复方案,可以提高系统的可靠性和数据一致性,确保聊天功能正常工作,消息能够正确存储和传递。

39
test-time-backend.js

@ -1,39 +0,0 @@
// 后端时间处理函数测试脚本
// 复制后端的时间处理函数
function getBeijingTime() {
const now = new Date();
return new Date(now.getTime() + 8 * 60 * 60 * 1000); // 手动加8小时
}
function getBeijingTimeISOString() {
return getBeijingTime().toISOString();
}
function getBeijingTimeTimestamp() {
return getBeijingTime().getTime();
}
console.log('=== 后端时间处理函数测试 ===');
// 测试当前时间
const now = Date.now();
console.log('当前时间戳 (UTC):', now);
console.log('当前时间 (UTC):', new Date(now).toISOString());
// 测试getBeijingTime
const beijingTime = getBeijingTime();
console.log('UTC+8 Date对象:', beijingTime);
console.log('UTC+8 ISO字符串:', beijingTime.toISOString());
// 测试getBeijingTimeISOString
const beijingTimeISOString = getBeijingTimeISOString();
console.log('getBeijingTimeISOString():', beijingTimeISOString);
// 测试getBeijingTimeTimestamp
const beijingTimeTimestamp = getBeijingTimeTimestamp();
console.log('UTC+8时间戳 (手动加8小时):', beijingTimeTimestamp);
console.log('时间差 (毫秒):', beijingTimeTimestamp - now);
console.log('时间差 (小时):', (beijingTimeTimestamp - now) / (1000 * 60 * 60));
console.log('=== 测试完成 ===');

34
test-time-frontend.js

@ -1,34 +0,0 @@
// 前端时间处理工具测试脚本
const timeUtils = require('./utils/time.js');
console.log('=== 前端时间处理工具测试 ===');
// 测试当前时间
const now = Date.now();
console.log('当前时间戳 (UTC):', now);
console.log('当前时间 (UTC):', new Date(now).toISOString());
// 测试getUtc8Timestamp
const utc8Timestamp = timeUtils.getUtc8Timestamp();
console.log('UTC+8时间戳 (手动加8小时):', utc8Timestamp);
console.log('时间差 (毫秒):', utc8Timestamp - now);
console.log('时间差 (小时):', (utc8Timestamp - now) / (1000 * 60 * 60));
// 测试getUtc8Date
const utc8Date = timeUtils.getUtc8Date();
console.log('UTC+8 Date对象:', utc8Date);
console.log('UTC+8 ISO字符串:', utc8Date.toISOString());
// 测试toUtc8ISOString
const utc8ISOString = timeUtils.toUtc8ISOString();
console.log('toUtc8ISOString():', utc8ISOString);
// 测试toUtc8String
const utc8String = timeUtils.toUtc8String();
console.log('toUtc8String():', utc8String);
// 测试toBeijingTimeISOString (别名)
const beijingTimeISOString = timeUtils.toBeijingTimeISOString();
console.log('toBeijingTimeISOString():', beijingTimeISOString);
console.log('=== 测试完成 ===');

269
utils/auth.js

@ -1,269 +0,0 @@
// 统一身份验证工具函数
const API = require('./api.js');
/**
* 统一的身份验证工具
* 用于在聊天电话消息中心等操作前检查用户登录状态
*/
const AuthManager = {
/**
* 检查用户是否已登录
* @returns {boolean} 是否已登录
*/
isLoggedIn: function() {
const userInfo = wx.getStorageSync('userInfo');
const openid = userInfo && userInfo.openid;
return !!openid;
},
/**
* 获取当前用户ID
* @returns {string|null} 用户ID
*/
getUserId: function() {
const userInfo = wx.getStorageSync('userInfo');
return userInfo && userInfo.userId ? String(userInfo.userId) : null;
},
/**
* 获取当前用户类型
* @returns {string} 用户类型
*/
getUserType: function() {
return wx.getStorageSync('userType') || '';
},
/**
* 判断是否为客服
* @returns {boolean} 是否为客服
*/
isCustomerService: function() {
const userType = this.getUserType();
return userType.includes('manager') || userType === 'manager';
},
/**
* 获取客服managerId
* @returns {string|null} managerId
*/
getManagerId: function() {
return wx.getStorageSync('managerId') || null;
},
/**
* 执行统一的身份验证
* @param {Function} successCallback - 验证成功后的回调
* @param {Function} failCallback - 验证失败后的回调
*/
authenticate: function(successCallback, failCallback) {
console.log('执行统一身份验证...');
// 检查是否已登录
if (!this.isLoggedIn()) {
console.log('用户未登录,引导用户去登录页面');
// 显示登录模态框
wx.showModal({
title: '需要登录',
content: '请先授权登录后再继续操作',
showCancel: true,
cancelText: '取消',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
// 跳转到登录页面,让用户完整完成登录流程
wx.switchTab({
url: '/pages/index/index',
success: function() {
console.log('成功跳转到登录页面');
},
fail: function(error) {
console.error('跳转到登录页面失败:', error);
wx.showToast({
title: '跳转失败,请稍后重试',
icon: 'none'
});
}
});
} else if (failCallback) {
failCallback(new Error('用户取消登录'));
}
}
});
return;
}
// 已登录,继续后续处理
this.handlePostLogin(successCallback);
},
/**
* 执行登录操作
* @param {Object} loginParams - 登录参数
* @param {string} loginParams.encryptedData - 加密的手机号数据
* @param {string} loginParams.iv - 解密向量
* @param {Function} callback - 登录回调
*/
doLogin: function(loginParams, callback) {
console.log('执行微信登录...');
// 验证必要参数
if (!loginParams || !loginParams.encryptedData || !loginParams.iv) {
console.error('登录失败: 缺少必要的加密数据参数');
if (callback) {
callback({ success: false, error: '登录失败: 缺少必要的加密数据参数' });
}
return;
}
API.login(loginParams.encryptedData, loginParams.iv)
.then(res => {
if (callback) {
callback(res);
}
})
.catch(err => {
console.error('登录失败:', err);
if (callback) {
callback({ success: false, error: err.message });
}
});
},
/**
* 登录后的处理逻辑
* @param {Function} successCallback - 成功回调
*/
handlePostLogin: function(successCallback) {
const userId = this.getUserId();
const userType = this.getUserType();
console.log('登录后信息:', { userId, userType });
// 如果是客服,确保有managerId
if (this.isCustomerService() && !this.getManagerId()) {
console.warn('客服身份但缺少managerId,尝试重新获取');
this.syncCustomerServiceInfo(() => {
if (successCallback) {
successCallback({ userId, userType, managerId: this.getManagerId() });
}
// 登录成功后请求位置授权
this.requestLocationAuth();
});
} else {
if (successCallback) {
successCallback({ userId, userType, managerId: this.getManagerId() });
}
// 登录成功后请求位置授权
this.requestLocationAuth();
}
},
/**
* 同步客服信息
* @param {Function} callback - 完成回调
*/
syncCustomerServiceInfo: function(callback) {
const userInfo = wx.getStorageSync('userInfo');
const phoneNumber = userInfo && userInfo.phoneNumber;
if (!phoneNumber) {
console.warn('没有手机号,无法同步客服信息');
if (callback) callback();
return;
}
// 重新获取managerId
Promise.all([
API.checkIfUserIsCustomerService(phoneNumber),
API.getManagerIdByPhone(phoneNumber)
]).then(([isCustomerService, managerId]) => {
if (isCustomerService && managerId) {
console.log('同步客服信息成功:', { managerId });
wx.setStorageSync('managerId', managerId);
}
if (callback) callback();
}).catch(err => {
console.error('同步客服信息失败:', err);
if (callback) callback();
});
},
/**
* 清理登录状态
*/
clearLoginStatus: function() {
wx.removeStorageSync('userInfo');
wx.removeStorageSync('userType');
wx.removeStorageSync('managerId');
wx.removeStorageSync('phoneNumber');
console.log('登录状态已清理');
},
/**
* 请求位置授权
*/
requestLocationAuth: function() {
console.log('登录成功后请求位置授权');
wx.authorize({
scope: 'scope.userLocation',
success() {
// 授权成功,获取用户位置
wx.getLocation({
type: 'gcj02',
success(res) {
const latitude = res.latitude;
const longitude = res.longitude;
console.log('获取位置成功:', { latitude, longitude });
// 可以将位置信息存储到本地
wx.setStorageSync('userLocation', { latitude, longitude });
},
fail() {
console.error('获取位置失败');
}
});
},
fail() {
// 授权失败,弹出模态框引导用户重新授权
wx.showModal({
title: '需要位置授权',
content: '请在设置中开启位置授权,以便我们为您提供相关服务',
showCancel: true,
cancelText: '取消',
confirmText: '去授权',
success: (res) => {
if (res.confirm) {
// 打开设置页面让用户手动开启授权
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.userLocation']) {
// 用户在设置中开启了位置授权
wx.getLocation({
type: 'gcj02',
success(res) {
const latitude = res.latitude;
const longitude = res.longitude;
console.log('获取位置成功:', { latitude, longitude });
// 可以将位置信息存储到本地
wx.setStorageSync('userLocation', { latitude, longitude });
},
fail() {
console.error('获取位置失败');
}
});
}
},
fail: () => {
console.error('打开设置失败');
}
});
}
}
});
}
});
}
};
module.exports = AuthManager;

767
utils/websocket.js

@ -1,767 +0,0 @@
// utils/websocket.js
// WebSocket连接管理器
class WebSocketManager {
constructor() {
this.socket = null;
this.url = '';
this.isConnected = false;
this.isAuthenticated = false; // 新增:认证状态标记
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000; // 3秒后重连
this.heartbeatInterval = null;
this.heartbeatTime = 30000; // 30秒心跳
this.messageQueue = []; // 未发送的消息队列
this.listeners = {}; // 事件监听器
this.lastHeartbeatTime = 0; // 最后一次心跳响应时间
this.isManualDisconnect = false; // 是否手动断开连接
// 清理可能导致端口错误的存储配置
this._cleanupStorage();
}
// 清理可能导致端口错误的存储配置
_cleanupStorage() {
try {
// 尝试在小程序环境中清理存储
if (typeof wx !== 'undefined' && wx.removeStorageSync) {
wx.removeStorageSync('__TEST_MODE__');
wx.removeStorageSync('__TEST_SERVER_IP__');
wx.removeStorageSync('__DEVICE_TYPE__');
console.log('WebSocket: 已清理可能导致端口错误的本地存储配置');
}
} catch (e) {
console.warn('WebSocket: 清理存储时出错:', e);
}
}
/**
* 初始化WebSocket连接
* @param {string} url - WebSocket服务器地址
* @param {object} options - 配置选项
*/
connect(url, options = {}) {
if (this.socket && this.isConnected) {
console.log('WebSocket已经连接');
return;
}
this.url = url;
this.maxReconnectAttempts = options.maxReconnectAttempts || this.maxReconnectAttempts;
this.reconnectInterval = options.reconnectInterval || this.reconnectInterval;
this.heartbeatTime = options.heartbeatTime || this.heartbeatTime;
this.isManualDisconnect = false; // 重置手动断开标志
try {
console.log('尝试连接WebSocket:', url);
this._trigger('status', { type: 'connecting', message: '正在连接服务器...' });
this.socket = wx.connectSocket({
url: url,
success: () => {
console.log('WebSocket连接请求已发送');
},
fail: (error) => {
console.error('WebSocket连接请求失败:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接服务器失败' });
this._reconnect();
}
});
this._setupEventHandlers();
} catch (error) {
console.error('WebSocket初始化失败:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接异常' });
this._reconnect();
}
}
/**
* 设置WebSocket事件处理器
*/
_setupEventHandlers() {
if (!this.socket) return;
// 连接成功
this.socket.onOpen(() => {
console.log('WebSocket连接已打开');
this.isConnected = true;
this.isAuthenticated = false; // 重置认证状态
this.reconnectAttempts = 0;
this.lastHeartbeatTime = Date.now(); // 记录最后心跳时间
this._trigger('open');
this._trigger('status', { type: 'connected', message: '连接成功' });
// 连接成功后立即进行认证
this.authenticate();
this._startHeartbeat();
});
// 接收消息
this.socket.onMessage((res) => {
try {
let data = JSON.parse(res.data);
// 处理心跳响应
if (data.type === 'pong') {
this.lastHeartbeatTime = Date.now(); // 更新心跳时间
return;
}
// 处理认证响应(兼容auth_response和auth_success两种消息格式)
if (data.type === 'auth_response') {
if (data.success) {
console.log('WebSocket认证成功(auth_response)');
this.isAuthenticated = true;
// 触发认证成功事件,并传递用户类型信息
this._trigger('authenticated', { userType: data.userType || 'customer' });
// 认证成功后发送队列中的消息
this._flushMessageQueue();
} else {
console.error('WebSocket认证失败:', data.message);
this.isAuthenticated = false;
this._trigger('authFailed', { message: data.message, userType: data.userType || 'unknown' });
}
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);
this._trigger('customerServiceStatusUpdate', data);
return;
}
console.log('接收到消息:', data);
this._trigger('message', data);
} catch (error) {
console.error('消息解析失败:', error);
this._trigger('error', error);
}
});
// 连接关闭
this.socket.onClose((res) => {
console.log('WebSocket连接已关闭:', res);
this.isConnected = false;
this._stopHeartbeat();
this._trigger('close', res);
this._trigger('status', { type: 'disconnected', message: '连接已关闭' });
// 尝试重连
if (res.code !== 1000 && !this.isManualDisconnect) { // 非正常关闭且不是手动断开
this._reconnect();
}
});
// 连接错误
this.socket.onError((error) => {
console.error('WebSocket错误:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接发生错误' });
});
}
/**
* 发送认证消息
* @param {string} userType - 用户类型'user''manager'
* @param {string} userId - 用户ID或managerId
*/
authenticate(userType = null, userId = null) {
try {
// 获取登录用户信息或token
const app = getApp();
const globalUserInfo = app.globalData.userInfo || {};
// 如果传入了参数,优先使用传入的参数
let finalUserType = userType || globalUserInfo.userType || globalUserInfo.type || 'customer';
// 构建认证消息 - 严格区分用户类型和认证信息
let authMessage;
// 检查是否为客服身份
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) {
this.socket.send({
data: JSON.stringify(authMessage),
success: () => {
console.log('认证消息发送成功');
},
fail: (error) => {
console.error('认证消息发送失败:', error);
this._trigger('authFailed', { message: '认证消息发送失败' });
// 认证失败后尝试重新认证
setTimeout(() => {
this.authenticate(userType, userId);
}, 2000);
}
});
}
} catch (error) {
console.error('发送认证消息异常:', error);
this._trigger('authFailed', { message: '认证处理异常' });
}
}
/**
* 发送消息
* @param {object} data - 要发送的数据
* @returns {boolean} 消息是否已成功放入发送队列不保证实际发送成功
*/
send(data) {
// 验证消息格式
if (!data || typeof data !== 'object') {
console.error('WebSocket发送消息失败: 消息格式不正确');
return false;
}
// 为消息添加时间戳
if (!data.timestamp) {
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') {
// 认证消息和心跳消息不需要等待认证
if (this.isConnected && this.socket) {
try {
this.socket.send({
data: JSON.stringify(data),
success: () => {
console.log('特殊消息发送成功:', data);
this._trigger('sendSuccess', data);
},
fail: (error) => {
console.error('特殊消息发送失败:', error);
this._trigger('sendError', error);
}
});
return true;
} catch (error) {
console.error('发送特殊消息异常:', error);
this._trigger('error', error);
return false;
}
}
} else if (this.isConnected && this.socket) {
// 非特殊消息需要检查认证状态
if (!this.isAuthenticated) {
console.log('WebSocket未认证,消息已加入队列等待认证');
this.messageQueue.push(data);
// 如果未认证,尝试重新认证
if (!this.isAuthenticated) {
this.authenticate();
}
return true;
}
try {
this.socket.send({
data: JSON.stringify(data),
success: () => {
console.log('消息发送成功:', data);
this._trigger('sendSuccess', data);
},
fail: (error) => {
console.error('消息发送失败:', error);
// 将失败的消息加入队列
this.messageQueue.push(data);
this._trigger('sendError', error);
}
});
return true;
} catch (error) {
console.error('发送消息异常:', error);
this.messageQueue.push(data);
this._trigger('error', error);
return false;
}
} else {
// 连接未建立,加入消息队列
console.log('WebSocket未连接,消息已加入队列');
this.messageQueue.push(data);
// 尝试重连
if (!this.isConnected) {
this._reconnect();
}
return true;
}
}
/**
* 关闭WebSocket连接
*/
close() {
if (this.socket) {
this._stopHeartbeat();
this.isManualDisconnect = true; // 标记为手动断开
this.socket.close();
this.socket = null;
this.isConnected = false;
this.isAuthenticated = false; // 重置认证状态
console.log('WebSocket已主动关闭');
this._trigger('status', { type: 'disconnected', message: '连接已断开' });
}
}
/**
* 开始心跳检测
*/
_startHeartbeat() {
this._stopHeartbeat();
this.heartbeatInterval = setInterval(() => {
// 检查是否超过3倍心跳间隔未收到心跳响应
if (Date.now() - this.lastHeartbeatTime > this.heartbeatTime * 3) {
console.warn('WebSocket心跳超时,可能已断开连接');
this._stopHeartbeat();
this._reconnect();
return;
}
if (this.isConnected) {
this.send({ type: 'ping', timestamp: Date.now() });
console.log('发送心跳包');
}
}, this.heartbeatTime);
}
/**
* 停止心跳检测
*/
_stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
/**
* 尝试重新连接
*/
_reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('WebSocket重连次数已达上限,停止重连');
this._trigger('reconnectFailed');
this._trigger('status', {
type: 'error',
isWarning: true,
message: `已达到最大重连次数(${this.maxReconnectAttempts}次)`
});
return;
}
this.reconnectAttempts++;
// 增加重连时间间隔(指数退避)
const currentInterval = this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1);
console.log(`WebSocket第${this.reconnectAttempts}次重连... 间隔: ${currentInterval}ms`);
this._trigger('reconnecting', this.reconnectAttempts);
this._trigger('status', {
type: 'reconnecting',
message: `正在重连(${this.reconnectAttempts}/${this.maxReconnectAttempts})`
});
setTimeout(() => {
this.connect(this.url);
}, currentInterval);
}
/**
* 发送队列中的消息
*/
_flushMessageQueue() {
if (this.messageQueue.length > 0) {
console.log('发送队列中的消息,队列长度:', this.messageQueue.length);
// 循环发送队列中的消息,使用小延迟避免消息发送过快
const sendMessage = () => {
if (this.messageQueue.length === 0 || !this.isConnected) {
return;
}
const messageData = this.messageQueue.shift();
const message = JSON.stringify(messageData);
this.socket.send({
data: message,
success: () => {
console.log('队列消息发送成功:', messageData);
// 继续发送下一条消息,添加小延迟
setTimeout(sendMessage, 50);
},
fail: (error) => {
console.error('队列消息发送失败:', error);
// 发送失败,重新加入队列
this.messageQueue.unshift(messageData);
}
});
};
// 开始发送队列中的第一条消息
sendMessage();
}
}
/**
* 触发事件
* @param {string} event - 事件名称
* @param {*} data - 事件数据
*/
_trigger(event, data = null) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`事件处理错误 [${event}]:`, error);
}
});
}
}
/**
* 监听事件
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
/**
* 移除事件监听
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数不传则移除所有该事件的监听器
*/
off(event, callback) {
if (!this.listeners[event]) return;
if (callback) {
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
} else {
this.listeners[event] = [];
}
}
/**
* 获取连接状态
* @returns {boolean} 是否连接
*/
getConnectionStatus() {
return this.isConnected;
}
/**
* 获取认证状态
* @returns {boolean} 是否已认证
*/
getAuthStatus() {
return this.isAuthenticated;
}
/**
* 获取重连次数
* @returns {number} 重连次数
*/
getReconnectAttempts() {
return this.reconnectAttempts;
}
/**
* 清空消息队列
*/
clearMessageQueue() {
this.messageQueue = [];
}
}
// 消息发送状态管理 - 全局作用域
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}`;
}
// 如果是get_messages消息,使用特殊格式生成ID,避免重复发送
else if (actualMessageData.type === 'get_messages') {
actualMessageData.messageId = `getmsg_${actualMessageData.conversationId || actualMessageData.targetUserId || 'unknown'}_${Date.now()}`;
}
else {
// 其他消息类型生成随机ID
actualMessageData.messageId = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// 【关键修复】对于get_messages消息,不进行去重检查,确保每次都能获取最新消息
if (actualMessageData.type !== 'get_messages') {
// 消息去重检查
if (!shouldSendMessage(actualMessageData.messageId)) {
return false;
}
}
try {
// 确保包含必要字段
if (actualMessageData.type === 'chat_message') {
// 客服消息不需要userId,只需要managerId
const isManager = actualMessageData.userType === 'manager' || actualMessageData.manager_mode;
if (!isManager && (!actualMessageData.userId || !actualMessageData.managerId)) {
console.error('[WebSocket] 聊天消息缺少必要的userId或managerId字段');
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
return false;
}
// 客服消息只需要managerId
if (isManager && !actualMessageData.managerId) {
console.error('[WebSocket] 客服消息缺少必要的managerId字段');
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
return false;
}
}
// 【关键修复】确保消息使用正确的字段名
if (actualMessageData.userId && !actualMessageData.sender_id) {
actualMessageData.sender_id = actualMessageData.userId;
}
if (actualMessageData.targetUserId && !actualMessageData.receiver_id) {
actualMessageData.receiver_id = actualMessageData.targetUserId;
}
// 设置消息状态为发送中
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(() => {
const status = messageStatus.get(actualMessageData.messageId);
if (status === MESSAGE_SENDING) {
console.warn(`[WebSocket] 消息 ${actualMessageData.messageId} 发送超时,可能需要重试`);
updateMessageStatus(actualMessageData.messageId, MESSAGE_FAILED);
}
}, 10000); // 增加超时时间到10秒,确保历史消息请求有足够时间返回
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