Compare commits
6 Commits
7ebb384f01
...
a62b9557ff
| Author | SHA1 | Date | |
|---|---|---|---|
| a62b9557ff | |||
| 1c23b85d92 | |||
| 72ee175bff | |||
| 771d8447b4 | |||
| acee68ab58 | |||
| 9c2a469e4d |
@@ -10,11 +10,17 @@ class MqttManager {
|
||||
this.messages = new Map() // serverId -> message[]
|
||||
this.publishHistory = new Map() // serverId -> publishHistory[]
|
||||
this.listeners = new Set() // IPC callback functions
|
||||
this.msgIdCounter = 0 // 用于生成唯一消息 id
|
||||
|
||||
this.dataPath = join(app.getPath('userData'), 'frost-mqtt-data.json')
|
||||
this.loadData()
|
||||
}
|
||||
|
||||
nextMsgId() {
|
||||
this.msgIdCounter = (this.msgIdCounter + 1) % Number.MAX_SAFE_INTEGER
|
||||
return 'msg_' + Date.now() + '_' + this.msgIdCounter
|
||||
}
|
||||
|
||||
// ==================== 数据持久化 ====================
|
||||
getDataDir() {
|
||||
const dir = app.getPath('userData')
|
||||
@@ -79,7 +85,7 @@ class MqttManager {
|
||||
initDefaultServers() {
|
||||
const defaults = [
|
||||
{
|
||||
id: 'srv_001',
|
||||
id: 'example_server',
|
||||
name: 'EMQX 公共测试',
|
||||
host: 'broker.emqx.io',
|
||||
port: 1883,
|
||||
@@ -94,23 +100,6 @@ class MqttManager {
|
||||
{ id: 'topic_default_001', topic: 'sensor/+/temperature', qos: 1, subscribed: false },
|
||||
{ id: 'topic_default_002', topic: 'device/status', qos: 2, subscribed: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'srv_002',
|
||||
name: '本地 Mosquitto',
|
||||
host: 'localhost',
|
||||
port: 1883,
|
||||
protocol: 'mqtt',
|
||||
clientId: 'frost_local_' + Math.random().toString(36).slice(2, 8),
|
||||
username: '',
|
||||
password: '',
|
||||
keepAlive: 60,
|
||||
cleanSession: true,
|
||||
status: 'disconnected',
|
||||
topics: [
|
||||
{ id: 'topic_default_003', topic: 'home/+/light', qos: 1, subscribed: false },
|
||||
{ id: 'topic_default_004', topic: 'home/alarm', qos: 2, subscribed: false }
|
||||
]
|
||||
}
|
||||
]
|
||||
defaults.forEach((s) => {
|
||||
@@ -269,9 +258,9 @@ class MqttManager {
|
||||
qos: packet.qos,
|
||||
retain: packet.retain,
|
||||
direction: 'sub',
|
||||
id: 'msg_' + Date.now()
|
||||
}
|
||||
const msgs = this.messages.get(id) || []
|
||||
id: this.nextMsgId()
|
||||
}
|
||||
const msgs = this.messages.get(id) || []
|
||||
msgs.unshift(msg)
|
||||
if (msgs.length > 500) msgs.pop()
|
||||
this.messages.set(id, msgs)
|
||||
@@ -479,10 +468,11 @@ class MqttManager {
|
||||
this.publishHistory.set(serverId, history)
|
||||
|
||||
// 同时添加到消息日志
|
||||
const pubMsgId = this.nextMsgId()
|
||||
const msgs = this.messages.get(serverId) || []
|
||||
msgs.unshift({
|
||||
...pubEntry,
|
||||
id: 'msg_' + Date.now()
|
||||
id: pubMsgId
|
||||
})
|
||||
if (msgs.length > 500) msgs.pop()
|
||||
this.messages.set(serverId, msgs)
|
||||
@@ -491,7 +481,7 @@ class MqttManager {
|
||||
serverId,
|
||||
message: {
|
||||
...pubEntry,
|
||||
id: 'msg_' + Date.now()
|
||||
id: pubMsgId
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -79,9 +79,6 @@ export function useMqttStore() {
|
||||
try {
|
||||
const data = await window.api.getServers()
|
||||
servers.value = data
|
||||
if (data.length > 0 && !activeServerId.value) {
|
||||
activeServerId.value = data[0].id
|
||||
}
|
||||
setupListeners()
|
||||
} catch (e) {
|
||||
console.error('初始化失败:', e)
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useMqttStore } from '../stores/mqtt.js'
|
||||
import { NCard, NSpace, NInput, NButton, NEmpty } from 'naive-ui'
|
||||
import { NCard, NSpace, NInput, NButton, NEmpty, useMessage } from 'naive-ui'
|
||||
|
||||
const store = useMqttStore()
|
||||
const message = useMessage()
|
||||
|
||||
const filter = ref('')
|
||||
|
||||
async function copyTopic(topic) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(topic)
|
||||
message.success('主题已复制')
|
||||
} catch {
|
||||
message.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
const filteredMessages = computed(() => {
|
||||
if (!filter.value.trim()) return store.messages.value
|
||||
const f = filter.value.trim().toLowerCase()
|
||||
@@ -40,7 +50,7 @@ function formatPayload(payload) {
|
||||
<template #header>
|
||||
<n-space align="center" justify="space-between" style="width: 100%">
|
||||
<div>
|
||||
<span style="font-weight: 700">消息监控</span>
|
||||
<span>消息监控</span>
|
||||
<span style="font-size: 12px; opacity: 0.55; margin-left: 8px"
|
||||
>实时查看接收和发送的 MQTT 消息</span
|
||||
>
|
||||
@@ -69,7 +79,7 @@ function formatPayload(payload) {
|
||||
<span class="msg-dir" :class="m.direction">{{
|
||||
m.direction === 'pub' ? 'PUB' : 'SUB'
|
||||
}}</span>
|
||||
<span class="msg-topic" :title="m.topic">{{ m.topic }}</span>
|
||||
<span class="msg-topic" :title="m.topic" @click="copyTopic(m.topic)">{{ m.topic }}</span>
|
||||
<span class="msg-meta">QoS{{ m.qos }}{{ m.retain ? ' R' : '' }}</span>
|
||||
</div>
|
||||
<pre class="msg-payload">{{ formatPayload(m.payload) }}</pre>
|
||||
@@ -102,7 +112,7 @@ function formatPayload(payload) {
|
||||
}
|
||||
|
||||
.msg-log {
|
||||
background: #1e1e2e;
|
||||
background: #f5f7fa;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
@@ -113,12 +123,12 @@ function formatPayload(payload) {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
color: #cdd6f4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.msg-entry {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.msg-header {
|
||||
@@ -129,7 +139,7 @@ function formatPayload(payload) {
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
color: #6c7086;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.msg-dir {
|
||||
@@ -138,31 +148,36 @@ function formatPayload(payload) {
|
||||
width: 28px;
|
||||
}
|
||||
.msg-dir.pub {
|
||||
color: #89b4fa;
|
||||
color: #2080f0;
|
||||
}
|
||||
.msg-dir.sub {
|
||||
color: #a6e3a1;
|
||||
color: #18a058;
|
||||
}
|
||||
.msg-topic {
|
||||
color: #f9e2af;
|
||||
color: #d25a00;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.msg-topic:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.msg-payload {
|
||||
margin: 6px 0 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #cdd6f4;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #333;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.msg-meta {
|
||||
color: #6c7086;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,15 @@ function handleFormatJson() {
|
||||
async function handleClearHistory() {
|
||||
await store.clearPublishHistory()
|
||||
}
|
||||
|
||||
async function copyTopic(topic) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(topic)
|
||||
msg.success('主题已复制')
|
||||
} catch {
|
||||
msg.error('复制失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -166,7 +175,7 @@ async function handleClearHistory() {
|
||||
<div class="msg-header">
|
||||
<span class="msg-time">{{ m.time }}</span>
|
||||
<span class="msg-dir pub">PUB</span>
|
||||
<span class="msg-topic" :title="m.topic">{{ m.topic }}</span>
|
||||
<span class="msg-topic" :title="m.topic" @click="copyTopic(m.topic)">{{ m.topic }}</span>
|
||||
<span class="msg-meta">QoS{{ m.qos }}{{ m.retain ? ' R' : '' }}</span>
|
||||
</div>
|
||||
<pre class="msg-payload">{{ m.payload }}</pre>
|
||||
@@ -276,7 +285,7 @@ async function handleClearHistory() {
|
||||
}
|
||||
|
||||
.msg-log {
|
||||
background: #1e1e2e;
|
||||
background: #f5f7fa;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
@@ -286,12 +295,12 @@ async function handleClearHistory() {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
color: #cdd6f4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.msg-entry {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.msg-header {
|
||||
@@ -302,7 +311,7 @@ async function handleClearHistory() {
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
color: #6c7086;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.msg-dir {
|
||||
@@ -311,28 +320,33 @@ async function handleClearHistory() {
|
||||
width: 28px;
|
||||
}
|
||||
.msg-dir.pub {
|
||||
color: #89b4fa;
|
||||
color: #2080f0;
|
||||
}
|
||||
.msg-topic {
|
||||
color: #f9e2af;
|
||||
color: #d25a00;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.msg-topic:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.msg-payload {
|
||||
margin: 6px 0 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #cdd6f4;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #333;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.msg-meta {
|
||||
color: #6c7086;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user