转诊方功能优化

This commit is contained in:
李琦
2026-02-07 11:12:18 +08:00
parent d233a42754
commit 184ff88bd2
8 changed files with 874 additions and 14 deletions

1
.gitignore vendored
View File

@@ -117,3 +117,4 @@ dist
/.hbuilderx/
/.idea/
/database/
/utils/utils.js

View File

@@ -475,6 +475,13 @@
"navigationBarTitleText": "转诊消息",
"enablePullDownRefresh": true
}
},
{
"path": "transfer/transfer-consultation",
"style": {
"navigationBarTitleText": "转诊咨询",
"enablePullDownRefresh": false
}
}
]
}

View File

@@ -34,3 +34,20 @@ export async function agreeTransferPrescriptionApi(params) {
export async function rejectTransferPrescriptionApi(params) {
return await post('/transfer-prescription/reject', params, 3)
}
/**
* 获取转诊问题列表(用于咨询页面)
* @returns {Promise<*>}
*/
export async function getTransferQuestionsApi() {
return await get('/transfer-prescription/get-questions', {}, 3)
}
/**
* 提交转诊问题答案
* @param params { transfer_id: number, answers: Array<{question_id: number, answer: string}> }
* @returns {Promise<*>}
*/
export async function submitTransferQuestionsApi(params) {
return await post('/transfer-prescription/submit-questions', params, 3)
}

View File

@@ -220,6 +220,20 @@ export default {
},
onLoad: function (option) {
// 确保 room_id 存在
if (!option.room_id) {
console.error('聊天页面缺少room_id参数');
uni.showToast({
title: '房间ID不能为空',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
return;
}
// 先设置基础信息
this.doctorUserInfo = option;
if (option.avatar) this.doctorAvatar = option.avatar;
this.currentUserId = 'user-' + uni.getStorageSync('user_id') || 'user-2512';
@@ -228,6 +242,7 @@ export default {
if (doctorId.includes('-type-')) doctorId = doctorId.split('-type-')[0];
this.doctorId = doctorId;
// 立即进入房间并设置ChatManager确保消息能正确路由
if (typeof ChatManager !== 'undefined') {
ChatManager.enterRoom(option.room_id);
ChatManager.resetUnreadCount(option.room_id);
@@ -244,6 +259,8 @@ export default {
const nickName = this.doctorUserInfo.nick_name;
if (nickName && nickName !== 'undefined' && nickName !== 'null') {
uni.setNavigationBarTitle({ title: `${nickName}医生的对话` });
} else {
uni.setNavigationBarTitle({ title: '与医生的对话' });
}
this.$nextTick(() => {
@@ -264,6 +281,15 @@ export default {
},
onShow() {
// 确保房间ID已设置从ChatManager获取作为备用
if (!this.doctorUserInfo.room_id && typeof ChatManager !== 'undefined') {
const currentRoomId = ChatManager.getCurrentRoomId();
if (currentRoomId) {
this.doctorUserInfo.room_id = currentRoomId;
}
}
// 注册事件监听器
uni.$on('new-room-message', this.handleNewRoomMessage);
uni.$on('load-room-message', this.handLoadMessageList);
uni.$on('no-more-history', this.handNoMessageList);
@@ -279,7 +305,12 @@ export default {
mounted() {
this.recorderManager = uni.getRecorderManager();
this.initRecorder();
if (typeof webSocketManager !== 'undefined') webSocketManager.addMessageHandler(this.handleSocketMessage);
// 延迟注册WebSocket消息处理器确保onLoad已执行完成room_id已设置
this.$nextTick(() => {
if (typeof webSocketManager !== 'undefined') {
webSocketManager.addMessageHandler(this.handleSocketMessage);
}
});
this.calculateScrollViewHeight();
},
@@ -487,7 +518,11 @@ export default {
},
handleSocketMessage(data) {
if (data.room_id === this.doctorUserInfo.room_id) {
// 获取当前房间ID优先使用doctorUserInfo.room_id如果未设置则从ChatManager获取
const currentRoomId = this.doctorUserInfo.room_id || (typeof ChatManager !== 'undefined' ? ChatManager.getCurrentRoomId() : null);
// 如果房间ID匹配或者消息的房间ID与当前房间ID匹配则处理消息
if (data.room_id && (data.room_id === currentRoomId || data.room_id === this.doctorUserInfo.room_id)) {
this.addMessage(data);
this.scrollToBottom();
}

View File

@@ -27,6 +27,24 @@
<text class="label">转诊原因</text>
<text class="value">{{ messageData.transfer_reason }}</text>
</view>
<!-- 咨询问题和答案 -->
<view class="questions-section" v-if="messageData.questions && messageData.questions.length > 0">
<view class="section-title">
<text class="section-icon"></text>
<text class="section-text">咨询问题</text>
</view>
<view class="question-item" v-for="(qa, index) in messageData.questions" :key="index">
<view class="question-text">
<text class="question-number">{{ index + 1 }}.</text>
<text class="question-content">{{ qa.question }}</text>
</view>
<view class="answer-text" :class="qa.answer === '是' ? 'answer-yes' : 'answer-no'">
<text class="answer-icon">{{ qa.answer === '是' ? '✓' : '✗' }}</text>
<text>{{ qa.answer }}</text>
</view>
</view>
</view>
</view>
<view class="bubble-footer" v-if="messageData.status === 0">
@@ -168,4 +186,95 @@ export default {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
/* 咨询问题区域 */
.questions-section {
margin-top: 20rpx;
padding: 20rpx;
background: rgba(255, 255, 255, 0.6);
border-radius: 12rpx;
border: 2rpx solid #fed7aa;
}
.section-title {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 16rpx;
padding-bottom: 12rpx;
border-bottom: 2rpx solid #fed7aa;
}
.section-icon {
font-size: 28rpx;
}
.section-text {
font-size: 28rpx;
font-weight: 600;
color: #1e293b;
}
.question-item {
margin-bottom: 16rpx;
padding: 16rpx;
background: white;
border-radius: 8rpx;
border: 1rpx solid #e2e8f0;
}
.question-item:last-child {
margin-bottom: 0;
}
.question-text {
font-size: 26rpx;
color: #334155;
line-height: 1.6;
margin-bottom: 12rpx;
}
.question-number {
font-weight: 600;
color: #0A84FF;
margin-right: 8rpx;
}
.question-content {
color: #1e293b;
}
.answer-text {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
font-weight: 500;
padding: 8rpx 16rpx;
border-radius: 6rpx;
width: fit-content;
}
.answer-yes {
background: #dcfce7;
color: #16a34a;
}
.answer-yes .answer-icon {
color: #16a34a;
}
.answer-no {
background: #fee2e2;
color: #dc2626;
}
.answer-no .answer-icon {
color: #dc2626;
}
.answer-icon {
font-size: 24rpx;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,639 @@
<template>
<view class="consultation-container safe-area-inset-bottom">
<!-- 顶部导航栏 -->
<view class="consultation-header">
<view class="header-back" @click="handleBack">
<text class="iconfont icon-arrow-left"></text>
</view>
<view class="header-title">转诊咨询</view>
<view class="header-placeholder"></view>
</view>
<!-- 提示信息类似图片中的灰色提示框 -->
<view class="notice-box" v-if="showNotice">
<text class="notice-text">
根据国家互联网医院管理办法要求平台仅为复诊患者提供服务为了保障您的用药安全购买该药物需要经过医生问诊请根据真实情况回答并请仔细阅读互联网医疗风险告知及知情同意书继续咨询即表示您已知悉相关规则与风险并同意相关条款
</text>
<text class="notice-link" @click="viewAgreement">互联网医疗风险告知及知情同意书</text>
</view>
<!-- 聊天消息区域 -->
<scroll-view class="messages-scroll" scroll-y="true" :scroll-into-view="scrollIntoView" scroll-with-animation>
<view class="messages-container">
<!-- 按时间顺序渲染消息 -->
<template v-for="(message, index) in displayedMessages" :key="message.id || index">
<!-- 欢迎消息 -->
<view v-if="message.type === 'welcome'" class="message-item other">
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
<view class="message-content">
<view class="assistant-label">医生助理</view>
<view class="message-bubble">
<text>您好! 我是医生助理, 已收到您的用药申请:{{ prescriptionInfo }}, 为了帮助医生判断准确, 请按照您的真实情况回答以下问题</text>
</view>
</view>
</view>
<!-- 问题消息 -->
<view v-else-if="message.type === 'question'" class="message-item other" :id="'question-' + message.questionId">
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
<view class="message-content">
<view class="assistant-label">医生助理</view>
<view class="message-bubble">
<text>{{ message.content }}</text>
</view>
</view>
</view>
<!-- 用户回答消息 -->
<view v-else-if="message.type === 'answer'" class="message-item mine">
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<view class="message-content">
<view class="message-bubble">
<text>{{ message.content }}</text>
</view>
</view>
</view>
<!-- 等待转接消息 -->
<view v-else-if="message.type === 'waiting'" class="message-item other">
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
<view class="message-content">
<view class="assistant-label">医生助理</view>
<view class="message-bubble">
<text>请稍等, 正在为您转接医生进行复诊开方...</text>
</view>
</view>
</view>
</template>
<!-- 底部锚点 -->
<view id="bottom-anchor" style="height: 1px;"></view>
</view>
</scroll-view>
<!-- 底部操作按钮只有"是""否" -->
<view class="action-footer safe-area-inset-bottom" v-if="currentQuestion && !isSubmitting && !showWaiting">
<button class="action-btn no-btn" @click="handleAnswer('否')"></button>
<button class="action-btn yes-btn" @click="handleAnswer('是')"></button>
</view>
<!-- 提交中提示 -->
<view class="submitting-overlay" v-if="isSubmitting">
<view class="submitting-content">
<view class="loading-spinner"></view>
<text class="submitting-text">正在提交...</text>
</view>
</view>
</view>
</template>
<script>
import {
getTransferPrescriptionDetailApi,
getTransferQuestionsApi,
submitTransferQuestionsApi
} from '@/request/api/transferPrescription.js'
export default {
name: 'TransferConsultation',
data() {
return {
transferId: 0, // 转诊ID
questions: [], // 问题列表
currentQuestionIndex: 0, // 当前问题索引
userAnswers: [], // 用户答案列表
showWelcome: true, // 显示欢迎消息
showNotice: true, // 显示提示信息
showWaiting: false, // 显示等待转接消息
isSubmitting: false, // 是否正在提交
scrollIntoView: '', // 滚动到指定位置
assistantAvatar: '/static/images/assistant-avatar.png', // 医生助理头像(需要替换为实际路径)
userAvatar: '/static/images/user-avatar.png', // 用户头像(需要替换为实际路径)
prescriptionInfo: '炒黄芪等共2味' // 处方信息(可以从转诊详情中获取)
}
},
computed: {
// 当前问题
currentQuestion() {
if (this.currentQuestionIndex < this.questions.length) {
return this.questions[this.currentQuestionIndex]
}
return null
},
// 按时间顺序显示的消息列表
displayedMessages() {
const messages = []
// 1. 欢迎消息
if (this.showWelcome) {
messages.push({
type: 'welcome',
id: 'welcome-msg',
timestamp: 0
})
}
// 2. 问题和答案交替显示
for (let i = 0; i <= this.currentQuestionIndex && i < this.questions.length; i++) {
const question = this.questions[i]
// 添加问题
messages.push({
type: 'question',
id: `question-${question.id}`,
questionId: question.id,
content: question.question,
timestamp: i * 2 + 1
})
// 如果有对应的答案,添加答案
if (i < this.userAnswers.length) {
messages.push({
type: 'answer',
id: `answer-${i}`,
content: this.userAnswers[i],
timestamp: i * 2 + 2
})
}
}
// 3. 等待转接消息
if (this.showWaiting) {
messages.push({
type: 'waiting',
id: 'waiting-msg',
timestamp: messages.length
})
}
return messages
}
},
onLoad(options) {
// 获取转诊ID
this.transferId = parseInt(options.transfer_id || 0)
if (!this.transferId) {
uni.showToast({
title: '转诊ID不能为空',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
}
// 加载转诊详情和问题列表
this.loadTransferDetail()
this.loadQuestions()
},
methods: {
// 加载转诊详情(获取药品信息)
async loadTransferDetail() {
try {
const res = await getTransferPrescriptionDetailApi({ id: this.transferId })
if (res.data && res.data.code === 0) {
const transferData = res.data.result || {}
// 从转诊详情中提取药品名称
if (transferData.drug_name_text) {
this.prescriptionInfo = transferData.drug_name_text
} else if (transferData.content) {
// 如果后端没有返回drug_name_text前端自己解析
const content = typeof transferData.content === 'string'
? JSON.parse(transferData.content)
: transferData.content
this.prescriptionInfo = this.extractDrugName(content, transferData.prescription_type || 1)
}
}
} catch (error) {
console.error('加载转诊详情失败:', error)
// 失败不影响继续,使用默认值
}
},
// 从处方内容中提取药品名称(前端备用方法)
extractDrugName(content, prescriptionType) {
if (!content) return '未知药品'
const drugNames = []
if (prescriptionType === 1) {
// 中药处方
if (content.repice && Array.isArray(content.repice)) {
content.repice.forEach(recipe => {
if (recipe.content) {
let recipeContent = recipe.content
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent)
} catch {
recipeContent = []
}
}
if (Array.isArray(recipeContent)) {
recipeContent.forEach(drug => {
const name = drug.name || drug.drug_name
if (name && !drugNames.includes(name)) {
drugNames.push(name)
}
})
}
}
})
}
} else {
// 西药处方
if (content.repice && Array.isArray(content.repice)) {
content.repice.forEach(recipe => {
if (recipe.content) {
let recipeContent = recipe.content
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent)
} catch {
recipeContent = {}
}
}
const name = recipeContent.drug_name || recipeContent.name
if (name && !drugNames.includes(name)) {
drugNames.push(name)
}
}
})
}
}
if (drugNames.length === 0) return '未知药品'
if (drugNames.length === 1) return drugNames[0]
return drugNames[0] + '等共' + drugNames.length + '味'
},
// 加载问题列表
async loadQuestions() {
try {
const res = await getTransferQuestionsApi()
if (res.data && res.data.code === 0) {
// 确保问题按sort字段排序
const questions = res.data.result || []
this.questions = questions.sort((a, b) => {
if (a.sort !== b.sort) {
return a.sort - b.sort
}
return a.id - b.id
})
if (this.questions.length === 0) {
uni.showToast({
title: '暂无咨询问题',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
} else {
throw new Error(res.data.message || '获取问题失败')
}
} catch (error) {
console.error('加载问题失败:', error)
uni.showToast({
title: error.message || '网络异常',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
// 处理用户回答
handleAnswer(answer) {
if (!this.currentQuestion) {
return
}
// 保存答案
this.userAnswers.push(answer)
// 如果还有下一个问题,显示下一个问题
if (this.currentQuestionIndex < this.questions.length - 1) {
this.currentQuestionIndex++
// 滚动到底部,显示新问题
this.$nextTick(() => {
this.scrollIntoView = 'bottom-anchor'
})
} else {
// 所有问题已回答,提交答案
// 先滚动到底部
this.$nextTick(() => {
this.scrollIntoView = 'bottom-anchor'
})
// 延迟提交,让用户看到最后的问题和答案
setTimeout(() => {
this.submitAnswers()
}, 500)
}
},
// 提交答案
async submitAnswers() {
if (this.isSubmitting) {
return
}
this.isSubmitting = true
this.showWaiting = true
// 滚动到底部
this.$nextTick(() => {
this.scrollIntoView = 'bottom-anchor'
})
try {
// 构建答案数组(按照问题的顺序,确保与后端保存顺序一致)
const answers = []
for (let i = 0; i < this.questions.length; i++) {
if (this.userAnswers[i]) {
answers.push({
question_id: this.questions[i].id,
answer: this.userAnswers[i]
})
}
}
const res = await submitTransferQuestionsApi({
transfer_id: this.transferId,
answers: answers
})
if (res.data && res.data.code === 0) {
const result = res.data.result || {}
uni.showToast({
title: '提交成功',
icon: 'success'
})
// 延迟跳转,让用户看到成功提示
setTimeout(() => {
// 跳转到聊天房间使用navigateTo而不是redirectTo确保页面生命周期正常执行
if (result.room_id && result.doctor_id) {
const registerId = result.register?.id || ''
// 获取医生信息优先使用返回的doctor_info否则使用默认值
const doctorInfo = result.doctor_info || {}
const doctorName = doctorInfo.name || '医生'
const doctorAvatar = doctorInfo.avatar || ''
// 构建跳转URL包含所有必要参数
let chatUrl = `/subPackages/chat/chat?user_id=${result.doctor_id}&room_id=${result.room_id}&nick_name=${encodeURIComponent(doctorName)}&room_status=0`
if (registerId) {
chatUrl += `&register_id=${registerId}`
}
if (doctorAvatar) {
chatUrl += `&avatar=${encodeURIComponent(doctorAvatar)}`
}
uni.navigateTo({
url: chatUrl
})
} else {
// 如果没有房间信息,跳转到转诊消息页面
uni.navigateBack()
}
}, 1500)
} else {
throw new Error(res.data.message || '提交失败')
}
} catch (error) {
console.error('提交答案失败:', error)
this.isSubmitting = false
this.showWaiting = false
uni.showToast({
title: error.message || '提交失败,请重试',
icon: 'none'
})
}
},
// 返回
handleBack() {
uni.showModal({
title: '提示',
content: '确定要退出咨询吗?未完成的回答将不会保存。',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
},
// 查看协议
viewAgreement() {
// TODO: 跳转到协议页面
uni.showToast({
title: '协议页面开发中',
icon: 'none'
})
}
}
}
</script>
<style lang="scss" scoped>
.consultation-container {
width: 100%;
height: 100vh;
background-color: #F8FAFC;
display: flex;
flex-direction: column;
}
.consultation-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
position: relative;
.header-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 32rpx;
color: #333;
}
}
.header-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.header-placeholder {
width: 60rpx;
}
}
.notice-box {
margin: 20rpx 30rpx;
padding: 24rpx;
background: #F5F5F5;
border-radius: 12rpx;
font-size: 24rpx;
line-height: 1.6;
color: #666;
.notice-text {
display: block;
margin-bottom: 12rpx;
}
.notice-link {
color: #0A84FF;
text-decoration: underline;
}
}
.messages-scroll {
flex: 1;
overflow: hidden;
}
.messages-container {
padding: 30rpx;
min-height: 100%;
}
.message-item {
display: flex;
align-items: flex-start;
margin-bottom: 30rpx;
.avatar {
width: 72rpx;
height: 72rpx;
border-radius: 10rpx;
flex-shrink: 0;
background: #f2f2f2;
}
.message-content {
display: flex;
flex-direction: column;
max-width: 70%;
margin-left: 16rpx;
.assistant-label {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
}
.message-bubble {
padding: 18rpx 24rpx;
font-size: 30rpx;
line-height: 1.5;
border-radius: 4rpx 12rpx 12rpx 12rpx;
background: #fff;
color: #333;
word-break: break-all;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
}
}
&.mine {
flex-direction: row-reverse;
.message-content {
margin-left: 0;
margin-right: 16rpx;
align-items: flex-end;
.message-bubble {
background: #0A84FF;
color: #fff;
border-radius: 12rpx 4rpx 12rpx 12rpx;
}
}
}
}
.action-footer {
padding: 20rpx 30rpx;
background: #fff;
border-top: 1rpx solid #f0f0f0;
display: flex;
gap: 20rpx;
justify-content: space-between;
.action-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
&::after {
border: none;
}
&.yes-btn {
background: #0A84FF;
color: #fff;
}
&.no-btn {
background: #fff;
color: #333;
border: 2rpx solid #ddd;
}
}
}
.submitting-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
.submitting-content {
background: #fff;
padding: 60rpx 80rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #0A84FF;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.submitting-text {
font-size: 28rpx;
color: #666;
}
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@@ -168,11 +168,21 @@ export default {
const response = await agreeTransferPrescriptionApi({ id: item.id })
if (response.data.code === 0) {
uni.showToast({ title: '已同意', icon: 'success' })
this.getTransferList()
// 跳转到咨询页面直接使用transfer_id传参
setTimeout(() => {
uni.redirectTo({
url: `/subPackages/transfer/transfer-consultation?transfer_id=${item.id}`,
fail: (err) => {
console.error('跳转失败:', err)
uni.showToast({ title: '跳转失败,请重试', icon: 'none' })
}
})
}, 1000)
} else {
uni.showToast({ title: response.data.message, icon: 'none' })
}
} catch (e) {
console.error('同意转诊异常:', e)
uni.showToast({ title: '操作失败', icon: 'none' })
}
}

View File

@@ -38,6 +38,7 @@ export class WebSocketManager {
/**
* 重新绑定用户(登录后调用)
* 会先清理旧连接,再绑定新连接
*/
rebindUser() {
const userId = uni.getStorageSync('user_id');
@@ -50,12 +51,13 @@ export class WebSocketManager {
this.clearCachedToken();
if (this.isConnected) {
// 如果已连接,直接绑定
return this.bindUser(userId);
// 如果已连接,直接bindGo后端会自动清理旧连接
// 注意不在这里调用unbind因为unbind会关闭当前连接
return this.bindUser(userId, true);
} else {
// 如果未连接,先连接再绑定
this.connect();
// 连接成功后会在 onOpen 中自动绑定
// 连接成功后会在 onOpen 中自动绑定(会自动清理旧连接)
return true;
}
}
@@ -86,7 +88,8 @@ export class WebSocketManager {
// 绑定当前用户使用正确的keyuser_id
const userId = uni.getStorageSync('user_id');
if (userId) {
this.bindUser(userId);
// 连接打开后立即绑定Go后端会自动清理旧连接
this.bindUser(userId, true);
}
});
@@ -113,20 +116,56 @@ export class WebSocketManager {
}
/**
* 绑用户(携带token进行认证
* @param {string} userId 用户ID
* 绑用户(清理旧连接
* 注意:此方法会关闭当前连接,只应在需要完全断开时使用
* @param {string} userId 用户ID格式user-xxx 或 数字)
* @returns {boolean} 是否发送成功
*/
bindUser(userId) {
unbindUser(userId) {
const token = this.getToken();
if (!token) {
console.warn('解绑用户失败token不存在跳过解绑');
return false;
}
// 确保userId格式正确如果是数字添加user-前缀)
const normalizedUserId = userId.toString().startsWith('user-') ? userId : `user-${userId}`;
console.log('发送unbind请求清理旧连接:', normalizedUserId);
return this.send({
request_type: 'unbind',
user_type: 'user',
sender_user_id: normalizedUserId,
token: token
}, false); // unbind不需要再次添加token
}
/**
* 绑定用户携带token进行认证
* 在绑定前会自动清理旧连接通过Go后端的clean_old_connections功能
* @param {string} userId 用户ID可以是数字或user-xxx格式
* @param {boolean} cleanOldConnections 是否清理旧连接默认true
*/
bindUser(userId, cleanOldConnections = true) {
const token = this.getToken();
if (!token) {
console.error('绑定用户失败token不存在');
return false;
}
// 确保userId格式正确统一为user-xxx格式
// 如果userId已经是user-xxx格式保持不变如果是数字添加user-前缀
const normalizedUserId = userId.toString().startsWith('user-') ? userId : `user-${userId}`;
// 注意不在bind前发送unbind因为unbind会关闭当前连接
// 清理旧连接的工作交给Go后端的clean_old_connections功能处理
// 发送bind请求添加clean_old_connections标志告诉Go后端清理旧连接
console.log('发送bind请求绑定新连接:', normalizedUserId, 'cleanOldConnections:', cleanOldConnections);
return this.send({
request_type: 'bind',
user_type: 'user',
sender_user_id: userId,
token: token
sender_user_id: normalizedUserId,
token: token,
clean_old_connections: cleanOldConnections // 告诉Go后端清理旧连接
});
}
@@ -199,11 +238,14 @@ export class WebSocketManager {
this.getUserIdAndBindClientId();
}, 5000)
} else {
// 直接发送bind请求Go后端会自动清理旧连接
// 注意不在这里调用unbind因为unbind会关闭当前连接
const bindMessage = {
request_type: 'bind',
user_type: 'user',
sender_user_id: `user-${userId}`,
token: token
token: token,
clean_old_connections: true // 告诉Go后端清理旧连接
};
this.send(bindMessage);
}
@@ -227,7 +269,7 @@ export class WebSocketManager {
this.cachedToken = null;
// 通知应用层token过期
uni.$emit('ws-token-expired');
// 尝试重新绑定
// 尝试重新绑定(会自动清理旧连接)
this.getUserIdAndBindClientId();
}
return;