add:pda出库确认页面
This commit is contained in:
2630
pda/pda/package-lock.json
generated
2630
pda/pda/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,12 +25,11 @@ const zh = {
|
||||
'平库上架',
|
||||
'平库库存查询',
|
||||
'库位绑定/解绑',
|
||||
'拣选大厅',
|
||||
'无容器收货入库',
|
||||
'空托上架/下架/注册/呼叫',
|
||||
'AGV配送',
|
||||
'人工盘点',
|
||||
'备货组盘查询',
|
||||
'平库出库'
|
||||
// '空托上架/下架/注册/呼叫',
|
||||
// 'AGV配送',
|
||||
// '人工盘点',
|
||||
// '备货组盘查询',
|
||||
],
|
||||
},
|
||||
receiveGroup: {
|
||||
@@ -131,6 +130,23 @@ const zh = {
|
||||
submit: '提交',
|
||||
submitSuccess: '提交成功',
|
||||
},
|
||||
flatOutBound: {
|
||||
title: '平库出库',
|
||||
sectCode: '库区',
|
||||
sectCodePlaceholder: '请选择库区',
|
||||
sectCodeRequired: '请选择库区',
|
||||
selectSect: '选择库区',
|
||||
total: '共{0}条',
|
||||
loading: '加载中...',
|
||||
noData: '暂无待出库物料',
|
||||
materialCode: '物料编码',
|
||||
structCode: '库位号',
|
||||
vehicleCode: '载具号',
|
||||
planQty: '计划数量',
|
||||
assignQty: '分配数量',
|
||||
complete: '完成',
|
||||
completeSuccess: '操作成功',
|
||||
},
|
||||
}
|
||||
|
||||
const en = {
|
||||
@@ -265,6 +281,23 @@ const en = {
|
||||
submit: 'Submit',
|
||||
submitSuccess: 'Submitted successfully',
|
||||
},
|
||||
flatOutBound: {
|
||||
title: 'Flat Outbound',
|
||||
sectCode: 'Warehouse Area',
|
||||
sectCodePlaceholder: 'Please select warehouse area',
|
||||
sectCodeRequired: 'Please select warehouse area',
|
||||
selectSect: 'Select Warehouse Area',
|
||||
total: 'Total {0} items',
|
||||
loading: 'Loading...',
|
||||
noData: 'No pending outbound materials',
|
||||
materialCode: 'Material Code',
|
||||
structCode: 'Location',
|
||||
vehicleCode: 'Vehicle Code',
|
||||
planQty: 'Plan Qty',
|
||||
assignQty: 'Assign Qty',
|
||||
complete: 'Complete',
|
||||
completeSuccess: 'Success',
|
||||
},
|
||||
}
|
||||
|
||||
const messages = {zh, en}
|
||||
|
||||
@@ -49,6 +49,12 @@ const routes = [
|
||||
component: () => import('@/views/ManualInbound.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/flat-outbound',
|
||||
name: 'FlatOutBound',
|
||||
component: () => import('@/views/FlatOutBound.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/developing',
|
||||
name: 'Developing',
|
||||
|
||||
315
pda/pda/src/views/FlatOutBound.vue
Normal file
315
pda/pda/src/views/FlatOutBound.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<van-nav-bar
|
||||
:title="t('flatOutBound.title')"
|
||||
left-arrow
|
||||
@click-left="router.push('/')"
|
||||
/>
|
||||
<div class="page-content">
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="sectName"
|
||||
:label="t('flatOutBound.sectCode')"
|
||||
:placeholder="t('flatOutBound.sectCodePlaceholder')"
|
||||
:rules="[{ required: true, message: t('flatOutBound.sectCodeRequired') }]"
|
||||
name="sectCode"
|
||||
readonly
|
||||
is-link
|
||||
@click="showSectPicker = true"
|
||||
/>
|
||||
</van-form>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-header">
|
||||
<span class="detail-header-text">{{ t('flatOutBound.total', materialList.length) }}</span>
|
||||
</div>
|
||||
<div v-if="loading" class="detail-loading">
|
||||
<van-loading size="24px">{{ t('flatOutBound.loading') }}</van-loading>
|
||||
</div>
|
||||
<div v-else-if="!materialList.length" class="detail-empty">
|
||||
<van-empty :description="t('flatOutBound.noData')" image="search" />
|
||||
</div>
|
||||
<div v-else class="card-list">
|
||||
<div
|
||||
v-for="(item, idx) in materialList"
|
||||
:key="idx"
|
||||
class="detail-card"
|
||||
>
|
||||
<div class="card-header">
|
||||
<span class="card-index">#{{ idx + 1 }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-content">
|
||||
<div class="card-left">
|
||||
<div class="card-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">库区</span>
|
||||
<span class="field-value">{{ item.sectName }}</span>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<span class="field-label">库位号</span>
|
||||
<span class="field-value">{{ item.structCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">载具号</span>
|
||||
<span class="field-value">{{ item.storagevehicleCode }}</span>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<span class="field-label">物料编码</span>
|
||||
<span class="field-value">{{ item.materialCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">计划数量</span>
|
||||
<span class="field-value field-value--primary">{{ item.planQty }}</span>
|
||||
</div>
|
||||
<div class="field-group field-group--stepper">
|
||||
<span class="field-label">分配数量</span>
|
||||
<van-stepper
|
||||
v-model="item.assignQty"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:decimal-length="3"
|
||||
input-width="60px"
|
||||
button-size="28px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-right">
|
||||
<van-button
|
||||
type="primary"
|
||||
size="normal"
|
||||
:loading="item.submitting"
|
||||
@click="onComplete(item, idx)"
|
||||
>
|
||||
完成
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showSectPicker" position="bottom">
|
||||
<van-picker
|
||||
:columns="sectOptions"
|
||||
:title="t('flatOutBound.selectSect')"
|
||||
@confirm="onSectConfirm"
|
||||
@cancel="showSectPicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import { useI18n } from '@/i18n'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const sectName = ref('')
|
||||
const sectCode = ref('')
|
||||
const showSectPicker = ref(false)
|
||||
const sectOptions = ref([])
|
||||
const materialList = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
async function fetchFlatWarehouse() {
|
||||
try {
|
||||
const res = await request.get('/api/pda/iosOut/flatWarehouse')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
sectOptions.value = res.data.map(item => ({
|
||||
text: item.label,
|
||||
value: item.value,
|
||||
}))
|
||||
}
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOutBoundDtl(code) {
|
||||
if (!code) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.get('/api/pda/iosOut/getOutBoundDtl', {
|
||||
params: { sectCode: code },
|
||||
})
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
materialList.value = res.data.map(item => ({
|
||||
...item,
|
||||
assignQty: item.assignQty ?? item.planQty,
|
||||
submitting: false,
|
||||
}))
|
||||
} else {
|
||||
materialList.value = []
|
||||
}
|
||||
} catch {
|
||||
materialList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onSectConfirm({ selectedOptions }) {
|
||||
const selected = selectedOptions[0]
|
||||
sectName.value = selected.text
|
||||
sectCode.value = selected.value
|
||||
showSectPicker.value = false
|
||||
fetchOutBoundDtl(selected.value)
|
||||
}
|
||||
|
||||
async function onComplete(item, idx) {
|
||||
if (item.submitting) return
|
||||
item.submitting = true
|
||||
try {
|
||||
const res = await request.post('/api/pda/iosOut/assignOutDis', {
|
||||
item
|
||||
})
|
||||
showToast({ message: res.message || t('flatOutBound.completeSuccess'), type: 'success' })
|
||||
materialList.value.splice(idx, 1)
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
} finally {
|
||||
item.submitting = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFlatWarehouse()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.detail-header-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.detail-empty {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--primary-gradient);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-index {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.card-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.field-group--stepper {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
flex-shrink: 0;
|
||||
width: 56px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.field-value--primary {
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-right {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 16px;
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
align-self: stretch;
|
||||
}
|
||||
</style>
|
||||
@@ -53,6 +53,8 @@ const menuRoutes = {
|
||||
3: '/putaway',
|
||||
4: '/inventory',
|
||||
5: '/bind-unbind',
|
||||
6: '/flat-outbound',
|
||||
7: '/flat-outbound',
|
||||
}
|
||||
|
||||
function onMenuClick(index) {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</div>
|
||||
<div class="login-header">
|
||||
<div class="login-logo">
|
||||
<div class="logo-box">ZD</div>
|
||||
<div class="logo-text">{{ t('login.title') }}</div>
|
||||
<div class="logo-box">诺力</div>
|
||||
<div class="logo-text">WMS系统</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-form">
|
||||
|
||||
Reference in New Issue
Block a user