From b44d9d9daff2ebb15b64034339ecf311dabc20c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= <2178503051@qq.com>
Date: Tue, 12 May 2026 14:18:01 +0800
Subject: [PATCH] =?UTF-8?q?refactor(wms/coil):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E7=94=9F=E4=BA=A7=E5=B7=A5=E8=89=BA=E6=95=B0=E6=8D=AE=E5=B1=95?=
=?UTF-8?q?=E7=A4=BA=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 将接口从get请求改为post请求,适配后端参数变更
2. 重新设计生产工艺数据页面,拆分为趋势参数、厚度曲线、带钢板形、板形曲线四个标签页
3. 新增趋势参数树形侧边栏,支持分类展开选择查看不同参数曲线
4. 新增厚度、板形3D热力图、板形曲线图表展示
5. 优化图表渲染逻辑和样式,统一数据处理与加载状态
---
klp-ui/src/api/wms/coil.js | 6 +-
klp-ui/src/views/wms/coil/info.vue | 816 ++++++++++++++++++++++-------
2 files changed, 626 insertions(+), 196 deletions(-)
diff --git a/klp-ui/src/api/wms/coil.js b/klp-ui/src/api/wms/coil.js
index d1e24225..fe86c1df 100644
--- a/klp-ui/src/api/wms/coil.js
+++ b/klp-ui/src/api/wms/coil.js
@@ -392,11 +392,11 @@ export function listTypeErrorCoil() {
})
}
-export function getCoilStatisticsList(params) {
+export function getCoilStatisticsList(data) {
return request({
url: '/wms/materialCoil/statisticsList',
- method: 'get',
- params,
+ method: 'post',
+ data,
})
}
diff --git a/klp-ui/src/views/wms/coil/info.vue b/klp-ui/src/views/wms/coil/info.vue
index c9c297da..bc73760f 100644
--- a/klp-ui/src/views/wms/coil/info.vue
+++ b/klp-ui/src/views/wms/coil/info.vue
@@ -734,22 +734,71 @@
-
-
+
+
+
+
+ 暂无生产数据
+ 加载中…
+
+
+
+
+
+ {{ group.label }}
+
+
+
+
+
+
+
+
+
+
+ 暂无厚度数据
+
+
+
+
+
+ 暂无板形数据
+
+
+
+
+
+ 暂无板形数据
+
+
+
@@ -766,11 +815,123 @@ import { listTransferOrderItem } from '@/api/wms/transferOrderItem'
import { listCoilQualityRejudge } from '@/api/wms/coilQualityRejudge'
// 引入 ECharts 和 L2 时序数据 API
import * as echarts from 'echarts'
-import { getTimingSegByEncoilId, getTimingPlanDetailByHotcoilId } from '@/api/l2/timing'
+import 'echarts-gl'
+import { getTimingSegByEncoilId, getTimingPlanDetailByHotcoilId, getTimingRealtimeData } from '@/api/l2/timing'
import AbnormalTable from '@/views/wms/coil/components/AbnormalTable.vue';
import FileList from "@/components/FileList";
+const TREND_GROUPS = [
+ {
+ label: '张力',
+ children: [
+ { label: '开卷张力', col: 'PORTENS' },
+ { label: '入口活套张力', col: 'ENLTENS' },
+ { label: '拉矫张力', col: 'TLTENS' },
+ { label: '酸洗张力', col: 'PLTENS' },
+ { label: '出口活套张力', col: 'CXLTENS' },
+ { label: '圆盘剪张力', col: 'TRIMTENS' }
+ ]
+ },
+ {
+ label: '速度',
+ children: [
+ { label: '开卷速度', col: 'PORSPEED' },
+ { label: '酸洗速度', col: 'PLSPEED' },
+ { label: '圆盘剪速度', col: 'TRIMSPEED' },
+ { label: '轧机入口速度', col: 'MILLENTRYSPEED' },
+ { label: '轧机出口速度', col: 'MILLEXITSPEED' }
+ ]
+ },
+ {
+ label: '拉矫机',
+ children: [
+ { label: '1#插入量', col: 'TLMESH1' },
+ { label: '2#插入量', col: 'TLMESH2' },
+ { label: '3#插入量', col: 'TLMESH3' },
+ { label: '延伸率', col: 'TLELONG' }
+ ]
+ },
+ {
+ label: '酸洗段',
+ children: [
+ { label: '1#温度', col: 'TK1TEMP' },
+ { label: '2#温度', col: 'TK2TEMP' },
+ { label: '3#温度', col: 'TK3TEMP' },
+ { label: '漂洗温度', col: 'RINSETEMP' }
+ ]
+ }
+]
+
+const GAUGE_COLS = [
+ { col: 'THICK0', title: '入口测厚仪 [mm]' },
+ { col: 'THICK1', title: '1架出口厚度 [mm]' },
+ { col: 'THICK4', title: '末架出口厚度 [mm]' },
+ { col: 'EXIT_SPEED', title: '轧制速度 [m/min]' }
+]
+
+const SHAPE_SCALAR_COLS = [
+ { col: 'ABSDEVIATION', title: '总板形偏差 [IU]' },
+ { col: 'TILT', title: '末架倾斜量 [mm]' },
+ { col: 'WRBEND', title: '工作辊弯辊力 [kN]' },
+ { col: 'IRBEND', title: '中间辊弯辊力 [kN]' }
+]
+
+function calcYRange(vals) {
+ const nums = vals.filter(v => v != null && isFinite(Number(v))).map(Number)
+ if (!nums.length) return {}
+ const min = Math.min(...nums)
+ const max = Math.max(...nums)
+ if (min === max) {
+ const base = Math.abs(min) || 1
+ return { min: parseFloat((min - base * 0.2).toFixed(4)), max: parseFloat((max + base * 0.2).toFixed(4)) }
+ }
+ const pad = (max - min) * 0.15
+ return {
+ min: parseFloat((min - pad).toFixed(4)),
+ max: parseFloat((max + pad).toFixed(4))
+ }
+}
+
+function makeLine(title, xData, yData) {
+ const range = calcYRange(yData)
+ return {
+ title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
+ tooltip: { trigger: 'axis' },
+ grid: { top: 36, bottom: 28, left: 8, right: 16, containLabel: true },
+ xAxis: {
+ type: 'category', data: xData,
+ name: 'pos(m)', nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 }
+ },
+ yAxis: {
+ type: 'value',
+ min: range.min,
+ max: range.max,
+ nameTextStyle: { fontSize: 10 },
+ axisLabel: { fontSize: 10 }
+ },
+ dataZoom: [
+ { type: 'inside', xAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true },
+ { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: false, moveOnMouseMove: true }
+ ],
+ series: [{
+ name: title, type: 'line', smooth: false, symbol: 'none',
+ lineStyle: { width: 1 }, data: yData
+ }]
+ }
+}
+
+function getRowVal(row, col) {
+ const v = row[col] !== undefined ? row[col] : row[col.toLowerCase()]
+ return v == null ? null : Number(v)
+}
+
+function xLocData(rows) {
+ return rows.map(r => {
+ const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation
+ return v == null ? '' : Number(v).toFixed(1)
+ })
+}
export default {
name: 'CoilInfo',
@@ -779,30 +940,45 @@ export default {
FileList
},
data() {
- return {
- loading: false,
- coilInfoLoading: false,
- traceLoading: false,
- traceResult: null,
- coilDetails: {},
- loadingCoilDetails: false,
- coilInfo: {},
- coilList: [],
- wipList: [],
- annealList: [],
- standardSteps: [],
- coilId: '',
- warehouseTranferList: [],
- abmornalList: [],
- transferOrderItemList: [], // 批量调拨记录
- coilQualityRejudgeList: [], // 技术部改判记录
- tranferList: [], // 合并后的调拨记录
- // 生产数据相关
- perfLoading: false,
- perfSeries: null,
- perfSegCount: 0
- }
- },
+ return {
+ loading: false,
+ coilInfoLoading: false,
+ traceLoading: false,
+ traceResult: null,
+ coilDetails: {},
+ loadingCoilDetails: false,
+ coilInfo: {},
+ coilList: [],
+ wipList: [],
+ annealList: [],
+ standardSteps: [],
+ coilId: '',
+ warehouseTranferList: [],
+ abmornalList: [],
+ transferOrderItemList: [], // 批量调拨记录
+ coilQualityRejudgeList: [], // 技术部改判记录
+ tranferList: [], // 合并后的调拨记录
+ // 生产数据相关
+ perfLoading: false,
+ perfSeries: null,
+ perfSegCount: 0,
+ // 新增图表数据状态
+ segLoading: false,
+ realtimeLoading: false,
+ segData: null,
+ gaugeRows: null,
+ shapeRows: null,
+ perfActiveTab: 'trend',
+ // 趋势参数树状态
+ trendGroups: TREND_GROUPS,
+ expandedGroups: { '张力': true, '速度': true, '拉矫机': true, '酸洗段': true },
+ selectedTrendParam: null,
+ trendChartInst: null,
+ _trendResizeFn: null,
+ chartInstances: [],
+ resizeHandler: null
+ }
+ },
computed: {
salesInfo() {
return this.coilInfo.orderList?.[0] || {};
@@ -1153,27 +1329,6 @@ export default {
}
return step.newCoilInfoList;
},
- // ECharts 辅助函数
- makeLine(name, data) {
- return { name, type: 'line', smooth: true, symbol: 'none', data };
- },
- baseOption(title, xData, series, yName) {
- return {
- title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
- tooltip: { trigger: 'axis' },
- legend: { top: 4, right: 8, textStyle: { fontSize: 11 } },
- grid: { top: 36, bottom: 28, left: 8, right: 8, containLabel: true },
- xAxis: {
- type: 'category',
- data: xData,
- name: 'pos(m)',
- nameTextStyle: { fontSize: 10 },
- axisLabel: { fontSize: 10 }
- },
- yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 } },
- series
- };
- },
// 加载生产数据
async loadProductionData() {
const hotCoilId = this.coilInfo.enterCoilNo;
@@ -1181,166 +1336,302 @@ export default {
this.perfLoading = true;
try {
- // 先查询详情
const detail = await getTimingPlanDetailByHotcoilId(hotCoilId);
const encoilId = detail?.data?.firstRow?.coilid || '';
- if (!encoilId) return;
+ // V_VBDA_GAUGE / V_VBDA_SHAPE 使用 EXCOILID 作为 MATID,对应入场卷号
+ const excoilId = this.coilInfo.enterCoilNo;
- const res = await getTimingSegByEncoilId(encoilId);
- const series = res?.data?.series || null;
- const rows = res?.data?.rows || [];
+ const [segRes, realtimeRes] = await Promise.all([
+ encoilId ? getTimingSegByEncoilId(encoilId) : Promise.resolve({ data: null }),
+ encoilId ? getTimingRealtimeData(encoilId) : Promise.resolve({ data: null })
+ ]);
+
+ const series = segRes?.data?.series || null;
+ const rows = segRes?.data?.rows || [];
this.perfSegCount = rows.length;
this.perfSeries = series;
+ this.segData = series;
+
+ const g = realtimeRes?.data?.gauge?.result;
+ const s = realtimeRes?.data?.shape?.result;
+ this.gaugeRows = Array.isArray(g) ? g : null;
+ this.shapeRows = Array.isArray(s) ? s : null;
if (series && rows.length) {
await this.$nextTick();
- this.renderCharts(series);
+ this.selectTrendParam(TREND_GROUPS[0].children[0]);
+ // 数据加载完成后,根据当前选中的标签页渲染对应图表
+ this.renderCurrentTab();
+ } else if (this.gaugeRows?.length || this.shapeRows?.length) {
+ await this.$nextTick();
+ this.renderCurrentTab();
}
} catch (error) {
console.error('获取生产数据异常:', error);
this.perfSeries = null;
this.perfSegCount = 0;
+ this.segData = null;
+ this.gaugeRows = null;
+ this.shapeRows = null;
} finally {
this.perfLoading = false;
}
},
// 销毁图表
disposeCharts() {
+ this.disposeSideCharts();
+ if (this._trendResizeFn) {
+ window.removeEventListener('resize', this._trendResizeFn);
+ this._trendResizeFn = null;
+ }
+ if (this.trendChartInst && !this.trendChartInst.isDisposed()) {
+ this.trendChartInst.dispose();
+ this.trendChartInst = null;
+ }
+ },
+ disposeSideCharts() {
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
}
- if (this.chartInstances && this.chartInstances.length) {
- this.chartInstances.forEach(c => { if (c) c.dispose(); });
- this.chartInstances = [];
- }
+ this.chartInstances.forEach(c => { if (c && !c.isDisposed()) c.dispose(); });
+ this.chartInstances = [];
},
- // 渲染图表
- renderCharts(series) {
- this.disposeCharts();
- if (!this.$refs.chartSpeed || !this.$refs.chartMillSpeed || !this.$refs.chartTension) {
- return;
- }
+ // 趋势参数树操作
+ toggleTrendGroup(label) {
+ this.$set(this.expandedGroups, label, !this.expandedGroups[label]);
+ },
+ selectTrendParam(item) {
+ this.selectedTrendParam = item;
+ this.$nextTick(() => this.renderTrendSingleChart());
+ },
+ renderTrendSingleChart() {
+ if (!this.selectedTrendParam || !this.segData) return;
+ const el = this.$refs.trendSingleChart;
+ if (!el) return;
- const pick = key => (series[key] || []).map(v => v == null ? null : Number(v).toFixed(2));
- const xData = (series.startpos || []).map(v => v == null ? '' : Number(v).toFixed(1));
-
- const c1 = echarts.init(this.$refs.chartSpeed);
- c1.setOption(this.baseOption(
- '速度趋势 (m/min)', xData,
- [
- this.makeLine('轧制速度 plspeed', pick('plspeed')),
- this.makeLine('剪切速度 trimspeed', pick('trimspeed'))
- ],
- 'm/min'
- ));
-
- const c2 = echarts.init(this.$refs.chartMillSpeed);
- c2.setOption(this.baseOption(
- '轧机速度 (m/min)', xData,
- [
- this.makeLine('入口速度 millentryspeed', pick('millentryspeed')),
- this.makeLine('出口速度 millexitspeed', pick('millexitspeed'))
- ],
- 'm/min'
- ));
-
- const c3 = echarts.init(this.$refs.chartTension);
- c3.setOption(this.baseOption(
- '张力趋势 (N)', xData,
- [
- this.makeLine('出口张力 pltens', pick('pltens')),
- this.makeLine('入口张力 enltens', pick('enltens')),
- this.makeLine('cxltens', pick('cxltens'))
- ],
- 'N'
- ));
-
- let c4, c5, c6, c7, c8, c9;
- if (this.$refs.chartPorSpeed) {
- c4 = echarts.init(this.$refs.chartPorSpeed);
- c4.setOption(this.baseOption(
- '开卷机速度 (m/min)', xData,
- [
- this.makeLine('开卷速度 porspeed', pick('porspeed')),
- this.makeLine('最大 porspeedmax', pick('porspeedmax')),
- this.makeLine('最小 porspeedmin', pick('porspeedmin'))
- ],
- 'm/min'
- ));
+ if (!this.trendChartInst || this.trendChartInst.isDisposed()) {
+ this.trendChartInst = echarts.init(el);
+ const resizeFn = () => this.trendChartInst && !this.trendChartInst.isDisposed() && this.trendChartInst.resize();
+ window.addEventListener('resize', resizeFn);
+ this._trendResizeFn = resizeFn;
}
-
- if (this.$refs.chartPorTens) {
- c5 = echarts.init(this.$refs.chartPorTens);
- c5.setOption(this.baseOption(
- '开卷机张力 (N)', xData,
- [
- this.makeLine('开卷张力 portens', pick('portens')),
- this.makeLine('最大 portensmax', pick('portensmax')),
- this.makeLine('最小 portensmin', pick('portensmin'))
- ],
- 'N'
- ));
- }
-
- if (this.$refs.chartTrim) {
- c6 = echarts.init(this.$refs.chartTrim);
- c6.setOption(this.baseOption(
- '剪切参数', xData,
- [
- this.makeLine('剪切张力 trimtens', pick('trimtens')),
- this.makeLine('剪切宽度 trimwidth', pick('trimwidth')),
- this.makeLine('trtens', pick('trtens'))
- ],
- ''
- ));
- }
-
- if (this.$refs.chartTemp) {
- c7 = echarts.init(this.$refs.chartTemp);
- c7.setOption(this.baseOption(
- '温度趋势 (℃)', xData,
- [
- this.makeLine('1号测温 tk1temp', pick('tk1temp')),
- this.makeLine('2号测温 tk2temp', pick('tk2temp')),
- this.makeLine('3号测温 tk3temp', pick('tk3temp')),
- this.makeLine('4号测温 tk4temp', pick('tk4temp')),
- this.makeLine('漂洗温度 rinsetemp', pick('rinsetemp'))
- ],
- '℃'
- ));
- }
-
- if (this.$refs.chartMesh) {
- c8 = echarts.init(this.$refs.chartMesh);
- c8.setOption(this.baseOption(
- '网纹辊参数', xData,
- [
- this.makeLine('网纹辊1 tlmesh1', pick('tlmesh1')),
- this.makeLine('网纹辊2 tlmesh2', pick('tlmesh2')),
- this.makeLine('网纹辊3 tlmesh3', pick('tlmesh3'))
- ],
- ''
- ));
- }
-
- if (this.$refs.chartElong) {
- c9 = echarts.init(this.$refs.chartElong);
- c9.setOption(this.baseOption(
- '延伸率', xData,
- [
- this.makeLine('延伸率 tlelong', pick('tlelong')),
- this.makeLine('总张力 tltens', pick('tltens')),
- this.makeLine('teltens', pick('teltens'))
- ],
- ''
- ));
- }
-
- this.chartInstances = [c1, c2, c3, c4, c5, c6, c7, c8, c9].filter(c => c);
- this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize(); });
+ const x = this.segX();
+ const yData = this.seg(this.selectedTrendParam.col);
+ this.trendChartInst.setOption(makeLine(this.selectedTrendParam.label, x, yData), true);
+ },
+ // 标签页切换
+ handlePerfTabSwitch() {
+ this.$nextTick(() => {
+ if (this.perfActiveTab === 'trend') {
+ if (this.selectedTrendParam && this.segData) {
+ this.renderTrendSingleChart();
+ }
+ } else {
+ this.renderCurrentTab();
+ }
+ });
+ },
+ renderCurrentTab() {
+ this.disposeSideCharts();
+ if (this.perfActiveTab === 'thickness' && this.gaugeRows?.length) this.renderGaugeCharts();
+ if (this.perfActiveTab === 'flatness3d' && this.shapeRows?.length) this.renderFlatness3d();
+ if (this.perfActiveTab === 'flatness' && this.shapeRows?.length) this.renderFlatnessCharts();
+ },
+ // SEG 数据辅助
+ seg(col) {
+ const s = this.segData;
+ const arr = s[col] !== undefined ? s[col] : (s[col.toLowerCase()] || []);
+ return arr.map(v => v == null ? null : Number(Number(v).toFixed(3)));
+ },
+ segX() {
+ const s = this.segData;
+ const arr = s['STARTPOS'] !== undefined ? s['STARTPOS'] : (s['startpos'] || []);
+ return arr.map(v => v == null ? '' : Number(v).toFixed(1));
+ },
+ // 图表初始化
+ makeChart(ref, option) {
+ const el = this.$refs[ref];
+ if (!el) return null;
+ const chart = echarts.init(el);
+ chart.setOption(option);
+ return chart;
+ },
+ setupResize() {
+ this.resizeHandler = () => this.chartInstances.forEach(c => {
+ if (c && !c.isDisposed()) c.resize();
+ });
window.addEventListener('resize', this.resizeHandler);
},
+ // 厚度曲线
+ renderGaugeCharts() {
+ const rows = this.gaugeRows;
+ if (!rows || !rows.length) return;
+ const xData = xLocData(rows);
+ const refs = ['chartGauge1', 'chartGauge2', 'chartGauge3', 'chartGauge4'];
+ const charts = refs.map((ref, i) => {
+ const { col, title } = GAUGE_COLS[i];
+ const yData = rows.map(r => {
+ const v = getRowVal(r, col);
+ return v == null ? null : parseFloat(v.toFixed(4));
+ });
+ return this.makeChart(ref, makeLine(title, xData, yData));
+ });
+ this.chartInstances = charts.filter(Boolean);
+ this.setupResize();
+ },
+ // 带钢板形 3D 热力图
+ renderFlatness3d() {
+ const rows = this.shapeRows;
+ if (!rows || !rows.length) return;
+ const firstRow = rows[0];
+ const high = parseInt(getRowVal(firstRow, 'HIGHZONEID')) || 26;
+ const low = parseInt(getRowVal(firstRow, 'LOWZONEID')) || 1;
+ const numZones = Math.min(Math.max(high - low + 1, 1), 26);
+ const zoneCols = Array.from({ length: numZones }, (_, i) =>
+ `VALUES${String(low + i).padStart(2, '0')}`
+ );
+ const step = Math.max(1, Math.floor(rows.length / 200));
+ const sampled = rows.filter((_, i) => i % step === 0);
+ const numX = sampled.length;
+
+ const xLabels = sampled.map(r => {
+ const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation;
+ return v == null ? '' : Number(v).toFixed(0);
+ });
+
+ let minV = Infinity, maxV = -Infinity;
+ sampled.forEach(row => {
+ zoneCols.forEach(col => {
+ const v = getRowVal(row, col);
+ if (v != null) {
+ if (v < minV) minV = v;
+ if (v > maxV) maxV = v;
+ }
+ });
+ });
+ if (!isFinite(minV)) { minV = -30; maxV = 30; }
+ const absMax = Math.max(Math.abs(minV), Math.abs(maxV));
+
+ const channelLines = zoneCols.map((col, yi) => ({
+ type: 'line3D',
+ coordinateSystem: 'cartesian3D',
+ data: sampled.map((row, xi) => {
+ const v = getRowVal(row, col);
+ return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))];
+ }).filter(Boolean),
+ lineStyle: { width: 2, opacity: 1 }
+ }));
+
+ const xStride = Math.max(1, Math.floor(numX / 60));
+ const crossLines = [];
+ for (let xi = 0; xi < numX; xi += xStride) {
+ const pts = zoneCols.map((col, yi) => {
+ const v = getRowVal(sampled[xi], col);
+ return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))];
+ }).filter(Boolean);
+ if (pts.length > 1) {
+ crossLines.push({
+ type: 'line3D',
+ coordinateSystem: 'cartesian3D',
+ data: pts,
+ lineStyle: { width: 1.5, opacity: 1 }
+ });
+ }
+ }
+
+ const series = [...channelLines, ...crossLines];
+
+ const option = {
+ title: { text: '实测平直度 [IU]', textStyle: { fontSize: 13, fontWeight: 'normal' }, top: 6, left: 10 },
+ tooltip: {},
+ visualMap: {
+ show: true,
+ dimension: 2,
+ min: -absMax,
+ max: absMax,
+ calculable: true,
+ orient: 'vertical',
+ right: 10,
+ top: 'center',
+ textStyle: { fontSize: 10 },
+ inRange: {
+ color: ['#8B0000','#CC2200','#E84C00','#F46D43',
+ '#FDAE61','#FEE08B',
+ '#66BD63','#1A9850','#006837',
+ '#3288BD','#5E4FA2','#762A83']
+ }
+ },
+ grid3D: {
+ boxWidth: 200,
+ boxHeight: 60,
+ boxDepth: 80,
+ viewControl: {
+ projection: 'orthographic',
+ autoRotate: false,
+ rotateSensitivity: 1,
+ zoomSensitivity: 1
+ },
+ light: {
+ main: { intensity: 1.2, shadow: false },
+ ambient: { intensity: 0.3 }
+ }
+ },
+ xAxis3D: {
+ type: 'value',
+ name: '位置',
+ min: 0,
+ max: numX - 1,
+ nameTextStyle: { fontSize: 10 },
+ axisLabel: {
+ fontSize: 9,
+ formatter: v => xLabels[Math.round(v)] || ''
+ }
+ },
+ yAxis3D: {
+ type: 'value',
+ name: '通道',
+ min: 0,
+ max: numZones - 1,
+ nameTextStyle: { fontSize: 10 },
+ axisLabel: {
+ fontSize: 9,
+ formatter: v => String(low + Math.round(v))
+ }
+ },
+ zAxis3D: {
+ type: 'value',
+ name: 'IU',
+ nameTextStyle: { fontSize: 10 },
+ axisLabel: { fontSize: 9 }
+ },
+ series
+ };
+
+ const el = this.$refs.chartFlatness3d;
+ if (!el) return;
+ const chart = echarts.init(el);
+ chart.setOption(option);
+ this.chartInstances = [chart];
+ this.setupResize();
+ },
+ // 板形曲线
+ renderFlatnessCharts() {
+ const rows = this.shapeRows;
+ if (!rows || !rows.length) return;
+ const xData = xLocData(rows);
+ const refs = ['chartFlatDev', 'chartTilt', 'chartWrBend', 'chartIrBend'];
+ const charts = refs.map((ref, i) => {
+ const { col, title } = SHAPE_SCALAR_COLS[i];
+ const yData = rows.map(r => {
+ const v = getRowVal(r, col);
+ return v == null ? null : parseFloat(v.toFixed(3));
+ });
+ return this.makeChart(ref, makeLine(title, xData, yData));
+ });
+ this.chartInstances = charts.filter(Boolean);
+ this.setupResize();
+ },
},
}
@@ -2215,14 +2506,153 @@ export default {
margin-left: 4px;
}
-.charts-wrap {
+.production-section {
+ min-height: 600px;
+ flex: 1;
+}
+
+.production-section .section-body {
+ padding: 0;
+ height: calc(100% - 48px);
+ min-height: 550px;
+}
+
+.perf-tabs {
+ height: 100%;
+ min-height: 550px;
display: flex;
flex-direction: column;
+
+ ::v-deep .el-tabs__header {
+ flex-shrink: 0;
+ }
+ ::v-deep .el-tabs__content {
+ flex: 1;
+ overflow: hidden;
+ min-height: 0;
+ padding: 0;
+ }
+ ::v-deep .el-tab-pane {
+ height: 100%;
+ min-height: 500px;
+ }
+}
+
+.trend-layout {
+ display: flex;
+ height: 100%;
+ gap: 0;
+}
+
+.trend-tree {
+ width: 140px;
+ flex-shrink: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ border-right: 1px solid #ebeef5;
+ padding: 4px 0;
+ max-height: calc(100vh - 400px);
+
+ &::-webkit-scrollbar {
+ width: 4px;
+ height: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: #dcdfe6;
+ border-radius: 2px;
+ }
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+}
+
+.tree-group {
+ user-select: none;
+}
+
+.tree-group-label {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 5px 8px;
+ font-size: 12px;
+ font-weight: 600;
+ color: #303133;
+ cursor: pointer;
+
+ &:hover { background: #f5f7fa; }
+
+ i { font-size: 10px; color: #909399; }
+}
+
+.tree-children { padding-left: 4px; }
+
+.tree-item {
+ padding: 4px 8px 4px 18px;
+ font-size: 12px;
+ color: #606266;
+ cursor: pointer;
+ border-radius: 3px;
+ margin: 1px 4px;
+
+ &:hover { background: #ecf5ff; color: #409eff; }
+
+ &.active {
+ background: #ecf5ff;
+ color: #409eff;
+ font-weight: 500;
+ }
+}
+
+.trend-chart-area {
+ flex: 1;
+ min-width: 0;
+ height: 500px;
+ padding: 4px 4px 4px 8px;
+ display: flex;
+ align-items: stretch;
+}
+
+.charts-scroll {
+ height: 100%;
+ overflow-y: auto;
+ padding: 8px;
+ box-sizing: border-box;
+
+ &::-webkit-scrollbar { width: 4px; }
+ &::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 2px; }
+}
+
+.charts-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
gap: 12px;
+ align-content: start;
+ min-height: 100%;
+ padding: 4px;
+
+ .chart-box { margin-bottom: 0; }
}
.chart-box {
width: 100%;
- height: 200px;
+ height: 260px;
+ margin-bottom: 12px;
+ min-height: 200px;
+}
+
+.chart-box-tall {
+ height: 480px;
+ min-height: 400px;
+}
+
+.no-data-hint {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 120px;
+ color: #c0c4cc;
+ font-size: 13px;
}