Files
klp-oa/klp-ui/src/views/micro/pages/acid/components/TrackingView.vue

437 lines
16 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="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>