设备控制

master
lld 2025-12-25 17:17:07 +08:00
parent 162cd01b8a
commit b68548ddd2
3 changed files with 682 additions and 1 deletions

View File

@ -30,6 +30,11 @@
"style": { "style": {
"navigationBarTitleText": "示例" "navigationBarTitleText": "示例"
} }
}, {
"path": "pages/control/index",
"style": {
"navigationBarTitleText": "控制中心"
}
},{ },{
"path": "pages/mine/avatar/index", "path": "pages/mine/avatar/index",
"style": { "style": {
@ -86,7 +91,14 @@
"iconPath": "static/images/tabbar/home.png", "iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home_.png", "selectedIconPath": "static/images/tabbar/home_.png",
"text": "首页" "text": "首页"
}, { },
{
"pagePath": "pages/control/index",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "控制中心"
},
{
"pagePath": "pages/work/index", "pagePath": "pages/work/index",
"iconPath": "static/images/tabbar/work.png", "iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png", "selectedIconPath": "static/images/tabbar/work_.png",

665
pages/control/index.vue Normal file
View File

@ -0,0 +1,665 @@
<!--
<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)
// 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) {
// jm2ksuc
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 ? "成功" : "失败"}`);
// msgIdUI
},
//
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>

View File

@ -66,3 +66,7 @@ export function mqttDisconnect(){
} }
} }