新增app和跟踪页面,现已经调通
This commit is contained in:
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