feat: auto-format JSON display everywhere
- Shared prettyJson/truncate/isJson utility in src/utils/format.ts - MessageStream: collapsed preview + expanded view both auto-format JSON - LogPanel: log details with JSON content auto-formatted - PresetList: payload preview auto-formatted - Deduplicated isJson/formatPayload logic
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { ref, computed, watch, nextTick } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import { useLogs } from '../../stores/useLogs'
|
import { useLogs } from '../../stores/useLogs'
|
||||||
import { mqttGetLogDir } from '../../stores/useMqttBridge'
|
import { mqttGetLogDir } from '../../stores/useMqttBridge'
|
||||||
|
import { prettyJson } from '../../utils/format'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const { logs, clear } = useLogs()
|
const { logs, clear } = useLogs()
|
||||||
@@ -121,7 +122,7 @@ function eventIcon(event: string): string {
|
|||||||
<span v-if="log.details" class="log-dtl-btn" @click="toggleDetails(log.id)">{{ expandedDetails.has(log.id) ? '收起' : '详情' }}</span>
|
<span v-if="log.details" class="log-dtl-btn" @click="toggleDetails(log.id)">{{ expandedDetails.has(log.id) ? '收起' : '详情' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="log.details && expandedDetails.has(log.id)" class="log-dtl-content">
|
<div v-if="log.details && expandedDetails.has(log.id)" class="log-dtl-content">
|
||||||
{{ log.details }}
|
{{ prettyJson(log.details) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick } from 'vue'
|
||||||
import type { MqttMessage } from '../../types/mqtt'
|
import type { MqttMessage } from '../../types/mqtt'
|
||||||
|
import { prettyJson, isJson, truncate } from '../../utils/format'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
messages: MqttMessage[]
|
messages: MqttMessage[]
|
||||||
@@ -50,11 +51,6 @@ function onScroll() {
|
|||||||
|
|
||||||
// ─── Payload Format Helpers ────────────────────────────────────
|
// ─── Payload Format Helpers ────────────────────────────────────
|
||||||
|
|
||||||
function isJson(str: string): boolean {
|
|
||||||
try { JSON.parse(str); return true }
|
|
||||||
catch { return false }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEffectiveFormat(msg: MqttMessage): string {
|
function getEffectiveFormat(msg: MqttMessage): string {
|
||||||
const fmt = messageFormats.value[msg.id] || 'auto'
|
const fmt = messageFormats.value[msg.id] || 'auto'
|
||||||
if (fmt === 'auto') return isJson(msg.payload) ? 'json' : 'plain'
|
if (fmt === 'auto') return isJson(msg.payload) ? 'json' : 'plain'
|
||||||
@@ -64,18 +60,11 @@ function getEffectiveFormat(msg: MqttMessage): string {
|
|||||||
function formatPayload(msg: MqttMessage): string {
|
function formatPayload(msg: MqttMessage): string {
|
||||||
const fmt = getEffectiveFormat(msg)
|
const fmt = getEffectiveFormat(msg)
|
||||||
switch (fmt) {
|
switch (fmt) {
|
||||||
case 'json':
|
case 'json': return prettyJson(msg.payload)
|
||||||
try {
|
case 'hex': return bytesToHex(new TextEncoder().encode(msg.payload))
|
||||||
const parsed = JSON.parse(msg.payload)
|
case 'base64': return btoa(msg.payload)
|
||||||
return JSON.stringify(parsed, null, 2)
|
|
||||||
} catch { return msg.payload }
|
|
||||||
case 'hex':
|
|
||||||
return bytesToHex(new TextEncoder().encode(msg.payload))
|
|
||||||
case 'base64':
|
|
||||||
return btoa(msg.payload)
|
|
||||||
case 'plain':
|
case 'plain':
|
||||||
default:
|
default: return msg.payload
|
||||||
return msg.payload
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +169,7 @@ function formatTime(ts: number): string {
|
|||||||
v-if="expandedPayloads.has(msg.id)"
|
v-if="expandedPayloads.has(msg.id)"
|
||||||
:class="['payload-full', { 'is-json': getEffectiveFormat(msg) === 'json' }]"
|
:class="['payload-full', { 'is-json': getEffectiveFormat(msg) === 'json' }]"
|
||||||
>{{ formatPayload(msg) }}</pre>
|
>{{ formatPayload(msg) }}</pre>
|
||||||
<pre v-else class="payload-truncated">{{ msg.payload.substring(0, 200) }}{{ msg.payload.length > 200 ? '...' : '' }}</pre>
|
<pre v-else class="payload-truncated">{{ truncate(prettyJson(msg.payload), 200) }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import type { Preset } from '../../types/mqtt'
|
import type { Preset } from '../../types/mqtt'
|
||||||
import { usePresets } from '../../stores/usePresets'
|
import { usePresets } from '../../stores/usePresets'
|
||||||
|
import { prettyJson, truncate } from '../../utils/format'
|
||||||
import PresetForm from './PresetForm.vue'
|
import PresetForm from './PresetForm.vue'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@@ -76,7 +77,7 @@ const qosLabel = (qos: number) => `QoS ${qos}`
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Payload 预览" min-width="200">
|
<el-table-column label="Payload 预览" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span class="payload-preview">{{ row.payloadTemplate.substring(0, 50) }}{{ row.payloadTemplate.length > 50 ? '...' : '' }}</span>
|
<span class="payload-preview">{{ truncate(prettyJson(row.payloadTemplate), 50) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" fixed="right">
|
<el-table-column label="操作" width="160" fixed="right">
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Pretty-print a JSON string if it's valid JSON.
|
||||||
|
* Returns the original string if it's not valid JSON.
|
||||||
|
*/
|
||||||
|
export function prettyJson(str: string): string {
|
||||||
|
if (!str || str.trim() === '') return str
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(str)
|
||||||
|
return JSON.stringify(parsed, null, 2)
|
||||||
|
} catch {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is valid JSON.
|
||||||
|
*/
|
||||||
|
export function isJson(str: string): boolean {
|
||||||
|
try {
|
||||||
|
JSON.parse(str)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a string to a maximum length, adding an ellipsis if truncated.
|
||||||
|
*/
|
||||||
|
export function truncate(str: string, maxLen: number): string {
|
||||||
|
if (str.length <= maxLen) return str
|
||||||
|
return str.substring(0, maxLen) + '...'
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user