uniapp下实现心跳检测服务端并且结合生命周期自动再次连接绑定客户端
page code
<template>
<view class="container" style="background-color: #F6F6F6;">
<u-navbar back-icon-color="#fff" :is-back="false" title-color="#fff"
:background="{backgroundColor: vuex_theme.color}" title="首页">
<view slot="right" @click="showDropdown" style="margin-right: 30rpx;">
<u-icon name="more-dot-fill" size="42" color="#ffffff"></u-icon>
</view>
</u-navbar>
<view v-if="dropdownVisible" class="dropdown-menu">
<view class="arrow-up"></view>
<view class="dropdown-menu-item" @click="handleOption('my')"><u-icon name="account" size="30"
color="#646464"></u-icon>
<view class="dropdown-menu-item-text">个人信息</view>
</view>
<view class="dropdown-menu-item" @click="handleOption('check_update')"><u-icon name="reload" size="30"
color="#646464"></u-icon>
<view class="dropdown-menu-item-text">检查更新</view>
</view>
<view class="dropdown-menu-item" @click="handleOption('app_set')"><u-icon name="setting" size="30"
color="#646464"></u-icon>
<view class="dropdown-menu-item-text">APP设置</view>
</view>
<view class="dropdown-menu-item" @click="handleOption('login_out')"><u-icon name="arrow-right-double"
size="30" color="#646464"></u-icon>
<view class="dropdown-menu-item-text">退出登录</view>
</view>
<view class="dropdown-menu-item" @click="handleOption('about_app')"><u-icon name="error-circle" size="30"
color="#646464"></u-icon>
<view class="dropdown-menu-item-text">关于APP</view>
</view>
</view>
<view v-if="dropdownVisible" class="arrow-up"></view>
<view class="shadow-box">
<view class="u-flex">
<view class="u-m-r-10">
<u-avatar :src="merchant.avatar" size="80"></u-avatar>
</view>
<view class="u-flex-1">
<view class="u-font-16 u-flex">
<view class="u-line-1" :style="{ color: vuex_theme.color, maxWidth: '180rpx' }">
{{merchant.nickname}}</view>
</view>
</view>
<view class="share">
<view class="u-flex">
<u-icon v-if="app_state==1" name="wifi" size="42" :color="iconColor"></u-icon>
<u-icon v-if="app_state==0" name="wifi-off" size="42" :color="iconColor"></u-icon>
<view class="u-p-l-10">{{app_status}}</view>
</view>
</view>
</view>
</view>
<view v-if="maskVisible" class="mask" @click="hideDropdown"></view>
</view>
</template>
<script>
import uploadCallLog from '@/util/calllog';
export default {
data() {
return {
count: '',
app_status: 'App连接正常',
app_state: 1,
merchant: {
avatar: "",
loginfailure: 0,
nickname: "未登录"
},
navbar: false,
dropdownVisible: false,
maskVisible: false
};
},
onLoad() {
console.log('页面加载了,initWebSocket')
},
onUnload() {
console.log('监听页面卸载 ,initWebSocket')
this.scoketClose() //
uni.$on('websocketError', this.handleWebSocketError);
uni.$on('websocketSuccess', this.handleWebSocketSuccess);
},
onShow() {
console.log('页面显示了')
this.getUser();
console.log('启动插件 start');
const levenCallCallModule = uni.requireNativePlugin("leven-call-CallModule");
levenCallCallModule.registerListener(res => {
console.log('开启监听')
uploadCallLog(res)
});
console.log('启动启动插件 end');
},
onHide() {
console.log('页面隐藏了,清理 WebSocket 客户端')
uni.$on('websocketError', this.handleWebSocketError);
uni.$on('websocketSuccess', this.handleWebSocketSuccess);
},
onPageScroll(e) {
},
computed: {
iconColor() {
return this.vuex_theme.color;
},
colorText() {
if (this.homeData.achievement.complete_percent > 20) {
return '#2fc25b';
} else if (this.homeData.achievement.complete_percent > 80) {
return '#f04864';
} else {
return '#1890ff';
}
}
},
methods: {
scoketClose(preventReconnect = false, allowConnection = true) {
this.socketIo.connectNum = 1
this.socketIo.Close(preventReconnect , allowConnection) // 主动 关闭连接 , 不会重连-----这里就一直不走
},
handleWebSocketError() {
// 更新页面状态或其他逻辑处理
this.app_status = 'App连接异常';
this.app_state = 0;
},
handleWebSocketSuccess() {
// 更新页面状态或其他逻辑处理
this.app_status = 'App连接正常';
this.app_state = 1;
},
handleOption(option) {
if (option == 'my') {
uni.navigateTo({
url: '/pages/more/personalDetails'
});
} else if (option == 'check_update') {
} else if (option == 'login_out') {
this.$u.api.onLogout().then(res => {
if (res.code == 1) {
this.scoketClose(true ,false) //
// vuex储存 token
uni.setStorageSync('vuex_token', '');
this.$u.vuex('vuex_token', '');
uni.setStorageSync('organise_id','');
uni.setStorageSync('admin_id','');
// 取消监听插件事件
const levenCallCallModule = uni.requireNativePlugin("leven-call-CallModule");
if (levenCallCallModule && levenCallCallModule.unregisterListener) {
levenCallCallModule.unregisterListener();
}
uni.showToast({
title: '退出成功',
icon: 'success',
duration: 500
});
// 导航到登录页面
setTimeout(() => {
this.$u.route('pages/login/index');
}, 500); // 等待 Toast 消失后再跳转
}
})
} else if (option == 'about_app') {
uni.navigateTo({
url: '/pages/more/aboutApp'
});
}
// 根据 option 执行不同的操作
console.log(`Selected option: ${option}`);
this.dropdownVisible = false;
this.maskVisible = false;
},
// 跳转到更多
onMore() {
this.$u.route('pages/more/index', {});
},
// 获取首页数据
// 获取用户信息
getUser() {
console.log('get user aaaa')
this.$u.api.onGetInfo().then(res => {
if (res.code == 1) {
console.log('get user response')
this.merchant = res.data;
uni.setStorageSync('admin_info', res.data);
uni.setStorageSync('organise_id', res.data.organise_department_id)
uni.setStorageSync('admin_id', res.data.id)
uni.setStorageSync('local_username', res.data.username)
uni.setStorageSync('vuex_token', res.data.token);
this.socketIo.preventReconnect = false;
this.socketIo.connectSocketInit()
uni.$on('websocketError', this.handleWebSocketError);
uni.$on('websocketSuccess', this.handleWebSocketSuccess);
}
}).catch(res => {
// vuex储存 token
uni.setStorageSync('vuex_token', '');
this.$u.vuex('vuex_token', '');
const levenCallCallModule = uni.requireNativePlugin("leven-call-CallModule");
levenCallCallModule.registerListener(res => {
console.log('取消监听')
});
// 未登录还原默认数据
if (res.data.code == 401) {
this.merchant = {
avatar: "",
groups: [],
loginfailure: 0,
nickname: "未登录",
username: "",
}
}
})
},
showDropdown() {
this.dropdownVisible = !this.dropdownVisible;
this.maskVisible = true;
},
hideDropdown() {
this.dropdownVisible = false;
this.maskVisible = false;
},
},
}
</script>
<style lang="scss" scoped>
.shadow-box {
max-width: 680rpx;
background-color: #ffffff;
border-radius: 10rpx;
/* 圆角半径 */
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.1);
/* 阴影效果 */
padding: 30rpx;
/* 可选:内部填充 */
margin: 20rpx auto;
/* 水平居中 */
}
.container {
background-color: #F7F7F7 !important;
padding-bottom: 50px;
min-height: 100vh;
}
.share {
border: 1px solid #fff;
border-radius: 5px;
padding: 15rpx 30rpx;
}
.dropdown-menu {
position: absolute;
top: 155rpx;
right: 18rpx;
background-color: #ffffff;
// border: 1rpx solid #ddd;
border-radius: 20rpx;
padding: 0 30rpx 30rpx 30rpx;
z-index: 1000;
color: #646464;
}
.dropdown-menu-item {
display: flex;
margin-top: 40rpx;
color: #646464;
}
.dropdown-menu-item-text {
margin-left: 20rpx;
color: #646464;
font-size: 28rpx;
}
.arrow-up {
width: 0;
height: 0;
border-left: 25rpx solid transparent;
border-right: 25rpx solid transparent;
border-bottom: 25rpx solid white;
position: absolute;
top: -20rpx;
right: 15rpx;
}
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.25);
z-index: 999;
}
</style>
main.js
//引入websocket文件 import socketIO from '@/util/socket.js' Vue.prototype.socketIo = new socketIO()
/util/socket.js文件内容
import { socket_url } from '@/common/config'; // 确保路径正确
class socketIO {
constructor(time, url = socket_url) {
this.socketTask = null;
this.is_open_socket = false; // 避免重复连接
this.url = url || socket_url; // 连接地址
this.connectNum = 1; // 重连次数
this.reconnectInterval = null; // 用于存储重连的定时器ID
this.reconnectDelay = 3000; // 每隔5秒尝试一次重连
this.maxRetries = Infinity; // 最大重试次数
this.heartbeatTimeout = time || 2000; // 心跳检测间隔时间
this.heartbeatInterval = null; // 心跳检测定时器
this.preventReconnect = false; // 新增:阻止重连的标志
this.allowConnection = true; // 新增:允许建立新连接的标志
}
connectSocketInit() {
console.log('initaaaa');
console.log(this.is_open_socket);
console.log(this.preventReconnect);
if (this.is_open_socket || this.preventReconnect) return;
console.log("尝试建立WebSocket连接...");
this.socketTask = uni.connectSocket({
url: this.url,
success: () => {
console.log("WebSocket 正准备建立...");
},
});
this.setupEventListeners();
}
setupEventListeners() {
this.socketTask.onOpen(() => {
console.log("WebSocket 连接正常!");
clearInterval(this.reconnectInterval);
clearInterval(this.heartbeatInterval);
this.is_open_socket = true;
this.connectNum = 1;
this.startHeartbeat();
this.socketTask.onMessage((res) => {
let data = res.data;
console.log("WebSocket 收到消息:" + res.data);
try {
data = JSON.parse(data);
uni.$emit('websocketSuccess');
} catch (e) {
console.log('ws接收到非对象数据', data);
return;
}
var type = data.type || '';
switch(type){
case 'get_client_id':
uni.setStorageSync('local_client_id', data.data.client_id);
this.bindUid(data.data.client_id);
break;
case "call_mobile":
let mobile = data.mobile;
let call_log_id = 0;
if (data.call_log_id != undefined && data.call_log_id != '') {
call_log_id = data.call_log_id;
uni.setStorageSync('local_call_log_id', call_log_id);
}
this.callphone(mobile)
break;
}
});
});
uni.onSocketError(() => {
console.log('WebSocket 连接打开失败,请检查!');
this.allowConnection = true;
this.handleConnectionError();
});
this.socketTask.onClose(() => {
console.log("WebSocket 已被关闭");
this.handleConnectionError();
});
}
handleConnectionError() {
this.is_open_socket = false;
clearInterval(this.heartbeatInterval);
clearInterval(this.reconnectInterval);
uni.$emit('websocketError');
console.log(this.preventReconnect);
console.log(this.connectNum);
console.log(this.maxRetries);
console.log(this.allowConnection);
if (!this.preventReconnect && this.connectNum <= this.maxRetries && this.allowConnection) {
uni.showToast({
title: `App连接异常,正尝试第${this.connectNum}次连接`,
icon: "none"
});
this.reconnect();
this.connectNum += 1;
} else {
uni.$emit('connectError');
this.connectNum = 1;
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
this.sendPing();
}, this.heartbeatTimeout);
}
sendPing() {
this.send({ traderid: 10260, type: "Ping" });
}
Close(preventReconnect = false, allowConnection = true) {
this.preventReconnect = preventReconnect;
this.allowConnection = allowConnection;
if (!this.is_open_socket) return;
this.socketTask.close();
this.is_open_socket = false;
clearInterval(this.heartbeatInterval);
clearInterval(this.reconnectInterval);
}
send(data) {
if (this.socketTask) {
this.socketTask.send({
data: JSON.stringify(data),
success: () => {
console.log("消息发送成功");
},
fail: () => {
console.error("消息发送失败");
}
});
}
}
reconnect() {
// 如果不是人为关闭的话,进行重连
if (!this.preventReconnect && !this.is_open_socket && this.allowConnection) {
clearInterval(this.reconnectInterval); // 清除之前的定时器
this.reconnectInterval = setInterval(() => {
console.log(`尝试重新连接... 尝试次数: ${this.connectNum}`);
this.connectSocketInit();
}, this.reconnectDelay);
}
}
bindUid(client_id) {
console.log('WebSocket 绑定客户端id' + client_id);
let organise_id = uni.getStorageSync('organise_id');
let admin_id = uni.getStorageSync('admin_id');
console.log(organise_id)
console.log(admin_id)
if (organise_id && admin_id) {
uni.$u.api.postBindAppAdminClientId({ client_id, organise_id, admin_id }).then(res => {
if (res.code == 1) {
uni.$emit('websocketSuccess');
console.log('绑定成功,可以拨号');
}
});
}
}
callphone(mobile) {
// #ifdef APP-PLUS
plus.device.dial(mobile, false);
// #endif
}
}
module.exports = socketIO
分类:
WebSocket/WorkerMan
, UniApp
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2024-01-01 fastadmin隐藏指定的bootstrap table 列和显示隐藏列中指定项