Compare commits
2 Commits
b15f58e90e
...
f5b9b4687f
| Author | SHA1 | Date |
|---|---|---|
|
|
f5b9b4687f | |
|
|
04e19f22df |
|
|
@ -0,0 +1,12 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询DTU温湿度上报数据列表
|
||||||
|
export function findDtuDataByInfo(query) {
|
||||||
|
return request({
|
||||||
|
url: '/system/data/findDtuDataByInfo',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<view class="container" :style="{padding: linePadding}">
|
||||||
|
<!-- 带文字的分割线 -->
|
||||||
|
<view class="divider" :style="{margin: lineMargin}">
|
||||||
|
<view class="line" :style="{height: lineHeight,backgroundColor: lineColor}"></view>
|
||||||
|
<text :style="{padding: textPadding,fontSize: fontSize,color: fontColor,fontWeight: fontWeight}">{{ lineText }}</text>
|
||||||
|
<view class="line" :style="{height: lineHeight,backgroundColor: lineColor,}"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "DividerText",
|
||||||
|
props: {
|
||||||
|
|
||||||
|
// 线边距
|
||||||
|
linePadding: {
|
||||||
|
type: String,
|
||||||
|
default: '10rpx 0'
|
||||||
|
},
|
||||||
|
//线距离上面的距离
|
||||||
|
lineMargin: {
|
||||||
|
type: String,
|
||||||
|
default: '30rpx 0'
|
||||||
|
},
|
||||||
|
// 分割线粗细
|
||||||
|
lineHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '1rpx'
|
||||||
|
},
|
||||||
|
// 分割线颜色
|
||||||
|
lineColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#e5e5e5'
|
||||||
|
},
|
||||||
|
// 字体距离分割线中间的距离
|
||||||
|
textPadding: {
|
||||||
|
type: String,
|
||||||
|
default: '0 20rpx'
|
||||||
|
},
|
||||||
|
// 字体大小
|
||||||
|
fontSize: {
|
||||||
|
type: String,
|
||||||
|
default: '28rpx'
|
||||||
|
},
|
||||||
|
// 字体颜色
|
||||||
|
fontColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#999'
|
||||||
|
},
|
||||||
|
// 字体粗细
|
||||||
|
fontWeight: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
|
},
|
||||||
|
// 分割线文字
|
||||||
|
lineText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<view class="divider" :style="{ margin: margin }">
|
||||||
|
<view
|
||||||
|
:style="{
|
||||||
|
backgroundColor: color,
|
||||||
|
height: thickness + 'rpx',
|
||||||
|
width: hasMargin ? 'calc(100% - ' + sideMargin + 'rpx)' : '100%'
|
||||||
|
}"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Divider",
|
||||||
|
props: {
|
||||||
|
// 分割线颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#e5e5e5"
|
||||||
|
},
|
||||||
|
// 分割线厚度
|
||||||
|
thickness: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
// 上下外边距(如:"20rpx 0")
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: "20rpx 0"
|
||||||
|
},
|
||||||
|
// 是否有左右边距
|
||||||
|
hasMargin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 左右边距大小
|
||||||
|
sideMargin: {
|
||||||
|
type: Number,
|
||||||
|
default: 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// 应用全局配置
|
// 应用全局配置
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
|
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
|
||||||
// baseUrl: 'http://localhost:8088',
|
baseUrl: 'http://172.14.24.109:8088',
|
||||||
baseUrl: 'http://1.94.254.176:8088',
|
// baseUrl: 'http://1.94.254.176:8088',
|
||||||
// 应用信息
|
// 应用信息
|
||||||
appInfo: {
|
appInfo: {
|
||||||
// 应用名称
|
// 应用名称
|
||||||
|
|
|
||||||
2
main.js
2
main.js
|
|
@ -5,6 +5,8 @@ import store from './store' // store
|
||||||
import plugins from './plugins' // plugins
|
import plugins from './plugins' // plugins
|
||||||
import './permission' // permission
|
import './permission' // permission
|
||||||
import { getDicts } from "@/api/system/dict/data"
|
import { getDicts } from "@/api/system/dict/data"
|
||||||
|
import "./utils/uni.css";
|
||||||
|
|
||||||
|
|
||||||
Vue.use(plugins)
|
Vue.use(plugins)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,289 +1,64 @@
|
||||||
<!--
|
|
||||||
<template>
|
|
||||||
<view class="container">
|
|
||||||
<view class="header">
|
|
||||||
<text class="title">控制中心</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<uni-section title="设备【864536071808560】" titleFontSize="16px" type="line" >
|
|
||||||
<uni-card :is-shadow="true">
|
|
||||||
<uni-list :border="false">
|
|
||||||
<uni-list-item title="卷被开" direction="row" note="暂停" :show-switch="true" @switchChange="switchChange">
|
|
||||||
</uni-list-item>
|
|
||||||
</uni-list>
|
|
||||||
</uni-card>
|
|
||||||
|
|
||||||
<uni-card :border="false" padding="20rpx" @click="handleCardClick('jbk')">
|
|
||||||
<template v-slot:content>
|
|
||||||
<view class="card-content">
|
|
||||||
<view class="card-text">
|
|
||||||
<text class="card-main">卷被开</text>
|
|
||||||
<text class="card-sub">{{ show.jbk }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="card-icon" :class="{ active: status.jbk === 1 }">
|
|
||||||
<uni-icons :type=" (status.jbk === 1)?'circle':'circle-filled'" size="24" color="#fff" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</uni-card>
|
|
||||||
</uni-section>
|
|
||||||
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mqtt from 'mqtt'
|
|
||||||
import UniList from "../../uni_modules/uni-list/components/uni-list/uni-list.vue";
|
|
||||||
import UniListItem from "../../uni_modules/uni-list/components/uni-list-item/uni-list-item.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {UniListItem, UniList},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mqttConfig: {
|
|
||||||
host: '1.94.254.176',
|
|
||||||
port: 9001,
|
|
||||||
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
|
|
||||||
username: 'admin',
|
|
||||||
password: 'Admin#12345678',
|
|
||||||
subscribeTopic: 'test/topic'
|
|
||||||
},
|
|
||||||
plain:true,
|
|
||||||
publishTopic: 'test/topic',
|
|
||||||
message: '',
|
|
||||||
client: null,
|
|
||||||
connected: false,
|
|
||||||
messages: [],
|
|
||||||
border:true,
|
|
||||||
cover: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg',
|
|
||||||
avatar: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
|
|
||||||
extraIcon:{
|
|
||||||
color: '#4cd964',
|
|
||||||
size: '22',
|
|
||||||
type: 'gear-filled'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick(e){
|
|
||||||
console.log(e)
|
|
||||||
},
|
|
||||||
actionsClick(text){
|
|
||||||
uni.showToast({
|
|
||||||
title:text,
|
|
||||||
icon:'none'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
connectMqtt() {
|
|
||||||
const options = {
|
|
||||||
clientId: this.mqttConfig.clientId,
|
|
||||||
username: this.mqttConfig.username,
|
|
||||||
password: this.mqttConfig.password,
|
|
||||||
clean: true,
|
|
||||||
connectTimeout: 4000,
|
|
||||||
reconnectPeriod: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `ws://${this.mqttConfig.host}:${this.mqttConfig.port}/mqtt`
|
|
||||||
|
|
||||||
this.client = mqtt.connect(url, options)
|
|
||||||
|
|
||||||
this.client.on('connect', () => {
|
|
||||||
this.connected = true
|
|
||||||
this.addMessage('已连接到MQTT服务器')
|
|
||||||
this.client.subscribe(this.mqttConfig.subscribeTopic, (err) => {
|
|
||||||
if (!err) {
|
|
||||||
this.addMessage(`已订阅主题: ${this.mqttConfig.subscribeTopic}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('message', (topic, payload) => {
|
|
||||||
this.addMessage(`收到消息 [${topic}]: ${payload.toString()}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('error', (err) => {
|
|
||||||
this.addMessage(`连接错误: ${err.message}`)
|
|
||||||
this.connected = false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('reconnect', () => {
|
|
||||||
this.addMessage('正在重新连接...')
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client.on('close', () => {
|
|
||||||
this.addMessage('连接已关闭')
|
|
||||||
this.connected = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnectMqtt() {
|
|
||||||
if (this.client) {
|
|
||||||
this.client.end()
|
|
||||||
this.connected = false
|
|
||||||
this.addMessage('已断开MQTT连接')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
publishMessage() {
|
|
||||||
if (!this.connected) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请先连接MQTT服务器',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.publishTopic || !this.message) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请填写主题和消息内容',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client.publish(this.publishTopic, this.message, (err) => {
|
|
||||||
if (!err) {
|
|
||||||
this.addMessage(`已发布消息到 [${this.publishTopic}]: ${this.message}`)
|
|
||||||
this.message = ''
|
|
||||||
} else {
|
|
||||||
this.addMessage(`发布失败: ${err.message}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
addMessage(content) {
|
|
||||||
this.messages.unshift({
|
|
||||||
time: new Date().toLocaleTimeString(),
|
|
||||||
content: content
|
|
||||||
})
|
|
||||||
},
|
|
||||||
switchChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.client) {
|
|
||||||
this.client.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
$uni-success: #18bc37 !default;
|
|
||||||
.container {
|
|
||||||
padding: 20rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40rpx 0;
|
|
||||||
background-color: #007AFF;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-section {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.button-group {
|
|
||||||
margin-top: 15px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
.example-body {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: block;
|
|
||||||
/* #endif */
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.decoration {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
margin-right: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: $uni-success;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-cover {
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-content {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 40px;
|
|
||||||
background-color: rgba($color: #000000, $alpha: 0.4);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
height: 45px;
|
|
||||||
border-top: 1px #eee solid;
|
|
||||||
}
|
|
||||||
.card-actions-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.card-actions-item-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
.cover-image {
|
|
||||||
flex: 1;
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
.no-border {
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
</style>-->
|
|
||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<!-- 控制设置标题 -->
|
<!-- 控制设置标题 -->
|
||||||
<view class="control-title">控制设置</view>
|
<view class="control-title">控制设置</view>
|
||||||
<uni-section title="请选择大棚:" titleFontSize="16px" type="line">
|
<uni-section title="请选择大棚:" titleFontSize="18px" type="line">
|
||||||
<view class="uni-px-5 uni-pb-5">
|
<view class="uni-px-5 uni-pb-5">
|
||||||
<uni-data-select v-model="value" :localdata="range" @change="change"></uni-data-select>
|
<uni-data-select v-model="value" :localdata="range" @change="change"></uni-data-select>
|
||||||
</view>
|
</view>
|
||||||
</uni-section>
|
</uni-section>
|
||||||
|
|
||||||
<uni-section :title="title" titleFontSize="16px" type="line" v-if="value!== 1">
|
|
||||||
<template v-slot:right>
|
<uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1">
|
||||||
|
<template v-slot:right >
|
||||||
{{ temp }}
|
{{ temp }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<view class="uni-flex_control uni-row" >
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.temp1 }}</text>
|
||||||
|
<text class="data">温度1</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.temp2 }}</text>
|
||||||
|
<text class="data">温度2</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.temp3 }}</text>
|
||||||
|
<text class="data">温度3</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.temp4 }}</text>
|
||||||
|
<text class="data">温度4</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="uni-flex_control uni-row" >
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.humi1 }}</text>
|
||||||
|
<text class="data">湿度1</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.humi2 }}</text>
|
||||||
|
<text class="data">湿度2</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.humi3 }}</text>
|
||||||
|
<text class="data">湿度3</text>
|
||||||
|
</view>
|
||||||
|
<view class="text uni-flex_control uni-view">
|
||||||
|
<text class="data" :style="fontStyle">{{ liveData.humi4 }}</text>
|
||||||
|
<text class="data">湿度4</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
</uni-section>
|
||||||
|
|
||||||
|
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1">
|
||||||
|
|
||||||
<!-- 卷膜/卷被卡片容器(2列栅格布局) -->
|
<!-- 卷膜/卷被卡片容器(2列栅格布局) -->
|
||||||
<view class="card-grid">
|
<view class="card-grid">
|
||||||
<!-- 卷被开卡片 -->
|
<!-- 卷被开卡片 -->
|
||||||
|
|
@ -379,8 +154,15 @@ $uni-success: #18bc37 !default;
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import mqtt from 'mqtt'
|
import mqtt from 'mqtt'
|
||||||
|
import UniDatetimePicker
|
||||||
|
from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
|
||||||
|
import { findDtuDataByInfo } from "@/api/system/data";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
dicts: ['sys_data_map'],
|
||||||
|
components: {
|
||||||
|
UniDatetimePicker
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
temp: "",
|
temp: "",
|
||||||
|
|
@ -408,6 +190,16 @@ export default {
|
||||||
title:'',
|
title:'',
|
||||||
message: {},
|
message: {},
|
||||||
connected:false,
|
connected:false,
|
||||||
|
liveData: {
|
||||||
|
temp1: '数据加载中...',
|
||||||
|
temp2: '数据加载中...',
|
||||||
|
temp3: '数据加载中...',
|
||||||
|
temp4: '数据加载中...',
|
||||||
|
humi1: '数据加载中...',
|
||||||
|
humi2: '数据加载中...',
|
||||||
|
humi3: '数据加载中...',
|
||||||
|
humi4: '数据加载中...'
|
||||||
|
},
|
||||||
// 卡片状态(模拟后端返回数据)
|
// 卡片状态(模拟后端返回数据)
|
||||||
show: {
|
show: {
|
||||||
jbk: "暂停",
|
jbk: "暂停",
|
||||||
|
|
@ -429,7 +221,8 @@ export default {
|
||||||
jm2g: 0,
|
jm2g: 0,
|
||||||
jm3k: 0,
|
jm3k: 0,
|
||||||
jm3g: 0
|
jm3g: 0
|
||||||
}
|
},
|
||||||
|
fontStyle: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
|
|
@ -475,59 +268,87 @@ export default {
|
||||||
if (selectedItem) {
|
if (selectedItem) {
|
||||||
this.selectedText = selectedItem.text; // 获取展示文本
|
this.selectedText = selectedItem.text; // 获取展示文本
|
||||||
this.title= this.selectedText;
|
this.title= this.selectedText;
|
||||||
|
var queryParams = {
|
||||||
|
imei: this.imei
|
||||||
|
}
|
||||||
|
findDtuDataByInfo(queryParams).then(response => {
|
||||||
|
this.liveData = {
|
||||||
|
temp1: response.data.temp1 || '已离线...',
|
||||||
|
temp2: response.data.temp2 || '已离线..',
|
||||||
|
temp3: response.data.temp3 || '已离线..',
|
||||||
|
temp4: response.data.temp4 || '已离线..',
|
||||||
|
humi1: response.data.humi1 || '已离线..',
|
||||||
|
humi2: response.data.humi2 || '已离线..',
|
||||||
|
humi3: response.data.humi3 || '已离线..',
|
||||||
|
humi4: response.data.humi4 || '已离线..'
|
||||||
|
}
|
||||||
|
this.temp = "最后更新时间:"+response.data.time;
|
||||||
|
this.fontStyle = 'font-size:16px'
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.selectedText = ''; // 无匹配项时清空
|
this.selectedText = ''; // 无匹配项时清空
|
||||||
this.title='';
|
this.title='';
|
||||||
this.value=1;
|
this.value=1;
|
||||||
}
|
}
|
||||||
this.reset();
|
this.reset();
|
||||||
|
this.style="";
|
||||||
// this.disconnectMqtt();
|
// this.disconnectMqtt();
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.show= {
|
this.show = {
|
||||||
jbk: "暂停",
|
jbk: "暂停",
|
||||||
jbg: "暂停",
|
jbg: "暂停",
|
||||||
jm1k: "暂停",
|
jm1k: "暂停",
|
||||||
jm1g: "暂停",
|
jm1g: "暂停",
|
||||||
jm2k: "暂停",
|
jm2k: "暂停",
|
||||||
jm2g: "暂停",
|
jm2g: "暂停",
|
||||||
jm3k: "暂停",
|
jm3k: "暂停",
|
||||||
jm3g: "暂停"
|
jm3g: "暂停"
|
||||||
}
|
}
|
||||||
this.deviceType= '';
|
this.deviceType = '';
|
||||||
this.status= {
|
this.status = {
|
||||||
jbk: 0,
|
jbk: 0,
|
||||||
jbg: 0,
|
jbg: 0,
|
||||||
jm1k: 0,
|
jm1k: 0,
|
||||||
jm1g: 0,
|
jm1g: 0,
|
||||||
jm2k: 0,
|
jm2k: 0,
|
||||||
jm2g: 0,
|
jm2g: 0,
|
||||||
jm3k: 0,
|
jm3k: 0,
|
||||||
jm3g: 0
|
jm3g: 0
|
||||||
};
|
};
|
||||||
this.message={};
|
this.message = {};
|
||||||
this.temp='';
|
this.temp = '';
|
||||||
|
this.liveData = {
|
||||||
|
temp1: '数据加载中...',
|
||||||
|
temp2: '数据加载中...',
|
||||||
|
temp3: '数据加载中...',
|
||||||
|
temp4: '数据加载中...',
|
||||||
|
humi1: '数据加载中...',
|
||||||
|
humi2: '数据加载中...',
|
||||||
|
humi3: '数据加载中...',
|
||||||
|
humi4: '数据加载中...'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 卡片点击事件(实际项目中调用接口修改状态) 功能标识
|
// 卡片点击事件(实际项目中调用接口修改状态) 功能标识
|
||||||
handleCardClick(status, type) {
|
handleCardClick(status, type) {
|
||||||
// 校验
|
// 校验
|
||||||
// 定义类型与提示文案的映射关系,减少重复代码
|
// 定义类型与提示文案的映射关系,减少重复代码
|
||||||
const tipMap = {
|
const tipMap = {
|
||||||
'jbk': { opposite: 'jbg', name: '卷被关', op: '卷被开' },
|
'jbk': {opposite: 'jbg', name: '卷被关', op: '卷被开'},
|
||||||
'jbg': { opposite: 'jbk', name: '卷被开', op: '卷被关' },
|
'jbg': {opposite: 'jbk', name: '卷被开', op: '卷被关'},
|
||||||
'jm1k': { opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开' },
|
'jm1k': {opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开'},
|
||||||
'jm1g': { opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关' },
|
'jm1g': {opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关'},
|
||||||
'jm2k': { opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开' },
|
'jm2k': {opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开'},
|
||||||
'jm2g': { opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关' },
|
'jm2g': {opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关'},
|
||||||
'jm3k': { opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开' },
|
'jm3k': {opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开'},
|
||||||
'jm3g': { opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关' }
|
'jm3g': {opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关'}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 先判断类型是否在映射表中,避免无效case
|
// 先判断类型是否在映射表中,避免无效case
|
||||||
if (!tipMap[type]) return;
|
if (!tipMap[type]) return;
|
||||||
|
|
||||||
const { opposite, name, op } = tipMap[type];
|
const {opposite, name, op} = tipMap[type];
|
||||||
// 核心校验逻辑(只写一次,无需重复)
|
// 核心校验逻辑(只写一次,无需重复)
|
||||||
if (status === 1 && this.status[opposite] === 1) {
|
if (status === 1 && this.status[opposite] === 1) {
|
||||||
this.$modal.msgError(`【${this.selectedText}】${name}在运行状态,不能运行${op}操作!`);
|
this.$modal.msgError(`【${this.selectedText}】${name}在运行状态,不能运行${op}操作!`);
|
||||||
|
|
@ -539,17 +360,17 @@ export default {
|
||||||
// 链接mqtt
|
// 链接mqtt
|
||||||
this.connectMqtt()
|
this.connectMqtt()
|
||||||
}
|
}
|
||||||
if (this.value===1) {
|
if (this.value === 1) {
|
||||||
this.$modal.msgError("设备控制失败!");
|
this.$modal.msgError("设备控制失败!");
|
||||||
console.info("大棚选取失败!")
|
console.info("大棚选取失败!")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '操作提示:',
|
title: '操作提示:',
|
||||||
content: '确定'+(status===1?"运行":"暂停")+'【'+this.selectedText+'】设备?',
|
content: '确定' + (status === 1 ? "运行" : "暂停") + '【' + this.selectedText + '】设备?',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
confirmText: '确定',
|
confirmText: '确定',
|
||||||
success: (res) =>{
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
// console.info("操作功能:【"+type+"】,变更状态为:"+ status)
|
// console.info("操作功能:【"+type+"】,变更状态为:"+ status)
|
||||||
|
|
||||||
|
|
@ -559,7 +380,7 @@ export default {
|
||||||
// 控制设备
|
// 控制设备
|
||||||
this.publishMessage();
|
this.publishMessage();
|
||||||
// 设备回执
|
// 设备回执
|
||||||
this.deviceType=type;
|
this.deviceType = type;
|
||||||
//todo
|
//todo
|
||||||
|
|
||||||
// this.status[type] = this.status[type] === 0 ? 1 : 0;
|
// this.status[type] = this.status[type] === 0 ? 1 : 0;
|
||||||
|
|
@ -586,11 +407,11 @@ export default {
|
||||||
|
|
||||||
this.client.on('connect', () => {
|
this.client.on('connect', () => {
|
||||||
this.connected = true
|
this.connected = true
|
||||||
this.client.subscribe('dtu/+/up', { qos: 0 })
|
this.client.subscribe('dtu/+/up', {qos: 0})
|
||||||
this.addMessage('已连接到MQTT服务器')
|
this.addMessage('已连接到MQTT服务器')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.client.on("message",this.ackMessage);
|
this.client.on("message", this.ackMessage);
|
||||||
|
|
||||||
this.client.on('error', (err) => {
|
this.client.on('error', (err) => {
|
||||||
this.addMessage(`控制失败: ${err.message}`)
|
this.addMessage(`控制失败: ${err.message}`)
|
||||||
|
|
@ -640,35 +461,35 @@ export default {
|
||||||
ackMessage(topic, payload) {
|
ackMessage(topic, payload) {
|
||||||
|
|
||||||
|
|
||||||
// console.info("消息解析:"+topic,payload)
|
// console.info("消息解析:"+topic,payload)
|
||||||
// 1. 先判断是否是目标订阅主题(如dtu/xxx/up)
|
// 1. 先判断是否是目标订阅主题(如dtu/xxx/up)
|
||||||
if (topic !== this.mqttConfig.subscribeTopic) return;
|
if (topic !== this.mqttConfig.subscribeTopic) return;
|
||||||
|
|
||||||
// 2. 解析消息体(注意异常捕获)
|
// 2. 解析消息体(注意异常捕获)
|
||||||
let msgData = {};
|
let msgData = {};
|
||||||
try {
|
try {
|
||||||
msgData = JSON.parse(payload);
|
msgData = JSON.parse(payload);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("消息解析失败:", e);
|
console.error("消息解析失败:", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.info("回执消息:"+msgData)
|
// console.info("回执消息:"+msgData)
|
||||||
// 3. 区分“回执”和“其他内容”
|
// 3. 区分“回执”和“其他内容”
|
||||||
if (msgData.prop && "suc" in msgData) {
|
if (msgData.prop && "suc" in msgData) {
|
||||||
// console.info("处理回执消息")
|
// console.info("处理回执消息")
|
||||||
// 👉 这是“指令回执”
|
// 👉 这是“指令回执”
|
||||||
this.handleCommandAck(msgData,this.deviceType);
|
this.handleCommandAck(msgData, this.deviceType);
|
||||||
} else {
|
} else {
|
||||||
this.handleOtherContent(msgData)
|
this.handleOtherContent(msgData,payload)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addMessage(content) {
|
addMessage(content) {
|
||||||
|
|
||||||
console.info("提示消息:"+content)
|
console.info("提示消息:" + content)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理指令回执的函数
|
// 处理指令回执的函数
|
||||||
handleCommandAck(ackData,type) {
|
handleCommandAck(ackData, type) {
|
||||||
// console.info("回执消息11111:"+ackData)
|
// console.info("回执消息11111:"+ackData)
|
||||||
// 拿到指令字段(如jm2k)和执行状态(suc)
|
// 拿到指令字段(如jm2k)和执行状态(suc)
|
||||||
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
|
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
|
||||||
|
|
@ -679,7 +500,7 @@ export default {
|
||||||
this.status[type] = this.status[type] === 0 ? 1 : 0;
|
this.status[type] = this.status[type] === 0 ? 1 : 0;
|
||||||
this.show[type] = this.status[type] === 0 ? "运行" : "暂停";
|
this.show[type] = this.status[type] === 0 ? "运行" : "暂停";
|
||||||
}
|
}
|
||||||
this.deviceType='';
|
this.deviceType = '';
|
||||||
this.$modal.msgSuccess("设备操作成功!")
|
this.$modal.msgSuccess("设备操作成功!")
|
||||||
|
|
||||||
// 业务逻辑:提示“指令执行成功/失败”
|
// 业务逻辑:提示“指令执行成功/失败”
|
||||||
|
|
@ -688,42 +509,64 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理其他内容的函数
|
// 处理其他内容的函数
|
||||||
handleOtherContent(msgData) {
|
handleOtherContent(msgData,payload) {
|
||||||
// 业务逻辑:处理传感器数据、设备状态等
|
// 业务逻辑:处理传感器数据、设备状态等
|
||||||
// console.log("收到其他内容:", msgData);
|
// console.log("收到其他内容:", msgData);
|
||||||
// 例如:更新温湿度显示、设备在线状态等
|
// 例如:更新温湿度显示、设备在线状态等
|
||||||
|
|
||||||
// 设备状态展示
|
// 设备状态展示
|
||||||
if (this.value!==1) {
|
if (this.value !== 1) {
|
||||||
var arr=['jbk',"jbg","jm1k","jm1g","jm2k","jm2g","jm3k","jm3g"]
|
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"]
|
||||||
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
|
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
|
||||||
if (allKeysNumeric) {
|
if (allKeysNumeric) {
|
||||||
// console.info(msgData)
|
// console.info(msgData)
|
||||||
this.status={...msgData}
|
this.status = {...msgData}
|
||||||
// console.info("imei: "+this.publishTopic+"copy: ",this.status)
|
// console.info("imei: "+this.publishTopic+"copy: ",this.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 实时温度展示
|
|
||||||
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
||||||
if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
|
if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
|
||||||
var avgResult = this.calculateAvg(msgData).averageFixed;
|
|
||||||
this.temp = "当前棚内平均温度:"+avgResult+"℃"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 你的键:温度101~104、湿度201~204(按你之前约定温度/10、湿度/10)
|
||||||
|
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10)
|
||||||
|
|
||||||
|
this.liveData = {
|
||||||
|
temp1: msgData["201"]==null ? "已离线...":div10(msgData["201"])+"℃",
|
||||||
|
humi1: msgData["101"]==null ? "已离线...":div10(msgData["101"])+"%RH",
|
||||||
|
temp2: msgData["202"]==null ? "已离线...":div10(msgData["202"])+"℃",
|
||||||
|
humi2: msgData["102"]==null ? "已离线...":div10(msgData["102"])+"%RH",
|
||||||
|
temp3: msgData["203"]==null ? "已离线...":div10(msgData["203"])+"℃",
|
||||||
|
humi3: msgData["103"]==null ? "已离线...":div10(msgData["103"])+"%RH",
|
||||||
|
temp4: msgData["204"]==null ? "已离线...":div10(msgData["204"])+"℃",
|
||||||
|
humi4: msgData["104"]==null ? "已离线...":div10(msgData["104"])+"%RH"
|
||||||
|
}
|
||||||
|
// 调用函数获取并输出格式化后的当前时间
|
||||||
|
this.temp = "最新更新时间:" + this.getCurrentTime();
|
||||||
|
this.fontStyle = 'font-size:16px'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取格式化后的当前时间
|
||||||
|
* @returns {string} 格式为 YYYY-MM-DD HH:mm:ss 的当前时间
|
||||||
|
*/
|
||||||
|
getCurrentTime() {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// 获取年、月、日(补零确保两位数)
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
// 获取时、分、秒(补零确保两位数)
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
// 拼接成标准格式
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
},
|
},
|
||||||
calculateAvg(dataObj) {
|
|
||||||
const targetKeys = ['201', '202', '203', '204'];
|
|
||||||
const values = targetKeys.map(key => dataObj[key]).filter(val => typeof val === 'number' && !isNaN(val));
|
|
||||||
if (values.length === 0) return { average: 0, averageFixed: '0.00' };
|
|
||||||
const sum = values.reduce((total, num) => total + num, 0);
|
|
||||||
const avg = sum/10 / values.length;
|
|
||||||
return {
|
|
||||||
average: avg,
|
|
||||||
averageFixed: avg.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onHide() {
|
onHide() {
|
||||||
this.disconnectMqtt();
|
this.disconnectMqtt();
|
||||||
|
|
@ -813,4 +656,45 @@ export default {
|
||||||
.uni-pb-5 {
|
.uni-pb-5 {
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
width: 50rpx;
|
||||||
|
margin: 10rpx 10rpx 8rpx 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 70rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
text-align: center;
|
||||||
|
background:#fff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx #bfbec1
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:first-child {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-view {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
height: 150rpx;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 23px;
|
||||||
|
color: #3a3a3a;
|
||||||
|
}
|
||||||
|
.data:nth-child(even) {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #9c9c9c;
|
||||||
|
}
|
||||||
|
/deep/ .uni-section-header__slot-right {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
const state = {
|
||||||
|
dict: new Array()
|
||||||
|
}
|
||||||
|
const mutations = {
|
||||||
|
SET_DICT: (state, { key, value }) => {
|
||||||
|
if (key !== null && key !== "") {
|
||||||
|
state.dict.push({
|
||||||
|
key: key,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
REMOVE_DICT: (state, key) => {
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < state.dict.length; i++) {
|
||||||
|
if (state.dict[i].key == key) {
|
||||||
|
state.dict.splice(i, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CLEAN_DICT: (state) => {
|
||||||
|
state.dict = new Array()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
// 设置字典
|
||||||
|
setDict({ commit }, data) {
|
||||||
|
commit('SET_DICT', data)
|
||||||
|
},
|
||||||
|
// 删除字典
|
||||||
|
removeDict({ commit }, key) {
|
||||||
|
commit('REMOVE_DICT', key)
|
||||||
|
},
|
||||||
|
// 清空字典
|
||||||
|
cleanDict({ commit }) {
|
||||||
|
commit('CLEAN_DICT')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
/**
|
||||||
|
* 通用js方法封装处理
|
||||||
|
* Copyright (c) 2019 agri
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 日期格式化
|
||||||
|
export function parseTime(time, pattern) {
|
||||||
|
if (arguments.length === 0 || !time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||||
|
time = parseInt(time)
|
||||||
|
} else if (typeof time === 'string') {
|
||||||
|
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
|
||||||
|
}
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||||
|
let value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
|
||||||
|
if (result.length > 0 && value < 10) {
|
||||||
|
value = '0' + value
|
||||||
|
}
|
||||||
|
return value || 0
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单重置
|
||||||
|
export function resetForm(refName) {
|
||||||
|
if (this.$refs[refName]) {
|
||||||
|
this.$refs[refName].resetFields()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加日期范围
|
||||||
|
export function addDateRange(params, dateRange, propName) {
|
||||||
|
let search = params
|
||||||
|
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
|
||||||
|
dateRange = Array.isArray(dateRange) ? dateRange : []
|
||||||
|
if (typeof (propName) === 'undefined') {
|
||||||
|
search.params['beginTime'] = dateRange[0]
|
||||||
|
search.params['endTime'] = dateRange[1]
|
||||||
|
} else {
|
||||||
|
search.params['begin' + propName] = dateRange[0]
|
||||||
|
search.params['end' + propName] = dateRange[1]
|
||||||
|
}
|
||||||
|
return search
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显数据字典
|
||||||
|
export function selectDictLabel(datas, value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var actions = []
|
||||||
|
Object.keys(datas).some((key) => {
|
||||||
|
if (datas[key].value == ('' + value)) {
|
||||||
|
actions.push(datas[key].label)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (actions.length === 0) {
|
||||||
|
actions.push(value)
|
||||||
|
}
|
||||||
|
return actions.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显数据字典(字符串、数组)
|
||||||
|
export function selectDictLabels(datas, value, separator) {
|
||||||
|
if (value === undefined || value.length ===0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value.join(",")
|
||||||
|
}
|
||||||
|
var actions = []
|
||||||
|
var currentSeparator = undefined === separator ? "," : separator
|
||||||
|
var temp = value.split(currentSeparator)
|
||||||
|
Object.keys(value.split(currentSeparator)).some((val) => {
|
||||||
|
var match = false
|
||||||
|
Object.keys(datas).some((key) => {
|
||||||
|
if (datas[key].value == ('' + temp[val])) {
|
||||||
|
actions.push(datas[key].label + currentSeparator)
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!match) {
|
||||||
|
actions.push(temp[val] + currentSeparator)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return actions.join('').substring(0, actions.join('').length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串格式化(%s )
|
||||||
|
export function sprintf(str) {
|
||||||
|
var args = arguments, flag = true, i = 1
|
||||||
|
str = str.replace(/%s/g, function () {
|
||||||
|
var arg = args[i++]
|
||||||
|
if (typeof arg === 'undefined') {
|
||||||
|
flag = false
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return arg
|
||||||
|
})
|
||||||
|
return flag ? str : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换字符串,undefined,null等转化为""
|
||||||
|
export function parseStrEmpty(str) {
|
||||||
|
if (!str || str == "undefined" || str == "null") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据合并
|
||||||
|
export function mergeRecursive(source, target) {
|
||||||
|
for (var p in target) {
|
||||||
|
try {
|
||||||
|
if (target[p].constructor == Object) {
|
||||||
|
source[p] = mergeRecursive(source[p], target[p])
|
||||||
|
} else {
|
||||||
|
source[p] = target[p]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
source[p] = target[p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造树型结构数据
|
||||||
|
* @param {*} data 数据源
|
||||||
|
* @param {*} id id字段 默认 'id'
|
||||||
|
* @param {*} parentId 父节点字段 默认 'parentId'
|
||||||
|
* @param {*} children 孩子节点字段 默认 'children'
|
||||||
|
*/
|
||||||
|
export function handleTree(data, id, parentId, children) {
|
||||||
|
let config = {
|
||||||
|
id: id || 'id',
|
||||||
|
parentId: parentId || 'parentId',
|
||||||
|
childrenList: children || 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
var childrenListMap = {}
|
||||||
|
var tree = []
|
||||||
|
for (let d of data) {
|
||||||
|
let id = d[config.id]
|
||||||
|
childrenListMap[id] = d
|
||||||
|
if (!d[config.childrenList]) {
|
||||||
|
d[config.childrenList] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let d of data) {
|
||||||
|
let parentId = d[config.parentId]
|
||||||
|
let parentObj = childrenListMap[parentId]
|
||||||
|
if (!parentObj) {
|
||||||
|
tree.push(d)
|
||||||
|
} else {
|
||||||
|
parentObj[config.childrenList].push(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理
|
||||||
|
* @param {*} params 参数
|
||||||
|
*/
|
||||||
|
export function tansParams(params) {
|
||||||
|
let result = ''
|
||||||
|
for (const propName of Object.keys(params)) {
|
||||||
|
const value = params[propName]
|
||||||
|
var part = encodeURIComponent(propName) + "="
|
||||||
|
if (value !== null && value !== "" && typeof (value) !== "undefined") {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
|
||||||
|
let params = propName + '[' + key + ']'
|
||||||
|
var subPart = encodeURIComponent(params) + "="
|
||||||
|
result += subPart + encodeURIComponent(value[key]) + "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += part + encodeURIComponent(value) + "&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回项目路径
|
||||||
|
export function getNormalPath(p) {
|
||||||
|
if (p.length === 0 || !p || p == 'undefined') {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
let res = p.replace('//', '/')
|
||||||
|
if (res[res.length - 1] === '/') {
|
||||||
|
return res.slice(0, res.length - 1)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否为blob格式
|
||||||
|
export function blobValidate(data) {
|
||||||
|
return data.type !== 'application/json'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import { mergeRecursive } from "@/utils/agri"
|
||||||
|
import DictMeta from './DictMeta'
|
||||||
|
import DictData from './DictData'
|
||||||
|
|
||||||
|
const DEFAULT_DICT_OPTIONS = {
|
||||||
|
types: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc 字典
|
||||||
|
* @property {Object} label 标签对象,内部属性名为字典类型名称
|
||||||
|
* @property {Object} dict 字段数组,内部属性名为字典类型名称
|
||||||
|
* @property {Array.<DictMeta>} _dictMetas 字典元数据数组
|
||||||
|
*/
|
||||||
|
export default class Dict {
|
||||||
|
constructor() {
|
||||||
|
this.owner = null
|
||||||
|
this.label = {}
|
||||||
|
this.type = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(options) {
|
||||||
|
if (options instanceof Array) {
|
||||||
|
options = { types: options }
|
||||||
|
}
|
||||||
|
const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
|
||||||
|
if (opts.types === undefined) {
|
||||||
|
throw new Error('need dict types')
|
||||||
|
}
|
||||||
|
const ps = []
|
||||||
|
this._dictMetas = opts.types.map(t => DictMeta.parse(t))
|
||||||
|
this._dictMetas.forEach(dictMeta => {
|
||||||
|
const type = dictMeta.type
|
||||||
|
Vue.set(this.label, type, {})
|
||||||
|
Vue.set(this.type, type, [])
|
||||||
|
if (dictMeta.lazy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ps.push(loadDict(this, dictMeta))
|
||||||
|
})
|
||||||
|
return Promise.all(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新加载字典
|
||||||
|
* @param {String} type 字典类型
|
||||||
|
*/
|
||||||
|
reloadDict(type) {
|
||||||
|
const dictMeta = this._dictMetas.find(e => e.type === type)
|
||||||
|
if (dictMeta === undefined) {
|
||||||
|
return Promise.reject(`the dict meta of ${type} was not found`)
|
||||||
|
}
|
||||||
|
return loadDict(this, dictMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载字典
|
||||||
|
* @param {Dict} dict 字典
|
||||||
|
* @param {DictMeta} dictMeta 字典元数据
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function loadDict(dict, dictMeta) {
|
||||||
|
return dictMeta.request(dictMeta)
|
||||||
|
.then(response => {
|
||||||
|
const type = dictMeta.type
|
||||||
|
let dicts = dictMeta.responseConverter(response, dictMeta)
|
||||||
|
if (!(dicts instanceof Array)) {
|
||||||
|
console.error('the return of responseConverter must be Array.<DictData>')
|
||||||
|
dicts = []
|
||||||
|
} else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
|
||||||
|
console.error('the type of elements in dicts must be DictData')
|
||||||
|
dicts = []
|
||||||
|
}
|
||||||
|
dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
|
||||||
|
dicts.forEach(d => {
|
||||||
|
Vue.set(dict.label[type], d.value, d.label)
|
||||||
|
})
|
||||||
|
return dicts
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import DictOptions from './DictOptions'
|
||||||
|
import DictData from './DictData'
|
||||||
|
|
||||||
|
export default function(dict, dictMeta) {
|
||||||
|
const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)
|
||||||
|
const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)
|
||||||
|
return new DictData(dict[label], dict[value], dict)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确定字典字段
|
||||||
|
* @param {DictData} dict
|
||||||
|
* @param {...String} fields
|
||||||
|
*/
|
||||||
|
function determineDictField(dict, ...fields) {
|
||||||
|
return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* @classdesc 字典数据
|
||||||
|
* @property {String} label 标签
|
||||||
|
* @property {*} value 标签
|
||||||
|
* @property {Object} raw 原始数据
|
||||||
|
*/
|
||||||
|
export default class DictData {
|
||||||
|
constructor(label, value, raw) {
|
||||||
|
this.label = label
|
||||||
|
this.value = value
|
||||||
|
this.raw = raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { mergeRecursive } from "@/utils/agri"
|
||||||
|
import DictOptions from './DictOptions'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc 字典元数据
|
||||||
|
* @property {String} type 类型
|
||||||
|
* @property {Function} request 请求
|
||||||
|
* @property {String} label 标签字段
|
||||||
|
* @property {String} value 值字段
|
||||||
|
*/
|
||||||
|
export default class DictMeta {
|
||||||
|
constructor(options) {
|
||||||
|
this.type = options.type
|
||||||
|
this.request = options.request
|
||||||
|
this.responseConverter = options.responseConverter
|
||||||
|
this.labelField = options.labelField
|
||||||
|
this.valueField = options.valueField
|
||||||
|
this.lazy = options.lazy === true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析字典元数据
|
||||||
|
* @param {Object} options
|
||||||
|
* @returns {DictMeta}
|
||||||
|
*/
|
||||||
|
DictMeta.parse= function(options) {
|
||||||
|
let opts = null
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
opts = DictOptions.metas[options] || {}
|
||||||
|
opts.type = options
|
||||||
|
} else if (typeof options === 'object') {
|
||||||
|
opts = options
|
||||||
|
}
|
||||||
|
opts = mergeRecursive(DictOptions.metas['*'], opts)
|
||||||
|
return new DictMeta(opts)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { mergeRecursive } from "@/utils/agri"
|
||||||
|
import dictConverter from './DictConverter'
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
metas: {
|
||||||
|
'*': {
|
||||||
|
/**
|
||||||
|
* 字典请求,方法签名为function(dictMeta: DictMeta): Promise
|
||||||
|
*/
|
||||||
|
request: (dictMeta) => {
|
||||||
|
console.log(`load dict ${dictMeta.type}`)
|
||||||
|
return Promise.resolve([])
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData
|
||||||
|
*/
|
||||||
|
responseConverter,
|
||||||
|
labelField: 'label',
|
||||||
|
valueField: 'value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 默认标签字段
|
||||||
|
*/
|
||||||
|
DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
|
||||||
|
/**
|
||||||
|
* 默认值字段
|
||||||
|
*/
|
||||||
|
DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射字典
|
||||||
|
* @param {Object} response 字典数据
|
||||||
|
* @param {DictMeta} dictMeta 字典元数据
|
||||||
|
* @returns {DictData}
|
||||||
|
*/
|
||||||
|
function responseConverter(response, dictMeta) {
|
||||||
|
const dicts = response.content instanceof Array ? response.content : response
|
||||||
|
if (dicts === undefined) {
|
||||||
|
console.warn(`no dict data of "${dictMeta.type}" found in the response`)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return dicts.map(d => dictConverter(d, dictMeta))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeOptions(src) {
|
||||||
|
mergeRecursive(options, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default options
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Dict from './Dict'
|
||||||
|
import { mergeOptions } from './DictOptions'
|
||||||
|
|
||||||
|
export default function(Vue, options) {
|
||||||
|
mergeOptions(options)
|
||||||
|
Vue.mixin({
|
||||||
|
data() {
|
||||||
|
if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const dict = new Dict()
|
||||||
|
dict.owner = this
|
||||||
|
return {
|
||||||
|
dict
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!(this.dict instanceof Dict)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
options.onCreated && options.onCreated(this.dict)
|
||||||
|
this.dict.init(this.$options.dicts).then(() => {
|
||||||
|
options.onReady && options.onReady(this.dict)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('dictReady', this.dict)
|
||||||
|
if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
|
||||||
|
this.$options.methods.onDictReady.call(this, this.dict)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
264
utils/mqtt.js
264
utils/mqtt.js
|
|
@ -1,72 +1,214 @@
|
||||||
import mqtt from 'mqtt/dist/mqtt.js' //引入mqtt依赖
|
import mqtt from 'mqtt'
|
||||||
|
// 全局MQTT实例(单例,避免多页面重复创建)
|
||||||
|
let mqttInstance = null;
|
||||||
|
|
||||||
var client
|
/**
|
||||||
let mqttConnected = false //mqtt连接状态,这个可以不要,直接查询client.connected即可
|
* 初始化MQTT客户端(单例模式)
|
||||||
const publishTopic = '/test/123/456/set' //发布Topic
|
* @param {Object} mqttConfig 配置项
|
||||||
const MQTT_IP = '192.168.9.128:8083/mqtt' //mqtt地址端口
|
* @param {Array|String} subTopic 初始订阅主题
|
||||||
const MQTT_OPTIONS = {
|
* @returns {Object} MQTT实例
|
||||||
connectTimeout: 5000, //连接超时时间
|
*/
|
||||||
clientId: 'SiD0FMMAxrs', //clientId不能重复,这里可以随机生成
|
function createMqttClient(mqttConfig, subTopic) {
|
||||||
username: 'test', //用户名
|
// 已存在实例则直接返回
|
||||||
password: 'test', //密码
|
if (mqttInstance && mqttInstance.connected) {
|
||||||
clean: false
|
// 若传入新主题,补充订阅
|
||||||
}
|
if (subTopic) {
|
||||||
|
mqttInstance.subscribe(subTopic);
|
||||||
|
}
|
||||||
|
return mqttInstance;
|
||||||
|
}
|
||||||
|
|
||||||
//创建客户端连接
|
// 标准化配置(默认值 + 防错)
|
||||||
export function mqttConnect() {
|
const config = {
|
||||||
// #ifdef H5
|
host: mqttConfig.host || '1.94.254.176',
|
||||||
client = mqtt.connect('ws://' + MQTT_IP, MQTT_OPTIONS,function(err){
|
port: mqttConfig.port || 9001,
|
||||||
console.log(err)
|
clientId: mqttConfig.clientId || `uniapp_mqtt_${Date.now()}${Math.random().toString(16).substr(2, 4)}`, // 时间戳+随机数,减少重复
|
||||||
})
|
username: mqttConfig.username || 'admin',
|
||||||
// #endif
|
password: mqttConfig.password || 'Admin#12345678',
|
||||||
// #ifdef MP-WEIXIN||APP-PLUS
|
clean: false, // 关键:改为false,保持会话缓存消息
|
||||||
client = mqtt.connect('wx://' + MQTT_IP, MQTT_OPTIONS,function(err){
|
reconnectPeriod: mqttConfig.reconnectPeriod || 3000,
|
||||||
console.log(err)
|
connectTimeout: mqttConfig.connectTimeout || 5000,
|
||||||
})
|
qos: mqttConfig.qos || 1 // 默认QoS1,确保消息不丢
|
||||||
// #endif
|
};
|
||||||
|
|
||||||
client.on('connect', function() {
|
// 连接选项
|
||||||
console.log('连接成功')
|
const options = {
|
||||||
mqttConnected = true
|
clientId: config.clientId,
|
||||||
}).on('reconnect', function(error) {
|
username: config.username,
|
||||||
console.log('正在重连...', error)
|
password: config.password,
|
||||||
mqttConnected = false
|
clean: config.clean,
|
||||||
}).on('error', function(error) {
|
connectTimeout: config.connectTimeout,
|
||||||
console.log('连接失败...', error)
|
reconnectPeriod: config.reconnectPeriod,
|
||||||
mqttConnected = false
|
keepalive: 60 // 新增心跳,避免连接被断开
|
||||||
}).on('end', function() {
|
};
|
||||||
console.log('连接断开')
|
|
||||||
mqttConnected = false
|
// 拼接WS地址(兼容配置错误)
|
||||||
}).on('close',function(){
|
const url = `ws://${config.host}:${config.port}/mqtt`;
|
||||||
console.log('连接关闭')
|
|
||||||
mqttConnected = false
|
// 创建客户端
|
||||||
}).on('offline',function(){
|
const client = mqtt.connect(url, options);
|
||||||
console.log('客户端下线')
|
|
||||||
|
// 实例状态管理
|
||||||
|
const instance = {
|
||||||
|
client: client,
|
||||||
|
connected: false,
|
||||||
|
subscribedTopics: new Set(), // 记录已订阅主题
|
||||||
|
config: config,
|
||||||
|
messageCallback: null, // 消息接收回调
|
||||||
|
statusCallback: null, // 状态变更回调
|
||||||
|
|
||||||
|
// 订阅主题(支持单个/多个,带重试)
|
||||||
|
subscribe: function (topics, qos = config.qos) {
|
||||||
|
if (!this.connected) {
|
||||||
|
console.warn('MQTT未连接,延迟订阅:', topics);
|
||||||
|
// 连接成功后自动订阅
|
||||||
|
client.once('connect', () => this.subscribe(topics, qos));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topicList = Array.isArray(topics) ? topics : [topics];
|
||||||
|
client.subscribe(topicList, { qos }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('订阅失败:', err, '主题:', topicList);
|
||||||
|
// 订阅失败重试(仅一次)
|
||||||
|
setTimeout(() => this.subscribe(topics, qos), 1000);
|
||||||
|
} else {
|
||||||
|
topicList.forEach(t => this.subscribedTopics.add(t));
|
||||||
|
console.log(`订阅成功${topicList.length > 1 ? '(批量)' : '(单个)'}:`, topicList);
|
||||||
|
this.statusCallback && this.statusCallback('subscribe_success', topicList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
unsubscribe: function (topics) {
|
||||||
|
if (!this.connected) return;
|
||||||
|
const topicList = Array.isArray(topics) ? topics : [topics];
|
||||||
|
client.unsubscribe(topicList, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('取消订阅失败:', err);
|
||||||
|
} else {
|
||||||
|
topicList.forEach(t => this.subscribedTopics.delete(t));
|
||||||
|
console.log('取消订阅成功:', topicList);
|
||||||
|
this.statusCallback && this.statusCallback('unsubscribe_success', topicList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发布消息(带参数,失败重试)
|
||||||
|
publish: function (topic, message, qos = config.qos, retain = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.connected) {
|
||||||
|
reject(new Error('MQTT未连接,无法发布消息'));
|
||||||
|
// 自动重连后发布
|
||||||
|
this.reconnectAndPublish(topic, message, qos, retain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准化消息格式(对象转JSON)
|
||||||
|
const payload = typeof message === 'object' ? JSON.stringify(message) : String(message);
|
||||||
|
|
||||||
|
client.publish(topic, payload, { qos, retain }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('发布失败:', err, '主题:', topic);
|
||||||
|
reject(err);
|
||||||
|
// 发布失败重试
|
||||||
|
setTimeout(() => this.publish(topic, message, qos, retain), 1000);
|
||||||
|
} else {
|
||||||
|
console.log('发布成功:', topic, '内容:', payload);
|
||||||
|
resolve({ topic, payload });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重连后补发消息
|
||||||
|
reconnectAndPublish: function (topic, message, qos, retain) {
|
||||||
|
client.once('connect', () => {
|
||||||
|
this.publish(topic, message, qos, retain);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
disconnect: function () {
|
||||||
|
if (this.connected && this.client) {
|
||||||
|
this.client.end(false, () => { // false:等待剩余消息发送完成
|
||||||
|
this.connected = false;
|
||||||
|
this.subscribedTopics.clear();
|
||||||
|
console.log('MQTT连接已断开(保留会话)');
|
||||||
|
this.statusCallback && this.statusCallback('disconnect');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 绑定客户端事件
|
||||||
|
client.on('connect', () => {
|
||||||
|
instance.connected = true;
|
||||||
|
console.log('MQTT连接成功,ClientId:', config.clientId);
|
||||||
|
instance.statusCallback && instance.statusCallback('connect_success');
|
||||||
|
// 初始订阅主题
|
||||||
|
if (subTopic) {
|
||||||
|
instance.subscribe(subTopic);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
.on('reconnect', (error) => {
|
||||||
//发布消息
|
instance.connected = false;
|
||||||
export function mqttPublish(msg){
|
console.log('MQTT正在重连...', error);
|
||||||
if(mqttConnected){
|
instance.statusCallback && instance.statusCallback('reconnect', error);
|
||||||
client.publish(publishTopic,msg,{qos:1,retain:false});//hello mqtt +
|
})
|
||||||
console.log('发布了一条消息',msg)
|
.on('error', (error) => {
|
||||||
}else{
|
instance.connected = false;
|
||||||
uni.showToast({
|
console.error('MQTT连接错误:', error);
|
||||||
title: 'MQTT服务器未连接',
|
instance.statusCallback && instance.statusCallback('error', error);
|
||||||
icon: 'none'
|
})
|
||||||
|
.on('close', () => {
|
||||||
|
instance.connected = false;
|
||||||
|
console.log('MQTT连接关闭');
|
||||||
|
instance.statusCallback && instance.statusCallback('close');
|
||||||
|
})
|
||||||
|
.on('offline', () => {
|
||||||
|
instance.connected = false;
|
||||||
|
console.log('MQTT客户端下线');
|
||||||
|
instance.statusCallback && instance.statusCallback('offline');
|
||||||
|
})
|
||||||
|
.on('message', (topic, payload) => {
|
||||||
|
const msg = payload.toString();
|
||||||
|
console.log('收到MQTT消息:', topic, msg);
|
||||||
|
// 消息回调,交给业务层处理
|
||||||
|
instance.messageCallback && instance.messageCallback(topic, msg);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
//断开连接
|
|
||||||
export function mqttDisconnect(){
|
|
||||||
console.log('mqttConnected',mqttConnected)
|
|
||||||
console.log('client',client)
|
|
||||||
if(mqttConnected){
|
|
||||||
client.end(true)
|
|
||||||
mqttConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 赋值单例
|
||||||
|
mqttInstance = instance;
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对外暴露的核心方法
|
||||||
|
*/
|
||||||
|
export const mqttTool = {
|
||||||
|
// 初始化连接
|
||||||
|
connect: function (mqttConfig, subTopic) {
|
||||||
|
return createMqttClient(mqttConfig, subTopic);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取全局实例
|
||||||
|
getInstance: function () {
|
||||||
|
return mqttInstance;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 页面切换时的订阅管理(核心解决多页面订阅问题)
|
||||||
|
switchPageTopic: function (newTopics, oldTopics) {
|
||||||
|
const instance = mqttInstance;
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
|
// 先订阅新主题,再取消旧主题(避免漏消息)
|
||||||
|
if (newTopics) {
|
||||||
|
instance.subscribe(newTopics);
|
||||||
|
}
|
||||||
|
if (oldTopics) {
|
||||||
|
instance.unsubscribe(oldTopics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue