feat(图表): 重构监控图表组件为多图表布局
将单图表展示重构为支持最多4个图表同时展示的布局,左侧添加参数树形选择器 优化图表数据加载逻辑,使用Vue.set确保响应式更新 调整样式实现动态布局,根据勾选数量自动适配1-4个图表的排列方式
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
class="summary-content"
|
class="summary-content"
|
||||||
:column="1"
|
:column="6"
|
||||||
:style="{ width: '100%' }"
|
:style="{ width: '100%' }"
|
||||||
size="mini"
|
size="mini"
|
||||||
>
|
>
|
||||||
@@ -73,7 +73,6 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.summary-content {
|
.summary-content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
|
|||||||
@@ -1,13 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="monitoring-container">
|
<div class="monitoring-container">
|
||||||
<div class="chart-wrapper" v-loading="loading">
|
<div style="height: 100%; display: flex; box-sizing: border-box;">
|
||||||
<!-- Parameter selection dropdown / 参数选择下拉框 -->
|
<!-- 左侧树形选择器(最多勾选4个) -->
|
||||||
<el-select v-model="paramField" class="param-select" @change="handleParamChange" size="mini">
|
<div style="height: 100%; width: 320px; overflow-y: scroll; border-right: 1px solid #e8e8e8; padding: 10px;">
|
||||||
<el-option v-for="item in paramFields" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
<el-tree :data="paramFields" :props="treeProps" node-key="value" show-checkbox @check="handleCheckChange"
|
||||||
</el-select>
|
ref="paramTree" :check-strictly="true"></el-tree>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Chart container / 图表容器 -->
|
<!-- 右侧图表布局容器(总高度100%) -->
|
||||||
<div ref="chart" class="chart-content"></div>
|
<div class="charts-layout-wrapper"
|
||||||
|
:style="{ height: '100%', width: '100%', padding: '10px', boxSizing: 'border-box' }">
|
||||||
|
<!-- 核心:根据勾选数量动态控制布局样式 -->
|
||||||
|
<div class="charts-container" :class="{
|
||||||
|
'count-1': checkedCount === 1,
|
||||||
|
'count-2': checkedCount === 2,
|
||||||
|
'count-3': checkedCount === 3,
|
||||||
|
'count-4': checkedCount === 4
|
||||||
|
}" :style="{ height: '100%', width: '100%' }">
|
||||||
|
<!-- 预生成4个固定图表容器(初始隐藏) -->
|
||||||
|
<div v-for="(chartKey, index) in presetChartKeys" :key="chartKey" class="preset-chart-item" :style="{
|
||||||
|
display: index < checkedCount ? 'block' : 'none', // 根据勾选数量显示对应图表
|
||||||
|
}" v-loading="getChartLoading(index)">
|
||||||
|
<div class="chart-title">{{ getChartTitle(index) }}</div>
|
||||||
|
<div :ref="chartKey" class="chart-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无勾选时显示提示 -->
|
||||||
|
<div v-if="checkedCount === 0" class="no-param-tip">
|
||||||
|
Please place the parameter on the left to display the chart. (Max 4)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,6 +38,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import { getSegmentList } from '@/api/business/segment'
|
import { getSegmentList } from '@/api/business/segment'
|
||||||
|
// 引入Vue(用于Vue.set实现响应式赋值,Vue2必备)
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DeviceMonitoring',
|
name: 'DeviceMonitoring',
|
||||||
@@ -26,276 +51,336 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// Parameter list from monitoring fields defined in DeviceEnum / 参数列表来源于 DeviceEnum 定义的监测字段
|
// 参数列表
|
||||||
paramFields: [
|
paramFields: [
|
||||||
{ label: 'Strip Speed', value: 'stripSpeed' }, // 带钢速度
|
{ label: 'Strip Speed', value: 'stripSpeed' },
|
||||||
{ label: 'Pay-off Tension 1#', value: 'tensionPorBr1' }, // 开卷张力1#
|
{ label: 'Pay-off Tension 1#', value: 'tensionPorBr1' },
|
||||||
{ label: 'Pay-off Tension 2#', value: 'tensionPorBr2' }, // 开卷张力2#
|
{ label: 'Pay-off Tension 2#', value: 'tensionPorBr2' },
|
||||||
{ label: 'Cleaning Voltage', value: 'cleaningVoltage' }, // 清洗电压
|
{ label: 'Cleaning Voltage', value: 'cleaningVoltage' },
|
||||||
{ label: 'Cleaning Current', value: 'cleaningCurrent' }, // 清洗电流
|
{ label: 'Cleaning Current', value: 'cleaningCurrent' },
|
||||||
{ label: 'Alkali Concentration', value: 'alkaliConcentration' }, // 碱液浓度
|
{ label: 'Alkali Concentration', value: 'alkaliConcentration' },
|
||||||
{ label: 'Alkali Temperature', value: 'alkaliTemperature' }, // 碱液温度
|
{ label: 'Alkali Temperature', value: 'alkaliTemperature' },
|
||||||
{ label: 'PH Furnace Exit Temp', value: 'phfExitStripTemp' }, // PH炉出口温度
|
{ label: 'PH Furnace Exit Temp', value: 'phfExitStripTemp' },
|
||||||
{ label: 'Heating Section Exit Temp', value: 'rtfExitStripTemp' }, // 加热段出口温度
|
{ label: 'Heating Section Exit Temp', value: 'rtfExitStripTemp' },
|
||||||
{ label: 'Cooling Section Exit Temp', value: 'jcsExitStripTemp' }, // 冷却段出口温度
|
{ label: 'Cooling Section Exit Temp', value: 'jcsExitStripTemp' },
|
||||||
{ label: 'Equilibrium Section Exit Temp', value: 'scsExitStripTemp' }, // 均衡段出口温度
|
{ label: 'Equilibrium Section Exit Temp', value: 'scsExitStripTemp' },
|
||||||
{ label: 'Pot Temperature', value: 'potTemperature' }, // 锌锅温度
|
{ label: 'Pot Temperature', value: 'potTemperature' },
|
||||||
{ label: 'Zinc Pot Power', value: 'zincPotPower' }, // 锌锅功率
|
{ label: 'Zinc Pot Power', value: 'zincPotPower' },
|
||||||
{ label: 'Gas Consumption', value: 'gasConsumption' }, // 燃气消耗
|
{ label: 'Gas Consumption', value: 'gasConsumption' },
|
||||||
{ label: 'Cooling Tower Temp', value: 'coolingTowerStripTemp' }, // 冷却塔温度
|
{ label: 'Cooling Tower Temp', value: 'coolingTowerStripTemp' },
|
||||||
{ label: 'TM Tension', value: 'tensionBr5Tm' }, // 光整机张力
|
{ label: 'TM Tension', value: 'tensionBr5Tm' },
|
||||||
{ label: 'TM Exit Speed', value: 'stripSpeedTmExit' }, // TM出口速度
|
{ label: 'TM Exit Speed', value: 'stripSpeedTmExit' },
|
||||||
{ label: 'TL Elongation', value: 'tlElongation' }, // 拉矫延伸率
|
{ label: 'TL Elongation', value: 'tlElongation' },
|
||||||
{ label: 'TL Tension', value: 'tensionTlBr7' } // 拉矫张力
|
{ label: 'TL Tension', value: 'tensionTlBr7' }
|
||||||
],
|
],
|
||||||
paramField: 'stripSpeed',
|
|
||||||
treeProps: {
|
treeProps: {
|
||||||
children: 'children',
|
children: 'children',
|
||||||
label: 'label'
|
label: 'label'
|
||||||
},
|
},
|
||||||
currentParam: null, // Currently selected parameter / 当前选中的参数
|
checkedNodes: [], // 已勾选参数列表
|
||||||
chart: null, // Chart instance / 图表实例
|
checkedCount: 0, // 已勾选数量(用于布局控制)
|
||||||
chartData: [], // Chart data / 图表数据
|
presetChartKeys: ['chart1', 'chart2', 'chart3', 'chart4'], // 4个固定图表key
|
||||||
timeStamps: [], // Timestamps / 时间戳
|
chartInstances: {}, // 4个固定图表实例 { chart1: echartsInstance, ... }
|
||||||
originalTimeStamps: [], // Original timestamps (for sampling) / 原始时间戳(用于采样)
|
chartDataMap: {}, // 参数数据映射 { paramValue: { timeStamps: [], chartData: [], ... } }
|
||||||
originalChartData: [], // Original chart data (for sampling) / 原始图表数据(用于采样)
|
statMap: {}, // 参数统计值映射 { paramValue: { latestValue: 0, ... } }
|
||||||
timeRange: '1', // Time range (minutes) / 时间范围(分钟)
|
loadingMap: {}, // 参数加载状态映射 { paramValue: boolean }
|
||||||
socket: null, // Simulated socket / 模拟socket
|
maxXAxisLabels: 40,
|
||||||
latestValue: 0,
|
maxCheckCount: 4
|
||||||
maxValue: 0,
|
|
||||||
minValue: 0,
|
|
||||||
avgValue: 0,
|
|
||||||
loading: false,
|
|
||||||
maxXAxisLabels: 40 // Maximum number of labels displayed on X-axis / X轴最多显示的标签数量
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
enCoilID: {
|
enCoilID: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
console.log('enCoilID changed:', newVal); // enCoilID 变化
|
console.log('enCoilID changed:', newVal);
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
this.fetchChartData();
|
// 批量更新所有已勾选参数的图表
|
||||||
|
this.checkedNodes.forEach((param, index) => {
|
||||||
|
if (index < this.presetChartKeys.length) {
|
||||||
|
this.fetchChartData(param.value, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.drawEmptyChart();
|
// 清空所有图表,并关闭对应loading
|
||||||
|
this.checkedNodes.forEach((param, index) => {
|
||||||
|
const chartKey = this.presetChartKeys[index];
|
||||||
|
this.drawEmptyChart(chartKey);
|
||||||
|
// 强制关闭loading
|
||||||
|
Vue.set(this.loadingMap, param.value, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true
|
||||||
},
|
},
|
||||||
|
// 监听勾选数量变化,触发布局更新(可选,确保响应式)
|
||||||
|
checkedCount: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// 窗口resize强制刷新图表尺寸
|
||||||
|
this.handleResize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// Initialize chart / 初始化图表
|
// 初始化4个固定图表实例(预生成,隐藏状态)
|
||||||
this.initChart();
|
this.initPresetCharts();
|
||||||
|
// 监听窗口resize
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
// Dispose chart instance and event listeners / 销毁图表实例和事件监听
|
// 销毁4个固定图表实例
|
||||||
if (this.chart) {
|
Object.values(this.chartInstances).forEach(chart => {
|
||||||
this.chart.dispose();
|
if (chart && chart.dispose) {
|
||||||
}
|
chart.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Initialize chart / 初始化图表
|
// 初始化4个固定图表
|
||||||
initChart() {
|
initPresetCharts() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.chart) {
|
this.presetChartKeys.forEach(chartKey => {
|
||||||
this.chart = echarts.init(this.$refs.chart);
|
// v-for中的ref返回数组,取第0项(单个DOM元素)
|
||||||
// Listen to window resize, adjust chart size / 监听窗口大小变化,调整图表尺寸
|
const chartDomArr = this.$refs[chartKey];
|
||||||
window.addEventListener('resize', this.handleResize);
|
// 严格校验DOM是否存在,避免传入无效值
|
||||||
|
const chartDom = chartDomArr && chartDomArr.length > 0 ? chartDomArr[0] : null;
|
||||||
// Initial display / 初始显示
|
if (chartDom) {
|
||||||
if (this.enCoilID) {
|
// 创建实例并存储
|
||||||
this.fetchChartData();
|
this.chartInstances[chartKey] = echarts.init(chartDom);
|
||||||
|
// 初始绘制空图表(隐藏状态,不显示)
|
||||||
|
this.drawEmptyChart(chartKey);
|
||||||
} else {
|
} else {
|
||||||
this.drawEmptyChart();
|
console.warn(`图表${chartKey} DOM元素未找到,初始化失败`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理树形节点勾选(限制最多4个)
|
||||||
|
handleCheckChange() {
|
||||||
|
const checkedNodes = this.$refs.paramTree.getCheckedNodes();
|
||||||
|
const checkedCount = checkedNodes.length;
|
||||||
|
|
||||||
|
// 超过4个时取消最后一个并提示
|
||||||
|
if (checkedCount > this.maxCheckCount) {
|
||||||
|
this.$message.warning(`Please select up to ${this.maxCheckCount} parameters.`);
|
||||||
|
const lastNode = checkedNodes[checkedNodes.length - 1];
|
||||||
|
this.$refs.paramTree.setChecked(lastNode.value, false, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先关闭所有旧勾选参数的loading
|
||||||
|
this.checkedNodes.forEach(param => {
|
||||||
|
Vue.set(this.loadingMap, param.value, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新勾选列表和数量
|
||||||
|
this.checkedNodes = this.$refs.paramTree.getCheckedNodes();
|
||||||
|
this.checkedCount = this.checkedNodes.length;
|
||||||
|
|
||||||
|
// 批量更新图表:仅setOption,不重新创建实例
|
||||||
|
this.checkedNodes.forEach((param, index) => {
|
||||||
|
if (index < this.presetChartKeys.length) {
|
||||||
|
const chartKey = this.presetChartKeys[index];
|
||||||
|
if (this.enCoilID) {
|
||||||
|
this.fetchChartData(param.value, index);
|
||||||
|
} else {
|
||||||
|
this.drawEmptyChart(chartKey);
|
||||||
|
// 确保关闭loading
|
||||||
|
Vue.set(this.loadingMap, param.value, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
// Handle window resize / 处理窗口大小变化
|
// 隐藏未用到的图表(已通过display控制,此处可选)
|
||||||
handleResize() {
|
for (let i = this.checkedCount; i < this.presetChartKeys.length; i++) {
|
||||||
if (this.chart) {
|
const chartKey = this.presetChartKeys[i];
|
||||||
this.chart.resize();
|
if (this.chartInstances[chartKey] && this.chartInstances[chartKey].clear) {
|
||||||
}
|
this.chartInstances[chartKey].clear();
|
||||||
},
|
}
|
||||||
|
}
|
||||||
// Get chart data / 获取图表数据
|
},
|
||||||
fetchChartData() {
|
|
||||||
if (!this.enCoilID || !this.paramField) {
|
// 获取对应索引图表的加载状态
|
||||||
this.drawNoDataChart();
|
getChartLoading(index) {
|
||||||
}
|
// 增加兜底校验,避免index越界导致返回undefined(v-loading需要布尔值)
|
||||||
this.loading = true;
|
if (index >= this.checkedNodes.length || !this.checkedNodes[index]?.value) {
|
||||||
getSegmentList({
|
return false;
|
||||||
enCoilID: this.enCoilID,
|
}
|
||||||
paramField: this.paramField
|
const paramValue = this.checkedNodes[index].value;
|
||||||
}).then(res => {
|
// 兜底:若loadingMap中无该属性,返回false
|
||||||
// Assume res.data format is [{ segNo: 'timestamp', value: 'value' }] / 假设res.data格式为[{ segNo: '时间戳', value: '数值' }]
|
return this.loadingMap[paramValue] || false;
|
||||||
if (res.data && res.data.length) {
|
},
|
||||||
this.processChartData(res.data);
|
|
||||||
this.drawLineChart();
|
// 获取对应索引图表的标题
|
||||||
console.log('Data available, drawing line chart') // 有数据,绘制折线图
|
getChartTitle(index) {
|
||||||
} else {
|
if (index >= this.checkedNodes.length) return '';
|
||||||
this.drawNoDataChart();
|
return this.checkedNodes[index].label || '';
|
||||||
console.log('No data, clearing line chart') // 无数据,清空折线图
|
},
|
||||||
|
|
||||||
|
// 窗口resize调整所有图表尺寸
|
||||||
|
handleResize() {
|
||||||
|
Object.values(this.chartInstances).forEach(chart => {
|
||||||
|
if (chart && chart.resize) {
|
||||||
|
chart.resize();
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
|
||||||
console.error('Failed to fetch data:', err); // 获取数据失败
|
|
||||||
this.drawErrorChart();
|
|
||||||
}).finally(_ => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Process chart data / 处理图表数据
|
// 获取单个参数数据并更新对应图表【核心修复:loading响应式赋值】
|
||||||
|
fetchChartData(paramValue, chartIndex) {
|
||||||
|
// 前置校验:参数无效直接关闭loading并绘制无数据图表
|
||||||
|
if (!this.enCoilID || !paramValue || chartIndex >= this.presetChartKeys.length) {
|
||||||
|
const chartKey = this.presetChartKeys[chartIndex];
|
||||||
|
this.drawNoDataChart(chartKey);
|
||||||
|
// 强制关闭loading
|
||||||
|
Vue.set(this.loadingMap, paramValue, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`请求参数: enCoilID=${this.enCoilID}, paramField=${paramValue}`);
|
||||||
|
|
||||||
|
// 修复1:使用Vue.set实现响应式赋值,让视图感知状态变化
|
||||||
|
Vue.set(this.loadingMap, paramValue, true);
|
||||||
|
|
||||||
|
getSegmentList({
|
||||||
|
enCoilID: this.enCoilID,
|
||||||
|
paramField: paramValue
|
||||||
|
}).then(res => {
|
||||||
|
const chartKey = this.presetChartKeys[chartIndex];
|
||||||
|
if (res.data && res.data.length > 0) {
|
||||||
|
// 处理数据
|
||||||
|
const { timeStamps, chartData, originalTimeStamps, originalChartData, stat } = this.processChartData(res.data);
|
||||||
|
this.chartDataMap[paramValue] = {
|
||||||
|
timeStamps,
|
||||||
|
chartData,
|
||||||
|
originalTimeStamps,
|
||||||
|
originalChartData
|
||||||
|
};
|
||||||
|
this.statMap[paramValue] = stat;
|
||||||
|
// 仅setOption更新图表,不重新初始化
|
||||||
|
console.log(res)
|
||||||
|
this.drawLineChart(chartKey, paramValue);
|
||||||
|
} else {
|
||||||
|
this.drawNoDataChart(chartKey);
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(`获取${paramValue}数据失败:`, err);
|
||||||
|
const chartKey = this.presetChartKeys[chartIndex];
|
||||||
|
this.drawErrorChart(chartKey);
|
||||||
|
}).finally(() => {
|
||||||
|
console.log(`完成${paramValue}数据获取, loading关闭`);
|
||||||
|
// 修复2:finally中同样使用Vue.set,确保无论成功失败都关闭loading
|
||||||
|
Vue.set(this.loadingMap, paramValue, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理图表数据
|
||||||
processChartData(rawData) {
|
processChartData(rawData) {
|
||||||
// Sort data (by segNo, assume it's timestamp or sequence number) / 排序数据(按segNo,假设是时间戳或序号)
|
|
||||||
rawData.sort((a, b) => a.segNo - b.segNo);
|
rawData.sort((a, b) => a.segNo - b.segNo);
|
||||||
|
const originalTimeStamps = rawData.map(item => item.segNo);
|
||||||
|
const originalChartData = rawData.map(item => parseFloat(item.value));
|
||||||
|
|
||||||
// Save original data / 保存原始数据
|
let timeStamps = originalTimeStamps;
|
||||||
this.originalTimeStamps = rawData.map(item => item.segNo);
|
let chartData = originalChartData;
|
||||||
this.originalChartData = rawData.map(item => parseFloat(item.value));
|
|
||||||
|
|
||||||
// Decide whether to sample based on data volume / 根据数据量决定是否采样
|
// 可选:数据采样逻辑
|
||||||
// if (rawData.length > this.maxXAxisLabels) {
|
// if (rawData.length > this.maxXAxisLabels) {
|
||||||
// // Data sampling - uniform sampling / 数据采样 - 均匀采样
|
// const sampleInterval = Math.ceil(rawData.length / this.maxXAxisLabels);
|
||||||
// this.sampleData(rawData);
|
// timeStamps = [];
|
||||||
// } else {
|
// chartData = [];
|
||||||
// Data volume is moderate, use all data directly / 数据量适中,直接使用全部数据
|
// for (let i = 0; i < rawData.length; i += sampleInterval) {
|
||||||
this.timeStamps = this.originalTimeStamps;
|
// timeStamps.push(rawData[i].segNo);
|
||||||
this.chartData = this.originalChartData;
|
// 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));
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Calculate statistics (based on original data) / 计算统计值(基于原始数据)
|
let stat = { latestValue: 0, maxValue: 0, minValue: 0, avgValue: 0 };
|
||||||
if (this.originalChartData.length) {
|
if (originalChartData.length) {
|
||||||
this.latestValue = this.originalChartData[this.originalChartData.length - 1];
|
stat.latestValue = originalChartData[originalChartData.length - 1];
|
||||||
this.maxValue = Math.max(...this.originalChartData);
|
stat.maxValue = Math.max(...originalChartData);
|
||||||
this.minValue = Math.min(...this.originalChartData);
|
stat.minValue = Math.min(...originalChartData);
|
||||||
this.avgValue = this.originalChartData.reduce((sum, val) => sum + val, 0) / this.originalChartData.length;
|
stat.avgValue = originalChartData.reduce((sum, val) => sum + val, 0) / originalChartData.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { timeStamps, chartData, originalTimeStamps, originalChartData, stat };
|
||||||
},
|
},
|
||||||
|
|
||||||
// Data sampling method / 数据采样方法
|
// 绘制折线图(仅setOption)
|
||||||
sampleData(rawData) {
|
drawLineChart(chartKey, paramValue) {
|
||||||
const total = rawData.length;
|
const chart = this.chartInstances[chartKey];
|
||||||
const sampleInterval = Math.ceil(total / this.maxXAxisLabels);
|
const chartDataObj = this.chartDataMap[paramValue];
|
||||||
|
const stat = this.statMap[paramValue];
|
||||||
// Initialize sampling array / 初始化采样数组
|
const param = this.paramFields.find(item => item.value === paramValue);
|
||||||
this.timeStamps = [];
|
if (!chart || !chartDataObj) return;
|
||||||
this.chartData = [];
|
|
||||||
|
|
||||||
// Uniform sampling / 均匀采样
|
|
||||||
for (let i = 0; i < total; i += sampleInterval) {
|
|
||||||
this.timeStamps.push(rawData[i].segNo);
|
|
||||||
this.chartData.push(parseFloat(rawData[i].value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the last data point is included / 确保最后一个数据点被包含
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Draw line chart / 绘制折线图
|
|
||||||
drawLineChart() {
|
|
||||||
if (!this.chart) return;
|
|
||||||
|
|
||||||
// Clear old layers to avoid "No data" residue / 清理旧图层,避免"暂无数据"残留
|
|
||||||
this.chart.clear();
|
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
graphic: [], // 清除占位/无数据图层
|
graphic: [],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: { type: 'line' },
|
||||||
type: 'line'
|
|
||||||
},
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
borderColor: '#d4d4d4',
|
borderColor: '#d4d4d4',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
textStyle: {
|
textStyle: { color: '#333' },
|
||||||
color: '#333'
|
|
||||||
},
|
|
||||||
formatter: (params) => {
|
formatter: (params) => {
|
||||||
const param = params[0];
|
const paramItem = params[0];
|
||||||
return `
|
return `
|
||||||
Sequence: ${param.name}<br>
|
Sequence: ${paramItem.name}<br>
|
||||||
<!-- 序号 -->
|
Value: ${paramItem.value.toFixed(2)}<br>
|
||||||
Value: ${param.value.toFixed(2)}<br>
|
Unit: ${this.getParamUnit(paramValue)}
|
||||||
<!-- 值 -->
|
|
||||||
Unit: ${this.getParamUnit()}
|
|
||||||
<!-- 单位 -->
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: '3%',
|
||||||
right: '4%',
|
right: '4%',
|
||||||
bottom: '10%', // Increase bottom space to prevent label truncation / 增加底部空间,防止标签被截断
|
bottom: '10%',
|
||||||
top: '15%',
|
top: '15%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
// data: this.timeStamps,
|
data: chartDataObj.timeStamps,
|
||||||
// axisLine: {
|
axisLine: { lineStyle: { color: '#d4d4d4' } },
|
||||||
// lineStyle: {
|
axisLabel: {
|
||||||
// color: '#d4d4d4'
|
color: '#666',
|
||||||
// }
|
rotate: 45,
|
||||||
// },
|
interval: (index) => {
|
||||||
// axisLabel: {
|
return index % Math.max(1, Math.ceil(chartDataObj.timeStamps.length / this.maxXAxisLabels)) === 0;
|
||||||
// color: '#666',
|
}
|
||||||
// rotate: 45,
|
},
|
||||||
// // 动态计算标签显示间隔
|
splitLine: { show: false }
|
||||||
// interval: (index, value) => {
|
|
||||||
// // 如果数据量大于maxXAxisLabels,按采样后的间隔显示
|
|
||||||
// return index % Math.max(1, Math.ceil(this.timeStamps.length / this.maxXAxisLabels)) === 0;
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
splitLine: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
name: this.getParamUnit(),
|
name: this.getParamUnit(paramValue),
|
||||||
nameLocation: 'middle',
|
nameLocation: 'middle',
|
||||||
nameGap: 30,
|
nameGap: 30,
|
||||||
nameTextStyle: {
|
nameTextStyle: { color: '#666' },
|
||||||
color: '#666'
|
axisLine: { lineStyle: { color: '#d4d4d4' } },
|
||||||
},
|
axisLabel: { color: '#666' },
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#d4d4d4'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#666'
|
|
||||||
},
|
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: { color: '#e8e8e8', type: 'dashed' }
|
||||||
color: '#e8e8e8',
|
|
||||||
type: 'dashed'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: this.getParamLabel(),
|
name: param?.label || paramValue,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: this.chartData,
|
data: chartDataObj.chartData,
|
||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 4,
|
symbolSize: 4,
|
||||||
lineStyle: {
|
lineStyle: { color: '#666', width: 2 },
|
||||||
color: '#666',
|
itemStyle: { color: '#999' },
|
||||||
width: 2
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
color: '#999'
|
|
||||||
},
|
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
color: {
|
color: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
@@ -310,39 +395,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
markPoint: {
|
markPoint: {
|
||||||
data: [
|
data: [{ type: 'max', name: 'Max' }, { type: 'min', name: 'Min' }],
|
||||||
{ type: 'max', name: 'Max' }, // 最大值
|
itemStyle: { color: '#999' },
|
||||||
{ type: 'min', name: 'Min' } // 最小值
|
label: { color: '#333' }
|
||||||
],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#999'
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: '#333'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
markLine: {
|
markLine: {
|
||||||
data: [
|
data: [{ type: 'average', name: 'Average' }],
|
||||||
{ type: 'average', name: 'Average' } // 平均值
|
lineStyle: { color: '#999', type: 'dashed' },
|
||||||
],
|
label: { color: '#333' }
|
||||||
lineStyle: {
|
|
||||||
color: '#999',
|
|
||||||
type: 'dashed'
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: '#333'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chart.setOption(option, true);
|
chart.setOption(option, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Draw empty chart (no performance selected) / 绘制空图表(未选择实绩)
|
// 绘制空图表
|
||||||
drawEmptyChart() {
|
drawEmptyChart(chartKey) {
|
||||||
if (!this.chart) return;
|
const chart = this.chartInstances[chartKey];
|
||||||
|
if (!chart) return;
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
graphic: {
|
graphic: {
|
||||||
@@ -352,7 +424,7 @@ export default {
|
|||||||
left: 'center',
|
left: 'center',
|
||||||
top: 'center',
|
top: 'center',
|
||||||
style: {
|
style: {
|
||||||
text: 'Please select performance', // 请选择实绩
|
text: 'Please select performance',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fill: '#999'
|
fill: '#999'
|
||||||
@@ -360,25 +432,18 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: { type: 'category', data: [] },
|
||||||
type: 'category',
|
yAxis: { type: 'value' },
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: []
|
series: []
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chart.setOption(option);
|
chart.setOption(option);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Draw no data chart / 绘制无数据图表
|
// 绘制无数据图表
|
||||||
drawNoDataChart() {
|
drawNoDataChart(chartKey) {
|
||||||
if (!this.chart) return;
|
const chart = this.chartInstances[chartKey];
|
||||||
|
if (!chart) return;
|
||||||
// Clear original chart content first / 先清除原有图表内容
|
|
||||||
this.chart.clear();
|
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
graphic: {
|
graphic: {
|
||||||
@@ -388,7 +453,7 @@ export default {
|
|||||||
left: 'center',
|
left: 'center',
|
||||||
top: 'center',
|
top: 'center',
|
||||||
style: {
|
style: {
|
||||||
text: 'No data', // 暂无数据
|
text: 'No data',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fill: '#999'
|
fill: '#999'
|
||||||
@@ -396,22 +461,18 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: { type: 'category', data: [] },
|
||||||
type: 'category',
|
yAxis: { type: 'value' },
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: []
|
series: []
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chart.setOption(option);
|
chart.setOption(option);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Draw error chart / 绘制错误图表
|
// 绘制错误图表
|
||||||
drawErrorChart() {
|
drawErrorChart(chartKey) {
|
||||||
if (!this.chart) return;
|
const chart = this.chartInstances[chartKey];
|
||||||
|
if (!chart) return;
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
graphic: {
|
graphic: {
|
||||||
@@ -421,7 +482,7 @@ export default {
|
|||||||
left: 'center',
|
left: 'center',
|
||||||
top: 'center',
|
top: 'center',
|
||||||
style: {
|
style: {
|
||||||
text: 'Data loading failed', // 数据加载失败
|
text: 'Data loading failed',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fill: '#f56c6c'
|
fill: '#f56c6c'
|
||||||
@@ -429,53 +490,31 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: { type: 'category', data: [] },
|
||||||
type: 'category',
|
yAxis: { type: 'value' },
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: []
|
series: []
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chart.setOption(option);
|
chart.setOption(option);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handle parameter change / 处理参数变更
|
// 获取参数单位
|
||||||
handleParamChange() {
|
getParamUnit(paramValue) {
|
||||||
// Clear existing data / 清空现有数据
|
switch (paramValue) {
|
||||||
this.chartData = [];
|
|
||||||
this.timeStamps = [];
|
|
||||||
this.originalTimeStamps = [];
|
|
||||||
this.originalChartData = [];
|
|
||||||
|
|
||||||
// Re-fetch data / 重新获取数据
|
|
||||||
if (this.enCoilID) {
|
|
||||||
this.fetchChartData();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get parameter label / 获取参数标签
|
|
||||||
getParamLabel() {
|
|
||||||
const param = this.paramFields.find(item => item.value === this.paramField);
|
|
||||||
return param ? param.label : this.paramField;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get parameter unit / 获取参数单位
|
|
||||||
getParamUnit() {
|
|
||||||
// Return different units based on different parameters / 根据不同参数返回不同单位
|
|
||||||
switch (this.paramField) {
|
|
||||||
case 'stripSpeed':
|
case 'stripSpeed':
|
||||||
|
case 'stripSpeedTmExit':
|
||||||
return 'm/s';
|
return 'm/s';
|
||||||
case 'tensionPorBr1':
|
case 'tensionPorBr1':
|
||||||
case 'tensionPorBr2':
|
case 'tensionPorBr2':
|
||||||
|
case 'tensionBr5Tm':
|
||||||
|
case 'tensionTlBr7':
|
||||||
return 'N';
|
return 'N';
|
||||||
case 'cleaningVoltage':
|
case 'cleaningVoltage':
|
||||||
return 'V';
|
return 'V';
|
||||||
case 'cleaningCurrent':
|
case 'cleaningCurrent':
|
||||||
return 'A';
|
return 'A';
|
||||||
case 'alkaliConcentration':
|
case 'alkaliConcentration':
|
||||||
|
case 'tlElongation':
|
||||||
return '%';
|
return '%';
|
||||||
case 'alkaliTemperature':
|
case 'alkaliTemperature':
|
||||||
case 'phfExitStripTemp':
|
case 'phfExitStripTemp':
|
||||||
@@ -489,13 +528,6 @@ export default {
|
|||||||
return 'kW';
|
return 'kW';
|
||||||
case 'gasConsumption':
|
case 'gasConsumption':
|
||||||
return 'm³/h';
|
return 'm³/h';
|
||||||
case 'tensionBr5Tm':
|
|
||||||
case 'tensionTlBr7':
|
|
||||||
return 'N';
|
|
||||||
case 'stripSpeedTmExit':
|
|
||||||
return 'm/s';
|
|
||||||
case 'tlElongation':
|
|
||||||
return '%';
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -506,33 +538,88 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.monitoring-container {
|
.monitoring-container {
|
||||||
height: 100%;
|
height: 100%; /* 根容器高度100%,可根据父容器调整为100% */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-wrapper {
|
.charts-container {
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
position: relative;
|
||||||
flex-direction: column;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-select {
|
/* 1个勾选:占满所有空间 */
|
||||||
position: absolute;
|
.count-1 {
|
||||||
top: 10px;
|
display: flex;
|
||||||
right: 10px;
|
flex-direction: column;
|
||||||
z-index: 10;
|
|
||||||
width: 150px;
|
|
||||||
|
|
||||||
::v-deep .el-input__inner {
|
.preset-chart-item {
|
||||||
border-color: #d4d4d4;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 2个勾选:纵向排布,横向占满,上下各50% */
|
||||||
|
.count-2 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px; /* 上下图表间距 */
|
||||||
|
|
||||||
|
.preset-chart-item {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(50% - 5px); /* 减去间距的一半,保证总高度100% */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3个/4个勾选:2*2格子布局 */
|
||||||
|
.count-3,
|
||||||
|
.count-4 {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px; /* 图表间距 */
|
||||||
|
|
||||||
|
.preset-chart-item {
|
||||||
|
width: calc(50% - 5px); /* 横向两列,减去间距的一半 */
|
||||||
|
height: calc(50% - 5px); /* 纵向两行,减去间距的一半 */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表项通用样式 */
|
||||||
|
.preset-chart-item {
|
||||||
|
position: relative;
|
||||||
|
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 {
|
.chart-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
height: calc(100% - 24px); /* 减去标题高度 */
|
||||||
min-height: 0;
|
box-sizing: border-box;
|
||||||
|
min-height: 180px; /* 兜底高度,确保ECharts初始化有尺寸 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 无勾选提示 */
|
||||||
|
.no-param-tip {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -10,14 +10,14 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Start Date" prop="startDate">
|
<el-form-item label="Start Date" prop="startDate">
|
||||||
<!-- 开始日期 -->
|
<!-- 开始日期 -->
|
||||||
<el-date-picker v-model="queryForm.startDate" type="date" placeholder="Select start date" value-format="yyyy-MM-dd"
|
<el-date-picker v-model="queryForm.startDate" type="date" placeholder="Select start date"
|
||||||
clearable></el-date-picker>
|
value-format="yyyy-MM-dd" clearable></el-date-picker>
|
||||||
<!-- 选择开始日期 -->
|
<!-- 选择开始日期 -->
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="End Date" prop="endDate">
|
<el-form-item label="End Date" prop="endDate">
|
||||||
<!-- 结束日期 -->
|
<!-- 结束日期 -->
|
||||||
<el-date-picker v-model="queryForm.endDate" type="date" placeholder="Select end date" value-format="yyyy-MM-dd"
|
<el-date-picker v-model="queryForm.endDate" type="date" placeholder="Select end date"
|
||||||
clearable></el-date-picker>
|
value-format="yyyy-MM-dd" clearable></el-date-picker>
|
||||||
<!-- 选择结束日期 -->
|
<!-- 选择结束日期 -->
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -33,122 +33,48 @@
|
|||||||
|
|
||||||
<!-- Card grid layout / 卡片网格布局 -->
|
<!-- Card grid layout / 卡片网格布局 -->
|
||||||
<div v-loading="tableLoading" class="card-grid-container">
|
<div v-loading="tableLoading" class="card-grid-container">
|
||||||
<el-row :gutter="10">
|
<el-table :data="tableData" style="width: 100%" border stripe @row-click="handleRowClick">
|
||||||
<el-col v-for="(item, index) in tableData" :key="index" :xs="24" :sm="12" :md="8" :lg="6" :xl="5"
|
<el-table-column prop="exitMatId" label="exitMat"></el-table-column>
|
||||||
class="card-col">
|
<el-table-column prop="entryMatId" label="entryMat"></el-table-column>
|
||||||
<el-card class="parameter-card" shadow="never" :body-style="{ padding: '8px' }"
|
<el-table-column prop="planNo" label="PlanNo"></el-table-column>
|
||||||
:class="{ 'card-selected': currentRow.exitMatId === item.exitMatId }" @click.native="handleRowClick(item)">
|
|
||||||
<div slot="header" class="card-header">
|
|
||||||
<div class="card-title">Finished Coil: {{ item.exitMatId || '-' }}</div>
|
|
||||||
<!-- 成品卷 -->
|
|
||||||
<div class="card-subtitle">{{ item.entryMatId || '-' }} | {{ item.planNo || '-' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="param-groups-row">
|
|
||||||
<!-- Basic info / 基本信息 -->
|
|
||||||
<div class="param-group">
|
|
||||||
<div class="group-title">Basic Info</div>
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<div class="param-list">
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Status:</span>
|
|
||||||
<!-- 状态 -->
|
|
||||||
<span class="param-value">{{ item.status || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Steel Grade:</span>
|
|
||||||
<!-- 钢种 -->
|
|
||||||
<span class="param-value">{{ item.steelGrade || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Product Type:</span>
|
|
||||||
<!-- 产品类型 -->
|
|
||||||
<span class="param-value">{{ item.prodCode || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Customer:</span>
|
|
||||||
<!-- 客户 -->
|
|
||||||
<span class="param-value">{{ item.customer || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Entry material info / 来料信息 -->
|
<el-table-column prop="status" label="Status"></el-table-column>
|
||||||
<div class="param-group">
|
<el-table-column prop="steelGrade" label="SteelGrade"></el-table-column>
|
||||||
<div class="group-title">Entry Material Info</div>
|
<el-table-column prop="prodCode" label="ProductType"></el-table-column>
|
||||||
<!-- 来料信息 -->
|
<el-table-column prop="customer" label="Customer"></el-table-column>
|
||||||
<div class="param-list">
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Thickness:</span>
|
|
||||||
<!-- 厚度 -->
|
|
||||||
<span class="param-value">{{ item.entryThick || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Width:</span>
|
|
||||||
<!-- 宽度 -->
|
|
||||||
<span class="param-value">{{ item.entryWidth || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Length:</span>
|
|
||||||
<!-- 长度 -->
|
|
||||||
<span class="param-value">{{ item.entryLength || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
|
||||||
<span class="param-label">Weight:</span>
|
|
||||||
<!-- 重量 -->
|
|
||||||
<span class="param-value">{{ item.entryWeight || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Finished product info / 成品信息 -->
|
<el-table-column prop="entryThick" label="EntryThickness"></el-table-column>
|
||||||
<div class="param-group">
|
<el-table-column prop="entryWidth" label="EntryWidth"></el-table-column>
|
||||||
<div class="group-title">Finished Product Info</div>
|
<el-table-column prop="entryLength" label="EntryLength"></el-table-column>
|
||||||
<!-- 成品信息 -->
|
<el-table-column prop="entryWeight" label="EntryWeight"></el-table-column>
|
||||||
<div class="param-list">
|
|
||||||
<div class="param-line">
|
<el-table-column prop="exitThickness" label="ExitThickness"></el-table-column>
|
||||||
<span class="param-label">Thickness:</span>
|
<el-table-column prop="exitWidth" label="ExitWidth"></el-table-column>
|
||||||
<!-- 厚度 -->
|
<el-table-column prop="exitLength" label="ExitLength"></el-table-column>
|
||||||
<span class="param-value">{{ item.exitThickness || '-' }}</span>
|
<el-table-column prop="exitNetWeight" label="ExitNetWeight"></el-table-column>
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
<el-table-column label="Operation">
|
||||||
<span class="param-label">Width:</span>
|
<template slot-scope="scope">
|
||||||
<!-- 宽度 -->
|
<el-button size="mini" type="text" icon="el-icon-view"
|
||||||
<span class="param-value">{{ item.exitWidth || '-' }}</span>
|
@click.stop="handlePrint(scope.row)">Print</el-button>
|
||||||
</div>
|
<!-- 打印 -->
|
||||||
<div class="param-line">
|
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleEdit(scope.row)">Edit</el-button>
|
||||||
<span class="param-label">Length:</span>
|
<!-- 操作 -->
|
||||||
<!-- 长度 -->
|
<el-button size="mini" type="text" icon="el-icon-document"
|
||||||
<span class="param-value">{{ item.exitLength || '-' }}</span>
|
@click.stop="handleQualityCertificate(scope.row)">Quality Certificate</el-button>
|
||||||
</div>
|
<!-- 质保书 -->
|
||||||
<div class="param-line">
|
<el-button size="mini" type="text" icon="el-icon-delete" :loading="scope.row.deleteLoading"
|
||||||
<span class="param-label">Weight:</span>
|
@click.stop="handleDelete(scope.row)">Delete</el-button>
|
||||||
<!-- 重量 -->
|
<!-- 删除 -->
|
||||||
<span class="param-value">{{ item.exitNetWeight || '-' }}</span>
|
</template>
|
||||||
</div>
|
</el-table-column>
|
||||||
</div>
|
</el-table>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<el-button size="mini" type="text" icon="el-icon-view" @click.stop="handlePrint(item)">Print</el-button>
|
|
||||||
<!-- 打印 -->
|
|
||||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleEdit(item)">Edit</el-button>
|
|
||||||
<!-- 操作 -->
|
|
||||||
<el-button size="mini" type="text" icon="el-icon-document" @click.stop="handleQualityCertificate(item)">Quality Certificate</el-button>
|
|
||||||
<!-- 质保书 -->
|
|
||||||
<el-button size="mini" type="text" icon="el-icon-delete" :loading="item.deleteLoading"
|
|
||||||
@click.stop="handleDelete(item)">Delete</el-button>
|
|
||||||
<!-- 删除 -->
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<div v-if="tableData.length === 0 && !tableLoading" class="empty-data">
|
<div v-if="tableData.length === 0 && !tableLoading" class="empty-data">
|
||||||
<el-empty description="No data"></el-empty>
|
<el-empty description="No data"></el-empty>
|
||||||
<!-- 暂无数据 -->
|
<!-- 暂无数据 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<pdo-summary :table-data="tableData" />
|
||||||
|
|
||||||
<!-- Statistics summary and chart area / 统计汇总和图表区域 -->
|
<!-- Statistics summary and chart area / 统计汇总和图表区域 -->
|
||||||
<div class="statistics-container">
|
<div class="statistics-container">
|
||||||
@@ -169,18 +95,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-row :gutter="15" class="statistics-content">
|
<el-row :gutter="15" class="statistics-content">
|
||||||
<el-col :span="4" class="summary-col">
|
<div class="chart-wrapper">
|
||||||
<div class="summary-wrapper">
|
<line-chart :enCoilID="currentRow.entryMatId" />
|
||||||
<div class="summary-header">Statistics Summary</div>
|
</div>
|
||||||
<!-- 统计汇总 -->
|
|
||||||
<pdo-summary :table-data="tableData" />
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="20" class="chart-col">
|
|
||||||
<div class="chart-wrapper">
|
|
||||||
<line-chart :enCoilID="currentRow.entryMatId" />
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -194,15 +111,15 @@
|
|||||||
|
|
||||||
<!-- Edit: keep original tab, show data correction + label print / 编辑:保留原有tab,显示数据修正+标签打印 -->
|
<!-- Edit: keep original tab, show data correction + label print / 编辑:保留原有tab,显示数据修正+标签打印 -->
|
||||||
<!-- <el-tabs v-else v-model="activeTab"> -->
|
<!-- <el-tabs v-else v-model="activeTab"> -->
|
||||||
<!-- <el-tab-pane label="Data Correction" name="basicInfo"> -->
|
<!-- <el-tab-pane label="Data Correction" name="basicInfo"> -->
|
||||||
<!-- 数据修正 -->
|
<!-- 数据修正 -->
|
||||||
<pdo-data-correction :detail="formData" :save-callback="handleSave"
|
<pdo-data-correction :detail="formData" :save-callback="handleSave"
|
||||||
:save-loading="saveLoading"></pdo-data-correction>
|
:save-loading="saveLoading"></pdo-data-correction>
|
||||||
<!-- </el-tab-pane> -->
|
<!-- </el-tab-pane> -->
|
||||||
<!-- <el-tab-pane label="Label Print" name="labelPrint"> -->
|
<!-- <el-tab-pane label="Label Print" name="labelPrint"> -->
|
||||||
<!-- 标签打印 -->
|
<!-- 标签打印 -->
|
||||||
|
|
||||||
<!-- </el-tab-pane>
|
<!-- </el-tab-pane>
|
||||||
</el-tabs> -->
|
</el-tabs> -->
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -393,16 +310,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pdo-header {
|
.pdo-header {
|
||||||
background: #ffffff;
|
|
||||||
border: 1px solid #d4d4d4;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 15px;
|
margin-bottom: 10px;
|
||||||
margin-bottom: 15px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid-container {
|
.card-grid-container {
|
||||||
flex: 1;
|
flex: 3;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
@@ -552,7 +466,7 @@ export default {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 280px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statistics-header {
|
.statistics-header {
|
||||||
@@ -601,7 +515,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.statistics-content {
|
.statistics-content {
|
||||||
flex: 1;
|
flex: 5;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user