feat: 移除PDI和订单号字段,新增设备巡检模块
- 从物料跟踪页面移除订单号列和表单字段 - 从导航菜单移除PDI管理,添加设备巡检 - 新增InspectionLocation和InspectionRecord后端模型和API - 新增设备巡检前端页面(左侧点位列表,右侧设备和历史记录)
This commit is contained in:
167
frontend/src/views/Message.vue
Normal file
167
frontend/src/views/Message.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<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;" placeholder="PC01..." />
|
||||
</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: #0a0f18; color: #d4d4d4;
|
||||
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>
|
||||
Reference in New Issue
Block a user