新增PDO管理页面及相关功能组件,包括: 1. 新增数据修正组件DataCorrection.vue 2. 新增标签打印组件LabelPrint.vue 3. 新增统计汇总组件PdoSummary.vue 4. 新增图表展示组件line.vue 5. 实现主页面index.vue布局及功能 6. 新增API接口文件用于业务数据交互 7. 修改lines/index.vue配置,移除baseURL动态获取逻辑
551 lines
14 KiB
Vue
551 lines
14 KiB
Vue
<template>
|
||
<div class="monitoring-container">
|
||
<div class="chart-wrapper" v-loading="loading">
|
||
<!-- 参数选择下拉框 -->
|
||
<el-select v-model="paramField" class="param-select" @change="handleParamChange" size="mini">
|
||
<el-option v-for="item in paramFields" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||
</el-select>
|
||
|
||
<!-- 图表容器 -->
|
||
<div ref="chart" class="chart-content"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import * as echarts from 'echarts'
|
||
import createFetch from '@/api/l2/pdo'
|
||
|
||
export default {
|
||
name: 'DeviceMonitoring',
|
||
props: {
|
||
enCoilID: {
|
||
type: String,
|
||
required: true,
|
||
},
|
||
url: {
|
||
type: String,
|
||
required: true,
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
pdoApi: undefined,
|
||
// 参数列表来源于 DeviceEnum 定义的监测字段
|
||
paramFields: [
|
||
{ label: '带钢速度', value: 'stripSpeed' },
|
||
{ label: '开卷张力1#', value: 'tensionPorBr1' },
|
||
{ label: '开卷张力2#', value: 'tensionPorBr2' },
|
||
{ label: '清洗电压', value: 'cleaningVoltage' },
|
||
{ label: '清洗电流', value: 'cleaningCurrent' },
|
||
{ label: '碱液浓度', value: 'alkaliConcentration' },
|
||
{ label: '碱液温度', value: 'alkaliTemperature' },
|
||
{ label: 'PH炉出口温度', value: 'phfExitStripTemp' },
|
||
{ label: '加热段出口温度', value: 'rtfExitStripTemp' },
|
||
{ label: '冷却段出口温度', value: 'jcsExitStripTemp' },
|
||
{ label: '均衡段出口温度', value: 'scsExitStripTemp' },
|
||
{ label: '锌锅温度', value: 'potTemperature' },
|
||
{ label: '锌锅功率', value: 'zincPotPower' },
|
||
{ label: '燃气消耗', value: 'gasConsumption' },
|
||
{ label: '冷却塔温度', value: 'coolingTowerStripTemp' },
|
||
{ label: '光整机张力', value: 'tensionBr5Tm' },
|
||
{ label: 'TM出口速度', value: 'stripSpeedTmExit' },
|
||
{ label: '拉矫延伸率', value: 'tlElongation' },
|
||
{ label: '拉矫张力', value: 'tensionTlBr7' }
|
||
],
|
||
paramField: 'stripSpeed',
|
||
treeProps: {
|
||
children: 'children',
|
||
label: 'label'
|
||
},
|
||
currentParam: null, // 当前选中的参数
|
||
chart: null, // 图表实例
|
||
chartData: [], // 图表数据
|
||
timeStamps: [], // 时间戳
|
||
originalTimeStamps: [], // 原始时间戳(用于采样)
|
||
originalChartData: [], // 原始图表数据(用于采样)
|
||
timeRange: '1', // 时间范围(分钟)
|
||
socket: null, // 模拟socket
|
||
latestValue: 0,
|
||
maxValue: 0,
|
||
minValue: 0,
|
||
avgValue: 0,
|
||
loading: false,
|
||
maxXAxisLabels: 40 // X轴最多显示的标签数量
|
||
}
|
||
},
|
||
watch: {
|
||
enCoilID: {
|
||
handler(newVal) {
|
||
console.log('enCoilID 变化:', newVal);
|
||
if (newVal && this.pdoApi) {
|
||
this.fetchChartData();
|
||
} else {
|
||
this.drawEmptyChart();
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
url: {
|
||
handler(newVal) {
|
||
console.log('url 变化:', newVal);
|
||
if (newVal) {
|
||
this.pdoApi = createFetch(newVal)
|
||
} else {
|
||
this.pdoApi = null
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
},
|
||
mounted() {
|
||
// 初始化图表
|
||
this.initChart();
|
||
},
|
||
beforeDestroy() {
|
||
// 销毁图表实例和事件监听
|
||
if (this.chart) {
|
||
this.chart.dispose();
|
||
}
|
||
window.removeEventListener('resize', this.handleResize);
|
||
},
|
||
methods: {
|
||
// 初始化图表
|
||
initChart() {
|
||
this.$nextTick(() => {
|
||
if (this.$refs.chart) {
|
||
this.chart = echarts.init(this.$refs.chart);
|
||
// 监听窗口大小变化,调整图表尺寸
|
||
window.addEventListener('resize', this.handleResize);
|
||
|
||
// 初始显示
|
||
if (this.enCoilID) {
|
||
this.fetchChartData();
|
||
} else {
|
||
this.drawEmptyChart();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 处理窗口大小变化
|
||
handleResize() {
|
||
if (this.chart) {
|
||
this.chart.resize();
|
||
}
|
||
},
|
||
|
||
// 获取图表数据
|
||
fetchChartData() {
|
||
if (!this.enCoilID || !this.paramField || !this.pdoApi) {
|
||
this.drawNoDataChart();
|
||
}
|
||
this.loading = true;
|
||
this.pdoApi.getSegmentList({
|
||
enCoilID: this.enCoilID,
|
||
paramField: this.paramField
|
||
}).then(res => {
|
||
// 假设res.data格式为[{ segNo: '时间戳', value: '数值' }]
|
||
if (res.data && res.data.length) {
|
||
this.processChartData(res.data);
|
||
this.drawLineChart();
|
||
console.log('有数据,绘制折线图')
|
||
} else {
|
||
this.drawNoDataChart();
|
||
console.log('无数据,清空折线图')
|
||
}
|
||
}).catch(err => {
|
||
console.error('获取数据失败:', err);
|
||
this.drawErrorChart();
|
||
}).finally(_ => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
|
||
// 处理图表数据
|
||
processChartData(rawData) {
|
||
// 排序数据(按segNo,假设是时间戳或序号)
|
||
rawData.sort((a, b) => a.segNo - b.segNo);
|
||
|
||
// 保存原始数据
|
||
this.originalTimeStamps = rawData.map(item => item.segNo);
|
||
this.originalChartData = rawData.map(item => parseFloat(item.value));
|
||
|
||
// 根据数据量决定是否采样
|
||
// if (rawData.length > this.maxXAxisLabels) {
|
||
// // 数据采样 - 均匀采样
|
||
// this.sampleData(rawData);
|
||
// } else {
|
||
// 数据量适中,直接使用全部数据
|
||
this.timeStamps = this.originalTimeStamps;
|
||
this.chartData = this.originalChartData;
|
||
// }
|
||
|
||
// 计算统计值(基于原始数据)
|
||
if (this.originalChartData.length) {
|
||
this.latestValue = this.originalChartData[this.originalChartData.length - 1];
|
||
this.maxValue = Math.max(...this.originalChartData);
|
||
this.minValue = Math.min(...this.originalChartData);
|
||
this.avgValue = this.originalChartData.reduce((sum, val) => sum + val, 0) / this.originalChartData.length;
|
||
}
|
||
},
|
||
|
||
// 数据采样方法
|
||
sampleData(rawData) {
|
||
const total = rawData.length;
|
||
const sampleInterval = Math.ceil(total / this.maxXAxisLabels);
|
||
|
||
// 初始化采样数组
|
||
this.timeStamps = [];
|
||
this.chartData = [];
|
||
|
||
// 均匀采样
|
||
for (let i = 0; i < total; i += sampleInterval) {
|
||
this.timeStamps.push(rawData[i].segNo);
|
||
this.chartData.push(parseFloat(rawData[i].value));
|
||
}
|
||
|
||
// 确保最后一个数据点被包含
|
||
if (this.timeStamps[this.timeStamps.length - 1] !== rawData[total - 1].segNo) {
|
||
this.timeStamps.push(rawData[total - 1].segNo);
|
||
this.chartData.push(parseFloat(rawData[total - 1].value));
|
||
}
|
||
},
|
||
|
||
// 绘制折线图
|
||
drawLineChart() {
|
||
if (!this.chart) return;
|
||
|
||
// 清理旧图层,避免“暂无数据”残留
|
||
this.chart.clear();
|
||
|
||
const option = {
|
||
backgroundColor: 'transparent',
|
||
graphic: [], // 清除占位/无数据图层
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'line'
|
||
},
|
||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||
borderColor: '#d4d4d4',
|
||
borderWidth: 1,
|
||
textStyle: {
|
||
color: '#333'
|
||
},
|
||
formatter: (params) => {
|
||
const param = params[0];
|
||
return `
|
||
序号: ${param.name}<br>
|
||
值: ${param.value.toFixed(2)}<br>
|
||
单位: ${this.getParamUnit()}
|
||
`;
|
||
}
|
||
},
|
||
grid: {
|
||
left: '3%',
|
||
right: '4%',
|
||
bottom: '10%', // 增加底部空间,防止标签被截断
|
||
top: '15%',
|
||
containLabel: true
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
boundaryGap: false,
|
||
// data: this.timeStamps,
|
||
// axisLine: {
|
||
// lineStyle: {
|
||
// color: '#d4d4d4'
|
||
// }
|
||
// },
|
||
// axisLabel: {
|
||
// color: '#666',
|
||
// rotate: 45,
|
||
// // 动态计算标签显示间隔
|
||
// interval: (index, value) => {
|
||
// // 如果数据量大于maxXAxisLabels,按采样后的间隔显示
|
||
// return index % Math.max(1, Math.ceil(this.timeStamps.length / this.maxXAxisLabels)) === 0;
|
||
// }
|
||
// },
|
||
splitLine: {
|
||
show: false
|
||
}
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
name: this.getParamUnit(),
|
||
nameLocation: 'middle',
|
||
nameGap: 30,
|
||
nameTextStyle: {
|
||
color: '#666'
|
||
},
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: '#d4d4d4'
|
||
}
|
||
},
|
||
axisLabel: {
|
||
color: '#666'
|
||
},
|
||
splitLine: {
|
||
lineStyle: {
|
||
color: '#e8e8e8',
|
||
type: 'dashed'
|
||
}
|
||
}
|
||
},
|
||
series: [
|
||
{
|
||
name: this.getParamLabel(),
|
||
type: 'line',
|
||
data: this.chartData,
|
||
smooth: true,
|
||
symbol: 'circle',
|
||
symbolSize: 4,
|
||
lineStyle: {
|
||
color: '#666',
|
||
width: 2
|
||
},
|
||
itemStyle: {
|
||
color: '#999'
|
||
},
|
||
areaStyle: {
|
||
color: {
|
||
type: 'linear',
|
||
x: 0,
|
||
y: 0,
|
||
x2: 0,
|
||
y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(153, 153, 153, 0.3)' },
|
||
{ offset: 1, color: 'rgba(153, 153, 153, 0.05)' }
|
||
]
|
||
}
|
||
},
|
||
markPoint: {
|
||
data: [
|
||
{ type: 'max', name: '最大值' },
|
||
{ type: 'min', name: '最小值' }
|
||
],
|
||
itemStyle: {
|
||
color: '#999'
|
||
},
|
||
label: {
|
||
color: '#333'
|
||
}
|
||
},
|
||
markLine: {
|
||
data: [
|
||
{ type: 'average', name: '平均值' }
|
||
],
|
||
lineStyle: {
|
||
color: '#999',
|
||
type: 'dashed'
|
||
},
|
||
label: {
|
||
color: '#333'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
};
|
||
|
||
this.chart.setOption(option, true);
|
||
},
|
||
|
||
// 绘制空图表(未选择实绩)
|
||
drawEmptyChart() {
|
||
if (!this.chart) return;
|
||
|
||
const option = {
|
||
graphic: {
|
||
elements: [
|
||
{
|
||
type: 'text',
|
||
left: 'center',
|
||
top: 'center',
|
||
style: {
|
||
text: '请选择实绩',
|
||
fontSize: 16,
|
||
fontWeight: 'bold',
|
||
fill: '#999'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: []
|
||
},
|
||
yAxis: {
|
||
type: 'value'
|
||
},
|
||
series: []
|
||
};
|
||
|
||
this.chart.setOption(option);
|
||
},
|
||
|
||
// 绘制无数据图表
|
||
drawNoDataChart() {
|
||
if (!this.chart) return;
|
||
|
||
// 先清除原有图表内容
|
||
this.chart.clear();
|
||
|
||
const option = {
|
||
graphic: {
|
||
elements: [
|
||
{
|
||
type: 'text',
|
||
left: 'center',
|
||
top: 'center',
|
||
style: {
|
||
text: '暂无数据',
|
||
fontSize: 16,
|
||
fontWeight: 'bold',
|
||
fill: '#999'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: []
|
||
},
|
||
yAxis: {
|
||
type: 'value'
|
||
},
|
||
series: []
|
||
};
|
||
|
||
this.chart.setOption(option);
|
||
},
|
||
|
||
// 绘制错误图表
|
||
drawErrorChart() {
|
||
if (!this.chart) return;
|
||
|
||
const option = {
|
||
graphic: {
|
||
elements: [
|
||
{
|
||
type: 'text',
|
||
left: 'center',
|
||
top: 'center',
|
||
style: {
|
||
text: '数据加载失败',
|
||
fontSize: 16,
|
||
fontWeight: 'bold',
|
||
fill: '#f56c6c'
|
||
}
|
||
}
|
||
]
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: []
|
||
},
|
||
yAxis: {
|
||
type: 'value'
|
||
},
|
||
series: []
|
||
};
|
||
|
||
this.chart.setOption(option);
|
||
},
|
||
|
||
// 处理参数变更
|
||
handleParamChange() {
|
||
// 清空现有数据
|
||
this.chartData = [];
|
||
this.timeStamps = [];
|
||
this.originalTimeStamps = [];
|
||
this.originalChartData = [];
|
||
|
||
// 重新获取数据
|
||
if (this.enCoilID) {
|
||
this.fetchChartData();
|
||
}
|
||
},
|
||
|
||
// 获取参数标签
|
||
getParamLabel() {
|
||
const param = this.paramFields.find(item => item.value === this.paramField);
|
||
return param ? param.label : this.paramField;
|
||
},
|
||
|
||
// 获取参数单位
|
||
getParamUnit() {
|
||
// 根据不同参数返回不同单位
|
||
switch (this.paramField) {
|
||
case 'stripSpeed':
|
||
return 'm/s';
|
||
case 'tensionPorBr1':
|
||
case 'tensionPorBr2':
|
||
return 'N';
|
||
case 'cleaningVoltage':
|
||
return 'V';
|
||
case 'cleaningCurrent':
|
||
return 'A';
|
||
case 'alkaliConcentration':
|
||
return '%';
|
||
case 'alkaliTemperature':
|
||
case 'phfExitStripTemp':
|
||
case 'rtfExitStripTemp':
|
||
case 'jcsExitStripTemp':
|
||
case 'scsExitStripTemp':
|
||
case 'potTemperature':
|
||
case 'coolingTowerStripTemp':
|
||
return '°C';
|
||
case 'zincPotPower':
|
||
return 'kW';
|
||
case 'gasConsumption':
|
||
return 'm³/h';
|
||
case 'tensionBr5Tm':
|
||
case 'tensionTlBr7':
|
||
return 'N';
|
||
case 'stripSpeedTmExit':
|
||
return 'm/s';
|
||
case 'tlElongation':
|
||
return '%';
|
||
default:
|
||
return '';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.monitoring-container {
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.chart-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.param-select {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
z-index: 10;
|
||
width: 150px;
|
||
|
||
::v-deep .el-input__inner {
|
||
border-color: #d4d4d4;
|
||
}
|
||
}
|
||
|
||
.chart-content {
|
||
width: 100%;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
</style> |