diff --git a/ruoyi-mill/src/main/java/com/ruoyi/mill/controller/MillProcessRecipeController.java b/ruoyi-mill/src/main/java/com/ruoyi/mill/controller/MillProcessRecipeController.java index 35ed624a..633e850d 100644 --- a/ruoyi-mill/src/main/java/com/ruoyi/mill/controller/MillProcessRecipeController.java +++ b/ruoyi-mill/src/main/java/com/ruoyi/mill/controller/MillProcessRecipeController.java @@ -37,7 +37,8 @@ public class MillProcessRecipeController extends BaseController { @PostMapping public AjaxResult add(@RequestBody MillProcessRecipe recipe) { - return toAjax(recipeService.save(recipe)); + recipeService.save(recipe); + return AjaxResult.success(recipe.getId()); } @PutMapping diff --git a/ruoyi-ui/src/views/mill/plan.vue b/ruoyi-ui/src/views/mill/plan.vue index 39f0ad19..b6835314 100644 --- a/ruoyi-ui/src/views/mill/plan.vue +++ b/ruoyi-ui/src/views/mill/plan.vue @@ -9,18 +9,18 @@ @current-change="handleQueueSelect" highlight-current-row height="calc(50vh - 120px)"> - - - - - - - + + + + + + + - - - + + + @@ -32,31 +32,116 @@
- +
-
- 轧制工艺 - {{ selectedPlan.recipeNo }} -
- - - - - - - - - - - - - - - - - + + + + + + + + + + + + +
@@ -90,11 +175,6 @@ Up 上移 Down 下移
- -
工艺方案
-
- 快速新增方案 -
@@ -120,7 +200,7 @@ - @@ -136,7 +216,8 @@ - @@ -186,80 +267,50 @@
- 取消 + 取消 确定
- - + - - - - - - - - - - - - - - - - - - - - - - - - -
- 道次参数 -
- 增加道次 - 删除末道 -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + +
- 取消 - 保存方案 + 取消 + 确定绑定 +
+
+ + + + + + + + + + + + + + + + + +
+ 取消 + 保存并绑定
@@ -289,11 +340,17 @@ export default { return { planList: [], selectedPlan: null, + // 只读展示用 passList: [], recipeOptions: [], query: { inMatNo: '', dateRange: null, hideFinished: false }, + + // 编辑模式 + passEditMode: false, + editingPassList: [], + // 钢卷对话框 - dialogVisible: false, + planDialogVisible: false, isNew: true, form: emptyForm(), rules: { @@ -302,23 +359,23 @@ export default { inMatThick: [{ required: true, message: '请输入采料厚度', trigger: 'blur' }], outThick: [{ required: true, message: '请输入成品厚度', trigger: 'blur' }], }, - // 快速新增方案对话框 - recipeDialogVisible: false, - recipeForm: { recipeNo: '', alloyNo: '', inThick: '', outThick: '', outWidth: '' }, - recipePassList: [], - recipeRules: { + + // 选择方案对话框 + selectRecipeVisible: false, + selectRecipeId: null, + + // 保存为方案对话框 + saveAsRecipeVisible: false, + saveAsForm: { recipeNo: '', alloyNo: '', inThick: '', outThick: '' }, + saveAsRules: { recipeNo: [{ required: true, message: '请输入方案记录号', trigger: 'blur' }], alloyNo: [{ required: true, message: '请输入合金号', trigger: 'blur' }], - inThick: [{ required: true, message: '请输入原料厚度', trigger: 'blur' }], - outThick: [{ required: true, message: '请输入成品厚度', trigger: 'blur' }], } } }, computed: { - dialogTitle() { return this.isNew ? '新增钢卷' : '修改钢卷' }, - currentCoil() { - return this.planList.find(p => p.prodStatus === '1') || {} - }, + planDialogTitle() { return this.isNew ? '新增钢卷' : '修改钢卷' }, + currentCoil() { return this.planList.find(p => p.prodStatus === '1') || {} }, nextCoil() { const idx = this.planList.findIndex(p => p.prodStatus === '1') if (idx >= 0 && idx + 1 < this.planList.length) return this.planList[idx + 1] @@ -340,42 +397,185 @@ export default { params.beginDate = this.query.dateRange[0] params.endDate = this.query.dateRange[1] } - listPlan(params).then(res => { - this.planList = res.data || [] - }) + 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 - if (!row || !row.recipeId) { this.passList = []; return } - getRecipeDetail(row.recipeId).then(res => { - this.passList = (res.data && res.data.passList) || [] + 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.id}` + 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 + const recipeNo = recipePayload.recipeNo.startsWith('__') ? '' : recipePayload.recipeNo + 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.id === this.selectedPlan.id) + 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.id === 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() + }) + }, + + // ── 钢卷 CRUD ── handleAdd() { this.isNew = true this.form = emptyForm() - this.dialogVisible = true + this.planDialogVisible = true }, handleEdit() { this.isNew = false this.form = { ...this.selectedPlan } - this.dialogVisible = true + this.planDialogVisible = true }, handleSave() { this.$refs.formRef.validate(valid => { if (!valid) return if (this.form.recipeId) { - const matched = this.recipeOptions.find(r => r.id === this.form.recipeId) - if (matched) { this.form.recipeNo = matched.recipeNo; this.form.passCount = matched.passCount } + const r = this.recipeOptions.find(x => x.id === 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.dialogVisible = false + this.planDialogVisible = false this.loadList() }) }) @@ -386,6 +586,7 @@ export default { this.$message.success('删除成功') this.selectedPlan = null this.passList = [] + this.passEditMode = false this.loadList() }) }) @@ -402,52 +603,15 @@ export default { if (!this.form.inMatWidth) this.form.inMatWidth = r.outWidth } }, - // 快速新增方案 - openQuickRecipe() { - this.recipeForm = { recipeNo: '', alloyNo: '', inThick: '', outThick: '', outWidth: '' } - this.recipePassList = [] - this.recipeDialogVisible = true - }, - addRecipePass() { - this.recipePassList.push(emptyPass(this.recipePassList.length + 1)) - }, - removeRecipePass() { - if (this.recipePassList.length) this.recipePassList.pop() - }, - calcRecipePass(row) { - const inT = parseFloat(row.inThick) || 0 - const outT = parseFloat(row.outThick) || 0 - row.reduction = outT > 0 ? (inT - outT).toFixed(3) : '' - }, - saveQuickRecipe() { - this.$refs.recipeFormRef.validate(valid => { - if (!valid) return - const data = { - ...this.recipeForm, - passCount: this.recipePassList.length, - passList: this.recipePassList - } - addRecipe(data).then(() => { - this.$message.success('工艺方案新增成功') - this.recipeDialogVisible = false - this.refreshRecipeOptions() - }) - }) - }, + queueRowClass({ row }) { if (row.prodStatus === '1') return 'row-rolling' if (row.prodStatus === '0' && this.currentCoil.sortNo && row.sortNo === this.currentCoil.sortNo + 1) return 'row-next' return '' }, - passRowClass({ rowIndex }) { - return rowIndex % 2 === 0 ? '' : 'alt-row' - }, - prodStatusLabel(s) { - return { '0': '待轧', '1': '轧制中', '2': '已完成', '9': '异常' }[s] || s - }, - prodStatusClass(s) { - return { '0': 'status-wait', '1': 'status-rolling', '2': 'status-done', '9': 'status-err' }[s] || '' - } + passRowClass({ rowIndex }) { return rowIndex % 2 === 0 ? '' : 'alt-row' }, + prodStatusLabel(s) { return { '0': '待轧', '1': '轧制中', '2': '已完成', '9': '异常' }[s] || s }, + prodStatusClass(s) { return { '0': 'status-wait', '1': 'status-rolling', '2': 'status-done', '9': 'status-err' }[s] || '' } } } @@ -480,9 +644,10 @@ export default { font-size: 11px; font-weight: 400; color: #a9bcd0; + margin-right: 4px; } -/* ── 上方队列 ── */ +/* 队列 */ .queue-section { background: #fff; border: 1px solid #dde1e6; @@ -491,14 +656,13 @@ export default { 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; @@ -506,6 +670,7 @@ export default { overflow: hidden; } +/* 道次面板 */ .pass-panel { flex: 1; background: #fff; @@ -516,6 +681,16 @@ export default { 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; } @@ -527,6 +702,17 @@ export default { 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; @@ -550,11 +736,10 @@ export default { flex-direction: column; gap: 6px; border-bottom: 1px solid #e4e7ed; - .el-button { width: 100%; justify-content: flex-start; } } -/* ── 底部状态栏 ── */ +/* 底部状态栏 */ .footer-bar { background: #1c2b3a; border-radius: 3px; @@ -564,51 +749,14 @@ export default { 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; -} +.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; } - -/* 快速新增方案弹窗内道次表头 */ -.recipe-pass-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0 4px; - font-size: 12px; - font-weight: 700; - color: #1c2b3a; - border-bottom: 1px solid #e4e7ed; - margin-bottom: 6px; -}