feat(entry-tracking): 新增入口跟踪独立页面(11×2 位置网格)

- 新增 /entry-tracking 路由与导航入口
- 11 列 × 2 行位置卡(上卷小车/称重位/地辊/上卷鞍座/倒卷小车)
- 1#地辊 绑生产中卷,2#地辊 绑在线队首
- 底部入口队列表保留「移动」按钮(计划→生产中)
This commit is contained in:
2026-06-27 14:20:10 +08:00
parent 74ad820dfb
commit def3ef24a0
3 changed files with 214 additions and 0 deletions

View File

@@ -22,6 +22,12 @@ const routes = [
component: () => import('@/views/Material.vue'),
meta: { title: '物料跟踪', icon: 'el-icon-box', requiresAuth: true }
},
{
path: 'entry-tracking',
name: 'EntryTracking',
component: () => import('@/views/EntryTracking.vue'),
meta: { title: '入口跟踪', icon: 'el-icon-position', requiresAuth: true }
},
{
path: 'production',
name: 'Production',

View File

@@ -0,0 +1,207 @@
<template>
<div class="entry-page">
<div class="card">
<div class="card-header">
入口跟踪
<span class="ch-badge">在线 {{ onlinePlans.length }} / 生产中 {{ producingPlan ? 1 : 0 }}</span>
<span style="margin-left:auto;display:flex;gap:8px;align-items:center;">
<button class="btn btn-outline" @click="fetchPlans">刷新</button>
</span>
</div>
<div class="entry-grid">
<div v-for="row in rows" :key="row.key" class="entry-row">
<div
v-for="pos in row.positions"
:key="pos.name"
:class="['pos-cell', { filled: !!pos.plan, highlight: pos.highlight }]"
>
<div class="pos-title">{{ pos.name }}</div>
<table class="pos-table">
<tbody>
<tr><td class="k">冷卷号</td><td class="v">{{ pos.plan ? (pos.plan.cold_coil_no || '—') : '' }}</td></tr>
<tr><td class="k">热卷号</td><td class="v">{{ pos.plan ? (pos.plan.hot_coil_no || '—') : '' }}</td></tr>
<tr><td class="k">钢种</td><td class="v">{{ pos.plan ? (pos.plan.steel_grade || '—') : '' }}</td></tr>
<tr><td class="k">来料厚度[mm]</td><td class="v">{{ pos.plan ? fmt(pos.plan.incoming_thickness, 2) : '' }}</td></tr>
<tr><td class="k">成品厚度[mm]</td><td class="v">{{ pos.plan ? fmt(pos.plan.product_thickness, 2) : '' }}</td></tr>
<tr><td class="k">厚差范围[mm]</td><td class="v">{{ pos.plan ? devRange(pos.plan) : '' }}</td></tr>
<tr><td class="k">来料宽度[mm]</td><td class="v">{{ pos.plan ? fmt(pos.plan.incoming_width, 0) : '' }}</td></tr>
<tr><td class="k">成品宽度[mm]</td><td class="v">{{ pos.plan ? fmt(pos.plan.product_width, 0) : '' }}</td></tr>
<tr><td class="k">来料重量[t]</td><td class="v">{{ pos.plan ? fmt(pos.plan.incoming_weight, 4) : '' }}</td></tr>
<tr><td class="k">轧制模式</td><td class="v">{{ pos.plan ? (pos.plan.rolling_mode || '—') : '' }}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
入口队列点击移动可将计划推到入口并开始生产
<span class="ch-badge">{{ onlinePlans.length }} </span>
</div>
<div class="table-scroll">
<table class="data-table compact">
<thead>
<tr>
<th>冷卷号</th><th>热卷号</th><th>钢种</th>
<th>规格(×)</th><th>来料重量</th><th>轧制模式</th><th>状态</th><th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="p in onlinePlans" :key="p.id">
<td class="td-num">{{ p.cold_coil_no || p.plan_no }}</td>
<td class="td-num">{{ p.hot_coil_no || '—' }}</td>
<td>{{ p.steel_grade || '—' }}</td>
<td class="td-num">{{ fmt(p.product_thickness, 2) }} × {{ fmt(p.product_width, 0) }}</td>
<td class="td-num">{{ fmt(p.incoming_weight, 2) }}</td>
<td>{{ p.rolling_mode || '—' }}</td>
<td><span :class="['badge', p.status === 'online' ? 'badge-green' : 'badge-gray']">{{ p.status === 'online' ? '在线' : '准备好' }}</span></td>
<td><span class="action-link" @click="moveToProducing(p)">移动</span></td>
</tr>
<tr v-if="!onlinePlans.length">
<td colspan="8" class="td-muted" style="text-align:center;padding:14px;">暂无在线/准备好的计划</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { getPlans, startProducing } from '@/api'
const ROW1 = ['1#上卷小车','1#称重位','1#地辊','13#上卷鞍座','11#上卷鞍座','9#上卷鞍座','7#上卷鞍座','1#倒卷小车','5#上卷鞍座','3#上卷鞍座','1#上卷鞍座']
const ROW2 = ['2#上卷小车','2#称重位','2#地辊','14#上卷鞍座','12#上卷鞍座','10#上卷鞍座','8#上卷鞍座','2#倒卷小车','6#上卷鞍座','4#上卷鞍座','2#上卷鞍座']
export default {
name: 'EntryTracking',
data() {
return { plans: [], timer: null }
},
computed: {
onlinePlans() { return this.plans.filter(p => p.status === 'online' || p.status === 'ready') },
producingPlan() { return this.plans.find(p => p.status === 'producing') || null },
rows() {
// 主辊位填充1#地辊 = 生产中卷; 2#地辊 = 在线队首
const onl = this.onlinePlans
const map1 = { '1#地辊': this.producingPlan }
const map2 = { '2#地辊': onl[0] || null }
const build = (names, map) => names.map(n => ({
name: n,
plan: map[n] || null,
highlight: n === '1#地辊' || n === '2#地辊',
}))
return [
{ key: 'r1', positions: build(ROW1, map1) },
{ key: 'r2', positions: build(ROW2, map2) },
]
},
},
created() {
this.fetchPlans()
this.timer = setInterval(this.fetchPlans, 5000)
},
beforeDestroy() { clearInterval(this.timer) },
methods: {
async fetchPlans() {
try {
const res = await getPlans({ page: 1, page_size: 50 })
this.plans = res.data.items || []
} catch (e) { /* ignore */ }
},
fmt(v, n = 2) { return v != null && v !== '' ? Number(v).toFixed(n) : '—' },
devRange(p) {
const u = p.deviation_upper, l = p.deviation_lower
if (u == null && l == null) return '—'
return `${l != null ? Number(l).toFixed(3) : '—'} / ${u != null ? Number(u).toFixed(3) : '—'}`
},
async moveToProducing(p) {
if (!confirm(`将计划 ${p.cold_coil_no || p.plan_no} 移动到入口并开始生产?`)) return
try {
await startProducing(p.id)
this.$message.success('已开始生产')
this.fetchPlans()
} catch (e) {
this.$message.error(e?.response?.data?.detail || '操作失败')
}
},
},
}
</script>
<style lang="scss" scoped>
@import '@/assets/styles/variables';
.entry-page { display: flex; flex-direction: column; gap: 12px; }
.entry-grid {
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
overflow-x: auto;
}
.entry-row {
display: grid;
grid-template-columns: repeat(11, minmax(118px, 1fr));
gap: 6px;
}
.pos-cell {
background: $bg-panel;
border: 1px solid $border;
border-radius: 3px;
padding: 4px 5px 6px;
min-height: 220px;
&.filled { border-color: $sms-highlight; background: rgba($sms-highlight, .04); }
&.highlight { box-shadow: 0 0 0 1px rgba($sms-highlight, .35) inset; }
}
.pos-title {
text-align: center;
font-size: 11px;
font-weight: 700;
color: $text-primary;
padding: 3px 0 5px;
border-bottom: 1px dashed $border;
margin-bottom: 4px;
letter-spacing: .3px;
}
.pos-table {
width: 100%;
border-collapse: collapse;
font-size: 10.5px;
line-height: 1.45;
td {
padding: 1px 2px;
vertical-align: top;
white-space: nowrap;
}
td.k {
color: $text-muted;
text-align: right;
width: 56%;
font-size: 10px;
}
td.v {
color: $sms-highlight;
text-align: right;
font-family: $font-mono;
font-weight: 600;
}
}
.action-link {
color: $accent-green;
cursor: pointer;
font-size: 12px;
&:hover { text-decoration: underline; }
}
</style>

View File

@@ -71,6 +71,7 @@ const IC = {
const MENU = [
{ path: '/plan', title: '计划管理', icon: IC.plan },
{ path: '/entry-tracking',title: '入口跟踪', icon: IC.material },
{ path: '/material', title: '物料跟踪', icon: IC.material },
{ path: '/production', title: '实绩管理', icon: IC.production },
{ path: '/process-model', title: '工艺段模型', icon: IC.process },