feat(zinc): 新增PDO接口和日期筛选组件,重构生产统计页面
refactor(api): 移除冗余API路径前缀 style(websocket): 移除重复的日志打印 feat(label): 新增原料标签组件并替换生产标签 refactor(lines): 移除废弃的流程图组件和配置 feat(utils): 新增日期范围工具函数 feat(statistic): 集成日期筛选和图表分析功能
This commit is contained in:
@@ -1,5 +1,728 @@
|
||||
<template>
|
||||
<div>
|
||||
实时监控
|
||||
<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>
|
||||
</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数据是否已追加
|
||||
stripLocationProgress: 0, // 带钢位置进度百分比
|
||||
// 各段指标配置
|
||||
entryMetrics: [
|
||||
{ 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: '℃' }
|
||||
],
|
||||
coatMetrics: [
|
||||
{ field: 'avrCoatingWeightTop', label: '上涂层重量', unit: 'g/m²' },
|
||||
{ field: 'avrCoatingWeightBottom', label: '下涂层重量', unit: 'g/m²' },
|
||||
{ field: 'airKnifePressure', label: '气刀压力', unit: 'bar' }
|
||||
],
|
||||
exitMetrics: [
|
||||
{ field: 'tensionBr8Br9', label: '张力BR8-BR9', unit: 'kN' },
|
||||
{ field: 'tensionBr9Tr', label: '张力BR9-TR', unit: 'kN' },
|
||||
{ field: 'speedExitSection', label: '出口速度', unit: 'm/min' }
|
||||
],
|
||||
// ECharts实例缓存
|
||||
chartInstances: {
|
||||
stripSpeed: null,
|
||||
entry: null,
|
||||
coat: null,
|
||||
exit: null
|
||||
}
|
||||
}
|
||||
},
|
||||
// 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 (isDestroyed) return
|
||||
console.log('收到消息:', event, vm)
|
||||
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
|
||||
|
||||
vm.appendStripSpeedPoint(payload)
|
||||
vm.updateLastTime()
|
||||
// 实时更新图表
|
||||
vm.updateCharts()
|
||||
// 更新带钢位置进度
|
||||
vm.stripLocationProgress = vm.getRealtimeValueBySource('ENTRY', 'stripLocation') * 10 || 0
|
||||
} 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.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映射到不同的字段前缀/规则
|
||||
const value = this.latestMeasure[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(3, 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>
|
||||
Reference in New Issue
Block a user