|
|
|
@ -1,187 +1,121 @@ |
|
|
|
<template> |
|
|
|
<div class="pick-container"> |
|
|
|
<van-nav-bar title="生产拣料" left-arrow @click-left="$router.back()" /> |
|
|
|
|
|
|
|
<!-- 订单信息 --> |
|
|
|
<div class="order-info"> |
|
|
|
<div class="info-header"> |
|
|
|
<div class="order-no">{{ orderInfo.orderNo }}</div> |
|
|
|
<div class="order-status">{{ getStatusText(orderInfo.status) }}</div> |
|
|
|
<!-- 顶部栏 --> |
|
|
|
<div class="header-bar"> |
|
|
|
<div class="header-left" @click="$router.back()"> |
|
|
|
<i class="el-icon-arrow-left"></i> |
|
|
|
<span>拣货出库</span> |
|
|
|
</div> |
|
|
|
<div class="header-right" @click="$router.push({ path: '/' })"> |
|
|
|
首页 |
|
|
|
</div> |
|
|
|
<van-cell-group> |
|
|
|
<van-cell title="产品编码" :value="orderInfo.productCode" /> |
|
|
|
<van-cell title="产品名称" :value="orderInfo.productName" /> |
|
|
|
<van-cell title="生产数量" :value="orderInfo.planQuantity" /> |
|
|
|
<van-cell title="工作中心" :value="orderInfo.workCenter" /> |
|
|
|
</van-cell-group> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 物料清单 --> |
|
|
|
<div class="material-section"> |
|
|
|
<div class="section-title">物料清单</div> |
|
|
|
<div |
|
|
|
v-for="(item, index) in materialList" |
|
|
|
:key="index" |
|
|
|
class="material-item" |
|
|
|
> |
|
|
|
<div class="material-info"> |
|
|
|
<div class="material-name">{{ item.materialCode }} - {{ item.materialName }}</div> |
|
|
|
<div class="material-spec">规格:{{ item.specification }}</div> |
|
|
|
<div class="material-location"> |
|
|
|
<van-tag type="primary" size="small">{{ item.locationCode }}</van-tag> |
|
|
|
<span class="stock-info">库存:{{ item.stock }}</span> |
|
|
|
<!-- 搜索框 --> |
|
|
|
<div class="search-container"> |
|
|
|
<el-input |
|
|
|
v-model="scanCode" |
|
|
|
placeholder="请扫描标签条码" |
|
|
|
clearable |
|
|
|
@keyup.enter.native="handleAdd" |
|
|
|
ref="scanInput" |
|
|
|
/> |
|
|
|
<el-button type="primary" @click="handleAdd" class="add-btn">添加</el-button> |
|
|
|
</div> |
|
|
|
<div class="material-quantity"> |
|
|
|
需求:{{ item.requiredQuantity }} | 已发:{{ item.issuedQuantity }} |
|
|
|
|
|
|
|
<!-- 订单信息卡片 --> |
|
|
|
<div class="order-card"> |
|
|
|
<div class="order-row"> |
|
|
|
<span class="label">出库单号</span> |
|
|
|
<span class="value">{{ orderInfo.orderNo }}</span> |
|
|
|
</div> |
|
|
|
<div class="order-row"> |
|
|
|
<span class="label">关联单号</span> |
|
|
|
<span class="value">{{ orderInfo.linkedNo }}</span> |
|
|
|
</div> |
|
|
|
<div class="pick-input"> |
|
|
|
<van-stepper |
|
|
|
v-model="item.currentPick" |
|
|
|
:min="0" |
|
|
|
:max="Math.min(item.requiredQuantity - item.issuedQuantity, item.stock)" |
|
|
|
integer |
|
|
|
/> |
|
|
|
<div class="order-row"> |
|
|
|
<span class="label">标签张数</span> |
|
|
|
<span class="value success">{{ orderInfo.labelCount }}/{{ orderInfo.labelTotal }}</span> |
|
|
|
</div> |
|
|
|
<div class="order-row"> |
|
|
|
<span class="label">物料总数</span> |
|
|
|
<span class="value success">{{ orderInfo.materialCount }}/{{ orderInfo.materialTotal }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 批次信息 --> |
|
|
|
<div class="batch-section"> |
|
|
|
<div class="section-title">批次信息</div> |
|
|
|
<van-field |
|
|
|
v-model="batchInfo.batchNo" |
|
|
|
label="批次号" |
|
|
|
placeholder="请输入或扫描批次号" |
|
|
|
> |
|
|
|
<template #right-icon> |
|
|
|
<van-icon name="scan" @click="handleScanBatch" /> |
|
|
|
</template> |
|
|
|
</van-field> |
|
|
|
<van-field |
|
|
|
v-model="batchInfo.workOrder" |
|
|
|
label="工单号" |
|
|
|
:value="orderInfo.orderNo" |
|
|
|
disabled |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 备注 --> |
|
|
|
<div class="remark-section"> |
|
|
|
<van-field |
|
|
|
v-model="remark" |
|
|
|
label="备注" |
|
|
|
type="textarea" |
|
|
|
placeholder="请输入发料备注" |
|
|
|
rows="3" |
|
|
|
autosize |
|
|
|
/> |
|
|
|
<!-- 出库信息确认表格 --> |
|
|
|
<div class="table-section"> |
|
|
|
<div class="table-title">出库信息确认</div> |
|
|
|
<el-table :data="tableData" border style="width: 100%" class="pick-table"> |
|
|
|
<el-table-column prop="no" label="NO." align="center" /> |
|
|
|
<el-table-column prop="labelCode" label="标签条码" align="center" /> |
|
|
|
<el-table-column prop="materialCode" label="物料编码" align="center" /> |
|
|
|
<el-table-column prop="unit" label="单位" align="center" /> |
|
|
|
<el-table-column prop="labelCount" label="标签数量" align="center" /> |
|
|
|
</el-table> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 底部按钮 --> |
|
|
|
<div class="bottom-actions"> |
|
|
|
<van-button |
|
|
|
type="primary" |
|
|
|
block |
|
|
|
:loading="submitting" |
|
|
|
@click="handleSubmit" |
|
|
|
> |
|
|
|
确认发料 |
|
|
|
</van-button> |
|
|
|
<el-button type="primary" @click="handleConfirm" :loading="submitting">确定</el-button> |
|
|
|
<el-button @click="handleCancel">取消</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
export default { |
|
|
|
name: 'ProductionIssuePick', |
|
|
|
name: 'ProductionPickOut', |
|
|
|
data() { |
|
|
|
return { |
|
|
|
scanCode: '', |
|
|
|
submitting: false, |
|
|
|
orderInfo: { |
|
|
|
orderNo: 'MO202401001', |
|
|
|
productCode: 'PROD001', |
|
|
|
productName: '成品A', |
|
|
|
planQuantity: 100, |
|
|
|
workCenter: '装配线1', |
|
|
|
status: 0 |
|
|
|
orderNo: 'W20250721001', |
|
|
|
linkedNo: '20250723001-1', |
|
|
|
labelCount: 10, |
|
|
|
labelTotal: 20, |
|
|
|
materialCount: 100, |
|
|
|
materialTotal: 2000 |
|
|
|
}, |
|
|
|
materialList: [ |
|
|
|
{ |
|
|
|
materialCode: 'MAT001', |
|
|
|
materialName: '原材料A', |
|
|
|
specification: '100*50*20mm', |
|
|
|
locationCode: 'A01-01-01', |
|
|
|
stock: 500, |
|
|
|
requiredQuantity: 200, |
|
|
|
issuedQuantity: 0, |
|
|
|
currentPick: 0 |
|
|
|
}, |
|
|
|
{ |
|
|
|
materialCode: 'MAT002', |
|
|
|
materialName: '原材料B', |
|
|
|
specification: '200*100*30mm', |
|
|
|
locationCode: 'A01-02-01', |
|
|
|
stock: 300, |
|
|
|
requiredQuantity: 150, |
|
|
|
issuedQuantity: 0, |
|
|
|
currentPick: 0 |
|
|
|
tableData: [ |
|
|
|
{ no: 8, labelCode: '2025070002', materialCode: '2025070002', unit: '↑', labelCount: 2 }, |
|
|
|
{ no: 7, labelCode: '2025070003', materialCode: '2025070003', unit: '↑', labelCount: 3 }, |
|
|
|
{ no: 6, labelCode: '2025070004', materialCode: '2025070004', unit: '↑', labelCount: 4 }, |
|
|
|
{ no: 5, labelCode: '2025070005', materialCode: '2025070005', unit: '↑', labelCount: 5 }, |
|
|
|
{ no: 4, labelCode: '2025070006', materialCode: '2025070006', unit: '↑', labelCount: 6 }, |
|
|
|
{ no: 3, labelCode: '2025070006', materialCode: '2025070006', unit: '↑', labelCount: 6 }, |
|
|
|
{ no: 2, labelCode: '2025070006', materialCode: '2025070006', unit: '↑', labelCount: 6 }, |
|
|
|
{ no: 1, labelCode: '2025070006', materialCode: '2025070006', unit: '↑', labelCount: 6 } |
|
|
|
] |
|
|
|
} |
|
|
|
], |
|
|
|
batchInfo: { |
|
|
|
batchNo: '', |
|
|
|
workOrder: '' |
|
|
|
}, |
|
|
|
remark: '', |
|
|
|
submitting: false |
|
|
|
} |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
this.loadOrderData() |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
loadOrderData() { |
|
|
|
const orderNo = this.$route.params.orderNo |
|
|
|
console.log('加载生产订单数据:', orderNo) |
|
|
|
handleAdd() { |
|
|
|
if (!this.scanCode.trim()) return; |
|
|
|
// 实际应调用接口或校验后添加到tableData |
|
|
|
this.$message.success('添加成功(演示)'); |
|
|
|
this.scanCode = ''; |
|
|
|
this.$nextTick(() => { |
|
|
|
if (this.$refs.scanInput) this.$refs.scanInput.focus(); |
|
|
|
}); |
|
|
|
}, |
|
|
|
handleScanBatch() { |
|
|
|
this.$toast('扫描批次功能开发中...') |
|
|
|
handleConfirm() { |
|
|
|
this.submitting = true; |
|
|
|
setTimeout(() => { |
|
|
|
this.submitting = false; |
|
|
|
this.$message.success('出库成功'); |
|
|
|
this.$router.back(); |
|
|
|
}, 1000); |
|
|
|
}, |
|
|
|
async handleSubmit() { |
|
|
|
// 验证发料数量 |
|
|
|
const hasPick = this.materialList.some(item => item.currentPick > 0) |
|
|
|
if (!hasPick) { |
|
|
|
this.$toast('请输入发料数量') |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 检查库存 |
|
|
|
const insufficientStock = this.materialList.find(item => item.currentPick > item.stock) |
|
|
|
if (insufficientStock) { |
|
|
|
this.$toast(`${insufficientStock.materialName} 库存不足`) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
this.submitting = true |
|
|
|
|
|
|
|
try { |
|
|
|
await new Promise(resolve => setTimeout(resolve, 2000)) |
|
|
|
|
|
|
|
this.$toast.success('发料成功') |
|
|
|
this.$router.back() |
|
|
|
} catch (error) { |
|
|
|
this.$toast.fail('发料失败') |
|
|
|
} finally { |
|
|
|
this.submitting = false |
|
|
|
handleCancel() { |
|
|
|
this.$router.back(); |
|
|
|
} |
|
|
|
}, |
|
|
|
getStatusText(status) { |
|
|
|
const statusMap = { |
|
|
|
0: '待发料', |
|
|
|
1: '部分发料', |
|
|
|
2: '已完成' |
|
|
|
} |
|
|
|
return statusMap[status] || '未知' |
|
|
|
} |
|
|
|
mounted() { |
|
|
|
this.$nextTick(() => { |
|
|
|
if (this.$refs.scanInput) this.$refs.scanInput.focus(); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
@ -192,106 +126,132 @@ export default { |
|
|
|
background-color: #f7f8fa; |
|
|
|
padding-bottom: 80px; |
|
|
|
} |
|
|
|
|
|
|
|
.order-info { |
|
|
|
background: white; |
|
|
|
margin-bottom: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.info-header { |
|
|
|
.header-bar { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding: 16px; |
|
|
|
border-bottom: 1px solid #ebedf0; |
|
|
|
padding: 8px 16px; |
|
|
|
background: #17B3A3; |
|
|
|
color: white; |
|
|
|
height: 40px; |
|
|
|
min-height: 40px; |
|
|
|
} |
|
|
|
|
|
|
|
.order-no { |
|
|
|
font-size: 18px; |
|
|
|
font-weight: bold; |
|
|
|
color: #323233; |
|
|
|
.header-left { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
cursor: pointer; |
|
|
|
font-size: 16px; |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
.order-status { |
|
|
|
padding: 4px 8px; |
|
|
|
border-radius: 4px; |
|
|
|
font-size: 12px; |
|
|
|
color: white; |
|
|
|
background-color: #ff976a; |
|
|
|
.header-right { |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.material-section, |
|
|
|
.batch-section, |
|
|
|
.remark-section { |
|
|
|
.search-container { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
padding: 10px 16px; |
|
|
|
background: white; |
|
|
|
margin-bottom: 10px; |
|
|
|
} |
|
|
|
.search-container .el-input { |
|
|
|
flex: 1; |
|
|
|
} |
|
|
|
.add-btn { |
|
|
|
margin-left: 10px; |
|
|
|
background: #17B3A3; |
|
|
|
color: #fff; |
|
|
|
border: none; |
|
|
|
border-radius: 16px; |
|
|
|
height: 32px; |
|
|
|
min-width: 56px; |
|
|
|
font-size: 15px; |
|
|
|
font-weight: 500; |
|
|
|
box-shadow: none; |
|
|
|
transition: background 0.2s; |
|
|
|
} |
|
|
|
.add-btn:hover, .add-btn:focus { |
|
|
|
background: #13998c; |
|
|
|
color: #fff; |
|
|
|
} |
|
|
|
|
|
|
|
.section-title { |
|
|
|
.order-card { |
|
|
|
background: white; |
|
|
|
margin: 16px; |
|
|
|
border-radius: 8px; |
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
|
|
|
padding: 16px; |
|
|
|
font-size: 16px; |
|
|
|
font-weight: bold; |
|
|
|
color: #323233; |
|
|
|
border-bottom: 1px solid #ebedf0; |
|
|
|
margin-bottom: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-item { |
|
|
|
.order-row { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding: 16px; |
|
|
|
border-bottom: 1px solid #ebedf0; |
|
|
|
} |
|
|
|
|
|
|
|
.material-item:last-child { |
|
|
|
border-bottom: none; |
|
|
|
font-size: 15px; |
|
|
|
margin-bottom: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-info { |
|
|
|
flex: 1; |
|
|
|
.label { |
|
|
|
color: #969799; |
|
|
|
} |
|
|
|
|
|
|
|
.material-name { |
|
|
|
font-size: 16px; |
|
|
|
font-weight: bold; |
|
|
|
.value { |
|
|
|
color: #323233; |
|
|
|
margin-bottom: 4px; |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
.material-spec { |
|
|
|
font-size: 12px; |
|
|
|
color: #969799; |
|
|
|
margin-bottom: 6px; |
|
|
|
.value.success { |
|
|
|
color: #19be6b; |
|
|
|
} |
|
|
|
|
|
|
|
.material-location { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 4px; |
|
|
|
.table-section { |
|
|
|
background: white; |
|
|
|
margin: 0 16px 10px 16px; |
|
|
|
border-radius: 8px; |
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
|
|
|
padding: 16px 8px 8px 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.stock-info { |
|
|
|
font-size: 12px; |
|
|
|
color: #646566; |
|
|
|
.table-title { |
|
|
|
font-size: 15px; |
|
|
|
color: #17B3A3; |
|
|
|
font-weight: bold; |
|
|
|
margin-bottom: 8px; |
|
|
|
margin-left: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-quantity { |
|
|
|
.pick-table { |
|
|
|
font-size: 14px; |
|
|
|
color: #646566; |
|
|
|
} |
|
|
|
|
|
|
|
.pick-input { |
|
|
|
margin-left: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.bottom-actions { |
|
|
|
position: fixed; |
|
|
|
bottom: 0; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
padding: 16px; |
|
|
|
padding: 16px 0 24px 0; |
|
|
|
background: white; |
|
|
|
border-top: 1px solid #ebedf0; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
gap: 32px; |
|
|
|
} |
|
|
|
.bottom-actions .el-button { |
|
|
|
min-width: 120px; |
|
|
|
height: 36px; |
|
|
|
border-radius: 18px; |
|
|
|
font-size: 16px; |
|
|
|
font-weight: 500; |
|
|
|
border: 1.5px solid #17B3A3; |
|
|
|
color: #17B3A3; |
|
|
|
background: #fff; |
|
|
|
box-shadow: none; |
|
|
|
transition: background 0.2s, color 0.2s; |
|
|
|
} |
|
|
|
.bottom-actions .el-button:hover, .bottom-actions .el-button:focus { |
|
|
|
background: #17B3A3; |
|
|
|
color: #fff; |
|
|
|
border-color: #17B3A3; |
|
|
|
} |
|
|
|
.bottom-actions .el-button--primary { |
|
|
|
background: #17B3A3; |
|
|
|
color: #fff; |
|
|
|
border-color: #17B3A3; |
|
|
|
} |
|
|
|
.bottom-actions .el-button--primary:hover, .bottom-actions .el-button--primary:focus { |
|
|
|
background: #13998c; |
|
|
|
color: #fff; |
|
|
|
border-color: #13998c; |
|
|
|
} |
|
|
|
</style> |