2026-05-27 16:38:40 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-body" style="padding:10px 14px;">
|
|
|
|
|
|
<div class="flex-row" style="flex-wrap:wrap;gap:12px;">
|
|
|
|
|
|
<div class="flex-row">
|
|
|
|
|
|
<span class="kv-label">状态</span>
|
|
|
|
|
|
<select v-model="query.status" class="kv-input" style="width:110px;">
|
|
|
|
|
|
<option value="">全部</option>
|
|
|
|
|
|
<option v-for="s in statusOptions" :key="s.value" :value="s.value">{{ s.label }}</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="flex-row">
|
|
|
|
|
|
<span class="kv-label">冷卷号</span>
|
|
|
|
|
|
<input v-model="query.cold_coil_no" class="kv-input" style="width:140px;" @keyup.enter="fetchData" />
|
|
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<div class="flex-row">
|
|
|
|
|
|
<span class="kv-label">日期</span>
|
|
|
|
|
|
<input v-model="query.start_date" type="date" class="kv-input" style="width:130px;" />
|
|
|
|
|
|
<span class="kv-label">~</span>
|
|
|
|
|
|
<input v-model="query.end_date" type="date" class="kv-input" style="width:130px;" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex-row">
|
|
|
|
|
|
<button class="btn btn-primary" @click="fetchData">查询</button>
|
|
|
|
|
|
<button class="btn btn-outline" @click="openDialog()">+ 新增计划</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
生产计划
|
|
|
|
|
|
<span class="ch-badge">共 {{ total }} 条</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="table-scroll" v-loading="loading">
|
|
|
|
|
|
<table class="data-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<th style="width:48px;">序号</th>
|
|
|
|
|
|
<th>冷卷号</th>
|
|
|
|
|
|
<th>热卷号</th>
|
|
|
|
|
|
<th>钢种</th>
|
|
|
|
|
|
<th>来料厚度</th>
|
|
|
|
|
|
<th>产品厚度</th>
|
|
|
|
|
|
<th>偏差上限</th>
|
|
|
|
|
|
<th>偏差下限</th>
|
|
|
|
|
|
<th>来料宽度</th>
|
|
|
|
|
|
<th>产品宽度</th>
|
|
|
|
|
|
<th>包装要求</th>
|
|
|
|
|
|
<th>卷径</th>
|
|
|
|
|
|
<th>分卷数</th>
|
|
|
|
|
|
<th>下工序</th>
|
|
|
|
|
|
<th>计划时间</th>
|
|
|
|
|
|
<th>状态</th>
|
|
|
|
|
|
<th>操作</th>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<tr v-for="(row, idx) in tableData" :key="row.id"
|
|
|
|
|
|
:class="{ 'row-selected': selectedRow && selectedRow.id === row.id }"
|
|
|
|
|
|
@click="selectRow(row)" style="cursor:pointer;">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<td class="td-num">{{ idx + 1 }}</td>
|
|
|
|
|
|
<td class="td-num">{{ row.cold_coil_no || row.plan_no || '—' }}</td>
|
|
|
|
|
|
<td class="td-num">{{ row.hot_coil_no || '—' }}</td>
|
|
|
|
|
|
<td>{{ row.steel_grade || '—' }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.incoming_thickness) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.product_thickness) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.deviation_upper, 3) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.deviation_lower, 3) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.incoming_width, 0) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.product_width, 0) }}</td>
|
|
|
|
|
|
<td>{{ row.packaging_req || '—' }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmtNum(row.coil_diameter, 0) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ row.split_count != null ? row.split_count : 1 }}</td>
|
|
|
|
|
|
<td class="td-num">{{ row.next_process != null ? row.next_process : '—' }}</td>
|
|
|
|
|
|
<td class="td-muted">{{ fmtTime(row.plan_date) }}</td>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<td><span :class="['badge', statusBadge(row.status)]">{{ statusLabel(row.status) }}</span></td>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<td @click.stop>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<span class="action-link" @click="openDialog(row)">编辑</span>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<span v-if="row.status === 'ready' || row.status === 'online'"
|
|
|
|
|
|
class="action-link" style="color:var(--accent-green)" @click="moveToProducing(row)">移动</span>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr v-if="!tableData.length && !loading">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<td colspan="17" class="td-muted" style="text-align:center;padding:24px;">暂无数据</td>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<!-- 计划详细 -->
|
|
|
|
|
|
<div class="card" v-if="selectedRow">
|
|
|
|
|
|
<div class="card-header">计划详细 — {{ selectedRow.cold_coil_no || selectedRow.plan_no }}</div>
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">钢卷号</span><span class="dt-v">{{ selectedRow.cold_coil_no || '—' }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">来料厚度</span><span class="dt-v">{{ fmtNum(selectedRow.incoming_thickness) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">来料宽度</span><span class="dt-v">{{ fmtNum(selectedRow.incoming_width, 0) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">1#分卷</span><span class="dt-v">{{ fmtNum(splitW(0), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">钢种</span><span class="dt-v">{{ selectedRow.steel_grade || '—' }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">产品厚度</span><span class="dt-v">{{ fmtNum(selectedRow.product_thickness) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">产品宽度</span><span class="dt-v">{{ fmtNum(selectedRow.product_width, 0) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">2#分卷</span><span class="dt-v">{{ fmtNum(splitW(1), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">轧制模式</span><span class="dt-v">{{ selectedRow.rolling_mode || '—' }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">偏差上限</span><span class="dt-v">{{ fmtNum(selectedRow.deviation_upper, 3) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">分卷数</span><span class="dt-v">{{ selectedRow.split_count != null ? selectedRow.split_count : 1 }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">3#分卷</span><span class="dt-v">{{ fmtNum(splitW(2), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">状态</span><span class="dt-v"><span :class="['badge', statusBadge(selectedRow.status)]">{{ statusLabel(selectedRow.status) }}</span></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">偏差下限</span><span class="dt-v">{{ fmtNum(selectedRow.deviation_lower, 3) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">下工序</span><span class="dt-v">{{ selectedRow.next_process || '—' }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">4#分卷</span><span class="dt-v">{{ fmtNum(splitW(3), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">下达时间</span><span class="dt-v">{{ fmtTime(selectedRow.plan_date) }}</span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">来料重量</span><span class="dt-v">{{ fmtNum(selectedRow.incoming_weight, 2) }} <i>[t]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">来料外径</span><span class="dt-v">{{ fmtNum(selectedRow.incoming_od, 0) }} <i>[mm]</i></span></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">5#分卷</span><span class="dt-v">{{ fmtNum(splitW(4), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dt-row"></div>
|
|
|
|
|
|
<div class="dt-row"></div>
|
|
|
|
|
|
<div class="dt-row"></div>
|
|
|
|
|
|
<div class="dt-row"><span class="dt-k">6#分卷</span><span class="dt-v">{{ fmtNum(splitW(5), 3) }} <i>[t]</i></span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<div v-if="dialogVisible" class="modal-mask" @click.self="dialogVisible=false">
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<div class="modal-box" style="width:880px;">
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<div class="modal-header">
|
|
|
|
|
|
{{ editRow ? '编辑计划' : '新增计划' }}
|
|
|
|
|
|
<span class="modal-close" @click="dialogVisible=false">✕</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-body">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="grid-3" style="gap:12px;">
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">计划号 *</div>
|
|
|
|
|
|
<input v-model="form.plan_no" class="kv-input" :disabled="!!editRow" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">冷卷号</div>
|
|
|
|
|
|
<input v-model="form.cold_coil_no" class="kv-input" />
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">热卷号</div>
|
|
|
|
|
|
<input v-model="form.hot_coil_no" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">钢种</div>
|
|
|
|
|
|
<input v-model="form.steel_grade" class="kv-input" placeholder="QTGLG-2019" />
|
|
|
|
|
|
</div>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">轧制模式</div>
|
|
|
|
|
|
<select v-model="form.rolling_mode" class="kv-input">
|
|
|
|
|
|
<option value="">—</option>
|
|
|
|
|
|
<option value="冷轧">冷轧</option>
|
|
|
|
|
|
<option value="热轧">热轧</option>
|
|
|
|
|
|
<option value="温轧">温轧</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">来料厚度 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.incoming_thickness" type="number" step="0.01" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">产品厚度 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.product_thickness" type="number" step="0.01" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">偏差上限</div>
|
|
|
|
|
|
<input v-model.number="form.deviation_upper" type="number" step="0.001" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">偏差下限</div>
|
|
|
|
|
|
<input v-model.number="form.deviation_lower" type="number" step="0.001" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">来料宽度 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.incoming_width" type="number" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">产品宽度 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.product_width" type="number" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">包装要求</div>
|
|
|
|
|
|
<select v-model="form.packaging_req" class="kv-input">
|
|
|
|
|
|
<option value="">—</option>
|
|
|
|
|
|
<option value="裸包">裸包</option>
|
|
|
|
|
|
<option value="筒包">筒包</option>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">卷径 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.coil_diameter" type="number" step="1" class="kv-input" />
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">来料外径 (mm)</div>
|
|
|
|
|
|
<input v-model.number="form.incoming_od" type="number" step="1" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<div class="kv-label">来料重量 (t)</div>
|
|
|
|
|
|
<input v-model.number="form.incoming_weight" type="number" step="0.001" class="kv-input" />
|
|
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">分卷数</div>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<input v-model.number="form.split_count" type="number" min="1" max="6" class="kv-input" @change="syncSplitWeights" />
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">下工序</div>
|
|
|
|
|
|
<input v-model="form.next_process" class="kv-input" />
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">计划时间</div>
|
|
|
|
|
|
<input v-model="form.plan_date" type="datetime-local" class="kv-input" />
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="kv-label">状态</div>
|
|
|
|
|
|
<select v-model="form.status" class="kv-input">
|
|
|
|
|
|
<option v-for="s in statusOptions" :key="s.value" :value="s.value">{{ s.label }}</option>
|
|
|
|
|
|
</select>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
<div style="margin-top:12px;border-top:1px dashed var(--border);padding-top:10px;">
|
|
|
|
|
|
<div class="kv-label" style="margin-bottom:6px;">分卷重量 (t)</div>
|
|
|
|
|
|
<div class="grid-6" style="gap:8px;">
|
|
|
|
|
|
<div v-for="i in 6" :key="i" class="form-field">
|
|
|
|
|
|
<div class="kv-label" style="font-size:11px;">{{ i }}#分卷</div>
|
|
|
|
|
|
<input v-model.number="form.split_weights[i-1]" type="number" step="0.001"
|
|
|
|
|
|
:disabled="i > (form.split_count || 1)" class="kv-input" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
|
<button class="btn btn-outline" @click="dialogVisible=false">取消</button>
|
|
|
|
|
|
<button class="btn btn-primary" :disabled="saving" @click="save">{{ saving ? '保存中...' : '保存' }}</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
import { getPlans, createPlan, updatePlan, confirmPlan as apiConfirm, startProducing, getLastPlanTemplate } from '@/api'
|
2026-05-27 16:38:40 +08:00
|
|
|
|
|
|
|
|
|
|
const STATUS_MAP = {
|
2026-06-21 23:42:22 +08:00
|
|
|
|
ready: { label: '准备好', badge: 'badge-gray' },
|
|
|
|
|
|
online: { label: '在线', badge: 'badge-green' },
|
|
|
|
|
|
producing: { label: '生产中', badge: 'badge-yellow' },
|
|
|
|
|
|
produced: { label: '生产完成', badge: 'badge-blue' },
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-21 23:42:22 +08:00
|
|
|
|
const TEMPLATE_KEYS = [
|
|
|
|
|
|
'steel_grade','incoming_thickness','product_thickness','deviation_upper','deviation_lower',
|
|
|
|
|
|
'incoming_width','product_width','packaging_req','trim_req','rolling_mode',
|
|
|
|
|
|
'coil_diameter','split_count','next_process','incoming_weight','incoming_od','split_weights',
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-05-27 16:38:40 +08:00
|
|
|
|
export default {
|
|
|
|
|
|
name: 'Plan',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
loading: false, saving: false,
|
|
|
|
|
|
tableData: [], total: 0,
|
2026-06-20 18:19:06 +08:00
|
|
|
|
query: { page: 1, page_size: 50, status: '', cold_coil_no: '', start_date: '', end_date: '' },
|
2026-05-27 16:38:40 +08:00
|
|
|
|
statusOptions: Object.entries(STATUS_MAP).map(([value, { label }]) => ({ value, label })),
|
2026-06-21 23:42:22 +08:00
|
|
|
|
dialogVisible: false, editRow: null,
|
|
|
|
|
|
form: this.emptyForm(),
|
|
|
|
|
|
selectedRow: null,
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() { this.fetchData() },
|
|
|
|
|
|
methods: {
|
2026-06-21 23:42:22 +08:00
|
|
|
|
emptyForm() {
|
|
|
|
|
|
return { plan_no: '', split_count: 1, status: 'online', split_weights: [null,null,null,null,null,null] }
|
|
|
|
|
|
},
|
2026-05-27 16:38:40 +08:00
|
|
|
|
async fetchData() {
|
|
|
|
|
|
this.loading = true
|
2026-06-20 18:19:06 +08:00
|
|
|
|
const params = { page: this.query.page, page_size: this.query.page_size }
|
|
|
|
|
|
if (this.query.status) params.status = this.query.status
|
|
|
|
|
|
if (this.query.start_date) params.start_date = this.query.start_date + 'T00:00:00'
|
|
|
|
|
|
if (this.query.end_date) params.end_date = this.query.end_date + 'T23:59:59'
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPlans(params)
|
|
|
|
|
|
let items = res.data.items
|
|
|
|
|
|
if (this.query.cold_coil_no) {
|
|
|
|
|
|
items = items.filter(x => (x.cold_coil_no || '').includes(this.query.cold_coil_no))
|
|
|
|
|
|
}
|
|
|
|
|
|
this.tableData = items
|
|
|
|
|
|
this.total = res.data.total
|
2026-06-21 23:42:22 +08:00
|
|
|
|
if (this.selectedRow) {
|
|
|
|
|
|
const fresh = items.find(x => x.id === this.selectedRow.id)
|
|
|
|
|
|
if (fresh) this.selectedRow = fresh
|
|
|
|
|
|
}
|
2026-06-20 18:19:06 +08:00
|
|
|
|
} finally { this.loading = false }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
fmtTime(t) { return t ? t.slice(0, 16).replace('T', ' ') : '—' },
|
2026-06-21 23:42:22 +08:00
|
|
|
|
fmtNum(v, n = 2) { return v != null && v !== '' ? Number(v).toFixed(n) : '—' },
|
2026-06-20 18:19:06 +08:00
|
|
|
|
statusLabel(s) { return STATUS_MAP[s]?.label || s || '—' },
|
2026-05-27 16:38:40 +08:00
|
|
|
|
statusBadge(s) { return STATUS_MAP[s]?.badge || 'badge-gray' },
|
2026-06-21 23:42:22 +08:00
|
|
|
|
selectRow(row) { this.selectedRow = row },
|
|
|
|
|
|
splitW(i) {
|
|
|
|
|
|
const arr = this.selectedRow && this.selectedRow.split_weights
|
|
|
|
|
|
return Array.isArray(arr) ? arr[i] : null
|
|
|
|
|
|
},
|
|
|
|
|
|
syncSplitWeights() {
|
|
|
|
|
|
const n = Math.max(1, Math.min(6, Number(this.form.split_count) || 1))
|
|
|
|
|
|
const arr = (this.form.split_weights || []).slice(0, 6)
|
|
|
|
|
|
while (arr.length < 6) arr.push(null)
|
|
|
|
|
|
for (let i = n; i < 6; i++) arr[i] = null
|
|
|
|
|
|
this.$set(this.form, 'split_weights', arr)
|
|
|
|
|
|
},
|
|
|
|
|
|
async openDialog(row = null) {
|
2026-06-20 18:19:06 +08:00
|
|
|
|
this.editRow = row
|
|
|
|
|
|
if (row) {
|
|
|
|
|
|
const r = { ...row }
|
|
|
|
|
|
if (r.plan_date) r.plan_date = r.plan_date.slice(0, 16)
|
2026-06-21 23:42:22 +08:00
|
|
|
|
if (!Array.isArray(r.split_weights)) r.split_weights = [null,null,null,null,null,null]
|
|
|
|
|
|
else { while (r.split_weights.length < 6) r.split_weights.push(null) }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
this.form = r
|
|
|
|
|
|
} else {
|
2026-06-21 23:42:22 +08:00
|
|
|
|
this.form = this.emptyForm()
|
|
|
|
|
|
this.form.plan_date = this.nowDT()
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getLastPlanTemplate()
|
|
|
|
|
|
const t = res.data || {}
|
|
|
|
|
|
for (const k of TEMPLATE_KEYS) {
|
|
|
|
|
|
if (t[k] != null) this.$set(this.form, k, t[k])
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(this.form.split_weights)) {
|
|
|
|
|
|
this.form.split_weights = [null,null,null,null,null,null]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
while (this.form.split_weights.length < 6) this.form.split_weights.push(null)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) { /* 无历史可回填,忽略 */ }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
this.dialogVisible = true
|
|
|
|
|
|
},
|
|
|
|
|
|
nowDT() {
|
|
|
|
|
|
const d = new Date(); const p = n => String(n).padStart(2, '0')
|
|
|
|
|
|
return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`
|
2026-05-27 16:38:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
async confirmPlan(row) {
|
2026-06-20 18:19:06 +08:00
|
|
|
|
if (!confirm(`将计划 ${row.plan_no} 上线?`)) return
|
2026-05-27 16:38:40 +08:00
|
|
|
|
await apiConfirm(row.id)
|
2026-06-20 18:19:06 +08:00
|
|
|
|
this.$message.success('已上线')
|
2026-05-27 16:38:40 +08:00
|
|
|
|
this.fetchData()
|
|
|
|
|
|
},
|
2026-06-21 23:42:22 +08:00
|
|
|
|
async moveToProducing(row) {
|
|
|
|
|
|
if (!confirm(`将计划 ${row.cold_coil_no || row.plan_no} 移动到入口并开始生产?`)) return
|
|
|
|
|
|
await startProducing(row.id)
|
|
|
|
|
|
this.$message.success('已开始生产')
|
|
|
|
|
|
this.fetchData()
|
|
|
|
|
|
},
|
2026-05-27 16:38:40 +08:00
|
|
|
|
async save() {
|
|
|
|
|
|
if (!this.form.plan_no) { this.$message.error('计划号不能为空'); return }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
if (!this.form.plan_date) { this.$message.error('计划时间不能为空'); return }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
this.saving = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const d = { ...this.form }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
if (d.plan_date && !d.plan_date.includes(':')) d.plan_date += 'T00:00:00'
|
|
|
|
|
|
else if (d.plan_date && d.plan_date.length === 16) d.plan_date += ':00'
|
2026-06-21 23:42:22 +08:00
|
|
|
|
// 修剪 split_weights 到 split_count 长度
|
|
|
|
|
|
const n = Math.max(1, Math.min(6, Number(d.split_count) || 1))
|
|
|
|
|
|
if (Array.isArray(d.split_weights)) {
|
|
|
|
|
|
d.split_weights = d.split_weights.slice(0, n).map(v => v === '' ? null : v)
|
|
|
|
|
|
}
|
2026-05-27 16:38:40 +08:00
|
|
|
|
if (this.editRow) await updatePlan(this.editRow.id, d)
|
|
|
|
|
|
else await createPlan(d)
|
|
|
|
|
|
this.$message.success('保存成功')
|
|
|
|
|
|
this.dialogVisible = false; this.fetchData()
|
|
|
|
|
|
} finally { this.saving = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
@import '@/assets/styles/variables';
|
|
|
|
|
|
.action-link { color: $sms-highlight; cursor: pointer; font-size: 12px; margin-right: 12px; &:hover { text-decoration: underline; } }
|
2026-06-21 23:42:22 +08:00
|
|
|
|
.row-selected { background: rgba(29, 142, 255, 0.12) !important; }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
.form-field { display: flex; flex-direction: column; gap: 5px; }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; }
|
2026-06-21 23:42:22 +08:00
|
|
|
|
.grid-6 { display: grid; grid-template-columns: repeat(6, 1fr); }
|
|
|
|
|
|
.detail-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
|
gap: 8px 24px;
|
|
|
|
|
|
padding: 14px 18px;
|
|
|
|
|
|
border: 1px solid #c43a3a;
|
|
|
|
|
|
margin: 0 6px 8px;
|
|
|
|
|
|
background: rgba(196, 58, 58, 0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
.dt-row { display: flex; align-items: center; gap: 10px; font-size: 12px; min-height: 22px; }
|
|
|
|
|
|
.dt-k { color: $text-muted; min-width: 64px; }
|
|
|
|
|
|
.dt-v { color: $text-primary; flex: 1; i { color: $text-muted; font-style: normal; font-size: 11px; margin-left: 2px; } }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; z-index: 9999; }
|
|
|
|
|
|
.modal-box { background: $bg-card; border: 1px solid $border; border-radius: 6px; max-width: 95vw; max-height: 90vh; display: flex; flex-direction: column; }
|
|
|
|
|
|
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: $bg-panel; border-bottom: 1px solid $border; font-size: 13px; font-weight: 600; color: $sms-highlight; .modal-close { cursor: pointer; color: $text-muted; &:hover { color: $text-primary; } } }
|
|
|
|
|
|
.modal-body { padding: 16px; overflow-y: auto; }
|
|
|
|
|
|
.modal-footer { padding: 10px 16px; background: $bg-panel; border-top: 1px solid $border; display: flex; justify-content: flex-end; gap: 10px; }
|
|
|
|
|
|
</style>
|