298 lines
7.5 KiB
Vue
298 lines
7.5 KiB
Vue
<template>
|
|
<div class="page-container">
|
|
<van-nav-bar
|
|
:title="t('receiveGroup.title')"
|
|
left-arrow
|
|
@click-left="router.push('/')"
|
|
/>
|
|
<div class="page-content">
|
|
<van-form ref="formRef">
|
|
<van-field
|
|
v-model="barcode"
|
|
:label="t('receiveGroup.barcode')"
|
|
:placeholder="t('receiveGroup.barcodePlaceholder')"
|
|
clearable
|
|
@blur="onBarcodeBlur"
|
|
@keyup.enter="onBarcodeBlur"
|
|
>
|
|
<template #right-icon>
|
|
<van-icon name="scan" class="scan-icon" @click="onScanBarcode" />
|
|
</template>
|
|
</van-field>
|
|
<van-field
|
|
v-model="motherTray"
|
|
:label="t('receiveGroup.motherTray')"
|
|
:placeholder="t('receiveGroup.motherTrayPlaceholder')"
|
|
:rules="[{ required: true, message: t('receiveGroup.motherTrayRequired') }]"
|
|
name="motherTray"
|
|
clearable
|
|
>
|
|
<template #right-icon>
|
|
<van-icon name="scan" class="scan-icon" @click="onScanMotherTray" />
|
|
</template>
|
|
</van-field>
|
|
<van-field
|
|
v-model="sectCode"
|
|
:label="t('receiveGroup.sectCode')"
|
|
readonly
|
|
is-link
|
|
/>
|
|
</van-form>
|
|
|
|
<div class="detail-section">
|
|
<div class="detail-header">
|
|
<span class="detail-header-text">{{ t('receiveGroup.total', detailList.length) }}</span>
|
|
</div>
|
|
<div v-if="!detailList.length" class="detail-empty">
|
|
<van-empty :description="t('receiveGroup.noData')" image="search" />
|
|
</div>
|
|
<div v-else class="card-list">
|
|
<div
|
|
v-for="(item, idx) in detailList"
|
|
:key="idx"
|
|
class="detail-card"
|
|
>
|
|
<div class="card-header">
|
|
<span class="card-index">#{{ idx + 1 }}</span>
|
|
<van-icon
|
|
name="delete-o"
|
|
class="card-delete"
|
|
@click="onDeleteItem(idx)"
|
|
/>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.skuCode') }}</span>
|
|
<span class="card-value">{{ item.sku_code }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.skuName') }}</span>
|
|
<span class="card-value">{{ item.sku_name }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.orderNo') }}</span>
|
|
<span class="card-value">{{ item.order_no }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.itemNo') }}</span>
|
|
<span class="card-value">{{ item.item_no }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.qty') }}</span>
|
|
<span class="card-value card-value--num">{{ item.qty }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.batchNo') }}</span>
|
|
<span class="card-value">{{ item.batch_no }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.categoryCode') }}</span>
|
|
<span class="card-value">{{ item.category_code }}</span>
|
|
</div>
|
|
<div class="card-row">
|
|
<span class="card-label">{{ t('receiveGroup.categoryName') }}</span>
|
|
<span class="card-value">{{ item.category_name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<BottomButton :text="t('receiveGroup.submit')" :loading="submitting" @click="onSubmit" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { showToast, showDialog } from 'vant'
|
|
import { useI18n } from '@/i18n'
|
|
import BottomButton from '@/components/BottomButton.vue'
|
|
import request from '@/utils/request'
|
|
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
|
|
const barcode = ref('')
|
|
const motherTray = ref('')
|
|
const sectCode = ref('')
|
|
const detailList = ref([])
|
|
const formRef = ref()
|
|
const submitting = ref(false)
|
|
const billId = ref('')
|
|
|
|
async function fetchInboundInfo(code) {
|
|
if (!code) return
|
|
try {
|
|
const res = await request.get('/api/pda/inbound/getInfoByCode', {
|
|
params: { code },
|
|
})
|
|
const data = res.data || []
|
|
if (!data.length) {
|
|
showToast({ message: t('receiveGroup.fetchFail'), type: 'fail' })
|
|
return
|
|
}
|
|
if (!billId.value && data[0].bill_id) {
|
|
billId.value = data[0].bill_id
|
|
}
|
|
if (!sectCode.value && data[0].sect_code) {
|
|
sectCode.value = data[0].sect_code
|
|
}
|
|
data.forEach((item) => {
|
|
detailList.value.push({ ...item })
|
|
})
|
|
} catch {
|
|
showToast({ message: t('receiveGroup.fetchFail'), type: 'fail' })
|
|
}
|
|
}
|
|
|
|
function onBarcodeBlur() {
|
|
const code = barcode.value.trim()
|
|
if (!code) return
|
|
fetchInboundInfo(code)
|
|
}
|
|
|
|
function onScanBarcode() {
|
|
onBarcodeBlur()
|
|
}
|
|
|
|
function onScanMotherTray() {
|
|
motherTray.value = 'TP' + Date.now().toString().slice(-6)
|
|
}
|
|
|
|
function onDeleteItem(idx) {
|
|
showDialog({
|
|
title: '',
|
|
message: t('receiveGroup.deleteConfirm'),
|
|
showCancelButton: true,
|
|
}).then(() => {
|
|
detailList.value.splice(idx, 1)
|
|
}).catch(() => {})
|
|
}
|
|
|
|
async function onSubmit() {
|
|
try {
|
|
await formRef.value?.validate('motherTray')
|
|
} catch {
|
|
return
|
|
}
|
|
if (!detailList.value.length) {
|
|
showToast({ message: t('receiveGroup.noData'), type: 'fail' })
|
|
return
|
|
}
|
|
const detailData = detailList.value.map((item) => ({
|
|
order_no: item.order_no,
|
|
sku_code: item.sku_code,
|
|
batch_no: item.batch_no,
|
|
qty: item.qty,
|
|
}))
|
|
submitting.value = true
|
|
try {
|
|
const res = await request.post('/api/pda/inbound/groupPlate', {
|
|
bill_id: billId.value,
|
|
vehicle_code: motherTray.value,
|
|
sect_code: sectCode.value,
|
|
detailList: detailData,
|
|
})
|
|
showToast({
|
|
message: res.message || t('receiveGroup.submitSuccess'),
|
|
type: 'success',
|
|
})
|
|
detailList.value = []
|
|
billId.value = ''
|
|
sectCode.value = ''
|
|
barcode.value = ''
|
|
motherTray.value = ''
|
|
} catch {
|
|
// error already handled by interceptor
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</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-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-delete {
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.card-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
padding: 4px 0;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.card-label {
|
|
flex-shrink: 0;
|
|
width: 80px;
|
|
color: #999;
|
|
}
|
|
|
|
.card-value {
|
|
flex: 1;
|
|
color: #333;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.card-value--num {
|
|
color: var(--primary-color);
|
|
font-weight: 600;
|
|
}
|
|
</style>
|