新增app和跟踪页面,现已经调通
This commit is contained in:
@@ -474,6 +474,30 @@ public class SqlServerApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryMatMap() {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select AREAID, GROUPID, POS, FULLPLACENAME, CREMATIDTYPE, MATIDTYPE, MATID, SUBPART, UPDMASTER, DISPLAYNAME, L1MAPIDX, UPDTIME from JXPLTCM.ROMTB_MATMAP order by L1MAPIDX",
|
||||
emptyParams()
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryEntryTrace() {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select * from JXPLTCM.V_PLTCM_ENTRY_TRACE order by L1MAPIDX",
|
||||
emptyParams()
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryExitTrace() {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select * from JXPLTCM.V_PLTCM_EXIT_TRACE order by L1MAPIDX",
|
||||
emptyParams()
|
||||
);
|
||||
}
|
||||
|
||||
private org.springframework.util.MultiValueMap<String, String> convertToQueryParams(Map<String, ?> queryParams) {
|
||||
org.springframework.util.LinkedMultiValueMap<String, String> multiValueMap = new org.springframework.util.LinkedMultiValueMap<>();
|
||||
if (queryParams == null || queryParams.isEmpty()) {
|
||||
|
||||
@@ -126,6 +126,34 @@ public class SqlServerApiBusinessService {
|
||||
return client.queryShapeByMatId(matId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪数据:ROMTB_MATMAP + V_PLTCM_ENTRY_TRACE + V_PLTCM_EXIT_TRACE 一次返回。
|
||||
*/
|
||||
public TrackDataView getTrackData() {
|
||||
List<Map<String, Object>> matMapRows = asRowList(client.queryMatMap());
|
||||
List<Map<String, Object>> entryRows = asRowList(client.queryEntryTrace());
|
||||
List<Map<String, Object>> exitRows = asRowList(client.queryExitTrace());
|
||||
return new TrackDataView(matMapRows, entryRows, exitRows);
|
||||
}
|
||||
|
||||
public static class TrackDataView {
|
||||
private final List<Map<String, Object>> matMap;
|
||||
private final List<Map<String, Object>> entryTrace;
|
||||
private final List<Map<String, Object>> exitTrace;
|
||||
|
||||
public TrackDataView(List<Map<String, Object>> matMap,
|
||||
List<Map<String, Object>> entryTrace,
|
||||
List<Map<String, Object>> exitTrace) {
|
||||
this.matMap = matMap;
|
||||
this.entryTrace = entryTrace;
|
||||
this.exitTrace = exitTrace;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getMatMap() { return matMap; }
|
||||
public List<Map<String, Object>> getEntryTrace() { return entryTrace; }
|
||||
public List<Map<String, Object>> getExitTrace() { return exitTrace; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 出口卷实绩列表(分页),来自 PLTCM_PDO_EXCOIL。
|
||||
*/
|
||||
|
||||
@@ -183,6 +183,14 @@ public class SqlServerApiController {
|
||||
return R.ok(businessService.getPresetSetupByCoilId(coilId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪数据:matMap + entryTrace + exitTrace 一次返回。
|
||||
*/
|
||||
@GetMapping("/track")
|
||||
public R<SqlServerApiBusinessService.TrackDataView> trackData() {
|
||||
return R.ok(businessService.getTrackData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 换辊历史总条数。
|
||||
*/
|
||||
|
||||
@@ -143,6 +143,14 @@ export function getExcoilCount() {
|
||||
})
|
||||
}
|
||||
|
||||
// 跟踪数据:matMap + entryTrace + exitTrace
|
||||
export function getTrackData() {
|
||||
return request({
|
||||
url: '/sql-server-api/track',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 工艺预设参数,按计划钢卷号查询,PLTCM_PRESET_SETUP
|
||||
export function getPresetSetupByCoilId(coilId) {
|
||||
return request({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="actual-container">
|
||||
<!-- 顶部实绩列表 (PLTCM_PDO_EXCOIL) -->
|
||||
<div class="top-section">
|
||||
@@ -74,6 +75,11 @@
|
||||
<el-tag type="primary" size="mini" effect="plain">{{ row.STATUS || row.status || '产出' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="" width="64" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="mini" style="color:#2c5282;font-weight:600" @click.stop="openQualityReport(row)">质保书</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-pagination">
|
||||
<el-pagination
|
||||
@@ -162,7 +168,7 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 右侧查找面板 -->
|
||||
<!-- 右侧查找面板 + 质保书入口 -->
|
||||
<div class="search-panel">
|
||||
<div class="panel-title">查找</div>
|
||||
<div class="search-type-group">
|
||||
@@ -194,11 +200,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<quality-report-dialog ref="qualityReport" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import 'echarts-gl'
|
||||
import QualityReportDialog from './QualityReportDialog.vue'
|
||||
import {
|
||||
getExcoilList,
|
||||
getExcoilCount,
|
||||
@@ -360,6 +370,7 @@ function xLocData(rows) {
|
||||
|
||||
export default {
|
||||
name: 'ActualPerformance',
|
||||
components: { QualityReportDialog },
|
||||
data() {
|
||||
return {
|
||||
excoilLoading: false,
|
||||
@@ -1012,6 +1023,17 @@ export default {
|
||||
return items
|
||||
},
|
||||
|
||||
openQualityReport(row) {
|
||||
// If the clicked row is already the selected row, use in-memory data
|
||||
const isSame = this.selectedRow &&
|
||||
(row.EXCOILID || row.excoilid) === (this.selectedRow.EXCOILID || this.selectedRow.excoilid)
|
||||
const segData = isSame ? this.segData : null
|
||||
const gaugeRows = isSame ? this.gaugeRows : null
|
||||
const shapeRows = isSame ? this.shapeRows : null
|
||||
const presetData = isSame ? this.presetData : null
|
||||
this.$refs.qualityReport.open(row, segData, gaugeRows, shapeRows, presetData)
|
||||
},
|
||||
|
||||
calcLengthPerTon(row) {
|
||||
const len = parseFloat(row.EXIT_LENGTH || row.exit_length)
|
||||
const wt = parseFloat(row.MEAS_EXIT_WEIGHT || row.meas_exit_weight)
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="产品质量报表"
|
||||
:visible.sync="visible"
|
||||
width="900px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@opened="onOpened"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="rpt-toolbar">
|
||||
<span class="rpt-coil-hint">{{ coilLabel }}</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-download" :loading="exporting" @click="exportPdf">导出 PDF</el-button>
|
||||
</div>
|
||||
|
||||
<div ref="reportContent" class="report-body">
|
||||
<!-- ─── 页眉 ─── -->
|
||||
<div class="rpt-header">
|
||||
<div class="rpt-header-left">
|
||||
<img src="~@/assets/logo/logo.png" class="rpt-logo" alt="KLP" />
|
||||
</div>
|
||||
<div class="rpt-title">产品质量报表</div>
|
||||
<div class="rpt-header-right">
|
||||
<div class="brand-name">科伦普</div>
|
||||
<div class="brand-sub">KE LUN PU</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 钢卷规格 ─── -->
|
||||
<div class="section-bar">钢卷规格</div>
|
||||
<table class="rpt-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="lbl">钢卷号</td>
|
||||
<td class="val bold" colspan="3">{{ rv('EXCOILID') || rv('HOT_COILID') }}</td>
|
||||
<td class="lbl">来料厚度[mm]</td><td class="val">{{ rv('ENTRY_THICK') }}</td>
|
||||
<td class="lbl">来料宽度[mm]</td><td class="val">{{ rv('ENTRY_WIDTH') }}</td>
|
||||
<td class="lbl">来料重量[t]</td><td class="val">{{ rv('USED_ENTRY_WEIGHT') || rv('ENTRY_WEIGHT') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">钢种</td>
|
||||
<td class="val" colspan="3">{{ rv('ORDER_QUALITY') || rv('GRADE') }}</td>
|
||||
<td class="lbl">成品厚度[mm]</td><td class="val">{{ rv('EXIT_THICK') }}</td>
|
||||
<td class="lbl">成品宽度[mm]</td><td class="val">{{ rv('EXIT_WIDTH') }}</td>
|
||||
<td class="lbl">成品重量[t]</td><td class="val">{{ rv('MEAS_EXIT_WEIGHT') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">班组</td>
|
||||
<td class="val" colspan="3">{{ shiftLabel }}</td>
|
||||
<td class="lbl">偏差上限[mm]</td><td class="val">{{ rv('EXIT_POS_DEV') }}</td>
|
||||
<td class="lbl">压下率[%]</td><td class="val">{{ reductionRate }}</td>
|
||||
<td class="lbl">成品长度[m]</td><td class="val">{{ rv('EXIT_LENGTH') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">下工序</td>
|
||||
<td class="val" colspan="3">{{ rv('PARK_TYPE') || rv('SIDE_TRIM') }}</td>
|
||||
<td class="lbl">偏差下限[mm]</td><td class="val">{{ rv('EXIT_NEG_DEV') }}</td>
|
||||
<td class="lbl">热卷号</td><td class="val" colspan="3">{{ rv('HOT_COILID') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">生产时间</td>
|
||||
<td class="val" colspan="4">{{ fmtDate(row && (row.END_DATE || row.end_date)) }}</td>
|
||||
<td class="lbl">生产时长</td>
|
||||
<td class="val" colspan="4">—</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ─── 质量判定 ─── -->
|
||||
<div class="section-bar">质量判定</div>
|
||||
<table class="rpt-table quality-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>轧制总长[m]</th>
|
||||
<th>头尾超差[m]</th>
|
||||
<th>厚度合格率[%]</th>
|
||||
<th>目标板形</th>
|
||||
<th>板形合格率[%]</th>
|
||||
<th>板形质量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ rolledLength }}</td>
|
||||
<td>—</td>
|
||||
<td>{{ thickPassRate }}</td>
|
||||
<td>平直</td>
|
||||
<td>{{ shapePassRate }}</td>
|
||||
<td :class="qualityClass">{{ qualityLabel }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ─── 成品厚度偏差 ─── -->
|
||||
<template v-if="hasGauge">
|
||||
<div class="chart-title">
|
||||
成品厚度偏差
|
||||
<span class="chart-subtitle">产出厚度 - {{ rv('EXIT_THICK') }} [mm]</span>
|
||||
</div>
|
||||
<div ref="chartThick1" class="rpt-chart" />
|
||||
<div class="chart-title">末架出口厚度 [mm]</div>
|
||||
<div ref="chartThick4" class="rpt-chart" />
|
||||
</template>
|
||||
|
||||
<!-- ─── 速度趋势 ─── -->
|
||||
<template v-if="hasSeg">
|
||||
<div class="chart-title">速度趋势 [m/min]</div>
|
||||
<div ref="chartSpeed" class="rpt-chart" />
|
||||
<div class="chart-title">张力趋势 [N/mm²]</div>
|
||||
<div ref="chartTension" class="rpt-chart" />
|
||||
<template v-if="hasSegField('TLMESH1') || hasSegField('TLMESH2')">
|
||||
<div class="chart-title">拉矫机插入量 [mm]</div>
|
||||
<div ref="chartMesh" class="rpt-chart rpt-chart-sm" />
|
||||
</template>
|
||||
<template v-if="hasSegField('TK1TEMP') || hasSegField('TK2TEMP')">
|
||||
<div class="chart-title">酸洗槽温度 [℃]</div>
|
||||
<div ref="chartTemp" class="rpt-chart rpt-chart-sm" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- ─── 板形曲线 ─── -->
|
||||
<template v-if="hasShape">
|
||||
<div class="chart-title">总板形偏差 [IU]</div>
|
||||
<div ref="chartFlatDev" class="rpt-chart" />
|
||||
<div class="chart-title">弯辊力 [kN]</div>
|
||||
<div ref="chartBend" class="rpt-chart rpt-chart-sm" />
|
||||
<div class="chart-title">板形热力图</div>
|
||||
<div ref="chartHeatmap" class="rpt-chart rpt-chart-heat" />
|
||||
</template>
|
||||
|
||||
<!-- ─── 页脚 ─── -->
|
||||
<div class="rpt-footer">生成时间:{{ nowStr }}</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
function getV(row, col) {
|
||||
if (!row) return null
|
||||
return row[col] !== undefined ? row[col] : row[col.toLowerCase()]
|
||||
}
|
||||
function getRowVal(row, col) {
|
||||
const v = getV(row, col)
|
||||
return v == null ? null : Number(v)
|
||||
}
|
||||
function xLoc(rows) {
|
||||
return rows.map(r => {
|
||||
const v = getRowVal(r, 'XLOCATION')
|
||||
return v == null ? '' : v.toFixed(1)
|
||||
})
|
||||
}
|
||||
function colData(rows, col) {
|
||||
return rows.map(r => {
|
||||
const v = getRowVal(r, col)
|
||||
return v == null ? null : parseFloat(v.toFixed(4))
|
||||
})
|
||||
}
|
||||
function segArr(seg, col) {
|
||||
if (!seg) return []
|
||||
const arr = seg[col] !== undefined ? seg[col] : (seg[col.toLowerCase()] || [])
|
||||
return arr.map(v => v == null ? null : parseFloat(Number(v).toFixed(3)))
|
||||
}
|
||||
function segX(seg) {
|
||||
const arr = seg['STARTPOS'] !== undefined ? seg['STARTPOS'] : (seg['startpos'] || seg['XLOCATION'] || seg['xlocation'] || [])
|
||||
return arr.map(v => v == null ? '' : Number(v).toFixed(1))
|
||||
}
|
||||
function calcYRange(vals) {
|
||||
const nums = vals.filter(v => v != null && isFinite(Number(v))).map(Number)
|
||||
if (!nums.length) return {}
|
||||
const min = Math.min(...nums), 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 lineOpt(title, xData, series, yName) {
|
||||
const allVals = series.flatMap(s => s.data)
|
||||
const range = calcYRange(allVals)
|
||||
return {
|
||||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal', color: '#1a365d' }, top: 4, left: 8 },
|
||||
legend: { top: 4, right: 6, textStyle: { fontSize: 9 }, itemWidth: 12, itemHeight: 8 },
|
||||
tooltip: { trigger: 'axis', confine: true },
|
||||
grid: { top: 38, bottom: 28, left: 10, right: 10, containLabel: true },
|
||||
xAxis: { type: 'category', data: xData, name: '带钢长度[m]', nameTextStyle: { fontSize: 9 }, axisLabel: { fontSize: 9, interval: 'auto' } },
|
||||
yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 9 }, axisLabel: { fontSize: 9 }, min: range.min, max: range.max },
|
||||
series: series.map((s, i) => ({
|
||||
name: s.name, type: 'line', smooth: false, symbol: 'none',
|
||||
lineStyle: { width: s.dash ? 1 : 1.5, type: s.dash ? 'dashed' : 'solid', color: s.color },
|
||||
data: s.data, z: s.dash ? 2 : 3
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const PALETTE = ['#409EFF', '#E6A23C', '#67C23A', '#F56C6C', '#9B59B6', '#1ABC9C']
|
||||
|
||||
export default {
|
||||
name: 'QualityReportDialog',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
exporting: false,
|
||||
row: null,
|
||||
segData: null,
|
||||
gaugeRows: null,
|
||||
shapeRows: null,
|
||||
presetData: null,
|
||||
nowStr: '',
|
||||
chartInsts: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
coilLabel() {
|
||||
const id = this.row ? (getV(this.row, 'EXCOILID') || getV(this.row, 'HOT_COILID') || '') : ''
|
||||
return id ? `钢卷号:${id}` : ''
|
||||
},
|
||||
shiftLabel() {
|
||||
const s = this.row ? (getV(this.row, 'SHIFT') || '') : ''
|
||||
const c = this.row ? (getV(this.row, 'CREW') || '') : ''
|
||||
return [s, c].filter(Boolean).join('/') || '—'
|
||||
},
|
||||
reductionRate() {
|
||||
const e = parseFloat(this.row?.ENTRY_THICK || this.row?.entry_thick)
|
||||
const x = parseFloat(this.row?.EXIT_THICK || this.row?.exit_thick)
|
||||
if (!e || !x || e <= 0) return '—'
|
||||
return ((e - x) / e * 100).toFixed(2)
|
||||
},
|
||||
rolledLength() {
|
||||
const el = getV(this.row, 'EXIT_LENGTH')
|
||||
if (el != null) return el
|
||||
if (this.gaugeRows?.length) {
|
||||
const max = Math.max(...this.gaugeRows.map(r => getRowVal(r, 'XLOCATION') || 0))
|
||||
if (max > 0) return max.toFixed(1)
|
||||
}
|
||||
return '—'
|
||||
},
|
||||
thickPassRate() {
|
||||
if (!this.gaugeRows?.length) return '—'
|
||||
const posLim = parseFloat(getV(this.row, 'EXIT_POS_DEV')) || 0.03
|
||||
const negLim = Math.abs(parseFloat(getV(this.row, 'EXIT_NEG_DEV')) || 0.03)
|
||||
const target = parseFloat(getV(this.row, 'EXIT_THICK'))
|
||||
if (!target) return '—'
|
||||
const total = this.gaugeRows.length
|
||||
const pass = this.gaugeRows.filter(r => {
|
||||
const v = getRowVal(r, 'THICK4') || getRowVal(r, 'THICK1')
|
||||
if (v == null) return true
|
||||
const dev = v - target
|
||||
return dev <= posLim && dev >= -negLim
|
||||
}).length
|
||||
return ((pass / total) * 100).toFixed(2)
|
||||
},
|
||||
shapePassRate() {
|
||||
if (!this.shapeRows?.length) return '—'
|
||||
const total = this.shapeRows.length
|
||||
const pass = this.shapeRows.filter(r => {
|
||||
const v = getRowVal(r, 'ABSDEVIATION')
|
||||
return v == null || Math.abs(v) <= 10
|
||||
}).length
|
||||
return ((pass / total) * 100).toFixed(2)
|
||||
},
|
||||
qualityLabel() {
|
||||
const q = getV(this.row, 'QUALITY')
|
||||
if (q) return q
|
||||
const tp = parseFloat(this.thickPassRate)
|
||||
if (!isNaN(tp)) return tp >= 99 ? '合格' : '不合格'
|
||||
return '—'
|
||||
},
|
||||
qualityClass() {
|
||||
return this.qualityLabel === '合格' ? 'cell-ok' : this.qualityLabel === '不合格' ? 'cell-ng' : ''
|
||||
},
|
||||
hasSeg() { return !!this.segData && Object.keys(this.segData).length > 0 },
|
||||
hasGauge() { return !!this.gaugeRows?.length },
|
||||
hasShape() { return !!this.shapeRows?.length }
|
||||
},
|
||||
methods: {
|
||||
rv(col) { const v = getV(this.row, col); return v != null && v !== '' ? v : '—' },
|
||||
hasSegField(col) { return !!(this.segData && (this.segData[col]?.length || this.segData[col.toLowerCase()]?.length)) },
|
||||
fmtDate(v) { if (!v) return '—'; return String(v).replace('T', ' ').substring(0, 19) },
|
||||
|
||||
open(row, segData, gaugeRows, shapeRows, presetData) {
|
||||
this.row = row
|
||||
this.segData = segData || null
|
||||
this.gaugeRows = gaugeRows || null
|
||||
this.shapeRows = shapeRows || null
|
||||
this.presetData = presetData || null
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
// @opened fires AFTER the dialog's CSS transition completes — DOM is fully painted
|
||||
onOpened() {
|
||||
this.nowStr = new Date().toLocaleString('zh-CN').replace(/\//g, '-')
|
||||
this.$nextTick(() => this.renderAll())
|
||||
},
|
||||
onClose() {
|
||||
this.disposeCharts()
|
||||
},
|
||||
disposeCharts() {
|
||||
this.chartInsts.forEach(c => { try { c.dispose() } catch (_) {} })
|
||||
this.chartInsts = []
|
||||
},
|
||||
// Pass explicit pixel size so echarts doesn't measure a zero-height el
|
||||
initChart(refName, height) {
|
||||
const el = this.$refs[refName]
|
||||
if (!el) return null
|
||||
const w = el.offsetWidth || 840
|
||||
const h = height || el.offsetHeight || 180
|
||||
const c = echarts.init(el, null, { renderer: 'canvas', width: w, height: h })
|
||||
this.chartInsts.push(c)
|
||||
return c
|
||||
},
|
||||
|
||||
renderAll() {
|
||||
this.disposeCharts()
|
||||
if (this.hasGauge) this.renderGaugeCharts()
|
||||
if (this.hasSeg) this.renderSegCharts()
|
||||
if (this.hasShape) this.renderShapeCharts()
|
||||
},
|
||||
|
||||
renderGaugeCharts() {
|
||||
const rows = this.gaugeRows
|
||||
const x = xLoc(rows)
|
||||
const target = parseFloat(getV(this.row, 'EXIT_THICK'))
|
||||
const posLim = parseFloat(getV(this.row, 'EXIT_POS_DEV')) || 0.03
|
||||
const negLim = Math.abs(parseFloat(getV(this.row, 'EXIT_NEG_DEV'))) || 0.03
|
||||
|
||||
// THICK1 偏差图 (1架出口 vs 目标)
|
||||
const t1 = colData(rows, 'THICK1')
|
||||
const t1Ref = colData(rows, 'THICK1REF')
|
||||
if (t1.some(v => v != null)) {
|
||||
const c = this.initChart('chartThick1', 180)
|
||||
if (c) {
|
||||
const series = [{ name: '1架出口厚度', data: t1, color: PALETTE[0] }]
|
||||
if (t1Ref.some(v => v != null)) series.push({ name: '目标', data: t1Ref, color: '#909399', dash: true })
|
||||
if (target) {
|
||||
const up = rows.map(() => parseFloat((target + posLim).toFixed(4)))
|
||||
const lo = rows.map(() => parseFloat((target - negLim).toFixed(4)))
|
||||
series.push({ name: '上限', data: up, color: '#F56C6C', dash: true })
|
||||
series.push({ name: '下限', data: lo, color: '#67C23A', dash: true })
|
||||
}
|
||||
c.setOption(lineOpt('1架出口厚度 [mm]', x, series, 'mm'))
|
||||
}
|
||||
}
|
||||
|
||||
// THICK4 末架出口
|
||||
const t4 = colData(rows, 'THICK4')
|
||||
const t4Ref = colData(rows, 'THICK4REF')
|
||||
if (t4.some(v => v != null)) {
|
||||
const c = this.initChart('chartThick4', 180)
|
||||
if (c) {
|
||||
const series = [{ name: '末架出口厚度', data: t4, color: PALETTE[0] }]
|
||||
if (t4Ref.some(v => v != null)) series.push({ name: '目标', data: t4Ref, color: '#909399', dash: true })
|
||||
if (target) {
|
||||
const up = rows.map(() => parseFloat((target + posLim).toFixed(4)))
|
||||
const lo = rows.map(() => parseFloat((target - negLim).toFixed(4)))
|
||||
series.push({ name: '上限', data: up, color: '#F56C6C', dash: true })
|
||||
series.push({ name: '下限', data: lo, color: '#67C23A', dash: true })
|
||||
}
|
||||
c.setOption(lineOpt('末架出口厚度 [mm]', x, series, 'mm'))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
renderSegCharts() {
|
||||
const seg = this.segData
|
||||
const x = segX(seg)
|
||||
|
||||
// 速度
|
||||
const speedSeries = [
|
||||
this.hasSegField('PLSPEED') && { name: '酸洗速度', data: segArr(seg,'PLSPEED'), color: PALETTE[0] },
|
||||
this.hasSegField('TRIMSPEED') && { name: '圆盘剪速度', data: segArr(seg,'TRIMSPEED'), color: PALETTE[1] },
|
||||
this.hasSegField('MILLEXITSPEED') && { name: '轧机出口速度', data: segArr(seg,'MILLEXITSPEED'), color: PALETTE[2] },
|
||||
this.hasSegField('MILLENTRYSPEED')&& { name: '轧机入口速度', data: segArr(seg,'MILLENTRYSPEED'),color: PALETTE[3] },
|
||||
this.hasSegField('PORSPEED') && { name: '开卷速度', data: segArr(seg,'PORSPEED'), color: PALETTE[4] }
|
||||
].filter(Boolean)
|
||||
if (speedSeries.length) {
|
||||
const c = this.initChart('chartSpeed', 180)
|
||||
if (c) c.setOption(lineOpt('速度趋势', x, speedSeries, 'm/min'))
|
||||
}
|
||||
|
||||
// 张力
|
||||
const tensSeries = [
|
||||
this.hasSegField('PLTENS') && { name: '酸洗张力', data: segArr(seg,'PLTENS'), color: PALETTE[0] },
|
||||
this.hasSegField('ENLTENS') && { name: '入口活套张力', data: segArr(seg,'ENLTENS'), color: PALETTE[1] },
|
||||
this.hasSegField('CXLTENS') && { name: '出口活套张力', data: segArr(seg,'CXLTENS'), color: PALETTE[2] },
|
||||
this.hasSegField('PORTENS') && { name: '开卷张力', data: segArr(seg,'PORTENS'), color: PALETTE[3] },
|
||||
this.hasSegField('TRIMTENS') && { name: '圆盘剪张力', data: segArr(seg,'TRIMTENS'), color: PALETTE[4] }
|
||||
].filter(Boolean)
|
||||
if (tensSeries.length) {
|
||||
const c = this.initChart('chartTension', 180)
|
||||
if (c) c.setOption(lineOpt('张力趋势', x, tensSeries, 'N/mm²'))
|
||||
}
|
||||
|
||||
// 拉矫插入量
|
||||
if (this.hasSegField('TLMESH1') || this.hasSegField('TLMESH2')) {
|
||||
const meshSeries = [
|
||||
this.hasSegField('TLMESH1') && { name: '1#插入量', data: segArr(seg,'TLMESH1'), color: PALETTE[0] },
|
||||
this.hasSegField('TLMESH2') && { name: '2#插入量', data: segArr(seg,'TLMESH2'), color: PALETTE[1] },
|
||||
this.hasSegField('TLMESH3') && { name: '3#插入量', data: segArr(seg,'TLMESH3'), color: PALETTE[2] },
|
||||
this.hasSegField('TLELONG') && { name: '延伸率', data: segArr(seg,'TLELONG'), color: PALETTE[3] }
|
||||
].filter(Boolean)
|
||||
const c = this.initChart('chartMesh', 140)
|
||||
if (c) c.setOption(lineOpt('拉矫机', x, meshSeries, 'mm/%'))
|
||||
}
|
||||
|
||||
// 酸洗温度
|
||||
if (this.hasSegField('TK1TEMP') || this.hasSegField('TK2TEMP')) {
|
||||
const tempSeries = [
|
||||
this.hasSegField('TK1TEMP') && { name: '1#酸槽', data: segArr(seg,'TK1TEMP'), color: PALETTE[0] },
|
||||
this.hasSegField('TK2TEMP') && { name: '2#酸槽', data: segArr(seg,'TK2TEMP'), color: PALETTE[1] },
|
||||
this.hasSegField('TK3TEMP') && { name: '3#酸槽', data: segArr(seg,'TK3TEMP'), color: PALETTE[2] },
|
||||
this.hasSegField('RINSETEMP') && { name: '漂洗', data: segArr(seg,'RINSETEMP'), color: PALETTE[3] }
|
||||
].filter(Boolean)
|
||||
const c = this.initChart('chartTemp', 140)
|
||||
if (c) c.setOption(lineOpt('酸洗槽温度', x, tempSeries, '℃'))
|
||||
}
|
||||
},
|
||||
|
||||
renderShapeCharts() {
|
||||
const rows = this.shapeRows
|
||||
const x = xLoc(rows)
|
||||
|
||||
// 总板形偏差
|
||||
const devData = colData(rows, 'ABSDEVIATION')
|
||||
if (devData.some(v => v != null)) {
|
||||
const c = this.initChart('chartFlatDev', 180)
|
||||
if (c) c.setOption(lineOpt('总板形偏差', x, [{ name: '板形偏差', data: devData, color: PALETTE[0] }], 'IU'))
|
||||
}
|
||||
|
||||
// 弯辊力
|
||||
const bendSeries = [
|
||||
colData(rows, 'WRBEND').some(v => v != null) && { name: '工作辊弯辊力', data: colData(rows, 'WRBEND'), color: PALETTE[0] },
|
||||
colData(rows, 'IRBEND').some(v => v != null) && { name: '中间辊弯辊力', data: colData(rows, 'IRBEND'), color: PALETTE[1] }
|
||||
].filter(Boolean)
|
||||
if (bendSeries.length) {
|
||||
const c = this.initChart('chartBend', 140)
|
||||
if (c) c.setOption(lineOpt('弯辊力', x, bendSeries, 'kN'))
|
||||
}
|
||||
|
||||
// 板形热力图 (2D heatmap)
|
||||
this.renderHeatmap(rows, x)
|
||||
},
|
||||
|
||||
renderHeatmap(rows, xLabels) {
|
||||
const el = this.$refs.chartHeatmap
|
||||
if (!el) return
|
||||
const firstRow = rows[0]
|
||||
const highId = parseInt(getRowVal(firstRow, 'HIGHZONEID')) || 26
|
||||
const lowId = parseInt(getRowVal(firstRow, 'LOWZONEID')) || 1
|
||||
const numZ = Math.min(Math.max(highId - lowId + 1, 1), 26)
|
||||
const zoneCols = Array.from({ length: numZ }, (_, i) => `VALUES${String(lowId + i).padStart(2, '0')}`)
|
||||
|
||||
// 降采样 X: 最多 300 点
|
||||
const step = Math.max(1, Math.floor(rows.length / 300))
|
||||
const sampled = rows.filter((_, i) => i % step === 0)
|
||||
|
||||
let minV = Infinity, maxV = -Infinity
|
||||
const data = []
|
||||
sampled.forEach((row, xi) => {
|
||||
zoneCols.forEach((col, yi) => {
|
||||
const v = getRowVal(row, col)
|
||||
if (v != null) {
|
||||
data.push([xi, yi, parseFloat(v.toFixed(2))])
|
||||
if (v < minV) minV = v
|
||||
if (v > maxV) maxV = v
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!isFinite(minV)) { minV = -20; maxV = 20 }
|
||||
const absMax = Math.max(Math.abs(minV), Math.abs(maxV))
|
||||
|
||||
const xSampleLabels = sampled.map(r => {
|
||||
const v = getRowVal(r, 'XLOCATION')
|
||||
return v == null ? '' : v.toFixed(0)
|
||||
})
|
||||
// Show ~8 labels
|
||||
const labelInterval = Math.max(1, Math.floor(sampled.length / 8))
|
||||
|
||||
const w = el.offsetWidth || 840
|
||||
const c = echarts.init(el, null, { renderer: 'canvas', width: w, height: 200 })
|
||||
this.chartInsts.push(c)
|
||||
c.setOption({
|
||||
title: { text: '板形热力图', textStyle: { fontSize: 12, fontWeight: 'normal', color: '#1a365d' }, top: 4, left: 8 },
|
||||
tooltip: {
|
||||
formatter: p => `位置: ${xSampleLabels[p.data[0]]} m<br/>通道: ${lowId + p.data[1]}<br/>值: ${p.data[2]} IU`
|
||||
},
|
||||
grid: { top: 36, bottom: 40, left: 40, right: 80, containLabel: false },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xSampleLabels,
|
||||
name: '带钢长度[m]',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 22,
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: { fontSize: 9, interval: labelInterval }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: Array.from({ length: numZ }, (_, i) => String(lowId + i)),
|
||||
name: '通道',
|
||||
nameTextStyle: { fontSize: 9 },
|
||||
axisLabel: { fontSize: 8, interval: Math.max(0, Math.floor(numZ / 8)) }
|
||||
},
|
||||
visualMap: {
|
||||
min: -absMax, max: absMax,
|
||||
calculable: false,
|
||||
orient: 'vertical',
|
||||
right: 4, top: 36, bottom: 40,
|
||||
textStyle: { fontSize: 9 },
|
||||
inRange: {
|
||||
color: ['#8B0000','#CC2200','#F46D43','#FDAE61','#FEE08B',
|
||||
'#FFFFBF','#D9EF8B','#A6D96A','#66BD63','#1A9850','#006837']
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'heatmap',
|
||||
data,
|
||||
emphasis: { itemStyle: { borderColor: '#333', borderWidth: 1 } }
|
||||
}]
|
||||
})
|
||||
this.chartInsts.push(c)
|
||||
},
|
||||
|
||||
async exportPdf() {
|
||||
this.exporting = true
|
||||
try {
|
||||
const [html2canvas, { jsPDF }] = await Promise.all([
|
||||
import('html2canvas').then(m => m.default),
|
||||
import('jspdf')
|
||||
])
|
||||
const el = this.$refs.reportContent
|
||||
const canvas = await html2canvas(el, { scale: 2, useCORS: true, allowTaint: true, backgroundColor: '#fff', logging: false })
|
||||
const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
||||
const pW = pdf.internal.pageSize.getWidth()
|
||||
const pH = pdf.internal.pageSize.getHeight()
|
||||
const mg = 8
|
||||
const printW = pW - mg * 2
|
||||
const ratio = canvas.width / printW
|
||||
const imgH = canvas.height / ratio
|
||||
let rem = imgH, srcY = 0
|
||||
while (rem > 0) {
|
||||
const sliceH = Math.min(pH - mg * 2, rem)
|
||||
const slicePx = sliceH * ratio
|
||||
const sc = document.createElement('canvas')
|
||||
sc.width = canvas.width; sc.height = slicePx
|
||||
sc.getContext('2d').drawImage(canvas, 0, srcY, canvas.width, slicePx, 0, 0, canvas.width, slicePx)
|
||||
pdf.addImage(sc.toDataURL('image/jpeg', 0.95), 'JPEG', mg, mg, printW, sliceH)
|
||||
srcY += slicePx; rem -= sliceH
|
||||
if (rem > 0) pdf.addPage()
|
||||
}
|
||||
const id = getV(this.row, 'EXCOILID') || getV(this.row, 'HOT_COILID') || 'report'
|
||||
pdf.save(`质量报表_${id}.pdf`)
|
||||
} catch (e) {
|
||||
this.$message.error('PDF导出失败:' + e.message)
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.exporting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rpt-toolbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding-bottom: 10px; border-bottom: 1px solid #f0f2f5; margin-bottom: 8px;
|
||||
}
|
||||
.rpt-coil-hint { font-size: 13px; color: #606266; font-weight: 600; }
|
||||
|
||||
.report-body {
|
||||
background: #fff;
|
||||
padding: 18px 22px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 页眉 */
|
||||
.rpt-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
border-bottom: 2px solid #2c5282; padding-bottom: 8px; margin-bottom: 12px;
|
||||
}
|
||||
.rpt-header-left { display: flex; align-items: center; }
|
||||
.rpt-logo { height: 44px; width: auto; object-fit: contain; }
|
||||
.company-sub { font-size: 10px; color: #888; }
|
||||
.rpt-title { font-size: 22px; font-weight: 700; color: #1a365d; letter-spacing: 3px; }
|
||||
.rpt-header-right { text-align: right; }
|
||||
.brand-name { font-size: 16px; font-weight: 700; color: #2c5282; }
|
||||
.brand-sub { font-size: 9px; color: #aaa; letter-spacing: 2px; }
|
||||
|
||||
/* 节标题 */
|
||||
.section-bar {
|
||||
background: #2c5282; color: #fff; font-size: 12px; font-weight: 600;
|
||||
padding: 3px 10px; border-radius: 2px; margin: 10px 0 5px; letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.rpt-table { width: 100%; border-collapse: collapse; font-size: 11px; margin-bottom: 4px; }
|
||||
.rpt-table td, .rpt-table th {
|
||||
border: 1px solid #c9d4e0; padding: 3px 7px; text-align: left; white-space: nowrap;
|
||||
}
|
||||
.rpt-table th { background: #dce8f4; color: #1a365d; font-weight: 600; text-align: center; }
|
||||
.lbl { background: #eef3f9; color: #4a6080; font-weight: 500; width: 88px; }
|
||||
.val { color: #1a1a2e; font-weight: 600; }
|
||||
.val.bold { font-size: 13px; color: #1a365d; letter-spacing: 0.5px; }
|
||||
.quality-table td { text-align: center; }
|
||||
.cell-ok { color: #2e7d32; font-weight: 700; }
|
||||
.cell-ng { color: #c62828; font-weight: 700; }
|
||||
|
||||
/* 图表 */
|
||||
.chart-title {
|
||||
font-size: 12px; font-weight: 600; color: #2c5282;
|
||||
margin: 12px 0 4px; padding-left: 6px;
|
||||
border-left: 3px solid #2c5282;
|
||||
}
|
||||
.chart-subtitle { font-size: 10px; font-weight: normal; color: #888; margin-left: 8px; }
|
||||
.rpt-chart { width: 100%; height: 180px; border: 1px solid #e8edf2; border-radius: 3px; background: #fafbfc; }
|
||||
.rpt-chart-sm { height: 140px; }
|
||||
.rpt-chart-heat { height: 200px; }
|
||||
|
||||
.rpt-footer { margin-top: 16px; padding-top: 6px; border-top: 1px solid #e2e8f0; text-align: center; font-size: 10px; color: #888; }
|
||||
|
||||
::v-deep .el-dialog__body { padding: 12px 20px 20px; }
|
||||
::v-deep .el-button--primary { background: #2c5282 !important; border-color: #2c5282 !important; }
|
||||
::v-deep .el-button--primary:hover { background: #1a365d !important; border-color: #1a365d !important; }
|
||||
</style>
|
||||
436
klp-ui/src/views/micro/pages/acid/components/TrackingView.vue
Normal file
436
klp-ui/src/views/micro/pages/acid/components/TrackingView.vue
Normal file
@@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<div class="tracking-wrap">
|
||||
<!-- 工具栏 -->
|
||||
<div class="track-toolbar">
|
||||
<span class="track-title">跟踪管理</span>
|
||||
<span class="track-update-time" v-if="lastUpdated">更新时间:{{ lastUpdated }}</span>
|
||||
<el-button size="mini" icon="el-icon-refresh" :loading="loading" circle @click="loadData" />
|
||||
</div>
|
||||
|
||||
<div class="tracking-body">
|
||||
<!-- ─── 流程图 ─── -->
|
||||
<div class="flow-diagram">
|
||||
<div class="flow-row">
|
||||
<div v-for="eq in flowRow1" :key="eq.name" class="flow-eq" :class="eq.cls">
|
||||
<div class="eq-name">{{ eq.name }}<span class="eq-sub"> / {{ eq.label }}</span></div>
|
||||
<div v-for="(slot, si) in slotsFor(eq.displayNames)" :key="si"
|
||||
class="eq-slot" :class="slot.matId ? 'slot-active' : 'slot-empty'">
|
||||
{{ slot.matId || '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow-row flow-row-reverse">
|
||||
<div v-for="eq in flowRow2" :key="eq.name" class="flow-eq" :class="eq.cls">
|
||||
<div class="eq-name">{{ eq.name }}<span class="eq-sub"> / {{ eq.label }}</span></div>
|
||||
<div v-for="(slot, si) in slotsFor(eq.displayNames)" :key="si"
|
||||
class="eq-slot" :class="slot.matId ? 'slot-active' : 'slot-empty'">
|
||||
{{ slot.matId || '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow-row flow-row-sparse">
|
||||
<div v-for="eq in flowRow3" :key="eq.name" class="flow-eq" :class="eq.cls" :style="eq.style">
|
||||
<div class="eq-name">{{ eq.name }}<span class="eq-sub"> / {{ eq.label }}</span></div>
|
||||
<div v-for="(slot, si) in slotsFor(eq.displayNames)" :key="si"
|
||||
class="eq-slot" :class="slot.matId ? 'slot-active' : 'slot-empty'">
|
||||
{{ slot.matId || '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow-row">
|
||||
<div v-for="eq in flowRow4" :key="eq.name" class="flow-eq" :class="eq.cls">
|
||||
<div class="eq-name">{{ eq.name }}<span class="eq-sub"> / {{ eq.label }}</span></div>
|
||||
<div v-for="(slot, si) in slotsFor(eq.displayNames)" :key="si"
|
||||
class="eq-slot" :class="slot.matId ? 'slot-active' : 'slot-empty'">
|
||||
{{ slot.matId || '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 入口跟踪 ─── -->
|
||||
<div class="section-label">入口跟踪</div>
|
||||
<div class="entry-wrap">
|
||||
<div v-for="(lane, li) in entryLanes" :key="li" class="entry-lane">
|
||||
<div v-for="pos in lane" :key="pos.displayName"
|
||||
class="entry-card" :class="{ 'has-coil': !!coilAt(pos.displayName) }">
|
||||
<div class="entry-card-header">{{ pos.label }}</div>
|
||||
<template v-if="coilAt(pos.displayName)">
|
||||
<div class="entry-kv"><span class="ek">冷卷号</span><span class="ev">{{ coilAt(pos.displayName).coilid }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">热卷号</span><span class="ev">{{ coilAt(pos.displayName).hot_coilid }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">钢种</span><span class="ev">{{ coilAt(pos.displayName).grade }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">来料厚[mm]</span><span class="ev">{{ coilAt(pos.displayName).entry_thick }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">成品厚[mm]</span><span class="ev">{{ coilAt(pos.displayName).exit_thick }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">厚差[mm]</span><span class="ev">{{ coilAt(pos.displayName).min_exit_thick }} / {{ coilAt(pos.displayName).max_exit_thick }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">来料宽[mm]</span><span class="ev">{{ coilAt(pos.displayName).entry_width }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">成品宽[mm]</span><span class="ev">{{ coilAt(pos.displayName).exit_width }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">重量[t]</span><span class="ev">{{ coilAt(pos.displayName).entry_weight }}</span></div>
|
||||
<div class="entry-kv"><span class="ek">轧制模式</span><span class="ev">{{ coilAt(pos.displayName).roll_mode }}</span></div>
|
||||
</template>
|
||||
<div v-else class="entry-empty">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 详细信息 ─── -->
|
||||
<div class="section-label">详细信息</div>
|
||||
<el-table :data="detailTableRows" size="mini" border stripe style="width:100%">
|
||||
<el-table-column prop="coilid" label="冷卷号" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="hot_coilid" label="热卷号" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="grade" label="钢种" width="100" />
|
||||
<el-table-column prop="entry_thick" label="来料厚度" width="80" />
|
||||
<el-table-column prop="exit_thick" label="产品厚度" width="80" />
|
||||
<el-table-column prop="max_exit_thick" label="偏差上限" width="80" />
|
||||
<el-table-column prop="min_exit_thick" label="偏差下限" width="80" />
|
||||
<el-table-column prop="entry_width" label="来料宽度" width="80" />
|
||||
<el-table-column prop="exit_width" label="产品宽度" width="80" />
|
||||
<el-table-column prop="roll_mode" label="轧制模式" width="100" />
|
||||
<el-table-column prop="park_type" label="包装要求" width="80" />
|
||||
<el-table-column prop="side_trim" label="切边要求" width="80" />
|
||||
<el-table-column prop="entry_weight" label="来料重量" width="80" />
|
||||
<el-table-column prop="split_num" label="分卷数" width="70" />
|
||||
<el-table-column prop="position" label="位置" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="online_date" label="上线时间" width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTrackData } from '@/api/l2/timing'
|
||||
|
||||
const DISPLAY_LABEL = {
|
||||
UC1: '1#上卷小车', UC2: '2#上卷小车',
|
||||
WEIT1: '1#称重位', WEIT2: '2#称重位',
|
||||
CR1: '1#地辊', CR2: '2#地辊',
|
||||
ENC1: '1#倒卷小车', ENC2: '2#倒卷小车',
|
||||
ENC3: '3#倒卷小车', ENC4: '4#倒卷小车',
|
||||
CS1: '1#上卷鞍座', CS2: '2#上卷鞍座',
|
||||
CS3: '3#上卷鞍座', CS4: '4#上卷鞍座',
|
||||
CS5: '5#上卷鞍座', CS6: '6#上卷鞍座',
|
||||
CS7: '7#上卷鞍座', CS8: '8#上卷鞍座',
|
||||
CS9: '9#上卷鞍座', CS10: '10#上卷鞍座',
|
||||
CS11: '11#上卷鞍座', CS12: '12#上卷鞍座',
|
||||
CS13: '13#上卷鞍座', CS14: '14#上卷鞍座',
|
||||
CXL: '出口活套', CXL1: '出口活套1', CXL2: '出口活套2',
|
||||
CPR: '酸洗段', CPR1: '酸洗段1', CPR2: '酸洗段2',
|
||||
CEL: '入口活套', CEL1: '入口活套1', CEL2: '入口活套2',
|
||||
TRIMMER: '圆盘剪',
|
||||
RINSE: '清洗段',
|
||||
TLV: '拉矫机', TLV1: '拉矫机1',
|
||||
TEL: '联机活套', TEL1: '联机活套1', TEL2: '联机活套2',
|
||||
EXAM: '检查站', EXAMC: '检查站',
|
||||
MILL: '轧机',
|
||||
COILER: '卷取机',
|
||||
EXC: '卸卷小车',
|
||||
DC0: '交接鞍座',
|
||||
EXC1: '步进梁1', EXC2: '步进梁2'
|
||||
}
|
||||
|
||||
const ENTRY_LANES = [
|
||||
[
|
||||
{ displayName: 'UC1', label: '1#上卷小车' },
|
||||
{ displayName: 'WEIT1', label: '1#称重位' },
|
||||
{ displayName: 'CR1', label: '1#地辊' },
|
||||
{ displayName: 'CS13', label: '13#上卷鞍座' },
|
||||
{ displayName: 'CS11', label: '11#上卷鞍座' },
|
||||
{ displayName: 'CS9', label: '9#上卷鞍座' },
|
||||
{ displayName: 'CS7', label: '7#上卷鞍座' },
|
||||
{ displayName: 'ENC1', label: '1#倒卷小车' },
|
||||
{ displayName: 'CS5', label: '5#上卷鞍座' },
|
||||
{ displayName: 'CS3', label: '3#上卷鞍座' },
|
||||
{ displayName: 'CS1', label: '1#上卷鞍座' }
|
||||
],
|
||||
[
|
||||
{ displayName: 'UC2', label: '2#上卷小车' },
|
||||
{ displayName: 'WEIT2', label: '2#称重位' },
|
||||
{ displayName: 'CR2', label: '2#地辊' },
|
||||
{ displayName: 'CS14', label: '14#上卷鞍座' },
|
||||
{ displayName: 'CS12', label: '12#上卷鞍座' },
|
||||
{ displayName: 'CS10', label: '10#上卷鞍座' },
|
||||
{ displayName: 'CS8', label: '8#上卷鞍座' },
|
||||
{ displayName: 'ENC2', label: '2#倒卷小车' },
|
||||
{ displayName: 'CS6', label: '6#上卷鞍座' },
|
||||
{ displayName: 'CS4', label: '4#上卷鞍座' },
|
||||
{ displayName: 'CS2', label: '2#上卷鞍座' }
|
||||
]
|
||||
]
|
||||
|
||||
const FLOW_ROWS = {
|
||||
row1: [
|
||||
{ name: 'CXL', label: '出口活套', displayNames: ['CXL'], cls: 'eq-process' },
|
||||
{ name: 'CPR', label: '酸洗段', displayNames: ['CPR'], cls: 'eq-process' },
|
||||
{ name: 'CEL', label: '入口活套', displayNames: ['CEL','CEL1'], cls: 'eq-process' },
|
||||
{ name: 'UC1', label: '开卷机', displayNames: ['UC1'], cls: 'eq-entry' },
|
||||
{ name: 'WEIT1', label: '称重位', displayNames: ['WEIT1'], cls: 'eq-entry' },
|
||||
{ name: 'CR1', label: '地辊', displayNames: ['CR1'], cls: 'eq-entry' }
|
||||
],
|
||||
row2: [
|
||||
{ name: 'TRIMMER', label: '圆盘剪', displayNames: ['TRIMMER'], cls: 'eq-process' },
|
||||
{ name: 'RINSE', label: '清洗段', displayNames: ['RINSE'], cls: 'eq-process' },
|
||||
{ name: 'TLV', label: '拉矫机', displayNames: ['TLV'], cls: 'eq-process' },
|
||||
{ name: 'UC2', label: '开卷机', displayNames: ['UC2'], cls: 'eq-entry' },
|
||||
{ name: 'WEIT2', label: '称重位', displayNames: ['WEIT2'], cls: 'eq-entry' },
|
||||
{ name: 'CR2', label: '地辊', displayNames: ['CR2'], cls: 'eq-entry' }
|
||||
],
|
||||
row3: [
|
||||
{ name: 'TEL', label: '联机活套', displayNames: ['TEL'], cls: 'eq-mill', style: 'flex:1' },
|
||||
{ name: 'EXAM', label: '检查站', displayNames: ['EXAM','EXAMC'], cls: 'eq-exit', style: 'flex:1;margin-left:auto' }
|
||||
],
|
||||
row4: [
|
||||
{ name: 'MILL', label: '轧机', displayNames: ['MILL'], cls: 'eq-mill' },
|
||||
{ name: 'COILER', label: '卷取机', displayNames: ['COILER'],cls: 'eq-mill' },
|
||||
{ name: 'EXC', label: '卸卷小车',displayNames: ['EXC'], cls: 'eq-exit' },
|
||||
{ name: 'DC0', label: '交接鞍座',displayNames: ['DC0'], cls: 'eq-exit' },
|
||||
{ name: 'EXC1', label: '步进梁1', displayNames: ['EXC1'], cls: 'eq-exit' },
|
||||
{ name: 'EXC2', label: '步进梁2', displayNames: ['EXC2'], cls: 'eq-exit' }
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TrackingView',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
lastUpdated: '',
|
||||
matMapRows: [],
|
||||
entryTraceRows: [],
|
||||
exitTraceRows: [],
|
||||
pollTimer: null,
|
||||
entryLanes: ENTRY_LANES,
|
||||
flowRow1: FLOW_ROWS.row1,
|
||||
flowRow2: FLOW_ROWS.row2,
|
||||
flowRow3: FLOW_ROWS.row3,
|
||||
flowRow4: FLOW_ROWS.row4
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
matMapByDisplayName() {
|
||||
const map = {}
|
||||
for (const row of this.matMapRows) {
|
||||
const dn = (row.displayname || row.DISPLAYNAME || '').toUpperCase()
|
||||
if (!dn) continue
|
||||
if (!map[dn]) map[dn] = []
|
||||
map[dn].push({
|
||||
matId: row.matid || row.MATID || null,
|
||||
l1MapIdx: row.l1mapidx != null ? row.l1mapidx : row.L1MAPIDX
|
||||
})
|
||||
}
|
||||
return map
|
||||
},
|
||||
entryByL1MapIdx() {
|
||||
const map = {}
|
||||
for (const row of this.entryTraceRows) {
|
||||
const idx = row.l1mapidx != null ? row.l1mapidx : row.L1MAPIDX
|
||||
if (idx != null) map[idx] = row
|
||||
}
|
||||
return map
|
||||
},
|
||||
detailTableRows() {
|
||||
return this.entryTraceRows.map(row => {
|
||||
const idx = row.l1mapidx != null ? row.l1mapidx : row.L1MAPIDX
|
||||
const posRow = this.matMapRows.find(m => (m.l1mapidx != null ? m.l1mapidx : m.L1MAPIDX) === idx)
|
||||
const dn = posRow ? (posRow.displayname || posRow.DISPLAYNAME || '').toUpperCase() : ''
|
||||
return {
|
||||
...row,
|
||||
position: DISPLAY_LABEL[dn] || dn || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
this.pollTimer = setInterval(this.loadData, 5000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.pollTimer) clearInterval(this.pollTimer)
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getTrackData()
|
||||
const data = res?.data || {}
|
||||
this.matMapRows = data.matMap || []
|
||||
this.entryTraceRows = data.entryTrace || []
|
||||
this.exitTraceRows = data.exitTrace || []
|
||||
this.lastUpdated = new Date().toLocaleTimeString()
|
||||
} catch (_) {}
|
||||
finally { this.loading = false }
|
||||
},
|
||||
coilAt(displayName) {
|
||||
const slots = this.matMapByDisplayName[displayName]
|
||||
if (!slots || !slots.length) return null
|
||||
const slot = slots[0]
|
||||
if (!slot.matId) return null
|
||||
return this.entryByL1MapIdx[slot.l1MapIdx] || null
|
||||
},
|
||||
slotsFor(displayNames) {
|
||||
const result = []
|
||||
for (const dn of displayNames) {
|
||||
for (const s of (this.matMapByDisplayName[dn] || [])) {
|
||||
result.push({ matId: s.matId })
|
||||
}
|
||||
}
|
||||
while (result.length < 2) result.push({ matId: null })
|
||||
return result.slice(0, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tracking-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 130px);
|
||||
background: #f5f7fa;
|
||||
padding: 8px 12px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.track-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.track-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.track-update-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tracking-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: #2c5282;
|
||||
padding: 4px 12px;
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ─── 流程图 ─── */
|
||||
.flow-diagram {
|
||||
background: #b8cde0;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.flow-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.flow-row-sparse {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flow-eq {
|
||||
min-width: 110px;
|
||||
flex: 1;
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.eq-name {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #1a2a4a;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.eq-sub { font-size: 10px; font-weight: normal; color: #2c4a7a; }
|
||||
|
||||
.eq-slot {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
text-align: center;
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.slot-empty { background: #cdd8e8; }
|
||||
.slot-active { color: #fff; background: #1a4a8a; }
|
||||
|
||||
.eq-process { background: #22d3ee44; border: 1px solid #06b6d4; }
|
||||
.eq-process .slot-active { background: #0891b2; }
|
||||
.eq-entry { background: #fbbf2444; border: 1px solid #f59e0b; }
|
||||
.eq-entry .slot-active { background: #b45309; }
|
||||
.eq-mill { background: #f8717144; border: 1px solid #ef4444; }
|
||||
.eq-mill .slot-active { background: #dc2626; }
|
||||
.eq-exit { background: #a3e63544; border: 1px solid #84cc16; }
|
||||
.eq-exit .slot-active { background: #4d7c0f; }
|
||||
|
||||
/* ─── 入口跟踪 ─── */
|
||||
.entry-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.entry-lane {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.entry-card {
|
||||
min-width: 108px;
|
||||
flex: 1;
|
||||
background: #d9e6f7;
|
||||
border: 1px solid #b0c8e8;
|
||||
border-radius: 4px;
|
||||
padding: 5px 7px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.entry-card.has-coil { background: #c5d8f0; }
|
||||
|
||||
.entry-card-header {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #2c3e6b;
|
||||
text-align: center;
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid #b0c8e8;
|
||||
}
|
||||
|
||||
.entry-kv {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 1px 0;
|
||||
gap: 2px;
|
||||
}
|
||||
.ek { color: #5a6e99; flex-shrink: 0; }
|
||||
.ev { color: #1a4a8a; font-weight: 500; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.entry-empty { text-align: center; color: #b0bec5; padding: 16px 0; }
|
||||
</style>
|
||||
@@ -46,6 +46,14 @@
|
||||
<i class="el-icon-warning"></i>
|
||||
<span slot="title">停机</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="acidTiming">
|
||||
<i class="el-icon-alarm-clock"></i>
|
||||
<span slot="title">app</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="tracking">
|
||||
<i class="el-icon-location-outline"></i>
|
||||
<span slot="title">跟踪</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div style="flex: 1; overflow: hidden;">
|
||||
@@ -65,6 +73,8 @@ import ActualPerformance from './components/ActualPerformance.vue';
|
||||
import RollConfig from '@/views/timing/roll/index.vue';
|
||||
import RollHistory from '@/views/timing/roll/history.vue';
|
||||
import Stoppage from '@/views/timing/stoppage/index.vue';
|
||||
import AcidTiming from '@/views/lines/acid/index.vue';
|
||||
import TrackingView from './components/TrackingView.vue';
|
||||
|
||||
export default {
|
||||
name: 'AcidSystem',
|
||||
@@ -78,7 +88,9 @@ export default {
|
||||
ActualPerformance,
|
||||
RollConfig,
|
||||
RollHistory,
|
||||
Stoppage
|
||||
Stoppage,
|
||||
AcidTiming,
|
||||
TrackingView
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -98,6 +110,8 @@ export default {
|
||||
rollConfig: 'RollConfig',
|
||||
rollHistory: 'RollHistory',
|
||||
stoppage: 'Stoppage',
|
||||
acidTiming: 'AcidTiming',
|
||||
tracking: 'TrackingView',
|
||||
};
|
||||
return componentMap[this.activeMenu];
|
||||
},
|
||||
|
||||
623
klp-ui/src/views/timing/acid/QualityReportDialog.vue
Normal file
623
klp-ui/src/views/timing/acid/QualityReportDialog.vue
Normal file
@@ -0,0 +1,623 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="产品质量报表预览"
|
||||
:visible.sync="visible"
|
||||
width="880px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<!-- 工具栏 -->
|
||||
<div class="rpt-toolbar">
|
||||
<span class="rpt-coil-hint">{{ coilLabel }}</span>
|
||||
<div class="rpt-toolbar-right">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
:loading="exporting"
|
||||
@click="exportPdf"
|
||||
>导出 PDF</el-button>
|
||||
<el-button size="small" @click="visible = false">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="dataLoading" class="rpt-loading">
|
||||
<i class="el-icon-loading" /> 正在加载实绩数据…
|
||||
</div>
|
||||
|
||||
<!-- 报告主体 -->
|
||||
<div v-show="!dataLoading" ref="reportContent" class="report-body">
|
||||
<!-- ─── 页眉 ─── -->
|
||||
<div class="rpt-header">
|
||||
<div class="rpt-header-left">
|
||||
<div class="company-line">中国五矿</div>
|
||||
<div class="mcc-line">MCC 中冶赛迪信息</div>
|
||||
</div>
|
||||
<div class="rpt-header-title">产品质量报表</div>
|
||||
<div class="rpt-header-right">
|
||||
<div class="brand-name">科伦普</div>
|
||||
<div class="brand-sub">KE LUN PU</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 钢卷规格 ─── -->
|
||||
<div class="rpt-section-bar">钢卷规格</div>
|
||||
<table class="rpt-table spec-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="cell-label">钢卷号</td>
|
||||
<td class="cell-val cell-coilid">{{ v('hot_coilid') || v('coilid') }}</td>
|
||||
<td class="cell-label">来料厚度[mm]</td>
|
||||
<td class="cell-val">{{ v('entry_thick') }}</td>
|
||||
<td class="cell-label">来料宽度[mm]</td>
|
||||
<td class="cell-val">{{ v('entry_width') }}</td>
|
||||
<td class="cell-label">来料重量[t]</td>
|
||||
<td class="cell-val">{{ v('entry_weight') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cell-label">钢种</td>
|
||||
<td class="cell-val">{{ v('steel_grade') || v('steelgrade') || v('process_code') }}</td>
|
||||
<td class="cell-label">成品厚度[mm]</td>
|
||||
<td class="cell-val">{{ v('exit_thick') }}</td>
|
||||
<td class="cell-label">成品宽度[mm]</td>
|
||||
<td class="cell-val">{{ v('exit_width') }}</td>
|
||||
<td class="cell-label">成品重量[t]</td>
|
||||
<td class="cell-val">{{ v('exit_weight') || v('entry_weight') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cell-label">班组</td>
|
||||
<td class="cell-val">{{ v('shift') || v('class_no') || '—' }}</td>
|
||||
<td class="cell-label">偏差上限[mm]</td>
|
||||
<td class="cell-val">{{ v('thick_upper') || v('up_tol') || '—' }}</td>
|
||||
<td class="cell-label">压下率[%]</td>
|
||||
<td class="cell-val">{{ reductionRate }}</td>
|
||||
<td class="cell-label">成品长度[m]</td>
|
||||
<td class="cell-val">{{ v('exit_length') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cell-label">下工序</td>
|
||||
<td class="cell-val">{{ v('next_process') || v('next_proc') || '—' }}</td>
|
||||
<td class="cell-label">偏差下限[mm]</td>
|
||||
<td class="cell-val">{{ v('thick_lower') || v('dn_tol') || '—' }}</td>
|
||||
<td class="cell-label">原料卷号</td>
|
||||
<td class="cell-val" colspan="3">{{ v('coilid') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cell-label">生产时间</td>
|
||||
<td class="cell-val" colspan="3">{{ v('prod_time') || v('start_time') || v('createtime') || '—' }}</td>
|
||||
<td class="cell-label">生产时长</td>
|
||||
<td class="cell-val" colspan="3">{{ productionDuration }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ─── 质量判定 ─── -->
|
||||
<div class="rpt-section-bar">质量判定</div>
|
||||
<table class="rpt-table quality-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>轧制总长[m]</th>
|
||||
<th>头尾超差[m]</th>
|
||||
<th>厚度合格率[%]</th>
|
||||
<th>目标板形</th>
|
||||
<th>板形合格率[%]</th>
|
||||
<th>板形质量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ rolledLength }}</td>
|
||||
<td>{{ v('head_tail_dev') || '—' }}</td>
|
||||
<td>{{ thickPassRate }}</td>
|
||||
<td>{{ v('target_shape') || '平直' }}</td>
|
||||
<td>{{ v('shape_pass_rate') || '—' }}</td>
|
||||
<td :class="qualityClass">{{ qualityLabel }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ─── 图表区 ─── -->
|
||||
<template v-if="hasSeries">
|
||||
<!-- 厚度偏差 -->
|
||||
<template v-if="hasField('thick_dev') || hasField('act_thick')">
|
||||
<div class="rpt-chart-title">
|
||||
成品厚度偏差
|
||||
<span class="rpt-chart-subtitle">产出厚度 - {{ v('exit_thick') }} [mm]</span>
|
||||
</div>
|
||||
<div ref="chartThick" class="rpt-chart" />
|
||||
</template>
|
||||
|
||||
<!-- 速度趋势 -->
|
||||
<template v-if="hasField('plspeed') || hasField('trimspeed') || hasField('millexitspeed')">
|
||||
<div class="rpt-chart-title">速度趋势 [m/min]</div>
|
||||
<div ref="chartSpeed" class="rpt-chart" />
|
||||
</template>
|
||||
|
||||
<!-- 轧机出口速度 -->
|
||||
<template v-if="hasField('millexitspeed') && (hasField('plspeed') || hasField('trimspeed'))">
|
||||
<div class="rpt-chart-title">轧机出口速度 [m/min]</div>
|
||||
<div ref="chartMillSpeed" class="rpt-chart" />
|
||||
</template>
|
||||
|
||||
<!-- 张力趋势 -->
|
||||
<template v-if="hasField('pltens') || hasField('enltens') || hasField('cxltens')">
|
||||
<div class="rpt-chart-title">张力趋势 [N]</div>
|
||||
<div ref="chartTension" class="rpt-chart" />
|
||||
</template>
|
||||
|
||||
<!-- 额外字段图表 -->
|
||||
<template v-for="grp in extraChartGroups">
|
||||
<div :key="grp.title + '_t'" class="rpt-chart-title">{{ grp.title }}</div>
|
||||
<div :key="grp.title + '_c'" :ref="'chartExtra_' + grp.refKey" class="rpt-chart" />
|
||||
</template>
|
||||
</template>
|
||||
<div v-else class="rpt-no-data">暂无实绩曲线数据</div>
|
||||
|
||||
<!-- ─── 页脚 ─── -->
|
||||
<div class="rpt-footer">生成时间: {{ nowStr }}</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { getPlanWithSeg } from '@/api/l2/timing'
|
||||
|
||||
// Series fields consumed by named charts — remaining fields get auto-grouped
|
||||
const NAMED_KEYS = new Set(['startpos', 'thick_dev', 'act_thick', 'plspeed', 'trimspeed', 'millexitspeed', 'pltens', 'enltens', 'cxltens'])
|
||||
|
||||
// Groups of extra series fields to chart together
|
||||
const EXTRA_GROUPS = [
|
||||
{ keys: ['bendf1', 'bendf2', 'bendf3', 'bendf4', 'bendf5'], title: '弯辊力 [kN]', unit: 'kN' },
|
||||
{ keys: ['rollf1', 'rollf2', 'rollf3', 'rollf4', 'rollf5'], title: '轧制力 [kN]', unit: 'kN' },
|
||||
{ keys: ['fwd_slip1', 'fwd_slip2', 'fwd_slip3', 'fwd_slip4', 'fwd_slip5'], title: '前滑值', unit: '' },
|
||||
{ keys: ['temp_in', 'temp_out', 'cool_temp'], title: '温度 [°C]', unit: '°C' },
|
||||
]
|
||||
|
||||
function lineOption(title, xData, series, yName) {
|
||||
return {
|
||||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal', color: '#303133' }, top: 4, left: 8 },
|
||||
tooltip: { trigger: 'axis', confine: true },
|
||||
legend: { top: 4, right: 8, textStyle: { fontSize: 10 } },
|
||||
grid: { top: 40, bottom: 32, left: 10, right: 12, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category', data: xData, name: '带钢长度[m]',
|
||||
nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 9, interval: 'auto' }
|
||||
},
|
||||
yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 9 } },
|
||||
series: series.map((s, i) => ({
|
||||
name: s.name, type: 'line', smooth: false, symbol: 'none',
|
||||
lineStyle: { width: 1.5 },
|
||||
data: s.data
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
function dualAxisOption(title, xData, devSeries, thickSeries, exitThick) {
|
||||
return {
|
||||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal', color: '#303133' }, top: 4, left: 8 },
|
||||
tooltip: { trigger: 'axis', confine: true },
|
||||
legend: { top: 4, right: 8, textStyle: { fontSize: 10 } },
|
||||
grid: { top: 40, bottom: 32, left: 10, right: 12, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category', data: xData, name: '带钢长度[m]',
|
||||
nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 9, interval: 'auto' }
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: 'μm', nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 9 } },
|
||||
{ type: 'value', name: 'mm', nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 9 }, splitLine: { show: false } }
|
||||
],
|
||||
series: [
|
||||
{ name: '厚度偏差', type: 'line', yAxisIndex: 0, smooth: false, symbol: 'none', lineStyle: { width: 1, color: '#5F7BA0' }, data: devSeries },
|
||||
thickSeries && { name: '实际厚度', type: 'line', yAxisIndex: 1, smooth: false, symbol: 'none', lineStyle: { width: 1.5, color: '#e6a23c' }, data: thickSeries }
|
||||
].filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'QualityReportDialog',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
dataLoading: false,
|
||||
exporting: false,
|
||||
plan: null,
|
||||
series: null,
|
||||
nowStr: '',
|
||||
chartInsts: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
coilLabel() {
|
||||
const id = this.plan?.hot_coilid || this.plan?.coilid || ''
|
||||
return id ? `钢卷号:${id}` : ''
|
||||
},
|
||||
hasSeries() {
|
||||
return !!this.series && Object.keys(this.series).length > 0
|
||||
},
|
||||
reductionRate() {
|
||||
const e = parseFloat(this.plan?.entry_thick)
|
||||
const x = parseFloat(this.plan?.exit_thick)
|
||||
if (!e || !x || e <= 0) return '—'
|
||||
return ((e - x) / e * 100).toFixed(2)
|
||||
},
|
||||
rolledLength() {
|
||||
if (this.plan?.exit_length) return this.plan.exit_length
|
||||
if (this.series?.startpos?.length) {
|
||||
const max = Math.max(...this.series.startpos.filter(v => v != null))
|
||||
return max > 0 ? max.toFixed(1) : '—'
|
||||
}
|
||||
return '—'
|
||||
},
|
||||
thickPassRate() {
|
||||
const r = this.plan?.thick_pass_rate || this.plan?.thick_passrate
|
||||
return r != null ? r : '—'
|
||||
},
|
||||
qualityLabel() {
|
||||
const r = this.plan?.quality || this.plan?.quality_result
|
||||
if (r) return r
|
||||
const tp = parseFloat(this.thickPassRate)
|
||||
if (!isNaN(tp)) return tp >= 99 ? '合格' : '不合格'
|
||||
return '—'
|
||||
},
|
||||
qualityClass() {
|
||||
return this.qualityLabel === '合格' ? 'cell-ok' : this.qualityLabel === '不合格' ? 'cell-ng' : ''
|
||||
},
|
||||
productionDuration() {
|
||||
const d = this.plan?.prod_duration || this.plan?.duration
|
||||
if (!d) return '—'
|
||||
const sec = parseInt(d)
|
||||
if (isNaN(sec)) return d
|
||||
const h = Math.floor(sec / 3600)
|
||||
const m = Math.floor((sec % 3600) / 60)
|
||||
const s = sec % 60
|
||||
return `${String(h).padStart(2, '0')}时${String(m).padStart(2, '0')}分${String(s).padStart(2, '0')}秒`
|
||||
},
|
||||
extraChartGroups() {
|
||||
if (!this.series) return []
|
||||
const result = []
|
||||
// first try the predefined groups
|
||||
for (const grp of EXTRA_GROUPS) {
|
||||
const presentKeys = grp.keys.filter(k => this.hasField(k))
|
||||
if (presentKeys.length) {
|
||||
result.push({ title: grp.title, unit: grp.unit, keys: presentKeys, refKey: grp.title.replace(/\s/g, '_') })
|
||||
}
|
||||
}
|
||||
// then collect truly unknown keys
|
||||
const knownInExtra = new Set(EXTRA_GROUPS.flatMap(g => g.keys))
|
||||
const unknown = Object.keys(this.series).filter(k => !NAMED_KEYS.has(k) && !knownInExtra.has(k) && Array.isArray(this.series[k]) && this.series[k].length)
|
||||
if (unknown.length) {
|
||||
result.push({ title: '其他工艺参数', unit: '', keys: unknown, refKey: 'other' })
|
||||
}
|
||||
return result
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
v(key) {
|
||||
const val = this.plan?.[key]
|
||||
return val != null && val !== '' ? val : '—'
|
||||
},
|
||||
hasField(key) {
|
||||
return !!(this.series?.[key]?.length)
|
||||
},
|
||||
|
||||
// Public: open with a row object
|
||||
open(row) {
|
||||
this.plan = row
|
||||
this.series = null
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
onOpen() {
|
||||
this.nowStr = new Date().toLocaleString('zh-CN').replace(/\//g, '-')
|
||||
this.$nextTick(() => {
|
||||
if (this.series) {
|
||||
this.renderAllCharts()
|
||||
} else if (this.plan) {
|
||||
this.fetchSeries()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.disposeCharts()
|
||||
},
|
||||
|
||||
async fetchSeries() {
|
||||
const id = this.plan?.encoilid || this.plan?.coilid
|
||||
if (!id) return
|
||||
this.dataLoading = true
|
||||
try {
|
||||
const res = await getPlanWithSeg(id)
|
||||
const plan = res?.data?.plan || res?.data?.firstRow || this.plan
|
||||
const series = res?.data?.series || null
|
||||
if (plan) this.plan = plan
|
||||
this.series = series
|
||||
this.$nextTick(() => this.renderAllCharts())
|
||||
} finally {
|
||||
this.dataLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
disposeCharts() {
|
||||
this.chartInsts.forEach(c => { try { c.dispose() } catch (_) {} })
|
||||
this.chartInsts = []
|
||||
},
|
||||
|
||||
pick(key) {
|
||||
return (this.series?.[key] || []).map(v => v == null ? null : +parseFloat(v).toFixed(3))
|
||||
},
|
||||
|
||||
xData() {
|
||||
return (this.series?.startpos || []).map(v => v == null ? '' : (+parseFloat(v).toFixed(1)))
|
||||
},
|
||||
|
||||
renderAllCharts() {
|
||||
this.disposeCharts()
|
||||
if (!this.series) return
|
||||
const x = this.xData()
|
||||
|
||||
// 厚度偏差
|
||||
if (this.$refs.chartThick) {
|
||||
const devKey = this.hasField('thick_dev') ? 'thick_dev' : null
|
||||
const actKey = this.hasField('act_thick') ? 'act_thick' : null
|
||||
if (devKey || actKey) {
|
||||
const devData = devKey ? this.pick(devKey).map(v => v == null ? null : v * 1000) : []
|
||||
const actData = actKey ? this.pick(actKey) : null
|
||||
const c = echarts.init(this.$refs.chartThick, null, { renderer: 'canvas' })
|
||||
if (devKey) {
|
||||
c.setOption(dualAxisOption('成品厚度偏差', x, devData, actData, this.plan?.exit_thick))
|
||||
} else {
|
||||
c.setOption(lineOption('实际厚度 [mm]', x, [{ name: '实际厚度', data: actData }], 'mm'))
|
||||
}
|
||||
this.chartInsts.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
// 速度趋势
|
||||
if (this.$refs.chartSpeed) {
|
||||
const series = []
|
||||
if (this.hasField('plspeed')) series.push({ name: '轧制速度 plspeed', data: this.pick('plspeed') })
|
||||
if (this.hasField('trimspeed')) series.push({ name: '剪切速度 trimspeed', data: this.pick('trimspeed') })
|
||||
if (!this.hasField('plspeed') && !this.hasField('trimspeed') && this.hasField('millexitspeed')) {
|
||||
series.push({ name: '轧机出口速度', data: this.pick('millexitspeed') })
|
||||
}
|
||||
if (series.length) {
|
||||
const c = echarts.init(this.$refs.chartSpeed, null, { renderer: 'canvas' })
|
||||
c.setOption(lineOption('速度趋势', x, series, 'm/min'))
|
||||
this.chartInsts.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
// 轧机出口速度(单独显示)
|
||||
if (this.$refs.chartMillSpeed && this.hasField('millexitspeed')) {
|
||||
const c = echarts.init(this.$refs.chartMillSpeed, null, { renderer: 'canvas' })
|
||||
c.setOption(lineOption('轧机出口速度', x, [{ name: 'millexitspeed', data: this.pick('millexitspeed') }], 'm/min'))
|
||||
this.chartInsts.push(c)
|
||||
}
|
||||
|
||||
// 张力趋势
|
||||
if (this.$refs.chartTension) {
|
||||
const series = []
|
||||
if (this.hasField('pltens')) series.push({ name: '出口张力 pltens', data: this.pick('pltens') })
|
||||
if (this.hasField('enltens')) series.push({ name: '入口张力 enltens', data: this.pick('enltens') })
|
||||
if (this.hasField('cxltens')) series.push({ name: 'cxltens', data: this.pick('cxltens') })
|
||||
if (series.length) {
|
||||
const c = echarts.init(this.$refs.chartTension, null, { renderer: 'canvas' })
|
||||
c.setOption(lineOption('张力趋势', x, series, 'N'))
|
||||
this.chartInsts.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
// 额外图表组
|
||||
for (const grp of this.extraChartGroups) {
|
||||
const refKey = 'chartExtra_' + grp.refKey
|
||||
const refEl = this.$refs[refKey]
|
||||
const el = Array.isArray(refEl) ? refEl[0] : refEl
|
||||
if (!el) continue
|
||||
const series = grp.keys.filter(k => this.hasField(k)).map(k => ({ name: k, data: this.pick(k) }))
|
||||
if (series.length) {
|
||||
const c = echarts.init(el, null, { renderer: 'canvas' })
|
||||
c.setOption(lineOption(grp.title, x, series, grp.unit))
|
||||
this.chartInsts.push(c)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async exportPdf() {
|
||||
this.exporting = true
|
||||
try {
|
||||
const [html2canvas, { jsPDF }] = await Promise.all([
|
||||
import('html2canvas').then(m => m.default),
|
||||
import('jspdf')
|
||||
])
|
||||
|
||||
const el = this.$refs.reportContent
|
||||
const canvas = await html2canvas(el, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff',
|
||||
logging: false
|
||||
})
|
||||
|
||||
const imgData = canvas.toDataURL('image/jpeg', 0.95)
|
||||
const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
||||
const pageW = pdf.internal.pageSize.getWidth() // 210
|
||||
const pageH = pdf.internal.pageSize.getHeight() // 297
|
||||
const margin = 8
|
||||
const printW = pageW - margin * 2
|
||||
const ratio = canvas.width / printW
|
||||
const imgH = canvas.height / ratio
|
||||
let remaining = imgH
|
||||
let srcY = 0
|
||||
|
||||
while (remaining > 0) {
|
||||
const sliceH = Math.min(pageH - margin * 2, remaining)
|
||||
const slicePx = sliceH * ratio
|
||||
const sliceCanvas = document.createElement('canvas')
|
||||
sliceCanvas.width = canvas.width
|
||||
sliceCanvas.height = slicePx
|
||||
const ctx = sliceCanvas.getContext('2d')
|
||||
ctx.drawImage(canvas, 0, srcY, canvas.width, slicePx, 0, 0, canvas.width, slicePx)
|
||||
pdf.addImage(sliceCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, margin, printW, sliceH)
|
||||
srcY += slicePx
|
||||
remaining -= sliceH
|
||||
if (remaining > 0) pdf.addPage()
|
||||
}
|
||||
|
||||
const coilId = this.plan?.hot_coilid || this.plan?.coilid || 'report'
|
||||
pdf.save(`质量报表_${coilId}.pdf`)
|
||||
} catch (e) {
|
||||
this.$message.error('PDF导出失败:' + e.message)
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.exporting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ── 工具栏 ── */
|
||||
.rpt-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 0 10px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.rpt-coil-hint { font-size: 13px; color: #606266; font-weight: 500; }
|
||||
.rpt-toolbar-right { display: flex; gap: 8px; }
|
||||
|
||||
/* ── 加载 ── */
|
||||
.rpt-loading {
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* ── 报告主体 ── */
|
||||
.report-body {
|
||||
background: #fff;
|
||||
padding: 20px 24px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
/* 页眉 */
|
||||
.rpt-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 2px solid #2c5282;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.rpt-header-left { font-size: 11px; color: #555; line-height: 1.6; }
|
||||
.company-line { font-weight: 600; font-size: 13px; }
|
||||
.mcc-line { font-size: 10px; color: #888; }
|
||||
.rpt-header-title { font-size: 22px; font-weight: 700; color: #1a365d; letter-spacing: 2px; }
|
||||
.rpt-header-right { text-align: right; }
|
||||
.brand-name { font-size: 16px; font-weight: 700; color: #2c5282; letter-spacing: 1px; }
|
||||
.brand-sub { font-size: 9px; color: #888; letter-spacing: 2px; }
|
||||
|
||||
/* 节标题 */
|
||||
.rpt-section-bar {
|
||||
background: #2c5282;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
border-radius: 2px;
|
||||
margin: 10px 0 6px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 规格表 & 质量判定表 */
|
||||
.rpt-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 11.5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.rpt-table td, .rpt-table th {
|
||||
border: 1px solid #c9d4e0;
|
||||
padding: 4px 8px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rpt-table th {
|
||||
background: #dce8f4;
|
||||
color: #1a365d;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
}
|
||||
.cell-label {
|
||||
background: #eef3f9;
|
||||
color: #4a6080;
|
||||
font-weight: 500;
|
||||
width: 110px;
|
||||
}
|
||||
.cell-val { color: #1a1a2e; font-weight: 600; }
|
||||
.cell-coilid { font-size: 13px; color: #1a365d; letter-spacing: 0.5px; }
|
||||
.cell-ok { color: #2e7d32; font-weight: 700; text-align: center; }
|
||||
.cell-ng { color: #c62828; font-weight: 700; text-align: center; }
|
||||
.quality-table td { text-align: center; }
|
||||
|
||||
/* 图表 */
|
||||
.rpt-chart-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #2c5282;
|
||||
margin: 14px 0 4px;
|
||||
padding-left: 6px;
|
||||
border-left: 3px solid #2c5282;
|
||||
}
|
||||
.rpt-chart-subtitle {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
color: #888;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.rpt-chart {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
border: 1px solid #e8edf2;
|
||||
border-radius: 3px;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
/* 无数据 */
|
||||
.rpt-no-data {
|
||||
padding: 28px 0;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.rpt-footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 覆盖 dialog padding */
|
||||
::v-deep .el-dialog__body { padding: 12px 20px 20px; }
|
||||
::v-deep .el-button--primary {
|
||||
background: #2c5282 !important; border-color: #2c5282 !important;
|
||||
}
|
||||
::v-deep .el-button--primary:hover { background: #1a365d !important; border-color: #1a365d !important; }
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="acid-view">
|
||||
<div class="filter-bar">
|
||||
<el-input
|
||||
@@ -38,6 +39,16 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="exit_thick" label="出口厚" />
|
||||
<el-table-column prop="entry_weight" label="重量(t)" />
|
||||
<el-table-column label="" width="70" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
style="color:#2c5282;font-weight:600"
|
||||
@click.stop="openQualityReport(row)"
|
||||
>质保书</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-bar">
|
||||
@@ -60,6 +71,16 @@
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 质保书按钮(右侧详情区顶部) -->
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:8px">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-document"
|
||||
style="color:#2c5282;border-color:#2c5282"
|
||||
@click="openQualityReport(selectedPlan)"
|
||||
>导出质保书</el-button>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div v-for="f in planFields" :key="f.key" class="detail-cell">
|
||||
<span class="cell-label">{{ f.label }}</span>
|
||||
@@ -84,6 +105,10 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 质保书 -->
|
||||
<quality-report-dialog ref="qualityReport" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -93,6 +118,7 @@ import {
|
||||
getTimingPlanCount,
|
||||
getPlanWithSeg
|
||||
} from '@/api/l2/timing'
|
||||
import QualityReportDialog from './QualityReportDialog.vue'
|
||||
|
||||
const PLAN_FIELDS = [
|
||||
{ key: 'status', label: '状态' },
|
||||
@@ -136,6 +162,7 @@ function baseOption(title, xData, series, yName) {
|
||||
|
||||
export default {
|
||||
name: 'TimingAcidPage',
|
||||
components: { QualityReportDialog },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
@@ -271,6 +298,21 @@ export default {
|
||||
this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize() })
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
openQualityReport(row) {
|
||||
const dialog = this.$refs.qualityReport
|
||||
// Pass currently loaded series if the row matches the selectedPlan
|
||||
const sameCoil = this.selectedPlan &&
|
||||
(row.encoilid || row.coilid) === (this.selectedPlan.encoilid || this.selectedPlan.coilid)
|
||||
if (sameCoil && this.perfSeries) {
|
||||
dialog.plan = { ...this.selectedPlan }
|
||||
dialog.series = this.perfSeries
|
||||
} else {
|
||||
dialog.plan = row
|
||||
dialog.series = null
|
||||
}
|
||||
dialog.visible = true
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.queryForm.coilId = ''
|
||||
this.selectedPlan = null
|
||||
|
||||
Reference in New Issue
Block a user