Files
double-rack/ruoyi-ui/src/views/mill/plan.vue
砂糖 e07387132b feat: 新增异常挂接日志页面,优化多个页面UI和功能
1.  新增mill/abnormal/log.vue页面,实现钢卷异常挂接日志的查询、导出功能
2.  调整成本项目页面查询表单label宽度为80px
3.  移除异常性能页面的导出按钮
4.  重构轧辊计划页面的操作按钮布局,新增分组样式
5.  注释并停用工作辊页面的实时性能数据加载和展示
2026-06-09 16:48:26 +08:00

832 lines
33 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="plan-page">
<!-- 上方轧制队列 -->
<div class="queue-section">
<div class="section-header"><span>轧制队列</span></div>
<el-table :data="planList" border size="mini" class="queue-table"
:row-class-name="queueRowClass"
@current-change="handleQueueSelect"
highlight-current-row
height="calc(50vh - 120px)">
<el-table-column label="序号" prop="sortNo" width="50" align="center" fixed />
<el-table-column label="钢卷编号" prop="inMatNo" width="110" />
<el-table-column label="合金牌号" prop="alloyNo" width="90" />
<el-table-column label="采料厚度(mm)" prop="inMatThick" width="105" align="right" />
<el-table-column label="成品厚度(mm)" prop="outThick" width="105" align="right" />
<el-table-column label="采料宽度(mm)" prop="inMatWidth" width="105" align="right" />
<el-table-column label="道次数" prop="passCount" width="65" align="center" />
<el-table-column label="采料长度(m)" prop="inMatLength" width="100" align="right" />
<el-table-column label="采料重量(t)" prop="inMatWeight" width="100" align="right" />
<el-table-column label="采料外径(mm)" prop="inMatOd" width="105" align="right" />
<el-table-column label="采料内径(mm)" prop="inMatId" width="105" align="right" />
<el-table-column label="生产状态" prop="prodStatus" width="80" align="center">
<template slot-scope="{ row }">
<span :class="prodStatusClass(row.prodStatus)">{{ prodStatusLabel(row.prodStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="工艺方案" prop="recipeNo" min-width="100" />
</el-table>
</div>
<!-- 下方 -->
<div class="bottom-section">
<!-- 左侧轧制工艺 -->
<div class="pass-panel">
<!-- 状态 A未选中计划 -->
<template v-if="!selectedPlan">
<div class="section-header"><span>轧制工艺</span></div>
<div class="pass-empty">请在上方选择一条计划</div>
</template>
<!-- 状态 B已选计划已绑定方案只读 -->
<template v-else-if="selectedPlan.recipeId && !passEditMode">
<div class="section-header">
<span>轧制工艺</span>
<div style="display:flex;align-items:center;gap:8px">
<span class="recipe-tag">{{ selectedPlan.recipeNo }}</span>
<el-button size="mini" icon="el-icon-edit" @click="enterEditMode">修改参数</el-button>
</div>
</div>
<el-table :data="passList" border size="mini" class="pass-table"
:row-class-name="passRowClass" height="calc(50vh - 160px)">
<el-table-column label="道次" prop="passNo" width="45" align="center" fixed />
<el-table-column label="入口厚(mm)" prop="inThick" width="90" align="right" />
<el-table-column label="出口厚(mm)" prop="outThick" width="90" align="right" />
<el-table-column label="轧制力(kN)" prop="rollForce" width="85" align="right" />
<el-table-column label="入口张力(kN)" prop="inTension" width="90" align="right" />
<el-table-column label="出口张力(kN)" prop="outTension" width="90" align="right" />
<el-table-column label="最高速度" prop="maxSpeed" width="80" align="right" />
<el-table-column label="入口单位张力" prop="inUnitTension" width="100" align="right" />
<el-table-column label="出口单位张力" prop="outUnitTension" width="100" align="right" />
<el-table-column label="压下量(mm)" prop="reduction" width="85" align="right">
<template slot-scope="{ row }"><span class="calc-val">{{ row.reduction }}</span></template>
</el-table-column>
<el-table-column label="总压下量(mm)" prop="totalReduction" width="100" align="right">
<template slot-scope="{ row }"><span class="calc-val">{{ row.totalReduction }}</span></template>
</el-table-column>
</el-table>
</template>
<!-- 状态 C已选计划未绑定方案等待操作 -->
<template v-else-if="!selectedPlan.recipeId && !passEditMode">
<div class="section-header"><span>轧制工艺</span></div>
<div class="pass-empty">
<span>该计划尚未绑定工艺参数</span>
<div style="margin-top:12px;display:flex;gap:10px">
<el-button size="mini" type="primary" icon="el-icon-document-copy" @click="openSelectRecipe">选择已有方案</el-button>
<el-button size="mini" icon="el-icon-edit-outline" @click="enterEditMode">新增工艺参数</el-button>
</div>
</div>
</template>
<!-- 状态 D编辑模式新增 修改参数 -->
<template v-else-if="passEditMode">
<div class="section-header">
<span>{{ selectedPlan.recipeId ? '修改工艺参数' : '新增工艺参数' }}</span>
<div>
<el-button size="mini" icon="el-icon-plus" @click="addEditPass">增加道次</el-button>
<el-button size="mini" icon="el-icon-minus" @click="removeEditPass" :disabled="editingPassList.length===0">删除末道</el-button>
</div>
</div>
<el-table :data="editingPassList" border size="mini" class="pass-table"
:row-class-name="passRowClass" height="calc(50vh - 200px)">
<el-table-column label="道次" prop="passNo" width="45" align="center" fixed>
<template slot-scope="{ row }"><b>{{ row.passNo }}</b></template>
</el-table-column>
<el-table-column label="入口厚(mm)" width="90">
<template slot-scope="{ row }">
<el-input v-model="row.inThick" size="mini" @blur="calcEditPass(row)" />
</template>
</el-table-column>
<el-table-column label="出口厚(mm)" width="90">
<template slot-scope="{ row }">
<el-input v-model="row.outThick" size="mini" @blur="calcEditPass(row)" />
</template>
</el-table-column>
<el-table-column label="宽度(mm)" width="80">
<template slot-scope="{ row }"><el-input v-model="row.width" size="mini" /></template>
</el-table-column>
<el-table-column label="轧制力(kN)" width="85">
<template slot-scope="{ row }"><el-input v-model="row.rollForce" size="mini" /></template>
</el-table-column>
<el-table-column label="入口张力" width="80">
<template slot-scope="{ row }"><el-input v-model="row.inTension" size="mini" /></template>
</el-table-column>
<el-table-column label="出口张力" width="80">
<template slot-scope="{ row }"><el-input v-model="row.outTension" size="mini" /></template>
</el-table-column>
<el-table-column label="最高速度" width="80">
<template slot-scope="{ row }"><el-input v-model="row.maxSpeed" size="mini" /></template>
</el-table-column>
<el-table-column label="入口单位张力" width="100">
<template slot-scope="{ row }"><el-input v-model="row.inUnitTension" size="mini" /></template>
</el-table-column>
<el-table-column label="出口单位张力" width="100">
<template slot-scope="{ row }"><el-input v-model="row.outUnitTension" size="mini" /></template>
</el-table-column>
<el-table-column label="压下量(mm)" prop="reduction" width="85" align="right">
<template slot-scope="{ row }"><span class="calc-val">{{ row.reduction }}</span></template>
</el-table-column>
<el-table-column label="总压下量(mm)" prop="totalReduction" width="100" align="right">
<template slot-scope="{ row }"><span class="calc-val">{{ row.totalReduction }}</span></template>
</el-table-column>
</el-table>
<!-- 编辑操作栏 -->
<div class="edit-action-bar">
<el-button size="mini" icon="el-icon-close" @click="cancelEditMode">取消</el-button>
<el-button size="mini" type="warning" icon="el-icon-document" @click="savePassOnly">仅用于本计划</el-button>
<el-button size="mini" type="primary" icon="el-icon-folder-checked" @click="openSaveAsRecipe">保存为方案...</el-button>
</div>
</template>
</div>
<!-- 右侧查询 + 操作 -->
<div class="op-panel">
<div class="section-header"><span>条件查询</span></div>
<div class="query-form">
<el-form size="mini" label-width="72px">
<el-form-item label="钢卷编号">
<el-input v-model="query.inMatNo" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="日期范围">
<el-date-picker v-model="query.dateRange" type="daterange"
range-separator="~" start-placeholder="开始" end-placeholder="结束"
value-format="yyyy-MM-dd" style="width:100%" />
</el-form-item>
<el-form-item label=" " label-width="72px">
<el-checkbox v-model="query.hideFinished">不显示生产完成</el-checkbox>
</el-form-item>
<el-form-item label=" " label-width="72px">
<el-button size="mini" type="primary" icon="el-icon-search" @click="loadList">条件查询</el-button>
<el-button size="mini" icon="el-icon-refresh" @click="resetQuery">全部显示</el-button>
</el-form-item>
</el-form>
</div>
<div class="section-header" style="margin-top:8px"><span>队列操作</span></div>
<div class="op-buttons">
<div class="op-btn-group">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="handleAdd">钢卷增加</el-button>
<el-button size="mini" icon="el-icon-edit" :disabled="!selectedPlan" @click="handleEdit">修改</el-button>
<el-button size="mini" icon="el-icon-document" :disabled="!selectedPlan" @click="handleFinish">完成</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" :disabled="!selectedPlan" @click="handleDelete">删除</el-button>
</div>
<div class="op-btn-sep"></div>
<div class="op-btn-group">
<el-button size="mini" icon="el-icon-top" :disabled="!selectedPlan" @click="handleMoveUp">上移</el-button>
<el-button size="mini" icon="el-icon-bottom" :disabled="!selectedPlan" @click="handleMoveDown">下移</el-button>
</div>
</div>
</div>
</div>
<!-- 底部状态栏 -->
<div class="footer-bar">
<div class="coil-info-block">
<span class="coil-label">当前带卷</span>
<span class="coil-field">卷号<b>{{ currentCoil.inMatNo || '—' }}</b></span>
<span class="coil-field">采料厚度<b>{{ currentCoil.inMatThick || '—' }} mm</b></span>
<span class="coil-field">成品厚度<b>{{ currentCoil.outThick || '—' }} mm</b></span>
<span class="coil-field">宽度<b>{{ currentCoil.inMatWidth || '—' }} mm</b></span>
<span class="coil-field">合金号<b>{{ currentCoil.alloyNo || '—' }}</b></span>
</div>
<div class="coil-divider"></div>
<div class="coil-info-block">
<span class="coil-label next">下一带卷</span>
<span class="coil-field">卷号<b>{{ nextCoil.inMatNo || '—' }}</b></span>
<span class="coil-field">采料厚度<b>{{ nextCoil.inMatThick || '—' }} mm</b></span>
<span class="coil-field">成品厚度<b>{{ nextCoil.outThick || '—' }} mm</b></span>
<span class="coil-field">宽度<b>{{ nextCoil.inMatWidth || '—' }} mm</b></span>
<span class="coil-field">合金号<b>{{ nextCoil.alloyNo || '—' }}</b></span>
</div>
</div>
<!-- 钢卷 新增/修改 对话框 -->
<el-dialog :title="planDialogTitle" :visible.sync="planDialogVisible" width="640px"
:close-on-click-modal="false" append-to-body>
<el-form :model="form" :rules="rules" ref="formRef" size="mini" label-width="90px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="钢卷编号" prop="inMatNo">
<el-input v-model="form.inMatNo" :disabled="!isNew"
@keyup.enter.native="isNew && queryKlpCoil()"
@blur="isNew && queryKlpCoil()"
placeholder="回车从三级查询钢卷数据">
<el-button slot="append" icon="el-icon-search"
:loading="coilQuerying" @click="queryKlpCoil" v-if="isNew" />
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合金牌号" prop="alloyNo">
<el-input v-model="form.alloyNo" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工艺方案">
<el-select v-model="form.recipeId" filterable clearable
placeholder="可选,也可在新增后在下方绑定"
style="width:100%" @change="onRecipeChange">
<el-option v-for="r in recipeOptions" :key="r.recipeId"
:label="`${r.recipeNo}${r.alloyNo}`" :value="r.recipeId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料厚度" prop="inMatThick">
<el-input v-model="form.inMatThick"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成品厚度" prop="outThick">
<el-input v-model="form.outThick"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料宽度">
<el-input v-model="form.inMatWidth"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料长度">
<el-input v-model="form.inMatLength"><template slot="append">m</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料重量">
<el-input v-model="form.inMatWeight"><template slot="append">t</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料外径">
<el-input v-model="form.inMatOd"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采料内径">
<el-input v-model="form.inMatId"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注">
<el-input v-model="form.remark" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer">
<el-button size="mini" @click="planDialogVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="handleSave">确定</el-button>
</div>
</el-dialog>
<!-- 选择已有方案 对话框 -->
<el-dialog title="选择工艺方案" :visible.sync="selectRecipeVisible" width="480px"
:close-on-click-modal="false" append-to-body>
<el-form size="mini" label-width="80px">
<el-form-item label="工艺方案">
<el-select v-model="selectRecipeId" filterable clearable
placeholder="输入方案号或合金号搜索" style="width:100%">
<el-option v-for="r in recipeOptions" :key="r.recipeId"
:label="`${r.recipeNo}${r.alloyNo}${r.inThick}→${r.outThick}mm`"
:value="r.recipeId" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="mini" @click="selectRecipeVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="confirmSelectRecipe">确定绑定</el-button>
</div>
</el-dialog>
<!-- 保存为方案 对话框 -->
<el-dialog title="保存为工艺方案" :visible.sync="saveAsRecipeVisible" width="400px"
:close-on-click-modal="false" append-to-body>
<el-form :model="saveAsForm" :rules="saveAsRules" ref="saveAsRef" size="mini" label-width="88px">
<el-form-item label="方案记录号" prop="recipeNo">
<el-input v-model="saveAsForm.recipeNo" placeholder="方案唯一编号" />
</el-form-item>
<el-form-item label="合金号" prop="alloyNo">
<el-input v-model="saveAsForm.alloyNo" />
</el-form-item>
<el-form-item label="原料厚度">
<el-input v-model="saveAsForm.inThick"><template slot="append">mm</template></el-input>
</el-form-item>
<el-form-item label="成品厚度">
<el-input v-model="saveAsForm.outThick"><template slot="append">mm</template></el-input>
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="mini" @click="saveAsRecipeVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="confirmSaveAsRecipe">保存并绑定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listPlan, addPlan, updatePlan, delPlan, moveUpPlan, moveDownPlan, finishPlan } from '@/api/mill/plan'
import { listAllRecipe, getRecipeDetail, addRecipe } from '@/api/mill/recipe'
import request from '@/utils/request'
const queryKlpCoilByNo = (coilNo) => request({ url: '/mill/klpCoil/queryByCoilNo', method: 'get', params: { coilNo } })
const emptyForm = () => ({
planId: null, planNo: '', inMatNo: '', alloyNo: '', recipeId: null, recipeNo: '',
outThick: '', inMatThick: '', inMatWidth: '', inMatLength: '',
inMatWeight: '', inMatOd: '', inMatId: '', passCount: 0, remark: ''
})
const emptyPass = (no) => ({
passNo: no, inThick: '', outThick: '', width: '',
rollForce: '', inTension: '', outTension: '',
maxSpeed: '', inUnitTension: '', outUnitTension: '',
reduction: '', totalReduction: '', remark: ''
})
export default {
name: 'MillPlan',
data() {
return {
planList: [],
selectedPlan: null,
// 只读展示用
passList: [],
recipeOptions: [],
query: { inMatNo: '', dateRange: null, hideFinished: false },
// 编辑模式
passEditMode: false,
editingPassList: [],
// 钢卷对话框
planDialogVisible: false,
isNew: true,
form: emptyForm(),
rules: {
inMatNo: [{ required: true, message: '请输入钢卷编号', trigger: 'blur' }],
alloyNo: [{ required: true, message: '请输入合金牌号', trigger: 'blur' }],
inMatThick: [{ required: true, message: '请输入采料厚度', trigger: 'blur' }],
outThick: [{ required: true, message: '请输入成品厚度', trigger: 'blur' }],
},
// 选择方案对话框
selectRecipeVisible: false,
selectRecipeId: null,
coilQuerying: false,
// 保存为方案对话框
saveAsRecipeVisible: false,
saveAsForm: { recipeNo: '', alloyNo: '', inThick: '', outThick: '' },
saveAsRules: {
recipeNo: [{ required: true, message: '请输入方案记录号', trigger: 'blur' }],
alloyNo: [{ required: true, message: '请输入合金号', trigger: 'blur' }],
}
}
},
computed: {
planDialogTitle() { return this.isNew ? '新增钢卷' : '修改钢卷' },
currentCoil() { return this.planList.find(p => p.prodStatus === 'Rolling') || {} },
nextCoil() {
const idx = this.planList.findIndex(p => p.prodStatus === 'Rolling')
if (idx >= 0 && idx + 1 < this.planList.length) return this.planList[idx + 1]
return this.planList.find(p => p.prodStatus === 'Idle') || {}
}
},
mounted() {
this.loadList()
this.refreshRecipeOptions()
},
methods: {
refreshRecipeOptions() {
listAllRecipe({}).then(res => { this.recipeOptions = res.data || [] })
},
handleFinish() {
const planId = this.selectedPlan.planId
if (!planId) return this.$message.warning('请先选择钢卷')
finishPlan(planId).then(res => {
if (res.code === 200) {
this.$message.success('完成成功')
this.loadList()
} else {
this.$message.error(res.msg || '完成失败')
}
})
},
loadList() {
const params = { inMatNo: this.query.inMatNo }
if (this.query.hideFinished) params.Idle = 'Idle'
if (this.query.dateRange && this.query.dateRange.length === 2) {
params.beginTime = this.query.dateRange[0]
params.endTime = this.query.dateRange[1]
}
listPlan(params).then(res => { this.planList = res.data || [] })
},
resetQuery() {
this.query = { inMatNo: '', dateRange: null, hideFinished: false }
this.loadList()
},
// ── 队列行点击 ──
handleQueueSelect(row) {
if (this.passEditMode) {
// 正在编辑时禁止切换行
this.$message.warning('请先保存或取消当前编辑')
return
}
this.selectedPlan = row
this.passList = []
if (!row) return
if (row.recipeId) {
getRecipeDetail(row.recipeId).then(res => {
this.passList = (res.data && res.data.passList) || []
})
}
},
// ── 编辑模式 ──
enterEditMode() {
// 若已有方案,用当前道次数据初始化;否则空列表
this.editingPassList = this.passList.map(p => ({ ...p }))
this.passEditMode = true
},
cancelEditMode() {
this.passEditMode = false
this.editingPassList = []
},
addEditPass() {
this.editingPassList.push(emptyPass(this.editingPassList.length + 1))
},
removeEditPass() {
if (this.editingPassList.length) this.editingPassList.pop()
},
calcEditPass(row) {
const inT = parseFloat(row.inThick) || 0
const outT = parseFloat(row.outThick) || 0
row.reduction = outT > 0 ? (inT - outT).toFixed(3) : ''
const base = parseFloat(this.selectedPlan.inMatThick) || 0
this.editingPassList.forEach(p => {
const pOut = parseFloat(p.outThick) || 0
if (base > 0 && pOut > 0) p.totalReduction = parseFloat((base - pOut).toFixed(3))
})
},
// ── 仅用于本计划 ──
savePassOnly() {
if (this.editingPassList.length === 0) {
this.$message.warning('请至少添加一个道次')
return
}
// 创建隐藏方案recipeNo 以 __ 开头表示私有,不展示在下拉列表中)
const autoNo = `__${this.selectedPlan.inMatNo || this.selectedPlan.planId}`
const payload = {
recipeNo: autoNo,
alloyNo: this.selectedPlan.alloyNo || '-',
inThick: this.selectedPlan.inMatThick || '0',
outThick: this.selectedPlan.outThick || '0',
passCount: this.editingPassList.length,
passList: this.editingPassList,
status: '1'
}
this._doSaveRecipeAndBindPlan(payload)
},
// ── 保存为方案(打开弹窗) ──
openSaveAsRecipe() {
this.saveAsForm = {
recipeNo: '',
alloyNo: this.selectedPlan.alloyNo || '',
inThick: this.selectedPlan.inMatThick || '',
outThick: this.selectedPlan.outThick || ''
}
this.saveAsRecipeVisible = true
},
confirmSaveAsRecipe() {
this.$refs.saveAsRef.validate(valid => {
if (!valid) return
if (this.editingPassList.length === 0) {
this.$message.warning('请至少添加一个道次')
return
}
const payload = {
...this.saveAsForm,
passCount: this.editingPassList.length,
passList: this.editingPassList,
status: '0'
}
this._doSaveRecipeAndBindPlan(payload).then(() => {
this.saveAsRecipeVisible = false
this.refreshRecipeOptions()
})
})
},
// 公共:新增 recipe然后把 recipeId 写回 plan
_doSaveRecipeAndBindPlan(recipePayload) {
return addRecipe(recipePayload).then(res => {
const newId = res.data // controller 返回新 ID
return updatePlan({
...this.selectedPlan,
recipeId: newId,
recipeNo: recipePayload.recipeNo,
passCount: recipePayload.passCount
})
}).then(() => {
this.$message.success('保存成功')
this.passEditMode = false
this.editingPassList = []
this.loadList()
// 刷新已选行数据
return listPlan({ inMatNo: this.selectedPlan.inMatNo }).then(res => {
const updated = (res.data || []).find(p => p.planId === this.selectedPlan.planId)
if (updated) {
this.selectedPlan = updated
getRecipeDetail(updated.recipeId).then(r => {
this.passList = (r.data && r.data.passList) || []
})
}
})
})
},
// ── 选择已有方案 ──
openSelectRecipe() {
this.selectRecipeId = null
this.selectRecipeVisible = true
},
confirmSelectRecipe() {
if (!this.selectRecipeId) {
this.$message.warning('请选择一个方案')
return
}
const r = this.recipeOptions.find(x => x.recipeId === this.selectRecipeId)
updatePlan({
...this.selectedPlan,
recipeId: this.selectRecipeId,
recipeNo: r ? r.recipeNo : '',
passCount: r ? r.passCount : 0
}).then(() => {
this.$message.success('方案绑定成功')
this.selectRecipeVisible = false
this.selectedPlan = { ...this.selectedPlan, recipeId: this.selectRecipeId, recipeNo: r ? r.recipeNo : '' }
getRecipeDetail(this.selectRecipeId).then(res => {
this.passList = (res.data && res.data.passList) || []
})
this.loadList()
})
},
// ── 从三级 WMS 查询钢卷数据 ──
queryKlpCoil() {
const coilNo = (this.form.inMatNo || '').trim()
if (!coilNo) return
this.coilQuerying = true
queryKlpCoilByNo(coilNo).then(res => {
const c = res && res.data
if (!c) { this.$message.info('三级 WMS 中未找到该钢卷号'); return }
if (c.actualThickness && !this.form.inMatThick) this.form.inMatThick = c.actualThickness
if (c.actualWidth && !this.form.inMatWidth) this.form.inMatWidth = String(c.actualWidth)
if (c.netWeight && !this.form.inMatWeight) this.form.inMatWeight = String(c.netWeight)
if (c.length && !this.form.inMatLength) this.form.inMatLength = String(c.length)
this.$message.success('已从三级 WMS 自动填入钢卷数据')
}).catch(() => {
this.$message.warning('三级 WMS 查询失败,请手动填写')
}).finally(() => { this.coilQuerying = false })
},
// ── 钢卷 CRUD ──
handleAdd() {
this.isNew = true
this.form = emptyForm()
this.planDialogVisible = true
},
handleEdit() {
this.isNew = false
this.form = { ...this.selectedPlan }
this.planDialogVisible = true
},
handleSave() {
this.$refs.formRef.validate(valid => {
if (!valid) return
if (this.form.recipeId) {
const r = this.recipeOptions.find(x => x.recipeId === this.form.recipeId)
if (r) { this.form.recipeNo = r.recipeNo; this.form.passCount = r.passCount }
}
const api = this.isNew ? addPlan : updatePlan
api(this.form).then(() => {
this.$message.success('保存成功')
this.planDialogVisible = false
this.loadList()
})
})
},
handleDelete() {
this.$confirm(`确定删除钢卷「${this.selectedPlan.inMatNo}」?`, '提示', { type: 'warning' }).then(() => {
delPlan(this.selectedPlan.planId).then(() => {
this.$message.success('删除成功')
this.selectedPlan = null
this.passList = []
this.passEditMode = false
this.loadList()
})
})
},
handleMoveUp() { moveUpPlan(this.selectedPlan.planId).then(() => this.loadList()) },
handleMoveDown() { moveDownPlan(this.selectedPlan.planId).then(() => this.loadList()) },
onRecipeChange(recipeId) {
if (!recipeId) return
const r = this.recipeOptions.find(x => x.recipeId === recipeId)
if (r) {
if (!this.form.alloyNo) this.form.alloyNo = r.alloyNo
if (!this.form.inMatThick) this.form.inMatThick = r.inThick
if (!this.form.outThick) this.form.outThick = r.outThick
if (!this.form.inMatWidth) this.form.inMatWidth = r.outWidth
}
},
queueRowClass({ row }) {
if (row.prodStatus === 'Rolling') return 'row-rolling'
if (row.prodStatus === 'Idle' && this.currentCoil.sortNo && row.sortNo === this.currentCoil.sortNo + 1) return 'row-next'
return ''
},
passRowClass({ rowIndex }) { return rowIndex % 2 === 0 ? '' : 'alt-row' },
prodStatusLabel(s) { return { Idle: '待轧', Rolling: '轧制中', NextCoil: '下一卷', Done: '已完成', Error: '异常' }[s] || s },
prodStatusClass(s) { return { Idle: 'status-wait', Rolling: 'status-rolling', NextCoil: 'status-next', Done: 'status-done', Error: 'status-err' }[s] || '' }
}
}
</script>
<style scoped lang="scss">
.plan-page {
display: flex;
flex-direction: column;
height: calc(100vh - 84px);
background: #f0f2f5;
padding: 8px 12px;
box-sizing: border-box;
gap: 8px;
}
.section-header {
background: #1c2b3a;
color: #ecf0f1;
padding: 6px 12px;
font-size: 12px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
border-radius: 3px 3px 0 0;
}
.recipe-tag {
font-size: 11px;
font-weight: 400;
color: #a9bcd0;
margin-right: 4px;
}
/* 队列 */
.queue-section {
background: #fff;
border: 1px solid #dde1e6;
border-radius: 3px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.queue-table {
::v-deep .row-rolling td { background: #fef3e2 !important; }
::v-deep .row-next td { background: #fdfbe4 !important; }
::v-deep .el-table__row.current-row td { background: #e8f0fb !important; }
}
/* 下方 */
.bottom-section {
display: flex;
flex: 1;
gap: 8px;
overflow: hidden;
}
/* 道次面板 */
.pass-panel {
flex: 1;
background: #fff;
border: 1px solid #dde1e6;
border-radius: 3px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.pass-empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 12px;
}
.pass-table {
flex: 1;
::v-deep .el-table__row.alt-row td { background: #f7f9fc !important; }
}
.calc-val {
font-family: 'Courier New', monospace;
font-weight: 600;
color: #1d4e89;
}
/* 编辑操作栏 */
.edit-action-bar {
padding: 6px 12px;
border-top: 1px solid #e4e7ed;
background: #f7f9fc;
display: flex;
justify-content: flex-end;
gap: 8px;
flex-shrink: 0;
}
/* 右侧操作区 */
.op-panel {
width: 260px;
flex-shrink: 0;
background: #fff;
border: 1px solid #dde1e6;
border-radius: 3px;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.query-form {
padding: 10px 12px 4px;
border-bottom: 1px solid #e4e7ed;
}
.op-buttons {
padding: 8px 12px;
display: flex;
flex-direction: column;
gap: 2px;
border-bottom: 1px solid #e4e7ed;
.op-btn-group {
display: flex;
flex-direction: column;
gap: 2px;
}
.op-btn-sep {
height: 1px;
background: #e4e7ed;
margin: 5px 0;
}
.el-button {
width: 100%;
justify-content: flex-start;
border-radius: 2px;
padding-left: 10px;
margin-left: 0 !important;
i { margin-right: 4px; }
&:not(.el-button--primary):not(.el-button--danger) {
border-color: #dcdfe6;
&:hover { color: #409eff; border-color: #c6e2ff; background: #ecf5ff; }
}
}
}
/* 底部状态栏 */
.footer-bar {
background: #1c2b3a;
border-radius: 3px;
padding: 6px 16px;
display: flex;
align-items: center;
flex-shrink: 0;
font-size: 11px;
}
.coil-info-block { display: flex; align-items: center; gap: 16px; flex: 1; }
.coil-label { color: #a9bcd0; font-size: 11px; font-weight: 700; white-space: nowrap; &.next { color: #f0b429; } }
.coil-field { color: #bdc3c7; white-space: nowrap; b { color: #ecf0f1; } }
.coil-divider { width: 1px; height: 20px; background: #2e4057; margin: 0 20px; }
/* 状态标签 */
.status-wait { color: #7f8c8d; }
.status-rolling { color: #d68910; font-weight: 700; }
.status-done { color: #2471a3; }
.status-err { color: #c0392b; font-weight: 700; }
</style>