Files
klp-oa/klp-ui/src/views/wms/coil/split.vue
Joshi ee376f922f feat(aps): 新增排产单明细合并功能及优化界面展示
- 在排产单明细表格中添加多选合并功能
- 实现排产单明细合并对话框及合并逻辑
- 优化排产单明细表格列配置和表单布局
- 添加合并校验和接收产需单API接口
- 重构订单绑定解绑逻辑提升用户体验
- 添加ScheduleDetailCoilBind组件引入
2026-06-29 16:07:29 +08:00

1461 lines
47 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="split-coil-container">
<!-- 顶部操作栏 -->
<div class="header-bar">
<div class="header-title">
<i class="el-icon-s-operation"></i>
<span>钢卷分条</span>
</div>
<div class="header-actions">
<el-button v-if="!readonly" type="primary" size="small" @click="handleSave" :loading="loading">保存分条</el-button>
<el-button size="small" @click="handleCancel" :disabled="loading">{{ readonly ? '返回' : '取消' }}</el-button>
</div>
</div>
<!-- 最近排产单 -->
<div class="plan-sheet-container">
<div class="plan-sheet-section">
<PlanSheetViewer :line-name="planSheetLineName" />
</div>
</div>
<!-- 流程图区域 -->
<div class="flow-container">
<!-- 左侧母卷信息 + 排产信息 -->
<div class="flow-left">
<div class="flow-section-title">
<span>母卷信息</span>
<schedule-detail-coil-bind />
</div>
<div class="coil-card mother-coil">
<div class="coil-header">
<i class="el-icon-s-grid"></i>
<span class="coil-title">钢卷信息</span>
</div>
<div class="coil-body two-col">
<div class="coil-info-row">
<span class="label">入场钢卷号</span>
<span class="value">{{ motherCoil.enterCoilNo || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">当前钢卷号</span>
<span class="value">{{ motherCoil.currentCoilNo || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">当前库区</span>
<span class="value">{{ motherCoil.warehouseName || '未分配' }}</span>
</div>
<div class="coil-info-row">
<span class="label">班组</span>
<span class="value">{{ motherCoil.team || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">毛重</span>
<span class="value">{{ motherCoil.grossWeight ? motherCoil.grossWeight + ' t' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">净重</span>
<span class="value">{{ motherCoil.netWeight ? motherCoil.netWeight + ' t' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">长度</span>
<span class="value">{{ motherCoil.length ? motherCoil.length + ' m' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">调制度</span>
<span class="value">{{ motherCoil.temperGrade || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">镀层种类</span>
<span class="value">{{ motherCoil.coatingType || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">钢卷表面处理</span>
<span class="value">{{ motherCoil.coilSurfaceTreatment || '—' }}</span>
</div>
<div class="coil-info-row" v-if="motherCoil.itemName || motherCoil.productName">
<span class="label">物料名称</span>
<span class="value">{{ motherCoil.itemName || motherCoil.productName || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">规格</span>
<span class="value">{{ motherCoil.itemSpecification || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">材质</span>
<span class="value">{{ motherCoil.itemMaterial || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">厂家</span>
<span class="value">{{ motherCoil.itemManufacturer || '—' }}</span>
</div>
</div>
</div>
</div>
<!-- 右侧子卷列表 -->
<div class="flow-right">
<div class="flow-section-title">
<span>子卷列表 {{ splitList.length }} </span>
<div>
<el-button v-if="!readonly" type="text" size="mini" @click="copyToAllSubCoils"
icon="el-icon-document-copy">复制到全部</el-button>
<el-button v-if="!readonly" type="text" size="mini" @click="addSplitItem"
icon="el-icon-plus">添加子卷</el-button>
</div>
</div>
<div class="split-list">
<div class="sub-coil-card" v-for="(item, index) in splitList" :key="index">
<div class="sub-coil-header">
<span class="sub-coil-number">{{ index + 1 }}</span>
<el-button v-if="!readonly" type="text" size="mini" icon="el-icon-delete" @click="removeSplitItem(index)"
class="btn-remove"></el-button>
</div>
<div class="sub-coil-body">
<el-form size="small" label-width="80px">
<div class="form-row">
<el-form-item label="卷号" required class="form-item-half">
<el-input v-model.trim="item.currentCoilNo" placeholder="输入子卷卷号" :disabled="readonly"></el-input>
<current-coil-no :current-coil-no="item.currentCoilNo" />
</el-form-item>
<el-form-item label="班组" required class="form-item-half">
<el-select v-model="item.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="材料类型" required class="form-item-half">
<el-select v-model="item.materialType" placeholder="请选择" style="width: 100%" :disabled="readonly"
@change="handleMaterialTypeChange(item, index)">
<el-option label="原料" value="原料" />
<el-option label="成品" value="成品" />
</el-select>
</el-form-item>
<el-form-item :label="getItemLabel(item.materialType)" class="form-item-half">
<raw-material-select v-if="item.materialType === '原料'" v-model="item.itemId" placeholder="请选择原料"
style="width: 100%" clearable :disabled="readonly || !item.materialType" />
<product-select v-else-if="item.materialType === '成品'" v-model="item.itemId" placeholder="请选择成品"
style="width: 100%" clearable :disabled="readonly || !item.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="item.qualityStatus" placeholder="请选择" style="width: 100%" :disabled="readonly">
<el-option v-for="opt in dict.type.coil_quality_status" :key="opt.value" :label="opt.label"
:value="opt.value" />
</el-select>
</el-form-item>
<el-form-item label="切边要求" prop="trimmingRequirement" class="form-item-half">
<el-select v-model="item.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="item.packingStatus" placeholder="请输入原料材质" :disabled="readonly" />
</el-form-item>
<el-form-item label="包装要求" prop="packagingRequirement" class="form-item-half">
<el-select v-model="item.packagingRequirement" placeholder="请选择" style="width: 100%"
:disabled="readonly">
<el-option label="裸包" value="裸包" />
<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)" required class="form-item-half">
<el-input-number :precision="3" :controls="false" v-model="item.grossWeight" placeholder="请输入毛重"
:step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="净重(t)" required class="form-item-half">
<el-input-number :precision="3" :controls="false" v-model="item.netWeight" placeholder="请输入净重"
: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="actualLength" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualLength" placeholder="实测长度" :step="0.01"
:disabled="readonly">
<template slot="append">m</template>
</el-input-number>
</el-form-item>
<el-form-item label="排产厚度(mm)" prop="scheduleThickness">
<el-input-number :controls="false" v-model="item.scheduleThickness" placeholder="请输入排产厚度"
type="number" :step="0.001">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="实测厚度(mm)" prop="actualThickness" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualThickness" placeholder="实测厚度" :step="0.01"
:disabled="readonly">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
<el-form-item label="实测宽度(mm)" prop="actualWidth" class="form-item-half">
<el-input-number :controls="false" v-model="item.actualWidth" placeholder="实测宽度" :step="0.01"
:disabled="readonly">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="业务目的" prop="businessPurpose" class="form-item-half">
<el-select v-model="item.businessPurpose" placeholder="请选择" style="width: 100%">
<el-option v-for="opt in dict.type.coil_business_purpose" :key="opt.value" :value="opt.value"
:label="opt.label" />
</el-select>
</el-form-item>
<el-form-item label="调制度" prop="temperGrade" class="form-item-half">
<el-input v-model="item.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="item.coatingType" placeholder="请输入镀层种类" />
</el-form-item>
<el-form-item label="钢卷表面处理" prop="coilSurfaceTreatment" class="form-item-half">
<MemoInput storageKey="surfaceTreatmentDesc" v-model="item.coilSurfaceTreatment"
placeholder="请输入" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="逻辑库区" required class="form-item-half">
<WarehouseSelect v-model="item.warehouseId" placeholder="请选择逻辑库区" :disabled="readonly" />
</el-form-item>
<el-form-item label="关联合同" prop="contractId" class="form-item-half">
<ContractSelect v-model="item.contractId" placeholder="请选择合同" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="生产开始时间" class="form-item-half">
<TimeInput v-model="item.productionStartTime" @input="() => calculateProductionDuration(item)"
:disabled="readonly" />
</el-form-item>
<el-form-item label="生产结束时间" class="form-item-half">
<TimeInput v-model="item.productionEndTime" @input="() => calculateProductionDuration(item)"
:disabled="readonly" :show-now-button="true" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="生产耗时" class="form-item-half">
<el-input v-model="item.formattedDuration" placeholder="自动计算" disabled />
</el-form-item>
<el-form-item label="备注" class="form-item-half">
<el-input v-model="item.remark" placeholder="请输入备注" :disabled="readonly" />
</el-form-item>
</div>
<el-form-item label="异常信息" class="form-item-full">
<div style="display: flex; align-items: center; gap: 8px;">
<div class="abnormal-container">
<div v-for="(abnormal, abnormalIndex) in item.abnormals" :key="abnormalIndex"
:class="['abnormal-item', { inherited: abnormal._inherited }]"
@click="editAbnormal(index, abnormalIndex)">
<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 v-if="abnormal._inherited" class="abnormal-inherit-tip">继承 · {{ abnormal.processSource
}}</div>
</div>
<el-button type="danger" size="mini" icon="el-icon-close" class="abnormal-delete"
@click.stop="deleteAbnormal(index, abnormalIndex)"></el-button>
</div>
</div>
<div class="abnormal-add" @click="addAbnormal(index)">
<i class="el-icon-plus"></i>
</div>
</div>
<el-button v-if="!readonly" type="text" size="mini" icon="el-icon-download"
style="color: #409eff; white-space: nowrap;" @click="handleInheritAbnormal(index)">
继承异常
</el-button>
</div>
</el-form-item>
</el-form>
</div>
</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>
<!-- 异常继承弹窗 -->
<el-dialog title="异常继承" :visible.sync="inheritDialogVisible" width="1200px" append-to-body top="5vh"
:close-on-click-modal="false">
<div v-loading="inheritLoading">
<template v-if="parentCoils.length > 0">
<el-alert title="以下为母卷的异常记录,请选择要继承的异常" type="info" :closable="false" show-icon style="margin-bottom: 16px;" />
<div v-for="(parent, pIdx) in parentCoils" :key="parent.coilId" class="parent-coil-section">
<div class="parent-header">
<span class="parent-title">母卷 #{{ pIdx + 1 }}{{ parent.currentCoilNo || parent.coilId }}</span>
</div>
<el-table :data="parent.abnormalList" border stripe size="small" style="width: 100%">
<el-table-column width="50">
<template slot="header">
<el-checkbox :indeterminate="parent.isIndeterminate" :value="parent.checkedAll"
@change="val => handleParentSelectAll(parent, val)" />
</template>
<template slot-scope="scope">
<el-checkbox v-model="scope.row._selected" @change="() => recalcParentCheckState(parent)" />
</template>
</el-table-column>
<el-table-column label="缺陷描述" prop="remark" show-overflow-tooltip />
<el-table-column label="开始位置" prop="startPosition" width="80" />
<el-table-column label="结束位置" prop="endPosition" width="80" />
<el-table-column label="长度" width="70">
<template slot-scope="scope">{{ scope.row.endPosition - scope.row.startPosition }}</template>
</el-table-column>
<el-table-column label="上下版面" prop="plateSurface" width="100" />
<el-table-column label="断面位置" prop="position" width="160" />
<el-table-column label="缺陷代码" prop="defectCode" width="80" />
<el-table-column label="程度" prop="degree" width="60" />
<el-table-column label="主缺陷" width="60">
<template slot-scope="scope">{{ scope.row.mainMark === 1 ? '是' : '否' }}</template>
</el-table-column>
</el-table>
</div>
</template>
<el-empty v-else description="未找到母卷的异常记录" />
</div>
<span slot="footer">
<el-button @click="inheritDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="inheritButtonLoading" @click="confirmInherit"
:disabled="selectedInheritCount === 0">
确认继承 ({{ selectedInheritCount }})
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getMaterialCoil, splitMaterialCoil, getFirstHeatCoilMaterial } from '@/api/wms/coil';
import { listCoilContractRel } from '@/api/wms/coilContractRel';
import { listCoilAbnormal } from '@/api/wms/coilAbnormal';
import { listWarehouse } from '@/api/wms/warehouse';
import { getPendingAction } from '@/api/wms/pendingAction';
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
import RawMaterialSelect from "@/components/KLPService/RawMaterialSelect";
import ProductSelect from "@/components/KLPService/ProductSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import TimeInput from "@/components/TimeInput";
import AbnormalForm from './components/AbnormalForm';
import PlanSheetViewer from './components/PlanSheetViewer.vue';
import ScheduleDetailCoilBind from './components/ScheduleDetailCoilBind'
import { generateCoilNoPrefix } from "@/utils/coil/coilNo";
import ContractSelect from "@/components/KLPService/ContractSelect";
export default {
name: 'SplitCoil',
components: {
ActualWarehouseSelect,
RawMaterialSelect,
ProductSelect,
WarehouseSelect,
TimeInput,
AbnormalForm,
ContractSelect,
PlanSheetViewer,
ScheduleDetailCoilBind,
},
dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'],
data() {
const currentCoilNoPrefix = generateCoilNoPrefix()
return {
currentCoilNoPrefix,
// 母卷信息
motherCoil: {
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
team: '',
warehouseId: null,
warehouseName: '',
itemType: null,
itemId: null,
materialName: '',
productName: '',
grossWeight: null,
netWeight: null,
itemName: null,
itemManufacturer: null,
itemMaterial: null,
itemSpecification: null,
},
// 子卷列表
splitList: [
{
currentCoilNo: currentCoilNoPrefix,
team: '',
materialType: null,
itemType: null,
itemId: null,
grossWeight: undefined,
netWeight: undefined,
warehouseId: null,
actualWarehouseId: null,
length: undefined,
temperGrade: '',
coatingType: '',
actualLength: undefined,
actualWidth: undefined,
productionStartTime: '',
productionEndTime: '',
productionDuration: '',
formattedDuration: '',
abnormals: []
}
],
loading: false,
// 钢卷选择器可见性
coilSelectorVisible: false,
// 库区列表
warehouseList: [],
// 原材料和产品列表(实时搜索,不再保存完整备份)
rawMaterialList: [],
productList: [],
itemSearchLoading: false,
// 只读模式
readonly: false,
// 待操作ID
actionId: null,
currentAction: {},
// 异常表单弹窗
abnormalDialogVisible: false,
// 当前编辑的子卷索引
currentSubCoilIndex: -1,
// 当前编辑的异常索引
currentAbnormalIndex: -1,
// 异常表单数据
abnormalForm: {
coilId: null,
position: null,
startPosition: 0,
endPosition: 0,
length: 0,
defectCode: null,
degree: null,
remark: null
},
// 最早热轧卷板材质
firstHeatMaterial: null,
// 异常继承
inheritDialogVisible: false,
inheritLoading: false,
inheritButtonLoading: false,
parentCoils: [],
currentInheritSubCoilIndex: -1,
defaultContractId: '',
};
},
computed: {
planSheetLineName() {
return '酸轧线'
},
todayDateStr() {
const d = new Date()
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
},
selectedInheritCount() {
let count = 0
for (const parent of this.parentCoils) {
for (const row of parent.abnormalList) {
if (row._selected) count++
}
}
return count
},
},
async created() {
// 先加载库区列表
await this.loadWarehouses();
// 加载母卷信息
// 从路由参数获取coilId、actionId和readonly
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
const readonly = this.$route.query.readonly;
// 获取待操作详情
getPendingAction(actionId).then(res => {
// 填写生产开始时间
this.currentAction = res.data
this.$set(this.splitList[0], 'productionStartTime', res.data.createTime)
})
if (coilId) {
await this.loadMotherCoil(coilId);
}
// 保存待操作ID
if (actionId) {
this.actionId = actionId;
}
// 设置只读模式
if (readonly === 'true' || readonly === true) {
this.readonly = true;
}
},
methods: {
// 处理材料类型变化
handleMaterialTypeChange(item, index) {
// 清空物品选择
this.$set(item, 'itemId', null);
// 根据材料类型设置物品类型
if (item.materialType === '成品') {
this.$set(item, 'itemType', 'product');
// 清空列表,等待用户搜索
this.productList = [];
} else if (item.materialType === '原料') {
this.$set(item, 'itemType', 'raw_material');
// 清空列表,等待用户搜索
this.rawMaterialList = [];
}
},
// 动态获取标签
getItemLabel(materialType) {
if (materialType === '成品') {
return '产品类型';
} else if (materialType === '原料') {
return '原料类型';
}
return '物品';
},
// 动态获取占位符
getItemPlaceholder(materialType) {
if (materialType === '成品') {
return '请选择产品类型';
} else if (materialType === '原料') {
return '请选择原料类型';
}
return '请先选择材料类型';
},
// 格式化物品名称(添加规格和参数信息)
formatItemName(item) {
if (!item) return '';
// 获取名称(原材料或产品)
const name = item.rawMaterialName || item.productName || '';
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;
},
// 加载母卷信息
async loadMotherCoil(coilId) {
try {
this.loading = true;
const response = await getMaterialCoil(coilId);
if (response.code === 200 && response.data) {
const data = response.data;
this.motherCoil = {
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
team: data.team || '',
warehouseId: data.warehouseId,
warehouseName: data.warehouseName || (data.warehouse ? data.warehouse.warehouseName : ''),
itemType: data.itemType,
itemId: data.itemId,
materialType: data.materialType,
itemName: data.itemName || '',
grossWeight: data.grossWeight,
netWeight: data.netWeight,
length: data.length,
itemManufacturer: data?.manufacturer || '',
itemMaterial: data?.material || '',
itemSpecification: data?.specification || '',
};
// 获取最早的热轧卷板材质
try {
const firstHeatMaterial = await getFirstHeatCoilMaterial(this.motherCoil.enterCoilNo);
if (firstHeatMaterial.code === 200 && firstHeatMaterial.msg) {
this.firstHeatMaterial = firstHeatMaterial.msg
}
for (const item of this.splitList) {
item.packingStatus = this.firstHeatMaterial || ''
}
} catch (error) {
// console.error('获取最早的热轧卷板材质失败', error);
}
// 获取母卷绑定的合同号作为子卷默认值
listCoilContractRel({ coilId }).then(res => {
const rows = res.rows || []
if (rows.length > 0 && rows[0].contractId) {
this.defaultContractId = rows[0].contractId
for (const item of this.splitList) {
if (!item.contractId) {
this.$set(item, 'contractId', this.defaultContractId)
}
}
}
})
}
} catch (error) {
this.$message.error('加载母卷信息失败');
console.error(error);
} finally {
this.loading = false;
}
},
// 加载库区列表
async loadWarehouses() {
try {
const response = await listWarehouse({ pageNum: 1, pageSize: 1000 });
if (response.code === 200) {
this.warehouseList = response.rows || response.data || [];
}
} catch (error) {
console.error('加载库区列表失败', error);
}
},
// 添加子卷
addSplitItem() {
this.splitList.push({
currentCoilNo: this.currentCoilNoPrefix,
team: '',
materialType: null,
itemType: null,
itemId: null,
grossWeight: null,
netWeight: null,
length: null,
warehouseId: null,
actualWarehouseId: null,
qualityStatus: '',
packagingRequirement: '',
packingStatus: this.firstHeatMaterial || '',
trimmingRequirement: '',
temperGrade: '',
coatingType: '',
contractId: this.defaultContractId || '',
actualLength: undefined,
actualWidth: undefined,
productionStartTime: this.currentAction.createTime,
productionEndTime: '',
productionDuration: '',
formattedDuration: '',
abnormals: []
});
},
// 删除子卷
removeSplitItem(index) {
if (this.splitList.length > 1) {
this.splitList.splice(index, 1);
} else {
this.$message.warning('至少保留一个子卷');
}
},
// 保存分条
async handleSave() {
// 验证母卷信息
if (!this.motherCoil.coilId) {
this.$message.error('请先选择母卷');
return;
}
// 验证子卷数量
if (this.splitList.length < 1) {
this.$message.error('至少需要一个子卷');
return;
}
// 验证子卷信息
for (let i = 0; i < this.splitList.length; i++) {
const item = this.splitList[i];
if (!item.currentCoilNo || item.currentCoilNo.trim() === '') {
this.$message.error(`${i + 1}个子卷的卷号不能为空`);
return;
}
if (!item.team || item.team.trim() === '') {
this.$message.error(`${i + 1}个子卷的班组不能为空`);
return;
}
if (!item.itemType) {
this.$message.error(`${i + 1}个子卷的物品类型不能为空`);
return;
}
if (!item.itemId) {
this.$message.error(`${i + 1}个子卷的物品不能为空`);
return;
}
if (item.grossWeight === null || item.grossWeight === undefined || item.grossWeight === '') {
this.$message.error(`${i + 1}个子卷的毛重不能为空`);
return;
}
if (item.netWeight === null || item.netWeight === undefined || item.netWeight === '') {
this.$message.error(`${i + 1}个子卷的净重不能为空`);
return;
}
if (!item.warehouseId) {
this.$message.error(`${i + 1}个子卷的目标库区不能为空`);
return;
}
if (!item.contractId) {
this.$message.error(`${i + 1}个子卷的合同不能为空`);
return;
}
}
const loadingInstance = this.$loading({
lock: true,
text: '正在分条,请稍后...',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
this.loading = true;
// 构造分条数据
const splitData = {
coilId: this.motherCoil.coilId,
enterCoilNo: this.motherCoil.enterCoilNo, // 入场钢卷号(必填)
currentCoilNo: this.motherCoil.currentCoilNo,
materialType: this.motherCoil.materialType,
hasMergeSplit: 1, // 1表示分条
newCoils: this.splitList.map(item => ({
...item,
itemType: item.itemType,
itemId: item.itemId,
hasMergeSplit: 1,
contractId: item.contractId,
}))
};
const response = await splitMaterialCoil({ ...splitData, actionId: this.actionId });
if (response.code === 200) {
this.$message.success('分条保存成功');
// 延迟返回,让用户看到成功提示
setTimeout(() => {
this.$router.back();
}, 100);
} else {
this.$message.error(response.msg || '分条保存失败');
}
} catch (error) {
this.$message.error('分条保存失败');
console.error(error);
} finally {
this.loading = false;
loadingInstance.close();
}
},
// 取消操作
handleCancel() {
this.$router.back();
},
// 复制到全部子卷
copyToAllSubCoils() {
if (!this.motherCoil.coilId) {
this.$message.warning('请先加载母卷信息');
return;
}
// 复制到所有子卷
this.splitList.forEach((item, index) => {
// 自动生成卷号:母卷号-1, 母卷号-2, 母卷号-3...
if (!item.currentCoilNo) {
item.currentCoilNo = `${this.motherCoil.currentCoilNo}-${index + 1}`;
}
// 复制班组
if (!item.team) {
item.team = this.motherCoil.team;
}
});
// 不再预加载物品列表,改为实时搜索
this.$message.success('已复制到所有子卷');
},
// 格式化毫秒值为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(item) {
if (!item) return;
const { productionStartTime, productionEndTime } = item;
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(item, 'productionDuration', '');
this.$set(item, 'formattedDuration', '');
} else {
const durationMs = end - start;
const durationMinutes = Math.round(durationMs / (1000 * 60));
this.$set(item, 'productionDuration', durationMinutes);
this.$set(item, 'formattedDuration', this.formatDuration(durationMinutes * 60 * 1000));
}
} else {
this.$set(item, 'productionDuration', '');
this.$set(item, 'formattedDuration', '');
}
},
// 新增异常
addAbnormal(subCoilIndex) {
this.currentSubCoilIndex = subCoilIndex;
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(subCoilIndex, abnormalIndex) {
this.currentSubCoilIndex = subCoilIndex;
this.currentAbnormalIndex = abnormalIndex;
this.abnormalForm = { ...this.splitList[subCoilIndex].abnormals[abnormalIndex] };
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.splitList[this.currentSubCoilIndex].abnormals.push({ ...this.abnormalForm });
} else {
// 编辑异常
this.splitList[this.currentSubCoilIndex].abnormals[this.currentAbnormalIndex] = { ...this.abnormalForm };
}
this.abnormalDialogVisible = false;
}
});
},
// 删除异常
deleteAbnormal(subCoilIndex, abnormalIndex) {
this.$confirm('确定要删除这个异常信息吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.splitList[subCoilIndex].abnormals.splice(abnormalIndex, 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;
},
// 异常继承
handleInheritAbnormal(subCoilIndex) {
const parentId = this.motherCoil.coilId
if (!parentId) {
this.$message.info('母卷信息未加载,无法继承异常')
return
}
this.currentInheritSubCoilIndex = subCoilIndex
this.inheritDialogVisible = true
this.parentCoils = []
this.inheritLoading = true
getMaterialCoil(parentId).then(res => {
const coil = res.data || {}
return listCoilAbnormal({ coilId: parentId, pageSize: 999 }).then(res2 => {
const list = (res2.rows || []).map(item => ({ ...item, _selected: false }))
return { coil, abnormalList: list, checkedAll: false, isIndeterminate: false }
})
}).catch(() => null).then(result => {
this.parentCoils = result ? [result] : []
if (this.parentCoils.length === 0) {
this.$message.info('未找到母卷异常记录')
}
}).finally(() => {
this.inheritLoading = false
})
},
recalcParentCheckState(parent) {
const selected = parent.abnormalList.filter(r => r._selected)
const total = parent.abnormalList.length
parent.checkedAll = selected.length === total && total > 0
parent.isIndeterminate = selected.length > 0 && selected.length < total
},
handleParentSelectAll(parent, val) {
parent.abnormalList.forEach(row => { row._selected = val })
parent.checkedAll = val
parent.isIndeterminate = false
},
confirmInherit() {
const selected = []
for (const parent of this.parentCoils) {
for (const row of parent.abnormalList) {
if (row._selected) {
const end = Number(row.endPosition) || 0
const start = Number(row.startPosition) || 0
selected.push({
position: row.position,
plateSurface: row.plateSurface,
startPosition: start,
endPosition: end,
length: end - start,
defectCode: row.defectCode,
degree: row.degree,
mainMark: row.mainMark,
remark: row.remark,
attachmentFiles: row.attachmentFiles,
productionLine: row.productionLine,
processSource: this.planSheetLineName,
_inherited: true,
parentAbnormalId: row.abnormalId
})
}
}
}
if (selected.length === 0) {
this.$message.info('请选择要继承的异常')
return
}
const subCoil = this.splitList[this.currentInheritSubCoilIndex]
if (subCoil) {
subCoil.abnormals.push(...selected)
}
this.$message.success(`成功继承 ${selected.length} 条异常记录`)
this.inheritDialogVisible = false
},
}
};
</script>
<style scoped lang="scss">
.split-coil-container {
padding: 12px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
/* 顶部操作栏 */
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 10px 16px;
margin-bottom: 12px;
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: 12px;
background: #fff;
padding: 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 500px;
}
.flow-left {
flex: 0 0 320px;
overflow-y: auto;
}
.flow-right {
flex: 1;
min-width: 0;
}
.flow-section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 10px;
padding-left: 10px;
border-left: 4px solid #0066cc;
display: flex;
justify-content: space-between;
align-items: center;
.el-button--text {
font-size: 12px;
}
}
/* 母卷卡片 */
.mother-coil {
border: 2px solid #0066cc;
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.15);
}
.coil-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
}
.coil-header {
background: #0066cc;
color: #fff;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
i {
font-size: 18px;
}
.coil-title {
flex: 1;
font-size: 14px;
font-weight: 600;
}
}
.coil-body {
padding: 12px;
&.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0 12px;
}
}
.coil-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #909399;
font-size: 13px;
min-width: 90px;
flex-shrink: 0;
}
.value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
word-break: break-all;
&.highlight {
color: #0066cc;
font-size: 16px;
font-weight: 600;
}
}
}
/* 参数参数展示 */
.bom-params {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 10px;
}
.param-item {
display: flex;
align-items: center;
font-size: 13px;
.param-name {
color: #909399;
min-width: 80px;
}
.param-value {
color: #303133;
font-weight: 500;
}
}
/* 流程箭头 */
.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;
}
/* 子卷列表 */
.split-list {
max-height: calc(100vh - 280px);
overflow-y: auto;
margin-bottom: 0;
padding-right: 10px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
.sub-coil-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 10px;
transition: all 0.3s;
&:hover {
border-color: #0066cc;
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.15);
}
}
.sub-coil-header {
background: #f5f7fa;
padding: 8px 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e4e7ed;
}
.sub-coil-number {
font-size: 14px;
font-weight: 600;
color: #0066cc;
width: 28px;
height: 28px;
border-radius: 50%;
background: #ecf5ff;
display: flex;
align-items: center;
justify-content: center;
}
.btn-remove {
color: #f56c6c;
padding: 0;
&:hover {
color: #f56c6c;
}
}
.sub-coil-body {
padding: 12px;
}
/* 双列表单布局 */
.form-row {
display: flex;
gap: 10px;
margin-bottom: 0;
&:last-child {
margin-bottom: 0;
}
}
.form-item-half {
flex: 1;
min-width: 0;
}
.form-item-full {
width: 100%;
}
/* 排产单区域 */
.plan-sheet-container {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 12px;
margin-bottom: 12px;
}
.plan-sheet-section {
margin-top: 0;
}
// 减少表单项间距
::v-deep .el-form-item {
margin-bottom: 10px;
}
::v-deep .el-form-item--small .el-form-item__content,
::v-deep .el-form-item--small .el-form-item__label {
line-height: 32px;
}
// 异常信息样式
.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);
}
}
.abnormal-item.inherited {
background-color: #f0f9ff;
border-color: #1890ff;
.abnormal-position {
color: #1890ff;
}
}
.abnormal-inherit-tip {
font-size: 10px;
color: #909399;
line-height: 1.2;
margin-top: 1px;
}
.parent-coil-section {
margin-bottom: 20px;
padding: 12px;
background-color: #fafafa;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.parent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #e4e7ed;
}
.parent-title {
font-size: 14px;
font-weight: 600;
color: #303133;
}
</style>