agri-app/pages/control/index.vue

667 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
<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 === '运行' }">
<uni-icons :type=" (status.jbk === '运行')?'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>
<view class="container">
<!-- 控制设置标题 -->
<view class="control-title">控制设置</view>
<uni-section :title="title" titleFontSize="16px" type="line">
<!-- 卷膜/卷被卡片容器2列栅格布局 -->
<view class="card-grid">
<!-- 卷被开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jbk, 'jbk')">
<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 === '运行' }">
<uni-icons :type=" (status.jbk === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷被关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jbg,'jbg')">
<view class="card-text">
<text class="card-main">卷被关</text>
<text class="card-sub">{{ show.jbg }}</text>
</view>
<view class="card-icon" :class="{ active: status.jbg === '运行' }">
<uni-icons :type=" (status.jbg === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1k, 'jm1k')">
<view class="card-text">
<text class="card-main">卷膜1开</text>
<text class="card-sub">{{ show.jm1k }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm1k === '运行' }">
<uni-icons :type="(status.jm1k === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1g, 'jm1g')">
<view class="card-text">
<text class="card-main">卷膜1关</text>
<text class="card-sub">{{ show.jm1g }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm1g === '运行' }">
<uni-icons :type="(status.jm1g === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2k, 'jm2k')">
<view class="card-text">
<text class="card-main">卷膜2开</text>
<text class="card-sub">{{ show.jm2k }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm2k === '运行' }">
<uni-icons :type="(status.jm2k === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2g, 'jm2g')">
<view class="card-text">
<text class="card-main">卷膜2关</text>
<text class="card-sub">{{ show.jm2g }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm2g === '运行' }">
<uni-icons :type="(status.jm2g === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3k, 'jm3k')">
<view class="card-text">
<text class="card-main">卷膜3开</text>
<text class="card-sub">{{ show.jm3k }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm3k === '运行' }">
<uni-icons :type="(status.jm3k === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3g, 'jm3g')">
<view class="card-text">
<text class="card-main">卷膜3关</text>
<text class="card-sub">{{ show.jm3g }}</text>
</view>
<view class="card-icon" :class="{ active: status.jm3g === '运行' }">
<uni-icons :type="(status.jm3g === '运行')?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
</view>
</uni-section>
</view>
</template>
<script>
import mqtt from 'mqtt'
export default {
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:'/up',
},
imei:'864536071808560',
publishTopic: '/down',
title:'',
message: {},
// 卡片状态(模拟后端返回数据)
show: {
jbk: "暂停",
jbg: "暂停",
jm1k: "暂停",
jm1g: "暂停",
jm2k: "暂停",
jm2g: "暂停",
jm3k: "暂停",
jm3g: "暂停"
},
status: {
jbk: 0,
jbg: 0,
jm1k: 0,
jm1g: 0,
jm2k: 0,
jm2g: 0,
jm3k: 0,
jm3g: 0
}
};
},
onLoad() {
this.publishTopic = "dtu/"+this.imei+this.publishTopic;
this.subscribeTopic = "dtu/"+this.imei+this.subscribeTopic;
this.title="设备【"+this.imei+"】";
if (this.status.jbk===1) {
this.status.jbg = 0;
}
if (this.status.jbg===1) {
this.status.jbk = 0;
}
if (this.status.jm1k===1) {
this.status.jm1g = 0;
}
if (this.status.jm2k===1) {
this.status.jm2g = 0;
}
if (this.status.jm3k===1) {
this.status.jm3g = 0;
}
if (this.status.jm1g===1) {
this.status.jm1k = 0;
}
if (this.status.jm2g===1) {
this.status.jm2k = 0;
}
if (this.status.jm2g===1) {
this.status.jm3k = 0;
}
console.info(this.status)
},
onUnload() {
this.disconnectMqtt()
},
methods: {
// 卡片点击事件(实际项目中调用接口修改状态) 功能标识
handleCardClick(status, type) {
uni.showModal({
title: '操作提示:',
content: '确定'+(status===1?"运行":"暂停")+'设备?',
cancelText: '取消',
confirmText: '确定',
success: (res) =>{
console.info("操作功能:【"+type+"】,变更状态为:"+ status)
if (!this.connected) {
// 链接mqtt
this.connectMqtt()
}
// 组装消息
this.message = JSON.stringify({[type]: status})
console.info("指令:"+this.message)
// 控制设备
this.publishMessage();
// 设备回执
this.ackMessage(type);
}
})
console.info(this.status)
},
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.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 || !this.publishTopic || !this.message) {
uni.showToast({
title: '控制设备失败',
icon: 'none'
})
return
}
this.client.publish(this.publishTopic, this.message, (err) => {
if (!err) {
this.addMessage(`【指令已发送】imei: ${this.publishTopic},指令: ${this.message}`);
} else {
this.addMessage(`发布失败: ${err.message},设备:[${this.publishTopic}]`)
}
})
this.message = {};
},
ackMessage(type) {
this.client.on("message", (topic, payload) => {
// 1. 先判断是否是目标订阅主题如dtu/xxx/up
if (topic !== this.status.subscribeTopic) return;
// 2. 解析消息体(注意异常捕获)
let msgData = {};
try {
msgData = JSON.parse(payload.toString());
} catch (e) {
console.error("消息解析失败:", e);
return;
}
// 3. 区分“回执”和“其他内容”
if (msgData.prop && "suc" in msgData) {
// 👉 这是“指令回执”
this.handleCommandAck(msgData,type);
}
});
},
addMessage(content) {
console.info("提示消息:"+content)
},
switchChange() {
},
// 处理指令回执的函数
handleCommandAck(ackData,type) {
// 拿到指令字段如jm2k和执行状态suc
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
const commandValue = ackData.prop[commandField]; // 这里是0
const isSuccess = ackData.suc; // 这里是true
if (isSuccess) {
this.status[type] = this.status[type] === 0 ? 1 : 0;
this.show[type] = this.status[type] === 0 ? "运行" : "暂停";
}
// 业务逻辑:提示“指令执行成功/失败”
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
// 可匹配之前发布的指令msgId更新UI状态
},
// 处理其他内容的函数
handleOtherContent(otherData) {
// 业务逻辑:处理传感器数据、设备状态等
console.log("收到其他内容:", otherData);
// 例如:更新温湿度显示、设备在线状态等
}
},
beforeDestroy() {
if (this.client) {
this.client.end()
}
},
};
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
}
/* 控制设置标题 */
.control-title {
font-size: 32rpx;
font-weight: 500;
text-align: center;
margin-bottom: 30rpx;
color: #333;
}
/* 2列栅格布局 */
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
padding: 20rpx;
}
/* 卡片样式 */
.control-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2rpx 8rpx #bfbec1;
}
/* 卡片文字区域 */
.card-text {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.card-main {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
.card-sub {
font-size: 24rpx;
color: #999;
}
/* 卡片图标容器 */
.card-icon {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background-color: #e5e5e5;
display: flex;
align-items: center;
justify-content: center;
}
/* 激活状态(运行) */
.card-icon.active {
background-color: #007aff;
}
</style>