Browse Source

2025-09-17 pda 生产领料

master
fengyuan_yang 4 months ago
parent
commit
043a632d9e
  1. 8
      src/api/production.js
  2. 383
      src/views/modules/production-pick/productionPicking.vue
  3. 14
      src/views/modules/production-pick/productionPickingDetail.vue

8
src/api/production.js

@ -28,4 +28,10 @@ export const confirmProductionPicking = data => createAPI(`production/outbound/c
* 获取出库单物料清单
* @param {Object} data - 查询参数 {site, buNo, outboundNo}
*/
export const getOutboundMaterialList = data => createAPI(`production/outbound/materialList`, 'post', data)
export const getOutboundMaterialList = data => createAPI(`production/outbound/materialList`, 'post', data)
/**
* 获取出库单物料明细列表
* @param {Object} data - 查询参数 {site, outboundNo}
*/
export const getOutboundMaterialDetails = data => createAPI(`production/outbound/materialDetails`, 'post', data)

383
src/views/modules/production-pick/productionPicking.vue

@ -23,55 +23,151 @@
</div>
<!-- 出库单列表 -->
<div class="content-area">
<div class="outbound-list">
<!-- 当没有选中出库单时显示所有出库单 -->
<template v-if="!selectedOutbound">
<div
v-for="(item, index) in outboundList"
:key="index"
class="outbound-card"
@click="selectOutbound(item)"
>
<div class="card-title">
<span class="title-label">出库单号{{ item.outboundNo }}</span>
<span class="title-value">{{ item.relatedNo }}</span>
</div>
<div class="card-details">
<div class="detail-item">
<div class="detail-label">标签张数</div>
<div class="detail-value">
<span class="qualified">{{ item.pickedLabels }}</span><span class="total">{{ item.totalLabels }}</span>
</div>
</div>
<div class="detail-item">
<div class="detail-label">物料总数</div>
<div class="detail-value">
<span class="qualified">{{ item.pickedQty }}</span><span class="total">{{ item.totalQty }}</span>
</div>
</div>
<div class="detail-item">
<div class="detail-label">状态</div>
<div class="detail-value">{{ item.status || "待出库" }}</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="outboundList.length === 0 && !loading" class="empty-state">
<i class="el-icon-box"></i>
<p>暂无待领料出库单</p>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<i class="el-icon-loading"></i>
<p>加载中...</p>
</div>
</template>
<!-- 当选中出库单时只显示选中的出库单 -->
<div
v-for="(item, index) in outboundList"
:key="index"
class="outbound-card"
@click="goToPickingPage(item)"
v-else
class="outbound-card selected"
@click="deselectOutbound"
>
<div class="card-title">
<span class="title-label">出库单号</span>
<span class="title-value">{{ item.outboundNo }}</span>
<span class="title-label">出库单号{{ selectedOutbound.outboundNo }}</span>
<span class="title-value">{{ selectedOutbound.relatedNo }}</span>
<i class="el-icon-arrow-left back-icon" title="返回列表"></i>
</div>
<div class="card-details">
<div class="detail-item">
<div class="detail-label">关联单号</div>
<div class="detail-value">{{ item.relatedNo }}</div>
</div>
<div class="detail-item">
<div class="detail-label">标签张数</div>
<div class="detail-value">
<span class="qualified">{{ item.pickedLabels }}</span><span class="total">{{ item.totalLabels }}</span>
<span class="qualified">{{ selectedOutbound.pickedLabels }}</span><span class="total">{{ selectedOutbound.totalLabels }}</span>
</div>
</div>
<div class="detail-item">
<div class="detail-label">物料总数</div>
<div class="detail-value">
<span class="qualified">{{ item.pickedQty }}</span><span class="total">{{ item.totalQty }}</span>
<span class="qualified">{{ selectedOutbound.pickedQty }}</span><span class="total">{{ selectedOutbound.totalQty }}</span>
</div>
</div>
<div class="detail-item">
<div class="detail-label">状态</div>
<div class="detail-value">{{ selectedOutbound.status || "待出库" }}</div>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="outboundList.length === 0 && !loading" class="empty-state">
<i class="el-icon-box"></i>
<p>暂无待领料出库单</p>
</div>
<!-- 物料详情列表 -->
<div
class="content-area"
v-if="selectedOutbound && materialList.length > 0"
>
<div
v-for="(material, index) in materialList"
:key="index"
class="material-card"
@click="selectMaterial(material)"
>
<!-- 第一行工单号| 需求数量 -->
<div class="card-row">
<span class="field-item">
<span class="field-label">工单号</span>
<span class="field-value">{{ material.orderNo }}</span>
</span>
<span class="field-item">
<span class="field-label">需求数量</span>
<span class="field-value">{{ material.requiredQty || 0 }}</span>
</span>
</div>
<!-- 第二行物料编码| 单位 -->
<div class="card-row">
<span class="field-item">
<span class="field-label">物料编码</span>
<span class="field-value">{{ material.partNo }}</span>
</span>
<span class="field-item">
<span class="field-label">单位</span>
<span class="field-value">{{ material.umId}}</span>
</span>
</div>
<!-- 第三行物料名称全宽 -->
<div class="card-row-full">
<span class="field-label">物料名称</span>
<span class="field-value">{{ material.materialDesc || material.partDesc }}</span>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<i class="el-icon-loading"></i>
<p>加载中...</p>
<!-- 第四行物料总数全宽 -->
<div class="card-row-full">
<span class="field-label">物料总数</span>
<span class="field-value">{{ material.pickedQty || 0 }}/{{ material.totalQty || 0 }}</span>
</div>
</div>
</div>
<!-- 物料列表加载状态 -->
<div v-if="selectedOutbound && materialListLoading" class="loading-state">
<i class="el-icon-loading"></i>
<p>加载物料详情中...</p>
</div>
<!-- 物料列表空状态 -->
<div v-if="selectedOutbound && !materialListLoading && materialList.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无物料详情</p>
</div>
</div>
</template>
<script>
import { getProductionOutboundList } from "@/api/production.js";
import { getProductionOutboundList, getOutboundMaterialDetails } from "@/api/production.js";
import { getCurrentWarehouse } from '@/utils'
import moment from 'moment';
@ -80,7 +176,10 @@ export default {
return {
searchCode: '',
outboundList: [],
loading: false
loading: false,
selectedOutbound: null,
materialList: [],
materialListLoading: false
};
},
methods: {
@ -154,13 +253,63 @@ export default {
});
},
//
goToPickingPage(item) {
//
selectOutbound(item) {
this.selectedOutbound = item;
this.loadMaterialList(item);
},
//
deselectOutbound() {
this.selectedOutbound = null;
this.materialList = [];
},
//
loadMaterialList(item) {
this.materialListLoading = true;
const params = {
site: localStorage.getItem('site'),
outboundNo: item.outboundNo
};
getOutboundMaterialDetails(params).then(({ data }) => {
this.materialListLoading = false;
if (data && data.code === 0) {
this.materialList = (data.data || []).map((item, index) => ({
...item,
//
materialCode: item.partNo,
materialDesc: item.partDescription,
requiredQty: item.requiredQty,
pickedQty: 0, // 0
partNo: item.partNo,
partDesc: item.partDescription,
unit: '个' //
}));
} else {
this.$message.error(data.msg || '获取物料明细失败');
this.materialList = [];
}
}).catch(error => {
this.materialListLoading = false;
console.error('获取物料明细失败:', error);
this.$message.error('获取物料明细失败');
this.materialList = [];
});
},
//
selectMaterial(material) {
this.$router.push({
name: 'productionPickingDetail',
params: {
buNo: item.buNo,
outboundNo: item.outboundNo
buNo: this.selectedOutbound.buNo,
outboundNo: this.selectedOutbound.outboundNo,
relatedNo: material.orderNo,
materialCode: material.materialCode || material.partNo,
materialDesc: material.materialDesc || material.partDesc
}
});
}
@ -226,9 +375,8 @@ export default {
background: white;
}
/* 内容区域 */
.content-area {
flex: 1;
/* 出库单列表 */
.outbound-list {
overflow-y: auto;
padding: 12px 16px;
}
@ -242,6 +390,7 @@ export default {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.outbound-card:hover {
@ -249,13 +398,66 @@ export default {
transform: translateY(-1px);
}
.outbound-card.selected {
border-color: #17B3A3;
background: #f0fffe;
}
.outbound-card:active {
transform: translateY(0);
}
/* 内容区域 */
.content-area {
flex: 1;
overflow-y: auto;
padding: 8px 0 12px 0;
background: #f8f9fa;
margin: 0 16px 12px 16px;
border-radius: 0 0 8px 8px;
}
/* 物料卡片 */
.material-card {
background: white;
border-radius: 6px;
margin-bottom: 8px;
margin-left: 20px;
margin-right: 16px;
padding: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #e8f4f3;
position: relative;
}
.material-card::before {
content: '';
position: absolute;
left: -12px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 20px;
background: #17B3A3;
border-radius: 2px;
}
.material-card:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
transform: translateY(-1px);
border-color: #17B3A3;
}
.material-card:active {
transform: translateY(0);
}
/* 卡片标题 */
.card-title {
margin-bottom: 12px;
position: relative;
}
.title-label {
@ -269,7 +471,103 @@ export default {
font-size: 16px;
font-weight: bold;
color: #333;
margin-left: 20px;
margin-left: 16px;
}
.back-icon {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
color: #17B3A3;
cursor: pointer;
transition: color 0.2s ease;
}
.back-icon:hover {
color: #0d8f7f;
}
/* 物料描述行 */
.part-desc-row {
margin-bottom: 8px;
padding: 0 4px;
}
.desc-text {
font-size: 11px;
color: #666;
line-height: 1.3;
word-break: break-all;
}
/* 物料卡片内的字段行样式 */
.material-card .card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
gap: 8px;
}
.material-card .card-row-full {
margin-bottom: 6px;
display: flex;
align-items: center;
}
.material-card .field-item {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.material-card .field-label {
font-size: 11px;
color: #666;
white-space: nowrap;
margin-right: 4px;
}
.material-card .field-value {
font-size: 12px;
color: #333;
font-weight: 500;
word-break: break-all;
flex: 1;
}
/* 物料卡片内的详情样式 */
.material-card .card-details {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 3px;
}
.material-card .detail-item {
flex: 1;
text-align: center;
min-width: 45px;
max-width: 60px;
}
.material-card .detail-label {
font-size: 10px;
color: #666;
margin-bottom: 3px;
line-height: 1.2;
margin-left: -8px;
}
.material-card .detail-value {
font-size: 11px;
color: #333;
font-weight: 500;
line-height: 1.2;
margin-left: -8px;
}
/* 卡片详情 */
@ -363,6 +661,7 @@ export default {
margin: 0;
}
/* 响应式设计 */
@media (max-width: 360px) {
.header-bar {
@ -373,14 +672,27 @@ export default {
padding: 8px 12px;
}
.outbound-list {
padding: 8px 12px;
}
.content-area {
padding: 8px 12px;
}
.outbound-card {
.outbound-card, .material-card {
padding: 12px;
}
.material-card {
margin-left: 16px;
margin-right: 12px;
}
.content-area {
margin: 0 12px 8px 12px;
}
.card-details {
flex-wrap: wrap;
gap: 6px;
@ -391,5 +703,10 @@ export default {
margin-bottom: 6px;
min-width: 50px;
}
.material-card .detail-item {
flex: 0 0 45%;
min-width: 40px;
}
}
</style>

14
src/views/modules/production-pick/productionPickingDetail.vue

@ -180,7 +180,8 @@ export default {
showMaterialDialog: false,
materialList: [],
materialListLoading: false,
isRemoveMode: false //
isRemoveMode: false, //
relatedNo: ''
};
},
methods: {
@ -208,7 +209,8 @@ export default {
outboundNo: this.outboundNo,
warehouseId: getCurrentWarehouse(),
site: localStorage.getItem('site'),
buNo: this.buNo
buNo: this.buNo,
relatedNo: this.relatedNo
};
validateLabelWithOutbound(params).then(({ data }) => {
@ -228,7 +230,8 @@ export default {
labelCode: result.labelCode,
partNo: result.partNo,
quantity: result.quantity,
batchNo: result.batchNo
batchNo: result.batchNo,
locationId: result.locationId
});
});
this.$message.success(`标签验证成功,共添加 ${resultList.length} 条记录`);
@ -263,6 +266,7 @@ export default {
buNo: this.buNo,
outboundNo: this.outboundNo,
warehouseId: getCurrentWarehouse(),
relatedNo: this.relatedNo,
labels: this.labelList.map(label => ({
labelCode: label.labelCode,
quantity: label.quantity,
@ -270,7 +274,7 @@ export default {
partNo: label.partNo,
locationId: label.locationId
}))
};
}
confirmProductionPicking(params).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success('操作成功');
@ -357,6 +361,7 @@ export default {
const params = {
outboundNo: this.outboundNo,
buNo: this.buNo,
relatedNo: this.relatedNo,
warehouseId: getCurrentWarehouse(),
site: localStorage.getItem('site'),
};
@ -378,6 +383,7 @@ export default {
//
this.outboundNo = this.$route.params.outboundNo;
this.buNo = this.$route.params.buNo;
this.relatedNo = this.$route.params.relatedNo;
if (!this.outboundNo || !this.buNo) {
this.$message.error('参数错误');
this.$router.back();

Loading…
Cancel
Save