feat(linkage): 鞍座改为预备生产位 + 在线改人工触发 + 计划删除
- 上卷鞍座=预备生产位(不生产);投入生产后离开鞍座、进入生产中并转入物料跟踪,鞍座随即空出
- 在线状态改为人工触发:移动到入口端才变在线,引擎不再自动上线(ensure_online 置空)
- 单卷在产:投入生产时若已有在产卷则拒绝
- 物料跟踪显示在产卷实时进度(客户端外推带头长度/进度条)
- 入口跟踪鞍座卡片改为预备生产展示(去掉进度)
- 计划管理新增删除按钮 + DELETE /plan/{id}(生产中不可删)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,18 @@ async def update_plan(
|
|||||||
return Response.ok(PlanOut.model_validate(plan))
|
return Response.ok(PlanOut.model_validate(plan))
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{plan_id}", response_model=Response[dict])
|
||||||
|
async def delete_plan(plan_id: int, db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)):
|
||||||
|
result = await db.execute(select(ProductionPlan).where(ProductionPlan.id == plan_id))
|
||||||
|
plan = result.scalar_one_or_none()
|
||||||
|
if not plan:
|
||||||
|
raise HTTPException(status_code=404, detail="计划不存在")
|
||||||
|
if plan.status == "producing":
|
||||||
|
raise HTTPException(status_code=400, detail="生产中的计划不可删除")
|
||||||
|
await db.delete(plan)
|
||||||
|
return Response.ok({"deleted": plan_id})
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{plan_id}/confirm", response_model=Response[PlanOut])
|
@router.patch("/{plan_id}/confirm", response_model=Response[PlanOut])
|
||||||
async def confirm_plan(plan_id: int, db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)):
|
async def confirm_plan(plan_id: int, db: AsyncSession = Depends(get_db), _ = Depends(get_current_user)):
|
||||||
result = await db.execute(select(ProductionPlan).where(ProductionPlan.id == plan_id))
|
result = await db.execute(select(ProductionPlan).where(ProductionPlan.id == plan_id))
|
||||||
@@ -171,7 +183,10 @@ async def commit_producing(plan_id: int, db: AsyncSession = Depends(get_db), _ =
|
|||||||
plan = result.scalar_one_or_none()
|
plan = result.scalar_one_or_none()
|
||||||
if not plan:
|
if not plan:
|
||||||
raise HTTPException(status_code=404, detail="计划不存在")
|
raise HTTPException(status_code=404, detail="计划不存在")
|
||||||
await line_service.commit_plan(db, plan)
|
try:
|
||||||
|
await line_service.commit_plan(db, plan)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
await db.flush()
|
await db.flush()
|
||||||
await db.refresh(plan)
|
await db.refresh(plan)
|
||||||
return Response.ok(PlanOut.model_validate(plan))
|
return Response.ok(PlanOut.model_validate(plan))
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ async def place_at_position(db: AsyncSession, plan: ProductionPlan, position: st
|
|||||||
plan.run_speed = 0
|
plan.run_speed = 0
|
||||||
plan.run_length_m = 0
|
plan.run_length_m = 0
|
||||||
plan.position = position
|
plan.position = position
|
||||||
if plan.status == "producing":
|
# 移动到入口端即变「在线」(人工触发,非自动)
|
||||||
|
if plan.status != "produced":
|
||||||
plan.status = "online"
|
plan.status = "online"
|
||||||
await ensure_online(db)
|
|
||||||
|
|
||||||
|
|
||||||
def _shift_of(dt: datetime) -> str:
|
def _shift_of(dt: datetime) -> str:
|
||||||
@@ -66,70 +66,40 @@ async def _saddle_plan(db: AsyncSession):
|
|||||||
|
|
||||||
|
|
||||||
async def ensure_online(db: AsyncSession):
|
async def ensure_online(db: AsyncSession):
|
||||||
"""单卷在产:有卷在上卷鞍座/生产中时不保留在线队首;否则把最早 ready 置为唯一在线。"""
|
"""在线为人工触发:用户手动「移动到入口」才变在线,引擎不再自动上线。"""
|
||||||
res = await db.execute(
|
return
|
||||||
select(ProductionPlan).where(ProductionPlan.status == "online", ProductionPlan.on_saddle != 1)
|
|
||||||
)
|
|
||||||
online = list(res.scalars())
|
|
||||||
|
|
||||||
# 是否已有在产/在鞍座的卷
|
|
||||||
res2 = await db.execute(
|
|
||||||
select(ProductionPlan).where(
|
|
||||||
(ProductionPlan.status == "producing") | (ProductionPlan.on_saddle == 1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
active = res2.scalars().first() is not None
|
|
||||||
|
|
||||||
if active:
|
|
||||||
# 正在生产时,不再保留「在线」队首,全部回退 ready,等当前卷完成
|
|
||||||
for p in online:
|
|
||||||
p.status = "ready"
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(online) > 1:
|
|
||||||
online.sort(key=lambda p: (p.plan_date or datetime.max, p.id))
|
|
||||||
for p in online[1:]:
|
|
||||||
p.status = "ready"
|
|
||||||
online = online[:1]
|
|
||||||
if not online:
|
|
||||||
res = await db.execute(
|
|
||||||
select(ProductionPlan)
|
|
||||||
.where(ProductionPlan.status == "ready")
|
|
||||||
.order_by(asc(ProductionPlan.plan_date), asc(ProductionPlan.id))
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
head = res.scalar_one_or_none()
|
|
||||||
if head:
|
|
||||||
head.status = "online"
|
|
||||||
|
|
||||||
|
|
||||||
async def move_to_saddle(db: AsyncSession, plan: ProductionPlan):
|
async def move_to_saddle(db: AsyncSession, plan: ProductionPlan):
|
||||||
"""把计划移动到上卷鞍座:上卷即获得速度 → 生产中。"""
|
"""把计划移动到上卷鞍座(预备生产位,尚未生产)。"""
|
||||||
occupied = await _saddle_plan(db)
|
occupied = await _saddle_plan(db)
|
||||||
if occupied and occupied.id != plan.id:
|
if occupied and occupied.id != plan.id:
|
||||||
raise ValueError("上卷鞍座已被占用,请等待当前钢卷生产完成")
|
raise ValueError("上卷鞍座已有预备卷,请先投入生产或移走")
|
||||||
now = datetime.now()
|
|
||||||
plan.on_saddle = 1
|
plan.on_saddle = 1
|
||||||
plan.position = SADDLE_NAME
|
plan.position = SADDLE_NAME
|
||||||
plan.saddle_at = now
|
plan.saddle_at = datetime.now()
|
||||||
|
plan.run_started_at = None
|
||||||
|
plan.run_speed = 0
|
||||||
|
plan.run_length_m = 0
|
||||||
if plan.status != "produced":
|
if plan.status != "produced":
|
||||||
plan.run_started_at = now
|
plan.status = "online" # 在鞍座预备(待投入生产)
|
||||||
plan.run_speed = SIM_SPEED_M_MIN
|
|
||||||
plan.run_length_m = 0
|
|
||||||
plan.status = "producing"
|
|
||||||
await ensure_online(db)
|
await ensure_online(db)
|
||||||
|
|
||||||
|
|
||||||
async def commit_plan(db: AsyncSession, plan: ProductionPlan):
|
async def commit_plan(db: AsyncSession, plan: ProductionPlan):
|
||||||
"""投入生产(兜底):鞍座计划置为生产中。"""
|
"""投入生产:鞍座预备卷进入生产中,离开鞍座进入产线(物料跟踪)。"""
|
||||||
if plan.on_saddle != 1:
|
res = await db.execute(select(ProductionPlan).where(ProductionPlan.status == "producing"))
|
||||||
await move_to_saddle(db, plan)
|
if res.scalars().first() is not None:
|
||||||
return
|
raise ValueError("产线已有钢卷在生产,请等待当前卷完成")
|
||||||
if plan.run_started_at is None:
|
now = datetime.now()
|
||||||
plan.run_started_at = datetime.now()
|
plan.on_saddle = 0 # 离开上卷鞍座
|
||||||
|
plan.position = None
|
||||||
|
plan.saddle_at = None
|
||||||
|
plan.run_started_at = now
|
||||||
plan.run_speed = SIM_SPEED_M_MIN
|
plan.run_speed = SIM_SPEED_M_MIN
|
||||||
|
plan.run_length_m = 0
|
||||||
plan.status = "producing"
|
plan.status = "producing"
|
||||||
await ensure_online(db)
|
await ensure_online(db) # 鞍座空出 → 队首自动上线
|
||||||
|
|
||||||
|
|
||||||
async def _produce(db: AsyncSession, plan: ProductionPlan):
|
async def _produce(db: AsyncSession, plan: ProductionPlan):
|
||||||
@@ -183,17 +153,17 @@ async def _produce(db: AsyncSession, plan: ProductionPlan):
|
|||||||
logger.info(f"生产完成并产生实绩: {plan.cold_coil_no or plan.plan_no}")
|
logger.info(f"生产完成并产生实绩: {plan.cold_coil_no or plan.plan_no}")
|
||||||
|
|
||||||
|
|
||||||
async def advance_saddle(db: AsyncSession):
|
async def advance_production(db: AsyncSession):
|
||||||
"""推进鞍座计划:累计带头长度到 2000m → 生产完成。"""
|
"""推进产线在产卷(已离开鞍座):累计带头长度到 2000m → 生产完成。"""
|
||||||
plan = await _saddle_plan(db)
|
res = await db.execute(select(ProductionPlan).where(ProductionPlan.status == "producing"))
|
||||||
if not plan:
|
plan = res.scalars().first()
|
||||||
|
if not plan or not plan.run_started_at:
|
||||||
return
|
return
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if plan.status == "producing" and plan.run_started_at:
|
elapsed = (now - plan.run_started_at).total_seconds()
|
||||||
elapsed = (now - plan.run_started_at).total_seconds()
|
plan.run_length_m = min(TARGET_LENGTH_M, (plan.run_speed or 0) / 60.0 * elapsed)
|
||||||
plan.run_length_m = min(TARGET_LENGTH_M, (plan.run_speed or 0) / 60.0 * elapsed)
|
if plan.run_length_m >= TARGET_LENGTH_M:
|
||||||
if plan.run_length_m >= TARGET_LENGTH_M:
|
await _produce(db, plan)
|
||||||
await _produce(db, plan)
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_line_state(db: AsyncSession) -> LineState:
|
async def _get_line_state(db: AsyncSession) -> LineState:
|
||||||
@@ -243,9 +213,8 @@ async def detect_downtime(db: AsyncSession):
|
|||||||
|
|
||||||
|
|
||||||
async def tick(db: AsyncSession):
|
async def tick(db: AsyncSession):
|
||||||
"""引擎单步:上线 + 推进鞍座 + 停机检测。"""
|
"""引擎单步:推进在产卷 + 停机检测(在线为人工触发,不自动上线)。"""
|
||||||
await ensure_online(db)
|
await advance_production(db)
|
||||||
await advance_saddle(db)
|
|
||||||
await detect_downtime(db)
|
await detect_downtime(db)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const updateProductionRecord = (id, data) => request.put(`/production/${i
|
|||||||
export const getPlans = params => request.get('/plan/', { params })
|
export const getPlans = params => request.get('/plan/', { params })
|
||||||
export const createPlan = data => request.post('/plan/', data)
|
export const createPlan = data => request.post('/plan/', data)
|
||||||
export const updatePlan = (id, data) => request.put(`/plan/${id}`, data)
|
export const updatePlan = (id, data) => request.put(`/plan/${id}`, data)
|
||||||
|
export const deletePlan = id => request.delete(`/plan/${id}`)
|
||||||
export const confirmPlan = id => request.patch(`/plan/${id}/confirm`)
|
export const confirmPlan = id => request.patch(`/plan/${id}/confirm`)
|
||||||
export const startProducing = id => request.patch(`/plan/${id}/start`) // 移动到上卷鞍座(兼容)
|
export const startProducing = id => request.patch(`/plan/${id}/start`) // 移动到上卷鞍座(兼容)
|
||||||
export const moveToSaddle = id => request.patch(`/plan/${id}/start`)
|
export const moveToSaddle = id => request.patch(`/plan/${id}/start`)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
入口跟踪
|
入口跟踪
|
||||||
<span class="ch-badge">{{ saddle ? '生产:' + statusLabel(saddle.status) : '鞍座空闲' }}</span>
|
<span class="ch-badge">{{ saddle ? '上卷鞍座:预备生产' : '上卷鞍座:空闲' }}</span>
|
||||||
<span style="margin-left:auto;display:flex;gap:8px;align-items:center;">
|
<span style="margin-left:auto;display:flex;gap:8px;align-items:center;">
|
||||||
<button v-if="saddle && saddle.status !== 'producing'" class="btn btn-primary" @click="commit(saddle)">投入生产</button>
|
<button v-if="saddle" class="btn btn-primary" @click="commit(saddle)">投入生产</button>
|
||||||
<button class="btn btn-outline" @click="refreshAll">刷新</button>
|
<button class="btn btn-outline" @click="refreshAll">刷新</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,28 +38,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下方:单个上卷鞍座(唯一进入生产的工位) -->
|
<!-- 下方:单个上卷鞍座(预备生产位;投入生产后离开鞍座进入物料跟踪) -->
|
||||||
<div :class="['pos-cell', 'saddle-cell', { filled: !!saddle }]">
|
<div :class="['pos-cell', 'saddle-cell', { filled: !!saddle }]">
|
||||||
<div class="pos-title saddle-title">上卷鞍座<span class="st-tag">生产工位</span></div>
|
<div class="pos-title saddle-title">上卷鞍座<span class="st-tag">预备生产位</span></div>
|
||||||
<div v-if="saddle" class="saddle-body">
|
<div v-if="saddle" class="saddle-body">
|
||||||
<div class="sb-info">
|
<div class="sb-info">
|
||||||
<span><i>冷卷号</i>{{ saddle.cold_coil_no || saddle.plan_no }}</span>
|
<span><i>冷卷号</i>{{ saddle.cold_coil_no || saddle.plan_no }}</span>
|
||||||
|
<span><i>热卷号</i>{{ saddle.hot_coil_no || '—' }}</span>
|
||||||
<span><i>钢种</i>{{ saddle.steel_grade || '—' }}</span>
|
<span><i>钢种</i>{{ saddle.steel_grade || '—' }}</span>
|
||||||
<span><i>规格</i>{{ fmt(saddle.product_thickness, 2) }}×{{ fmt(saddle.product_width, 0) }}</span>
|
<span><i>规格</i>{{ fmt(saddle.product_thickness, 2) }}×{{ fmt(saddle.product_width, 0) }}</span>
|
||||||
<span><i>来料重[t]</i>{{ fmt(saddle.incoming_weight, 3) }}</span>
|
<span><i>来料重[t]</i>{{ fmt(saddle.incoming_weight, 3) }}</span>
|
||||||
<span><i>轧制模式</i>{{ saddle.rolling_mode || '—' }}</span>
|
<span><i>轧制模式</i>{{ saddle.rolling_mode || '—' }}</span>
|
||||||
<span><i>状态</i><b :class="badgeOf(saddle.status)" style="padding:0 6px;border-radius:2px;">{{ statusLabel(saddle.status) }}</b></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-run">
|
<div class="sb-stage">
|
||||||
<div class="sb-metric"><span class="m-v">{{ fmt(saddle.run_speed, 0) }}</span><span class="m-u">m/min</span></div>
|
<span class="badge badge-yellow">预备生产</span>
|
||||||
<div class="sb-metric"><span class="m-v">{{ fmt(saddle.run_length_m, 0) }}</span><span class="m-u">/ {{ TARGET }} m</span></div>
|
<span class="sb-hint">点击右上「投入生产」→ 进入生产中并转入物料跟踪</span>
|
||||||
<div class="sb-prog">
|
|
||||||
<div class="prog-bar-wrap" style="height:9px;"><div class="prog-bar-fill" :style="{ width: progPct(saddle) + '%', background: progColor(saddle) }"></div></div>
|
|
||||||
<span class="sb-pct">{{ progPct(saddle).toFixed(1) }}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="pos-empty">空闲 — 移动计划到「上卷鞍座」开始生产</div>
|
<div v-else class="pos-empty">空闲 — 把在线计划移动到「上卷鞍座」预备生产</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,8 +199,8 @@ export default {
|
|||||||
async commit(p) {
|
async commit(p) {
|
||||||
try {
|
try {
|
||||||
await commitProducing(p.id)
|
await commitProducing(p.id)
|
||||||
this.$message.success('已投入生产')
|
this.$message.success('已投入生产,转入物料跟踪')
|
||||||
this.fetchSaddle()
|
this.refreshAll()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$message.error(e?.response?.data?.detail || '操作失败')
|
this.$message.error(e?.response?.data?.detail || '操作失败')
|
||||||
}
|
}
|
||||||
@@ -252,12 +248,9 @@ export default {
|
|||||||
span { font-size: 12px; color: $text-primary; font-family: $font-mono; font-weight: 600; display: flex; gap: 6px;
|
span { font-size: 12px; color: $text-primary; font-family: $font-mono; font-weight: 600; display: flex; gap: 6px;
|
||||||
i { color: $text-muted; font-style: normal; font-family: $font-main; font-weight: 400; min-width: 56px; } }
|
i { color: $text-muted; font-style: normal; font-family: $font-main; font-weight: 400; min-width: 56px; } }
|
||||||
}
|
}
|
||||||
.sb-run { display: flex; align-items: center; gap: 16px; border-left: 1px solid $border; padding-left: 16px; }
|
.sb-stage { display: flex; flex-direction: column; align-items: center; gap: 8px; justify-content: center;
|
||||||
.sb-metric { display: flex; flex-direction: column; line-height: 1.1;
|
border-left: 1px solid $border; padding-left: 16px;
|
||||||
.m-v { font-size: 22px; font-family: $font-mono; font-weight: 700; color: $sms-teal; }
|
.sb-hint { font-size: 11px; color: $text-muted; text-align: center; } }
|
||||||
.m-u { font-size: 10px; color: $text-muted; } }
|
|
||||||
.sb-prog { flex: 1; display: flex; flex-direction: column; gap: 5px;
|
|
||||||
.sb-pct { font-size: 11px; font-family: $font-mono; font-weight: 700; color: $sms-teal; text-align: right; } }
|
|
||||||
|
|
||||||
.action-link { color: $accent-green; cursor: pointer; font-size: 12px; &:hover { text-decoration: underline; } }
|
.action-link { color: $accent-green; cursor: pointer; font-size: 12px; &:hover { text-decoration: underline; } }
|
||||||
|
|
||||||
|
|||||||
@@ -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.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">{{ 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">{{ 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>
|
</div>
|
||||||
<table class="data-table compact" v-if="onlinePlans.length">
|
<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>
|
<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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
l1Online: false,
|
l1Online: false,
|
||||||
|
now: Date.now(),
|
||||||
|
prodBase: null, // { id, len, at, speed } 用于客户端平滑外推生产进度
|
||||||
current: { coil_no: '26053552', speed: 95.0 },
|
current: { coil_no: '26053552', speed: 95.0 },
|
||||||
prev_coil_no: '26053551',
|
prev_coil_no: '26053551',
|
||||||
weld: { position: 0.08 },
|
weld: { position: 0.08 },
|
||||||
@@ -428,8 +435,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
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 },
|
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() {
|
equipments() {
|
||||||
const n = EQUIPMENTS.length
|
const n = EQUIPMENTS.length
|
||||||
const xStart = 50, xEnd = 1850
|
const xStart = 50, xEnd = 1850
|
||||||
@@ -540,10 +557,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const res = await getPlans({ page: 1, page_size: 50 })
|
const res = await getPlans({ page: 1, page_size: 50 })
|
||||||
this.plans = res.data.items || []
|
this.plans = res.data.items || []
|
||||||
// 把生产中的卷号同步到产线显示
|
// 把生产中的卷号同步到产线显示 + 记录进度外推基准
|
||||||
if (this.producingPlan && this.producingPlan.cold_coil_no) {
|
const pp = this.producingPlan
|
||||||
this.current.coil_no = this.producingPlan.cold_coil_no
|
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 */ }
|
} catch (e) { /* ignore */ }
|
||||||
},
|
},
|
||||||
async movePlan(p) {
|
async movePlan(p) {
|
||||||
@@ -623,6 +640,7 @@ export default {
|
|||||||
return { coil, gap, speed, aux }
|
return { coil, gap, speed, aux }
|
||||||
},
|
},
|
||||||
tick() {
|
tick() {
|
||||||
|
this.now = Date.now()
|
||||||
this.weld.position = (this.weld.position + 0.012) % 1
|
this.weld.position = (this.weld.position + 0.012) % 1
|
||||||
// 新一卷开始时滚动卷号
|
// 新一卷开始时滚动卷号
|
||||||
if (this.weld.position < 0.012) {
|
if (this.weld.position < 0.012) {
|
||||||
|
|||||||
@@ -78,8 +78,10 @@
|
|||||||
<td><span :class="['badge', statusBadge(row.status)]">{{ statusLabel(row.status) }}</span></td>
|
<td><span :class="['badge', statusBadge(row.status)]">{{ statusLabel(row.status) }}</span></td>
|
||||||
<td @click.stop>
|
<td @click.stop>
|
||||||
<span class="action-link" @click="openDialog(row)">编辑</span>
|
<span class="action-link" @click="openDialog(row)">编辑</span>
|
||||||
<span v-if="row.status === 'online' || row.status === 'ready'"
|
<span v-if="row.status === 'ready' || row.status === 'online'"
|
||||||
class="action-link" style="color:var(--accent-green)" @click="openMove(row)">移动</span>
|
class="action-link" style="color:var(--accent-green)" @click="openMove(row)">移动</span>
|
||||||
|
<span v-if="row.status !== 'producing'"
|
||||||
|
class="action-link" style="color:var(--accent-red)" @click="removeRow(row)">删除</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="!tableData.length && !loading">
|
<tr v-if="!tableData.length && !loading">
|
||||||
@@ -268,7 +270,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getPlans, createPlan, updatePlan, confirmPlan as apiConfirm, movePlan, getLastPlanTemplate } from '@/api'
|
import { getPlans, createPlan, updatePlan, deletePlan, confirmPlan as apiConfirm, movePlan, getLastPlanTemplate } from '@/api'
|
||||||
|
|
||||||
const SADDLE = '上卷鞍座'
|
const SADDLE = '上卷鞍座'
|
||||||
const POSITIONS = [
|
const POSITIONS = [
|
||||||
@@ -381,6 +383,17 @@ export default {
|
|||||||
this.$message.success('已上线')
|
this.$message.success('已上线')
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
|
async removeRow(row) {
|
||||||
|
if (!confirm(`确认删除计划 ${row.cold_coil_no || row.plan_no}?`)) return
|
||||||
|
try {
|
||||||
|
await deletePlan(row.id)
|
||||||
|
this.$message.success('已删除')
|
||||||
|
if (this.selectedRow && this.selectedRow.id === row.id) this.selectedRow = null
|
||||||
|
this.fetchData()
|
||||||
|
} catch (e) {
|
||||||
|
this.$message.error(e?.response?.data?.detail || '删除失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
openMove(row) {
|
openMove(row) {
|
||||||
this.moveDialog = { visible: true, plan: row, target: row.position || '' }
|
this.moveDialog = { visible: true, plan: row, target: row.position || '' }
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user