在称重小程序是使用 GlobalWebsoket 实现获取实时数据
- 前言
- 一、逻辑分析
- 二、实现方式
- 1.方法整体流转分析 -- 初始化并绑定
- 1. onLoad
- 1. init
- 2. getDeviceInfo
- 3. initWebSocket
- 4. setProperties
- 2.方法整体流转分析 -- 解除绑定
- 1. onBackPress
- 2. remoeSubscribe
- 三、参数调用分析
- 四、请求查看
- 整体代码
前言
这里使用的 GlobalWebsoket 是封装好的,详细见
GlobalWebsoket.js 封装配置分析
一、逻辑分析
现在小程序的功能需求是当用户点击某个设备,进入到设备详情页,测试需要创建 websoket 的连接,并监听当前设备的实时数据,切换设备之后,解除上次监听的内容,重新绑定新的设备的实时数据监听
实现思路 :
设备页面打开,获取设备信息成功之后,初始化 initwebsoket ,并发送需要监听的数据
在页面离开的时候,接触所有监听,但是并不断掉 websoket 的连接,只是接触绑定的监听
切换设备后,获取设备信息成功之后绑定新的设备需要监听的内容
二、实现方式
1.方法整体流转分析 – 初始化并绑定
当页面打开,执行 onLoad()
1. onLoad
onLoad(option) {
// console.log(option.id)
this.init(option.id);
this.deviceId = option.id
this.device.id = option.id
},
1. init
init(deviceId) {
this.getDeviceInfo(deviceId);
},
2. getDeviceInfo
getDeviceInfo(deviceId) {
DeviceApi.getDevice({
id: deviceId
// id:'2208004'
}).then(res => {
console.log(res)
this.maxCapacity = res.data.maxCapacity
// 这里获取企业名称
// this.getCustomerNameById(res.data.customerId)
this.device = res.data || {}
this.initWebSocket();
console.log(this.device);
})
},
3. initWebSocket
// 初始化 WebSocket
initWebSocket() {
const app = this
// 这里解除绑定别忘了 !!! 放到了点击返回按钮的时候,也就是回到列表页面
// app.remoeSubscribe();
// debugger
app.subsProperties = [];
//把当前设备数据里面的metadata(单据,称重...)转成json
const metadata = JSON.parse(app.device.metadata);
app.deviceStatus = getWebsocket(
`instance-editor-info-status-${app.deviceId}`,
`/dashboard/device/status/change/realTime`, {
deviceId: app.deviceId,
},
).subscribe(resp => {
const {
payload
} = resp;
app.device.state =
payload.value.type === 'online' ? {
value: 'online',
text: '在线'
} : {
value: 'offline',
text: '离线',
};
if (payload.value.type === 'online') {
app.device.onlineTime = payload.timestamp;
} else {
app.device.offlineTime = payload.timestamp;
}
app.$set(app.device, 'timeStamp', moment().unix())
});
console.log(app.deviceStatus)
/**
这里的 getWebsocket 是引入的
import {
getWebsocket
} from '@/js-sdk/socket/GlobalWebSocket.js'
*/
app.eventSubs = getWebsocket(
`location-info-event-${app.deviceId}-${app.device.productId}`,
`/dashboard/device/${app.device.productId}/events/realTime`, {
deviceId: app.deviceId,
},
).subscribe(resp => {
const {
payload
} = resp;
}, );
//
app.propertiesMap = {}
if (metadata.propertiesGroup && metadata.propertiesGroup.length > 0) {
//获取时间戳(以秒为单位)
const timeStamp = moment().unix();
metadata.propertiesGroup.map(group => {
let points = []
let properties = {}
// 这里把 properties 存了起来,方法在下面
app.setProperties(group);
group.properties.map(item => {
points.push(item.id)
properties[item.id] = item
})
if (!app.propertiesMap.data) {
app.propertiesMap.data = {}
}
//
app.propertiesMap.data[group.id] = properties
//"$BELT_DAYBELT_SENSOR.....传递的都是这些里面的数据
let str = points.join("-");
// debugger
const mSub = getWebsocket(
`instance-info-property-${app.deviceId}-${app.device.productId}-${group.id}-${str}`,
`/dashboard/device/${app.device.productId}/properties/realTime`, {
deviceId: app.deviceId,
groupId: group.id,
properties: points,
history: 0,
},
).subscribe(resp => {
const {
payload
} = resp;
const property = payload.value.property;
const item = app.propertiesMap.data[group.id][property];
if (item) {
item.next(payload);
}
}, );
app.subsProperties.push(mSub)
})
app.$set(app.propertiesMap, 'timeStamp', timeStamp)
}
},
4. setProperties
setProperties(group) {
group.properties.map(item => {
item.listener = [];
item.onSubscribe = function(callback) {
item.listener.push(callback);
}
item.next = function(data) {
item.listener.forEach(element => {
element(data);
});
}
return item;
})
},
2.方法整体流转分析 – 解除绑定
1. onBackPress
离开当前页面的生命周期钩子
onBackPress(){
console.log('页面返回...')
// 解除绑定
this.remoeSubscribe();
},
2. remoeSubscribe
remoeSubscribe() {
console.log("解绑了")
const app = this
console.log(app.deviceStatus)
console.log(app.eventSubs)
console.log(app.subsProperties)
if (app.deviceStatus) {
app.deviceStatus.unsubscribe();
}
if (app.eventSubs) {
app.eventSubs.unsubscribe();
}
if (app.subsProperties.length > 0) {
app.subsProperties.forEach(function(i, index) {
i.unsubscribe();
});
}
},
三、参数调用分析
这里记录调用过程中参数的输出,之后 再写
四、请求查看
请求成功后,可以看到绑定了(sub)
当点击返回按钮,可以看到解除了绑定(unsub),以及再次进入,重新绑定(sub)
整体代码
代码如下:
<template>
<view>
<!-- 顶部 -->
<view class="top">
<!-- 顶部标题 -->
<div class="info-title">
<div>DCL20221025</div>
<u-button size="mini" type='primary'>在线</u-button>
<div class="showmore-text" @click="toDeviceInfoPage()">查看设备详情</div>
</div>
<!-- 设备信息卡片 -->
<view class="info-card">
<!-- info-text -->
<view class="info-text">所属企业:{{ customerUser }}</view>
<view class="info-text">物料名称 : xx饲料</view>
<view class="info-text">罐体容量 : {{maxCapacity}}吨</view>
<view class="info-text contact">负责人 :未登记
<!-- {{contact}} -->
<u-icon name="phone" color="#60CD9A" size='22px'></u-icon><!-- {{phone}} -->
</view>
</view>
</view>
<!-- 中间分隔 -->
<u-gap :bg-color="$u.color.bgColor" height="45"></u-gap>
<!-- 数据看板 -->
<view class="data-board">
<div class="data-box">
<div class="prop">当前存量</div>
<div class="data">13.02吨</div>
</div>
<div class="block-box">
<div class="inner-box show-line"></div>
<div class="inner-box"></div>
</div>
<div class="data-box">
<div class="prop">满仓率</div>
<div class="data">10.35%</div>
</div>
</view>
<!-- 中间分隔 -->
<u-gap :bg-color="$u.color.bgColor" height="45"></u-gap>
<!-- 运行数据看板 -->
<view class="running-data-board warpper my-border_radius">
<!-- 顶部标题 -->
<div class="board-title my-border_radius">
<div class="title-text">运行数据监控</div>
<div class="showmore-text" @click="toDeviceRunningDataPage()">查看完整数据</div>
</div>
<div class="content">
<div class="device-img">
<img src="../../static/img/device.png" style="height: 100%;width: 115px;">
</div>
<div class="divice-running-data">
<div class="data-box">
<div class="data-item">塔内重量 : 13.02 吨<u-button size="mini" type='error' slot="right"
style="margin-left:30rpx">缺料</u-button>
</div>
<div class="data-item">工作状态 : 运行中<u-icon name="moments" color="#52C08C" size="40"
style="margin-left:30rpx"></u-icon>
</div>
<div class="data-item">倾斜度 : 0.2°<u-icon name="warning" color="#E60707" size="40"
style="margin-left:30rpx"></u-icon>
</div>
<div class="data-item">通讯时间 : 2022-10-25 17:19</div>
</div>
</div>
</div>
<div class="table">
<u-table>
<u-tr>
<u-td v-for="(val,key) in tableData" style="background-color: #f5f6f8">{{key}}</u-td>
</u-tr>
<u-tr>
<u-td v-for="(val,key) in tableData">{{val}}</u-td>
</u-tr>
</u-table>
</div>
</view>
<!-- 中间分隔 -->
<u-gap :bg-color="$u.color.bgColor" height="45"></u-gap>
<!-- 喂养数据看板 -->
<view class="feeding-data-board warpper my-border_radius">
<!-- 顶部标题 -->
<div class="board-title my-border_radius">
<div class="title-text">喂养数据</div>
<div class="showmore-text">查看完整数据</div>
</div>
<!-- 喂养数据表格部分 -->
<div class="feeding-data-content">
<view class="row form-title">
<view class="time">喂养时间</view>
<view class="feed-weight">喂养重量</view>
<view class="staff">操作人</view>
</view>
<view class="form-data">
<u-empty text="数据为空" mode="list" v-if="!hasData"></u-empty>
<view class="row" v-for="item in feedData" v-if="hasData">
<view class="time">{{item.time}}</view>
<view class="feed-weight">{{item.feedWeight}}</view>
<view class="staff">{{item.staff}}</view>
</view>
</view>
</div>
</view>
<span @click="hasData=!hasData" style="background-color: antiquewhite;">临时:点击切换数据是否为空</span>
<!-- 中间分隔 -->
<u-gap :bg-color="$u.color.bgColor" height="45"></u-gap>
<!-- 喂养数据看板 -->
<view class="running-operating warpper my-border_radius">
<!-- 顶部标题 -->
<div class="board-title my-border_radius">
<div class="title-text">设备操作</div>
<!-- <div class="showmore-text">查看完整数据</div> -->
</div>
<div class="operation-content">
<!-- 此处switch的slot为right,如果不填写slot名,也即<u-switch v-model="model.remember"></u-switch>,将会左对齐 -->
<u-form-item label="开始运行" prop="remember" label-width="170" :border-bottom="false"
style="font-size: 36rpx;padding-left: 20rpx;padding-right: 100rpx;height:100rpx; display:flex;">
<u-switch slot="right" v-model="workState" active-color="#7493E9" style="margin-left: 200rpx">
</u-switch>
</u-form-item>
<div class="button-box">
<u-button size="medium" :customStyle="customStyle" shape="circle">--0-- 置零</u-button>
<u-button size="medium" :customStyle="customStyle" shape="circle" @click="toParameterSetting()">
运行参数设置
</u-button>
</div>
</div>
</view>
<!-- 中间分隔 -->
<u-gap :bg-color="$u.color.bgColor" height="45"></u-gap>
</view>
</template>
<script>
import moment from 'moment'
import * as DeviceApi from '@/api/device'
import * as CustomerApi from '@/api/customer/index.js'
import * as SiteApi from '@/api/site/index.js'
import {
getCustomerNameById
} from '@/utils/util.js'
import {
getWebsocket
} from '@/js-sdk/socket/GlobalWebSocket.js'
export default {
components: {
},
watch: {
workState(val) {
// 这里监听设备操作中开始关闭工作的 按钮切换
console.log(val);
}
},
onLoad(option) {
// console.log(option.id)
this.init(option.id);
this.deviceId = option.id
this.device.id = option.id
},
onHide(){
console.log("页面切走了")
},
onBackPress(){
console.log('页面返回...')
// 解除绑定
this.remoeSubscribe();
},
data() {
return {
device: {
id: ''
},
deviceId: '',
deviceStatus: null,
subsProperties: [],
eventSubs: null,
propertiesMap: {},
propertiesRealData: {},
maxCapacity: '',
customerUser: '',
phone: '',
contact: '',
tableData: {
"CH1": 934,
"CH2": 456,
"CH3": 25,
"CH4": 165,
"CH5": 95,
"CH6": 15613,
},
hasData: false,
feedData: [{
time: '2022-05-10 10:02:56',
feedWeight: '30吨',
staff: '刘俊杰1'
},
{
time: '2022-06-10 10:02:56',
feedWeight: '31吨',
staff: '刘俊杰2'
},
{
time: '2022-09-10 10:02:56',
feedWeight: '32吨',
staff: '刘俊杰3'
}
],
workState: false,
// 按钮颜色,需要这样写才可以解决兼容问题
customStyle: {
background: '#7493E9 !important',
color: '#fff !important',
border: 'none !important'
}
}
},
methods: {
init(deviceId) {
this.getDeviceInfo(deviceId);
},
getDeviceInfo(deviceId) {
DeviceApi.getDevice({
id: deviceId
// id:'2208004'
}).then(res => {
console.log(res)
this.maxCapacity = res.data.maxCapacity
// 这里获取企业名称
// this.getCustomerNameById(res.data.customerId)
this.device = res.data || {}
this.initWebSocket();
console.log(this.device);
})
},
// 初始化 WebSocket
initWebSocket() {
const app = this
// 这里接触绑定别忘了 !!!
app.remoeSubscribe();
// debugger
app.subsProperties = [];
//把当前设备数据里面的metadata(单据,称重...)转成json
const metadata = JSON.parse(app.device.metadata);
app.deviceStatus = getWebsocket(
`instance-editor-info-status-${app.deviceId}`,
`/dashboard/device/status/change/realTime`, {
deviceId: app.deviceId,
},
).subscribe(resp => {
const {
payload
} = resp;
app.device.state =
payload.value.type === 'online' ? {
value: 'online',
text: '在线'
} : {
value: 'offline',
text: '离线',
};
if (payload.value.type === 'online') {
app.device.onlineTime = payload.timestamp;
} else {
app.device.offlineTime = payload.timestamp;
}
app.$set(app.device, 'timeStamp', moment().unix())
});
console.log(app.deviceStatus)
app.eventSubs = getWebsocket(
`location-info-event-${app.deviceId}-${app.device.productId}`,
`/dashboard/device/${app.device.productId}/events/realTime`, {
deviceId: app.deviceId,
},
).subscribe(resp => {
const {
payload
} = resp;
}, );
//
app.propertiesMap = {}
if (metadata.propertiesGroup && metadata.propertiesGroup.length > 0) {
//获取时间戳(以秒为单位)
const timeStamp = moment().unix();
metadata.propertiesGroup.map(group => {
let points = []
let properties = {}
app.setProperties(group);
group.properties.map(item => {
points.push(item.id)
properties[item.id] = item
})
if (!app.propertiesMap.data) {
app.propertiesMap.data = {}
}
//
app.propertiesMap.data[group.id] = properties
//"$BELT_DAYBELT_SENSOR.....传递的都是这些里面的数据
let str = points.join("-");
// debugger
const mSub = getWebsocket(
`instance-info-property-${app.deviceId}-${app.device.productId}-${group.id}-${str}`,
`/dashboard/device/${app.device.productId}/properties/realTime`, {
deviceId: app.deviceId,
groupId: group.id,
properties: points,
history: 0,
},
).subscribe(resp => {
const {
payload
} = resp;
const property = payload.value.property;
const item = app.propertiesMap.data[group.id][property];
if (item) {
item.next(payload);
}
}, );
app.subsProperties.push(mSub)
})
app.$set(app.propertiesMap, 'timeStamp', timeStamp)
}
},
setProperties(group) {
group.properties.map(item => {
item.listener = [];
item.onSubscribe = function(callback) {
item.listener.push(callback);
}
item.next = function(data) {
item.listener.forEach(element => {
element(data);
});
}
return item;
})
},
remoeSubscribe() {
console.log("解绑了")
const app = this
console.log(app.deviceStatus)
console.log(app.eventSubs)
console.log(app.subsProperties)
if (app.deviceStatus) {
app.deviceStatus.unsubscribe();
}
if (app.eventSubs) {
app.eventSubs.unsubscribe();
}
if (app.subsProperties.length > 0) {
app.subsProperties.forEach(function(i, index) {
i.unsubscribe();
});
}
},
// async getCustomerNameById(id) {
// const {
// data: res
// } = await CustomerApi.get({
// id: id
// }).catch(err => {})
// this.customerUser = res.name
// this.phone = res.phone
// this.contact = res.contact
// },
// async getSiteNameById(id) {
// const {
// data: res
// } = await SiteApi.get({
// id: id
// }).catch(err => {})
// console.log(res.name)
// console.log(res)
// },
// 跳转到设备详情页
toDeviceInfoPage() {
// this.device.id = 2208004;
console.log(this.device.id)
// console.log('/pages_device/device/detail/device-info&id='+this.device.id);
uni.navigateTo({
url: '/pages_device/device/detail/device-info?id=' + this.device.id
})
},
toParameterSetting() {
uni.navigateTo({
url: '/pages_device/device/setting/parameter-setting?id=' + this.device.id
})
},
toDeviceRunningDataPage() {
console.log()
uni.navigateTo({
url: '/pages_device/device/setting/parameter-setting'
})
},
}
}
</script>
<style lang="scss" scoped>
// 顶部
.top {
width: 100%;
height: 340rpx;
background-color: #fff;
padding: 30rpx 20rpx 20rpx 20rpx;
// 顶部标题
.info-title {
display: flex;
justify-content: space-between;
font-size: 36rpx;
height: 50rpx;
line-height: 50rpx;
margin-bottom: 20rpx;
// 顶部标题 查看设备详情
.showmore-text {
font-size: 30rpx;
color: rgba(16, 16, 16, 0.5);
}
}
// 设备信息卡片
.info-card {
height: 220rpx;
width: 80%;
margin: 0 auto;
color: rgb(153, 153, 153);
// 信息文本
.info-text {
font-size: 36rpx;
margin-top: 8rpx;
}
// 联系人
.contact {
display: flex;
justify-content: space-between;
padding-right: 50rpx;
}
}
}
// 数据看板
.data-board {
width: 100%;
height: 220rpx;
background-color: #fff;
border: #BBBBBB 1rpx solid;
display: flex;
justify-content: space-around;
// 左右两侧的数据盒子
.data-box {
width: 40%;
padding: 20rpx 30rpx;
// 标题
.prop {
height: 50%;
background-color: #fff;
font-size: 44rpx;
line-height: 86rpx;
text-align: center;
color: rgba(16, 16, 16, 0.5);
}
// 数据值
.data {
height: 50%;
font-size: 66rpx;
line-height: 86rpx;
text-align: center;
color: rgb(82, 192, 140);
}
}
// 中间为了显示淑竖线了两个盒子
.block-box {
height: 220rpx;
width: 10%;
display: flex;
justify-content: center;
align-items: center;
.inner-box {
height: 60%;
width: 50%;
}
// 竖线的样式
.show-line {
border-right: 1px solid #BBBBBB
}
}
}
// 上面两个圆角的样式
.my-border_radius {
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
.warpper {
width: 95%;
background-color: #fff;
border: #BBBBBB 1rpx solid;
margin: 0 auto;
}
.running-data-board {
height: 670rpx;
position: relative;
.content {
width: 100%;
height: 450rpx;
// background-color: red;
display: flex;
flex-direction: row;
.device-img {
// width: 100%;
height: 100%;
// background-color: red;
// background: url('') fixed center;
flex: 3;
text-align: center;
}
.divice-running-data {
height: 100%;
// background-color: #666;
flex: 5;
display: flex;
justify-content: center;
align-items: center;
padding-right: 10rpx;
.data-box {
width: 100%;
.data-item {
width: 98%;
height: 70rpx;
font-size: 32rpx;
padding-left: 10rpx;
line-height: 70rpx;
border-left: 1px solid #DBDBDB;
border-bottom: 1px solid #DBDBDB;
border-right: 1px solid #DBDBDB;
}
.data-item:first-child {
border-top: 1px solid #DBDBDB;
}
}
}
}
.table {
width: 100%;
position: absolute;
bottom: 0;
}
}
.feeding-data-board {
height: 400rpx;
position: relative;
.feeding-data-content {
height: 320rpx;
width: 100%;
position: absolute;
bottom: 0;
.feedbox {
width: 90%;
margin-left: 40rpx;
}
.form-data {
width: 100%;
height: 120px;
background-color: #fff;
display: flex;
flex-direction: column
}
.row {
width: 100%;
height: 80rpx;
background-color: #f4f4f4;
text-align: center;
line-height: 80rpx;
}
.time {
height: 80rpx;
background-color: #fff;
flex: 4;
border: 1rpx solid #f4f4f4;
}
.feed-weight {
height: 80rpx;
background-color: #fff;
flex: 2;
border: 1rpx solid #f4f4f4;
}
.staff {
height: 80rpx;
background-color: #fff;
flex: 2;
border: 1rpx solid #f4f4f4;
}
}
}
.running-operating {
height: 300rpx;
border-radius: 16rpx;
.operation-content {
padding-left: 40rpx;
padding-right: 40rpx;
font-size: 52rpx;
}
.button-box {
display: flex;
justify-content: space-between;
}
}
// 每一个卡片的标题样式
.board-title {
display: flex;
// flex: ;
justify-content: space-between;
height: 80rpx;
background-color: rgba(63, 106, 225, 0.72);
font-size: 36rpx;
color: #fff;
font-weight: 500;
line-height: 80rpx;
padding: 0 16rpx;
.showmore-text {
font-size: 30rpx;
}
}
</style>