agri-app/pages/demo/index.vue

661 lines
16 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="config-section">
<view class="section-title">MQTT连接配置</view>
<form @submit="handleConnect">
<view class="form-item">
<label>服务器地址</label>
<view class="input-wrapper">
<input
v-model="mqttConfig.broker"
type="text"
placeholder="如ws://test.mosquitto.org:8080/mqtt"
class="form-input"
/>
<text class="clear-icon" @click="clearInput('broker')" v-if="mqttConfig.broker">×</text>
</view>
</view>
<view class="form-item" v-if="false">
<label>客户端ID</label>
<view class="input-wrapper">
<input
v-model="mqttConfig.clientId"
type="text"
placeholder="自动生成或自定义"
class="form-input"
/>
<text class="clear-icon" @click="clearInput('clientId')" v-if="mqttConfig.clientId">×</text>
</view>
</view>
<view class="form-item">
<label>用户名:</label>
<view class="input-wrapper">
<input
v-model="mqttConfig.username"
type="text"
placeholder="可选"
class="form-input"
/>
<text class="clear-icon" @click="clearInput('username')" v-if="mqttConfig.username">×</text>
</view>
</view>
<view class="form-item">
<label>密码:</label>
<view class="input-wrapper">
<input
v-model="mqttConfig.password"
type="password"
placeholder="可选"
class="form-input"
/>
<text class="clear-icon" @click="clearInput('password')" v-if="mqttConfig.password">×</text>
</view>
</view>
<view class="form-item" v-if="false">
<label>连接超时:</label>
<view class="input-wrapper">
<input
v-model.number="mqttConfig.timeout"
type="number"
placeholder="默认30秒"
class="form-input"
/>
<text class="clear-icon" @click="clearInput('timeout')" v-if="mqttConfig.timeout">×</text>
</view>
</view>
<view class="btn-group">
<button
type="primary"
: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>
<view class="input-wrapper">
<input
v-model="subscribeTopic"
type="text"
placeholder="如test/topic"
class="form-input"
/>
<text class="clear-icon" @click="subscribeTopic = ''" v-if="subscribeTopic">×</text>
</view>
</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-wrapper">
<view class="section-title">消息日志</view>
<button
size="mini"
type="warn"
@click="clearAllLogs"
:disabled="messageLogs.length === 0"
class="clear-log-btn"
>
清空日志
</button>
</view>
<scroll-view
class="log-content"
scroll-y="true"
:style="{height: logHeight + 'px'}"
>
<!-- 空日志提示 -->
<view class="empty-log-tip" v-if="messageLogs.length === 0">
暂无消息日志
</view>
<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>
<view class="input-wrapper">
<input
v-model="publishTopic"
type="text"
placeholder="如test/topic"
class="form-input"
/>
<text class="clear-icon" @click="publishTopic = ''" v-if="publishTopic">×</text>
</view>
</view>
<view class="form-item">
<label>消息内容:</label>
<view class="textarea-wrapper">
<textarea
v-model="publishMessage"
placeholder="输入要发布的消息内容"
class="form-textarea"
></textarea>
<text class="clear-icon textarea-clear" @click="publishMessage = ''" v-if="publishMessage">×</text>
</view>
</view>
<button
type="primary"
@click="handlePublish"
>
发布消息
</button>
</view>
</view>
</template>
<script>
import mqtt from 'mqtt/dist/mqtt.min'
export default {
data() {
return {
// MQTT配置
mqttConfig: {
broker: 'ws://1.94.254.176:9001/mqtt', // 公共测试服务器
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
username: 'admin',
password: 'Admin#12345678',
timeout: 30
},
// 连接状态
isConnected: false,
client: null,
// 订阅相关
subscribeTopic: '',
subscribedTopics: [],
// 发布相关
publishTopic: '',
publishMessage: '',
// 消息日志
messageLogs: [],
logHeight: 300 // 日志区域高度
}
},
onReady() {
// 计算日志区域高度
uni.getSystemInfo({
success: (res) => {
this.logHeight = res.windowHeight - 600
}
})
},
onUnload() {
// 页面卸载时断开连接
if (this.client && this.isConnected) {
this.client.end()
}
},
methods: {
// 清除配置项输入框内容
clearInput(field) {
if (field === 'timeout') {
this.mqttConfig[field] = ''
} else {
this.$set(this.mqttConfig, field, '')
}
},
// 清空所有消息日志
clearAllLogs() {
uni.showModal({
title: '确认清空',
content: '是否确定清空所有消息日志?',
success: (res) => {
if (res.confirm) {
this.messageLogs = []
uni.showToast({
title: '日志已清空',
icon: 'success',
duration: 1500
})
}
}
})
},
// 添加日志
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() {
if (!this.mqttConfig.broker) {
uni.showToast({
title: '请输入服务器地址',
icon: 'none'
})
return
}
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', this.subscribeTopic, '订阅失败:' + 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>
<style scoped>
.container {
padding: 15px;
background-color: #f5f5f5;
min-height: 100vh;
}
.config-section, .subscribe-section, .publish-section, .log-section {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
}
/* 日志区域标题容器 - 包含标题和清空按钮 */
.section-title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
/* 清空日志按钮样式 */
.clear-log-btn {
height: 30px;
line-height: 30px;
padding: 0 10px;
}
.form-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
}
.form-item label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
/* 输入框容器 - 包含清除按钮 */
.input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.form-input {
flex: 1;
height: 40px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0 30px 0 10px; /* 右侧留出清除按钮空间 */
font-size: 14px;
}
/* 文本域容器 */
.textarea-wrapper {
position: relative;
}
.form-textarea {
width: 100%;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px 30px 10px 10px; /* 右侧留出清除按钮空间 */
font-size: 14px;
min-height: 100px;
box-sizing: border-box;
}
/* 清除按钮样式 */
.clear-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
font-size: 18px;
color: #999;
cursor: pointer;
z-index: 10;
}
/* 文本域清除按钮位置调整 */
.textarea-clear {
top: 10px;
transform: none;
}
.clear-icon:hover {
color: #f00;
}
.btn-group {
display: flex;
gap: 10px;
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;
padding: 8px 10px;
background-color: #f9f9f9;
border-radius: 4px;
margin-bottom: 5px;
}
.log-section {
margin-top: 15px;
}
.log-content {
background-color: #f9f9f9;
border-radius: 4px;
padding: 10px;
}
/* 空日志提示 */
.empty-log-tip {
text-align: center;
color: #999;
padding: 20px 0;
font-size: 14px;
}
.log-item {
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>