Files
xgy-oa/klp-ui/src/views/wms/anneal/plan/index.vue
砂糖 668af2140a fix: 修复规格分割可能导致的undefined错误
处理多处规格(specification)分割时的空值情况,添加可选链操作符和默认值
修改wms报表合并查询参数,清空时间条件
调整acid页面容器样式
2026-04-25 09:40:12 +08:00

752 lines
26 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, '{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" width="80">
<template slot-scope="scope">
<el-input v-model="scope.row.furnaceLevel" placeholder="请输入钢卷层级"
@change="handlePLanCoilChange(scope.row)" />
</template>
</el-table-column>
<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 v-if="currentPlan.status === 0" 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">
<WarehouseSelect v-model="scope.row.warehouseId" />
</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="目标炉" v-if="!form.planId">
<el-select v-model="targetFurnaces" placeholder="请选择" filterable clearable multiple key="mu">
<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="targetFurnaceId" v-else>
<el-select v-model="form.targetFurnaceId" placeholder="请选择" filterable clearable key="si">
<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="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>
</div>
</template>
<script>
import { listAnnealPlan, getAnnealPlan, updateAnnealPlanCoil ,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 WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import { addAnnealOperateEvent } from "@/api/wms/annealOperateEvent";
export default {
name: "AnnealPlan",
components: {
WarehouseSelect
},
data() {
return {
buttonLoading: false,
loading: true,
coilLoading: false,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
planList: [],
furnaceOptions: [],
targetFurnaces: [],
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();
},
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();
},
handlePLanCoilChange(row) {
updateAnnealPlanCoil(row).then(() => {
this.$message.success('已更新');
}).finally(() => {
this.loadPlanCoils();
});
},
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('已加入计划');
// 查找对应id退火炉的名称
const targetFurnaceName = this.furnaceOptions.find(item => item.furnaceId === this.currentPlan.targetFurnaceId)?.furnaceName || '';
// anneal-todo: 新增操作事件
addAnnealOperateEvent({
annealFurnaceId: this.currentPlan.targetFurnaceId,
operateType: 'ADD',
operateContent: '钢卷号' + item.enterCoilNo + '加入退火炉' + targetFurnaceName,
coilId: item.coilId,
})
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.targetFurnaces = [];
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 {
if (this.targetFurnaces.length === 0) {
this.$message.warning('请选择至少一个目标炉');
return;
}
Promise.all(this.targetFurnaces.map(id => {
return addAnnealPlan({
...this.form,
targetFurnaceId: id,
}).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
})).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 });
// anneal-todo: 新增操作事件
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;
// anneal-todo: 新增操作事件
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('解绑成功');
// 查找对应id退火炉的名称
const targetFurnaceName = this.furnaceOptions.find(item => item.furnaceId === this.currentPlan.targetFurnaceId)?.furnaceName || '';
// anneal-todo: 新增操作事件
addAnnealOperateEvent({
annealFurnaceId: this.currentPlan.targetFurnaceId,
operateType: 'UNBIND',
operateContent: '钢卷号' + row.enterCoilNo + '解绑退火炉' + targetFurnaceName,
coilId: row.coilId,
})
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>