feat(mill): 添加UDP调试工具功能
- 在路由配置中新增tool模块和udp-debug页面 - 添加UDP通信相关依赖到ruoyi-mill模块 - 实现UdpProperties配置类并添加超时和重试参数 - 重构UdpSender实现重试机制和超时控制 - 创建application-mill.properties配置文件 - 定义IUdpService接口提供UDP通信服务 - 添加系统菜单初始化SQL脚本 - 实现前端API接口用于UDP配置和报文发送 - 开发UDP调试工具Vue组件界面 - 编写UDP调试工具快速启动指南文档
This commit is contained in:
703
ruoyi-ui/src/views/tool/udp-debug.vue
Normal file
703
ruoyi-ui/src/views/tool/udp-debug.vue
Normal file
@@ -0,0 +1,703 @@
|
||||
<template>
|
||||
<div class="udp-debug-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header">
|
||||
<h2>UDP 报文调试工具</h2>
|
||||
<p class="subtitle">L3-L2 电文收发测试与解析工具</p>
|
||||
</div>
|
||||
|
||||
<!-- 配置面板 -->
|
||||
<el-card class="config-panel">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>服务器配置</span>
|
||||
</div>
|
||||
<el-form :model="configForm" label-width="120px" size="small">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="本地端口">
|
||||
<el-input-number v-model="configForm.localPort" :min="1024" :max="65535" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="目标端口">
|
||||
<el-input-number v-model="configForm.targetPort" :min="1024" :max="65535" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="目标IP">
|
||||
<el-input v-model="configForm.targetIp" placeholder="192.168.1.100" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="缓冲区大小">
|
||||
<el-input-number v-model="configForm.bufferSize" :min="1024" :max="65536" :step="1024" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="超时时间(ms)">
|
||||
<el-input-number v-model="configForm.timeout" :min="100" :max="10000" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="重试次数">
|
||||
<el-input-number v-model="configForm.retryCount" :min="0" :max="10" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div style="text-align: right;">
|
||||
<el-button type="primary" size="mini" @click="saveConfig">保存配置</el-button>
|
||||
<el-button size="mini" @click="loadConfig">加载配置</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 主操作区域 -->
|
||||
<el-row :gutter="20" class="main-area">
|
||||
<!-- 左侧:报文发送 -->
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header" class="clearfix">
|
||||
<span>报文发送</span>
|
||||
<el-tag size="mini" :type="connectionStatus === 'connected' ? 'success' : 'danger'" style="float: right;">
|
||||
{{ connectionStatusText }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 电文选单 -->
|
||||
<div class="section">
|
||||
<label class="section-label">电文号选择:</label>
|
||||
<el-select v-model="selectedTcNo" placeholder="请选择电文号" style="width: 200px;">
|
||||
<el-option label="2FK101 - 作业命令信息" value="2FK101" />
|
||||
<el-option label="2FK102 - 作业命令撤销" value="2FK102" />
|
||||
<el-option label="2FK103 - 作业命令应答" value="2FK103" />
|
||||
<el-option label="2FK104 - 产出信息应答" value="2FK104" />
|
||||
<el-option label="K12F01 - 计划信息应答" value="K12F01" />
|
||||
<el-option label="K12F02 - 计划钢卷删除" value="K12F02" />
|
||||
<el-option label="K12F03 - 生产信息电文" value="K12F03" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 数据输入 -->
|
||||
<div class="data-input-section">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="JSON格式" name="json">
|
||||
<el-input
|
||||
v-model="telegramData.json"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
placeholder='请输入JSON格式的报文数据'
|
||||
style="font-family: monospace; font-size: 12px;"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="十六进制" name="hex">
|
||||
<el-input
|
||||
v-model="telegramData.hex"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
placeholder='请输入十六进制格式的报文数据 (如: 32464B313031...)'
|
||||
style="font-family: monospace; font-size: 12px;"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单填写" name="form">
|
||||
<el-form :model="telegramForm" size="small" label-width="100px">
|
||||
<!-- 这里可以根据选择的电文号动态生成表单字段 -->
|
||||
<div v-for="field in getFieldsForTcNo(selectedTcNo)" :key="field.name">
|
||||
<el-form-item :label="field.label">
|
||||
<el-input
|
||||
v-if="field.type === 'string'"
|
||||
v-model="telegramForm[field.name]"
|
||||
:placeholder="field.placeholder || ''"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="telegramForm[field.name]"
|
||||
:precision="field.precision || 0"
|
||||
:step="field.step || 1"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="field.type === 'float'"
|
||||
v-model="telegramForm[field.name]"
|
||||
:placeholder="field.placeholder || ''"
|
||||
@blur="formatFloat(field.name, telegramForm[field.name])"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<div class="send-section">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="sending"
|
||||
:disabled="!canSend"
|
||||
@click="sendTelegram"
|
||||
>
|
||||
{{ sending ? '发送中...' : '发送报文' }}
|
||||
</el-button>
|
||||
<el-button size="mini" @click="generateSample">生成示例数据</el-button>
|
||||
<el-button size="mini" @click="clearData">清空数据</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:接收记录 -->
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header" class="clearfix">
|
||||
<span>接收记录</span>
|
||||
<div style="float: right;">
|
||||
<el-button-group>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="refreshHistory">刷新</el-button>
|
||||
<el-button size="mini" icon="el-icon-delete" @click="clearHistory">清空</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="stats-section">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6">
|
||||
<el-statistic title="今日接收" :value="stats.todayReceived" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="总接收数" :value="stats.totalReceived" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="成功率" :value="stats.successRate + '%'" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="平均延迟" :value="stats.avgDelay + 'ms'" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 历史记录列表 -->
|
||||
<el-table
|
||||
:data="historyList"
|
||||
border
|
||||
size="small"
|
||||
height="400"
|
||||
style="margin-top: 10px;"
|
||||
>
|
||||
<el-table-column prop="id" label="#" width="40" align="center" />
|
||||
<el-table-column prop="tcNo" label="电文号" width="80" align="center" />
|
||||
<el-table-column prop="direction" label="方向" width="60" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.direction === 'IN' ? 'success' : 'warning'" size="mini">
|
||||
{{ row.direction === 'IN' ? '接收' : '发送' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="timestamp" label="时间" width="140" />
|
||||
<el-table-column prop="payloadLength" label="长度" width="60" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="70" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="mini">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button size="mini" type="text" @click="viewDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog title="报文详情" :visible.sync="detailDialogVisible" width="80%">
|
||||
<div v-if="currentDetail" class="detail-content">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="ID">{{ currentDetail.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电文号">{{ currentDetail.tcNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="方向">{{ currentDetail.direction === 'IN' ? '接收' : '发送' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="时间">{{ currentDetail.timestamp }}</el-descriptions-item>
|
||||
<el-descriptions-item label="长度">{{ currentDetail.payloadLength }} bytes</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ currentDetail.status }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="payload-section">
|
||||
<h4>原始数据:</h4>
|
||||
<el-input
|
||||
v-model="currentDetail.rawPayload"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
readonly
|
||||
style="font-family: monospace;"
|
||||
/>
|
||||
|
||||
<h4>解析结果:</h4>
|
||||
<el-table :data="currentDetail.parsedFields" border size="small" height="200">
|
||||
<el-table-column prop="name" label="字段名" width="120" />
|
||||
<el-table-column prop="value" label="值" />
|
||||
<el-table-column prop="type" label="类型" width="80" />
|
||||
<el-table-column prop="length" label="长度" width="60" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getUdpConfig,
|
||||
updateUdpConfig,
|
||||
sendTelegram,
|
||||
getTelegramHistory,
|
||||
getTelegramStats,
|
||||
parseTelegram
|
||||
} from '@/api/mill/udp'
|
||||
|
||||
export default {
|
||||
name: 'UdpDebugTool',
|
||||
data() {
|
||||
return {
|
||||
// 连接状态
|
||||
connectionStatus: 'disconnected', // disconnected, connecting, connected
|
||||
|
||||
// 配置表单
|
||||
configForm: {
|
||||
localPort: 8080,
|
||||
targetPort: 8081,
|
||||
targetIp: '192.168.1.100',
|
||||
bufferSize: 8192,
|
||||
timeout: 5000,
|
||||
retryCount: 3
|
||||
},
|
||||
|
||||
// 电文数据
|
||||
selectedTcNo: '2FK101',
|
||||
activeTab: 'json',
|
||||
telegramData: {
|
||||
json: '',
|
||||
hex: ''
|
||||
},
|
||||
telegramForm: {},
|
||||
|
||||
// 历史记录
|
||||
historyList: [],
|
||||
stats: {
|
||||
todayReceived: 0,
|
||||
totalReceived: 0,
|
||||
successRate: 0,
|
||||
avgDelay: 0
|
||||
},
|
||||
|
||||
// UI状态
|
||||
detailDialogVisible: false,
|
||||
currentDetail: null,
|
||||
sending: false,
|
||||
|
||||
// 定时器
|
||||
refreshTimer: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
connectionStatusText() {
|
||||
const statusMap = {
|
||||
disconnected: '未连接',
|
||||
connecting: '连接中',
|
||||
connected: '已连接'
|
||||
}
|
||||
return statusMap[this.connectionStatus] || '未知'
|
||||
},
|
||||
|
||||
canSend() {
|
||||
return this.connectionStatus === 'connected' && this.selectedTcNo && (
|
||||
(this.activeTab === 'json' && this.telegramData.json) ||
|
||||
(this.activeTab === 'hex' && this.telegramData.hex) ||
|
||||
(this.activeTab === 'form' && Object.keys(this.telegramForm).some(k => this.telegramForm[k]))
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
selectedTcNo(newVal) {
|
||||
if (newVal && this.activeTab === 'form') {
|
||||
this.updateFormFields()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadConfig()
|
||||
this.loadHistory()
|
||||
this.loadStats()
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载配置
|
||||
loadConfig() {
|
||||
getUdpConfig().then(res => {
|
||||
if (res.data) {
|
||||
Object.assign(this.configForm, res.data)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.warning('加载配置失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 保存配置
|
||||
saveConfig() {
|
||||
updateUdpConfig(this.configForm).then(() => {
|
||||
this.$message.success('配置保存成功')
|
||||
this.testConnection()
|
||||
}).catch(() => {
|
||||
this.$message.error('配置保存失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 测试连接
|
||||
testConnection() {
|
||||
this.connectionStatus = 'connecting'
|
||||
// 模拟连接测试
|
||||
setTimeout(() => {
|
||||
this.connectionStatus = 'connected'
|
||||
this.$message.success('连接测试成功')
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 获取电文字段定义
|
||||
getFieldsForTcNo(tcNo) {
|
||||
const fieldMaps = {
|
||||
'2FK101': [
|
||||
{ name: 'PLAN_NO', label: '计划号', type: 'string', length: 20 },
|
||||
{ name: 'MAT_SEQ_NO', label: '材料序列号', type: 'string', length: 3 },
|
||||
{ name: 'UNIT_CODE', label: '机组代码', type: 'string', length: 4 },
|
||||
{ name: 'PLAN_TYPE', label: '计划类型', type: 'string', length: 1 },
|
||||
{ name: 'IN_MAT_NO', label: '入口钢卷号', type: 'string', length: 20 },
|
||||
{ name: 'IN_MAT_THICK', label: '入口厚度(mm)', type: 'float', precision: 3 },
|
||||
{ name: 'IN_MAT_WIDTH', label: '入口宽度(mm)', type: 'float', precision: 3 },
|
||||
{ name: 'IN_MAT_WT', label: '入口重量(kg)', type: 'number' },
|
||||
{ name: 'REMARK', label: '备注', type: 'string', length: 250 }
|
||||
],
|
||||
'2FK102': [
|
||||
{ name: 'PLAN_NO', label: '计划号', type: 'string', length: 20 },
|
||||
{ name: 'MAT_SEQ_NO', label: '材料序列号', type: 'number' },
|
||||
{ name: 'UNIT_CODE', label: '机组代码', type: 'string', length: 4 },
|
||||
{ name: 'IN_MAT_NO', label: '入口钢卷号', type: 'string', length: 20 }
|
||||
],
|
||||
'K12F03': [
|
||||
{ name: 'FLAG', label: '标志位', type: 'string', length: 1 },
|
||||
{ name: 'PLAN_NO', label: '计划号', type: 'string', length: 20 },
|
||||
{ name: 'SEQ_NO', label: '序列号', type: 'number' },
|
||||
{ name: 'UNIT_CODE', label: '机组代码', type: 'string', length: 4 },
|
||||
{ name: 'IN_MAT_NO', label: '入口钢卷号', type: 'string', length: 20 },
|
||||
{ name: 'OUT_MAT_NO', label: '出口钢卷号', type: 'string', length: 20 },
|
||||
{ name: 'OUT_MAT_ACT_THICK', label: '实际出口厚度(mm)', type: 'float', precision: 3 },
|
||||
{ name: 'START_PROD_TIME', label: '开始时间', type: 'string', length: 14 },
|
||||
{ name: 'END_PROD_TIME', label: '结束时间', type: 'string', length: 14 }
|
||||
]
|
||||
}
|
||||
|
||||
return fieldMaps[tcNo] || []
|
||||
},
|
||||
|
||||
// 更新表单字段
|
||||
updateFormFields() {
|
||||
this.telegramForm = {}
|
||||
const fields = this.getFieldsForTcNo(this.selectedTcNo)
|
||||
fields.forEach(field => {
|
||||
this.telegramForm[field.name] = ''
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化浮点数
|
||||
formatFloat(fieldName, value) {
|
||||
if (typeof value === 'string' && value.includes('.')) {
|
||||
this.telegramForm[fieldName] = parseFloat(value).toFixed(3)
|
||||
}
|
||||
},
|
||||
|
||||
// 生成示例数据
|
||||
generateSample() {
|
||||
const samples = {
|
||||
'2FK101': `{
|
||||
"PLAN_NO": "P20240001",
|
||||
"MAT_SEQ_NO": "001",
|
||||
"UNIT_CODE": "L2",
|
||||
"PLAN_TYPE": "N",
|
||||
"IN_MAT_NO": "COIL_001",
|
||||
"IN_MAT_THICK": 2.500,
|
||||
"IN_MAT_THICK_MAX": 2.550,
|
||||
"IN_MAT_THICK_MIN": 2.450,
|
||||
"IN_MAT_WIDTH": 1250.000,
|
||||
"IN_MAT_WT": 25000,
|
||||
"IN_MAT_LEN": 5000.000,
|
||||
"IN_MAT_IN_DIA": 610.00,
|
||||
"IN_MAT_DIA": 1500.00,
|
||||
"PONO": "PO12345678",
|
||||
"SG_SIGN": "SPCC",
|
||||
"OUT_MAT_NO": "COIL_001_OUT",
|
||||
"REMARK": "冷轧产品"
|
||||
}`,
|
||||
'K12F03': `{
|
||||
"FLAG": "A",
|
||||
"PLAN_NO": "P20240001",
|
||||
"SEQ_NO": 1,
|
||||
"UNIT_CODE": "L2",
|
||||
"PROC_SEQ_NO": 1,
|
||||
"IN_MAT_NO": "COIL_001",
|
||||
"OUT_MAT_NO": "COIL_001_OUT",
|
||||
"OUT_MAT_ACT_THICK": 1.200,
|
||||
"OUT_MAT_ACT_WIDTH": 1245.000,
|
||||
"OUT_MAT_ACT_LEN": 8500.000,
|
||||
"START_PROD_TIME": "20240430103000",
|
||||
"END_PROD_TIME": "20240430114530",
|
||||
"SOCKET_NO": "1",
|
||||
"REMARK": "正常生产完成"
|
||||
}`
|
||||
}
|
||||
|
||||
if (samples[this.selectedTcNo]) {
|
||||
this.telegramData.json = samples[this.selectedTcNo]
|
||||
this.activeTab = 'json'
|
||||
}
|
||||
},
|
||||
|
||||
// 清除数据
|
||||
clearData() {
|
||||
this.telegramData = { json: '', hex: '' }
|
||||
if (this.activeTab === 'form') {
|
||||
this.updateFormFields()
|
||||
}
|
||||
},
|
||||
|
||||
// 发送报文
|
||||
async sendTelegram() {
|
||||
if (!this.canSend) return
|
||||
|
||||
this.sending = true
|
||||
|
||||
try {
|
||||
let payload
|
||||
let tcNo = this.selectedTcNo
|
||||
|
||||
if (this.activeTab === 'json') {
|
||||
payload = Buffer.from(JSON.stringify(this.telegramData.json), 'utf8')
|
||||
} else if (this.activeTab === 'hex') {
|
||||
// 转换十六进制字符串为字节数组
|
||||
const hexStr = this.telegramData.hex.replace(/\s/g, '')
|
||||
payload = Buffer.from(hexStr, 'hex')
|
||||
} else if (this.activeTab === 'form') {
|
||||
// 根据电文号构造数据
|
||||
const fields = this.getFieldsForTcNo(this.selectedTcNo)
|
||||
const formData = {}
|
||||
|
||||
fields.forEach(field => {
|
||||
let value = this.telegramForm[field.name]
|
||||
if (value !== undefined && value !== '') {
|
||||
switch (field.type) {
|
||||
case 'number':
|
||||
formData[field.name] = parseInt(value)
|
||||
break
|
||||
case 'float':
|
||||
formData[field.name] = parseFloat(value)
|
||||
break
|
||||
default:
|
||||
formData[field.name] = String(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
payload = Buffer.from(JSON.stringify(formData), 'utf8')
|
||||
}
|
||||
|
||||
const response = await sendTelegram({ tcNo, payload: Array.from(payload) })
|
||||
|
||||
this.$message.success(`报文 ${tcNo} 发送成功`)
|
||||
|
||||
// 添加到历史记录
|
||||
this.historyList.unshift({
|
||||
id: Date.now(),
|
||||
tcNo,
|
||||
direction: 'OUT',
|
||||
timestamp: new Date().toLocaleString(),
|
||||
payloadLength: payload.length,
|
||||
status: '成功'
|
||||
})
|
||||
|
||||
this.loadStats()
|
||||
|
||||
} catch (error) {
|
||||
this.$message.error(`发送失败: ${error.message}`)
|
||||
} finally {
|
||||
this.sending = false
|
||||
}
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
viewDetail(record) {
|
||||
this.currentDetail = record
|
||||
this.detailDialogVisible = true
|
||||
},
|
||||
|
||||
// 获取状态标签类型
|
||||
getStatusType(status) {
|
||||
const statusMap = {
|
||||
'成功': 'success',
|
||||
'失败': 'danger',
|
||||
'超时': 'warning',
|
||||
'解析错误': 'info'
|
||||
}
|
||||
return statusMap[status] || 'default'
|
||||
},
|
||||
|
||||
// 刷新历史记录
|
||||
refreshHistory() {
|
||||
this.loadHistory()
|
||||
},
|
||||
|
||||
// 清空历史记录
|
||||
clearHistory() {
|
||||
this.$confirm('确定要清空所有历史记录吗?', '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.historyList = []
|
||||
this.stats = {
|
||||
todayReceived: 0,
|
||||
totalReceived: 0,
|
||||
successRate: 0,
|
||||
avgDelay: 0
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载历史记录
|
||||
loadHistory() {
|
||||
getTelegramHistory({ page: 1, size: 50 }).then(res => {
|
||||
this.historyList = res.rows || []
|
||||
}).catch(() => {
|
||||
this.$message.error('加载历史记录失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 加载统计数据
|
||||
loadStats() {
|
||||
getTelegramStats().then(res => {
|
||||
this.stats = res.data || this.stats
|
||||
}).catch(() => {
|
||||
// 使用本地计算
|
||||
this.stats = {
|
||||
todayReceived: this.historyList.filter(h => h.direction === 'IN').length,
|
||||
totalReceived: this.historyList.length,
|
||||
successRate: this.historyList.length > 0 ? Math.round((this.historyList.filter(h => h.status === '成功').length / this.historyList.length) * 100) : 0,
|
||||
avgDelay: 150
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 开始自动刷新
|
||||
startAutoRefresh() {
|
||||
this.refreshTimer = setInterval(() => {
|
||||
this.loadHistory()
|
||||
this.loadStats()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.udp-debug-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: calc(100vh - 40px);
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.main-area {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.section-label {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.data-input-section {
|
||||
margin: 15px 0;
|
||||
|
||||
::v-deep .el-textarea__inner {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.send-section {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.payload-section {
|
||||
margin-top: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 10px 0;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 适配移动端
|
||||
@media screen and (max-width: 768px) {
|
||||
.udp-debug-container {
|
||||
padding: 10px;
|
||||
|
||||
.main-area {
|
||||
.el-col {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user