Files
klp-mono/apps/hand-factory/pages/receive/receive.vue
砂糖 041ac52600 feat(hand-factory): 新增收货页面及发货计划API
新增收货页面功能,包含收货计划选择、钢卷号筛选、收货表单提交等
添加发货计划相关API接口,包括查询、新增、修改、删除和报表统计
2025-12-03 10:01:37 +08:00

708 lines
16 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="container">
<!-- 筛选栏 -->
<view class="filter-bar">
<!-- 左侧收货计划选择 -->
<view class="plan-select" @click="openPlanPopup">
<uni-icons type="shop" size="20" color="#666"></uni-icons>
<text class="plan-text">{{ currentPlan.planNo || '选择收货计划' }}</text>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
<!-- 右侧钢卷号筛选 -->
<view class="coil-filter">
<input v-model="currentCoilNo" placeholder="请输入钢卷号筛选" clearable @confirm="fetchList" class="coil-input"></input>
<uni-icons type="search" size="18" color="#666" @click="fetchList(true)"></uni-icons>
</view>
</view>
<!-- 收货计划选择悬浮窗 -->
<uni-popup ref="planPopup" type="bottom" :mask-click="true" height="70%">
<view class="popup-container">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">选择收货计划</text>
<uni-icons type="close" size="20" @click="closePlanPopup"></uni-icons>
</view>
<!-- 计划筛选输入框 -->
<view class="plan-search">
<input v-model="planKeyword" placeholder="请输入计划编号筛选" clearable class="plan-search-input"
@confirm="fetchDeliveryPlan"></input>
<uni-icons type="search" size="18" color="#666" @click="fetchDeliveryPlan(true)"></uni-icons>
</view>
<!-- 收货计划列表 -->
<view class="plan-list">
<uni-list v-if="planList.length">
<uni-list-item v-for="(item, index) in planList" :key="index" :title="item.planName" clickable
@click="selectPlan(item)"></uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无收货计划数据</view>
<!-- 加载更多 -->
<uni-load-more :status="planHasMore ? 'more' : 'noMore'" @clickLoadMore="fetchDeliveryPlan(false)"
v-if="planList.length"></uni-load-more>
</view>
</view>
</uni-popup>
<uni-popup ref='receivePopup'>
<!-- 表单区域 -->
<view class="form-card" v-if="form.coilId">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">录入信息</text>
</view>
<!-- 当前钢卷号 -->
<view class="form-item">
<text class="form-label">当前钢卷号</text>
<input v-model="form.currentCoilNo" placeholder="请输入当前钢卷号" class="form-input" />
</view>
<!-- 班组 -->
<view class="form-item">
<text class="form-label">班组</text>
<input v-model="form.team" placeholder="请输入班组名称" class="form-input" :disabled="form.dataType === 0"
:class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 库区选择 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">目标库位</text>
<klp-warehouse-picker v-model="form.warehouseId" :disabled="form.dataType === 0" placeholder="请选择目标库区" />
</view>
<!-- <view class="form-item form-item-optional">
<text class="form-label-optional">真实库区</text>
<klp-warehouse-picker v-model="form.actualWarehouseId" :disabled="form.dataType === 0"
placeholder="请选择目标库区" ware-type="actual" />
</view> -->
<!-- 物品类型产品原材料选择使用封装组件 -->
<klp-material-picker
:item-type.sync="form.itemType"
:item-id.sync="form.itemId"
:disabled="form.dataType === 0"
:page-size="2000"
/>
<!-- 毛重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">毛重 ()</text>
<input v-model="form.grossWeight" type="digit" placeholder="请输入毛重(选填)" class="form-input"
:disabled="form.dataType === 0" :class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 净重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">净重 ()</text>
<input v-model="form.netWeight" type="digit" placeholder="请输入净重(选填)" class="form-input"
:disabled="form.dataType === 0" :class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 操作者信息 -->
<view class="operator-info">
<text class="operator-label">操作人</text>
<text class="operator-name">{{ operatorName }}</text>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<button v-if="form.dataType === 1" class="btn btn-primary" @click="confirmReceive" :disabled="loading">
{{ loading ? '提交中...' : '确认收货' }}
</button>
</view>
</view>
</uni-popup>
<!-- 主列表区域 -->
<view class="main-list">
<scroll-view class="list-scroll" scroll-y>
<uni-list v-if="list.length">
<uni-list-item clickable v-for="(item, index) in list" :key="index" @click="openReceivePopup(item)"
:title="`钢卷号:${item.currentCoilNo || '-'}`" :note="`操作时间:${item.updateTime || '-'}`">
</uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无待操作数据</view>
<!-- 加载更多 -->
<uni-load-more :status="hasMore ? 'more' : 'noMore'" @clickLoadMore="fetchList(false)"
v-if="list.length"></uni-load-more>
</scroll-view>
</view>
</view>
</template>
<script>
import {
listPendingAction,
updatePendingAction
} from '@/api/wms/pendingAction';
import {
listDeliveryPlan
} from '@/api/wms/deliveryPlan.js';
import {
updateMaterialCoilSimple,
getMaterialCoil
} from '@/api/wms/coil.js'
// 入库操作编码
const ACTION_TYPE = 401;
// 收货计划类型编码
const RECEIVE_PLAN_TYPE = 1;
// 未开始操作状态
const ACTION_STATUS = 0;
export default {
data() {
return {
// 主列表数据
list: [],
currentPlan: {}, // 当前选中的收货计划
currentCoilNo: undefined, // 当前输入的钢卷号
hasMore: false, // 是否有更多主列表数据
pageNum: 1, // 主列表页码
pageSize: 10, // 每页条数
refreshing: false, // 下拉刷新状态
// 收货计划弹窗相关
planKeyword: '', // 计划筛选关键词
planList: [], // 收货计划列表
planHasMore: false, // 计划列表是否有更多
planPageNum: 1, // 计划列表页码
popupShow: false, // 弹窗显示状态
form: {},
loading: false,
currentAction: {},
};
},
computed: {
// 获取当前操作者昵称
operatorName() {
return this.$store.state.user.nickName || this.$store.state.user.name || '未知'
}
},
onShow() {
// 页面显示时初始化加载数据
this.fetchList(true);
},
methods: {
/**
* 获取待操作列表数据
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchList(isRefresh = false) {
try {
// 刷新时重置页码和加载状态
if (isRefresh) {
this.pageNum = 1;
this.refreshing = true;
}
// 构造请求参数
const params = {
actionStatus: ACTION_STATUS,
actionType: ACTION_TYPE,
warehouseId: this.currentPlan.planId || '', // 选中的计划ID
currentCoilNo: this.currentCoilNo || '', // 钢卷号
pageNum: this.pageNum,
pageSize: this.pageSize
};
// 请求接口
const res = await listPendingAction(params);
if (res.code === 200) {
const list = res.rows || [];
// 刷新时替换数据,加载更多时追加数据
this.list = isRefresh ? list : [...this.list, ...list];
// 判断是否有更多数据
this.hasMore = this.pageNum * this.pageSize < res.total;
}
console.log(this.list, '需要渲染的数据')
} catch (err) {
console.error('获取待操作列表失败:', err);
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
} finally {
// 结束下拉刷新状态
this.refreshing = false;
// 加载更多时页码+1
if (!isRefresh) this.pageNum++;
}
},
/**
* 获取收货计划列表
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchDeliveryPlan(isRefresh = false) {
try {
if (isRefresh) {
this.planPageNum = 1;
}
// 构造请求参数
const params = {
planType: RECEIVE_PLAN_TYPE,
planName: this.planKeyword || '', // 计划编号筛选
pageNum: this.planPageNum,
pageSize: this.pageSize
};
const res = await listDeliveryPlan(params);
if (res.code === 200) {
const list = res.rows || [];
this.planList = isRefresh ? list : [...this.planList, ...list];
this.planHasMore = this.planPageNum * this.pageSize < res.total;
}
} catch (err) {
console.error('获取收货计划失败:', err);
uni.showToast({
title: '计划加载失败',
icon: 'none'
});
} finally {
if (!isRefresh) this.planPageNum++;
}
},
/**
* 打开收货计划弹窗
*/
openPlanPopup() {
this.$refs.planPopup.open();
// 打开弹窗时加载计划数据
this.fetchDeliveryPlan(true);
},
/**
* 关闭收货计划弹窗
*/
closePlanPopup() {
this.$refs.planPopup.close();
},
/**
* 打开收货弹窗
*/
openReceivePopup(row) {
this.$refs.receivePopup.open('bottom')
getMaterialCoil(row.coilId).then(res => {
this.form = res.data;
this.currentAction = row;
})
},
/**
* 确认收货
*/
confirmReceive(row) {
const currentAction = this.currentAction;
const form = this.form;
const that = this;
uni.showModal({
title: '确定要收货吗?',
success() {
// console.log(currentAction, form)
that.loading = true;
Promise.all([
updatePendingAction({
...currentAction,
actionStatus: 2
}),
updateMaterialCoilSimple({
...form,
dataType: 1
})
]).then(_ => {
uni.showToast({
title: '钢卷已收货'
});
that.fetchList(true);
that.loading = false;
that.$refs.receivePopup.close()
})
}
})
},
/**
* 选择收货计划
* @param {Object} plan 选中的计划对象
*/
selectPlan(plan) {
this.currentPlan = plan;
this.closePlanPopup();
// 选择计划后重新加载主列表
this.fetchList(true);
},
/**
* 下拉刷新触发(修正后事件可正常绑定)
*/
onPullDownRefresh() {
this.fetchList(true);
},
/**
* 页面上拉加载(可选:补充页面级上拉加载)
*/
onReachBottom() {
if (this.hasMore) {
this.fetchList(false);
}
}
},
// 补充页面级上拉加载钩子(可选,增强体验)
onReachBottom() {
this.onReachBottom();
}
};
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 筛选栏样式 */
.filter-bar {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
background-color: #fff;
border-bottom: 1px solid #eee;
}
.plan-select {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 15rpx;
background-color: #f8f8f8;
border-radius: 6rpx;
margin-right: 20rpx;
min-width: 200rpx;
}
.plan-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.coil-filter {
display: flex;
align-items: center;
flex: 1;
background-color: #f8f8f8;
border-radius: 6rpx;
padding: 0 15rpx;
}
.coil-input {
flex: 1;
font-size: 28rpx;
}
/* 弹窗样式 */
.popup-container {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.plan-search {
display: flex;
align-items: center;
gap: 10rpx;
padding: 15rpx 20rpx;
border-bottom: 1px solid #eee;
}
.plan-search-input {
flex: 1;
font-size: 28rpx;
}
.plan-list {
flex: 1;
overflow-y: auto;
padding: 10rpx;
}
/* 列表样式 */
.main-list {
flex: 1;
overflow: hidden;
}
.list-scroll {
width: 100%;
height: 100%;
}
.uni-list {
background-color: #fff;
margin: 10rpx;
border-radius: 8rpx;
}
.uni-list-item {
font-size: 28rpx;
}
/* 空数据提示 */
.empty-tip {
text-align: center;
padding: 50rpx 0;
font-size: 28rpx;
color: #999;
}
/* 加载更多样式 */
.uni-load-more {
margin: 20rpx 0;
}
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
overflow: scroll;
height: 70vh;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #007aff;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.status-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
margin-right: 10rpx;
&.status-1 {
background: #d1f2eb;
color: #0c6957;
}
&.status-0 {
background: #f8d7da;
color: #721c24;
}
}
.more-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
background: #f0f7ff;
border-radius: 20rpx;
border: 1rpx solid #007aff;
.icon-more {
font-size: 24rpx;
color: #007aff;
}
.more-text {
font-size: 24rpx;
color: #007aff;
}
}
}
/* 信息网格 */
.info-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.info-item {
flex: 1;
min-width: 45%;
background: #f8f9fa;
padding: 20rpx;
border-radius: 12rpx;
&.full-width {
flex: 0 0 100%;
}
.item-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 10rpx;
}
.item-value {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
/* 表单项 */
.form-item {
margin-bottom: 30rpx;
&:last-of-type {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
&::before {
content: '*';
color: #ff4d4f;
margin-right: 6rpx;
}
}
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
transition: all 0.3s;
&:focus {
background: #fff;
border-color: #007aff;
}
&.form-input-disabled {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
}
}
}
/* 操作者信息 */
.operator-info {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
margin-top: 20rpx;
.operator-label {
font-size: 26rpx;
color: #999;
}
.operator-name {
font-size: 26rpx;
color: #007aff;
font-weight: 500;
}
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
.btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
transition: all 0.2s;
&:active {
transform: scale(0.98);
}
&[disabled] {
opacity: 0.6;
}
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-primary {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
}
}
</style>