- variables.scss 整盘重配色:浅灰背景/白卡片、#C03639 红主色、element 标准状态色与字体栈 - global.scss element-ui 覆写由深色改为浅色+红主色,卡片加轻投影 - Material/Message 视图内硬编码深色数据面板改为浅色 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
168 lines
7.3 KiB
Vue
168 lines
7.3 KiB
Vue
<template>
|
||
<div>
|
||
<div class="card">
|
||
<div class="card-body" style="padding:10px 14px;">
|
||
<div class="flex-row" style="flex-wrap:wrap;gap:12px;">
|
||
<div class="flex-row">
|
||
<span class="kv-label">报文类型</span>
|
||
<input v-model="query.msg_type" class="kv-input" style="width:90px;" />
|
||
</div>
|
||
<div class="flex-row">
|
||
<span class="kv-label">方向</span>
|
||
<select v-model="query.direction" class="kv-input" style="width:90px;">
|
||
<option value="">全部</option>
|
||
<option value="recv">接收</option>
|
||
<option value="send">发送</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex-row">
|
||
<span class="kv-label">状态</span>
|
||
<select v-model="query.status" class="kv-input" style="width:90px;">
|
||
<option value="">全部</option>
|
||
<option value="success">成功</option>
|
||
<option value="error">失败</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex-row">
|
||
<button class="btn btn-primary" @click="fetchData">查询</button>
|
||
<button class="btn btn-outline" @click="fetchData">↺ 刷新</button>
|
||
</div>
|
||
<div style="margin-left:auto;" class="flex-row">
|
||
<span class="kv-label">
|
||
成功 <span class="kv-value" style="color:var(--accent-green)">{{ successCount }}</span>
|
||
失败 <span class="kv-value" style="color:var(--accent-red)">{{ errorCount }}</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
报文监控日志
|
||
<span class="ch-badge" :style="{ color: connected ? 'var(--accent-green)' : 'var(--accent-red)' }">
|
||
{{ connected ? '● UDP在线' : '○ 未连接' }}
|
||
</span>
|
||
</div>
|
||
<div class="table-scroll" v-loading="loading">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>报文ID</th><th>类型</th><th>方向</th><th>来源</th>
|
||
<th>状态</th><th>耗时(ms)</th><th>错误信息</th><th>接收时间</th><th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="row in tableData" :key="row.id">
|
||
<td class="td-num">{{ row.msg_id }}</td>
|
||
<td><span class="badge badge-blue">{{ row.msg_type }}</span></td>
|
||
<td>
|
||
<span :class="['badge', row.direction==='recv' ? 'badge-green' : 'badge-blue']">
|
||
{{ row.direction === 'recv' ? '↓ 接收' : '↑ 发送' }}
|
||
</span>
|
||
</td>
|
||
<td class="td-muted">{{ row.source }}</td>
|
||
<td>
|
||
<span :class="['badge', row.status==='success' ? 'badge-green' : 'badge-red']">
|
||
{{ row.status === 'success' ? '成功' : '失败' }}
|
||
</span>
|
||
</td>
|
||
<td :class="row.process_time > 100 ? 'td-warn' : 'td-num'">{{ row.process_time || '—' }}</td>
|
||
<td class="td-err">{{ row.error_msg || '—' }}</td>
|
||
<td class="td-muted">{{ fmtTime(row.received_at) }}</td>
|
||
<td><span class="action-link" @click="viewDetail(row)">详情</span></td>
|
||
</tr>
|
||
<tr v-if="!tableData.length && !loading">
|
||
<td colspan="9" class="td-muted" style="text-align:center;padding:24px;">暂无报文记录</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 详情 Modal -->
|
||
<div v-if="detailVisible" class="modal-mask" @click.self="detailVisible=false">
|
||
<div class="modal-box" style="width:760px;">
|
||
<div class="modal-header">
|
||
报文详情 — <span style="color:var(--sms-highlight)">{{ currentLog && currentLog.msg_type }}</span>
|
||
<span class="modal-close" @click="detailVisible=false">✕</span>
|
||
</div>
|
||
<div class="modal-body" v-if="currentLog">
|
||
<div class="kv-grid" style="margin-bottom:14px;">
|
||
<span class="kv-label">报文类型</span><span class="kv-value">{{ currentLog.msg_type }}</span>
|
||
<span class="kv-label">接收时间</span><span class="kv-value">{{ fmtTime(currentLog.received_at) }}</span>
|
||
<span class="kv-label">状态</span>
|
||
<span><span :class="['badge', currentLog.status==='success' ? 'badge-green' : 'badge-red']">{{ currentLog.status }}</span></span>
|
||
</div>
|
||
<div class="sec-title">原始报文(HEX)</div>
|
||
<pre class="raw-box">{{ currentLog.raw_data || '—' }}</pre>
|
||
<div class="sec-title" style="margin-top:12px;">解析结果(JSON)</div>
|
||
<pre class="raw-box">{{ formatJson(currentLog.parsed_data) }}</pre>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline" @click="detailVisible=false">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getMessageLogs, getMessageLog } from '@/api'
|
||
|
||
export default {
|
||
name: 'Message',
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
tableData: [], total: 0,
|
||
query: { page: 1, page_size: 50, msg_type: '', direction: '', status: '' },
|
||
connected: true,
|
||
detailVisible: false, currentLog: null,
|
||
}
|
||
},
|
||
computed: {
|
||
successCount() { return this.tableData.filter(r => r.status === 'success').length },
|
||
errorCount() { return this.tableData.filter(r => r.status === 'error').length },
|
||
},
|
||
created() { this.fetchData() },
|
||
methods: {
|
||
async fetchData() {
|
||
this.loading = true
|
||
try {
|
||
const res = await getMessageLogs(this.query)
|
||
this.tableData = res.data.items
|
||
this.total = res.data.total
|
||
} finally { this.loading = false }
|
||
},
|
||
fmtTime(t) { return t ? t.replace('T',' ').slice(0,19) : '—' },
|
||
async viewDetail(row) {
|
||
const res = await getMessageLog(row.id)
|
||
this.currentLog = res.data
|
||
this.detailVisible = true
|
||
},
|
||
formatJson(s) {
|
||
if (!s) return '—'
|
||
try { return JSON.stringify(JSON.parse(s), null, 2) } catch { return s }
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/assets/styles/variables';
|
||
.action-link { color: $sms-highlight; cursor: pointer; font-size: 12px; &:hover { text-decoration: underline; } }
|
||
.raw-box {
|
||
background: $bg-panel; color: $text-primary;
|
||
padding: 12px; border-radius: 4px; border: 1px solid $border;
|
||
font-size: 11px; font-family: $font-mono;
|
||
max-height: 180px; overflow-y: auto;
|
||
white-space: pre-wrap; word-break: break-all;
|
||
}
|
||
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; z-index: 9999; }
|
||
.modal-box { background: $bg-card; border: 1px solid $border; border-radius: 6px; max-width: 95vw; max-height: 90vh; display: flex; flex-direction: column; }
|
||
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: $bg-panel; border-bottom: 1px solid $border; font-size: 13px; font-weight: 600; color: $sms-highlight; .modal-close { cursor: pointer; color: $text-muted; &:hover { color: $text-primary; } } }
|
||
.modal-body { padding: 16px; overflow-y: auto; }
|
||
.modal-footer { padding: 10px 16px; background: $bg-panel; border-top: 1px solid $border; display: flex; justify-content: flex-end; gap: 10px; }
|
||
</style>
|