454 lines
12 KiB
Vue
454 lines
12 KiB
Vue
<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> |