Files
klp-oa/klp-ui/src/views/wms/anneal/plan/index.vue
砂糖 71e2467572 feat(wms): 优化退火炉界面和修复钢卷合卷功能
- 重构退火炉界面为卡片式布局,增加可视化状态展示
- 修复钢卷合卷API路径和方法,添加操作ID和类型参数
- 移除标签打印中冗余的itemName字段
- 修复退火计划物料卡片布局自适应问题
2026-03-16 15:38:11 +08:00

719 lines
25 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="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="queryParams.planNo" placeholder="请输入计划号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="queryParams.targetFurnaceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single"
@click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
@click="handleDelete">删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<KLPTable v-loading="loading" :data="planList" @selection-change="handleSelectionChange"
@row-click="handleRowClick">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="计划号" align="center" prop="planNo" />
<el-table-column label="计划时间" align="center" prop="planStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="目标炉" align="center" prop="targetFurnaceName" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="actualStartTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.actualStartTime || scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(scope.row)">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-s-operation"
@click.stop="openStatusDialog(scope.row)">状态</el-button>
<el-button v-if="scope.row.status === 0" size="mini" type="text" icon="el-icon-s-flag"
:disabled="!scope.row.coilCount" @click.stop="handleInFurnace(scope.row)">入炉</el-button>
<el-button v-if="scope.row.status === 2" size="mini" type="text" icon="el-icon-check"
@click.stop="handleComplete(scope.row)">完成</el-button>
</template>
</el-table-column>
</KLPTable>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<el-row :gutter="20" class="mt16">
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>领料列表</span>
<el-button size="mini" icon="el-icon-refresh" @click="getMaterialCoils">刷新</el-button>
</div>
<el-form :model="materialQueryParams" ref="materialQueryForm" size="small" :inline="true" class="mb8">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="materialQueryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="materialQueryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
style="width: 160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleMaterialQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetMaterialQuery">重置</el-button>
</el-form-item>
</el-form>
<div v-loading="materialLoading" class="material-grid">
<div v-if="materialList.length === 0" class="empty-tip">
<el-empty description="暂无待领物料" />
</div>
<div v-for="item in materialList" :key="item.coilId" class="material-card">
<div class="material-header">
<div class="material-text-wrap">
<div class="material-title">{{ item.currentCoilNo || '-' }}</div>
<div class="material-sub">入场{{ item.enterCoilNo || '-' }}</div>
</div>
<el-button type="primary" size="mini" @click="handleAddToPlan(item)"
:disabled="!currentPlan.planId || currentPlan.status !== 0">加入计划</el-button>
</div>
<div class="material-body">
<div class="material-row">厂家{{ item.supplierCoilNo || '-' }}</div>
<div class="material-row">库位{{ item.actualWarehouseName || '-' }}</div>
<div class="material-row">重量{{ item.netWeight || '-' }}t</div>
</div>
</div>
</div>
<pagination v-show="materialTotal > 0" :total="materialTotal" :page.sync="materialQueryParams.pageNum"
:limit.sync="materialQueryParams.pageSize" @pagination="getMaterialCoils" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>退火计划</span>
<div>
<el-button size="mini" icon="el-icon-refresh" @click="loadPlanCoils"
:disabled="!currentPlan.planId">刷新</el-button>
<el-button size="mini" type="primary" icon="el-icon-s-flag"
:disabled="!currentPlan.planId || currentPlan.status !== 0 || !currentPlan.coilCount"
@click="handleInFurnace(currentPlan)">入炉</el-button>
<el-button size="mini" type="success" icon="el-icon-check"
:disabled="!currentPlan.planId || currentPlan.status !== 2" @click="openCompleteDialog">完成退火</el-button>
</div>
</div>
<div v-if="!currentPlan.planId" class="empty-tip">
<el-empty description="请点击上方选择一个计划" />
</div>
<div v-else>
<div class="plan-summary">
<div>计划号{{ currentPlan.planNo }}</div>
<div>目标炉{{ currentPlan.targetFurnaceName || '-' }}</div>
<div>状态{{ statusLabel(currentPlan.status) }}</div>
<div>
<el-button size="mini" type="primary" icon="el-icon-s-flag" :disabled="currentPlan.status !== 0"
@click="handleInFurnace(currentPlan)">入炉</el-button>
</div>
</div>
<el-table :data="coilList" v-loading="coilLoading" class="light-table">
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo" />
<el-table-column label="创建时间" align="center" prop="createTime">
<template slot-scope="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="实际库位" align="center" width="220">
<template slot-scope="scope">
<span>{{ scope.row.actualWarehouseName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleUnbind(scope.row)">
{{ unbindLabel(currentPlan.status) }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="720px" append-to-body>
<div class="complete-tip">请为每条钢卷分配实际库位未分配将无法完成</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="实际库位" align="center" width="240">
<template slot-scope="scope">
<ActualWarehouseSelect v-model="scope.row.actualWarehouseId" :clear-input="false" :show-empty="false" />
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitComplete"> </el-button>
<el-button @click="completeOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog :title="title" :visible.sync="open" width="520px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-form-item label="计划号" prop="planNo">
<el-input v-model="form.planNo" placeholder="请输入计划号" />
</el-form-item>
<el-form-item label="计划开始" prop="planStartTime">
<el-date-picker clearable v-model="form.planStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择计划时间" />
</el-form-item>
<el-form-item label="目标炉" prop="targetFurnaceId">
<el-select v-model="form.targetFurnaceId" placeholder="请选择" filterable>
<el-option v-for="item in furnaceOptions" :key="item.furnaceId" :label="item.furnaceName"
:value="item.furnaceId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog title="更新状态" :visible.sync="statusOpen" width="360px" append-to-body>
<el-form label-width="90px">
<el-form-item label="状态">
<el-select v-model="statusForm.status" placeholder="请选择">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitStatus"> </el-button>
<el-button @click="statusOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAnnealPlan, getAnnealPlan, addAnnealPlan, updateAnnealPlan, delAnnealPlan, changeAnnealPlanStatus, inFurnace, completeAnnealPlan, listAnnealPlanCoils, bindAnnealPlanCoils, unbindAnnealPlanCoil } from "@/api/wms/annealPlan";
import { listAnnealFurnace } from "@/api/wms/annealFurnace";
import { listMaterialCoil } from "@/api/wms/coil";
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
export default {
name: "AnnealPlan",
components: {
ActualWarehouseSelect
},
data() {
return {
buttonLoading: false,
loading: true,
coilLoading: false,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
planList: [],
furnaceOptions: [],
title: "",
open: false,
statusOpen: false,
queryParams: {
pageNum: 1,
pageSize: 20,
planNo: undefined,
targetFurnaceId: undefined,
status: undefined,
},
form: {},
rules: {
planNo: [{ required: true, message: "计划号不能为空", trigger: "blur" }],
targetFurnaceId: [{ required: true, message: "目标炉子不能为空", trigger: "change" }],
},
currentPlan: {},
coilList: [],
materialLoading: false,
materialTotal: 0,
materialList: [],
materialQueryParams: {
pageNum: 1,
pageSize: 20,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
},
statusForm: {
planId: undefined,
status: undefined,
},
completeOpen: false,
completeLoading: false,
completePlanId: null,
completeCoils: [],
};
},
created() {
this.getList();
this.loadFurnaces();
this.getMaterialCoils();
this.loadActualWarehouses();
},
methods: {
getList() {
this.loading = true;
listAnnealPlan(this.queryParams).then(response => {
this.planList = response.rows;
this.total = response.total;
this.loading = false;
});
},
loadFurnaces() {
listAnnealFurnace({ pageNum: 1, pageSize: 999, status: 1 }).then(response => {
this.furnaceOptions = response.rows || [];
});
},
handleRowClick(row) {
this.currentPlan = row;
this.loadPlanCoils();
},
getMaterialCoils() {
this.materialLoading = true;
listMaterialCoil(this.materialQueryParams).then(response => {
this.materialList = response.rows || [];
this.materialTotal = response.total || 0;
this.materialLoading = false;
});
},
handleMaterialQuery() {
this.materialQueryParams.pageNum = 1;
this.getMaterialCoils();
},
resetMaterialQuery() {
this.resetForm("materialQueryForm");
this.resetMaterialForm();
this.handleMaterialQuery();
},
openCompleteDialog() {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status !== 2) {
this.$message.warning('计划未进行中,无法完成');
return;
}
this.completePlanId = this.currentPlan.planId;
this.completeOpen = true;
this.completeLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
this.completeCoils = (response.data || []).map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
actualWarehouseId: item.actualWarehouseId || null
}));
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
});
},
handleAddToPlan(item) {
if (!this.currentPlan.planId) {
this.$message.warning('请先选择计划');
return;
}
if (this.currentPlan.status === 2) {
this.$message.warning('当前计划进行中,无法再领料');
return;
}
this.coilLoading = true;
bindAnnealPlanCoils({
planId: this.currentPlan.planId,
coilId: item.coilId
}).then(() => {
this.$message.success('已加入计划');
this.loadPlanCoils();
}).finally(() => {
this.coilLoading = false;
});
},
loadPlanCoils() {
if (!this.currentPlan.planId) {
this.coilList = [];
return;
}
this.coilLoading = true;
listAnnealPlanCoils(this.currentPlan.planId).then(response => {
this.coilList = response.data || [];
this.coilLoading = false;
});
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = {
planId: undefined,
planNo: undefined,
planStartTime: undefined,
targetFurnaceId: undefined,
status: 0,
remark: undefined,
};
this.resetForm("form");
},
resetMaterialForm() {
this.materialQueryParams = {
pageNum: 1,
pageSize: 10,
enterCoilNo: undefined,
currentCoilNo: undefined,
status: 0,
dataType: 1,
};
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.planId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加退火计划";
},
handleUpdate(row) {
this.loading = true;
this.reset();
const planId = row.planId || this.ids;
getAnnealPlan(planId).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改退火计划";
});
},
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.planId != null) {
updateAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addAnnealPlan(this.form).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
handleDelete(row) {
const ids = row.planId || this.ids;
this.$modal.confirm('是否确认删除计划编号为"' + ids + '"的数据项?').then(() => {
this.loading = true;
return delAnnealPlan(ids);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).finally(() => {
this.loading = false;
});
},
openStatusDialog(row) {
this.statusForm = {
planId: row.planId,
status: row.status,
};
this.statusOpen = true;
},
submitStatus() {
changeAnnealPlanStatus(this.statusForm).then(() => {
this.$modal.msgSuccess("状态已更新");
this.statusOpen = false;
this.getList();
});
},
async handleInFurnace(row) {
await this.$confirm('点击入炉后钢卷将会被释放库位,是否确认入炉', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
this.loading = true;
await inFurnace({ planId: row.planId });
this.loading = false;
row.status = 2;
row.actualStartTime = new Date();
if (this.currentPlan.planId === row.planId) {
this.currentPlan.status = 2;
this.currentPlan.actualStartTime = row.actualStartTime;
this.loadPlanCoils();
}
this.getList();
this.$message.success('已入炉');
},
submitComplete() {
const locations = (this.completeCoils || []).map(item => ({
coilId: item.coilId,
actualWarehouseId: item.actualWarehouseId
}));
const missing = locations.filter(item => !item.actualWarehouseId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
this.completeLoading = true;
completeAnnealPlan({
planId: this.completePlanId,
locations: locations
}).then(() => {
this.$message.success('已完成');
this.completeOpen = false;
this.getList();
this.loadPlanCoils();
}).finally(() => {
this.completeLoading = false;
});
},
handleUnbind(row) {
this.$confirm('确定解绑该钢卷吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return unbindAnnealPlanCoil({
planId: this.currentPlan.planId,
coilId: row.coilId
});
}).then(() => {
this.$message.success('解绑成功');
this.loadPlanCoils();
});
},
statusLabel(status) {
if (status === 2) {
return '进行中';
}
if (status === 3) {
return '已完成';
}
return '未开始';
},
statusTag(status) {
if (status === 2) {
return 'success';
}
if (status === 3) {
return 'info';
}
return 'warning';
},
unbindLabel(status) {
if (status === 2) {
return '取消';
}
if (status === 3) {
return '撤回';
}
return '解绑';
}
}
};
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.empty-tip {
margin-top: 10px;
}
.panel-card {
border: 1px solid #f0f2f5;
background: #ffffff;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
}
/* ========== 修复在这里 ========== */
.material-grid {
display: grid;
/* 核心修复:去掉固定 4 列,改用自动填充,实现真正自适应 */
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
min-height: 120px;
/* 必须加,让 grid 不受父级弹性压缩影响 */
width: 100%;
box-sizing: border-box;
}
/* 媒体查询只需要控制最小宽度即可,不用写死列数 */
@media (max-width: 768px) {
.material-grid {
grid-template-columns: 1fr;
}
}
/* =============================== */
.material-card {
border: 1px solid #e9ecf2;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.material-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
/* 新增:让文字区域和按钮分行适配小宽度 */
flex-wrap: wrap;
}
/* 新增:文字容器,限制宽度并溢出省略 */
.material-text-wrap {
flex: 1;
min-width: 0;
/* 关键让flex子元素遵守宽度限制 */
}
.material-title {
font-weight: 600;
color: #303133;
/* 新增:标题溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 可根据需要调整最大宽度 */
}
.material-sub {
font-size: 12px;
color: #909399;
/* 核心:入场钢卷号溢出省略 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
/* 限制最大长度,避免挤压按钮 */
}
.material-body {
font-size: 12px;
color: #606266;
display: grid;
gap: 4px;
}
.material-row {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.plan-summary {
display: grid;
grid-template-columns: repeat(3, minmax(120px, 1fr));
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 8px;
}
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
background: #ffffff;
}
:deep(.el-table th),
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
}
:deep(.el-table::before) {
background-color: transparent;
}
</style>