596 lines
18 KiB
Vue
596 lines
18 KiB
Vue
<template>
|
||
<view class="chat-container">
|
||
<!-- 聊天头部 -->
|
||
<view class="chat-header" :style="{ paddingTop: top + 'px', height: localHeight + 'px' }">
|
||
<view class="back-btn" @tap="goBack">
|
||
<uni-icons type="left" size="28" color="#333"></uni-icons>
|
||
</view>
|
||
<view class="chat-title">{{ chatTarget.title || '客服' }}</view>
|
||
<view class="change-service-btn" @tap="goToChangeService">
|
||
<uni-icons type="switch" size="22" color="#333"></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 连接状态提示 -->
|
||
<view v-if="connectingStatus" class="connecting-status">{{ connectingStatus }}</view>
|
||
|
||
<!-- 聊天消息区域 -->
|
||
<scroll-view class="chat-messages" scroll-y="true" :scroll-into-view="scrollToView"
|
||
@scrolltoupper="loadMoreHistory">
|
||
<!-- 加载历史消息提示 -->
|
||
<view v-if="isLoadingHistory" class="message-time">加载历史消息...</view>
|
||
|
||
<!-- 消息列表 -->
|
||
<block v-for="(message, index) in messages" :key="index">
|
||
<!-- 时间分割线 -->
|
||
<view v-if="needShowTime(index)" class="message-time">{{ formatTime(message.time) }}</view>
|
||
|
||
<!-- 消息项 -->
|
||
<view class="message-item" :class="{
|
||
'self': message.isSelf,
|
||
'other': !message.isSelf,
|
||
'loading': message.isLoading
|
||
}" :id="'msg-' + index">
|
||
<image class="message-avatar" :src="message.isSelf ? userAvatar : (chatTarget.avatar || '/static/logo.png')"
|
||
mode="aspectFill"></image>
|
||
<view class="message-content">
|
||
{{ message.content }}
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</scroll-view>
|
||
|
||
<!-- 输入区域 -->
|
||
<view class="chat-input-area">
|
||
<view class="input-container">
|
||
<textarea class="message-input" placeholder="请输入消息..." v-model="inputMessage" @input="handleInput"
|
||
@confirm="sendMessage" :adjust-position="true"></textarea>
|
||
<button class="send-btn" :disabled="!canSend || (!mqttUtils || !mqttUtils.getIsConnected())" @tap="sendMessage">
|
||
发送
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
request,
|
||
picUrl,
|
||
uniqueByField,
|
||
menuButtonInfo,
|
||
NavgateTo
|
||
} from '../../../utils';
|
||
import { apiArr } from '../../../api/customerService';
|
||
// 修改导入路径
|
||
import mqtt from 'mqtt'
|
||
import MqttUtils from '../../../utils/mqtt';
|
||
import awaitRated from "@/packages/myOrders/awaitRated/awaitRated.vue";
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
localHeight: "",
|
||
top: "",
|
||
// 聊天目标信息
|
||
chatTarget: {
|
||
mchId: '',
|
||
title: '',
|
||
avatar: '',
|
||
openId: '' // 接收方的open_id
|
||
},
|
||
// 用户头像
|
||
userAvatar: '/static/logo.png',
|
||
// 消息列表
|
||
messages: [],
|
||
// 输入的消息
|
||
inputMessage: '',
|
||
// 是否可以发送消息
|
||
canSend: false,
|
||
// 连接状态
|
||
isConnected: false,
|
||
// 连接状态文本
|
||
connectingStatus: '',
|
||
// 滚动到指定视图
|
||
scrollToView: '',
|
||
// 是否加载历史消息
|
||
isLoadingHistory: false,
|
||
// 心跳包定时器
|
||
keepaliveTimer: null,
|
||
// MQTT工具实例
|
||
client: null,
|
||
// 重连失败提示定时器
|
||
reconnectFailedTimer: null
|
||
};
|
||
},
|
||
onLoad(options) {
|
||
const meun = menuButtonInfo();
|
||
this.top = meun.top;
|
||
this.localHeight = meun.height;
|
||
// 获取聊天对象信息
|
||
if (options.item) {
|
||
this.chatTarget = JSON.parse(options.item);
|
||
this.chatTarget.title = `客服${this.chatTarget.employee_name}`;
|
||
}
|
||
|
||
// 创建MQTT工具实例
|
||
// this.mqttUtils = new MqttUtils({
|
||
// username: 'dev01',
|
||
// password: '211561',
|
||
// clientId: 'mqtt_8812321421_' + Math.random().toString(16).substr(2, 8),
|
||
// protocolVersion: 3,
|
||
// connectTimeout: 30 * 1000,
|
||
// reconnectPeriod: 5000,
|
||
// keepalive: 60,
|
||
// clean: true,
|
||
// // 配置重连参数
|
||
// maxReconnectAttempts: 15,
|
||
// reconnectExponentialBackoff: true
|
||
// });
|
||
// this.mqttUtils.connect()
|
||
|
||
// 初始化MQTT连接
|
||
this.initChat();
|
||
},
|
||
onShow() {
|
||
// 检查MQTT连接状态
|
||
// if (this.mqttUtils && !this.mqttUtils.getIsConnected()) {
|
||
// this.connectingStatus = '连接中...';
|
||
// }
|
||
},
|
||
methods: {
|
||
async connect() {
|
||
const options = {
|
||
clientId: 'mqtt_821321321',
|
||
username: 'dev01',
|
||
password: '211561',
|
||
keepalive: 30,
|
||
protocolId: 'MQTT',
|
||
protocolVersion: 3,
|
||
reconnectPeriod: 1000,
|
||
connectTimeout: 15 * 1000,
|
||
clean: true,
|
||
path: '/',
|
||
pathname: 'mqtt'
|
||
}
|
||
// #ifdef H5
|
||
this.client = mqtt.connect('ws://api.hshuishang.com:8084', options)
|
||
console.log('H5',this.client)
|
||
// #endif
|
||
// #ifdef MP-WEIXIN||APP-PLUS
|
||
this.client = mqtt.connect('wx://api.hshuishang.com:8084', options)
|
||
console.log('WX',this.client)
|
||
// #endif
|
||
this.client.on('connect', async function () {
|
||
this.isConnected = true
|
||
console.log('连接成功')
|
||
this.subscribe()
|
||
}).on('reconnect', function () {
|
||
console.log('正在重新连接.....')
|
||
}).on('error', function (err) {
|
||
console.log('on error',err)
|
||
}).on('end', function () {
|
||
console.log('on end')
|
||
}).on('message', function (topic, message) {
|
||
console.log(message)
|
||
console.log(message.toString())
|
||
})
|
||
},
|
||
async subscribe() {
|
||
if (this.isConnected && this.client) {
|
||
this.client.subscribe('contact/message/receive_msg',{qos:0}, function (err) {
|
||
if (!err) {
|
||
console.log('订阅成功')
|
||
this.client.publish({'contact/message/send_msg': 0}, 'hello mqtt')
|
||
} else {
|
||
console.log('订阅失败:', err);
|
||
this.connectingStatus = '订阅失败,请重试';
|
||
}
|
||
})
|
||
} else {
|
||
console.log('连接失败', this.isConnected, this.client)
|
||
}
|
||
},
|
||
// 初始化聊天
|
||
async initChat() {
|
||
try {
|
||
// 显示连接状态
|
||
this.connectingStatus = '连接中...';
|
||
|
||
this.connect()
|
||
// // 获取MQTT连接配置
|
||
// await this.getMqttConfig();
|
||
|
||
// // 连接MQTT服务器
|
||
// this.mqttUtils.connect();
|
||
|
||
// // 监听事件
|
||
// this.mqttUtils.on('connect', (connack) => {
|
||
// console.log('MQTT连接成功:', connack);
|
||
// this.isConnected = true;
|
||
// this.connectingStatus = '';
|
||
|
||
// // 订阅主题
|
||
// this.mqttUtils.subscribe('contact/message/receive_msg', (err) => {
|
||
// if (err) {
|
||
// console.error('订阅失败:', err);
|
||
// this.connectingStatus = '订阅失败,请重试';
|
||
// } else {
|
||
// console.log('订阅成功: contact/message/receive_msg');
|
||
// // 加载历史消息
|
||
// this.loadHistoryMessages();
|
||
|
||
// // 启动心跳包
|
||
// this.startKeepalive();
|
||
// }
|
||
// });
|
||
// });
|
||
|
||
// this.mqttUtils.on('error', (error) => {
|
||
// console.log('MQTT连接错误:', error);
|
||
// console.error('错误码:', error.code);
|
||
// console.error('错误详情:', error.message);
|
||
// this.isConnected = false;
|
||
// this.connectingStatus = '连接失败,请检查网络';
|
||
// });
|
||
|
||
// this.mqttUtils.on('disconnect', () => {
|
||
// console.log('MQTT连接已断开');
|
||
// })
|
||
|
||
// this.mqttUtils.on('message', (topic, message) => {
|
||
// console.log('收到消息:', topic, message.toString());
|
||
// try {
|
||
// // 解析消息
|
||
// let msgData;
|
||
// if (typeof message === 'string') {
|
||
// try {
|
||
// msgData = JSON.parse(message);
|
||
// } catch (e) {
|
||
// console.error('消息解析失败', e);
|
||
// return;
|
||
// }
|
||
// } else {
|
||
// msgData = JSON.parse(message.toString());
|
||
// }
|
||
|
||
// // 只处理与当前聊天对象相关的消息
|
||
// if (msgData.receive_client === this.mqttConfig.clientId &&
|
||
// msgData.send_client === this.chatTarget.openId) {
|
||
|
||
// // 添加到消息列表
|
||
// this.messages.push({
|
||
// id: `msg_${Date.now()}_${Math.random()}`,
|
||
// content: msgData.content,
|
||
// isSelf: false,
|
||
// time: Date.now(),
|
||
// isLoading: false
|
||
// });
|
||
|
||
// // 滚动到底部
|
||
// this.scrollToBottom();
|
||
// }
|
||
// } catch (error) {
|
||
// console.error('处理消息失败', error);
|
||
// }
|
||
// });
|
||
|
||
// this.mqttUtils.on('reconnect', (attempts, delay) => {
|
||
// console.log('MQTT正在重连...', attempts, delay);
|
||
// this.connectingStatus = `连接已断开,正在重连(${attempts})...`;
|
||
// });
|
||
|
||
// this.mqttUtils.on('close', () => {
|
||
// console.log('MQTT连接关闭');
|
||
// this.isConnected = false;
|
||
// this.connectingStatus = '连接已关闭';
|
||
// this.stopKeepalive();
|
||
// });
|
||
|
||
// this.mqttUtils.on('offline', () => {
|
||
// console.log('MQTT离线');
|
||
// this.isConnected = false;
|
||
// this.connectingStatus = '离线,请检查网络';
|
||
// this.stopKeepalive();
|
||
// });
|
||
|
||
// this.mqttUtils.on('end', () => {
|
||
// console.log('MQTT客户端结束');
|
||
// this.isConnected = false;
|
||
// this.connectingStatus = '客户端已结束';
|
||
// this.stopKeepalive();
|
||
// });
|
||
|
||
// // 添加重连失败事件监听
|
||
// this.mqttUtils.on('reconnectFailed', () => {
|
||
// console.error('MQTT重连失败,已达到最大重连次数');
|
||
// this.connectingStatus = '连接失败,请检查网络后重试';
|
||
|
||
// // 5秒后尝试重新初始化连接
|
||
// this.reconnectFailedTimer = setTimeout(() => {
|
||
// console.log('重连失败后,尝试重新初始化连接');
|
||
// this.initChat();
|
||
// }, 5000);
|
||
// });
|
||
|
||
} catch (error) {
|
||
console.error('初始化聊天失败', error);
|
||
this.connectingStatus = '连接失败,请重试';
|
||
|
||
// 失败后尝试重新连接
|
||
setTimeout(() => {
|
||
this.initChat();
|
||
}, 3000);
|
||
}
|
||
},
|
||
|
||
// 获取MQTT连接配置
|
||
async getMqttConfig() {
|
||
console.log("🚀 ~ getMqttConfig ~ getMqttConfig:", 11111)
|
||
try {
|
||
// // 直接使用已创建的MQTT实例的clientId
|
||
// if (this.mqttUtils && this.mqttUtils.options.clientId) {
|
||
// this.mqttConfig.clientId = this.mqttUtils.options.clientId;
|
||
// return Promise.resolve();
|
||
// }
|
||
|
||
// 如果没有已创建的实例或clientId,则通过API获取
|
||
return new Promise((resolve, reject) => {
|
||
const params = {
|
||
worker_id: this.chatTarget.id || '',
|
||
open_id: uni.getStorageSync('openId') || '',
|
||
}
|
||
request(apiArr.csGetToClientId, "POST", params).then((res) => {
|
||
// 检查响应数据格式是否正确
|
||
if (res && res.client_bind && res.client_bind.client_id_one && res.client_bind.client_id_two) {
|
||
this.mqttConfig.clientId = res.client_bind.client_id_one; // 登录人的open_id
|
||
this.chatTarget.openId = res.client_bind.client_id_two; // 接收方的open_id
|
||
|
||
// 如果MQTT实例已创建,更新clientId
|
||
if (this.mqttUtils) {
|
||
this.mqttUtils.options.clientId = this.mqttConfig.clientId;
|
||
}
|
||
|
||
resolve();
|
||
} else {
|
||
console.error('MQTT配置响应格式不正确:', res);
|
||
reject(new Error('未获取到有效的MQTT配置'));
|
||
}
|
||
}).catch(error => {
|
||
console.error('获取MQTT配置失败', error);
|
||
reject(error);
|
||
})
|
||
});
|
||
} catch (error) {
|
||
console.error('获取MQTT配置失败', error);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// MQTT断开连接回调 (为了兼容旧的调用)
|
||
onDisconnect(packet) {
|
||
console.log('MQTT连接断开', packet);
|
||
this.isConnected = false;
|
||
|
||
// 根据断开原因设置不同的连接状态文本
|
||
if (packet.error) {
|
||
// 连接失败的情况
|
||
this.connectingStatus = '连接失败,请检查网络或服务器';
|
||
} else if (packet.reconnecting) {
|
||
// 正在重连的情况
|
||
this.connectingStatus = '连接已断开,正在重连...';
|
||
} else {
|
||
// 其他断开连接的情况
|
||
this.connectingStatus = '连接已断开,正在重连...';
|
||
}
|
||
|
||
// 停止心跳包
|
||
this.stopKeepalive();
|
||
},
|
||
|
||
// 加载历史消息
|
||
async loadHistoryMessages() {
|
||
try {
|
||
this.isLoadingHistory = true;
|
||
|
||
// 调用API获取历史消息
|
||
// const response = await this.$http.get(apiArr.csGetMsgRecord, {
|
||
// mchId: this.chatTarget.mchId,
|
||
// pageSize: 20,
|
||
// pageNum: 1
|
||
// });
|
||
|
||
// if (response.success && response.data && response.data.list) {
|
||
// // 处理历史消息
|
||
// const historyMessages = response.data.list.map(item => ({
|
||
// id: item.id || `msg_${item.time}_${Math.random()}`,
|
||
// content: item.content,
|
||
// isSelf: item.from === 'self',
|
||
// time: item.time,
|
||
// isLoading: false
|
||
// }));
|
||
|
||
// // 添加到消息列表开头
|
||
// this.messages = [...historyMessages, ...this.messages];
|
||
|
||
// // 滚动到底部
|
||
// this.scrollToBottom();
|
||
// }
|
||
} catch (error) {
|
||
console.error('加载历史消息失败', error);
|
||
} finally {
|
||
this.isLoadingHistory = false;
|
||
}
|
||
},
|
||
|
||
// 加载更多历史消息
|
||
loadMoreHistory() {
|
||
if (this.isLoadingHistory) return;
|
||
// 这里可以实现加载更多历史消息的逻辑
|
||
},
|
||
|
||
// 发送消息
|
||
sendMessage() {
|
||
const content = this.inputMessage.trim();
|
||
if (!content || !this.mqttUtils || !this.mqttUtils.getIsConnected()) return;
|
||
|
||
// 创建消息对象
|
||
const message = {
|
||
id: `msg_${Date.now()}_${Math.random()}`,
|
||
content: content,
|
||
isSelf: true,
|
||
time: Date.now(),
|
||
isLoading: true
|
||
};
|
||
|
||
// 添加到消息列表
|
||
// this.messages.push(message);
|
||
|
||
// 滚动到底部
|
||
this.scrollToBottom();
|
||
|
||
// 按照用户提供的格式构建发送消息
|
||
const msgData = {
|
||
bind_id: 1, // 聊天列表的ID,这里暂时固定为1
|
||
send_client: this.mqttConfig.clientId, // 消息发送方open_id
|
||
receive_client: this.chatTarget.openId, // 消息接收方open_id
|
||
type: 1, // 消息类型,1表示文字消息
|
||
content: content, // 消息内容
|
||
receive_read_status: 2, // 接收方阅读状态
|
||
};
|
||
|
||
this.mqttUtils.publish(
|
||
'contact/message/send_msg', // 使用指定的发送消息主题
|
||
JSON.stringify(msgData),
|
||
{ Qos: 0 },
|
||
(err) => {
|
||
// 更新消息状态
|
||
const index = this.messages.findIndex(m => m.id === message.id);
|
||
if (index !== -1) {
|
||
this.$set(this.messages[index], 'isLoading', false);
|
||
}
|
||
|
||
if (err) {
|
||
console.error('发送消息失败', err);
|
||
// 可以在这里添加消息发送失败的处理逻辑
|
||
} else {
|
||
console.log('发送消息成功');
|
||
}
|
||
}
|
||
);
|
||
|
||
// 清空输入框
|
||
this.inputMessage = '';
|
||
this.canSend = false;
|
||
},
|
||
|
||
// 处理输入事件
|
||
handleInput() {
|
||
this.canSend = this.inputMessage.trim().length > 0;
|
||
},
|
||
|
||
// 是否需要显示时间分割线
|
||
needShowTime(index) {
|
||
if (index === 0) return true;
|
||
|
||
const currentMsg = this.messages[index];
|
||
const prevMsg = this.messages[index - 1];
|
||
|
||
// 如果两条消息间隔超过5分钟,则显示时间分割线
|
||
return (currentMsg.time - prevMsg.time) > 5 * 60 * 1000;
|
||
},
|
||
|
||
// 格式化时间
|
||
formatTime(time) {
|
||
const date = new Date(time);
|
||
const hours = date.getHours().toString().padStart(2, '0');
|
||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
|
||
return `${hours}:${minutes}`;
|
||
},
|
||
|
||
// 滚动到底部
|
||
scrollToBottom() {
|
||
setTimeout(() => {
|
||
this.scrollToView = 'msg-' + (this.messages.length - 1);
|
||
}, 100);
|
||
},
|
||
|
||
// 返回上一页
|
||
goBack() {
|
||
uni.navigateBack();
|
||
},
|
||
|
||
// 启动心跳包 - 增强版:添加错误处理和状态检查
|
||
startKeepalive() {
|
||
// 停止之前的定时器
|
||
this.stopKeepalive();
|
||
|
||
// 每30秒发送一次心跳包
|
||
this.keepaliveTimer = setInterval(() => {
|
||
if (this.mqttUtils && this.mqttUtils.getIsConnected()) {
|
||
const keepaliveData = {
|
||
client_id: this.mqttConfig.clientId
|
||
};
|
||
|
||
this.mqttUtils.publish(
|
||
'contact/message/keep_time',
|
||
JSON.stringify(keepaliveData),
|
||
{},
|
||
(err) => {
|
||
if (err) {
|
||
console.error('发送心跳包失败', err);
|
||
// 心跳包发送失败可能表示连接有问题,可以考虑触发重连
|
||
if (!this.isConnected) {
|
||
return;
|
||
}
|
||
console.log('心跳包发送失败,尝试检查连接状态');
|
||
// 这里可以添加额外的连接检查逻辑
|
||
}
|
||
}
|
||
);
|
||
} else {
|
||
console.warn('MQTT未连接,停止心跳包');
|
||
this.stopKeepalive();
|
||
}
|
||
}, 30000);
|
||
},
|
||
|
||
// 停止心跳包
|
||
stopKeepalive() {
|
||
if (this.keepaliveTimer) {
|
||
clearInterval(this.keepaliveTimer);
|
||
this.keepaliveTimer = null;
|
||
}
|
||
},
|
||
|
||
// 跳转到切换客服页面
|
||
goToChangeService() {
|
||
uni.navigateTo({
|
||
url: '/packages/customerService/changeService/index?currentMchId=' + this.chatTarget.mchId
|
||
});
|
||
}
|
||
},
|
||
|
||
// 页面卸载时停止心跳包
|
||
onUnload() {
|
||
// 断开MQTT连接
|
||
if (this.mqttUtils) {
|
||
this.mqttUtils.disconnect();
|
||
}
|
||
|
||
// 停止心跳包
|
||
this.stopKeepalive();
|
||
|
||
// 清除重连失败提示定时器
|
||
if (this.reconnectFailedTimer) {
|
||
clearTimeout(this.reconnectFailedTimer);
|
||
this.reconnectFailedTimer = null;
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
@import url("./index.css");
|
||
</style> |