feat: 增加订阅主题备注功能
This commit is contained in:
@@ -90,8 +90,8 @@ export function registerIpcHandlers() {
|
||||
|
||||
// ==================== 主题管理 ====================
|
||||
|
||||
ipcMain.handle('mqtt:add-topic', (_, serverId, topic, qos) => {
|
||||
return mqttManager.addTopic(serverId, topic, qos);
|
||||
ipcMain.handle('mqtt:add-topic', (_, serverId, topic, qos, comment) => {
|
||||
return mqttManager.addTopic(serverId, topic, qos, comment);
|
||||
});
|
||||
|
||||
ipcMain.handle('mqtt:remove-topic', (_, serverId, topic) => {
|
||||
@@ -110,8 +110,8 @@ export function registerIpcHandlers() {
|
||||
return mqttManager.subscribeAll(serverId);
|
||||
});
|
||||
|
||||
ipcMain.handle('mqtt:update-topic', (_, serverId, topicId, topic, qos) => {
|
||||
return mqttManager.updateTopic(serverId, topicId, topic, qos);
|
||||
ipcMain.handle('mqtt:update-topic', (_, serverId, topicId, topic, qos, comment) => {
|
||||
return mqttManager.updateTopic(serverId, topicId, topic, qos, comment);
|
||||
});
|
||||
|
||||
// ==================== 消息发布 ====================
|
||||
|
||||
@@ -82,6 +82,7 @@ class MqttManager {
|
||||
s.topics = (s.topics || []).map((t) => ({
|
||||
...t,
|
||||
id: t.id || 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
|
||||
comment: t.comment ?? '',
|
||||
subscribed: false,
|
||||
}));
|
||||
this.servers.set(s.id, s);
|
||||
@@ -154,8 +155,8 @@ class MqttManager {
|
||||
reconnectInterval: 5000,
|
||||
status: 'disconnected',
|
||||
topics: [
|
||||
{ id: 'topic_default_001', topic: 'sensor/+/temperature', qos: 1, subscribed: false },
|
||||
{ id: 'topic_default_002', topic: 'device/status', qos: 2, subscribed: false },
|
||||
{ id: 'topic_default_001', topic: 'sensor/+/temperature', qos: 1, comment: '', subscribed: false },
|
||||
{ id: 'topic_default_002', topic: 'device/status', qos: 2, comment: '', subscribed: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -272,6 +273,7 @@ class MqttManager {
|
||||
id: t.id || 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
|
||||
topic: t.topic,
|
||||
qos: t.qos ?? 1,
|
||||
comment: t.comment ?? '',
|
||||
subscribed: false,
|
||||
};
|
||||
}
|
||||
@@ -361,6 +363,7 @@ class MqttManager {
|
||||
id: t.id,
|
||||
topic: t.topic,
|
||||
qos: t.qos,
|
||||
comment: t.comment ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -575,9 +578,10 @@ class MqttManager {
|
||||
* @param {string} serverId - 服务器 id
|
||||
* @param {string} topic - 主题名称
|
||||
* @param {MqttQoS} qos - QoS 等级
|
||||
* @param {string} comment - 主题备注
|
||||
* @returns {MqttTopic | null} 添加后的主题对象或 null
|
||||
*/
|
||||
addTopic(serverId, topic, qos = 1) {
|
||||
addTopic(serverId, topic, qos = 1, comment = '') {
|
||||
let server = this.servers.get(serverId);
|
||||
if (!server) {
|
||||
return null;
|
||||
@@ -588,7 +592,7 @@ class MqttManager {
|
||||
|
||||
let topicId = 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
||||
|
||||
let newTopic = { id: topicId, topic, qos, subscribed: false };
|
||||
let newTopic = { id: topicId, topic, qos, comment, subscribed: false };
|
||||
server.topics.push(newTopic);
|
||||
|
||||
this.saveData();
|
||||
@@ -686,9 +690,10 @@ class MqttManager {
|
||||
* @param {string} topicId - 主题 id
|
||||
* @param {string} newTopic - 新的主题名称
|
||||
* @param {number} newQos - 新的 QoS 等级
|
||||
* @param {string} comment - 新的主题备注
|
||||
* @returns {boolean} 是否更新成功
|
||||
*/
|
||||
updateTopic(serverId, topicId, newTopic, newQos) {
|
||||
updateTopic(serverId, topicId, newTopic, newQos, comment = '') {
|
||||
|
||||
let server = this.servers.get(serverId);
|
||||
|
||||
@@ -721,6 +726,7 @@ class MqttManager {
|
||||
|
||||
t.topic = newTopic;
|
||||
t.qos = newQos;
|
||||
t.comment = comment;
|
||||
|
||||
// 如果已连接且此前已订阅,重新订阅新主题
|
||||
if (server.status === 'connected' && wasSubscribed) {
|
||||
@@ -733,6 +739,7 @@ class MqttManager {
|
||||
topic: newTopic,
|
||||
subscribed: t.subscribed,
|
||||
topicId,
|
||||
comment: t.comment,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,13 +20,14 @@ const api = {
|
||||
disconnect: (id) => ipcRenderer.invoke('mqtt:disconnect', id),
|
||||
|
||||
// 主题管理
|
||||
addTopic: (serverId, topic, qos) => ipcRenderer.invoke('mqtt:add-topic', serverId, topic, qos),
|
||||
addTopic: (serverId, topic, qos, comment) =>
|
||||
ipcRenderer.invoke('mqtt:add-topic', serverId, topic, qos, comment),
|
||||
removeTopic: (serverId, topic) => ipcRenderer.invoke('mqtt:remove-topic', serverId, topic),
|
||||
subscribe: (serverId, topic) => ipcRenderer.invoke('mqtt:subscribe', serverId, topic),
|
||||
unsubscribe: (serverId, topic) => ipcRenderer.invoke('mqtt:unsubscribe', serverId, topic),
|
||||
subscribeAll: (serverId) => ipcRenderer.invoke('mqtt:subscribe-all', serverId),
|
||||
updateTopic: (serverId, topicId, topic, qos) =>
|
||||
ipcRenderer.invoke('mqtt:update-topic', serverId, topicId, topic, qos),
|
||||
updateTopic: (serverId, topicId, topic, qos, comment) =>
|
||||
ipcRenderer.invoke('mqtt:update-topic', serverId, topicId, topic, qos, comment),
|
||||
|
||||
// 消息发布
|
||||
publish: (serverId, topic, payload, opts) =>
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ref, computed } from 'vue';
|
||||
* @property {string} serverId 所属服务器 id
|
||||
* @property {string} topic 主题名称
|
||||
* @property {string} [topicId] 主题 id
|
||||
* @property {string} [comment] 主题备注
|
||||
* @property {boolean} subscribed 是否已订阅
|
||||
*/
|
||||
|
||||
@@ -57,12 +58,12 @@ import { ref, computed } from 'vue';
|
||||
* @property {(serverList: ServerConfig[]) => Promise<MqttServer[]>} importServers 导入服务器配置
|
||||
* @property {(id: string) => Promise<boolean>} connectServer 连接服务器
|
||||
* @property {(id: string) => Promise<void>} disconnectServer 断开服务器
|
||||
* @property {(serverId: string, topic: string, qos: MqttQoS) => Promise<MqttTopic | null>} addTopic 添加主题
|
||||
* @property {(serverId: string, topic: string, qos: MqttQoS, comment: string) => Promise<MqttTopic | null>} addTopic 添加主题
|
||||
* @property {(serverId: string, topic: string) => Promise<void>} removeTopic 移除主题
|
||||
* @property {(serverId: string, topic: string) => Promise<boolean>} subscribeTopic 订阅主题
|
||||
* @property {(serverId: string, topic: string) => Promise<boolean>} unsubscribeTopic 取消订阅主题
|
||||
* @property {(serverId: string) => Promise<boolean>} subscribeAllTopics 批量订阅所有主题
|
||||
* @property {(serverId: string, topicId: string, topic: string, qos: MqttQoS) => Promise<boolean>} updateTopic 更新主题
|
||||
* @property {(serverId: string, topicId: string, topic: string, qos: MqttQoS, comment: string) => Promise<boolean>} updateTopic 更新主题
|
||||
* @property {(serverId: string, topic: string, payload: string, opts: PublishOptions) => Promise<boolean>} publishMessage 发布消息
|
||||
* @property {() => Promise<void>} refreshMessages 刷新消息列表
|
||||
* @property {() => Promise<void>} refreshPublishHistory 刷新发布历史
|
||||
@@ -181,6 +182,9 @@ function setupListeners() {
|
||||
|
||||
if (t) {
|
||||
t.subscribed = data.subscribed;
|
||||
if (typeof data.comment === 'string') {
|
||||
t.comment = data.comment;
|
||||
}
|
||||
if (data.topic && data.topic !== t.topic) {
|
||||
t.topic = data.topic;
|
||||
sortTopics(srv);
|
||||
@@ -400,11 +404,12 @@ export function useMqttStore() {
|
||||
* @param {string} serverId - 服务器 id
|
||||
* @param {string} topic - 主题名称
|
||||
* @param {MqttQoS} qos - QoS 等级
|
||||
* @param {string} comment - 主题备注
|
||||
* @returns {Promise<MqttTopic | null>} 添加后的主题对象或 null
|
||||
*/
|
||||
async function addTopic(serverId, topic, qos) {
|
||||
async function addTopic(serverId, topic, qos, comment) {
|
||||
|
||||
let result = await window.api.addTopic(serverId, topic, qos);
|
||||
let result = await window.api.addTopic(serverId, topic, qos, comment);
|
||||
|
||||
if (result) {
|
||||
let srv = servers.value.find((s) => s.id === serverId);
|
||||
@@ -472,10 +477,11 @@ export function useMqttStore() {
|
||||
* @param {string} topicId - 主题 id
|
||||
* @param {string} topic - 新的主题名称
|
||||
* @param {MqttQoS} qos - 新的 QoS 等级
|
||||
* @param {string} comment - 新的主题备注
|
||||
* @returns {Promise<boolean>} 是否更新成功
|
||||
*/
|
||||
async function updateTopic(serverId, topicId, topic, qos) {
|
||||
return await window.api.updateTopic(serverId, topicId, topic, qos);
|
||||
async function updateTopic(serverId, topicId, topic, qos, comment) {
|
||||
return await window.api.updateTopic(serverId, topicId, topic, qos, comment);
|
||||
}
|
||||
|
||||
// ==================== 消息发布 ====================
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useMqttStore } from '../stores/mqtt.js';
|
||||
import { NCard, NSpace, NFormItem, NInput, NSelect, NButton, NEmpty, NList, NListItem, NTag, NEllipsis, useMessage } from 'naive-ui';
|
||||
import { NCard, NSpace, NFormItem, NInput, NSelect, NButton, NEmpty, NList, NListItem, NTag, useMessage } from 'naive-ui';
|
||||
|
||||
// 全局状态与消息 API
|
||||
const store = useMqttStore();
|
||||
@@ -10,11 +10,13 @@ const msg = useMessage();
|
||||
// 主题输入与 QoS 选择
|
||||
const topicInput = ref('');
|
||||
const qos = ref(2);
|
||||
const comment = ref('');
|
||||
|
||||
// 编辑状态
|
||||
const editingTopicId = ref(null);
|
||||
const editingTopic = ref('');
|
||||
const editingQos = ref(2);
|
||||
const editingComment = ref('');
|
||||
|
||||
// QoS 下拉选项
|
||||
const qosOptions = [
|
||||
@@ -84,11 +86,12 @@ async function handleAddTopic() {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = await store.addTopic(srv.id, topic, qos.value);
|
||||
let result = await store.addTopic(srv.id, topic, qos.value, comment.value.trim());
|
||||
|
||||
if (result) {
|
||||
msg.success('主题已添加' + (srv.status === 'connected' ? '并已订阅' : ''));
|
||||
topicInput.value = '';
|
||||
comment.value = '';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -173,6 +176,7 @@ function startEdit(t) {
|
||||
editingTopicId.value = t.id || t.topic;
|
||||
editingTopic.value = t.topic;
|
||||
editingQos.value = t.qos ?? 2;
|
||||
editingComment.value = t.comment ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,6 +186,7 @@ function cancelEdit() {
|
||||
editingTopicId.value = null;
|
||||
editingTopic.value = '';
|
||||
editingQos.value = 2;
|
||||
editingComment.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +218,13 @@ async function handleEditSave(t) {
|
||||
}
|
||||
|
||||
let topicId = t.id || t.topic;
|
||||
let result = await store.updateTopic(srv.id, topicId, topicName, editingQos.value);
|
||||
let result = await store.updateTopic(
|
||||
srv.id,
|
||||
topicId,
|
||||
topicName,
|
||||
editingQos.value,
|
||||
editingComment.value.trim()
|
||||
);
|
||||
|
||||
if (result) {
|
||||
msg.success('主题已更新');
|
||||
@@ -258,6 +269,16 @@ async function handleEditSave(t) {
|
||||
:options="qosOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
class="comment-field"
|
||||
label="备注"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="comment"
|
||||
placeholder="可选备注"
|
||||
@keydown.enter="handleAddTopic"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-button
|
||||
class="topic-submit"
|
||||
type="primary"
|
||||
@@ -329,6 +350,11 @@ async function handleEditSave(t) {
|
||||
:options="qosOptions"
|
||||
style="width: 160px; flex-shrink: 0"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="editingComment"
|
||||
size="small"
|
||||
placeholder="备注"
|
||||
/>
|
||||
<n-space
|
||||
:size="4"
|
||||
:wrap="false"
|
||||
@@ -352,7 +378,20 @@ async function handleEditSave(t) {
|
||||
v-else
|
||||
class="topic-name"
|
||||
>
|
||||
<n-ellipsis>{{ t.topic }}</n-ellipsis>
|
||||
<span
|
||||
class="topic-label"
|
||||
:title="t.topic"
|
||||
>{{ t.topic }}</span>
|
||||
<div
|
||||
v-if="t.comment"
|
||||
class="topic-comment"
|
||||
>
|
||||
<span class="topic-comment-prefix">备注</span>
|
||||
<span
|
||||
class="topic-comment-text"
|
||||
:title="t.comment"
|
||||
>{{ t.comment }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #suffix>
|
||||
<n-space
|
||||
@@ -443,12 +482,18 @@ async function handleEditSave(t) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.comment-field {
|
||||
flex: 1 1 180px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.topic-submit {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.topic-field :deep(.n-form-item-blank),
|
||||
.qos-field :deep(.n-form-item-blank) {
|
||||
.qos-field :deep(.n-form-item-blank),
|
||||
.comment-field :deep(.n-form-item-blank) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -460,12 +505,51 @@ async function handleEditSave(t) {
|
||||
}
|
||||
|
||||
.topic-name {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-family: monospace;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.topic-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
font-family: monospace;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topic-comment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 2px 6px;
|
||||
gap: 5px;
|
||||
border: 1px solid rgba(14, 165, 160, 0.16);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 184, 166, 0.06);
|
||||
color: var(--n-text-color-3);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.topic-comment-prefix {
|
||||
flex-shrink: 0;
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.topic-comment-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topic-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -17,6 +17,8 @@ export interface MqttTopic {
|
||||
topic: string;
|
||||
/** 订阅 QoS 等级 */
|
||||
qos: MqttQoS;
|
||||
/** 主题备注,空字符串表示未备注 */
|
||||
comment: string;
|
||||
/** 当前是否已订阅 */
|
||||
subscribed: boolean;
|
||||
}
|
||||
@@ -29,6 +31,8 @@ export interface TopicConfig {
|
||||
topic: string;
|
||||
/** 订阅 QoS 等级,默认 1 */
|
||||
qos?: MqttQoS;
|
||||
/** 主题备注,默认空字符串 */
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
/** MQTT 服务器配置(不含运行时状态) */
|
||||
|
||||
Reference in New Issue
Block a user