Files
klp-oa/klp-ui/src/views/wms/coil/merge.vue
砂糖 4fefc68bbc fix(wms): 修复合同ID为空时添加合同关系的错误
在合并、分条和批量分条操作中,当合同ID为空时不再尝试添加合同关系
同时优化批量分条时的合同关系添加逻辑,先过滤掉空合同ID
2026-04-20 11:04:14 +08:00

1442 lines
43 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="merge-coil-container">
<!-- 顶部操作栏 -->
<div class="header-bar">
<div class="header-title">
<i class="el-icon-connection"></i>
<span>钢卷合卷</span>
</div>
<div class="header-actions">
<el-button v-if="!readonly" type="primary" size="small" @click="handleSave" :disabled="buttonLoading"
v-loading="buttonLoading">保存合卷</el-button>
<el-button size="small" @click="handleCancel" :disabled="loading">{{ readonly ? '返回' : '取消' }}</el-button>
</div>
</div>
<!-- 流程图区域 -->
<div class="flow-container">
<!-- 左侧源卷列表 -->
<div class="flow-left">
<div class="flow-section-title">
<span>源卷列表</span>
<el-button v-if="!readonly" type="primary" size="small" @click="addSourceCoil">添加源卷</el-button>
</div>
<div class="source-list">
<div class="source-coil-card" v-for="(item, index) in sourceCoils" :key="index">
<div class="source-coil-header">
<div class="source-number">{{ index + 1 }}</div>
<div class="source-info">
<div class="source-id">{{ item.currentCoilNo || '待选择' }}</div>
</div>
<el-button v-if="!readonly" type="text" size="mini" icon="el-icon-delete" @click="removeSourceCoil(index)"
class="btn-remove"></el-button>
</div>
<div class="source-coil-body">
<template v-if="item.coilId">
<div class="source-detail-row">
<span class="detail-label">入场钢卷号</span>
<span class="detail-value">{{ item.enterCoilNo || '—' }}</span>
</div>
<div class="source-detail-row">
<span class="detail-label">逻辑库区</span>
<span class="detail-value">{{ item.warehouseName || '—' }}</span>
</div>
<div class="source-detail-row">
<span class="detail-label">真实库区</span>
<span class="detail-value">{{ item.actualWarehouseName || '—' }}</span>
</div>
<div class="source-detail-row">
<span class="detail-label">物料名称</span>
<span class="detail-value">{{ item.itemName || '—' }}</span>
</div>
<div class="source-detail-row" v-if="item.specification">
<span class="detail-label">物料规格</span>
<span class="detail-value">{{ item.specification }}</span>
</div>
<!-- 参数参数展示 -->
<template v-if="item.bomItems && item.bomItems.length > 0">
<div class="source-detail-divider">参数参数</div>
<div class="source-bom-params">
<div class="source-bom-item" v-for="(param, idx) in item.bomItems.slice(0, 3)" :key="idx">
<span class="bom-key">{{ param.attrKey }}</span>
<span class="bom-value">{{ param.attrValue }}</span>
</div>
</div>
</template>
</template>
<!-- 第二个位置显示待合卷列表 -->
<template v-else-if="!readonly">
<div class="pending-list-title">待合卷钢卷列表</div>
<div class="pending-coil-list">
<div class="pending-coil-item" v-for="pending in renderPendingMergeList" :key="pending.actionId"
@click="selectPendingCoil(pending, index)">
<div class="pending-coil-no">{{ pending.currentCoilNo }}</div>
<div class="pending-coil-info">
<span class="pending-label">更新时间</span>
<span class="pending-value">{{ formatTime(pending.updateTime) }}</span>
</div>
</div>
<coil-selector @select="selectCoil($event, index)">
<div class="pending-coil-item">
选择其他钢卷
</div>
</coil-selector>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- 右侧目标卷信息 -->
<div class="flow-right">
<div class="flow-section-title">目标卷信息</div>
<div class="target-coil-card">
<div class="target-coil-header">
<i class="el-icon-s-grid"></i>
<span>新钢卷</span>
</div>
<div class="target-coil-body">
<el-form size="small" label-width="80px" :model="targetCoil" :rules="rules">
<div class="form-row">
<el-form-item label="卷号" class="form-item-half" prop="currentCoilNo">
<el-input v-model="targetCoil.currentCoilNo" placeholder="输入目标卷号" :disabled="readonly"></el-input>
<current-coil-no :current-coil-no="targetCoil.currentCoilNo" />
</el-form-item>
<el-form-item label="班组" class="form-item-half" prop="team">
<el-select v-model="targetCoil.team" placeholder="请选择班组" style="width: 100%" :disabled="readonly">
<el-option key="甲" label="甲" value="甲" />
<el-option key="乙" label="乙" value="乙" />
</el-select>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="材料类型" class="form-item-half" prop="materialType">
<el-select v-model="targetCoil.materialType" placeholder="请选择材料类型" style="width: 100%"
:disabled="readonly" @change="handleMaterialTypeChange">
<el-option label="原料" value="原料" />
<el-option label="成品" value="成品" />
</el-select>
</el-form-item>
<el-form-item :label="getItemLabel" class="form-item-half" prop="itemId">
<raw-material-selector v-if="targetCoil.materialType === '原料'" v-model="targetCoil.itemId"
placeholder="请选择原料" style="width: 100%" clearable
:disabled="readonly || !targetCoil.materialType" />
<product-selector v-else-if="targetCoil.materialType === '成品'" v-model="targetCoil.itemId"
placeholder="请选择成品" style="width: 100%" clearable
:disabled="readonly || !targetCoil.materialType" />
<div v-else>请先选择物料类型</div>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="质量状态" prop="qualityStatus" class="form-item-half">
<el-select v-model="targetCoil.qualityStatus" placeholder="请选择质量状态" style="width: 100%"
:disabled="readonly">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="切边要求" prop="trimmingRequirement" class="form-item-half">
<el-select v-model="targetCoil.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%"
:disabled="readonly">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="原料材质" prop="packingStatus" class="form-item-half">
<el-input v-model="targetCoil.packingStatus" placeholder="请输入原料材质" :disabled="readonly">
</el-input>
</el-form-item>
<el-form-item label="包装要求" prop="packagingRequirement" class="form-item-half">
<el-select v-model="targetCoil.packagingRequirement" placeholder="请选择包装要求" style="width: 100%"
:disabled="readonly">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
</el-select>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="毛重(t)" prop="grossWeight" class="form-item-half">
<el-input-number precision="3" :controls="false" v-model="targetCoil.grossWeight" placeholder="请输入毛重"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="净重(t)" prop="netWeight" class="form-item-half">
<el-input-number precision="3" :controls="false" v-model="targetCoil.netWeight" placeholder="请输入净重"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="长度(m)" prop="length" class="form-item-half">
<el-input-number :controls="false" v-model="targetCoil.length" placeholder="请输入长度" type="number"
:step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="实测长度(m)" prop="actualLength" class="form-item-half">
<el-input-number :controls="false" v-model="targetCoil.actualLength" placeholder="请输入实测长度"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="实测厚度(m)" prop="actualThickness" class="form-item-half">
<el-input-number :controls="false" v-model="targetCoil.actualThickness" placeholder="请输入实测厚度"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="实测宽度(m)" prop="actualWidth" class="form-item-half">
<el-input-number :controls="false" v-model="targetCoil.actualWidth" placeholder="请输入实测宽度"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="调制度" prop="temperGrade" class="form-item-half">
<el-input v-model="targetCoil.temperGrade" placeholder="请输入调制度" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="镀层种类" prop="coatingType" class="form-item-half">
<MemoInput storageKey="coatingType" v-model="targetCoil.coatingType" placeholder="请输入镀层种类" />
</el-form-item>
<el-form-item label="逻辑库区" prop="warehouseId" class="form-item-half">
<WarehouseSelect v-model="targetCoil.warehouseId" placeholder="请选择逻辑库区" :disabled="readonly" />
</el-form-item>
</div>
<el-form-item label="业务目的" prop="businessPurpose" class="form-item-half">
<el-select v-model="targetCoil.businessPurpose" placeholder="请选择业务目的" style="width: 100%"
:disabled="readonly">
<el-option v-for="item in dict.type.coil_business_purpose" :key="item.value" :value="item.value"
:label="item.label" />
</el-select>
</el-form-item>
<el-form-item label="钢卷表面处理" prop="coilSurfaceTreatment" class="form-item-half">
<MemoInput storageKey="surfaceTreatmentDesc" v-model="targetCoil.coilSurfaceTreatment"
placeholder="请输入钢卷表面处理" />
</el-form-item>
<!-- <div class="form-row"> -->
<el-form-item label="生产开始时间" prop="productionStartTime" class="form-item-half">
<TimeInput v-model="targetCoil.productionStartTime" @input="calculateProductionDuration"
:disabled="readonly" />
</el-form-item>
<el-form-item label="生产结束时间" prop="productionEndTime" class="form-item-half">
<TimeInput v-model="targetCoil.productionEndTime" @input="calculateProductionDuration"
:disabled="readonly" :show-now-button="true" />
</el-form-item>
<!-- </div> -->
<div class="form-row">
<el-form-item label="生产耗时" prop="productionDuration" class="form-item-half">
<el-input v-model="targetCoil.formattedDuration" placeholder="自动计算" disabled />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="关联合同" prop="contractId">
<ContractSelect v-model="targetCoil.contractId" placeholder="请选择合同" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="异常信息" class="form-item-full">
<div class="abnormal-container">
<div v-for="(abnormal, index) in abnormals" :key="index" class="abnormal-item"
@click="editAbnormal(index)">
<div class="abnormal-content">
<div class="abnormal-info">
<div class="abnormal-position">{{ getAbnormalPositionText(abnormal.position) }}</div>
<div class="abnormal-code">{{ getAbnormalCodeText(abnormal.defectCode) }}</div>
</div>
<el-button type="danger" size="mini" icon="el-icon-close" class="abnormal-delete"
@click.stop="deleteAbnormal(index)"></el-button>
</div>
</div>
<div class="abnormal-add" @click="addAbnormal">
<i class="el-icon-plus"></i>
</div>
</div>
</el-form-item>
</div>
</el-form>
</div>
</div>
</div>
</div>
<!-- 异常表单弹窗 -->
<el-dialog :title="currentAbnormalIndex === -1 ? '新增异常' : '编辑异常'" :visible.sync="abnormalDialogVisible"
width="600px">
<abnormal-form ref="abnormalForm" v-model="abnormalForm" :show-coil-selector="false"></abnormal-form>
<div slot="footer" class="dialog-footer">
<el-button @click="abnormalDialogVisible = false"> </el-button>
<el-button type="primary" @click="saveAbnormal"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getMaterialCoil, mergeMaterialCoil } from '@/api/wms/coil';
import { listPendingAction, getPendingAction } from '@/api/wms/pendingAction';
import CoilSelector from '@/components/CoilSelector';
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
import RawMaterialSelector from "@/components/KLPService/RawMaterialSelect";
import ProductSelector from "@/components/KLPService/ProductSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import TimeInput from "@/components/TimeInput";
import AbnormalForm from './components/AbnormalForm';
import { generateCoilNoPrefix } from "@/utils/coil/coilNo";
import ContractSelect from "@/components/KLPService/ContractSelect";
import { addCoilContractRel } from "@/api/wms/coilContractRel";
export default {
name: 'MergeCoil',
components: {
CoilSelector,
ActualWarehouseSelect,
RawMaterialSelector,
ProductSelector,
WarehouseSelect,
TimeInput,
AbnormalForm,
ContractSelect
},
dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'],
data() {
const currentCoilNoPrefix = generateCoilNoPrefix()
return {
currentCoilNoPrefix,
actionTypeCode: '',
// 源卷列表
sourceCoils: [],
// 目标卷信息
targetCoil: {
currentCoilNo: currentCoilNoPrefix,
team: '',
materialType: null,
itemType: null,
itemId: null,
grossWeight: null,
netWeight: null,
warehouseId: null,
actualWarehouseId: null,
qualityStatus: '',
packagingRequirement: '',
packingStatus: '',
trimmingRequirement: '',
length: null,
temperGrade: '',
coatingType: '',
actualLength: undefined,
actualWidth: undefined,
productionStartTime: '',
productionEndTime: '',
productionDuration: '',
formattedDuration: '',
},
rules: {
currentCoilNo: [
{ required: true, message: "当前钢卷号不能为空", trigger: "blur" },
{
// 当前钢卷号必须大于等于10位
validator: (rule, value, callback) => {
if (value.length < 11) {
callback(new Error('当前钢卷号必须大于等于11位'));
} else {
callback();
}
}, trigger: 'blur'
},
],
team: [
{ required: true, message: "班组不能为空", trigger: "change" }
],
materialType: [
{ required: true, message: "材料类型不能为空", trigger: "change" }
],
itemId: [
{ required: true, message: "物品ID不能为空", trigger: "blur" }
],
itemType: [
{ required: true, message: "物品类型不能为空", trigger: "change" }
],
// 净重和毛重
netWeight: [
{ required: true, message: "净重不能为空", trigger: "blur" }
],
},
buttonLoading: false,
loading: false,
// 只读模式
readonly: false,
// 待合卷列表(其他待操作的合卷任务)
pendingMergeList: [],
pendingLoading: false,
// 待操作ID
actionId: null,
currentAction: {},
// 异常信息
abnormals: [],
// 异常表单弹窗
abnormalDialogVisible: false,
// 当前编辑的异常索引
currentAbnormalIndex: -1,
// 异常表单数据
abnormalForm: {
coilId: null,
position: null,
startPosition: 0,
endPosition: 0,
length: 0,
defectCode: null,
degree: null,
remark: null
}
};
},
computed: {
// 动态显示标签
getItemLabel() {
if (this.targetCoil.materialType === '成品') {
return '产品类型';
} else if (this.targetCoil.materialType === '原料') {
return '原料类型';
}
return '物品';
},
// 动态显示占位符
getItemPlaceholder() {
if (this.targetCoil.materialType === '成品') {
return '请选择产品类型';
} else if (this.targetCoil.materialType === '原料') {
return '请选择原料类型';
}
return '请先选择材料类型';
},
renderPendingMergeList() {
// 从pendingMergeList中筛选出不好含在sourceCoils中的,通过enterCoilNo
if (this.pendingMergeList.length > 0) {
return this.pendingMergeList.filter(pending => !this.sourceCoils.some(source => source.enterCoilNo === pending.enterCoilNo))
}
return [];
}
},
watch: {
// 不再需要watch直接用@change事件处理
},
async created() {
// 不再一次性加载所有数据,改为实时搜索
// await this.loadAllItems();
// 从路由参数获取coilId、actionId和readonly
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
const readonly = this.$route.query.readonly;
const actionTypeCode = this.$route.query.actionTypeCode;
// 保存当前页面的待操作ID
if (actionId) {
this.actionId = actionId;
}
// 获取待操作详情
getPendingAction(actionId).then(res => {
// 填写生产开始时间
this.currentAction = res.data
this.$set(this.targetCoil, 'productionStartTime', res.data.createTime)
})
// 保存当前页面的待操作类型
if (actionTypeCode) {
this.actionTypeCode = actionTypeCode;
}
// 设置只读模式
if (readonly === 'true' || readonly === true) {
this.readonly = true;
}
// 如果有coilId加载该钢卷作为第一个源卷
if (coilId) {
await this.loadFirstCoil(coilId, actionId);
// 加载其他待合卷的钢卷列表
await this.loadPendingMergeList();
} else {
// 没有coilId初始化空的源卷
this.sourceCoils = [
{
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
itemType: null,
itemId: null,
warehouseName: '',
actualWarehouseName: '',
specification: '',
grossWeight: undefined,
netWeight: undefined,
length: undefined,
bomItems: []
},
{
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
itemType: null,
itemId: null,
warehouseName: '',
actualWarehouseName: '',
specification: '',
grossWeight: undefined,
netWeight: undefined,
length: undefined,
bomItems: []
}
];
}
},
methods: {
// 处理材料类型变化
handleMaterialTypeChange(value) {
// 清空物品选择
this.$set(this.targetCoil, 'itemId', null);
// 根据材料类型设置物品类型
if (value === '成品') {
this.$set(this.targetCoil, 'itemType', 'product');
} else if (value === '原料') {
this.$set(this.targetCoil, 'itemType', 'raw_material');
}
this.$set(this.targetCoil, 'itemId', undefined);
},
// 加载第一个钢卷(从待操作进入时)
async loadFirstCoil(coilId, actionId) {
try {
this.loading = true;
const response = await getMaterialCoil(coilId);
const data = response.data;
// 初始化源卷列表,第一个是当前钢卷
this.sourceCoils = [
{
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
itemType: data.itemType,
itemId: data.itemId,
itemName: data.itemName || '',
warehouseName: data.warehouseName || (data.warehouse ? data.warehouse.warehouseName : ''),
actualWarehouseName: data.actualWarehouseName || (data.actualWarehouse ? data.actualWarehouse.warehouseName : ''),
specification: data?.specification || '',
bomItems: data.bomItemList || [],
grossWeight: data.grossWeight || null,
netWeight: data.netWeight || null,
length: data.length || null,
actionId: actionId // 保存待操作ID用于后续完成操作
},
{
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
itemType: null,
itemId: null,
warehouseName: '',
actualWarehouseName: '',
specification: '',
bomItems: [],
grossWeight: null,
netWeight: null,
length: null,
}
];
// 自动填充目标卷信息
this.targetCoil.itemType = data.itemType;
this.targetCoil.itemId = data.itemId;
this.targetCoil.warehouseId = data.warehouseId;
} catch (error) {
this.$message.error('加载钢卷信息失败');
console.error(error);
} finally {
this.loading = false;
}
},
// 加载待合卷列表(查询待处理和处理中的记录)
async loadPendingMergeList() {
try {
this.pendingLoading = true;
// 分别查询待处理和处理中的记录
const responses = await listPendingAction({
actionType: this.actionTypeCode, // 合卷操作
actionStatus: '-1', // -1表示只查询不为2的记录
pageNum: 1,
pageSize: 1000
});
// 排除当前钢卷
const currentCoilId = this.sourceCoils[0] ? this.sourceCoils[0].coilId : null;
this.pendingMergeList = responses.rows.filter(item => item.coilId !== currentCoilId);
} catch (error) {
console.error('加载待合卷列表失败', error);
} finally {
this.pendingLoading = false;
}
},
// 选择待操作中的钢卷
async selectPendingCoil(pending, index) {
try {
this.loading = true;
const response = await getMaterialCoil(pending.coilId);
const data = response.data;
this.$set(this.sourceCoils, index, {
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
itemType: data.itemType,
itemId: data.itemId,
itemName: data.itemName || '',
warehouseName: data.warehouseName || (data.warehouse ? data.warehouse.warehouseName : ''),
actualWarehouseName: data.actualWarehouseName || (data.actualWarehouse ? data.actualWarehouse.warehouseName : ''),
specification: data.specification || '',
grossWeight: data.grossWeight || null,
netWeight: data.netWeight || null,
length: data.length || null,
actionId: pending.actionId // 保存待操作ID用于后续完成操作
});
this.$message.success('已添加到合卷列表');
} catch (error) {
this.$message.error('加载钢卷信息失败');
console.error(error);
} finally {
this.loading = false;
}
},
// 格式化时间
formatTime(time) {
if (!time) return '';
const date = new Date(time);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return `${month}-${day} ${hour}:${minute}`;
},
// 格式化物品名称(添加规格和参数信息)
formatItemName(item) {
if (!item) return '';
// 获取名称(原材料或产品)
const name = item.itemName || '';
if (!name) return '';
let displayName = name;
const specs = [];
// 1. 优先显示规格从对象的specification字段
if (item.specification) {
specs.push(item.specification);
}
// 2. 添加参数参数最多2个
if (item.bomItems && item.bomItems.length > 0) {
const bomParams = item.bomItems
.filter(bomItem => bomItem.attrKey && bomItem.attrValue)
.slice(0, 2); // 最多2个参数参数
bomParams.forEach(param => {
specs.push(`${param.attrKey}:${param.attrValue}`);
});
}
// 3. 拼接成最终格式
if (specs.length > 0) {
displayName += `${specs.join(' ')}`;
}
return displayName;
},
// 添加源卷
addSourceCoil() {
this.sourceCoils.push({
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
itemType: null,
itemId: null,
warehouseName: '',
actualWarehouseName: '',
itemName: '',
specification: '',
bomItems: []
});
},
// 删除源卷
removeSourceCoil(index) {
if (this.sourceCoils.length > 1) {
this.sourceCoils.splice(index, 1);
}
},
// 保存合卷
async handleSave() {
// 验证源卷信息
for (let i = 0; i < this.sourceCoils.length; i++) {
const item = this.sourceCoils[i];
if (!item.coilId) {
this.$message.error(`${i + 1}个源卷未选择`);
return;
}
}
// 验证目标卷卷号
if (!this.targetCoil.currentCoilNo || this.targetCoil.currentCoilNo.trim() === '') {
this.$message.error('请输入目标卷卷号');
return;
}
// 验证班组
if (!this.targetCoil.team || this.targetCoil.team.trim() === '') {
this.$message.error('请输入班组名称');
return;
}
let loadingInstance = null;
try {
this.loading = true;
// 构造合卷数据
// 入场钢卷号:所有源卷的入场钢卷号拼接(逗号分隔)
const enterCoilNos = this.sourceCoils
.map(item => item.enterCoilNo)
.filter(no => no) // 过滤空值
.join(',');
const mergeData = {
...this.targetCoil,
enterCoilNo: enterCoilNos, // 拼接的入场钢卷号
hasMergeSplit: 2, // 2表示合卷
abnormals: this.abnormals,
newCoils: this.sourceCoils.map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
currentCoilNo: item.currentCoilNo,
materialType: item.materialType,
actionId: item.actionId, // 保存待操作ID用于后续完成操作
actionType: this.actionTypeCode, // 合卷操作类型
}))
};
loadingInstance = this.$loading({
lock: true,
text: '正在合卷,请稍后...',
background: 'rgba(0, 0, 0, 0.7)'
});
const response = await mergeMaterialCoil(mergeData);
const coilId = response.data;
if (this.targetCoil.contractId) {
addCoilContractRel({
coilId: coilId,
contractId: this.targetCoil.contractId,
})
}
this.$message.success('合卷保存成功');
// 延迟返回,让用户看到成功提示
setTimeout(() => {
this.$router.back();
}, 100);
} catch (error) {
this.$message.error('合卷保存失败');
console.error(error);
} finally {
this.loading = false;
if (loadingInstance) {
loadingInstance.close();
}
}
},
async selectCoil(coil, index) {
console.log(coil, index);
const data = coil;
this.$set(this.sourceCoils, index, {
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
itemType: data.itemType,
itemId: data.itemId,
warehouseName: data.warehouseName || (data.warehouse ? data.warehouse.warehouseName : ''),
actualWarehouseName: data.actualWarehouseName || (data.actualWarehouse ? data.actualWarehouse.warehouseName : ''),
itemName: data.itemName || '',
specification: data.specification || '',
grossWeight: data.grossWeight || null,
netWeight: data.netWeight || null,
length: data.length || null,
bomItems: data.bomItemList || [],
nc: true,
actionId: coil?.actionId // 保存待操作ID用于后续完成操作
});
},
// 取消操作
handleCancel() {
console.log('取消合卷操作', this.$tab);
this.$router.back();
},
// 格式化毫秒值为xx天xx小时xx分钟
formatDuration(milliseconds) {
if (!milliseconds || milliseconds < 0) return '';
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const remainingHours = hours % 24;
const remainingMinutes = minutes % 60;
let result = '';
if (days > 0) result += `${days}`;
if (remainingHours > 0) result += `${remainingHours}小时`;
if (remainingMinutes > 0) result += `${remainingMinutes}分钟`;
return result || '0分钟';
},
// 计算生产耗时
calculateProductionDuration() {
const { productionStartTime, productionEndTime } = this.targetCoil;
if (productionStartTime && productionEndTime) {
const start = new Date(productionStartTime).getTime();
const end = new Date(productionEndTime).getTime();
if (end < start) {
this.$message({
message: '结束时间不能早于开始时间',
type: 'error',
});
this.$set(this.targetCoil, 'productionDuration', '');
this.$set(this.targetCoil, 'formattedDuration', '');
} else {
const durationMs = end - start;
const durationMinutes = Math.round(durationMs / (1000 * 60));
this.$set(this.targetCoil, 'productionDuration', durationMinutes);
this.$set(this.targetCoil, 'formattedDuration', this.formatDuration(durationMinutes * 60 * 1000));
}
} else {
this.$set(this.targetCoil, 'productionDuration', '');
this.$set(this.targetCoil, 'formattedDuration', '');
}
},
// closePage 关闭当前页面
closePage() {
this.$router.back();
},
// 新增异常
addAbnormal() {
this.currentAbnormalIndex = -1;
this.abnormalForm = {
coilId: null,
position: null,
startPosition: 0,
endPosition: 0,
length: 0,
defectCode: null,
degree: null,
remark: null
};
this.abnormalDialogVisible = true;
},
// 编辑异常
editAbnormal(index) {
this.currentAbnormalIndex = index;
this.abnormalForm = { ...this.abnormals[index] };
this.abnormalDialogVisible = true;
},
// 保存异常
saveAbnormal() {
this.$refs.abnormalForm.validate(valid => {
if (valid) {
// 计算缺陷长度
this.abnormalForm.length = this.abnormalForm.endPosition - this.abnormalForm.startPosition;
if (this.currentAbnormalIndex === -1) {
// 新增异常
this.abnormals.push({ ...this.abnormalForm });
} else {
// 编辑异常
this.abnormals[this.currentAbnormalIndex] = { ...this.abnormalForm };
}
this.abnormalDialogVisible = false;
}
});
},
// 删除异常
deleteAbnormal(index) {
this.$confirm('确定要删除这个异常信息吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.abnormals.splice(index, 1);
});
},
// 获取异常位置文本
getAbnormalPositionText(position) {
if (!position) return '';
const dict = this.dict.type.coil_abnormal_position;
if (!dict) return position;
const item = dict.find(item => item.value === position);
return item ? item.label : position;
},
// 获取异常代码文本
getAbnormalCodeText(code) {
if (!code) return '';
const dict = this.dict.type.coil_abnormal_code;
if (!dict) return code;
const item = dict.find(item => item.value === code);
return item ? item.label : code;
}
}
};
</script>
<style scoped lang="scss">
.merge-coil-container {
padding: 20px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
/* 顶部操作栏 */
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 16px 20px;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header-title {
font-size: 18px;
font-weight: 500;
color: #303133;
display: flex;
align-items: center;
gap: 8px;
i {
color: #0066cc;
font-size: 20px;
}
}
/* 流程图容器 */
.flow-container {
display: flex;
gap: 20px;
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 600px;
}
.flow-left {
flex: 1;
min-width: 300px;
}
.flow-middle {
flex: 0 0 80px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.flow-right {
flex: 1;
min-width: 350px;
}
.flow-section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
padding-left: 12px;
border-left: 4px solid #0066cc;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 源卷列表 */
.source-list {
max-height: 500px;
overflow-y: auto;
padding-right: 10px;
margin-bottom: 20px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
.source-coil-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 10px;
transition: all 0.3s;
overflow: hidden;
&:hover {
border-color: #0066cc;
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.15);
}
}
.source-coil-header {
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid #f5f7fa;
}
.source-number {
width: 24px;
height: 24px;
border-radius: 50%;
background: #0066cc;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.source-info {
flex: 1;
min-width: 0;
}
.source-id {
font-size: 14px;
font-weight: 500;
color: #303133;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.source-coil-body {
padding: 10px 14px;
&:empty {
text-align: center;
}
.empty-tip {
text-align: center;
color: #909399;
font-size: 12px;
padding: 8px 0;
i {
display: block;
font-size: 20px;
margin-bottom: 4px;
}
}
}
/* 待合卷列表 */
.pending-list-title {
font-size: 13px;
font-weight: 500;
color: #606266;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #e4e7ed;
}
.pending-coil-list {
max-height: 300px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 2px;
}
}
.pending-coil-item {
padding: 10px;
margin-bottom: 8px;
background: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #ecf5ff;
border-color: #0066cc;
}
.pending-coil-no {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.pending-coil-info {
font-size: 12px;
color: #909399;
.pending-label {
margin-right: 4px;
}
}
}
.source-detail-row {
display: flex;
align-items: flex-start;
margin-bottom: 6px;
font-size: 12px;
&:last-child {
margin-bottom: 0;
}
.detail-label {
color: #909399;
min-width: 80px;
flex-shrink: 0;
}
.detail-value {
color: #303133;
flex: 1;
word-break: break-all;
}
}
.source-detail-divider {
margin: 8px 0 6px;
padding: 4px 0;
font-size: 11px;
font-weight: 500;
color: #606266;
border-bottom: 1px solid #e4e7ed;
}
.source-bom-params {
margin-top: 6px;
}
.source-bom-item {
display: flex;
align-items: center;
margin-bottom: 4px;
font-size: 11px;
.bom-key {
color: #909399;
min-width: 50px;
}
.bom-value {
color: #303133;
flex: 1;
}
}
.btn-remove {
color: #f56c6c;
padding: 0;
&:hover {
color: #f56c6c;
}
}
/* 流程箭头 */
.merge-arrow-container {
width: 100%;
height: 100%;
}
.flow-label {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: #ecf5ff;
color: #0066cc;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
border: 1px solid #d9ecff;
}
/* 目标卷卡片 */
.target-coil-card {
background: #fff;
border: 2px solid #0066cc;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.15);
margin-bottom: 20px;
}
.target-coil-header {
background: #0066cc;
color: #fff;
padding: 16px 20px;
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 600;
}
.target-coil-body {
padding: 20px;
}
/* 双列表单布局 */
.form-row {
display: flex;
gap: 16px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.form-item-half {
flex: 1;
min-width: 0;
}
/* 响应式布局 */
@media screen and (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 12px;
}
.form-item-half {
width: 100%;
}
}
/* 规则说明卡片 */
.rule-card {
background: #fff9f0;
border: 1px solid #ffe9c2;
border-radius: 8px;
padding: 16px;
}
.rule-title {
font-size: 14px;
font-weight: 500;
color: #e6a23c;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.rule-list {
padding-left: 20px;
margin: 0;
li {
color: #606266;
font-size: 13px;
line-height: 24px;
}
}
// 优化按钮文字颜色
// 实心primary按钮白色文字
::v-deep .el-button--primary.el-button--small:not(.is-plain) {
color: #fff;
}
// plain按钮和text按钮蓝色文字
::v-deep .el-button--primary.el-button--small.is-plain,
::v-deep .el-button--text {
color: #409eff;
&:hover {
color: #66b1ff;
}
}
// 修复数字输入框的上下箭头溢出
.target-coil-body {
::v-deep input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
}
}
.abnormal-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 5px;
}
.abnormal-item {
width: 120px;
height: 80px;
background-color: #fff1f0;
border: 1px solid #ff4d4f;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
&:hover {
box-shadow: 0 2px 8px rgba(255, 77, 79, 0.2);
transform: translateY(-2px);
}
.abnormal-content {
padding: 8px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.abnormal-info {
flex: 1;
}
.abnormal-position {
font-size: 12px;
font-weight: 500;
color: #ff4d4f;
margin-bottom: 4px;
}
.abnormal-code {
font-size: 11px;
color: #666;
line-height: 1.3;
}
.abnormal-delete {
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #fff;
}
}
.abnormal-add {
width: 120px;
height: 80px;
border: 2px dashed #ff4d4f;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: #ff4d4f;
font-size: 24px;
&:hover {
background-color: #fff1f0;
transform: translateY(-2px);
}
}
</style>