2025-09-24 08:37:16 +08:00

596 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>