- 将数据源参数从大写改为首字母大写以保持一致性 - 重构带钢位置进度计算为计算属性 - 扩展并优化各段指标配置,增加更多监控项 - 调整网格布局为8列以适应新增指标 - 优化WebSocket数据处理逻辑,移除冗余代码
772 lines
27 KiB
Vue
772 lines
27 KiB
Vue
<template>
|
||
<div class="realtime-monitoring">
|
||
<!-- 替换uniapp scroll-view为普通div + 滚动样式 -->
|
||
<div class="scroll-container" v-if="currentTab === 1" ref="scrollContainer">
|
||
<!-- 顶部状态栏 -->
|
||
<div class="status-bar">
|
||
<div class="status-item">
|
||
<span class="status-label">网络状态</span>
|
||
<span class="status-value" :class="isConnected ? 'status-通畅' : 'status-异常'">
|
||
{{ isConnected ? '已连接' : '未连接' }}
|
||
</span>
|
||
</div>
|
||
<div class="status-divider"></div>
|
||
<div class="status-item">
|
||
<span class="status-label">更新</span>
|
||
<span class="status-value status-time">{{ lastUpdateTime }}</span>
|
||
</div>
|
||
<div class="status-divider"></div>
|
||
<div class="status-item">
|
||
<span class="status-label">设备数</span>
|
||
<span class="status-value">{{ deviceDefs.length }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 带钢位置和速度模块 -->
|
||
<div class="section">
|
||
<div class="section-title">带钢状态</div>
|
||
<div class="strip-status-grid">
|
||
<!-- 带钢位置卡片 -->
|
||
<div class="strip-status-card">
|
||
<span class="strip-status-label">带钢位置</span>
|
||
<span class="strip-status-value">{{ getRealtimeValueBySource('Entry', 'stripLocation') }}</span>
|
||
<span class="strip-status-unit">m</span>
|
||
<div class="progress-container">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" :style="{ width: stripLocationProgress + '%' }"></div>
|
||
</div>
|
||
<span class="progress-text">{{ stripLocationProgress.toFixed(1) }}%</span>
|
||
</div>
|
||
</div>
|
||
<!-- 带钢速度卡片 -->
|
||
<div class="strip-status-card">
|
||
<span class="strip-status-label">带钢速度</span>
|
||
<span class="strip-status-value">{{ getRealtimeValueBySource('Entry', 'stripSpeed') }}</span>
|
||
<span class="strip-status-unit">m/min</span>
|
||
</div>
|
||
</div>
|
||
<!-- 带钢速度折线图:替换为ECharts容器 -->
|
||
<div class="chart-box" ref="stripSpeedChart"></div>
|
||
</div>
|
||
|
||
<!-- 实时数据卡片 + 统计图 -->
|
||
<div class="section">
|
||
<div class="section-title">入口段</div>
|
||
<div class="metrics-grid-3">
|
||
<div class="metric-box" v-for="it in entryMetrics" :key="'entry_' + it.field">
|
||
<span class="metric-name">{{ it.label || getFieldLabel(it.field) }}</span>
|
||
<span class="metric-value">{{ getRealtimeValueBySource('Entry', it.field) }}</span>
|
||
<span class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="chart-box" ref="entryChart"></div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">退火炉段</div>
|
||
<div class="metrics-grid-3">
|
||
<div class="metric-box" v-for="it in furnaceMetrics" :key="'furnace_' + it.actualField">
|
||
<span class="metric-name">{{ it.label || getFieldLabel(it.actualField) }}</span>
|
||
<span class="metric-value">{{ getRealtimeValueBySource('Furnace', it.actualField) }}</span>
|
||
<span class="metric-unit">{{ it.unit || getFieldUnit(it.actualField) }}</span>
|
||
<span class="metric-set-value" v-if="it.setField">
|
||
设定: {{ getRealtimeValueBySource('Furnace', it.setField) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">后处理/涂层段</div>
|
||
<div class="metrics-grid-3">
|
||
<div class="metric-box" v-for="it in coatMetrics" :key="'coat_' + it.field">
|
||
<span class="metric-name">{{ it.label || getFieldLabel(it.field) }}</span>
|
||
<span class="metric-value">{{ getRealtimeValueBySource('Coat', it.field) }}</span>
|
||
<span class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="chart-box" ref="coatChart"></div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">出口段</div>
|
||
<div class="metrics-grid-3">
|
||
<div class="metric-box" v-for="it in exitMetrics" :key="'exit_' + it.field">
|
||
<span class="metric-name">{{ it.label || getFieldLabel(it.field) }}</span>
|
||
<span class="metric-value">{{ getRealtimeValueBySource('Exit', it.field) }}</span>
|
||
<span class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="chart-box" ref="exitChart"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
// 引入ECharts(Vue2常用图表库,替换uniapp的qiun-data-charts)
|
||
import * as echarts from 'echarts'
|
||
import WebSocketManager from '@/utils/websocketManager';
|
||
import { listDeviceEnumAll } from '@/api/pocket/deviceEnum'
|
||
import { getDeviceFieldMetaAll } from '@/api/pocket/deviceFieldMeta'
|
||
import { listDeviceSnapshotLatest } from '@/api/pocket/deviceSnapshot'
|
||
// 模拟API请求(实际项目替换为真实接口)
|
||
const mockApi = {
|
||
listDeviceEnumAll: () => Promise.resolve({ data: [] }),
|
||
getDeviceFieldMetaAll: () => Promise.resolve({ data: {} }),
|
||
listDeviceSnapshotLatest: ({ limit, deviceCode }) => Promise.resolve({ data: [] })
|
||
}
|
||
|
||
export default {
|
||
name: 'RealTimeMonitoring',
|
||
// Vue2必须有data函数,返回响应式数据
|
||
data() {
|
||
return {
|
||
currentTab: 1, // 默认显示标签1
|
||
isConnected: false, // 网络连接状态
|
||
lastUpdateTime: '--', // 最后更新时间
|
||
deviceDefs: [], // 设备定义列表
|
||
fieldMeta: {}, // 字段元数据
|
||
isDestroyed: false, // 组件销毁标志
|
||
chartMaxPoints: 30, // 图表最大数据点
|
||
chartSeries: { // 图表系列数据
|
||
entry: { time: [], stripSpeed: [], tensionPorBr1: [], tensionBr1Br2: [], tensionBr2Br3: [] },
|
||
furnace: { time: [], jcf1FurnaceTemperatureActual: [], jcf2FurnaceTemperatureActual: [], lbzFurnaceTemperatureActual: [], lthFurnaceTemperatureActual: [], nof1FurnaceTemperatureActual: [], nof2FurnaceTemperatureActual: [], nof3FurnaceTemperatureActual: [], nof4FurnaceTemperatureActual: [], nof5FurnaceTemperatureActual: [], phFurnaceTemperatureActual: [], rtf1FurnaceTemperatureActual: [], rtf2FurnaceTemperatureActual: [], sfFurnaceTemperatureActual: [], tdsFurnaceTemperatureActual: [], potTemperature: [] },
|
||
coat: { time: [], avrCoatingWeightTop: [], avrCoatingWeightBottom: [], airKnifePressure: [], stripSpeedTmExit: [] },
|
||
exit: { time: [], tensionBr8Br9: [], tensionBr9Tr: [], speedExitSection: [] },
|
||
stripSpeed: { time: [], stripSpeed: [] }
|
||
},
|
||
latestMeasure: null, // 最新测量数据
|
||
socketDataAppended: false, // socket数据是否已追加
|
||
// 各段指标配置
|
||
entryMetrics: [
|
||
{ field: 'stripLocation', label: '带钢位置', unit: 'm' },
|
||
{ field: 'stripSpeed', label: '带钢速度', unit: 'm/min' },
|
||
{ field: 'tensionPorBr1', label: '张力POR-BR1', unit: 'kN' },
|
||
{ field: 'tensionBr1Br2', label: '张力BR1-BR2', unit: 'kN' },
|
||
{ field: 'tensionBr2Br3', label: '张力BR2-BR3', unit: 'kN' },
|
||
|
||
],
|
||
furnaceMetrics: [
|
||
{ actualField: 'jcf1FurnaceTemperatureActual', setField: 'jcf1FurnaceTemperatureSet', label: 'JCF1炉温', unit: '℃' },
|
||
{ actualField: 'jcf2FurnaceTemperatureActual', setField: 'jcf2FurnaceTemperatureSet', label: 'JCF2炉温', unit: '℃' },
|
||
{ actualField: 'lbzFurnaceTemperatureActual', setField: 'lbzFurnaceTemperatureSet', label: 'LBZ炉温', unit: '℃' },
|
||
{ actualField: 'lthFurnaceTemperatureActual', setField: 'lthFurnaceTemperatureSet', label: 'LTH炉温', unit: '℃' },
|
||
{ actualField: 'nof1FurnaceTemperatureActual', setField: 'nof1FurnaceTemperatureSet', label: 'NOF1炉温', unit: '℃' },
|
||
{ actualField: 'nof2FurnaceTemperatureActual', setField: 'nof2FurnaceTemperatureSet', label: 'NOF2炉温', unit: '℃' },
|
||
{ actualField: 'nof3FurnaceTemperatureActual', setField: 'nof3FurnaceTemperatureSet', label: 'NOF3炉温', unit: '℃' },
|
||
{ actualField: 'nof4FurnaceTemperatureActual', setField: 'nof4FurnaceTemperatureSet', label: 'NOF4炉温', unit: '℃' },
|
||
{ actualField: 'nof5FurnaceTemperatureActual', setField: 'nof5FurnaceTemperatureSet', label: 'NOF5炉温', unit: '℃' },
|
||
{ actualField: 'phFurnaceTemperatureActual', setField: null, label: 'PH炉温', unit: '℃' },
|
||
{ actualField: 'rtf1FurnaceTemperatureActual', setField: 'rtf1FurnaceTemperatureSet', label: 'RTF1炉温', unit: '℃' },
|
||
{ actualField: 'rtf2FurnaceTemperatureActual', setField: 'rtf2FurnaceTemperatureSet', label: 'RTF2炉温', unit: '℃' },
|
||
{ actualField: 'sfFurnaceTemperatureActual', setField: 'sfFurnaceTemperatureSet', label: 'SF炉温', unit: '℃' },
|
||
{ actualField: 'tdsFurnaceTemperatureActual', setField: 'tdsFurnaceTemperatureSet', label: 'TDS炉温', unit: '℃' },
|
||
],
|
||
coatMetrics: [
|
||
{ field: 'avrCoatingWeightTop', label: '上层平均涂层重量', unit: 'g/m²' },
|
||
{ field: 'avrCoatingWeightBottom', label: '下层平均涂层重量', unit: 'g/m²' },
|
||
{ field: 'airKnifePressure', label: '气刀压力', unit: '' },
|
||
{ field: 'stripSpeedTmExit', label: 'TM出口速度', unit: 'm/min' }
|
||
],
|
||
exitMetrics: [
|
||
{ field: 'alkaliConcentration', label: '碱液浓度', unit: '' },
|
||
{ field: 'alkaliTemperature', label: '碱液温度', unit: '℃' },
|
||
{ field: 'celCapacity', label: '电池容量', unit: '' },
|
||
{ field: 'celLength', label: '电池长度', unit: 'm' },
|
||
{ field: 'cleaningCurrent', label: '清洗电流', unit: 'A' },
|
||
{ field: 'cleaningVoltage', label: '清洗电压', unit: 'V' },
|
||
{ field: 'dryingTemperature', label: '干燥温度', unit: '℃' },
|
||
{ field: 'entryCoilId', label: '入口卷号', unit: '' },
|
||
{ field: 'hotAirFlow', label: '热风流量', unit: '' },
|
||
{ field: 'hotAirPressure', label: '热风压力', unit: '' },
|
||
{ field: 'payOffReelNumber', label: '放卷卷号', unit: '' },
|
||
{ field: 'rinseConductivity', label: '冲洗电导率', unit: '' },
|
||
{ field: 'rinseTemperature', label: '冲洗温度', unit: '℃' },
|
||
{ field: 'stripLocation', label: '带钢位置', unit: 'm' },
|
||
{ field: 'stripSpeed', label: '带钢速度', unit: 'm/min' },
|
||
{ field: 'tensionBr1Br2', label: '张力 BR1-BR2', unit: 'daN' },
|
||
{ field: 'tensionBr2Br3', label: '张力 BR2-BR3', unit: 'daN' },
|
||
{ field: 'tensionPorBr1', label: '入口张力 POR-BR1', unit: 'daN' },
|
||
{ field: 'tensionBr8Br9', label: '张力 BR8-BR9', unit: 'daN' },
|
||
{ field: 'tensionBr9Tr', label: '张力 BR9-TR', unit: 'daN' },
|
||
{ field: 'speedExitSection', label: '出口速度', unit: 'm/min' },
|
||
{ field: 'coilLength', label: '钢卷长度', unit: 'm' }
|
||
],
|
||
// ECharts实例缓存
|
||
chartInstances: {
|
||
stripSpeed: null,
|
||
entry: null,
|
||
coat: null,
|
||
exit: null
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
// 带钢位置进度(位置/3000)
|
||
stripLocationProgress() {
|
||
if (this.isDestroyed) return 0
|
||
const m = this.latestMeasure || {}
|
||
const entry = m.appMeasureEntryMessage || {}
|
||
const location = entry.stripLocation !== null && entry.stripLocation !== undefined
|
||
? parseFloat(entry.stripLocation) || 0
|
||
: 0
|
||
const maxLocation = 3000
|
||
const progress = (location / maxLocation) * 100
|
||
return Math.min(Math.max(progress, 0), 100)
|
||
},
|
||
},
|
||
// Vue2生命周期钩子
|
||
created() {
|
||
// 组件创建时加载初始数据
|
||
this.loadDeviceDefs()
|
||
this.loadFieldMeta()
|
||
this.loadHistoryForCharts()
|
||
// 初始化WebSocket连接
|
||
this.initSocket()
|
||
},
|
||
mounted() {
|
||
// DOM挂载后初始化图表
|
||
this.$nextTick(() => {
|
||
this.initCharts()
|
||
})
|
||
},
|
||
beforeDestroy() {
|
||
// 组件销毁前清理资源
|
||
this.isDestroyed = true
|
||
// 关闭WebSocket连接
|
||
if (this.socket) {
|
||
this.socket.disconnectAll()
|
||
}
|
||
// 销毁ECharts实例
|
||
Object.values(this.chartInstances).forEach(instance => {
|
||
if (instance) {
|
||
instance.dispose()
|
||
}
|
||
})
|
||
// 执行socket清理逻辑
|
||
if (this._clearSocketFlag) {
|
||
this._clearSocketFlag()
|
||
}
|
||
},
|
||
methods: {
|
||
// 加载设备定义
|
||
async loadDeviceDefs() {
|
||
try {
|
||
const res = await listDeviceEnumAll()
|
||
if (this.isDestroyed) return
|
||
this.deviceDefs = res.data || []
|
||
} catch (e) {
|
||
console.error('加载设备定义失败:', e)
|
||
}
|
||
},
|
||
// 加载字段元数据
|
||
async loadFieldMeta() {
|
||
try {
|
||
const res = await getDeviceFieldMetaAll()
|
||
if (this.isDestroyed) return
|
||
this.fieldMeta = res.data || {}
|
||
} catch (e) {
|
||
console.error('加载字段元数据失败:', e)
|
||
}
|
||
},
|
||
// 加载图表历史数据
|
||
async loadHistoryForCharts() {
|
||
try {
|
||
// 重置图表系列(保留带钢速度数据)
|
||
this.chartSeries = {
|
||
entry: { time: [], stripSpeed: [], tensionPorBr1: [], tensionBr1Br2: [], tensionBr2Br3: [] },
|
||
furnace: { time: [], jcf1FurnaceTemperatureActual: [], jcf2FurnaceTemperatureActual: [], lbzFurnaceTemperatureActual: [], lthFurnaceTemperatureActual: [], nof1FurnaceTemperatureActual: [], nof2FurnaceTemperatureActual: [], nof3FurnaceTemperatureActual: [], nof4FurnaceTemperatureActual: [], nof5FurnaceTemperatureActual: [], phFurnaceTemperatureActual: [], rtf1FurnaceTemperatureActual: [], rtf2FurnaceTemperatureActual: [], sfFurnaceTemperatureActual: [], tdsFurnaceTemperatureActual: [], potTemperature: [] },
|
||
coat: { time: [], avrCoatingWeightTop: [], avrCoatingWeightBottom: [], airKnifePressure: [], stripSpeedTmExit: [] },
|
||
exit: { time: [], tensionBr8Br9: [], tensionBr9Tr: [], speedExitSection: [] },
|
||
stripSpeed: this.chartSeries.stripSpeed || { time: [], stripSpeed: [] }
|
||
}
|
||
|
||
const limit = this.chartMaxPoints
|
||
const tasks = []
|
||
|
||
const deviceMap = {
|
||
entry: { deviceCode: 'POR1', fields: ['stripSpeed', 'tensionPorBr1', 'tensionBr1Br2', 'tensionBr2Br3'] },
|
||
furnace: {
|
||
deviceCode: 'FUR2',
|
||
fields: [
|
||
'jcf1FurnaceTemperatureActual', 'jcf2FurnaceTemperatureActual',
|
||
'lbzFurnaceTemperatureActual', 'lthFurnaceTemperatureActual',
|
||
'nof1FurnaceTemperatureActual', 'nof2FurnaceTemperatureActual',
|
||
'nof3FurnaceTemperatureActual', 'nof4FurnaceTemperatureActual',
|
||
'nof5FurnaceTemperatureActual', 'phFurnaceTemperatureActual',
|
||
'rtf1FurnaceTemperatureActual', 'rtf2FurnaceTemperatureActual',
|
||
'sfFurnaceTemperatureActual', 'tdsFurnaceTemperatureActual',
|
||
'potTemperature'
|
||
]
|
||
},
|
||
coat: {
|
||
deviceCode: 'COAT',
|
||
fields: ['avrCoatingWeightTop', 'avrCoatingWeightBottom', 'airKnifePressure', 'stripSpeedTmExit']
|
||
},
|
||
exit: { deviceCode: 'TR', fields: ['tensionBr8Br9', 'tensionBr9Tr', 'speedExitSection'] }
|
||
}
|
||
|
||
// 批量加载各设备历史快照
|
||
Object.keys(deviceMap).forEach((group) => {
|
||
const cfg = deviceMap[group]
|
||
tasks.push(
|
||
listDeviceSnapshotLatest({ limit, deviceCode: cfg.deviceCode })
|
||
.then((res) => {
|
||
if (this.isDestroyed) return
|
||
const rows = res.data || []
|
||
const list = rows.slice().reverse() // 按时间升序
|
||
list.forEach((row) => {
|
||
if (this.isDestroyed) return
|
||
const t = (row.createTime || new Date().toTimeString()).slice(0, 8) // HH:mm:ss
|
||
let snap = {}
|
||
try {
|
||
snap = row.snapshotData ? JSON.parse(row.snapshotData) : {}
|
||
} catch (e) {
|
||
snap = {}
|
||
}
|
||
const values = {}
|
||
cfg.fields.forEach((f) => {
|
||
values[f] = snap[f] || 0
|
||
})
|
||
this.pushSeries(group, t, values)
|
||
})
|
||
})
|
||
.catch(() => { })
|
||
)
|
||
})
|
||
|
||
await Promise.all(tasks)
|
||
// 历史数据加载完成后更新图表
|
||
this.updateCharts()
|
||
} catch (e) {
|
||
console.error('加载图表历史数据失败:', e)
|
||
}
|
||
},
|
||
// 初始化WebSocket连接(替换uniapp的WebSocketManager为原生WebSocket)
|
||
initSocket() {
|
||
const vm = this
|
||
let isDestroyed = false
|
||
const pendingUpdates = []
|
||
|
||
// 替换为真实的WebSocket地址
|
||
const wsUrl = 'ws://140.143.206.120:18081/websocket'
|
||
const socket = new WebSocketManager(wsUrl)
|
||
this.socket = socket
|
||
|
||
console.log('初始化WebSocket连接')
|
||
socket.connect('track_measure', {
|
||
onOpen: () => {
|
||
if (isDestroyed) return
|
||
vm.isConnected = true
|
||
console.log('WebSocket连接成功')
|
||
},
|
||
onClose: () => {
|
||
if (isDestroyed) return
|
||
vm.isConnected = false
|
||
// 断线重连逻辑
|
||
if (!vm.isDestroyed) {
|
||
setTimeout(() => vm.initSocket(), 3000)
|
||
}
|
||
},
|
||
onError: (error) => {
|
||
if (isDestroyed) return
|
||
vm.isConnected = false
|
||
console.error('WebSocket错误:', error)
|
||
},
|
||
onMessage: (event) => {
|
||
if (!vm) {
|
||
isDestroyed = true
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 解析WebSocket消息
|
||
let payload = null
|
||
if (typeof event.data === 'string') {
|
||
payload = JSON.parse(event)
|
||
} else {
|
||
payload = event
|
||
}
|
||
|
||
// if (!payload || isDestroyed || !vm) return
|
||
|
||
// 异步更新数据,避免阻塞UI
|
||
const updateFrame = () => {
|
||
try {
|
||
vm.latestMeasure = payload
|
||
|
||
console.log('更新数据:', payload)
|
||
vm.appendStripSpeedPoint(payload)
|
||
vm.updateLastTime()
|
||
// 实时更新图表
|
||
vm.updateCharts()
|
||
} catch (e) {
|
||
isDestroyed = true
|
||
console.error('更新实时数据失败:', e)
|
||
}
|
||
}
|
||
|
||
// 用requestAnimationFrame优化渲染
|
||
let frameId = requestAnimationFrame(updateFrame)
|
||
pendingUpdates.push(frameId)
|
||
} catch (e) {
|
||
if (!isDestroyed) {
|
||
console.error('解析WebSocket数据失败:', e)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
// 保存清理函数
|
||
this._clearSocketFlag = () => {
|
||
isDestroyed = true
|
||
// 取消所有待执行的更新
|
||
pendingUpdates.forEach(id => cancelAnimationFrame(id))
|
||
pendingUpdates.length = 0
|
||
// 关闭socket
|
||
socket.disconnectAll()
|
||
}
|
||
},
|
||
// 初始化ECharts图表实例
|
||
initCharts() {
|
||
// 带钢速度图表
|
||
this.chartInstances.stripSpeed = echarts.init(this.$refs.stripSpeedChart)
|
||
this.chartInstances.stripSpeed.setOption({
|
||
title: { text: '带钢速度趋势' },
|
||
xAxis: { type: 'category', data: this.chartSeries.stripSpeed.time },
|
||
yAxis: { type: 'value', name: 'm/min' },
|
||
series: [{ name: '带钢速度', type: 'line', data: this.chartSeries.stripSpeed.stripSpeed }],
|
||
tooltip: { trigger: 'axis' },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }
|
||
})
|
||
|
||
// 入口段图表
|
||
this.chartInstances.entry = echarts.init(this.$refs.entryChart)
|
||
// 涂层段图表
|
||
this.chartInstances.coat = echarts.init(this.$refs.coatChart)
|
||
// 出口段图表
|
||
this.chartInstances.exit = echarts.init(this.$refs.exitChart)
|
||
|
||
// 监听窗口大小变化,自适应图表
|
||
window.addEventListener('resize', () => {
|
||
Object.values(this.chartInstances).forEach(instance => {
|
||
if (instance) instance.resize()
|
||
})
|
||
})
|
||
},
|
||
// 更新图表数据
|
||
updateCharts() {
|
||
// 更新带钢速度图表
|
||
if (this.chartInstances?.stripSpeed) {
|
||
this.chartInstances.stripSpeed.setOption({
|
||
xAxis: { data: this.chartSeries.stripSpeed.time },
|
||
series: [{ data: this.chartSeries.stripSpeed.stripSpeed }]
|
||
})
|
||
}
|
||
|
||
// 更新入口段图表
|
||
if (this.chartInstances?.entry) {
|
||
this.chartInstances.entry.setOption(this.toGroupLineChart('entry'))
|
||
}
|
||
|
||
// 更新涂层段图表
|
||
if (this.chartInstances?.coat) {
|
||
this.chartInstances.coat.setOption(this.toGroupLineChart('coat'))
|
||
}
|
||
|
||
// 更新出口段图表
|
||
if (this.chartInstances?.exit) {
|
||
this.chartInstances.exit.setOption(this.toGroupLineChart('exit'))
|
||
}
|
||
},
|
||
// 向图表系列追加数据
|
||
pushSeries(group, time, values) {
|
||
const series = this.chartSeries[group]
|
||
if (!series) return
|
||
|
||
// 控制数据点数量不超过最大值
|
||
if (series.time.length >= this.chartMaxPoints) {
|
||
series.time.shift()
|
||
Object.keys(values).forEach(key => {
|
||
if (series[key]) series[key].shift()
|
||
})
|
||
}
|
||
|
||
// 添加新数据
|
||
series.time.push(time)
|
||
Object.keys(values).forEach(key => {
|
||
if (series[key]) {
|
||
series[key].push(values[key] || 0)
|
||
}
|
||
})
|
||
},
|
||
// 追加图表数据点(从WebSocket)
|
||
appendChartPoint(payload) {
|
||
const now = new Date().toTimeString().slice(0, 8)
|
||
// 按设备分组处理payload数据
|
||
const groups = {
|
||
entry: ['stripSpeed', 'tensionPorBr1', 'tensionBr1Br2', 'tensionBr2Br3'],
|
||
coat: ['avrCoatingWeightTop', 'avrCoatingWeightBottom', 'airKnifePressure', 'stripSpeedTmExit'],
|
||
exit: ['tensionBr8Br9', 'tensionBr9Tr', 'speedExitSection']
|
||
}
|
||
|
||
Object.keys(groups).forEach(group => {
|
||
const values = {}
|
||
groups[group].forEach(field => {
|
||
values[field] = payload[field] || 0
|
||
})
|
||
this.pushSeries(group, now, values)
|
||
})
|
||
},
|
||
// 追加带钢速度数据点
|
||
appendStripSpeedPoint(payload) {
|
||
const now = new Date().toTimeString().slice(0, 8)
|
||
const speed = payload.appMeasureEntryMessage.stripSpeed || 0
|
||
this.pushSeries('stripSpeed', now, { stripSpeed: speed })
|
||
},
|
||
// 更新最后更新时间
|
||
updateLastTime() {
|
||
this.lastUpdateTime = new Date().toTimeString().slice(0, 8)
|
||
},
|
||
// 根据数据源和字段获取实时值
|
||
getRealtimeValueBySource(source, field) {
|
||
if (!this.latestMeasure) return '--'
|
||
// 按source映射到不同的字段前缀/规则
|
||
console.log('source:', source, field, this.latestMeasure[`appMeasure${source}Message`])
|
||
const value = this.latestMeasure[`appMeasure${source}Message`][field] || 0
|
||
return value.toFixed(2) // 格式化显示
|
||
},
|
||
// 获取字段标签
|
||
getFieldLabel(field) {
|
||
return this.fieldMeta[field]?.label || field
|
||
},
|
||
// 获取字段单位
|
||
getFieldUnit(field) {
|
||
return this.fieldMeta[field]?.unit || ''
|
||
},
|
||
// 生成分组折线图配置
|
||
toGroupLineChart(group) {
|
||
const seriesData = this.chartSeries[group]
|
||
if (!seriesData) return {}
|
||
|
||
// 构建ECharts系列配置
|
||
const series = []
|
||
// 过滤掉time字段,生成每个指标的折线
|
||
Object.keys(seriesData).forEach(key => {
|
||
if (key === 'time') return
|
||
series.push({
|
||
name: this.getFieldLabel(key),
|
||
type: 'line',
|
||
data: seriesData[key],
|
||
smooth: true // 平滑曲线
|
||
})
|
||
})
|
||
|
||
return {
|
||
title: { text: `${this.getGroupTitle(group)}趋势` },
|
||
xAxis: { type: 'category', data: seriesData.time },
|
||
yAxis: { type: 'value' },
|
||
series,
|
||
tooltip: { trigger: 'axis' },
|
||
legend: { top: 20 },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }
|
||
}
|
||
},
|
||
// 获取分组标题
|
||
getGroupTitle(group) {
|
||
const titles = {
|
||
entry: '入口段',
|
||
coat: '后处理/涂层段',
|
||
exit: '出口段'
|
||
}
|
||
return titles[group] || group
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.realtime-monitoring {
|
||
width: 100%;
|
||
height: calc(100vh - 124px);
|
||
box-sizing: border-box;
|
||
padding: 10px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.scroll-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
/* 状态栏样式 */
|
||
.status-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 12px 15px;
|
||
margin-bottom: 15px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.status-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0 15px;
|
||
}
|
||
|
||
.status-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.status-value {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.status-通畅 {
|
||
color: #00b42a;
|
||
}
|
||
|
||
.status-异常 {
|
||
color: #f53f3f;
|
||
}
|
||
|
||
.status-divider {
|
||
width: 1px;
|
||
height: 20px;
|
||
background-color: #eee;
|
||
margin: 0 5px;
|
||
}
|
||
|
||
/* 通用区块样式 */
|
||
.section {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin-bottom: 15px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
/* 带钢状态样式 */
|
||
.strip-status-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 15px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.strip-status-card {
|
||
padding: 15px;
|
||
border: 1px solid #eee;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.strip-status-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.strip-status-value {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.strip-status-unit {
|
||
font-size: 14px;
|
||
color: #999;
|
||
}
|
||
|
||
.progress-container {
|
||
margin-top: 10px;
|
||
width: 100%;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 8px;
|
||
background-color: #eee;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background-color: #1890ff;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.progress-text {
|
||
display: block;
|
||
text-align: right;
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
/* 指标网格样式 */
|
||
.metrics-grid-3 {
|
||
display: grid;
|
||
grid-template-columns: repeat(8, 1fr);
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.metric-box {
|
||
padding: 12px;
|
||
border: 1px solid #eee;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.metric-name {
|
||
font-size: 13px;
|
||
color: #666;
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.metric-unit {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.metric-set-value {
|
||
font-size: 12px;
|
||
color: #999;
|
||
display: block;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
/* 图表容器样式 */
|
||
.chart-box {
|
||
width: 100%;
|
||
height: 300px;
|
||
margin-top: 10px;
|
||
}
|
||
</style> |