Files
klp-mono/apps/l2/src/views/l2/pdo/components/ParamEcharts.vue
2026-01-04 17:04:42 +08:00

454 lines
12 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="param-echarts-container" v-loading="loading">
<!-- 图表标题 -->
<div class="chart-title">{{ chartTitle }}</div>
<!-- ECharts 渲染容器 -->
<div ref="chartDom" class="chart-content"></div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { getSegmentList } from '@/api/business/segment'
export default {
name: 'ParamEcharts',
// 接收父组件传入的核心参数
props: {
enCoilID: {
type: String,
required: true,
description: '线圈ID用于请求对应数据'
},
paramField: {
type: String,
required: true,
description: '参数字段名(对应原 paramFields 的 value'
}
},
data() {
return {
chartInstance: null, // ECharts 实例
loading: false, // 数据加载状态
chartDataObj: { // 图表数据
timeStamps: [],
chartData: [],
originalTimeStamps: [],
originalChartData: []
},
stat: { // 参数统计值
latestValue: 0,
maxValue: 0,
minValue: 0,
avgValue: 0
},
maxXAxisLabels: 40, // X轴最大显示标签数
paramLabelMap: { // 参数字段与标签的映射(与父组件保持一致,替换为中文)
stripSpeed: '带钢速度',
tensionPorBr1: '开卷张力1#',
tensionPorBr2: '开卷张力2#',
cleaningVoltage: '清洗电压',
cleaningCurrent: '清洗电流',
alkaliConcentration: '碱液浓度',
alkaliTemperature: '碱液温度',
phfExitStripTemp: 'PH炉出口温度',
rtfExitStripTemp: '加热段出口温度',
jcsExitStripTemp: '冷却段出口温度',
scsExitStripTemp: '均热段出口温度',
potTemperature: '锌锅温度',
zincPotPower: '锌锅功率',
gasConsumption: '燃气消耗量',
coolingTowerStripTemp: '冷却塔温度',
tensionBr5Tm: 'TM张力',
stripSpeedTmExit: 'TM出口速度',
tlElongation: 'TL延伸率',
tensionTlBr7: 'TL张力'
}
}
},
computed: {
// 图表标题(根据 paramField 自动匹配)
chartTitle() {
return this.paramLabelMap[this.paramField] || this.paramField
}
},
watch: {
// 监听线圈ID变化重新请求数据
enCoilID: {
handler(newVal) {
this.fetchChartData()
},
immediate: true
},
// 监听参数字段变化,重新请求数据
paramField: {
handler(newVal) {
this.fetchChartData()
},
immediate: true
}
},
mounted() {
// 初始化 ECharts 实例
this.initEchartsInstance()
// 监听窗口 resize自适应图表尺寸
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
// 销毁 ECharts 实例,释放资源
if (this.chartInstance && this.chartInstance.dispose) {
this.chartInstance.dispose()
}
// 移除 resize 监听
window.removeEventListener('resize', this.handleResize)
},
methods: {
// 初始化 ECharts 实例
initEchartsInstance() {
this.$nextTick(() => {
const chartDom = this.$refs.chartDom
if (!chartDom) {
console.warn('ECharts 渲染容器未找到,初始化失败')
return
}
// 创建 ECharts 实例
this.chartInstance = echarts.init(chartDom)
// 初始绘制空图表
this.drawEmptyChart()
})
},
// 获取图表数据
fetchChartData() {
// 前置校验:参数无效时绘制空图表并关闭加载
if (!this.enCoilID || !this.paramField) {
this.drawEmptyChart()
this.loading = false
return
}
// 开启加载状态
this.loading = true
// 请求数据
getSegmentList({
enCoilID: this.enCoilID,
paramField: this.paramField
}).then(res => {
if (res.data && res.data.length > 0) {
// 处理数据
const processedData = this.processChartData(res.data)
this.chartDataObj = processedData
this.stat = processedData.stat
// 绘制折线图
this.drawLineChart()
} else {
// 无数据时绘制无数据图表
this.drawNoDataChart()
}
}).catch(err => {
console.error(`获取${this.paramField}数据失败:`, err)
// 数据请求失败时绘制错误图表
this.drawErrorChart()
}).finally(() => {
// 无论成功失败,都关闭加载状态
this.loading = false
})
},
// 处理原始数据
processChartData(rawData) {
// 按 segNo 排序
rawData.sort((a, b) => a.segNo - b.segNo)
const originalTimeStamps = rawData.map(item => item.segNo)
const originalChartData = rawData.map(item => parseFloat(item.value))
let timeStamps = originalTimeStamps
let chartData = originalChartData
// X轴标签过多时进行采样保持原有逻辑
if (rawData.length > this.maxXAxisLabels) {
const sampleInterval = Math.ceil(rawData.length / this.maxXAxisLabels)
timeStamps = []
chartData = []
for (let i = 0; i < rawData.length; i += sampleInterval) {
timeStamps.push(rawData[i].segNo)
chartData.push(parseFloat(rawData[i].value))
}
// 确保最后一个数据点被保留
if (timeStamps[timeStamps.length - 1] !== rawData[rawData.length - 1].segNo) {
timeStamps.push(rawData[rawData.length - 1].segNo)
chartData.push(parseFloat(rawData[rawData.length - 1].value))
}
}
// 计算统计值
let stat = { latestValue: 0, maxValue: 0, minValue: 0, avgValue: 0 }
if (originalChartData.length) {
stat.latestValue = originalChartData[originalChartData.length - 1]
stat.maxValue = Math.max(...originalChartData)
stat.minValue = Math.min(...originalChartData)
stat.avgValue = originalChartData.reduce((sum, val) => sum + val, 0) / originalChartData.length
}
return {
timeStamps,
chartData,
originalTimeStamps,
originalChartData,
stat
}
},
// 绘制折线图(核心逻辑)
drawLineChart() {
if (!this.chartInstance || !this.chartDataObj.timeStamps.length) {
return
}
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'line' },
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#d4d4d4',
borderWidth: 1,
textStyle: { color: '#333' },
formatter: (params) => {
const paramItem = params[0]
return `
序号: ${paramItem.name}<br>
数值: ${paramItem.value.toFixed(2)}<br>
单位: ${this.getParamUnit()}
`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: this.chartDataObj.timeStamps,
axisLine: { lineStyle: { color: '#d4d4d4' } },
axisLabel: {
color: '#666',
rotate: 45,
interval: (index) => {
return index % Math.max(1, Math.ceil(this.chartDataObj.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.chartTitle,
type: 'line',
data: this.chartDataObj.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.chartInstance.setOption(option, true)
},
// 绘制空图表(初始无参数/无线圈ID时
drawEmptyChart() {
if (!this.chartInstance) 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.chartInstance.setOption(option)
},
// 绘制无数据图表
drawNoDataChart() {
if (!this.chartInstance) 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.chartInstance.setOption(option)
},
// 绘制数据加载失败图表
drawErrorChart() {
if (!this.chartInstance) 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.chartInstance.setOption(option)
},
// 窗口 resize 时自适应图表尺寸
handleResize() {
if (this.chartInstance && this.chartInstance.resize) {
this.chartInstance.resize()
}
},
// 获取参数单位(保持原有逻辑,单位为标准符号无需修改)
getParamUnit() {
switch (this.paramField) {
case 'stripSpeed':
case 'stripSpeedTmExit':
return 'm/s'
case 'tensionPorBr1':
case 'tensionPorBr2':
case 'tensionBr5Tm':
case 'tensionTlBr7':
return 'N'
case 'cleaningVoltage':
return 'V'
case 'cleaningCurrent':
return 'A'
case 'alkaliConcentration':
case 'tlElongation':
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'
default:
return ''
}
}
}
}
</script>
<style lang="scss" scoped>
.param-echarts-container {
position: relative;
width: 100%;
height: 100%;
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 10px;
box-sizing: border-box;
}
.chart-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
text-align: center;
}
.chart-content {
width: 100%;
height: calc(100% - 24px);
box-sizing: border-box;
min-height: 180px; /* 兜底高度,确保 ECharts 初始化有尺寸 */
}
</style>