feat: 阿里云 MQTT 连接预设、连接分组与 MQTT 3.1.1 支持
- 后端引入 rumqttc v4 客户端,支持 MQTT 3.1.1 连接/发布/订阅,协议版本随连接下发 - 连接表单:阿里云预设管理(分组、导入导出、去抖自动保存),退出云模式需确认,mqtts 时端口跟随 8883 - 连接列表:分组列就地编辑(输入即创建),空分组提示优化 - 导入按内容去重并提示跳过数量,导出提示含明文密钥 - 调试日志密码脱敏;修复多处取消弹窗的 unhandled rejection
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import type { AliyunPreset } from '../types/mqtt'
|
||||
import { defaultAliyunPreset } from '../types/mqtt'
|
||||
|
||||
const STORAGE_KEY = 'mqttx_aliyun_presets'
|
||||
|
||||
function loadPresets(): AliyunPreset[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (!raw) return []
|
||||
const parsed = JSON.parse(raw) as AliyunPreset[]
|
||||
return parsed.map((p) => ({
|
||||
...defaultAliyunPreset(),
|
||||
...p,
|
||||
id: p.id || crypto.randomUUID(),
|
||||
group: p.group ?? '',
|
||||
}))
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function savePresets(presets: AliyunPreset[]) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(presets))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const presets = ref<AliyunPreset[]>(loadPresets())
|
||||
|
||||
watch(presets, (val) => savePresets(val), { deep: true })
|
||||
|
||||
export function useAliyunPresets() {
|
||||
function add(preset?: Partial<AliyunPreset>): AliyunPreset {
|
||||
const p: AliyunPreset = { ...defaultAliyunPreset(), ...preset }
|
||||
presets.value.push(p)
|
||||
return p
|
||||
}
|
||||
|
||||
function update(id: string, data: Partial<AliyunPreset>) {
|
||||
const idx = presets.value.findIndex((p) => p.id === id)
|
||||
if (idx !== -1) {
|
||||
presets.value[idx] = { ...presets.value[idx], ...data }
|
||||
}
|
||||
}
|
||||
|
||||
function remove(id: string) {
|
||||
const idx = presets.value.findIndex((p) => p.id === id)
|
||||
if (idx !== -1) presets.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
function getById(id: string): AliyunPreset | undefined {
|
||||
return presets.value.find((p) => p.id === id)
|
||||
}
|
||||
|
||||
// ─── 分组 ────────────────────────────────────────────────────────────────
|
||||
|
||||
function getGroups(): string[] {
|
||||
const set = new Set<string>()
|
||||
for (const p of presets.value) {
|
||||
if (p.group) set.add(p.group)
|
||||
}
|
||||
return Array.from(set).sort()
|
||||
}
|
||||
|
||||
function renameGroup(oldName: string, newName: string) {
|
||||
for (const p of presets.value) {
|
||||
if (p.group === oldName) p.group = newName
|
||||
}
|
||||
}
|
||||
|
||||
function deleteGroup(group: string) {
|
||||
for (const p of presets.value) {
|
||||
if (p.group === group) p.group = ''
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 导入 / 导出 ────────────────────────────────────────────────────────
|
||||
|
||||
function exportPresets(ids?: string[]): string {
|
||||
const list = ids
|
||||
? presets.value.filter((p) => ids.includes(p.id))
|
||||
: presets.value
|
||||
return JSON.stringify(list, null, 2)
|
||||
}
|
||||
|
||||
/** 按 实例ID+GroupID+设备ID+AK 去重,返回导入/跳过数量 */
|
||||
function importPresets(json: string): { imported: number; skipped: number } {
|
||||
const parsed = JSON.parse(json) as Partial<AliyunPreset>[]
|
||||
const seen = new Set(
|
||||
presets.value.map((p) => `${p.instanceId}|${p.groupId}|${p.deviceId}|${p.accessKeyId}`),
|
||||
)
|
||||
let imported = 0
|
||||
let skipped = 0
|
||||
for (const item of parsed) {
|
||||
const p: AliyunPreset = {
|
||||
...defaultAliyunPreset(),
|
||||
...item,
|
||||
id: crypto.randomUUID(),
|
||||
}
|
||||
const key = `${p.instanceId}|${p.groupId}|${p.deviceId}|${p.accessKeyId}`
|
||||
if (seen.has(key)) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
seen.add(key)
|
||||
presets.value.push(p)
|
||||
imported++
|
||||
}
|
||||
return { imported, skipped }
|
||||
}
|
||||
|
||||
return {
|
||||
presets,
|
||||
add,
|
||||
update,
|
||||
remove,
|
||||
getById,
|
||||
getGroups,
|
||||
renameGroup,
|
||||
deleteGroup,
|
||||
exportPresets,
|
||||
importPresets,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user