Files
klp-oa/klp-ui/src/views/lines/pdo/components/line.vue
砂糖 30d5d914ef feat(pdo): 新增PDO管理功能及相关组件
新增PDO管理页面及相关功能组件,包括:
1. 新增数据修正组件DataCorrection.vue
2. 新增标签打印组件LabelPrint.vue
3. 新增统计汇总组件PdoSummary.vue
4. 新增图表展示组件line.vue
5. 实现主页面index.vue布局及功能
6. 新增API接口文件用于业务数据交互
7. 修改lines/index.vue配置,移除baseURL动态获取逻辑
2025-12-25 15:50:19 +08:00

551 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>