Files
klp-oa/klp-ui/src/views/wms/coil/typing.vue
Joshi 9d208770d8 feat(service): 添加按热卷号查询计划详情功能
- 在 SqlServerApiBusinessService 中实现按热卷号查询计划详情方法
- 新增调用查询扩展卷数据接口并整合到返回结果中
- 修改 PlanDetailView 构造函数以支持扩展卷数据列表
- 添加 excoilRows 属性和相应的 getter 方法
- 提供新的 fromExecuteSqlResponse 静态工厂方法处理双响应对象
- 在 SqlServerApiClient 中新增 queryExcoilByHotCoilId 查询接口
2026-06-04 10:59:07 +08:00

1376 lines
48 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="typing-coil-container">
<div>
<CoilInfoRender title="原料信息" :coilInfo="currentInfo" border :column="7">
<template slot="extra">
<el-button type="text" size="mini" @click="copyFromCurrent" icon="el-icon-document-copy">
复制源卷信息
</el-button>
</template>
</CoilInfoRender>
</div>
<!-- 排产计划 -->
<el-card class="plan-card" v-if="planSheetList.length > 0" style="margin-bottom: 20px;">
<div slot="header" class="card-header">
<span><i class="el-icon-s-order"></i> {{ currentLineName }}排产计划</span>
</div>
<el-tabs v-model="activePlanSheetId" type="card" size="mini">
<el-tab-pane v-for="sheet in planSheetList" :key="sheet.planSheetId" :name="sheet.planSheetId"
:label="sheet.planCode || ('排产单' + sheet.planSheetId)" />
</el-tabs>
<div style="overflow-x: auto;">
<el-table :data="activePlanSheetDetails" size="small" max-height="300" stripe border>
<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>
</el-card>
<el-alert v-if="acidPrefill.visible" :title="acidPrefill.title" :type="acidPrefill.type" :closable="false" show-icon
style="margin-bottom: 20px;" />
<!-- 主内容区 - 左右布局 -->
<div class="content-wrapper">
<!-- 左侧快捷写入面板DR操作用双机架计划面板其他用L2匹配 -->
<div>
<dr-match-panel v-if="isDrAction" :action-id="actionId" @fill="applyDrFill" />
<l2-match-panel v-else :hot-coil-id="l2HotCoilId" @fill="applyL2Fill" />
</div>
<!-- 右侧更新表单 -->
<div>
<el-card class="form-card">
<div slot="header" class="card-header">
<span><i class="el-icon-edit-outline"></i> {{ '更新信息' }}</span>
<div>
<el-button size="small" @click="saveTemp" :loading="loading">暂存内容</el-button>
<el-button type="primary" size="small" @click="handleSave" :loading="loading">保存更新</el-button>
</div>
</div>
<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>
<div v-if="matchedSpec" style="margin-bottom:10px">
<el-tag type="success" size="small">
<i class="el-icon-s-order" style="margin-right:4px" />
已匹配规程{{ matchedSpec.specCode }}{{ matchedSpec.specName ? ' - ' + matchedSpec.specName : '' }} / {{
matchedSpec.versionCode }}
</el-tag>
</div>
<el-form ref="updateForm" :model="updateForm" :rules="rules" label-width="86px" size="small">
<el-row>
<el-col :span="16">
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="updateForm.currentCoilNo" placeholder="请输入当前钢卷号">
<template slot="prepend">
<i class="el-icon-document"></i> <current-coil-no
:current-coil-no="updateForm.currentCoilNo"></current-coil-no>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班组" prop="team">
<el-select v-model="updateForm.team" placeholder="请选择班组" style="width: 100%">
<el-option key="甲" label="甲" value="甲" />
<el-option key="乙" label="乙" value="乙" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="材料类型" prop="materialType">
<el-select v-model="updateForm.materialType" placeholder="请选择材料类型" style="width: 100%"
@change="handleMaterialTypeChange">
<el-option label="原料" value="原料" />
<el-option label="成品" value="成品" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="getItemLabel" prop="itemId" :rules="rules.itemId">
<RawMaterialSelect v-if="updateForm.materialType === '原料'" v-model="updateForm.itemId"
placeholder="请选择原料" style="width: 100%" clearable :disabled="!updateForm.materialType"
:default-query-params="itemSelectorQueryParams" />
<ProductSelect v-else-if="updateForm.materialType === '成品'" v-model="updateForm.itemId"
placeholder="请选择成品" style="width: 100%" clearable :disabled="!updateForm.materialType"
:default-query-params="itemSelectorQueryParams" />
<div v-else>请先选择物料类型</div>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="原料材质" prop="packingStatus">
<el-input v-model="updateForm.packingStatus" placeholder="请输入原料材质">
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="质量状态" prop="qualityStatus">
<el-select v-model="updateForm.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-col>
<el-col :span="8">
<el-form-item label="切边要求" prop="trimmingRequirement">
<el-select v-model="updateForm.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="包装要求" prop="packagingRequirement">
<el-select v-model="updateForm.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-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="毛重(t)" prop="grossWeight">
<el-input-number :precision="3" :controls="false" v-model="updateForm.grossWeight" placeholder="请输入毛重"
type="number" :step="0.01">
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="净重(t)" prop="netWeight">
<el-input-number :precision="3" :controls="false" v-model="updateForm.netWeight" placeholder="请输入净重"
type="number" :step="0.001">
<template slot="append"></template>
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="参考长度(m)" prop="length">
<el-input-number :controls="false" v-model="updateForm.length" placeholder="请输入参考长度" type="number"
:step="0.001">
<template slot="append"></template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="实测长度(m)" prop="actualLength">
<el-input-number :controls="false" v-model="updateForm.actualLength" placeholder="请输入实测长度"
type="number" :step="0.001">
<template slot="append">m</template>
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="实测厚度(mm)" prop="actualThickness" class="form-item-half">
<el-input-number :controls="false" v-model="updateForm.actualThickness" placeholder="请输入实测厚度"
type="number" :step="0.01">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="实测宽度(mm)" prop="actualWidth">
<el-input-number :controls="false" v-model="updateForm.actualWidth" placeholder="请输入实测宽度"
type="number" :step="0.001">
<template slot="append">mm</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="业务目的" prop="businessPurpose">
<el-select v-model="updateForm.businessPurpose" placeholder="请选择业务目的" style="width: 100%">
<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-col>
<el-col :span="8">
<el-form-item label="调制度" prop="temperGrade">
<el-input v-model="updateForm.temperGrade" placeholder="请输入调制度" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="镀层种类" prop="coatingType">
<MemoInput storageKey="coatingType" v-model="updateForm.coatingType" placeholder="请输入镀层种类" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="钢卷表面处理" prop="coilSurfaceTreatment">
<MemoInput storageKey="surfaceTreatmentDesc" v-model="updateForm.coilSurfaceTreatment"
placeholder="请输入钢卷表面处理" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="逻辑库区" prop="warehouseId">
<WarehouseSelect v-model="updateForm.warehouseId" placeholder="请选择逻辑库区" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="真实库区" prop="actualWarehouseId">
<ActualWarehouseSelect :clearInput="false" clearable v-model="updateForm.actualWarehouseId"
placeholder="请选择真实库区" block />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="生产开始时间" prop="productionStartTime">
<TimeInput v-model="updateForm.productionStartTime" @input="calculateProductionDuration" />
</el-form-item>
<el-form-item label="生产结束时间" prop="productionEndTime">
<TimeInput v-model="updateForm.productionEndTime" @input="calculateProductionDuration"
:show-now-button="true" />
</el-form-item>
<el-form-item label="生产耗时" prop="productionDuration">
<el-input v-model="updateForm.formattedDuration" placeholder="自动计算" disabled />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="updateForm.remark" type="textarea" :rows="4" placeholder="请输入备注信息(非必填)" maxlength="500"
show-word-limit />
</el-form-item>
<el-form-item label="关联合同" prop="contractId">
<ContractSelect v-model="updateForm.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)"></el-button>
</div>
</div>
<div class="abnormal-add" @click="addAbnormal">
<i class="el-icon-plus"></i>
</div>
</div>
</el-form-item>
</el-form>
</el-card>
</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="cacheDialogVisible" width="800px" append-to-body>
<div>
<el-alert title="检测到您之前有暂存的钢卷信息,是否恢复使用?" type="info" show-icon :closable="false"></el-alert>
<el-divider content-position="left">暂存的表单数据</el-divider>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="当前钢卷号">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.currentCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班组">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.team || '-' }}</el-descriptions-item>
<el-descriptions-item label="材料类型">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.materialType || '-' }}</el-descriptions-item>
<el-descriptions-item label="质量状态">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.qualityStatus || '-' }}</el-descriptions-item>
<el-descriptions-item label="净重">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.netWeight || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ parsedCacheData && parsedCacheData.updateForm &&
parsedCacheData.updateForm.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, updateMaterialCoil, getFirstHeatCoilMaterial } from '@/api/wms/coil';
import { matchBestSpecVersion } from '@/api/wms/processSpecVersion';
import { completeAction, getPendingAction } from '@/api/wms/pendingAction';
import { listPlanSheet } from '@/api/aps/planSheet';
import { listPlanDetail } from '@/api/aps/planDetail';
import { saveCoilCache, getCoilCacheByCoilId, delCoilCache } from '@/api/wms/coilCache';
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 { generateCoilNoPrefix } from "@/utils/coil/coilNo";
import { addCoilContractRel } from "@/api/wms/coilContractRel";
import ContractSelect from "@/components/KLPService/ContractSelect";
import L2MatchPanel from './panels/L2MatchPanel.vue'
import DrMatchPanel from './panels/DrMatchPanel.vue';
// actionType -> 产线名称映射
const actionTypeToLineName = {
11: '酸轧线', 120: '酸轧线', 200: '酸轧线', 201: '酸轧线', 520: '酸轧线',
202: '镀锌线', 501: '镀锌线', 521: '镀锌线',
203: '脱脂线', 502: '脱脂线', 522: '脱脂线',
204: '拉矫线', 503: '拉矫线', 523: '拉矫线',
205: '双机架线', 504: '双机架线', 524: '双机架线',
206: '镀铬线', 505: '镀铬线', 525: '镀铬线',
506: '分条线',
}
export default {
name: 'TypingCoil',
components: {
ActualWarehouseSelect,
RawMaterialSelect,
ProductSelect,
WarehouseSelect,
TimeInput,
AbnormalForm,
ContractSelect,
L2MatchPanel,
DrMatchPanel,
},
dicts: ['coil_quality_status', 'coil_abnormal_position', 'coil_abnormal_code', 'coil_abnormal_degree', 'coil_business_purpose'],
data() {
return {
loading: false,
historyLoading: false,
// 当前信息(只读)
currentInfo: {
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
supplierCoilNo: '',
team: '',
itemType: null,
itemId: null,
itemName: '',
grossWeight: undefined,
netWeight: undefined,
warehouseId: null,
warehouseId: null,
status: 0,
remark: '',
length: undefined,
},
// 更新表单
updateForm: {
currentCoilNo: '',
team: '',
materialType: '成品',
itemType: 'product',
itemId: null,
grossWeight: undefined,
netWeight: undefined,
warehouseId: null,
actualWarehouseId: null,
remark: '',
qualityStatus: '',
packagingRequirement: '',
packingStatus: '',
trimmingRequirement: '',
length: undefined,
temperGrade: '',
coatingType: '',
actualLength: undefined,
actualWidth: undefined,
productionStartTime: '',
productionEndTime: '',
productionDuration: '',
formattedDuration: '',
specId: null,
versionId: null,
},
matchedSpec: null,
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: 'blur' }
],
materialType: [
{ required: true, message: '请选择材料类型', trigger: 'change' }
],
itemType: [
{ required: true, message: '请选择物品类型', trigger: 'change' }
],
itemId: [
{ required: true, message: '请选择物品', trigger: 'change' }
],
grossWeight: [
{ required: true, message: '请输入毛重', trigger: 'blur' },
{ type: 'number', message: '毛重必须为数字', trigger: 'blur' }
],
netWeight: [
{ required: true, message: '请输入净重', trigger: 'blur' },
{ type: 'number', message: '净重必须为数字', trigger: 'blur' }
],
warehouseId: [
{ required: true, message: '请选择逻辑库区', trigger: 'change' }
],
},
actionId: null,
actionType: null, // 待操作类型504/524 = 双机架
acidPrefill: {
visible: false,
type: 'info',
title: ''
},
// 异常信息
abnormals: [],
// 异常表单弹窗
abnormalDialogVisible: false,
// 当前编辑的异常索引
currentAbnormalIndex: -1,
// 异常表单数据
abnormalForm: {
coilId: null,
position: null,
startPosition: 0,
endPosition: 0,
length: 0,
defectCode: null,
degree: null,
remark: null
},
contractList: [],
// 缓存相关
cacheDialogVisible: false,
currentCache: null,
parsedCacheData: null,
itemSelectorQueryParams: {},
// 排产计划
planSheetList: [],
planSheetDetailMap: {},
activePlanSheetId: null,
currentLineName: '',
};
},
computed: {
l2HotCoilId() {
return (this.currentInfo && this.currentInfo.enterCoilNo) || ''
},
activePlanSheetDetails() {
return this.planSheetDetailMap[this.activePlanSheetId] || []
},
/** 是否双机架工序actionType 504=正常 / 524=修复) */
isDrAction() {
return this.actionType === 504 || this.actionType === 524
},
/** 镀锌/酸轧产线免验净重和厚度范围 */
isExemptFromValidation() {
return [11, 120, 200, 201, 520, 202, 501, 521].includes(Number(this.actionType))
},
// 动态显示标签
getItemLabel() {
if (this.updateForm.materialType === '成品') {
return '产品类型';
} else if (this.updateForm.materialType === '原料') {
return '原料类型';
}
return '物品';
},
// 动态显示占位符
getItemPlaceholder() {
if (this.updateForm.materialType === '成品') {
return '请选择产品类型';
} else if (this.updateForm.materialType === '原料') {
return '请选择原料类型';
}
return '请先选择材料类型';
},
},
async created() {
// 从路由参数获取coilId和actionId
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
const pendingActionRes = await getPendingAction(actionId)
// 填写生产开始时间
this.$set(this.updateForm, 'productionStartTime', pendingActionRes.data.createTime)
// 记录操作类型(用于判断是否双机架工序)
if (pendingActionRes.data && pendingActionRes.data.actionType != null) {
this.actionType = pendingActionRes.data.actionType
}
if (coilId) {
await this.loadCoilInfo(coilId);
}
const currentCoilNoPrefix = generateCoilNoPrefix()
this.$set(this.updateForm, 'currentCoilNo', currentCoilNoPrefix)
if (actionId) {
this.actionId = actionId;
}
// 检测是否有暂存数据
if (coilId) {
try {
const res = await getCoilCacheByCoilId(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);
}
}
// 加载排产计划
await this.loadPlanDetails();
},
methods: {
/** 双机架计划/道次快捷写入 */
applyDrFill(data) {
if (data.outThick != null) this.$set(this.updateForm, 'actualThickness', parseFloat(data.outThick))
if (data.inMatWidth != null) this.$set(this.updateForm, 'actualWidth', parseFloat(data.inMatWidth))
if (data.inMatWeight != null) {
this.$set(this.updateForm, 'netWeight', parseFloat(data.inMatWeight))
this.$set(this.updateForm, 'grossWeight', parseFloat(data.inMatWeight))
}
if (data.inMatLength != null) {
this.$set(this.updateForm, 'actualLength', parseFloat(data.inMatLength))
this.$set(this.updateForm, 'length', parseFloat(data.inMatLength))
}
},
applyL2Fill(data) {
if (data.entry_weight != null) {
const w = parseFloat(data.entry_weight)
if (!Number.isNaN(w)) {
this.$set(this.updateForm, 'grossWeight', w)
this.$set(this.updateForm, 'netWeight', w)
}
}
if (data.exit_length != null) {
const len = parseFloat(data.exit_length)
if (!Number.isNaN(len)) {
this.$set(this.updateForm, 'length', len)
this.$set(this.updateForm, 'actualLength', len)
}
}
if (data.exit_thick != null) this.$set(this.updateForm, 'actualThickness', parseFloat(data.exit_thick))
if (data.exit_width != null) this.$set(this.updateForm, 'actualWidth', parseFloat(data.exit_width))
if (data.start_date) this.$set(this.updateForm, 'productionStartTime', data.start_date)
if (data.end_date) this.$set(this.updateForm, 'productionEndTime', data.date)
const query = {
specification: data.exit_thick ? `${data.exit_thick}*${data.exit_width}` : '',
material: data.grade,
}
this.itemSelectorQueryParams = query
// 包装要求
if (data.park_type != null && data.park_type !== '') {
this.$set(this.updateForm, 'packagingRequirement', data.park_type)
}
// 切边要求1=净边料0=毛边料
if (data.trimming != null) {
const t = String(data.trimming)
if (t === '1') this.$set(this.updateForm, 'trimmingRequirement', '净边料')
else if (t === '0') this.$set(this.updateForm, 'trimmingRequirement', '毛边料')
}
this.$message.success('L2 数据已写入表单')
const ACID_ROLL_ACTION_TYPES = [11, 200, 520]
const ACID_LINE_ID = 1
if (this.l2HotCoilId && ACID_ROLL_ACTION_TYPES.includes(this.actionType)) {
matchBestSpecVersion(this.l2HotCoilId, ACID_LINE_ID).then(res => {
const v = res.data
if (v) {
this.$set(this.updateForm, 'specId', v.specId)
this.$set(this.updateForm, 'versionId', v.versionId)
this.matchedSpec = v
}
}).catch(() => { })
}
},
// 处理材料类型变化
handleMaterialTypeChange(value) {
// 清空物品选择
this.$set(this.updateForm, 'itemId', null);
// 根据材料类型设置物品类型
if (value === '成品') {
this.$set(this.updateForm, 'itemType', 'product');
} else if (value === '原料') {
this.$set(this.updateForm, 'itemType', 'raw_material');
}
},
// 加载钢卷信息
async loadCoilInfo(coilId) {
try {
this.loading = true;
const response = await getMaterialCoil(coilId);
if (response.code === 200 && response.data) {
const data = response.data;
// 填充当前信息(左侧)
this.currentInfo = {
...data,
};
const firstHeatMaterial = await getFirstHeatCoilMaterial(this.currentInfo.enterCoilNo);
console.log(firstHeatMaterial)
if (firstHeatMaterial.code === 200 && firstHeatMaterial.msg) {
this.$set(this.updateForm, 'packingStatus', firstHeatMaterial.msg)
}
// 填充时间相关字段
if (data.productionStartTime) {
this.updateForm.productionStartTime = data.productionStartTime;
}
if (data.productionEndTime) {
this.updateForm.productionEndTime = data.productionEndTime;
}
if (data.productionDuration) {
this.updateForm.productionDuration = data.productionDuration;
this.updateForm.formattedDuration = this.formatDuration(data.productionDuration);
}
}
} catch (error) {
this.$message.error('加载钢卷信息失败');
} finally {
this.loading = false;
}
},
// 根据当前actionType加载对应产线的最近3个排产计划
async loadPlanDetails() {
const lineName = actionTypeToLineName[this.actionType]
if (!lineName) return
this.currentLineName = lineName
try {
const sheetRes = await listPlanSheet({
lineName,
pageNum: 1,
pageSize: 3,
})
this.planSheetList = sheetRes.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 (error) {
console.error('加载排产计划失败', error)
}
},
async fetchPlanSheetDetail(planSheetId) {
try {
const detailRes = await listPlanDetail({
planSheetId,
pageNum: 1,
pageSize: 50,
})
if (detailRes.rows) {
const details = detailRes.rows.sort((a, b) => (parseInt(a.bizSeqNo) || 0) - (parseInt(b.bizSeqNo) || 0))
this.$set(this.planSheetDetailMap, planSheetId, details)
}
} catch (e) { console.error('查询排产单明细失败', e) }
},
// 复制当前信息到更新表单
copyFromCurrent() {
// 复制除了指定字段之外的其他字段
const excludeFields = ['enterCoilNo', 'currentCoilNo', 'coilId', 'createTime', 'createBy'];
// 构建要复制的字段
const copiedFields = {
team: this.currentInfo.team,
materialType: this.currentInfo.materialType,
itemType: this.currentInfo.itemType,
itemId: this.currentInfo.itemId,
grossWeight: parseFloat(this.currentInfo.grossWeight) || null,
netWeight: parseFloat(this.currentInfo.netWeight) || null,
warehouseId: this.currentInfo.warehouseId,
actualWarehouseId: this.currentInfo.actualWarehouseId,
length: parseFloat(this.currentInfo.length) || null,
actualLength: parseFloat(this.currentInfo.actualLength) || null,
actualWidth: parseFloat(this.currentInfo.actualWidth) || null,
temperGrade: this.currentInfo.temperGrade,
coatingType: this.currentInfo.coatingType,
qualityStatus: this.currentInfo.qualityStatus,
packagingRequirement: this.currentInfo.packagingRequirement,
packingStatus: this.currentInfo.packingStatus,
trimmingRequirement: this.currentInfo.trimmingRequirement,
remark: this.currentInfo.remark,
productionStartTime: this.currentInfo.productionStartTime,
productionEndTime: this.currentInfo.productionEndTime,
productionDuration: this.currentInfo.productionDuration,
formattedDuration: this.currentInfo.productionDuration ? this.formatDuration(this.currentInfo.productionDuration * 60 * 1000) : ''
};
// 合并到更新表单
this.updateForm = {
...this.updateForm,
...copiedFields
};
this.$message.success('已复制源卷信息,请根据需要修改');
},
// 保存更新
async handleSave() {
this.$refs.updateForm.validate(async (valid) => {
if (!valid) {
return false;
}
// 验证时间逻辑
const { productionStartTime, productionEndTime } = this.updateForm;
if (productionStartTime && productionEndTime) {
const start = new Date(productionStartTime).getTime();
const end = new Date(productionEndTime).getTime();
if (end < start) {
this.$message({
message: '结束时间不能早于开始时间',
type: 'error',
});
return false;
}
}
// 校验净重和实测厚度不超过源卷(镀锌/酸轧除外)
if (!this.isExemptFromValidation) {
const parentNetWeight = parseFloat(this.currentInfo.netWeight) || 0
const parentThickness = parseFloat(this.currentInfo.actualThickness) || 0
const updateNetWeight = parseFloat(this.updateForm.netWeight) || 0
const updateThickness = parseFloat(this.updateForm.actualThickness) || 0
if (updateNetWeight > 0 && parentNetWeight > 0 && updateNetWeight > parentNetWeight) {
this.$message.error(`更新后净重(${updateNetWeight}T)不能超过源卷净重(${parentNetWeight}T)`)
return false
}
if (updateThickness > 0 && parentThickness > 0 && updateThickness > parentThickness) {
this.$message.error(`更新后实测厚度(${updateThickness}mm)不能超过源卷实测厚度(${parentThickness}mm)`)
return false
}
}
const loadingInstance = this.$loading({
lock: true,
text: '正在更新钢卷信息,请稍后...',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
this.loading = true;
// 构造更新数据使用标准update接口直接更新原记录
const updateData = {
...this.updateForm,
coilId: this.currentInfo.coilId,
enterCoilNo: this.currentInfo.enterCoilNo,
supplierCoilNo: this.currentInfo.supplierCoilNo,
abnormals: this.abnormals,
};
const response = await updateMaterialCoil({ ...updateData, actionId: this.actionId });
// 更新完成后如果选定了合同,需要增加与合同的绑定关系
const coilId = response.msg;
if (this.updateForm.contractId) {
await addCoilContractRel({
coilId: coilId,
contractId: this.updateForm.contractId,
});
}
if (response.code === 200) {
this.$message.success('钢卷信息更新成功');
// 如果是从待操作列表进来的,标记操作为完成
// if (this.actionId) {
// await completeAction(this.actionId, response.msg);
// }
// 延迟返回
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();
}
});
},
// 格式化毫秒值为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.updateForm;
if (productionStartTime && productionEndTime) {
const start = new Date(productionStartTime).getTime();
const end = new Date(productionEndTime).getTime();
if (end < start) {
this.$message({
message: '结束时间不能早于开始时间',
type: 'error',
});
this.updateForm.productionDuration = '';
this.updateForm.formattedDuration = '';
} else {
const durationMs = end - start;
const durationMinutes = Math.round(durationMs / (1000 * 60));
this.updateForm.productionDuration = durationMinutes;
this.updateForm.formattedDuration = this.formatDuration(durationMinutes * 60 * 1000);
}
} else {
this.updateForm.productionDuration = '';
this.updateForm.formattedDuration = '';
}
},
// 取消操作
handleCancel() {
this.$router.back();
},
// 新增异常
addAbnormal() {
this.currentAbnormalIndex = -1;
this.abnormalForm = {
coilId: this.currentInfo.coilId,
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;
},
// 暂存表单内容
async saveTemp() {
try {
this.loading = true;
const cacheData = {
updateForm: this.updateForm,
abnormals: this.abnormals
};
const data = {
coilId: this.currentInfo.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.loading = false;
}
},
// 使用暂存数据
useCacheData() {
if (this.parsedCacheData && this.parsedCacheData.updateForm) {
this.updateForm = { ...this.parsedCacheData.updateForm };
if (this.parsedCacheData.abnormals) {
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 lang="scss">
.typing-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;
}
}
/* 主内容区 */
.content-wrapper {
display: grid;
margin-top: 10px;
grid-template-columns: 340px 1fr;
gap: 10px;
align-items: stretch; // 改为stretch让子元素高度一致
}
/* 左侧面板 */
.left-panel {
min-width: 0;
display: flex;
flex-direction: column;
}
/* 确保两侧卡片高度一致 */
.info-card,
.form-card {
flex: 1;
display: flex;
flex-direction: column;
::v-deep .el-card__body {
flex: 1;
display: flex;
flex-direction: column;
}
}
// 新增:最近记录卡片样式
.recent-records-card {
flex: none // 不伸缩,只占据内容所需空间
}
/* 变更历史区域(占满整行) */
.history-section {
margin-top: 20px;
}
/* 卡片头部 */
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 500;
i {
color: #0066cc;
margin-right: 5px;
}
}
/* 当前信息展示 */
.info-section {
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.info-label {
color: #909399;
font-size: 14px;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
word-break: break-all;
}
}
}
/* 变更历史 */
.history-card {
::v-deep .el-card__body {
max-height: 400px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
::v-deep .el-timeline {
padding-left: 10px;
}
}
.history-item {
.history-title {
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.history-detail {
font-size: 13px;
color: #606266;
margin-bottom: 4px;
.detail-label {
color: #909399;
margin-right: 5px;
}
}
}
.empty-history {
text-align: center;
padding: 40px 0;
color: #909399;
i {
font-size: 48px;
margin-bottom: 10px;
display: block;
}
p {
margin: 0;
}
}
/* 表单样式优化 */
.form-card {
::v-deep .el-input-number {
width: 100%;
.el-input__inner {
text-align: left;
}
}
::v-deep .el-form-item {
margin-bottom: 20px;
}
// 修复数字输入框的样式
::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;
}
}
}
// 优化按钮文字颜色
// 实心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;
}
}
// 异常信息样式
.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>