refactor(wms/coil): 重构生产工艺数据展示模块
1. 将接口从get请求改为post请求,适配后端参数变更 2. 重新设计生产工艺数据页面,拆分为趋势参数、厚度曲线、带钢板形、板形曲线四个标签页 3. 新增趋势参数树形侧边栏,支持分类展开选择查看不同参数曲线 4. 新增厚度、板形3D热力图、板形曲线图表展示 5. 优化图表渲染逻辑和样式,统一数据处理与加载状态
This commit is contained in:
@@ -734,22 +734,71 @@
|
||||
<div class="section-header">
|
||||
<span class="section-icon">📈</span>
|
||||
<span class="section-title">生产工艺数据</span>
|
||||
<el-tag v-if="perfLoading" size="mini" type="info" style="margin-left:8px">加载中…</el-tag>
|
||||
<el-tag v-if="perfLoading || segLoading || realtimeLoading" size="mini" type="info" style="margin-left:8px">加载中…</el-tag>
|
||||
<span v-if="hasPerfData" class="perf-count">({{ perfSegCount }} 段)</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div v-if="hasPerfData" class="charts-wrap">
|
||||
<div ref="chartSpeed" class="chart-box" />
|
||||
<div ref="chartMillSpeed" class="chart-box" />
|
||||
<div ref="chartTension" class="chart-box" />
|
||||
<div ref="chartPorSpeed" class="chart-box" />
|
||||
<div ref="chartPorTens" class="chart-box" />
|
||||
<div ref="chartTrim" class="chart-box" />
|
||||
<div ref="chartTemp" class="chart-box" />
|
||||
<div ref="chartMesh" class="chart-box" />
|
||||
<div ref="chartElong" class="chart-box" />
|
||||
</div>
|
||||
<el-empty v-else-if="!perfLoading" description="暂无生产工艺数据" :image-size="56" style="margin-top: 24px" />
|
||||
<el-tabs v-model="perfActiveTab" size="small" class="perf-tabs" @tab-click="handlePerfTabSwitch">
|
||||
|
||||
<!-- 趋势参数:左树形目录 + 右单图 -->
|
||||
<el-tab-pane label="趋势参数" name="trend">
|
||||
<div v-if="!hasPerfData && !segLoading" class="no-data-hint">暂无生产数据</div>
|
||||
<div v-else-if="segLoading" class="no-data-hint">加载中…</div>
|
||||
<div v-else class="trend-layout">
|
||||
<div class="trend-tree">
|
||||
<div v-for="group in trendGroups" :key="group.label" class="tree-group">
|
||||
<div class="tree-group-label" @click="toggleTrendGroup(group.label)">
|
||||
<i :class="expandedGroups[group.label] ? 'el-icon-caret-bottom' : 'el-icon-caret-right'" />
|
||||
{{ group.label }}
|
||||
</div>
|
||||
<div v-show="expandedGroups[group.label]" class="tree-children">
|
||||
<div
|
||||
v-for="item in group.children"
|
||||
:key="item.col"
|
||||
class="tree-item"
|
||||
:class="{ active: selectedTrendParam && selectedTrendParam.col === item.col }"
|
||||
@click="selectTrendParam(item)"
|
||||
>{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-chart-area">
|
||||
<div v-if="!selectedTrendParam" class="no-data-hint">← 点击左侧参数查看曲线</div>
|
||||
<div ref="trendSingleChart" :style="{ display: selectedTrendParam ? 'block' : 'none', height: '100%', width: '100%' }" />
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 厚度曲线 -->
|
||||
<el-tab-pane label="厚度曲线" name="thickness">
|
||||
<div v-if="!gaugeRows || !gaugeRows.length" class="no-data-hint">暂无厚度数据</div>
|
||||
<div v-else class="charts-scroll charts-grid">
|
||||
<div ref="chartGauge1" class="chart-box" />
|
||||
<div ref="chartGauge2" class="chart-box" />
|
||||
<div ref="chartGauge3" class="chart-box" />
|
||||
<div ref="chartGauge4" class="chart-box" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 带钢板形 3D 热力图 -->
|
||||
<el-tab-pane label="带钢板形" name="flatness3d">
|
||||
<div v-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||||
<div v-else class="charts-scroll">
|
||||
<div ref="chartFlatness3d" class="chart-box chart-box-tall" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 板形曲线 -->
|
||||
<el-tab-pane label="板形曲线" name="flatness">
|
||||
<div v-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||||
<div v-else class="charts-scroll charts-grid">
|
||||
<div ref="chartFlatDev" class="chart-box" />
|
||||
<div ref="chartTilt" class="chart-box" />
|
||||
<div ref="chartWrBend" class="chart-box" />
|
||||
<div ref="chartIrBend" class="chart-box" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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();
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user