@@ -19,8 +19,9 @@
< / view >
< view v-if = "task.taskStatus === 'CLAIMED'" class="section-card" >
< text class = "section-title" > 1. 扫箱清点 < / text >
< text class = "section-desc" > 扫描箱唛 , 核对 SKU 与本箱实际件数 。 < / text >
< text class = "section-title" > 1. 扫托盘或扫 箱 < / text >
< text class = "section-desc" > 顺序不限 : 先扫托盘后扫箱会直接归托 ; 先扫箱后扫托会把待归托箱绑定到该托盘 。 < / text >
< ScanTargetInput v-model = "palletCode" name="palletCode" label="托盘码" placeholder="扫描或输入已生成的 DP 托盘码" :active="activeScan === 'palletCode'" @activate="activeScan = 'palletCode'" @submit="selectPallet" / >
< ScanTargetInput v-model = "boxCode" name="boxCode" label="箱码" placeholder="扫描或输入箱唛箱码" :active="activeScan === 'boxCode'" @activate="activeScan = 'boxCode'" @submit="resolveCarton" / >
< view v-if = "carton" class="carton-card" >
< view class = "carton-head" > < text > { { carton . boxCode } } < / text > < text > { { carton . countStatus === 'UNCOUNTED' ? '待清点' : '已处理' } } < / text > < / view >
@@ -28,23 +29,19 @@
< text class = "carton-line" > 计划 { { carton . expectedSkuQuantity || 0 } } 件 < / text >
< view class = "field" > < text > 实际件数 < / text > < input v-model = "actualQuantity" type="number" aria-label="本箱实际件数" / > < / view >
< view v-if = "quantityDiffers" class="field stacked"><text>差异说明</text><textarea v-model="discrepancyRemark" maxlength="500" placeholder="数量不一致时必填" / > < / view >
< button class = "primary-button" : disabled = "!canCount || submitting" @tap ="countCarton" > 确认本箱并进入待归托区 < / button >
< button class = "primary-button" : disabled = "!canCount || submitting" @tap ="countCarton" > {{ currentPallet ? ` 确认并归入 $ { currentPallet.palletNo } ` : ' 确认本箱并进入待归托区 ' }} < / button >
< / view >
< / view >
< view v-if = "task.taskStatus === 'CLAIMED'" class="section-card" >
< text class = "section-title" > 2. 完成入库 < / text >
< text class = "section-desc" > 全部箱码清点完成后 , 系统一次性完成归托 、 封托并生成正式 DP 托盘码 。 < / text >
< button class = "primary-button" : disabled = "!allCartonsCounted || !task.unboundCartons || submitting" @tap ="completeInbound" > 完成入库并生成托盘码 < / button >
< / view >
< view class = "section-card" >
< view class = "section-heading" > < view > < text class = "section-title" > 3. 托盘码与打印 < / text > < text class = "section-desc" > 完成入库后显示最终托盘码 , 可立即 打印并 粘贴 。 < / text > < / view > < button class = "text-button" :disabled = "loading" @tap ="loadTask" > 刷新 < / button > < / view >
< view class = "section-heading" > < view > < text class = "section-title" > 2. 预生成 托盘码< / text > < text class = "section-desc" > 测量审核后已按预计托数生成 , 可先 打印粘贴 ; 装完一个托盘即可封托 。 < / text > < / view > < button class = "text-button" :disabled = "loading" @tap ="loadTask" > 刷新 < / button > < / view >
< view v-if = "!task.pallets || !task.pallets.length" class="empty" > 暂无托盘 < / view >
< view v-for = "pallet in task.pallets" :key="pallet.palletId" class="pallet-card" >
< view class = "pallet-head" > < text class = "pallet-code" > { { pallet . palletNo } } < / text > < text class = "pallet-status" : class = "pallet.status === 'SEALED' ? 'sealed' : ''" > { { pallet . status === 'SEALED' ? '已封托' : '待封托' } } < / text > < / view >
< text class = "pallet-meta" > { { pallet . cartonCount || 0 } } 箱 · { { pallet . skuCount || 0 } } SKU · { { pallet . weightKg || 0 } } kg · { { pallet . volumeCbm || 0 } } m³ < / text >
< button v-if = "pallet.status === 'SEALED '" class="prim ary-button" :disabled="prin ting" @tap="prin tPallet(pallet)" > {{ printing ? ' 正在生成打印文件 … ' : ' 打印 托盘码 ' }} < / button >
< button v-if = "pallet.status === 'OPEN '" class="second ary-button" :disabled="submit ting" @tap="selec tPallet(pallet.palletNo )" > {{ currentPallet ? .palletId = = = pallet.palletId ? ' 当前托盘 ' : ' 设为当前 托盘' }} < / button >
< button class = "secondary-button" :disabled = "printing" @tap ="printPallet(pallet)" > {{ printing ? ' 正在生成打印文件 … ' : ' 打印托盘码 ' }} < / button >
< button v-if = "pallet.status === 'OPEN'" class="primary-button" :disabled="!pallet.cartonCount || submitting" @tap="sealPallet(pallet)" > 封托 < / button >
< / view >
< / view >
< / template >
@@ -55,19 +52,19 @@
< script >
import PortalHeader from '@/components/PortalHeader.vue'
import ScanTargetInput from '@/components/ScanTargetInput.vue'
import { claimDropshipPalletizationTask , countDropshipCarton , createAndBindDropshipPallet , downloadDropshipPalletLabel , getDropshipPalletizationTask , resolveDropshipCarton } from '@/api/pda-dropship.js'
import { bindDropshipPendingCartons , claimDropshipPalletizationTask , countDropshipCarton , downloadDropshipPalletLabel , getDropshipPalletizationTask , resolveDropshipCarton , sealDropshipPallet } from '@/api/pda-dropship.js'
import { session , requireSession } from '@/stores/session.js'
import { formatApiError } from '@/utils/user-facing.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
export default {
components : { PortalHeader , ScanTargetInput } ,
data ( ) { return { user : session . currentUser , taskId : '' , warehouseId : '' , task : null , boxCode : '' , carton : null , actualQuantity : '' , discrepancyRemark : '' , activeScan : 'boxCode' , loading : false , submitting : false , printing : false , error : '' , notice : '' } } ,
data ( ) { return { user : session . currentUser , taskId : '' , warehouseId : '' , task : null , palletCode : '' , selectedPalletId : '' , boxCode : '' , carton : null , actualQuantity : '' , discrepancyRemark : '' , activeScan : 'boxCode' , loading : false , submitting : false , printing : false , error : '' , notice : '' } } ,
computed : {
statusLabel ( ) { return this . task ? . taskStatus === 'COMPLETED' ? '已完成' : ( this . task ? . taskStatus === 'CLAIMED' ? '作业中' : '待领取' ) } ,
quantityDiffers ( ) { return this . carton && Number ( this . actualQuantity ) !== Number ( this . carton . expectedSkuQuantity ) } ,
canCount ( ) { return Boolean ( this . carton ? . canCount && Number ( this . actualQuantity ) > 0 && ( ! this . quantityDiffers || this . discrepancyRemark . trim ( ) ) ) } ,
allCartonsCounted ( ) { return Number ( this . task ? . totalCartons || 0 ) > 0 && Number ( this . task ? . countedCartons || 0 ) === Number ( this . task ? . totalCartons || 0 ) }
currentPallet ( ) { return ( this . task ? . pallets || [ ] ) . find ( ( item ) => String ( item . palletId ) === String ( this . selectedPalletId ) && item . status === 'OPEN' ) || null }
} ,
onLoad ( options ) { this . taskId = String ( options . taskId || '' ) ; this . warehouseId = String ( options . warehouseId || '' ) } ,
onShow ( ) { if ( ! requireSession ( ) ) return ; this . user = session . currentUser ; this . startScanner ( ) ; this . loadTask ( ) } ,
@@ -75,7 +72,7 @@
methods : {
startScanner ( ) { this . stopScanner ( ) ; this . _stopScanner = startScanBroadcast ( { onScan : ( value ) => this . handleScan ( value ) , onError : ( ) => { this . error = '扫码头连接失败,可使用输入框继续作业' } } ) } ,
stopScanner ( ) { if ( this . _stopScanner ) this . _stopScanner ( ) ; this . _stopScanner = null } ,
handleScan ( value ) { this . boxCode = String ( value ) . trim ( ) . toUpperCase ( ) ; this . resolveCarton ( ) } ,
handleScan ( value ) { const code = String ( value ) . trim ( ) . toUpperCase ( ) ; if ( code . startsWith ( 'DP-' ) ) { this . palletCode = code ; this . selectPallet ( code ) } else { this . boxCode = code ; this . resolveCarton ( ) } } ,
resetMessages ( ) { this . error = '' ; this . notice = '' } ,
async loadTask ( ) {
if ( ! this . taskId || ! this . warehouseId ) { this . error = '缺少合托任务或仓库信息,请返回任务列表' ; return }
@@ -93,12 +90,20 @@
finally { this . loading = false }
} ,
async countCarton ( ) {
const result = await this . runMutation ( ( ) => countDropshipCarton ( this . taskId , { warehouseId : this . warehouseId , boxCode : this . carton . boxCode , actualSkuQuantity : Number ( this . actualQuantity ) , discrepancyAction : this . quantityDiffers ? 'ACCEPT_VARIANCE' : 'MATCH' , discrepancyRemark : this . quantityDiffers ? this . discrepancyRemark . trim ( ) : null } ) , '本箱已完成入库清点' )
const result = await this . runMutation ( ( ) => countDropshipCarton ( this . taskId , { warehouseId : this . warehouseId , boxCode : this . carton . boxCode , palletNo : this . currentPallet ? . palletNo || null , actualSkuQuantity : Number ( this . actualQuantity ) , discrepancyAction : this . quantityDiffers ? 'ACCEPT_VARIANCE' : 'MATCH' , discrepancyRemark : this . quantityDiffers ? this . discrepancyRemark . trim ( ) : null } ) , this . currentPallet ? '本箱已清点并归托' : '本箱已完成入库清点' )
if ( result ) { this . boxCode = '' ; this . carton = null ; this . actualQuantity = '' ; this . discrepancyRemark = '' }
} ,
async completeInbound ( ) { await this . runMutation ( ( ) => createAndBindDropshipPallet ( this . taskId , this . warehouseId ) , '入库已完成,正式托盘码已生成' ) } ,
async selectPallet ( value ) {
const code = String ( value || this . palletCode || '' ) . trim ( ) . toUpperCase ( ) ; const pallet = ( this . task ? . pallets || [ ] ) . find ( ( item ) => item . palletNo === code )
if ( ! pallet ) { this . error = '托盘码不属于当前一件代发任务' ; return }
if ( pallet . status !== 'OPEN' ) { this . error = '托盘已封托,不能继续归托' ; return }
this . palletCode = code ; this . selectedPalletId = String ( pallet . palletId ) ; this . activeScan = 'boxCode'
if ( Number ( this . task ? . unboundCartons || 0 ) > 0 ) await this . runMutation ( ( ) => bindDropshipPendingCartons ( this . taskId , this . warehouseId , code ) , ` 待归托箱已绑定至 ${ code } ` )
else this . notice = ` 当前托盘: ${ code } `
} ,
async sealPallet ( pallet ) { const result = await this . runMutation ( ( ) => sealDropshipPallet ( this . taskId , this . warehouseId , pallet ) , ` ${ pallet . palletNo } 已封托 ` ) ; if ( result && String ( this . selectedPalletId ) === String ( pallet . palletId ) ) { this . selectedPalletId = '' ; this . palletCode = '' } } ,
async printPallet ( pallet ) {
if ( this . printing || pallet . status !== 'SEALED' ) return
if ( this . printing ) return
this . printing = true ; this . resetMessages ( )
try {
const filePath = await downloadDropshipPalletLabel ( this . taskId , this . warehouseId , pallet . palletId )