master
lld 2025-12-28 23:41:48 +08:00
parent e7f9d0affa
commit 7372ec452e
1 changed files with 491 additions and 178 deletions

View File

@ -1,220 +1,533 @@
<template> <template>
<view class="container"> <view class="container">
<uni-section :title="lastTopic" type="line" padding> <!-- 连接配置区域 -->
<uni-grid :column="4" border-color="#03a9f4"> <view class="config-section">
<uni-grid-item :index="0"> <view class="section-title">MQTT连接配置</view>
<view class="grid-item-box"> <form @submit="handleConnect">
<text class="text">{{ data.temp1 }} </text> <view class="form-item">
<text class="text"> 温度1 </text> <label>服务器地址</label>
</view> <input
</uni-grid-item> v-model="mqttConfig.broker"
<uni-grid-item :index="1"> type="text"
<view class="grid-item-box"> placeholder="如ws://test.mosquitto.org:8080/mqtt"
<text class="text">{{ data.temp2 }}</text> class="form-input"
<text class="text"> 温度2 </text> />
</view> </view>
</uni-grid-item> <view class="form-item" v-if="false">
<uni-grid-item :index="2"> <label>客户端ID</label>
<view class="grid-item-box"> <input
<text class="text">{{ data.temp3 }}</text> v-model="mqttConfig.clientId"
<text class="text"> 温度3 </text> type="text"
</view> placeholder="自动生成或自定义"
</uni-grid-item> class="form-input"
<uni-grid-item :index="3"> />
<view class="grid-item-box"> </view>
<text class="text">{{ data.temp4 }}</text> <view class="form-item">
<text class="text"> 温度4 </text> <label>用户名</label>
</view> <input
</uni-grid-item> v-model="mqttConfig.username"
</uni-grid> type="text"
<uni-grid :column="4" border-color="#03a9f4"> placeholder="可选"
<uni-grid-item :index="0"> class="form-input"
<view class="grid-item-box"> />
<text class="text">{{ data.humi1 }}</text> </view>
<text class="text"> 湿度1 </text> <view class="form-item">
</view> <label>密码</label>
</uni-grid-item> <input
<uni-grid-item :index="1"> v-model="mqttConfig.password"
<view class="grid-item-box"> type="password"
<text class="text">{{ data.humi2 }}</text> placeholder="可选"
<text class="text"> 湿度2 </text> class="form-input"
</view> />
</uni-grid-item> </view>
<uni-grid-item :index="2"> <view class="form-item" v-if="false">
<view class="grid-item-box"> <label>连接超时</label>
<text class="text">{{ data.humi3 }}</text> <input
<text class="text"> 湿度3 </text> v-model.number="mqttConfig.timeout"
</view> type="number"
</uni-grid-item> placeholder="默认30秒"
<uni-grid-item :index="3"> class="form-input"
<view class="grid-item-box"> />
<text class="text">{{ data.humi4 }}</text> </view>
<text class="text"> 湿度4 %RH</text>
</view> <view class="btn-group">
</uni-grid-item> <button
</uni-grid> type="primary"
</uni-section> :disabled="isConnected"
@click="handleConnect"
>
连接
</button>
<button
type="warn"
:disabled="!isConnected"
@click="handleDisconnect"
>
断开连接
</button>
</view>
</form>
</view>
<!-- 订阅区域 -->
<view class="subscribe-section" v-if="isConnected">
<view class="section-title">主题订阅</view>
<view class="form-item">
<label>订阅主题</label>
<input
v-model="subscribeTopic"
type="text"
placeholder="如test/topic"
class="form-input"
/>
</view>
<button
type="primary"
@click="handleSubscribe"
>
订阅
</button>
<!-- 已订阅主题列表 -->
<view class="topic-list" v-if="subscribedTopics.length">
<view class="list-title">已订阅主题</view>
<view
class="topic-item"
v-for="(topic, index) in subscribedTopics"
:key="index"
>
{{topic}}
<button
size="mini"
type="warn"
@click="handleUnsubscribe(topic)"
>
取消订阅
</button>
</view>
</view>
</view>
<!-- 消息日志区域 -->
<view class="log-section">
<view class="section-title">消息日志</view>
<scroll-view
class="log-content"
scroll-y="true"
:style="{height: logHeight + 'px'}"
>
<view
class="log-item"
v-for="(log, index) in messageLogs"
:key="index"
:class="{'received': log.type === 'received', 'sent': log.type === 'sent', 'system': log.type === 'system'}"
>
<view class="log-time">{{log.time}}</view>
<view class="log-type">{{log.type === 'received' ? '接收' : log.type === 'sent' ? '发送' : '系统'}}</view>
<view class="log-topic">主题{{log.topic}}</view>
<view class="log-message">内容{{log.message}}</view>
</view>
</scroll-view>
</view>
<!-- 发布消息区域 -->
<view class="publish-section" v-if="isConnected">
<view class="section-title">发布消息</view>
<view class="form-item">
<label>发布主题</label>
<input
v-model="publishTopic"
type="text"
placeholder="如test/topic"
class="form-input"
/>
</view>
<view class="form-item">
<label>消息内容</label>
<textarea
v-model="publishMessage"
placeholder="输入要发布的消息内容"
class="form-textarea"
></textarea>
</view>
<button
type="primary"
@click="handlePublish"
>
发布消息
</button>
</view>
</view> </view>
</template> </template>
<script> <script>
import mqtt from 'mqtt/dist/mqtt.js' //mqtt import mqtt from 'mqtt/dist/mqtt.min'
// mqtt.connect(...)
let client = null
export default { export default {
data() { data() {
return { return {
connected: false, // MQTT
data: { mqttConfig: {
temp1: null, broker: 'ws://1.94.254.176:9001/mqtt', //
humi1: null, clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
temp2: null, username: 'admin',
humi2: null, password: 'Admin#12345678',
temp3: null, timeout: 30
humi3: null,
temp4: null,
humi4: null,
}, },
lastTopic: "设备" + '', //
lastRaw: '' isConnected: false,
client: null,
//
subscribeTopic: '',
subscribedTopics: [],
//
publishTopic: '',
publishMessage: '',
//
messageLogs: [],
logHeight: 300 //
} }
}, },
onShow() { onReady() {
const clientId = 'uniapp_' + Date.now() + '_' + Math.floor(Math.random() * 100000) //
uni.getSystemInfo({
client = mqtt.connect('ws://1.94.254.176:9001', { success: (res) => {
clientId, this.logHeight = res.windowHeight - 600
username: 'admin', //
password: 'Admin#12345678', //
clean: true,
reconnectPeriod: 2000,
connectTimeout: 5000
})
client.on('connect', () => {
this.connected = true
client.subscribe('dtu/+/up', { qos: 0 })
})
client.on('message', (topic, payload) => {
const s = payload.toString()
this.lastRaw = s
// JSON
if (!(s.startsWith('{') && s.endsWith('}'))) return
let obj
try {
obj = JSON.parse(s)
const allKeysNumeric = Object.keys(obj).every(key => /^\d+$/.test(key));
if (Object.keys(obj).length <= 0 || !allKeysNumeric) {
return;
}
} catch(e) { return }
// 101~104湿201~204/10湿/10
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10)
this.lastTopic = "设备【" + topic+"】"
this.data = {
temp1: div10(obj["201"]),
humi1: div10(obj["101"]),
temp2: div10(obj["202"]),
humi2: div10(obj["102"]),
temp3: div10(obj["203"]),
humi3: div10(obj["103"]),
temp4: div10(obj["204"]),
humi4: div10(obj["104"])
} }
}) })
client.on('close', () => { this.connected = false })
client.on('error', () => { this.connected = false })
}, },
onUnload() { onUnload() {
try { client && client.end(true) } catch(e) {} //
if (this.client && this.isConnected) {
this.client.end()
}
},
methods: {
//
addLog(type, topic, message) {
const time = new Date().toLocaleTimeString()
this.messageLogs.unshift({
time,
type, // received/sent/system
topic,
message
})
//
if (this.messageLogs.length > 100) {
this.messageLogs.pop()
}
},
// // MQTT
// handleConnect() {
// connected if (!this.mqttConfig.broker) {
// uni.showToast({
// temp1/humi1 temp4/humi4 title: '请输入服务器地址',
// icon: 'none'
// lastTopic lastRaw })
}, return
onHide() { }
client && client.end(true)
}, try {
//
const options = {
clientId: this.mqttConfig.clientId || 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
username: this.mqttConfig.username,
password: this.mqttConfig.password,
connectTimeout: (this.mqttConfig.timeout || 30) * 1000,
keepalive: 60,
clean: true
}
//
this.client = mqtt.connect(this.mqttConfig.broker, options)
//
this.client.on('connect', () => {
this.isConnected = true
this.addLog('system', '', 'MQTT连接成功')
uni.showToast({
title: '连接成功',
icon: 'success'
})
})
//
this.client.on('message', (topic, message) => {
this.addLog('received', topic, message.toString())
})
//
this.client.on('close', () => {
this.isConnected = false
this.subscribedTopics = []
this.addLog('system', '', 'MQTT连接已断开')
})
//
this.client.on('error', (error) => {
this.isConnected = false
this.addLog('system', '', '连接错误:' + error.message)
uni.showToast({
title: '连接失败:' + error.message,
icon: 'none',
duration: 3000
})
})
} catch (error) {
this.addLog('system', '', '连接异常:' + error.message)
uni.showToast({
title: '连接异常:' + error.message,
icon: 'none',
duration: 3000
})
}
},
//
handleDisconnect() {
if (this.client) {
this.client.end()
this.isConnected = false
this.subscribedTopics = []
this.addLog('system', '', '已手动断开MQTT连接')
uni.showToast({
title: '已断开连接',
icon: 'success'
})
}
},
//
handleSubscribe() {
if (!this.subscribeTopic) {
uni.showToast({
title: '请输入订阅主题',
icon: 'none'
})
return
}
if (this.subscribedTopics.includes(this.subscribeTopic)) {
uni.showToast({
title: '该主题已订阅',
icon: 'none'
})
return
}
this.client.subscribe(this.subscribeTopic, (error) => {
if (error) {
this.addLog('system', '', '订阅失败:' + error.message)
uni.showToast({
title: '订阅失败',
icon: 'none'
})
} else {
this.subscribedTopics.push(this.subscribeTopic)
this.addLog('system', this.subscribeTopic, '订阅成功')
uni.showToast({
title: '订阅成功',
icon: 'success'
})
}
})
},
//
handleUnsubscribe(topic) {
this.client.unsubscribe(topic, (error) => {
if (error) {
this.addLog('system', topic, '取消订阅失败:' + error.message)
uni.showToast({
title: '取消订阅失败',
icon: 'none'
})
} else {
this.subscribedTopics = this.subscribedTopics.filter(item => item !== topic)
this.addLog('system', topic, '取消订阅成功')
uni.showToast({
title: '取消订阅成功',
icon: 'success'
})
}
})
},
//
handlePublish() {
if (!this.publishTopic) {
uni.showToast({
title: '请输入发布主题',
icon: 'none'
})
return
}
if (!this.publishMessage) {
uni.showToast({
title: '请输入消息内容',
icon: 'none'
})
return
}
this.client.publish(this.publishTopic, this.publishMessage, (error) => {
if (error) {
this.addLog('system', this.publishTopic, '发布失败:' + error.message)
uni.showToast({
title: '发布失败',
icon: 'none'
})
} else {
this.addLog('sent', this.publishTopic, this.publishMessage)
uni.showToast({
title: '发布成功',
icon: 'success'
})
//
this.publishMessage = ''
}
})
}
}
} }
</script> </script>
<style scoped> <style scoped>
.container { .container {
padding: 20rpx; padding: 15px;
background-color: #f5f5f5; background-color: #f5f5f5;
min-height: 100vh; min-height: 100vh;
} }
.text { .config-section, .subscribe-section, .publish-section, .log-section {
font-size: 22px; background-color: #fff;
font-weight: bolder; border-radius: 8px;
display: inline-block; padding: 15px;
}
.text:nth-child(even) {
font-size: 14px;
margin-top: 5px;
color: #9c9c9c;
}
.example-body {
/* #ifndef APP-NVUE */
// display: block;
/* #endif */
}
.grid-dynamic-box {
margin-bottom: 15px; margin-bottom: 15px;
} }
.grid-item-box { .section-title {
flex: 1; font-size: 16px;
// position: relative; font-weight: bold;
/* #ifndef APP-NVUE */ margin-bottom: 15px;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
.form-item {
display: flex; display: flex;
/* #endif */
flex-direction: column; flex-direction: column;
align-items: center; margin-bottom: 12px;
justify-content: center;
padding: 15px 0;
} }
.grid-item-box-row { .form-item label {
flex: 1; font-size: 14px;
// position: relative; color: #666;
/* #ifndef APP-NVUE */ margin-bottom: 5px;
}
.form-input {
height: 40px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0 10px;
font-size: 14px;
}
.form-textarea {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
font-size: 14px;
min-height: 100px;
}
.btn-group {
display: flex; display: flex;
/* #endif */ gap: 10px;
flex-direction: row; margin-top: 20px;
}
.btn-group button {
flex: 1;
}
.topic-list {
margin-top: 15px;
}
.list-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.topic-item {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
justify-content: center; padding: 8px 10px;
padding: 15px 0; background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 5px;
} }
.log-section {
/* #ifdef H5 */ margin-top: 15px;
@media screen and (min-width: 768px) and (max-width: 1425px) {
.swiper {
height: 630px;
}
} }
@media screen and (min-width: 1425px) { .log-content {
.swiper { background-color: #f9f9f9;
height: 830px; border-radius: 4px;
} padding: 10px;
} }
/* #endif */ .log-item {
</style> padding: 8px;
border-bottom: 1px solid #eee;
margin-bottom: 5px;
font-size: 14px;
}
.log-item.received {
background-color: #e8f5e9;
border-radius: 4px;
}
.log-item.sent {
background-color: #e3f2fd;
border-radius: 4px;
}
.log-item.system {
background-color: #fff8e1;
border-radius: 4px;
}
.log-time {
color: #999;
font-size: 12px;
margin-bottom: 3px;
}
.log-type {
font-weight: bold;
margin-bottom: 3px;
}
.log-topic {
color: #666;
margin-bottom: 3px;
}
.log-message {
color: #333;
}
</style>