diff --git a/src/components/dashboard/SendPanel.vue b/src/components/dashboard/SendPanel.vue index af00248..7f44348 100644 --- a/src/components/dashboard/SendPanel.vue +++ b/src/components/dashboard/SendPanel.vue @@ -25,23 +25,25 @@ const { variables: globalVars, setVariable: setGlobalVar, importFromBindings } = // ─── State ───────────────────────────────────────────────────── const selectedConnectionId = ref('') -const selectedPresetIds = ref([]) -const topic = ref('') -const payload = ref('') -const qos = ref<0 | 1 | 2>(0) -const retain = ref(false) +const activePresetId = ref(null) const showVariables = ref(false) const showV5Props = ref(false) -const variableBindings = ref>({}) -const userProperties = ref<{ key: string; value: string }[]>([]) -const contentType = ref('') -const messageExpiryInterval = ref(null) -const responseTopic = ref('') -const correlationData = ref('') -const sending = ref(false) const showGlobalVarMgr = ref(false) -const batchMode = ref(false) -const batchProgress = ref<{ presetName: string; status: 'pending' | 'sending' | 'success' | 'error'; message?: string }[]>([]) +const sending = ref(false) + +// Per-preset editor state (keyed by preset id) +const editorStates = ref + userProperties: { key: string; value: string }[] + contentType: string + messageExpiryInterval: number | null + responseTopic: string + correlationData: string +}>>({}) // ─── Computed ────────────────────────────────────────────────── @@ -49,257 +51,224 @@ const connectedConnections = computed(() => props.connections.filter((c) => c.status === 'connected'), ) -const selectedPresets = computed(() => - props.presets.filter((p) => selectedPresetIds.value.includes(p.id)), +const activePreset = computed(() => + props.presets.find((p) => p.id === activePresetId.value), ) -const selectedPreset = computed(() => - props.presets.find((p) => p.id === selectedPresetIds.value[selectedPresetIds.value.length - 1]), +const activeState = computed(() => + activePresetId.value ? editorStates.value[activePresetId.value] : null, ) const presetVariables = computed(() => { - if (!selectedPreset.value) return [] - const vars = extractVariables(selectedPreset.value.payloadTemplate) - for (const v of extractVariables(selectedPreset.value.topic)) { + if (!activePreset.value) return [] + const vars = extractVariables(activePreset.value.payloadTemplate) + for (const v of extractVariables(activePreset.value.topic)) { if (!vars.includes(v)) vars.push(v) } - for (const up of selectedPreset.value.userProperties) { + for (const up of activePreset.value.userProperties) { for (const v of extractVariables(up.key)) if (!vars.includes(v)) vars.push(v) for (const v of extractVariables(up.value)) if (!vars.includes(v)) vars.push(v) } return vars }) -// ─── Watch preset ────────────────────────────────────────────── +// ─── Init editor state for a preset ──────────────────────────── -watch(selectedPreset, (preset) => { - if (preset && !batchMode.value) { - topic.value = preset.topic - payload.value = preset.payloadTemplate - qos.value = preset.qos - retain.value = preset.retain - userProperties.value = JSON.parse(JSON.stringify(preset.userProperties)) - const newBindings: Record = {} - const presetVars = extractVariables(preset.payloadTemplate) - const topicVars = extractVariables(preset.topic) - const allVars = [...new Set([...presetVars, ...topicVars])] - for (const v of allVars) { - newBindings[v] = globalVars.value[v] ?? variableBindings.value[v] ?? '' - } - variableBindings.value = newBindings - showVariables.value = allVars.length > 0 +function initEditorState(preset: Preset) { + if (editorStates.value[preset.id]) return + + const allVars = [...new Set([ + ...extractVariables(preset.payloadTemplate), + ...extractVariables(preset.topic), + ...preset.userProperties.flatMap(up => [ + ...extractVariables(up.key), + ...extractVariables(up.value), + ]), + ])] + + const bindings: Record = {} + for (const v of allVars) { + bindings[v] = globalVars.value[v] ?? '' } -}) -// ─── Send Single ─────────────────────────────────────────────── + editorStates.value[preset.id] = { + topic: preset.topic, + payload: preset.payloadTemplate, + qos: preset.qos, + retain: preset.retain, + variableBindings: bindings, + userProperties: JSON.parse(JSON.stringify(preset.userProperties)), + contentType: '', + messageExpiryInterval: null, + responseTopic: '', + correlationData: '', + } +} -function getPublishDto(preset?: Preset): PublishDto { - const t = preset?.topic ?? topic.value - const p = preset?.payloadTemplate ?? payload.value - const q = preset?.qos ?? qos.value - const r = preset?.retain ?? retain.value - const up = preset ? JSON.parse(JSON.stringify(preset.userProperties)) : userProperties.value +// ─── Switch active preset ────────────────────────────────────── - const resolvedTopic = resolveTemplate(t, variableBindings.value) - const resolvedPayload = resolveTemplate(p, variableBindings.value) - const resolvedProps = resolveUserProperties(up, variableBindings.value) +function selectPreset(presetId: string) { + const preset = props.presets.find((p) => p.id === presetId) + if (!preset) return + initEditorState(preset) + activePresetId.value = presetId + showVariables.value = presetVariables.value.length > 0 +} + +// Auto-select first preset when presets change +watch(() => props.presets, (ps) => { + if (ps.length > 0) { + // Init all presets + for (const p of ps) initEditorState(p) + // Select first if none selected + if (!activePresetId.value || !ps.find((p) => p.id === activePresetId.value)) { + selectPreset(ps[0].id) + } + } else { + activePresetId.value = null + } +}, { immediate: true, deep: true }) + +// ─── Send ────────────────────────────────────────────────────── + +function getPublishDto(): PublishDto | null { + if (!activePreset.value || !activeState.value) return null + const st = activeState.value + const resolvedTopic = resolveTemplate(st.topic, st.variableBindings) + const resolvedPayload = resolveTemplate(st.payload, st.variableBindings) + const resolvedProps = resolveUserProperties(st.userProperties, st.variableBindings) return { connection_id: selectedConnectionId.value, topic: resolvedTopic, payload: resolvedPayload, - qos: q, - retain: r, + qos: st.qos, + retain: st.retain, user_properties: resolvedProps, - content_type: contentType.value || null, - message_expiry_interval: messageExpiryInterval.value, - response_topic: responseTopic.value || null, - correlation_data: correlationData.value || null, - } -} - -async function sendSingle(preset?: Preset) { - if (!selectedConnectionId.value) { - ElMessage.warning('请先选择一个连接') - return false - } - const dto = getPublishDto(preset) - if (!dto.topic.trim()) { ElMessage.warning('请输入主题'); return false } - if (!dto.payload.trim()) { ElMessage.warning('请输入消息内容'); return false } - - try { - await mqttPublish(dto) - emit('sent', dto) - return true - } catch (e: any) { - ElMessage.error(`发送失败: ${e}`) - return false + content_type: st.contentType || null, + message_expiry_interval: st.messageExpiryInterval, + response_topic: st.responseTopic || null, + correlation_data: st.correlationData || null, } } async function send() { - importFromBindings(variableBindings.value) + if (!selectedConnectionId.value) { + ElMessage.warning('请先选择一个连接') + return + } + if (!activePreset.value || !activeState.value) { + ElMessage.warning('请选择一个预设') + return + } + importFromBindings(activeState.value.variableBindings) sending.value = true - if (batchMode.value && selectedPresets.value.length > 0) { - // Batch send all selected presets - batchProgress.value = selectedPresets.value.map((p) => ({ - presetName: p.name, - status: 'pending' as const, - })) - for (let i = 0; i < selectedPresets.value.length; i++) { - const preset = selectedPresets.value[i] - batchProgress.value[i].status = 'sending' - try { - const dto = getPublishDto(preset) - if (!dto.topic.trim()) { - batchProgress.value[i].status = 'error' - batchProgress.value[i].message = '主题为空' - continue - } - if (!dto.payload.trim()) { - batchProgress.value[i].status = 'error' - batchProgress.value[i].message = 'Payload 为空' - continue - } - await mqttPublish(dto) - emit('sent', dto) - batchProgress.value[i].status = 'success' - } catch (e: any) { - batchProgress.value[i].status = 'error' - batchProgress.value[i].message = String(e) - } - } - const successCount = batchProgress.value.filter((p) => p.status === 'success').length - const errorCount = batchProgress.value.filter((p) => p.status === 'error').length - ElMessage.success(`批量发送完成: ${successCount} 成功, ${errorCount} 失败`) - } else { - // Single send - const ok = await sendSingle() - if (ok) ElMessage.success('消息已发送') + try { + const dto = getPublishDto() + if (!dto) { ElMessage.warning('预设数据异常'); return } + if (!dto.topic.trim()) { ElMessage.warning('主题为空'); return } + if (!dto.payload.trim()) { ElMessage.warning('Payload 为空'); return } + await mqttPublish(dto) + emit('sent', dto) + ElMessage.success(`"${activePreset.value.name}" 已发送`) + } catch (e: any) { + ElMessage.error(`发送失败: ${e}`) + } finally { + sending.value = false } - sending.value = false } -// ─── Save as global variable when a binding value changes ────── +// ─── Variable binding changed ────────────────────────────────── function onBindingChange(name: string, value: string) { - variableBindings.value = { ...variableBindings.value, [name]: value } + if (!activePresetId.value || !activeState.value) return + activeState.value.variableBindings = { ...activeState.value.variableBindings, [name]: value } if (value.trim()) { setGlobalVar(name, value) } } -function toggleBatchMode() { - batchMode.value = !batchMode.value - if (!batchMode.value) { - selectedPresetIds.value = [] - batchProgress.value = [] +// ─── Close tab ───────────────────────────────────────────────── + +function closePreset(id: string) { + // Keep the editor state but deselect + if (activePresetId.value === id) { + const idx = props.presets.findIndex((p) => p.id === id) + const next = props.presets[idx + 1] ?? props.presets[idx - 1] ?? null + activePresetId.value = next?.id ?? null } }