Files
klp-oa/klp-ui/src/views/wms/coil/panels/stepSplit.vue
砂糖 6b8eac4139 feat(contract): 新增合同打印功能,重构产品金额计算逻辑
1. 新增合同打印预览功能,支持A4格式打印,包含合同完整信息和产品明细
2. 新增多个产品金额计算工具函数,统一管理产品税额、无税单价等字段计算
3. 重构产品内容组件,新增税率除数、无税单价、税额列,实现字段联动自动计算
4. 新增合同logo静态资源,优化表格布局和样式
2026-06-02 08:56:31 +08:00

1381 lines
51 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 v-loading="loading" class="split-coil-container">
<!-- 左右分栏布局 -->
<el-row :gutter="20">
<!-- 左侧钢卷信息 + 新增按钮 + 已分条列表 -->
<el-col :span="12">
<div class="coil-info-card">
<el-row :gutter="20" flex justify="end">
<!-- 新增分条按钮 -->
<el-button
v-if="actionStatus != 2"
type="primary"
icon="el-icon-plus"
:loading="buttonLoading"
@click="addSplitForm"
>
新增分条
</el-button>
<!-- 完成分条按钮 -->
<el-button
v-if="actionStatus != 2"
type="success"
icon="el-icon-check"
:disabled="splitList.length === 0"
:loading="buttonLoading"
@click="completeSplit"
>
完成整体分条
</el-button>
<el-button type="primary" icon="el-icon-refresh" :loading="buttonLoading" @click="refresh">
刷新
</el-button>
</el-row>
<el-descriptions :column="2" border title="待分条钢卷信息">
<template slot="extra">
<el-button
v-if="showSplitForm"
type="info"
icon="el-icon-document-copy"
@click="copyFromSourceCoil"
>复制源卷信息</el-button>
</template>
<el-descriptions-item label="入场钢卷号">{{ coilInfo.enterCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="当前钢卷号">{{ coilInfo.currentCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="厂家原料卷号">{{ coilInfo.supplierCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="所在库位">{{ coilInfo.warehouseName || '-' }}</el-descriptions-item>
<el-descriptions-item label="材料类型">{{ coilInfo.materialType || '-' }}</el-descriptions-item>
<el-descriptions-item label="净重">{{ coilInfo.netWeight || '-' }} T</el-descriptions-item>
<el-descriptions-item label="物料名称">{{ coilInfo.itemName || '-' }}</el-descriptions-item>
<el-descriptions-item label="规格">{{ coilInfo.specification || '-' }}</el-descriptions-item>
<el-descriptions-item label="材质">{{ coilInfo.material || '-' }}</el-descriptions-item>
<el-descriptions-item label="厂家">{{ coilInfo.manufacturer || '-' }}</el-descriptions-item>
<el-descriptions-item label="原料材质">{{ coilInfo.packingStatus || '-' }}</el-descriptions-item>
<el-descriptions-item label="实测长度(m)">{{ coilInfo.actualLength || '-' }}</el-descriptions-item>
<el-descriptions-item label="实测宽度(mm)">{{ coilInfo.actualWidth || '-' }}</el-descriptions-item>
<el-descriptions-item label="实测厚度(mm)">{{ coilInfo.actualThickness || '-' }}</el-descriptions-item>
<el-descriptions-item label="钢卷表面处理">{{ coilInfo.coilSurfaceTreatment || '-' }}</el-descriptions-item>
<el-descriptions-item label="净重">{{ coilInfo.netWeight || '-' }} T</el-descriptions-item>
</el-descriptions>
<!-- 已分条钢卷列表 -->
<el-descriptions :column="1" border title="已分出的钢卷列表" />
<el-table
v-loading="splitListLoading"
:data="splitList"
highlight-current-row
border
stripe
@row-click="handleSplitItemClick"
>
<el-table-column prop="enterCoilNo" label="入场钢卷号" />
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
<el-table-column label="物料类型">
<template #default="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row" />
</template>
</el-table-column>
<el-table-column prop="netWeight" label="净重" width="70" />
<el-table-column prop="dataType" label="钢卷状态" width="80">
<template #default="scope">
<div v-if="scope.row.status == 1">
<el-tag type="info">已发货</el-tag>
</div>
<div v-else-if="scope.row.dataType == 1">
<el-tag type="success">当前在库</el-tag>
</div>
<div v-else-if="scope.row.dataType == 0 || scope.row.dataType == 2">
<el-tag type="warning">历史卷</el-tag>
</div>
<div v-else>
<el-tag type="danger">未知状态</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<div v-if="scope.row.dataType == 1 && scope.row.status == 0">
<el-button @click.stop="handlePrint(scope.row)">打印</el-button>
<el-button @click.stop="handleEditSplit(scope.row)">编辑</el-button>
<el-button @click.stop="handleDeleteSplit(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 今日排产单 -->
<div v-if="planSheetList.length > 0" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" />
<el-tabs v-model="activePlanSheetId" type="card">
<el-tab-pane
v-for="sheet in planSheetList"
:key="sheet.planSheetId"
:name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)"
/>
</el-tabs>
<el-table
v-loading="planSheetLoading"
:data="activePlanSheetDetails"
border
stripe
size="mini"
max-height="400"
>
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="订单信息" header-align="center">
<el-table-column prop="contractCode" label="合同号" width="140" show-overflow-tooltip />
<el-table-column prop="customerName" label="客户" width="120" show-overflow-tooltip />
<el-table-column prop="salesman" label="业务员" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column label="成品信息" header-align="center">
<el-table-column prop="productName" label="成品名称" width="140" show-overflow-tooltip />
<el-table-column prop="productMaterial" label="材质" width="100" />
<el-table-column prop="coatingG" label="镀层g" width="80" />
<el-table-column prop="productWidth" label="成品宽度" width="100" />
<el-table-column prop="rollingThick" label="轧制厚度" width="100" />
<el-table-column prop="markCoatThick" label="标丝厚度" width="100" />
<el-table-column prop="tonSteelLengthRange" label="长度区间(m)" width="120" />
<el-table-column prop="planQty" label="数量" width="80" />
<el-table-column prop="planWeight" label="重量" width="100" />
<el-table-column prop="surfaceTreatment" label="表面处理" width="110" show-overflow-tooltip />
<el-table-column prop="widthReq" label="切边要求" width="110" show-overflow-tooltip />
<el-table-column prop="productPackaging" label="包装要求" width="100" show-overflow-tooltip />
<el-table-column prop="productEdgeReq" label="宽度要求" width="100" show-overflow-tooltip />
<el-table-column prop="usageReq" label="用途" width="100" show-overflow-tooltip />
</el-table-column>
<el-table-column prop="remark" label="备注" width="140" show-overflow-tooltip />
</el-table>
</div>
<!-- 今日排产单空状态 -->
<div v-if="planSheetList.length === 0 && planSheetLineName && !planSheetLoading" class="plan-sheet-section">
<el-descriptions :column="1" border :title="'最近排产单(' + planSheetLineName + ''" />
<el-empty description="今天暂无排产单" :image-size="80" />
</div>
</div>
</el-col>
<!-- 右侧分条表单 / 分条详情 -->
<el-col :span="12">
<div v-if="showSplitForm" class="split-form-card">
<el-card title="分条钢卷信息录入" shadow="hover">
<el-alert v-if="!isExemptFromValidation" type="warning" :closable="false" show-icon style="margin-bottom:12px">
<template slot="title">
子卷的<b>净重</b><b>实测厚度</b>均不能超过父卷完成分条时所有子卷净重总和不能超过父卷净重
</template>
</el-alert>
<el-form ref="splitFormRef" :model="splitForm" :rules="rules" label-width="100px" style="max-width: 800px;">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="splitForm.enterCoilNo" placeholder="请输入入场钢卷号" disabled />
</el-form-item>
<el-form-item v-if="isGrindAction" label="镀铬卷号" prop="chromePlateCoilNo">
<el-input v-model="splitForm.chromePlateCoilNo" placeholder="请输入镀铬卷号" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="splitForm.currentCoilNo" placeholder="请输入当前钢卷号" />
<current-coil-no :current-coil-no="splitForm.currentCoilNo" />
</el-form-item>
<el-form-item label="所在库位" prop="warehouseId">
<warehouse-select
v-model="splitForm.warehouseId"
placeholder="请选择仓库/库区/库位"
style="width: 100%;"
clearable
/>
</el-form-item>
<el-form-item label="班组" prop="team">
<el-select v-model="splitForm.team" placeholder="请选择班组" style="width: 100%">
<el-option key="甲" label="甲" value="甲" />
<el-option key="乙" label="乙" value="乙" />
</el-select>
</el-form-item>
<el-form-item label="材料类型" prop="materialType">
<el-select v-model="splitForm.materialType" placeholder="请选择材料类型" @change="handleMaterialTypeChange">
<el-option label="成品" value="成品" />
<el-option label="原料" value="原料" />
</el-select>
</el-form-item>
<el-form-item :label="getItemLabel" prop="itemId">
<product-select
v-if="splitForm.itemType === 'product'"
v-model="splitForm.itemId"
placeholder="请选择成品"
style="width: 100%;"
clearable
/>
<raw-material-select
v-else-if="splitForm.itemType === 'raw_material'"
v-model="splitForm.itemId"
placeholder="请选择原料"
style="width: 100%;"
clearable
/>
<div v-else>请先选择材料类型</div>
</el-form-item>
<el-form-item label="质量状态" prop="qualityStatus">
<el-select v-model="splitForm.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
<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">
<el-select v-model="splitForm.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
</el-form-item>
<el-form-item label="原料材质" prop="packingStatus">
<el-input v-model="splitForm.packingStatus" placeholder="请输入原料材质" />
</el-form-item>
<el-form-item label="包装要求" prop="packagingRequirement">
<el-select v-model="splitForm.packagingRequirement" placeholder="请选择包装要求" style="width: 100%">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
<el-option label="精包" value="精包" />
</el-select>
</el-form-item>
<el-form-item label="毛重" prop="grossWeight">
<el-input v-model="splitForm.grossWeight" placeholder="请输入毛重" type="number" />
</el-form-item>
<el-form-item label="净重" prop="netWeight">
<el-input v-model="splitForm.netWeight" placeholder="请输入净重" type="number" />
</el-form-item>
<el-form-item label="长度" prop="length">
<el-input v-model="splitForm.length" placeholder="请输入长度" type="number" />
</el-form-item>
<el-form-item label="实测长度(m)" prop="actualLength">
<el-input-number
v-model="splitForm.actualLength"
:controls="false"
placeholder="请输入实测长度"
type="number"
:step="0.01"
/>
</el-form-item>
<el-form-item label="实测厚度(mm)" prop="actualThickness">
<el-input-number
v-model="splitForm.actualThickness"
:controls="false"
placeholder="请输入实测厚度"
type="number"
:step="0.01"
/>
</el-form-item>
<el-form-item label="实测宽度(mm)" prop="actualWidth">
<el-input-number
v-model="splitForm.actualWidth"
:controls="false"
placeholder="请输入实测宽度"
type="number"
:step="0.01"
/>
</el-form-item>
<el-form-item label="业务目的" prop="businessPurpose">
<el-select v-model="splitForm.businessPurpose" placeholder="业务目的" filterable>
<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="temperGrade">
<el-input v-model="splitForm.temperGrade" placeholder="请输入调制度" />
</el-form-item>
<el-form-item label="镀层种类" prop="coatingType">
<MemoInput v-model="splitForm.coatingType" storage-key="coatingType" placeholder="请输入镀层种类" />
</el-form-item>
<el-form-item label="钢卷表面处理" prop="coilSurfaceTreatment">
<MemoInput
v-model="splitForm.coilSurfaceTreatment"
storage-key="surfaceTreatmentDesc"
placeholder="请输入钢卷表面处理"
/>
</el-form-item>
<el-form-item label="生产开始时间" prop="productionStartTime">
<TimeInput v-model="splitForm.productionStartTime" @input="calculateProductionDuration" />
</el-form-item>
<el-form-item label="生产结束时间" prop="productionEndTime">
<TimeInput
v-model="splitForm.productionEndTime"
:show-now-button="true"
@input="calculateProductionDuration"
/>
</el-form-item>
<el-form-item label="生产耗时" prop="productionDuration">
<el-input v-model="splitForm.formattedDuration" placeholder="自动计算" disabled />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="splitForm.remark" placeholder="请输入备注" type="textarea" />
</el-form-item>
<el-form-item label="关联合同" prop="contractId">
<ContractSelect v-model="splitForm.contractId" placeholder="请选择合同" />
</el-form-item>
<el-form-item label="异常信息">
<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)"
/>
</div>
</div>
<div class="abnormal-add" @click="addAbnormal">
<i class="el-icon-plus" />
</div>
</div>
</el-form-item>
<el-form-item>
<el-button :loading="buttonLoading" type="success" plain @click="saveSplitForm">暂存内容</el-button>
<el-button :loading="buttonLoading" type="primary" @click="addSplit">提交分条</el-button>
<el-button :loading="buttonLoading" @click="resetSplitForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 分条钢卷详情选中列表项时显示 -->
<div v-else-if="selectedSplitItem" class="split-detail-card">
<el-card title="分条钢卷详情" shadow="hover">
<CoilInfoRender :coil-info="selectedSplitItem" :column="2" border />
</el-card>
</div>
<!-- 初始提示 -->
<div v-else class="empty-tip">
<el-empty description="请选择左侧已分条钢卷查看详情,或点击「新增分条」创建新分条" />
</div>
</el-col>
</el-row>
<!-- 异常表单弹窗 -->
<el-dialog
:title="currentAbnormalIndex === -1 ? '新增异常' : '编辑异常'"
:visible.sync="abnormalDialogVisible"
width="600px"
append-to-body
>
<abnormal-form ref="abnormalForm" v-model="abnormalForm" :show-coil-selector="false" />
<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="cacheDialogVisible" width="800px" append-to-body>
<div>
<el-alert title="检测到您之前有暂存的分条数据,是否恢复使用?" type="info" show-icon :closable="false" />
<el-divider content-position="left">暂存的表单数据</el-divider>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="入场钢卷号">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.enterCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="当前钢卷号">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.currentCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="所在库位">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.warehouseId || '-' }}</el-descriptions-item>
<el-descriptions-item label="班组">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.team
|| '-' }}</el-descriptions-item>
<el-descriptions-item label="材料类型">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.materialType || '-' }}</el-descriptions-item>
<el-descriptions-item label="质量状态">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.qualityStatus || '-' }}</el-descriptions-item>
<el-descriptions-item label="净重">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.netWeight || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ parsedCacheData && parsedCacheData.splitForm &&
parsedCacheData.splitForm.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<el-divider
v-if="parsedCacheData && parsedCacheData.abnormals && parsedCacheData.abnormals.length > 0"
content-position="left"
>暂存的异常信息 ({{ parsedCacheData.abnormals.length }})</el-divider>
<div
v-if="parsedCacheData && parsedCacheData.abnormals && parsedCacheData.abnormals.length > 0"
class="abnormal-container"
style="margin-bottom: 20px;"
>
<div v-for="(abnormal, index) in parsedCacheData.abnormals" :key="index" class="abnormal-item">
<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>
</div>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="cacheDialogVisible = false">暂不使用</el-button>
<el-button type="danger" @click="deleteCache">删除暂存</el-button>
<el-button type="primary" @click="useCacheData">使用暂存数据</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getMaterialCoil, listMaterialCoil, createSpecialChild, completeSpecialSplit, updateMaterialCoilSimple, delMaterialCoil, getFirstHeatCoilMaterial } from '@/api/wms/coil'
import { completeAction, getPendingAction, updatePendingAction } from '@/api/wms/pendingAction'
import { saveCoilCache, getCoilCacheByCoilId, delCoilCache } from '@/api/wms/coilCache'
import { getGalvanize1TypingPrefill } from '@/api/pocket/acidTyping'
import { listPlanSheet } from '@/api/aps/planSheet'
import { listPlanDetail } from '@/api/aps/planDetail'
import ProductSelect from '@/components/KLPService/ProductSelect'
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect'
import WarehouseSelect from '@/components/KLPService/WarehouseSelect'
import ActualWarehouseSelect from '@/components/KLPService/ActualWarehouseSelect'
import TimeInput from '@/components/TimeInput'
import AbnormalForm from '../components/AbnormalForm'
import { generateCoilNoPrefix } from '@/utils/coil/coilNo'
import ProductInfo from '@/components/KLPService/Renderer/ProductInfo'
import RawMaterialInfo from '@/components/KLPService/Renderer/RawMaterialInfo'
import ContractSelect from '@/components/KLPService/ContractSelect'
import { addCoilContractRel } from '@/api/wms/coilContractRel'
export default {
name: 'StepSplit',
components: {
ProductSelect,
RawMaterialSelect,
WarehouseSelect,
ActualWarehouseSelect,
TimeInput,
AbnormalForm,
ProductInfo,
RawMaterialInfo,
ContractSelect
},
props: {
actionId: {
type: [String, Number],
required: true
},
coilId: {
type: String,
required: true
},
actionStatus: {
type: Number,
default: 0
},
actionType: {
type: Number,
required: true
}
},
dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'],
data() {
const currentCoilNoPrefix = generateCoilNoPrefix()
return {
currentCoilNoPrefix,
// 待分条钢卷基础信息
coilInfo: {},
loading: false,
// 分条表单数据
splitForm: {
coilId: '', // 分条钢卷ID编辑时赋值
enterCoilNo: '',
currentCoilNo: currentCoilNoPrefix,
supplierCoilNo: '',
warehouseId: '',
actualWarehouseId: '',
team: '',
materialType: '',
itemType: '', // product/raw_material
itemId: '',
qualityStatus: '',
trimmingRequirement: '',
packingStatus: '',
packagingRequirement: '',
grossWeight: '',
netWeight: '',
length: '',
actualLength: '',
actualWidth: '',
temperGrade: '',
coatingType: '',
remark: '',
chromePlateCoilNo: '',
productionStartTime: '',
productionEndTime: '',
productionDuration: '',
formattedDuration: ''
},
// 已分条钢卷列表
splitList: [],
// 列表加载状态
splitListLoading: false,
// 选中的分条项(用于显示详情)
selectedSplitItem: null,
// 是否显示分条表单
showSplitForm: false,
// 表单验证规则
rules: {
currentCoilNo: [
{ required: true, message: '当前钢卷号不能为空', trigger: 'blur' },
{
// 当前钢卷号必须大于等于10位
validator: (rule, value, callback) => {
if (value.length < 11) {
callback(new Error('当前钢卷号必须大于等于11位'))
} else {
callback()
}
}, trigger: 'blur'
}
],
materialType: [{ required: true, message: '请选择材料类型', trigger: 'change' }],
itemId: [{ required: true, message: '请选择成品/原料', trigger: 'change' }],
netWeight: [{ required: true, message: '请输入净重', trigger: 'blur' }],
warehouseId: [{ required: true, message: '请选择所在库位', trigger: 'change' }]
},
buttonLoading: false,
currentAction: {},
// 异常信息
abnormals: [],
// 异常表单弹窗
abnormalDialogVisible: false,
// 当前编辑的异常索引
currentAbnormalIndex: -1,
// 异常表单数据
abnormalForm: {
coilId: null,
position: null,
startPosition: 0,
endPosition: 0,
length: 0,
defectCode: null,
degree: null,
remark: null
},
zincList: [],
zincLoading: false,
// 缓存相关
cacheDialogVisible: false,
currentCache: null,
parsedCacheData: null,
// 最早的热轧卷板材质
firstHeatMaterial: '',
// 排产单相关
planSheetList: [],
planSheetDetailMap: {},
planSheetLoading: false,
activePlanSheetId: null
}
},
computed: {
// 动态获取成品/原料标签
getItemLabel() {
return this.splitForm.itemType === 'product' ? '成品' : this.splitForm.itemType === 'raw_material' ? '原料' : '材料项'
},
// actionType → 产线名称映射
planSheetLineName() {
const mapping = {
11: '酸轧线',
200: '酸轧线',
520: '酸轧线',
206: '镀锌线',
501: '镀锌线',
521: '镀锌线',
203: '脱脂线',
502: '脱脂线',
522: '脱脂线',
204: '拉矫线',
503: '拉矫线',
523: '拉矫线',
205: '双机架',
504: '双机架',
524: '双机架',
206: '镀铬线',
505: '镀铬线',
525: '镀铬线'
}
return mapping[this.actionType] || ''
},
todayDateStr() {
const d = new Date()
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
},
activePlanSheetDetails() {
return this.planSheetDetailMap[this.activePlanSheetId] || []
},
// 是否是镀铬操作
isGrindAction() {
return this.actionType == 505 || this.actionType == 525 || this.actionType == 206
},
// 镀锌/酸轧产线免验净重和厚度范围
isExemptFromValidation() {
return [11, 200, 520, 206, 501, 521].includes(Number(this.actionType))
}
},
watch: {
coilId: {
immediate: true, // 初始化时立即执行
async handler(val) {
if (val) {
this.loading = true
await this.getCoilInfo()
await this.getSplitList()
this.loading = false
// 更新父钢卷ID
this.splitForm.parentCoilId = val
}
}
},
actionId: {
immediate: true,
handler(val) {
// 若actionId变化需要重新加载数据可在此补充逻辑
}
},
actionType: {
immediate: true,
handler(val) {
console.log('actionType', val)
if (val == 501) {
// 获取镀锌线二级系统数据
this.getZincList()
}
if (this.planSheetLineName) {
this.fetchPlanSheets()
}
}
}
},
methods: {
// 查询最近排产单及明细
async fetchPlanSheets() {
if (!this.planSheetLineName) return
this.planSheetLoading = true
try {
const res = await listPlanSheet({
lineName: this.planSheetLineName,
pageSize: 3,
pageNum: 1
})
this.planSheetList = res.rows || []
this.planSheetDetailMap = {}
if (this.planSheetList.length > 0) {
this.activePlanSheetId = this.planSheetList[0].planSheetId
for (const sheet of this.planSheetList) {
this.fetchPlanSheetDetail(sheet.planSheetId)
}
}
} catch (e) {
console.error('查询排产单失败', e)
} finally {
this.planSheetLoading = false
}
},
async fetchPlanSheetDetail(planSheetId) {
try {
const res = await listPlanDetail({
planSheetId,
pageSize: 100,
pageNum: 1
})
const details = res.rows || []
details.sort((a, b) => {
const na = parseInt(a.bizSeqNo) || 0
const nb = parseInt(b.bizSeqNo) || 0
return na - nb
})
this.$set(this.planSheetDetailMap, planSheetId, details)
} catch (e) {
console.error('查询排产单明细失败', e)
}
},
// 查询待分条的钢卷信息
async getCoilInfo() {
try {
const res = await getMaterialCoil(this.coilId)
if (res.code === 200) {
this.coilInfo = res.data || {}
// 获取最早的热轧卷板材质
try {
const firstHeatMaterial = await getFirstHeatCoilMaterial(this.coilId)
if (firstHeatMaterial.code === 200 && firstHeatMaterial.msg) {
this.firstHeatMaterial = firstHeatMaterial.msg
}
} catch {
}
} else {
this.$message.error('查询钢卷信息失败:' + res.msg)
}
} catch (error) {
this.$message.error('查询钢卷信息异常:' + error.message)
}
},
async getZincList() {
this.zincLoading = true
const res = await getGalvanize1TypingPrefill({
pageSize: 10,
pageNum: 1
})
this.zincList = res.rows || []
this.zincLoading = false
},
async handlePrint(row) {
this.$emit('print', row)
},
async handleDeleteSplit(row) {
this.$modal.confirm('确认删除该分卷吗?').then(async() => {
try {
await delMaterialCoil(row.coilId)
this.$message.success('删除成功')
// 刷新钢卷信息
this.refresh()
} catch (error) {
this.$message.error('删除失败:' + error.message)
}
})
},
// 刷新分条列表
async refresh() {
this.loading = true
await this.getSplitList()
await this.getCoilInfo()
this.loading = false
},
// 查询钢卷的已分条列表
async getSplitList() {
this.splitListLoading = true
try {
if (!this.actionId) {
return
}
const action = await getPendingAction(this.actionId)
this.currentAction = action.data || {}
const coilIds = action.data.remark
console.log('coilIds', coilIds)
if (!coilIds) {
this.splitList = []
return
}
// 如果coildIds不满足格式coilId1,coilId2,coilId3任意个coilId返回空数组, 其中coilId必须是数字字符串
if (!coilIds || !/^\d+(,\d+)*$/.test(coilIds)) {
this.splitList = []
return
}
const res = await listMaterialCoil({
coilIds
})
this.splitList = res.rows || []
updatePendingAction({
actionId: action.data.actionId,
actionStatus: action.data.actionStatus,
actionType: action.data.actionType,
coilId: action.data.coilId,
currentCoilNo: action.data.currentCoilNo,
remark: res.rows.map(item => item.coilId).join(',')
})
} catch (error) {
this.$message.error('查询分条列表异常:' + error.message)
} finally {
this.splitListLoading = false
}
},
// 新增一个分条表单(重置表单并显示)
async addSplitForm() {
this.showSplitForm = true
this.selectedSplitItem = null
this.resetSplitForm()
this.splitForm.enterCoilNo = this.coilInfo.enterCoilNo || ''
// 如果是镀铬工序,设置默认镀铬卷号:取父卷入场卷号(多个逗号分隔时取第一个)
if (this.isGrindAction && this.coilInfo.enterCoilNo) {
this.splitForm.chromePlateCoilNo = this.coilInfo.enterCoilNo.split(',')[0].trim()
}
// 查询是否有缓存
try {
const res = await getCoilCacheByCoilId(this.coilId)
if (res.code === 200 && res.data) {
this.currentCache = res.data
try {
this.parsedCacheData = JSON.parse(res.data.coilJson)
} catch (e) {
this.parsedCacheData = null
this.$message.warning('缓存数据解析失败')
return
}
// 显示缓存对话框
this.cacheDialogVisible = true
}
} catch (error) {
console.error('查询缓存失败', error)
}
},
// 重置分条表单
resetSplitForm() {
this.$refs.splitFormRef?.resetFields()
this.splitForm = {
coilId: undefined,
enterCoilNo: '',
currentCoilNo: this.currentCoilNoPrefix,
supplierCoilNo: '',
warehouseId: '',
actualWarehouseId: '',
team: '',
materialType: '',
itemType: '',
itemId: '',
qualityStatus: '',
trimmingRequirement: '',
packingStatus: this.firstHeatMaterial || '',
packagingRequirement: '',
grossWeight: '',
netWeight: '',
length: '',
temperGrade: '',
coatingType: '',
remark: '',
chromePlateCoilNo: '',
productionStartTime: this.currentAction.createTime,
productionEndTime: '',
productionDuration: '',
formattedDuration: '',
parentCoilId: this.coilId
}
// 重置异常信息
this.abnormals = []
},
// 材料类型变更处理
handleMaterialTypeChange(val) {
// 清空物品选择
this.splitForm.itemId = null
// 根据材料类型设置物品类型
if (val === '成品') {
this.splitForm.itemType = 'product'
} else if (val === '原料') {
this.splitForm.itemType = 'raw_material'
}
},
// 选中分条列表项(显示详情)
handleSplitItemClick(row) {
this.selectedSplitItem = row
this.showSplitForm = false
},
handleZincItemClick(row) {
this.splitForm = {
...this.splitForm,
team: row.shiftNo,
// enterCoilNo: row.enterCoilNo,
productionStartTime: row.createTime,
productionEndTime: row.endTime,
itemType: 'product',
materialType: '成品',
length: row.exitLength,
netWeight: row.exitNetWeight
}
},
// 编辑分条项
async handleEditSplit(row) {
this.showSplitForm = true
this.selectedSplitItem = null
// 赋值表单数据
this.splitForm = { ...row }
},
// 新增/编辑分条
async addSplit() {
// 表单验证
const valid = await this.$refs.splitFormRef.validate()
console.log('valid', valid)
if (!valid) {
return
}
// 校验子卷净重和实测厚度不超过父卷(镀锌/酸轧除外)
if (!this.isExemptFromValidation) {
const parentNetWeight = parseFloat(this.coilInfo.netWeight) || 0
const parentThickness = parseFloat(this.coilInfo.actualThickness) || 0
const childNetWeight = parseFloat(this.splitForm.netWeight) || 0
const childThickness = parseFloat(this.splitForm.actualThickness) || 0
if (childNetWeight > 0 && parentNetWeight > 0 && childNetWeight > parentNetWeight) {
this.$message.error(`子卷净重(${childNetWeight}T)不能超过父卷净重(${parentNetWeight}T)`)
return
}
if (childThickness > 0 && parentThickness > 0 && childThickness > parentThickness) {
this.$message.error(`子卷实测厚度(${childThickness}mm)不能超过父卷实测厚度(${parentThickness}mm)`)
return
}
}
try {
// 区分新增/编辑有coilId则为编辑否则为新增
let res
this.buttonLoading = true
// 添加异常信息到表单数据
const splitData = {
...this.splitForm,
abnormals: this.abnormals
}
if (this.splitForm.coilId) {
// 编辑分条:调用更新接口
res = await updateMaterialCoilSimple(splitData)
} else {
// 新增分条:调用创建接口
res = await createSpecialChild(this.coilId, this.actionId, splitData)
// 新增分条后,需要添加分条的合同关系
if (this.splitForm.contractId) {
addCoilContractRel({
coilId: res.data.coilId,
contractId: this.splitForm.contractId
})
}
}
this.$message.success(this.splitForm.coilId ? '编辑分条成功' : '新增分条成功')
// 重置表单
this.resetSplitForm()
this.showSplitForm = false
// 刷新分条列表
this.getSplitList()
} catch (error) {
// 表单验证失败时的提示
console.log('error', error)
if (error.name !== 'ValidationError') {
this.$message.error((this.splitForm.coilId ? '编辑' : '新增') + '分条异常:' + error.message)
}
} finally {
this.buttonLoading = false
}
},
// 完成整体分条
async completeSplit() {
// 校验所有子卷(镀锌/酸轧除外)
if (!this.isExemptFromValidation) {
const parentThickness = parseFloat(this.coilInfo.actualThickness) || 0
const parentNetWeight = parseFloat(this.coilInfo.netWeight) || 0
let totalChildNetWeight = 0
for (const child of this.splitList) {
const childThickness = parseFloat(child.actualThickness) || 0
const childNetWeight = parseFloat(child.netWeight) || 0
totalChildNetWeight += childNetWeight
if (childThickness > 0 && parentThickness > 0 && childThickness > parentThickness) {
this.$message.error(`子卷【${child.currentCoilNo}】实测厚度(${childThickness}mm)超过父卷实测厚度(${parentThickness}mm),请先修正`)
return
}
}
if (totalChildNetWeight > 0 && parentNetWeight > 0 && totalChildNetWeight > parentNetWeight) {
this.$message.error(`所有子卷净重总和(${totalChildNetWeight.toFixed(3)}T)超过父卷净重(${parentNetWeight}T),请先修正`)
return
}
}
this.$confirm('确认完成整体分条操作?完成后将无法修改分条信息', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const loading = this.$loading({
lock: true,
text: '正在记录分条操作...',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
this.buttonLoading = true
// 1. 完成分条主流程
const splitRes = await completeSpecialSplit(this.actionId)
if (splitRes.code !== 200) {
this.$message.error('完成分条失败:' + splitRes.msg)
return
}
// 2. 完成待办动作(根据业务逻辑调整)
const actionRes = await completeAction(this.actionId, splitRes.data.childCoilIds.join(','))
if (actionRes.code !== 200) {
this.$message.error('完成待办动作失败:' + actionRes.msg)
return
}
this.buttonLoading = false
this.$message.success('分条操作已完成')
// 通知父组件(如需要)
this.$emit('complete')
} catch (error) {
if (error.message !== 'cancel') { // 排除取消确认的情况
this.$message.error('完成分条异常:' + error.message)
}
} finally {
loading.close()
}
})
},
// 格式化毫秒值为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.splitForm
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.splitForm, 'productionDuration', '')
this.$set(this.splitForm, 'formattedDuration', '')
} else {
const durationMs = end - start
const durationMinutes = Math.round(durationMs / (1000 * 60))
this.$set(this.splitForm, 'productionDuration', durationMinutes)
this.$set(this.splitForm, 'formattedDuration', this.formatDuration(durationMinutes * 60 * 1000))
}
} else {
this.$set(this.splitForm, 'productionDuration', '')
this.$set(this.splitForm, 'formattedDuration', '')
}
},
// 新增异常
addAbnormal() {
this.currentAbnormalIndex = -1
this.abnormalForm = {
coilId: this.splitForm.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
},
// 复制源卷信息到分条表单
copyFromSourceCoil() {
// 复制除了指定字段之外的其他字段
const excludeFields = ['enterCoilNo', 'currentCoilNo', 'coilId', 'createTime', 'createBy']
// 构建要复制的字段
const copiedFields = {
supplierCoilNo: this.coilInfo.supplierCoilNo,
warehouseId: this.coilInfo.warehouseId,
actualWarehouseId: this.coilInfo.actualWarehouseId,
team: this.coilInfo.team,
materialType: this.coilInfo.materialType,
itemType: this.coilInfo.itemType,
itemId: this.coilInfo.itemId,
qualityStatus: this.coilInfo.qualityStatus,
trimmingRequirement: this.coilInfo.trimmingRequirement,
packingStatus: this.coilInfo.packingStatus,
packagingRequirement: this.coilInfo.packagingRequirement,
grossWeight: parseFloat(this.coilInfo.grossWeight) || null,
netWeight: parseFloat(this.coilInfo.netWeight) || null,
length: parseFloat(this.coilInfo.length) || null,
actualLength: parseFloat(this.coilInfo.actualLength) || null,
actualWidth: parseFloat(this.coilInfo.actualWidth) || null,
temperGrade: this.coilInfo.temperGrade,
coatingType: this.coilInfo.coatingType,
chromePlateCoilNo: this.coilInfo.chromePlateCoilNo,
remark: this.coilInfo.remark,
productionStartTime: this.coilInfo.productionStartTime,
productionEndTime: this.coilInfo.productionEndTime,
productionDuration: this.coilInfo.productionDuration,
formattedDuration: this.coilInfo.productionDuration ? this.formatDuration(this.coilInfo.productionDuration * 60 * 1000) : ''
}
// 合并到分条表单
this.splitForm = {
...this.splitForm,
...copiedFields
}
this.$message.success('已复制源卷信息,请根据需要修改')
},
// 暂存表单内容
async saveSplitForm() {
try {
this.buttonLoading = true
const cacheData = {
splitForm: this.splitForm,
abnormals: this.abnormals
}
const data = {
coilId: this.coilId,
coilJson: JSON.stringify(cacheData)
}
const res = await saveCoilCache(data)
if (res.code === 200) {
this.$message.success('暂存成功')
} else {
this.$message.error('暂存失败:' + res.msg)
}
} catch (error) {
this.$message.error('暂存异常:' + error.message)
} finally {
this.buttonLoading = false
}
},
// 使用缓存数据
useCacheData() {
if (this.parsedCacheData) {
this.splitForm = { ...this.parsedCacheData.splitForm }
this.abnormals = [...this.parsedCacheData.abnormals]
this.$message.success('已恢复缓存数据')
}
this.cacheDialogVisible = false
},
// 删除缓存
async deleteCache() {
this.$confirm('确定要删除该缓存吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
try {
await delCoilCache(this.currentCache.cacheId)
this.$message.success('删除缓存成功')
this.cacheDialogVisible = false
} catch (error) {
this.$message.error('删除缓存失败:' + error.message)
}
})
}
}
}
</script>
<style scoped>
.split-coil-container {
padding: 20px;
}
.coil-info-card {
height: 100%;
display: flex;
flex-direction: column;
gap: 10px;
}
.split-form-card,
.split-detail-card {
height: 100%;
}
.empty-tip {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
min-height: 400px;
}
.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);
}
}
.plan-sheet-section {
margin-top: 16px;
}
</style>