feat(分卷): 实现镀锌工序特殊分卷功能

新增特殊分卷功能,包括分步分条界面、API接口及状态管理。主要修改:
1. 添加分步分条组件,支持新增、编辑、删除分条
2. 扩展分卷API接口,包括开始/完成/取消特殊分卷
3. 优化操作按钮加载状态和错误处理
4. 调整界面布局和样式
This commit is contained in:
砂糖
2026-01-22 16:52:07 +08:00
parent 6f83ae7dae
commit 8adad71acd
7 changed files with 761 additions and 67 deletions

View File

@@ -1,54 +1,557 @@
<template>
<div>
分步分条
<div class="split-coil-container" v-loading="loading">
<!-- 左右分栏布局 -->
<el-row :gutter="20">
<!-- 左侧钢卷信息 + 新增按钮 + 已分条列表 -->
<el-col :span="12">
<div class="coil-info-card">
<el-row :gutter="20" flex justify="end">
<!-- 新增分条按钮 -->
<el-button type="primary" icon="el-icon-plus" @click="addSplitForm" :loading="buttonLoading"
v-if="actionStatus != 2">
新增分条
</el-button>
<!-- 完成分条按钮 -->
<el-button type="success" icon="el-icon-check" @click="completeSplit" :disabled="splitList.length === 0"
:loading="buttonLoading" v-if="actionStatus != 2">
完成整体分条
</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="refresh" :loading="buttonLoading">
刷新
</el-button>
</el-row>
<el-descriptions :column="2" border title="待分条钢卷信息">
<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 || '-' }} kg</el-descriptions-item>
</el-descriptions>
<!-- 已分条钢卷列表 -->
<el-descriptions :column="1" border title="已分出的钢卷列表"></el-descriptions>
<el-table v-loading="splitListLoading" :data="splitList" @row-click="handleSplitItemClick"
highlight-current-row border stripe>
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
<el-table-column prop="materialType" label="材料类型" />
<el-table-column prop="dataType" label="钢卷状态">
<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">
<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="220">
<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>
</el-col>
<!-- 右侧分条表单 / 分条详情 -->
<el-col :span="12">
<div class="split-form-card" v-if="showSplitForm">
<el-card title="分条钢卷信息录入" shadow="hover">
<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 label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="splitForm.currentCoilNo" placeholder="请输入当前钢卷号" />
</el-form-item>
<el-form-item label="厂家原料卷号" prop="supplierCoilNo">
<el-input v-model="splitForm.supplierCoilNo" placeholder="请输入厂家原料卷号" />
</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="actualWarehouseId">
<actual-warehouse-select
v-model="splitForm.actualWarehouseId"
:clearInput="splitForm.coilId != null"
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-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="调制度" prop="temperGrade">
<el-input v-model="splitForm.temperGrade" placeholder="请输入调制度" />
</el-form-item>
<el-form-item label="镀层种类" prop="coatingType">
<el-input v-model="splitForm.coatingType" placeholder="请输入镀层种类" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="splitForm.remark" placeholder="请输入备注" type="textarea" />
</el-form-item>
<el-form-item>
<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 class="split-detail-card" v-else-if="selectedSplitItem">
<el-card title="分条钢卷详情" shadow="hover">
<!-- <el-button type="primary" @click="handlePrint(selectedSplitItem)">打印</el-button> -->
<el-descriptions :column="2" border size="large">
<el-descriptions-item label="入场钢卷号">{{ selectedSplitItem.enterCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="当前钢卷号">{{ selectedSplitItem.currentCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="厂家原料卷号">{{ selectedSplitItem.supplierCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="逻辑库位">{{ selectedSplitItem.warehouseName || '-' }}</el-descriptions-item>
<el-descriptions-item label="实际库区">{{ selectedSplitItem.actualWarehouseName || '-'
}}</el-descriptions-item>
<el-descriptions-item label="班组">{{ selectedSplitItem.team || '-' }}</el-descriptions-item>
<el-descriptions-item label="材料类型">{{ selectedSplitItem.materialType || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品/原料">{{ selectedSplitItem.itemName || '-' }}</el-descriptions-item>
<el-descriptions-item label="质量状态">{{ selectedSplitItem.qualityStatus || '-' }}</el-descriptions-item>
<el-descriptions-item label="切边要求">{{ selectedSplitItem.trimmingRequirement || '-'
}}</el-descriptions-item>
<el-descriptions-item label="打包状态">{{ selectedSplitItem.packingStatus || '-' }}</el-descriptions-item>
<el-descriptions-item label="包装要求">{{ selectedSplitItem.packagingRequirement || '-'
}}</el-descriptions-item>
<el-descriptions-item label="毛重">{{ selectedSplitItem.grossWeight || '-' }} kg</el-descriptions-item>
<el-descriptions-item label="净重">{{ selectedSplitItem.netWeight || '-' }} kg</el-descriptions-item>
<el-descriptions-item label="长度" v-if="selectedSplitItem.length">{{ selectedSplitItem.length }}
m</el-descriptions-item>
<el-descriptions-item label="调制度">{{ selectedSplitItem.temperGrade || '-' }}</el-descriptions-item>
<el-descriptions-item label="镀层种类">{{ selectedSplitItem.coatingType || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ selectedSplitItem.remark || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<!-- 初始提示 -->
<div class="empty-tip" v-else>
<el-empty description="请选择左侧已分条钢卷查看详情,或点击「新增分条」创建新分条"></el-empty>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { getMaterialCoil, listMaterialCoil } from '@/api/wms/coil'
import { completeAction } from '@/api/wms/pendingAction'
import { getMaterialCoil, listMaterialCoil, createSpecialChild, completeSpecialSplit, updateMaterialCoilSimple, checkCoilNo, delMaterialCoil } from '@/api/wms/coil'
import { completeAction, getPendingAction, updatePendingAction } from '@/api/wms/pendingAction'
import ProductSelect from "@/components/KLPService/ProductSelect";
import RawMaterialSelect from "@/components/KLPService/RawMaterialSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
export default {
name: 'StepSplit',
props: {
actionId: {
type: String,
required: true,
export default {
name: 'StepSplit',
props: {
actionId: {
type: String,
required: true,
},
coilId: {
type: String,
required: true,
},
actionStatus: {
type: Number,
default: 0,
},
},
components: {
ProductSelect,
RawMaterialSelect,
WarehouseSelect,
ActualWarehouseSelect,
},
dicts: ['coil_quality_status'],
data() {
return {
// 待分条钢卷基础信息
coilInfo: {},
loading: false,
// 分条表单数据
splitForm: {
coilId: '', // 分条钢卷ID编辑时赋值
enterCoilNo: '',
currentCoilNo: '',
supplierCoilNo: '',
warehouseId: '',
actualWarehouseId: '',
team: '',
materialType: '',
itemType: '', // product/raw_material
itemId: '',
qualityStatus: '',
trimmingRequirement: '',
packingStatus: '',
packagingRequirement: '',
grossWeight: '',
netWeight: '',
length: '',
temperGrade: '',
coatingType: '',
remark: '',
},
coilId: {
type: String,
required: true,
// 已分条钢卷列表
splitList: [],
// 列表加载状态
splitListLoading: false,
// 选中的分条项(用于显示详情)
selectedSplitItem: null,
// 是否显示分条表单
showSplitForm: false,
// 表单验证规则
rules: {
currentCoilNo: [
{ required: true, message: "当前钢卷号不能为空", trigger: "blur" },
// 仅在新增的时候校验
{
validator: (rule, value, callback) => {
// 没有coilId则为新增 触发校验
checkCoilNo({ currentCoilNo: value, coilId: this.splitForm.coilId }).then(res => {
const { duplicateType } = res.data;
if (duplicateType === 'current' || duplicateType === 'both') {
// alert('当前钢卷号重复,请重新输入');
callback(new Error('当前钢卷号重复,请重新输入'));
} 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,
}
},
computed: {
// 动态获取成品/原料标签
getItemLabel() {
return this.splitForm.itemType === 'product' ? '成品' : this.splitForm.itemType === 'raw_material' ? '原料' : '材料项'
},
},
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
}
},
},
data() {
return {
actionId: {
immediate: true,
handler(val) {
// 若actionId变化需要重新加载数据可在此补充逻辑
},
},
},
methods: {
// 查询待分条的钢卷信息
async getCoilInfo() {
try {
const res = await getMaterialCoil(this.coilId)
if (res.code === 200) {
this.coilInfo = res.data || {}
} else {
this.$message.error('查询钢卷信息失败:' + res.msg)
}
} catch (error) {
this.$message.error('查询钢卷信息异常:' + error.message)
}
},
methods: {
// 查询待分条的钢卷信息
async getCoilInfo() {
},
// 查询钢卷的已分条列表
async getSplitList() {
},
async handlePrint(row) {
this.$emit('print', row)
},
// 新增一个分条表单
async addSplitForm() {
},
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 addSplit() {
},
// 刷新分条列表
async refresh() {
this.loading = true
await this.getSplitList()
await this.getCoilInfo()
this.loading = false
},
// 完成整体分条
async completeSplit() {
},
}
}
</script>
// 查询钢卷的已分条列表
async getSplitList() {
this.splitListLoading = true
try {
if (!this.actionId) {
return
}
const action = await getPendingAction(this.actionId)
const coilIds = action.data.remark;
console.log('coilIds', coilIds)
if (!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 || ''
},
// 重置分条表单
resetSplitForm() {
this.$refs.splitFormRef?.resetFields()
this.splitForm = {
coilId: undefined,
enterCoilNo: '',
currentCoilNo: '',
supplierCoilNo: '',
warehouseId: '',
actualWarehouseId: '',
team: '',
materialType: '',
itemType: '',
itemId: '',
qualityStatus: '',
trimmingRequirement: '',
packingStatus: '',
packagingRequirement: '',
grossWeight: '',
netWeight: '',
length: '',
temperGrade: '',
coatingType: '',
remark: '',
parentCoilId: this.coilId,
}
},
// 材料类型变更处理
handleMaterialTypeChange(val) {
// 根据材料类型设置itemType
this.splitForm.itemType = val === '成品' ? 'product' : val === '原料' ? 'raw_material' : ''
},
// 选中分条列表项(显示详情)
handleSplitItemClick(row) {
this.selectedSplitItem = row
this.showSplitForm = false
},
// 编辑分条项
async handleEditSplit(row) {
this.showSplitForm = true
this.selectedSplitItem = null
// 赋值表单数据
this.splitForm = { ...row }
// 同步材料类型和长度显示状态
this.handleMaterialTypeChange(row.materialType)
},
// 新增/编辑分条
async addSplit() {
try {
// 表单验证
const valid = await this.$refs.splitFormRef.validate()
if (!valid) {
return
}
// 区分新增/编辑有coilId则为编辑否则为新增
let res
this.buttonLoading = true
if (this.splitForm.coilId) {
// 编辑分条:调用更新接口
res = await updateMaterialCoilSimple(this.splitForm)
} else {
// 新增分条:调用创建接口
res = await createSpecialChild(this.coilId, this.actionId, this.splitForm)
}
this.$message.success(this.splitForm.coilId ? '编辑分条成功' : '新增分条成功')
// 重置表单
this.resetSplitForm()
this.showSplitForm = false
// 刷新分条列表
this.getSplitList()
} catch (error) {
// 表单验证失败时的提示
if (error.name !== 'ValidationError') {
this.$message.error((this.splitForm.coilId ? '编辑' : '新增') + '分条异常:' + error.message)
}
} finally {
this.buttonLoading = false
}
},
// 完成整体分条
async completeSplit() {
this.$confirm('确认完成整体分条操作?完成后将无法修改分条信息', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
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)
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)
}
}
})
},
},
}
</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;
}
</style>