Vue3连接MQTT作为客户端
先下载依赖
npx --yes --registry https://registry.npmmirror.com npm install mqtt
在src的api创建 mes.js
// 导入axios
import axios from 'axios';
// 定义一个变量,记录公共的前缀, baseURL
const baseURL = 'http://localhost:8080';
const instance = axios.create({ baseURL });
// 添加响应拦截器
instance.interceptors.response.use(
(response) => {
return response.data; // 直接返回响应的数据部分
},
(error) => {
alert('服务异常');
return Promise.reject(error); // 异步的状态转化成失败的状态
}
);
// 定义 meslist 函数,使用定制的 Axios 实例发送请求
export const meslist = async (topic) => {
try {
const response = await instance.get('/getmes', {
params: {
topic: topic
}
});
return response; // 返回整个响应对象,因为拦截器已经提取了 data 部分
} catch (error) {
console.error('API 请求失败:', error);
throw error;
}
};
export default instance;
创建一个vue文件,然后填自己的ip,如果设置了用户名密码也填上
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ElMessage, ElButton, ElInput, ElTable, ElTableColumn } from 'element-plus';
import mqtt from 'mqtt'; // 导入 mqtt,默认导出的是客户端工厂函数
import { meslist } from '@/api/mes'; // 引入 meslist API
// 响应式变量
const SelectTopic = ref(''); // 可以动态修改的选择项(如输入框内容)
const results = ref([]); // 用来存储搜索结果
const loading = ref(false); // 用于控制加载状态
const error = ref(null); // 用于显示错误信息给用户
let client;
// 搜索函数
const search = async () => {
if (!SelectTopic.value.trim()) {
ElMessage.warning("请输入有效的搜索内容");
return;
}
loading.value = true; // 开始加载
error.value = null; // 清除之前的错误信息
try {
const response = await meslist(SelectTopic.value);
console.log('API Response:', response); // 输出原始响应以便调试
// 如果是数组,则按照新逻辑处理
if (Array.isArray(response)) {
if (response.length > 0) {
results.value = response;
console.log('搜索成功:', results.value);
} else {
ElMessage.info('未找到相关结果');
results.value = [];
}
}
// 如果都不是,则抛出异常
else {
throw new Error('API 返回的数据格式不正确');
}
} catch (err) {
// 更详细的错误信息记录
console.error('搜索失败:', err);
ElMessage.error('搜索失败,请稍后重试或检查网络连接');
results.value = [];
} finally {
loading.value = false; // 搜索完成,结束加载状态
}
};
// MQTT 连接配置
const connectMqtt = () => {
const brokerUrl = 'ws://ip/mqtt'; // 使用提供的主机名、端口和路径
const clientId = 'emqx_MTgwND'; // 客户端 ID
const options = {
username: 'web', // 替换为实际的用户名~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
password: '123', // 替换为实际的密码~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
keepalive: 60, // Keepalive 时间
clean: true, // Clean Start
sessionExpiryInterval: 0, // 会话过期时间
protocolVersion: 5, // 协议版本 MQTT 5
};
client = mqtt.connect(brokerUrl, options);
client.on('connect', () => {
console.log('Connected to MQTT broker');
if (SelectTopic.value.trim()) {
client.subscribe(SelectTopic.value, (err) => {
if (err) {
console.error('Failed to subscribe:', err);
} else {
console.log('Subscribed to topic:', SelectTopic.value);
}
});
}
});
client.on('message', (topic, message) => {
console.log('Received message:', topic, message.toString()); // 调试信息
try {
const msg = JSON.parse(message.toString());
// 更新结果列表
results.value = [...results.value, {
id: msg.id || 0,
topic: topic,
payload: msg.payload || message.toString(),
qos: msg.qos || 0,
timestamp: new Date().toLocaleString()
}];
} catch (e) {
console.error('Failed to parse message:', e);
// 如果解析失败,直接将原始消息作为字符串处理
results.value = [...results.value, {
id: -1,
topic: topic,
payload: message.toString(),
qos: 0,
timestamp: new Date().toLocaleString()
}];
}
});
client.on('error', (err) => {
console.error('MQTT connection error:', err);
});
client.on('close', () => {
console.log('MQTT connection closed');
});
};
// 断开 MQTT 连接
const disconnectMqtt = () => {
if (client) {
client.end();
client = null;
}
};
// 监听 SelectTopic 的变化
watch(SelectTopic, (newVal) => {
if (newVal.trim()) {
disconnectMqtt();
connectMqtt();
} else {
disconnectMqtt();
}
});
// 在组件挂载时启动 MQTT 连接
onMounted(() => {
if (SelectTopic.value.trim()) {
connectMqtt();
}
});
// 在组件卸载时断开 MQTT 连接
onUnmounted(() => {
disconnectMqtt();
});
</script>
<template>
<div class="container">
<h1 class="title">历史数据查看</h1>
<div class="input-container">
<el-input
v-model="SelectTopic"
placeholder="请输入搜索内容"
size="large"
clearable
class="input-box"
/>
</div>
<div class="button-container">
<el-button
type="primary"
@click="search"
:loading="loading"
class="search-button"
>
{{ loading ? "加载中..." : "搜索" }}
</el-button>
</div>
<!-- 搜索结果表格 -->
<el-table v-if="results.length > 0" :data="results" style="width: 100%" border>
<el-table-column prop="id" label="ID" width="100"></el-table-column>
<el-table-column prop="qos" label="QoS" width="100"></el-table-column>
<el-table-column prop="payload" label="Payload"></el-table-column>
<el-table-column prop="timestamp" label="Timestamp" width="200">
<template #default="scope">
{{ scope.row.timestamp }}
</template>
</el-table-column>
</el-table>
<!-- 如果没有数据或错误时的提示 -->
<div v-else-if="!loading && SelectTopic.trim()">
<p class="error-message">{{ error || '没有找到相关结果' }}</p>
</div>
<div v-else-if="!loading">
<p class="placeholder-message">请输入搜索内容</p>
</div>
</div>
</template>
<style scoped>
/* 设置容器样式 */
.container {
padding: 40px;
max-width: 1000px;
margin: 0 auto;
background-color: #f7f7f7;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* 标题样式 */
.title {
font-size: 28px;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 30px;
}
/* 输入框容器样式 */
.input-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
/* 输入框样式 */
.input-box {
width: 50%;
font-size: 16px;
}
/* 按钮容器样式 */
.button-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
/* 按钮样式 */
.search-button {
width: 200px;
height: 40px;
font-size: 16px;
}
/* 错误消息样式 */
.error-message {
color: red;
text-align: center;
font-size: 16px;
}
/* 占位符消息样式 */
.placeholder-message {
text-align: center;
font-size: 16px;
color: #888;
}
</style>
在输入框输入,就可以订阅相应的主题了,然后其他客户端发送此主题的内容,就可以订阅接收到了。因为我的代码里面还有给后端发请求的部分,所以相关后端接口需要你们自己完成或者把这部分的代码删掉。