feat(linkage): 鞍座改为预备生产位 + 在线改人工触发 + 计划删除

- 上卷鞍座=预备生产位(不生产);投入生产后离开鞍座、进入生产中并转入物料跟踪,鞍座随即空出
- 在线状态改为人工触发:移动到入口端才变在线,引擎不再自动上线(ensure_online 置空)
- 单卷在产:投入生产时若已有在产卷则拒绝
- 物料跟踪显示在产卷实时进度(客户端外推带头长度/进度条)
- 入口跟踪鞍座卡片改为预备生产展示(去掉进度)
- 计划管理新增删除按钮 + DELETE /plan/{id}(生产中不可删)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 15:20:16 +08:00
parent 1a6deea4bb
commit 62c484411e
6 changed files with 103 additions and 94 deletions

View File

@@ -45,7 +45,12 @@
<span class="kv-label">冷卷号</span><span class="kv-value">{{ producingPlan.cold_coil_no || producingPlan.plan_no }}</span>
<span class="kv-label">钢种</span><span class="kv-value">{{ producingPlan.steel_grade || '—' }}</span>
<span class="kv-label">规格</span><span class="kv-value">{{ fmt(producingPlan.product_thickness) }}×{{ fmt(producingPlan.product_width, 0) }}</span>
<span class="kv-label">分卷</span><span class="kv-value">{{ producingPlan.split_count || 1 }}</span>
<span class="kv-label">线速度</span><span class="kv-value">{{ fmt(producingPlan.run_speed, 0) }}<span class="kv-unit"> m/min</span></span>
<span class="kv-label">带头</span><span class="kv-value">{{ fmt(prodLength, 0) }}<span class="kv-unit"> / 2000 m</span></span>
<div class="prog-bar-wrap" style="flex:1;min-width:120px;height:8px;">
<div class="prog-bar-fill" :style="{ width: prodPct + '%', background: prodPct >= 100 ? 'var(--accent-green)' : 'var(--sms-teal)' }"></div>
</div>
<span class="kv-value">{{ prodPct.toFixed(0) }}%</span>
</div>
<table class="data-table compact" v-if="onlinePlans.length">
<thead><tr><th>冷卷号</th><th>钢种</th><th>厚度</th><th>宽度</th><th>分卷</th><th>下达时间</th><th>操作</th></tr></thead>
@@ -380,6 +385,8 @@ export default {
data() {
return {
l1Online: false,
now: Date.now(),
prodBase: null, // { id, len, at, speed } 用于客户端平滑外推生产进度
current: { coil_no: '26053552', speed: 95.0 },
prev_coil_no: '26053551',
weld: { position: 0.08 },
@@ -428,8 +435,18 @@ export default {
}
},
computed: {
onlinePlans() { return this.plans.filter(p => p.status === 'online') },
onlinePlans() { return this.plans.filter(p => p.status === 'online' && p.on_saddle !== 1) },
producingPlan() { return this.plans.find(p => p.status === 'producing') || null },
prodLength() {
const p = this.producingPlan
if (!p) return 0
const b = this.prodBase
if (b && b.id === p.id) {
return Math.min(2000, b.len + b.speed / 60 * Math.max(0, (this.now - b.at) / 1000))
}
return p.run_length_m || 0
},
prodPct() { return this.producingPlan ? Math.max(0, Math.min(100, this.prodLength / 2000 * 100)) : 0 },
equipments() {
const n = EQUIPMENTS.length
const xStart = 50, xEnd = 1850
@@ -540,10 +557,10 @@ export default {
try {
const res = await getPlans({ page: 1, page_size: 50 })
this.plans = res.data.items || []
// 把生产中的卷号同步到产线显示
if (this.producingPlan && this.producingPlan.cold_coil_no) {
this.current.coil_no = this.producingPlan.cold_coil_no
}
// 把生产中的卷号同步到产线显示 + 记录进度外推基准
const pp = this.producingPlan
if (pp && pp.cold_coil_no) this.current.coil_no = pp.cold_coil_no
this.prodBase = pp ? { id: pp.id, len: pp.run_length_m || 0, at: Date.now(), speed: pp.run_speed || 0 } : null
} catch (e) { /* ignore */ }
},
async movePlan(p) {
@@ -623,6 +640,7 @@ export default {
return { coil, gap, speed, aux }
},
tick() {
this.now = Date.now()
this.weld.position = (this.weld.position + 0.012) % 1
// 新一卷开始时滚动卷号
if (this.weld.position < 0.012) {