Files
shanghaiganxiangtuzhuangwor…/pages/outWarehouse/outWarehouse.vue
赵正易 9115f03533 feat(出库管理): 新增PDA出库功能模块
新增出库单列表、出库计划清单和成品出库页面
添加防抖函数工具和PDA出库相关API接口
重构出库逻辑,支持按计划批次出库和严格校验
优化扫码录入和出库操作流程,增加计划完成状态显示
2025-08-24 18:23:14 +08:00

908 lines
22 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>
<view class="common-nav-container">
<view class="title-box">
<!-- <span class="title-text">出库</span> -->
<view class="warehoseInfo-box">
<!-- 扫码 -->
<view>
<view v-if="searchType === 3" class="color1 aciton-box">请扫出库单</view>
<view v-if="searchType === 2" class="color2 aciton-box">请扫箱码</view>
<!-- 扫描操作 -->
<view class="pda-search-box">
<PdaScanInput @getInfo="handleGetInfo" :type="searchType"></PdaScanInput>
</view>
</view>
<!-- 出库单信息 -->
<uni-card is-full :title="'出库单号:' + outInfo.shipmentNum"
:extra="'备注:' + (outInfo.remarks ? outInfo.remarks : '无')">
<!-- <view class="row">
<span class="col">库存货物数{{ quantityTotal }}</span>
<span class="col">库存箱数{{ newMaterialList.length }}</span>
</view> -->
<view class="row">
<span class="col">当前已扫货物数{{ quantityTotal }}</span>
<span class="col">当前已扫箱数{{ newMaterialList.length }}</span>
</view>
</uni-card>
<!-- 选择物料号 -->
<u-input :value="'物料号:' + material_id" color="#2979ff" fontSize="20px" readonly>
<template slot="suffix">
<u-button type="primary" size="mini" @click="showChouseMaterial = true">选择物料号</u-button>
</template>
</u-input>
<CustomPopup :customStyle="{ padding: '20px' }" :show="showChouseMaterial" @close="chouseMaterialClose"
@clickMask="chouseMaterialClose" mode="center" closeable>
<view>
<CustomList height="240">
<CustomListItem v-for="(item, index) in columns[0]" :key="index"
@click="chouseMaterial({ name: item })">
<text class="cell-title">{{ item }}</text>
</CustomListItem>
</CustomList>
</view>
</CustomPopup>
<!-- <u-picker :show="show" :columns="columns" @cancel="pick_cancel" @confirm="pick_confirm"></u-picker> -->
<!-- 查看出库计划 -->
<u-button type="primary" @click="showPopup = true" :disabled="!outInfo.shipmentNum">出库计划查看</u-button>
<CustomPopup :customStyle="{ padding: '10px' }" mode="center" round closeable :show="showPopup"
@close="popupClose" @open="popupOpen" @clickMask="popupClose">
<view>
<CustomList height="240">
<view v-if="outOrderPlanList.length === 0 && !loading" class="empty-plan">
<text>出库计划未生成,请先生成出库计划</text>
</view>
<CustomListItem v-else v-for="(item, index) in outOrderPlanList" :key="index">
<view class="plan-item">
<view class="plan-header">
<view class="plan-title">
<text class="plan-title-text">{{ item.outOrder }} 计划批次号: {{ item.packageCode
}}</text>
</view>
<view class="plan-tags">
<text v-if="item.isError" class="tag error">异常</text>
<text v-if="item.isOver" class="tag success">已完成</text>
<text v-if="!item.isOver" class="tag info">未完成</text>
</view>
</view>
<view class="plan-content">
<text>物料号: {{ item.partnumber }}</text>
<text>仓库编号: {{ item.warehouseCode }}</text>
<text class="info">描述: {{ item.description }}</text>
<text class="primary">计划需求零件数: {{ item.requireNum }}</text>
<text class="info">库存现有箱数: {{ item.packageNum }} 库存现有零件数: {{ item.partnumberNum
}}</text>
<text class="warning">计划出库箱数: {{ item.packagePlanNum }} 计划出库零件数: {{
item.partnumberPlanNum
}}</text>
<text class="success">该批次已出箱数: {{ item.outPackageNum }} 该批次已出零件数: {{
item.outPartnumberNum
}}</text>
</view>
</view>
</CustomListItem>
</CustomList>
</view>
</CustomPopup>
<!-- <view>出库单号{{ outInfo.shipmentNum }}</view>
<view class="row">
<view>备注{{ outInfo.remarks }}</view>
</view>
<view class="row">
<span class="col">已扫货物数{{ quantityTotal }}</span>
<span class="col">已扫箱数{{ newMaterialList.length }}</span>
</view> -->
</view>
</view>
<!-- 功能切换 -->
<!-- <u-subsection :list="subsectionList" :current="subsectionCurrent" @change="subsectionChange"
fontSize="36"></u-subsection> -->
<!-- 货物列表 -->
<view class="scroll-view-box" v-if="subsectionCurrent === 0">
<view class="scroll-view-title">出库清单</view>
<view v-if="newMaterialList.length === 1">
<uni-card is-full :title="'批次号:' + newMaterialList[0].patchCode"
:extra="'数量:' + newMaterialList[0].quantity" @click="handleDeleteItem(0)">
<u--text size="26"
:text="'描述:' + (newMaterialList[0].productionDescribe ? newMaterialList[0].productionDescribe : '无描述,请检查零件号是否对应')"></u--text>
</uni-card>
</view>
</view>
<view class="scroll-view-box" v-if="subsectionCurrent === 1">
<view class="scroll-view-title">出库清单</view>
<view>
<CustomList height="260">
<CustomListItem v-for="(item, index) in newMaterialList" :key="index">
<uni-card is-full :title="'批次号:' + item.patchCode" :extra="'数量:' + item.quantity"
@click="handleDeleteItem(index)">
<u--text size="26"
:text="'描述:' + (item.productionDescribe ? item.productionDescribe : '无描述,请检查零件号是否对应')"></u--text>
</uni-card>
</CustomListItem>
</CustomList>
</view>
</view>
<!-- 底部按钮 -->
<view class="button-box">
<u-button style="width: 40%" type="primary" @click="handlerDoOut" :disabled="loading"
:loading="loading">货物出库</u-button>
<u-button style="width: 40%" type="success" @click="handlerSubmit" :disabled="loading"
:loading="loading">出库单完成</u-button>
</view>
</view>
</template>
<script>
import materialItem from '@/components/material-item/material-item.vue';
import packageCard from '@/components/package-card/package-card.vue';
import CustomPopup from './components/CustomPopup.vue';
import CustomList from './components/CustomList.vue';
import CustomListItem from './components/CustomListItem.vue';
import * as WarehoseApi from '@/api/warehouse/warehose.js';
export default {
components: {
materialItem,
packageCard,
CustomPopup,
CustomList,
CustomListItem
},
data() {
return {
loading: false,
showPopup: false,
showChouseMaterial: false,
// 双击判定
touchNum: 0,
clearData: {},
// 出货单信息
outInfo: {
id: '',
// 出库单号
shipmentNum: '',
// 备注
remarks: '',
materialList: []
},
// 货物信息
lastPackageInfo: {
// 工单号
workoderID: '',
// 批次号(工单号+箱号+班组)
patchCode: '',
// 零件号
partNumner: '',
// 描述
productionDescribe: '',
// 出厂日期/生产日期
productionTime: '',
// 此箱数量
quantity: 0
},
// 新录入货物信息
newMaterialList: [],
//工单计划
outOrderPlanList: [],
// 1-仓库扫码 2-货物扫码 3-出货单扫码
searchType: 3,
subsectionList: ['单箱出库', '多箱出库'],
subsectionCurrent: 1,
columns: [
['无物料号']
],
material_id: '无物料号', //物料号
};
},
watch: {},
created() {
this.init();
},
computed: {
quantityTotal() {
let total = 0;
for (let i = 0; i < this.newMaterialList.length; i++) {
total += parseInt(this.newMaterialList[i].quantity) || 0;
}
return total;
}
},
methods: {
init() {
// 需要展示的参数:【零件号[物料号](35233201041) 描述(鲨鱼鳍) 箱号+班组(BNW240312023_18B1) 数量 时间 】
// 初始化,并且清空数据
this.clearData.outInfo = { ...this.outInfo };
this.clearData.newMaterialList = [...this.newMaterialList];
this.clearData.outOrderPlanList = [...this.outOrderPlanList];
this.searchType = 3;
},
clear() {
this.outInfo = { ...this.clearData.outInfo };
this.newMaterialList = [...this.clearData.newMaterialList];
this.outOrderPlanList = [...this.clearData.outOrderPlanList];
this.searchType = 3;
},
// 显示出库错误信息
showOutError(message) {
uni.showModal({
title: '提示',
content: message,
showCancel: false,
confirmText: '确认'
});
},
// 显示获取信息错误
showGetInfoError() {
uni.showToast({
title: '出库单信息获取失败!'
});
},
// 显示出库成功信息
showOutSuccess(count) {
uni.showModal({
title: '提示',
content: '出库成功!已成功出库' + count + '箱',
showCancel: false,
confirmText: '确认'
});
},
// 显示提交成功信息
showSubmitSuccess() {
uni.showModal({
title: '提示',
content: '此出库单已完成',
showCancel: false,
confirmText: '确认',
success: () => {
this.clear();
}
});
},
// 显示提交错误信息
showSubmitError() {
uni.showModal({
title: '提示',
content: '出库单完成异常!',
showCancel: false,
confirmText: '确认'
});
},
// 显示计划错误信息
showPlanError() {
uni.showModal({
title: '提示',
content: '获取出库计划失败',
showCancel: false,
confirmText: '确认'
});
},
// 显示箱数据错误信息
showBoxDataError(originalCode) {
uni.showModal({
title: '提示',
content: '箱数据有异常:' + originalCode,
showCancel: false,
confirmText: '确认'
});
},
// 显示删除确认
showDeleteConfirm(index) {
uni.showModal({
title: '删除提示',
content: '是否从列表中删除此货物?',
showCancel: true,
cancelText: '取消',
confirmText: '删除',
success: (res) => {
if (res.confirm) {
this.newMaterialList.splice(index, 1);
}
}
});
},
// 扫码信息录入
handleGetInfo(data, type) {
if (type === 3) {
// 出货单扫完后是扫箱号
this.searchType = 2;
this.outInfo = data;
this.newMaterialList = [];
let arry = [];
this.columns = [];
this.outInfo.materialList.forEach((item) => {
arry.push(item.partnumber);
});
if (arry.length > 0) {
this.columns.push(arry);
this.material_id = this.outInfo.materialList[0].partnumber;
} else {
this.columns.push(['无物料号']);
}
} else if (type === 2) {
if (this.loading) {
uni.showModal({
title: '提示',
content: '请等待加载完成!',
showCancel: false,
confirmText: '确定'
});
}
if (this.subsectionCurrent === 0 && this.newMaterialList.length > 0) {
uni.showModal({
title: '提示',
content: '当前货物未确认出库,请先点击下方出库按钮,出库当前货物!',
showCancel: false,
confirmText: '确定'
});
return;
}
if (this.subsectionCurrent === 1 && this.newMaterialList.length > 0) {
if (this.newMaterialList.length > 50) {
uni.showModal({
title: '提示',
content: '批量出库数量已达到50箱请先出库此50箱',
showCancel: false,
confirmText: '确定'
});
return;
}
for (let item of this.newMaterialList) {
if (item.patchCode === data.patchCode) {
uni.showModal({
title: '提示',
content: '此货物已录入过!',
showCancel: false,
confirmText: '确定'
});
return;
}
}
}
const checkData = {
production_packcode: data.originalCode,
shipment_num: this.outInfo.shipmentNum,
partnumber: this.material_id
};
this.loading = true;
//检查是否可以出货
WarehoseApi.checkProductionOut(checkData).then((res) => {
if (res.code === 200 && res.data) {
this.newMaterialList.push(data);
this.loading = false;
} else {
uni.showModal({
title: '提示',
content: '不可出库:' + res.msg,
showCancel: false,
confirmText: '确定'
});
this.loading = false;
}
});
// 此时扫描的是箱号
// TODO 检查扫描的箱号零件号是否在此工单下
// let flag = false;
// let list = JSON.parse(JSON.stringify(this.outOrderPlan));
// for(let item of list){
// // 扫描箱的工单号与计划中的批次号对比,数字是否在其中
// if(!item.Patchcode){
// continue;
// }
// if(item.Patchcode.includes(data.workoderID)){
// flag = true;
// break;
// }
// }
// if(!flag){
// // 此箱号不在计划内
// uni.showModal({
// title: '提示',
// content: '当前货物不在出库单计划内,请扫其他货物!',
// showCancel: false,
// confirmText: '确定'
// });
// return;
// }
// TODO 箱出库,并关联到此出库单下
}
},
// 货物出库
handlerDoOut() {
this.loading = true;
if (this.searchType === 3) {
uni.showModal({
title: '提示',
content: '请先扫出库单!',
showCancel: false,
confirmText: '确定'
});
this.loading = false;
return;
}
const length = this.newMaterialList.length;
if (length === 0) {
uni.showModal({
title: '提示',
content: '当前未扫描货物!',
showCancel: false,
confirmText: '确定'
});
this.loading = false;
return;
}
uni.showModal({
title: '操作',
content: '当前已扫描:' + length + '箱货物,是否确认执行出库操作?',
showCancel: true,
cancelText: '取消',
confirmText: '确认出库',
success: (res) => {
if (res.confirm) {
let data = {
shipmentNum: this.outInfo.shipmentNum,
patchCode: []
};
if (this.subsectionCurrent === 0) {
data.patchCode = [this.newMaterialList[0].patchCode];
} else if (this.subsectionCurrent === 1) {
// 提取货物patchCode
let patchCodeList = uni.$u.deepClone(this.newMaterialList);
let newPatchCodeList = [];
for (let item of patchCodeList) {
if (item.patchCode === "" || item.patchCode === null) {
this.showBoxDataError(item.originalCode);
this.loading = false;
return;
} else {
newPatchCodeList.push(item.patchCode)
}
}
data.patchCode = newPatchCodeList;
}
if (data.patchCode.length === 0) {
uni.showModal({
title: '提示',
content: '无出库箱,不可出库!',
showCancel: false,
confirmText: '确认'
});
this.loading = false;
return;
}
// 使用防抖函数避免重复提交
const debouncedDoOut = uni.$u.debounce(() => {
WarehoseApi.doMaterialOut(data).then((res) => {
if (res.code !== 200) {
this.showOutError('出库异常');
this.loading = false;
return;
} else {
if (res.data.item1 == 100) {
this.showOutError('此物料已经出库完成,不可以再出库');
this.newMaterialList = [];
this.loading = false;
} else if (res.data.item2 == 200) {
this.showOutError('不是此物料最早批次,无法出库');
this.newMaterialList = [];
this.loading = false;
} else {
this.showOutSuccess(res.data.item1);
this.newMaterialList = [];
this.loading = false;
}
}
}).catch((error) => {
// 处理请求失败的情况确保loading状态被重置
console.error('出库请求失败:', error);
uni.showToast({
title: '请求失败,请重试!'
});
this.loading = false;
});
}, 1000);
// 调用防抖函数
debouncedDoOut();
} else {
this.loading = false;
}
}
});
},
// 出库单完成
handlerSubmit() {
this.loading = true;
uni.showModal({
title: '操作',
content: '是否确认执行出库单完成操作?完成后将不可继续出库!',
showCancel: true,
cancelText: '取消',
confirmText: '确认出库单完成',
success: (res) => {
if (res.confirm) {
//TODO 变更出库单状态
const data = {
shipmentNum: this.outInfo.shipmentNum
};
// 使用防抖函数避免重复提交
const debouncedSubmit = uni.$u.debounce(() => {
WarehoseApi.doOverOutorderplan(data).then((res) => {
if (res.code === 200) {
this.showSubmitSuccess();
} else {
this.showSubmitError();
}
}).catch((error) => {
// 处理请求失败的情况确保loading状态被重置
console.error('出库单完成请求失败:', error);
uni.showToast({
title: '请求失败,请重试!'
});
this.loading = false;
});
}, 1000);
// 调用防抖函数
debouncedSubmit();
} else {
this.loading = false;
}
}
});
},
//选择物料
chouseMaterialClose() {
this.showChouseMaterial = false;
},
chouseMaterial(item) {
this.material_id = item.name;
this.chouseMaterialClose();
if (this.newMaterialList.length > 0) {
uni.showModal({
title: '提示',
content: '当前货物列表有货物!切换物料号将清空当前列表,是否继续?',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
success: (res) => {
if (res.confirm) {
this.newMaterialList = [];
} else {
this.material_id = '';
}
}
});
} else {
this.newMaterialList = [];
}
},
// 确认
pick_confirm(e) {
this.material_id = e.value[0];
this.show = false;
},
// 长按弹出删除
handleDeleteItem(index) {
// 双击判定
this.touchNum++;
// 设置500毫秒时间间隔
setTimeout(() => {
if (this.touchNum >= 2) {
this.showDeleteConfirm(index);
}
this.touchNum = 0;
}, 500);
},
subsectionChange(index) {
if (this.newMaterialList.length > 0) {
uni.showModal({
title: '提示',
content: '当前货物列表有货物!请先删除列表中的所有货物,然后才能切换模式。',
showCancel: false,
confirmText: '确认'
});
return;
}
this.subsectionCurrent = index;
},
// 弹窗打开
popupOpen() {
if (!this.outInfo.shipmentNum) {
uni.showModal({
title: '提示',
content: '请先扫描出库单!',
showCancel: false,
confirmText: '确认'
});
return;
}
this.getPlan();
},
// 弹窗关闭
popupClose() {
this.showPopup = false;
},
// 获取计划
getPlan() {
this.loading = true;
this.outOrderPlanList = [];
const data = {
shipment_num: this.outInfo.shipmentNum,
partnumber: this.material_id
}
WarehoseApi.getOutOrderPlanAndOutProductionNum(data).then(res => {
if (res.code === 200) {
this.outOrderPlanList = res.data;
this.loading = false;
} else {
this.showPlanError();
this.loading = false;
}
}).catch((error) => {
this.showPlanError();
this.loading = false;
})
},
}
};
</script>
<style scoped>
.title-box {
/* margin-top: 54px; */
/* margin-bottom: 20px; */
font-size: 26px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.warehoseInfo-box {
width: 100%;
font-size: 20px;
}
.warehoseInfo-box .row {
display: flex;
flex-direction: row;
}
.warehoseInfo-box .row .col {
width: 50%;
}
.aciton-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.color1 {
color: yellowgreen;
}
.color2 {
color: orange;
}
.pda-search-box {
margin-bottom: 5px;
}
.list-box {
width: 100%;
}
.scroll-view-title {
font-size: 24px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.scroll-view-box {
width: 98%;
margin: 0 auto;
margin-top: 10px;
padding-left: 10px;
padding-right: 10px;
height: 300px;
background-color: rgba(179, 179, 179, 0.3);
border-radius: 5px;
}
.scroll-view-last {
width: 100%;
height: 60px;
font-size: 20px;
color: rgba(0, 9, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 10px;
padding-left: 10px;
padding-right: 10px;
border-radius: 10px;
background-color: white;
}
.button-box {
width: 80%;
margin: 10px auto;
display: flex;
flex-direction: row;
}
/* 自定义弹窗样式 */
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.popup-container {
background-color: #fff;
position: relative;
max-width: 90%;
max-height: 90%;
}
.popup-container.center {
align-self: center;
}
.popup-container.top {
align-self: flex-start;
width: 100%;
}
.popup-container.bottom {
align-self: flex-end;
width: 100%;
}
.popup-container.left {
align-self: center;
height: 100%;
}
.popup-container.right {
align-self: center;
height: 100%;
}
.popup-container.round {
border-radius: 10rpx;
}
.popup-close {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.close-icon {
font-size: 36rpx;
font-weight: bold;
color: #999;
}
/* 自定义列表样式 */
.custom-list {
width: 100%;
overflow-y: auto;
}
.custom-list-item {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.custom-list-item:last-child {
border-bottom: none;
}
/* 出库计划样式 */
.empty-plan {
text-align: center;
padding: 40rpx;
color: #999;
}
.plan-item {
border: 1rpx solid #eee;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.plan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
}
.plan-title {
flex: 1;
}
.plan-title-text {
font-size: 16px;
font-weight: bold;
}
.plan-tags {
display: flex;
gap: 10rpx;
}
.tag {
padding: 5rpx 10rpx;
border-radius: 5rpx;
font-size: 12px;
}
.tag.error {
background-color: #fee;
color: #f00;
}
.tag.success {
background-color: #efe;
color: #0a0;
}
.tag.info {
background-color: #eef;
color: #00f;
}
.plan-content {
display: flex;
flex-direction: column;
gap: 5rpx;
}
.plan-content text {
font-size: 14px;
}
.plan-content .info {
color: #666;
}
.plan-content .primary {
color: #007aff;
}
.plan-content .warning {
color: #ff9500;
}
.plan-content .success {
color: #34c759;
}
</style>