Files
klp-oa/klp-ui/src/views/timing/acid/index.vue
2026-05-13 13:56:19 +08:00

396 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="acid-view">
<div class="filter-bar">
<el-input
v-model="queryForm.coilId"
placeholder="热卷号 / 成品卷号"
clearable
size="small"
style="width: 220px"
@keyup.enter.native="handleSearch"
/>
<el-button size="small" type="primary" :loading="loading" @click="handleSearch">查询</el-button>
<el-button size="small" @click="handleReset">重置</el-button>
</div>
<el-row :gutter="12" class="main-row">
<!-- 左侧计划列表 -->
<el-col :span="9">
<div class="left-card">
<el-table
ref="planTable"
:data="planRows"
size="mini"
highlight-current-row
:height="tableHeight"
@row-click="handlePlanRowClick"
>
<el-table-column prop="hot_coilid" label="热卷号" show-overflow-tooltip />
<el-table-column prop="coilid" label="原料卷号" show-overflow-tooltip />
<el-table-column label="状态">
<template slot-scope="{ row }">
<span :class="['status-dot', statusClass(row.status)]" width="70px"/>
<span class="status-text">{{ row.status || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="厚×宽">
<template slot-scope="{ row }">{{ row.entry_thick }}×{{ row.entry_width }}</template>
</el-table-column>
<el-table-column prop="exit_thick" label="出口厚" />
<el-table-column prop="entry_weight" label="重量(t)" />
</el-table>
<div class="pagination-bar">
<el-pagination
small
layout="total, prev, pager, next"
:total="pagination.total"
:page-size="pagination.pageSize"
:current-page="pagination.page"
@current-change="handlePageChange"
/>
</div>
</div>
</el-col>
<!-- 右侧详情 + 图表 -->
<el-col :span="15">
<div v-if="!selectedPlan" class="empty-hint">
<el-empty description="选择左侧计划" :image-size="72" />
</div>
<template v-else>
<div class="detail-grid">
<div v-for="f in planFields" :key="f.key" class="detail-cell">
<span class="cell-label">{{ f.label }}</span>
<span class="cell-value">{{ selectedPlan[f.key] != null ? selectedPlan[f.key] : '—' }}</span>
</div>
</div>
<div class="perf-header">
实轧实绩
<span v-if="hasPerfData" class="perf-count">({{ perfSegCount }} )</span>
<el-tag v-if="perfLoading" size="mini" type="info" style="margin-left:8px">加载中</el-tag>
</div>
<div v-if="hasPerfData" class="charts-wrap">
<div ref="chartSpeed" class="chart-box" />
<div ref="chartMillSpeed" class="chart-box" />
<div ref="chartTension" class="chart-box" />
</div>
<el-empty v-else-if="!perfLoading" description="暂无实绩数据" :image-size="56" style="margin-top: 24px" />
</template>
</el-col>
</el-row>
</div>
</template>
<script>
import * as echarts from 'echarts'
import {
getTimingPlanList,
getTimingPlanCount,
getPlanWithSeg
} from '@/api/l2/timing'
const PLAN_FIELDS = [
{ key: 'status', label: '状态' },
{ key: 'process_code', label: '工艺编码' },
{ key: 'entry_thick', label: '入口厚度(mm)' },
{ key: 'entry_width', label: '入口宽度(mm)' },
{ key: 'entry_weight', label: '入口重量(t)' },
{ key: 'exit_thick', label: '出口厚度(mm)' },
{ key: 'exit_width', label: '出口宽度(mm)' },
{ key: 'exit_length', label: '出口长度(m)' }
]
const STATUS_CLASS = {
READY: 'status-ready',
ONLINE: 'status-online',
PRODUCING: 'status-producing',
PRODUCT: 'status-product'
}
function makeLine(name, data) {
return { name, type: 'line', smooth: true, symbol: 'none', data }
}
function baseOption(title, xData, series, yName) {
return {
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
tooltip: { trigger: 'axis' },
legend: { top: 4, right: 8, textStyle: { fontSize: 11 } },
grid: { top: 36, bottom: 28, left: 8, right: 8, containLabel: true },
xAxis: {
type: 'category',
data: xData,
name: 'pos(m)',
nameTextStyle: { fontSize: 10 },
axisLabel: { fontSize: 10 }
},
yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 } },
series
}
}
export default {
name: 'TimingAcidPage',
data() {
return {
loading: false,
perfLoading: false,
queryForm: { coilId: '' },
planRows: [],
selectedPlan: null,
perfSeries: null,
perfSegCount: 0,
planFields: PLAN_FIELDS,
pagination: { page: 1, pageSize: 50, total: 0 },
tableHeight: 'calc(100vh - 210px)'
}
},
computed: {
hasPerfData() {
return this.perfSeries && this.perfSegCount > 0
}
},
created() {
// plain instance properties — Vue 2 does NOT proxy underscore-prefixed names
this.chartInstances = []
this.resizeHandler = null
this.loadPlanCount()
this.loadPlanList()
},
beforeDestroy() {
this.disposeCharts()
},
methods: {
statusClass(status) {
return STATUS_CLASS[status] || 'status-default'
},
async loadPlanCount() {
try {
const res = await getTimingPlanCount()
this.pagination.total = res?.data?.total ?? 0
} catch (_) {}
},
async loadPlanList() {
this.loading = true
try {
const { page, pageSize } = this.pagination
const res = await getTimingPlanList(page, pageSize)
this.planRows = res?.data?.rows || []
} finally {
this.loading = false
}
},
handlePageChange(page) {
this.pagination.page = page
this.loadPlanList()
},
async handleSearch() {
if (!this.queryForm.coilId) return this.loadPlanList()
this.loading = true
try {
await this._loadFull(this.queryForm.coilId)
} finally {
this.loading = false
}
},
async handlePlanRowClick(row) {
const encoilId = row.encoilid || row.coilid
if (!encoilId) return
this.disposeCharts()
this.perfSeries = null
this.perfSegCount = 0
this.perfLoading = true
try {
await this._loadFull(encoilId)
} finally {
this.perfLoading = false
}
},
async _loadFull(encoilId) {
const res = await getPlanWithSeg(encoilId)
const plan = res?.data?.plan || res?.data?.firstRow || null
const series = res?.data?.series || null
const rows = res?.data?.rows || []
if (plan) this.selectedPlan = plan
this.perfSegCount = rows.length
this.perfSeries = series
if (series && rows.length) {
await this.$nextTick()
this.renderCharts(series)
}
},
disposeCharts() {
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler)
this.resizeHandler = null
}
if (this.chartInstances && this.chartInstances.length) {
this.chartInstances.forEach(c => { if (c) c.dispose() })
this.chartInstances = []
}
},
renderCharts(series) {
this.disposeCharts()
const pick = key => (series[key] || []).map(v => v == null ? null : Number(v).toFixed(2))
const xData = (series.startpos || []).map(v => v == null ? '' : Number(v).toFixed(1))
const c1 = echarts.init(this.$refs.chartSpeed)
c1.setOption(baseOption(
'速度趋势 (m/min)', xData,
[
makeLine('轧制速度 plspeed', pick('plspeed')),
makeLine('剪切速度 trimspeed', pick('trimspeed'))
],
'm/min'
))
const c2 = echarts.init(this.$refs.chartMillSpeed)
c2.setOption(baseOption(
'轧机出口速度 (m/min)', xData,
[makeLine('millexitspeed', pick('millexitspeed'))],
'm/min'
))
const c3 = echarts.init(this.$refs.chartTension)
c3.setOption(baseOption(
'张力趋势 (N)', xData,
[
makeLine('出口张力 pltens', pick('pltens')),
makeLine('入口张力 enltens', pick('enltens')),
makeLine('cxltens', pick('cxltens'))
],
'N'
))
this.chartInstances = [c1, c2, c3]
this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize() })
window.addEventListener('resize', this.resizeHandler)
},
handleReset() {
this.queryForm.coilId = ''
this.selectedPlan = null
this.perfSeries = null
this.perfSegCount = 0
this.disposeCharts()
this.pagination.page = 1
this.loadPlanList()
}
}
}
</script>
<style scoped>
.filter-bar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.main-row {
margin: 0 !important;
}
.left-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
.empty-hint {
display: flex;
justify-content: center;
padding-top: 80px;
}
.pagination-bar {
display: flex;
justify-content: flex-end;
padding: 6px 8px;
border-top: 1px solid #ebeef5;
background: #fafafa;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
border-top: 1px solid #ebeef5;
border-left: 1px solid #ebeef5;
margin-bottom: 16px;
}
.detail-cell {
display: flex;
flex-direction: column;
padding: 8px 10px;
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
}
.cell-label {
font-size: 11px;
color: #909399;
margin-bottom: 3px;
}
.cell-value {
font-size: 13px;
color: #303133;
font-weight: 500;
}
.perf-header {
font-size: 13px;
font-weight: 600;
color: #606266;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.perf-count {
font-weight: normal;
color: #909399;
font-size: 12px;
}
.charts-wrap {
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
max-height: calc(100vh - 310px);
}
.chart-box {
width: 100%;
height: 200px;
}
/* 状态指示点 */
.status-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
margin-right: 4px;
vertical-align: middle;
}
.status-text {
font-size: 8px;
vertical-align: middle;
}
.status-ready { background: #909399; }
.status-online { background: #67c23a; }
.status-producing { background: #e6a23c; }
.status-product { background: #409eff; }
.status-default { background: #c0c4cc; }
</style>