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} * @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>
<!-- 出库单列表 --> <!-- 出库单列表 -->
<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 <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"> <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>
<div class="card-details"> <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-item">
<div class="detail-label">标签张数</div> <div class="detail-label">标签张数</div>
<div class="detail-value"> <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> </div>
<div class="detail-item"> <div class="detail-item">
<div class="detail-label">物料总数</div> <div class="detail-label">物料总数</div>
<div class="detail-value"> <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> </div>
<div class="detail-item">
<div class="detail-label">状态</div>
<div class="detail-value">{{ selectedOutbound.status || "待出库" }}</div>
</div>
</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> </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> </div>
</template> </template>
<script> <script>
import { getProductionOutboundList } from "@/api/production.js";
import { getProductionOutboundList, getOutboundMaterialDetails } from "@/api/production.js";
import { getCurrentWarehouse } from '@/utils' import { getCurrentWarehouse } from '@/utils'
import moment from 'moment'; import moment from 'moment';
@ -80,7 +176,10 @@ export default {
return { return {
searchCode: '', searchCode: '',
outboundList: [], outboundList: [],
loading: false
loading: false,
selectedOutbound: null,
materialList: [],
materialListLoading: false
}; };
}, },
methods: { 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({ this.$router.push({
name: 'productionPickingDetail', name: 'productionPickingDetail',
params: { 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; background: white;
} }
/* 内容区域 */
.content-area {
flex: 1;
/* 出库单列表 */
.outbound-list {
overflow-y: auto; overflow-y: auto;
padding: 12px 16px; padding: 12px 16px;
} }
@ -242,6 +390,7 @@ export default {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
border: 2px solid transparent;
} }
.outbound-card:hover { .outbound-card:hover {
@ -249,13 +398,66 @@ export default {
transform: translateY(-1px); transform: translateY(-1px);
} }
.outbound-card.selected {
border-color: #17B3A3;
background: #f0fffe;
}
.outbound-card:active { .outbound-card:active {
transform: translateY(0); 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 { .card-title {
margin-bottom: 12px; margin-bottom: 12px;
position: relative;
} }
.title-label { .title-label {
@ -269,7 +471,103 @@ export default {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
color: #333; 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; margin: 0;
} }
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 360px) { @media (max-width: 360px) {
.header-bar { .header-bar {
@ -373,14 +672,27 @@ export default {
padding: 8px 12px; padding: 8px 12px;
} }
.outbound-list {
padding: 8px 12px;
}
.content-area { .content-area {
padding: 8px 12px; padding: 8px 12px;
} }
.outbound-card {
.outbound-card, .material-card {
padding: 12px; padding: 12px;
} }
.material-card {
margin-left: 16px;
margin-right: 12px;
}
.content-area {
margin: 0 12px 8px 12px;
}
.card-details { .card-details {
flex-wrap: wrap; flex-wrap: wrap;
gap: 6px; gap: 6px;
@ -391,5 +703,10 @@ export default {
margin-bottom: 6px; margin-bottom: 6px;
min-width: 50px; min-width: 50px;
} }
.material-card .detail-item {
flex: 0 0 45%;
min-width: 40px;
}
} }
</style> </style>

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

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

Loading…
Cancel
Save