71 changed files with 674 additions and 16023 deletions
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.8 MiB |
File diff suppressed because it is too large
@ -1,7 +1,3 @@ |
|||||
{ |
{ |
||||
"navigationBarTitleText": "聊天详情", |
"navigationBarTitleText": "聊天详情" |
||||
"navigationBarBackgroundColor": "#ffffff", |
|
||||
"navigationBarTextStyle": "black", |
|
||||
"usingComponents": {}, |
|
||||
"enablePullDownRefresh": false |
|
||||
} |
} |
||||
@ -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> |
||||
@ -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; |
|
||||
} |
} |
||||
@ -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❌ 测试结果: 失败!请检查代码实现。'); |
|
||||
} |
|
||||
}); |
|
||||
@ -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 |
|
||||
}; |
|
||||
@ -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 |
|
||||
}; |
|
||||
@ -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 }); |
|
||||
} |
} |
||||
}) |
}); |
||||
@ -1,6 +1,3 @@ |
|||||
{ |
{ |
||||
"navigationBarTitleText": "消息", |
"navigationBarTitleText": "消息中心" |
||||
"navigationBarBackgroundColor": "#ffffff", |
|
||||
"navigationBarTextStyle": "black", |
|
||||
"usingComponents": {} |
|
||||
} |
} |
||||
@ -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> |
||||
@ -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; |
|
||||
} |
} |
||||
@ -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: '' |
|
||||
}; |
|
||||
} |
|
||||
}); |
|
||||
@ -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" |
||||
|
} |
||||
} |
} |
||||
@ -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> |
||||
@ -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; |
||||
} |
} |
||||
@ -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(); |
|
||||
} |
} |
||||
}); |
}); |
||||
@ -1,5 +1,3 @@ |
|||||
{ |
{ |
||||
"navigationBarBackgroundColor": "#f8f8f8", |
"navigationBarTitleText": "客服中心" |
||||
"navigationBarTextStyle": "black", |
|
||||
"usingComponents": {} |
|
||||
} |
} |
||||
@ -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> |
||||
@ -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; |
|
||||
} |
} |
||||
@ -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' |
|
||||
}); |
|
||||
} |
|
||||
} |
} |
||||
}); |
}); |
||||
@ -1,4 +1,3 @@ |
|||||
{ |
{ |
||||
"navigationBarTitleText": "消息列表", |
"navigationBarTitleText": "消息列表" |
||||
"usingComponents": {} |
|
||||
} |
} |
||||
@ -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> |
||||
@ -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(); |
|
||||
} |
|
||||
}); |
|
||||
@ -1,3 +0,0 @@ |
|||||
{ |
|
||||
"usingComponents": {} |
|
||||
} |
|
||||
@ -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> |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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); |
|
||||
@ -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(); |
|
||||
@ -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); |
|
||||
@ -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(); |
|
||||
@ -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都存在`); |
|
||||
@ -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('测试脚本已启动,请等待测试结果...'); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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); |
|
||||
}); |
|
||||
@ -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测试完成!'); |
|
||||
}); |
|
||||
@ -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); |
|
||||
}); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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); |
|
||||
}); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
}); |
|
||||
@ -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(); |
|
||||
@ -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(); |
|
||||
@ -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的混用。 |
|
||||
|
|
||||
通过实施建议的修复方案,可以提高系统的可靠性和数据一致性,确保聊天功能正常工作,消息能够正确存储和传递。 |
|
||||
@ -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('=== 测试完成 ==='); |
|
||||
@ -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('=== 测试完成 ==='); |
|
||||
@ -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; |
|
||||
@ -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…
Reference in new issue