feat: 优化过滤主题功能,支持下拉选择

This commit is contained in:
2026-07-21 22:28:55 +08:00
parent 6a60b4daf4
commit 821b895d04
+67 -20
View File
@@ -1,14 +1,14 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useMqttStore } from '../stores/mqtt.js'; import { useMqttStore } from '../stores/mqtt.js';
import { NCard, NSpace, NInput, NButton, NEmpty, useMessage } from 'naive-ui'; import { NCard, NSelect, NButton, NEmpty, useMessage } from 'naive-ui';
// 全局状态与消息 API // 全局状态与消息 API
const store = useMqttStore(); const store = useMqttStore();
const message = useMessage(); const message = useMessage();
// 主题过滤关键字 // 已选中的过滤主题列表
const filter = ref(''); const filter = ref([]);
/** /**
* 复制主题到剪贴板 * 复制主题到剪贴板
@@ -24,22 +24,65 @@ async function copyTopic(topic) {
} }
/** /**
* 根据过滤关键字筛选后的消息列表 * 可过滤的主题选项(来自已配置主题和已接收消息)
* @type {import('vue').ComputedRef<Array<{label: string, value: string}>>}
*/
const topicOptions = computed(() => {
let server = store.activeServer.value;
let serverTopics = server?.topics?.map((t) => t.topic) || [];
let messageTopics = store.messages.value.map((m) => m.topic);
let all = [...new Set([...serverTopics, ...messageTopics])];
all.sort((a, b) => a.localeCompare(b));
return all.map((topic) => ({ label: topic, value: topic }));
});
/**
* 判断消息主题是否匹配指定的过滤主题(支持 + 单层通配符与 # 多层通配符)
* @param {string} filterTopic - 过滤主题,可包含 + 或 #
* @param {string} messageTopic - 消息主题
* @returns {boolean} 是否匹配
*/
function topicMatchesFilter(filterTopic, messageTopic) {
if (filterTopic === '#') {
return true;
}
if (filterTopic.endsWith('/#')) {
let prefix = filterTopic.slice(0, -2);
return messageTopic === prefix || messageTopic.startsWith(prefix + '/');
}
let filterParts = filterTopic.split('/');
let messageParts = messageTopic.split('/');
if (filterParts.length !== messageParts.length) {
return false;
}
return filterParts.every((part, index) => part === '+' || part === messageParts[index]);
}
/**
* 根据选中主题筛选后的消息列表(支持 + / # 通配符)
* @type {import('vue').ComputedRef<Array<Object>>} * @type {import('vue').ComputedRef<Array<Object>>}
*/ */
const filteredMessages = computed(() => { const filteredMessages = computed(() => {
if (!filter.value.trim()) { if (!filter.value || filter.value.length === 0) {
return store.messages.value; return store.messages.value;
} }
let f = filter.value.trim().toLowerCase(); return store.messages.value.filter((m) =>
filter.value.some((f) => topicMatchesFilter(f, m.topic))
);
return store.messages.value.filter((m) => { });
return m.topic.toLowerCase().includes(f);
});
})
/** /**
* 清空当前活动服务器的消息列表 * 清空当前活动服务器的消息列表
@@ -79,10 +122,8 @@ function formatPayload(payload) {
}" }"
> >
<template #header> <template #header>
<n-space <div
align="center" style="display: flex; flex-direction: column; gap: 8px; width: 100%"
justify="space-between"
style="width: 100%"
> >
<div> <div>
<span>消息监控</span> <span>消息监控</span>
@@ -90,20 +131,26 @@ function formatPayload(payload) {
style="font-size: 12px; opacity: 0.55; margin-left: 8px" style="font-size: 12px; opacity: 0.55; margin-left: 8px"
>实时查看接收和发送的 MQTT 消息</span> >实时查看接收和发送的 MQTT 消息</span>
</div> </div>
<n-space> <div
<n-input style="display: flex; align-items: center; gap: 8px; width: 100%"
>
<n-select
v-model:value="filter" v-model:value="filter"
size="small" size="small"
placeholder="过滤主题..." placeholder="过滤主题..."
style="width: 180px" style="flex: 1 1 auto; max-width: 480px; min-width: 0"
multiple
filterable
clearable clearable
max-tag-count="responsive"
:options="topicOptions"
/> />
<n-button <n-button
size="small" size="small"
@click="handleClear" @click="handleClear"
>清空消息</n-button> >清空消息</n-button>
</n-space> </div>
</n-space> </div>
</template> </template>
<n-empty <n-empty
v-if="filteredMessages.length === 0" v-if="filteredMessages.length === 0"