@@ -26,7 +26,7 @@ type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
type RelabelTemplateType = 'carton' | 'sku' | 'mixed'
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; returnCartonId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface SkuRelabelRow { id : number ; sourceRowId : number | null ; cartonMark : string ; originalSku : string ; newSku : string ; quantity : number ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface ImportedRelabelBinding extends SkuRelabelRow { templateType : RelabelTemplateType ; selected : boolean ; shipmentNo : string }
interface ServiceConfig { title : string ; createTitle : string ; numberLabel : string ; emptyText : string ; columns : string [ ] }
@@ -67,16 +67,17 @@ const operationWarehouseAddress = computed(() => {
} )
const cargoRows = ref < CargoRow [ ] > ( [ ] )
const transferRelabelRows = ref < CargoRow [ ] > ( [ ] )
const transferRelabelDefaultDestination = ref ( '' )
const labelingMode = ref < LabelingMode > ( 'carton' )
const shippingLabelFile = ref < File | null > ( null )
const skuRelabelRows = ref < SkuRelabelRow [ ] > ( [ ] )
const importedRelabelBindings = ref < ImportedRelabelBinding [ ] > ( [ ] )
const cartonDetailRows = computed ( ( ) => cargoRows . value . filter ( ( row ) => row . included ) )
const labelingModeHint = computed ( ( ) => labelingMode . value === 'carton'
? '只替换外箱箱唛,不修改箱内商品 SKU 标签;按箱填写新 的 FBA 箱唛和 目的仓。'
? '只替换外箱箱唛,不修改箱内商品 SKU 标签;整张申请统一使用订单信息中 的 FBA 目的仓。'
: labelingMode . value === 'sku'
? '只替换箱内商品 SKU 标签,保留原箱唛;同一箱唛可以添加多条 SKU 明细。'
: '同一箱同时替换商品 SKU 标签和外箱箱唛;同一箱唛的多条 SKU 使用同一个新 FBA 箱唛和 目的仓。' )
: '同一箱同时替换商品 SKU 标签和外箱箱唛;整张申请统一使用订单信息中的 FBA 目的仓。' )
const submittedRows = ref < Record < string , string > [ ] > ( [ ] )
const page = ref ( 1 )
const pageSize = ref ( 15 )
@@ -123,13 +124,13 @@ const relabelCandidatesLoading = ref(false)
const relabelInboundOptions = computed ( ( ) => {
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number } > ( )
for ( const candidate of relabelCandidates . value ) {
const value = String ( candidate . inboundOrderId )
const value = candidate . sourceType === 'RETURN_CARTON' ? ` RETURN_CARTON: ${ candidate . returnCartonId } ` : String ( candidate . inboundOrderId )
const current = grouped . get ( value )
if ( current ) current . availablePackages += candidate . availablePackages
else grouped . set ( value , {
value ,
inboundOrderNo : candidate . inboundOrderNo || '—' ,
inboundCode : candidate . inboundCode || '—' ,
inboundOrderNo : candidate . sourceType === 'RETURN_CARTON' ? ` 退货合箱 ${ candidate . cartonMark || '—' } ` : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . sourceType === 'RETURN_CARTON' ? '仓库唛头库存' : ( candidate . inboundCode || '—' ) ,
availablePackages : candidate . availablePackages ,
} )
}
@@ -160,6 +161,12 @@ watch(() => form.relabel, (value) => {
}
} )
watch ( labelingMode , ( value ) => { if ( value !== 'carton' && ! skuRelabelRows . value . length ) addSkuRelabelRow ( ) } )
watch ( ( ) => form . destination , ( value ) => {
if ( props . service !== 'labeling' ) return
for ( const row of cargoRows . value ) row . destinationWarehouseId = value
for ( const row of skuRelabelRows . value ) row . destinationWarehouseId = value
for ( const row of importedRelabelBindings . value ) row . destinationWarehouseId = value
} )
function refreshWarehouseMarkLabels ( ) {
if ( props . service !== 'labeling' ) return
const root = document . querySelector ( '.warehouse-service-page' )
@@ -181,6 +188,7 @@ function resetForm() {
Object . assign ( form , { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , privateDestinationAddress : '' , privateDestinationPostalCode : '' , privateDestinationContact : '' , privateDestinationPhone : '' , address : '' , contact : '' , phone : '' , pickupContact : '' , pickupPhone : '' , relabelContact : '' , relabelPhone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
cargoRows . value = [ ]
transferRelabelRows . value = [ ]
transferRelabelDefaultDestination . value = ''
labelingMode . value = 'carton'
skuRelabelRows . value = [ ]
importedRelabelBindings . value = [ ]
@@ -234,6 +242,8 @@ async function openEdit(transferNo: string) {
fbaCartonMark : binding . fbaCartonMark || '' , destinationWarehouseId : binding . destinationWarehouseCode || '' ,
remark : binding . remark || '' ,
} ) )
const destinations = [ ... new Set ( bindings . map ( ( binding ) => binding . destinationWarehouseCode ) . filter ( Boolean ) ) ]
transferRelabelDefaultDestination . value = destinations . length === 1 ? destinations [ 0 ] ! : ''
}
if ( form . warehouse ) {
try { selectedOperationWarehouse . value = await getMerchantWarehouseDetail ( form . warehouse ) } catch { selectedOperationWarehouse . value = null }
@@ -265,7 +275,7 @@ function addSkuRelabelRow() {
const source = cargoRows . value [ Math . min ( skuRelabelRows . value . length , Math . max ( 0 , cargoRows . value . length - 1 ) ) ]
skuRelabelRows . value . push ( { id : ++ skuRelabelSequence , sourceRowId : source ? . id || null ,
cartonMark : '' , originalSku : source ? . sku || '' , newSku : '' , quantity : 1 ,
fbaCartonMark : '' , destinationWarehouseId : source ? . destinationWarehouseId || '' , remark : '' } )
fbaCartonMark : '' , destinationWarehouseId : form . destination , remark : '' } )
}
function removeSkuRelabelRow ( id : number ) { skuRelabelRows . value = skuRelabelRows . value . filter ( ( row ) => row . id !== id ) }
function applyExistingSkuBinding ( row : SkuRelabelRow ) {
@@ -315,21 +325,21 @@ function buildSkuRelabelItems(): RelabelCreate['items'] {
}
return [ ... groups . entries ( ) ] . map ( ( [ _ , rows ] , groupIndex ) => {
const source = cargoRows . value . find ( ( item ) => item . id === rows [ 0 ] . sourceRowId ) || anchors [ groupIndex ]
if ( ! source ? . sourceInventoryBalanceId || ! source . sourceInventoryPositionId || ! source . sourceInboundOrderId ) {
if ( ! source ? . returnCartonId && ( ! source ? . sourceInventoryBalanceId || ! source . sourceInventoryPositionId || ! source . sourceInboundOrderId ) ) {
throw new Error ( '来源库存已变化,请重新选择有库存的历史入仓单' )
}
const cartonMark = rows [ 0 ] . cartonMark . trim ( )
const changeCarton = labelingMode . value === 'carton_and_sku'
const fbaCartonMark = rows [ 0 ] . fbaCartonMark . trim ( )
const destinationWarehouseId = rows [ 0 ] . destinationWarehouseId
if ( changeCarton && ( ! fbaCartonMark || ! destinationWarehouseId
|| rows . some ( ( row ) => row . fbaCartonMark . trim ( ) !== fbaCartonMark || row . destinationWarehouseId !== destinationWarehouseId ) ) ) {
throw new Error ( '同一我方箱唛的组合换标必须使用相同的新 FBA 箱唛和目的仓' )
if ( changeCarton && ( ! fbaCartonMark || ! form . destination
|| rows . some ( ( row ) => row . fbaCartonMark . trim ( ) !== fbaCartonMark ) ) ) {
throw new Error ( '同一我方箱唛的组合换标必须使用相同的新 FBA 箱唛,并选择整票 FBA 目的仓' )
}
return {
sourceInventoryBalanceId : source . sourceInventoryBalanceId ,
sourceInventoryPositionId : source . sourceInventoryPositionId ,
sourceInboundOrderId : source . sourceInboundOrderId ,
returnCartonId : source . returnCartonId ,
skuCode : source . sku || rows [ 0 ] . originalSku ,
productNameZh : source . name ,
totalPackageCount : 1 ,
@@ -340,7 +350,7 @@ function buildSkuRelabelItems(): RelabelCreate['items'] {
labelRequirement : form . requirement || undefined ,
cartonMark ,
fbaCartonMark : changeCarton ? fbaCartonMark : undefined ,
destinationFbaWarehouseCode : changeCarton ? destinationWarehouseId : undefined ,
destinationFbaWarehouseCode : changeCarton ? form . destination : undefined ,
operationType : changeCarton ? 'CARTON_AND_SKU' as const : 'SKU_LABEL' as const ,
skuItems : rows . map ( ( row ) => ( { skuCode : row . originalSku . trim ( ) , newSkuCode : row . newSku . trim ( ) , quantity : row . quantity , remark : row . remark || undefined } ) ) ,
remark : undefined ,
@@ -402,14 +412,16 @@ function downloadRelabelTemplate(templateType: RelabelTemplateType) {
if ( ! cargoRows . value . length ) { ElMessage . warning ( '请先选择有库存的历史入仓单' ) ; return }
const cartonTemplate = templateType === 'carton'
const mixedTemplate = templateType === 'mixed'
const includeDestination = false
const cartonMarkHeader = props . service === 'transfer' ? '客户箱唛号' : '仓库唛头'
const header = cartonTemplate
? [ '明细行标识' , '仓库唛头' , '新FBA箱唛' , 'FBA目的仓' , 'FBA货件编号' , '备注' ]
: [ '明细行标识' , '仓库唛头' , '旧SKU标签' , '新SKU标签' , '换标数量(件)' ,
... ( mixedTemplate ? [ '新FBA箱唛' , 'FBA目的仓' , 'FBA货件编号' ] : [ ] ) , '备注' ]
? [ '明细行标识' , cartonMarkHeader , '新FBA箱唛' , ... ( includeDestination ? [ 'FBA目的仓' ] : [ ] ) , 'FBA货件编号' , '备注' ]
: [ '明细行标识' , cartonMarkHeader , '旧SKU标签' , '新SKU标签' , '换标数量(件)' ,
... ( mixedTemplate ? [ '新FBA箱唛' , ... ( includeDestination ? [ 'FBA目的仓' ] : [ ] ) , 'FBA货件编号' ] : [ ] ) , '备注' ]
const rows = cargoRows . value . map ( ( item ) => cartonTemplate
? [ item . id , item . cartonMark , item . fbaCartonMark , item . destinationWarehouseId , item . shipmentNo , item . remark ]
? [ item . id , item . cartonMark , item . fbaCartonMark , ... ( includeDestination ? [ item . destinationWarehouseId ] : [ ] ) , item . shipmentNo , item . remark ]
: [ item . id , item . cartonMark , item . sku , '' , 1 ,
... ( mixedTemplate ? [ item . fbaCartonMark , item . destinationWarehouseId , item . shipmentNo ] : [ ] ) , item . remark ] )
... ( mixedTemplate ? [ item . fbaCartonMark , ... ( includeDestination ? [ item . destinationWarehouseId ] : [ ] ) , item . shipmentNo ] : [ ] ) , item . remark ] )
const blob = new Blob ( [ ` \ uFEFF ${ [ header , ... rows ] . map ( ( row ) => row . map ( csvCell ) . join ( ',' ) ) . join ( '\r\n' ) } \ r \ n ` ] , { type : 'text/csv;charset=utf-8' } )
const url = URL . createObjectURL ( blob )
const anchor = document . createElement ( 'a' )
@@ -434,11 +446,11 @@ async function importRelabelTemplate(event: Event, templateType: RelabelTemplate
const lines = ( await file . text ( ) ) . replace ( /^\uFEFF/ , '' ) . split ( /\r?\n/ ) . filter ( ( line ) => line . trim ( ) )
if ( lines . length < 2 ) { ElMessage . error ( '模板中没有可导入的明细' ) ; return }
// Accept both the current display name and legacy template header while keeping the internal key stable.
const headers = parseCsvLine ( lines [ 0 ] ) . map ( ( name ) => name === '仓库唛头' || name === '旧唛头' ? '旧箱唛' : name )
const headers = parseCsvLine ( lines [ 0 ] ) . map ( ( name ) => name === '仓库唛头' || name === '客户箱唛号' || name === '旧唛头' ? '旧箱唛' : name )
if ( templateType !== 'carton' ) {
const mixedTemplate = templateType === 'mixed'
const required = [ '明细行标识' , '旧箱唛' , '旧SKU标签' , '新SKU标签' , '换标数量(件)' ,
... ( mixedTemplate ? [ '新FBA箱唛' , 'FBA目的仓' , 'FBA货件编号' ] : [ ] ) ]
... ( mixedTemplate ? [ '新FBA箱唛' , 'FBA货件编号' ] : [ ] ) ]
if ( required . some ( ( name ) => ! headers . includes ( name ) ) ) {
ElMessage . error ( 'SKU 关联模板格式不正确,请重新下载 SKU 关联模板' ) ; return
}
@@ -464,7 +476,7 @@ async function importRelabelTemplate(event: Event, templateType: RelabelTemplate
newSku : item [ '新SKU标签' ] . trim ( ) ,
quantity ,
fbaCartonMark : mixedTemplate ? item [ '新FBA箱唛' ] . trim ( ) : '' ,
destinationWarehouseId : mixedTemplate ? item [ 'FBA目的仓' ] . trim ( ) : '' ,
destinationWarehouseId : form . destination ,
remark : item [ '备注' ] || '' ,
templateType ,
selected : true ,
@@ -475,13 +487,13 @@ async function importRelabelTemplate(event: Event, templateType: RelabelTemplate
ElMessage . success ( ` 已读取 ${ rows . length } 条绑定关系,请选择后导入到明细 ` )
return
}
const required = [ '明细行标识' , '旧箱唛' , '新FBA箱唛' , 'FBA目的仓' , 'FBA货件编号' ]
const required = [ '明细行标识' , '旧箱唛' , '新FBA箱唛' , 'FBA货件编号' ]
if ( required . some ( ( name ) => ! headers . includes ( name ) ) ) { ElMessage . error ( '箱号关联模板格式不正确,请重新下载箱号关联模板' ) ; return }
const imported = lines . slice ( 1 ) . map ( parseCsvLine )
. map ( ( values ) => Object . fromEntries ( headers . map ( ( key , index ) => [ key , values [ index ] || '' ] ) ) )
. filter ( ( item ) => item [ '旧箱唛' ] || item [ '新FBA箱唛' ] )
if ( ! imported . length || imported . some ( ( item ) => required . some ( ( key ) => ! item [ key ] ) ) ) {
ElMessage . error ( '模板中的明细行、仓库唛头、新 FBA 箱唛、FBA 目的仓 和货件编号必须填写完整' ) ; return
ElMessage . error ( '模板中的明细行、仓库唛头、新 FBA 箱唛和货件编号必须填写完整' ) ; return
}
if ( new Set ( imported . map ( ( item ) => item [ '明细行标识' ] ) ) . size !== imported . length ) {
ElMessage . error ( '模板中的明细行标识不能重复' ) ; return
@@ -495,7 +507,7 @@ async function importRelabelTemplate(event: Event, templateType: RelabelTemplate
const row = cargoRows . value . find ( ( candidate ) => String ( candidate . id ) === rowId ) !
bindings . push ( { id : ++ skuRelabelSequence , sourceRowId : row . id , cartonMark : item [ '旧箱唛' ] ,
originalSku : row . sku , newSku : '' , quantity : 1 , fbaCartonMark : item [ '新FBA箱唛' ] ,
destinationWarehouseId : item [ 'FBA目的仓' ] , remark : item [ '备注' ] || '' , templateType ,
destinationWarehouseId : form . destination , remark : item [ '备注' ] || '' , templateType ,
selected : true , shipmentNo : item [ 'FBA货件编号' ] } )
}
importedRelabelBindings . value = bindings
@@ -509,13 +521,34 @@ function importSelectedBindingsToDetails() {
if ( ! selected . length ) { ElMessage . warning ( '请至少选择一条绑定关系' ) ; return }
const templateType = selected [ 0 ] . templateType
if ( selected . some ( ( item ) => item . templateType !== templateType ) ) { ElMessage . warning ( '请重新上传同一种绑定模板' ) ; return }
if ( props . service === 'transfer' ) {
const importedRows = selected . map ( ( binding ) => ( {
... createCargoRow ( ) ,
sku : binding . originalSku ,
newSkuCode : binding . newSku ,
quantity : binding . quantity ,
customerCartonMarks : binding . cartonMark ,
fbaCartonMark : binding . fbaCartonMark ,
destinationWarehouseId : '' ,
remark : binding . remark ,
} ) )
const importedMarks = new Set ( importedRows . flatMap ( transferCustomerMarks ) . map ( ( mark ) => mark . toUpperCase ( ) ) )
transferRelabelRows . value = [
... transferRelabelRows . value . filter ( ( row ) => ! transferCustomerMarks ( row ) . some ( ( mark ) => importedMarks . has ( mark . toUpperCase ( ) ) ) ) ,
... importedRows ,
]
labelingMode . value = templateType === 'carton' ? 'carton' : templateType === 'sku' ? 'sku' : 'carton_and_sku'
importedRelabelBindings . value = importedRelabelBindings . value . filter ( ( item ) => ! item . selected )
ElMessage . success ( ` 已导入 ${ importedRows . length } 条仓储转运换标明细 ` )
return
}
if ( templateType === 'carton' ) {
for ( const binding of selected ) {
const source = cargoRows . value . find ( ( row ) => row . id === binding . sourceRowId )
if ( ! source ) continue
Object . assign ( source , { included : true , quantity : 1 , shipmentNo : binding . shipmentNo ,
cartonMark : binding . cartonMark , fbaCartonMark : binding . fbaCartonMark ,
destinationWarehouseId : binding . destinationWarehouseId , remark : binding . remark } )
destinationWarehouseId : form . destination , remark : binding . remark } )
}
labelingMode . value = 'carton'
} else {
@@ -523,12 +556,13 @@ function importSelectedBindingsToDetails() {
for ( const item of importedRows ) {
skuRelabelRows . value = skuRelabelRows . value . filter ( ( row ) => ! ( row . cartonMark . trim ( ) . toUpperCase ( ) === item . cartonMark . trim ( ) . toUpperCase ( )
&& row . originalSku . trim ( ) . toUpperCase ( ) === item . originalSku . trim ( ) . toUpperCase ( ) ) )
item . destinationWarehouseId = form . destination
skuRelabelRows . value . push ( item )
if ( templateType === 'mixed' ) {
const source = cargoRows . value . find ( ( row ) => row . id === item . sourceRowId )
const shipmentNo = selected . find ( ( binding ) => binding . id === item . id ) ? . shipmentNo || ''
if ( source ) Object . assign ( source , { included : true , cartonMark : item . cartonMark ,
fbaCartonMark : item . fbaCartonMark , destinationWarehouseId : item . destinationWarehouseId , shipmentNo } )
fbaCartonMark : item . fbaCartonMark , destinationWarehouseId : form . destination , shipmentNo } )
}
}
labelingMode . value = templateType === 'mixed' ? 'carton_and_sku' : 'sku'
@@ -634,7 +668,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
|| cargoRows . value . some ( ( item ) => item . quantity < 0 || item . quantity > Number ( item . availableQuantity || 0 ) )
: ( props . service === 'labeling' || props . service === 'relabel' ) && ( relabelRows . length === 0 || ( props . service === 'labeling' && labelingMode . value !== 'carton'
? skuRelabelRows . value . some ( ( item ) => ! item . cartonMark || ! item . originalSku || ! item . newSku || item . quantity < 1
|| ( labelingMode . value === 'carton_and_sku' && ( ! item . fbaCartonMark || ! item . destinationWarehouseId ) ) )
|| ( labelingMode . value === 'carton_and_sku' && ! item . fbaCartonMark ) )
: cargoRows . value . some ( ( item ) => ( props . service !== 'labeling' && ! item . sku ) || ! item . name
|| ( props . service !== 'labeling' && item . quantity < 1 ) ) ) )
if ( ( props . service === 'relabel' && ! form . inboundNo ) || ! primaryWarehouse || invalidCargo ) {
@@ -646,6 +680,9 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ! form . deliveryMethod ) {
ElMessage . warning ( '请选择换标完成后的派送方式' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ! form . destination ) {
ElMessage . warning ( '请选择本张贴标申请统一使用的 FBA 目的仓' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ( form . deliveryMethod === 'EXPRESS' || form . deliveryMethod === 'EXPRESS_PICKUP' )
&& form . relabel && ! shippingLabelFile . value ) {
ElMessage . warning ( '选择打印面单时必须上传面单文件' ) ; return
@@ -653,8 +690,8 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . shipmentNo || ! item . cartonMark || ! item . fbaCartonMark ) ) {
ElMessage . warning ( '请上传关联面单,确保每箱的我方箱唛和 FBA 箱唛完整' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . destinationWarehouseId || ! item . sourceInventoryPositionId ) ) {
ElMessage . warning ( '请选择有库存的历史入仓单,并为每项填写 FBA 目的仓 ' ) ; return
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . sourceInventoryPositionId ) ) {
ElMessage . warning ( '请选择有库存的历史入仓单' ) ; return
}
if ( props . service === 'disposal' && ! form . reason ) {
ElMessage . warning ( '请选择废弃原因' ) ; return
@@ -674,13 +711,13 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
}
if ( props . service === 'transfer' && form . relabel ) {
if ( ! form . labelDeadline || ! form . relabelContact || ! form . relabelPhone || ( labelingMode . value !== 'sku' && ! form . relabelDeliveryMethod ) ) { ElMessage . warning ( '请填写换标截止时间、换标联系人和换标后派送方式' ) ; return }
if ( ! transferRelabelDefaultDestination . value ) { ElMessage . warning ( '请选择整票换标货物的 FBA 目的仓' ) ; return }
const relabelRows = transferRelabelRows . value
const allMarks = relabelRows . flatMap ( transferCustomerMarks )
if ( ! relabelRows . length || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . length !== row . quantity ) ) { ElMessage . warning ( '每个换标明细的客户箱唛数量必须与换标数量一致' ) ; return }
if ( new Set ( allMarks . map ( ( mark ) => mark . toUpperCase ( ) ) ) . size !== allMarks . length ) { ElMessage . warning ( '客户箱唛号不能重复' ) ; return }
if ( allMarks . join ( ',' ) . length > 2000 || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . join ( ',' ) . length > 255 ) ) { ElMessage . warning ( '客户箱唛内容过长,请减少单次提交箱数或缩短箱唛号' ) ; return }
if ( labelingMode . value !== 'sku' && ! form . destination ) { ElMessage . warning ( '箱唛换标需要先在入库与转运信息中选择目的 FBA 仓 ') ; return }
if ( relabelRows . some ( ( row ) => row . quantity < 1 || ( labelingMode . value !== 'carton' && ( ! row . sku . trim ( ) || ! row . newSkuCode . trim ( ) ) ) || ( labelingMode . value !== 'sku' && ! row . fbaCartonMark . trim ( ) ) ) ) { ElMessage . warning ( '请按当前换标类型完整填写换标明细' ) ; return }
if ( relabelRows . some ( ( row ) => row . quantity < 1 || ( labelingMode . value !== 'carton' && ( ! row . sku . trim ( ) || ! row . newSkuCode . trim ( ) ) ) || ( labelingMode . value !== 'sku' && ! row . fbaCartonMark . trim ( ) ) ) ) { ElMessage . warning ( '请按当前换标类型填写完整明细 ') ; return }
}
if ( props . service !== 'transfer' || action === 'submit' ) {
try { await ElMessageBox . confirm ( ` 确认提交 ${ config . value . title } 吗? ` , '提交确认' , { confirmButtonText : '提交' , cancelButtonText : '取消' } ) } catch { return }
@@ -691,7 +728,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' ) {
const sourceInboundOrders = [ ... new Set ( cargoRows . value . map ( ( item ) => item . sourceInboundOrderNo || item . sourceInboundCode ) . filter ( ( value ) : value is string => Boolean ( value ) ) ) ] . join ( '、' ) . slice ( 0 , 128 )
const items = labelingMode . value === 'carton'
? cartonDetailRows . value . map ( ( item ) => ( { sourceInventoryBalanceId : item . sourceInventoryBalanceId ! , sourceInventoryPositionId : item . sourceInventoryPositionId ! , sourceInboundOrderId : item . sourceInboundOrderId ! , skuCode : item . sku , productNameZh : item . name , totalPackageCount : 1 , notifiedPackageCount : 1 , shipmentNo : item . shipmentNo , trackingCode : form . trackingNo || undefined , newSkuCode : undefined , labelRequirement : form . requirement || undefined , cartonMark : item . cartonMark , fbaCartonMark : item . fbaCartonMark , destinationFbaWarehouseCode : item . destinationWarehouseId , operationType : 'CARTON_LABEL' as const , skuItems : [ ] , remark : item . remark || undefined } ) )
? cartonDetailRows . value . map ( ( item ) => ( { sourceInventoryBalanceId : item . sourceInventoryBalanceId , sourceInventoryPositionId : item . sourceInventoryPositionId , sourceInboundOrderId : item . sourceInboundOrderId , returnCartonId : item . returnCartonId , skuCode : item . sku , productNameZh : item . name , totalPackageCount : 1 , notifiedPackageCount : 1 , shipmentNo : item . shipmentNo , trackingCode : form . trackingNo || undefined , newSkuCode : undefined , labelRequirement : form . requirement || undefined , cartonMark : item . cartonMark , fbaCartonMark : item . fbaCartonMark , destinationFbaWarehouseCode : form . destination , operationType : 'CARTON_LABEL' as const , skuItems : [ ] , remark : item . remark || undefined } ) )
: buildSkuRelabelItems ( )
const created = await createRelabel ( { customerOrderNo : sourceInboundOrders || '贴标申请' , operationWarehouseId : form . warehouse , labelDeadline : form . labelDeadline , contactName : form . contact , contactPhone : form . phone , remark : form . remark || undefined , idempotencyKey , items ,
deliveryMethod : labelingMode . value === 'sku' ? undefined : form . deliveryMethod as 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPRESS_PICKUP' ,
@@ -722,7 +759,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
const transferRemark = [ form . remark , form . relabel && form . requirement ? ` 换标要求: ${ form . requirement } ` : '' ] . filter ( Boolean ) . join ( '; ' )
const operationType = labelingMode . value === 'carton' ? 'CARTON_LABEL' : labelingMode . value === 'sku' ? 'SKU_LABEL' : 'CARTON_AND_SKU'
const selectedFba = fbaWarehouses . value . find ( ( warehouse ) => warehouse . warehouseCode === form . destination )
const payload : TransferCreate = { operatingWarehouseId : primaryWarehouse , destinationType : transferDestinationType . value , fbaWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseId : transferDestinationType . value === 'FBA' ? String ( selectedFba ? . id || '' ) || undefined : undefined , destinationWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseName : transferDestinationType . value === 'FBA' ? selectedFba ? . warehouseName : undefined , privateDestinationAddress : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationAddress : undefined , privateDestinationPostalCode : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPostalCode : undefined , privateDestinationContactName : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationContact : undefined , privateDestinationContactPhone : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPhone : undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , estimatedShipAt : form . estimatedShipAt || undefined , fbaShipmentNo : transferDestinationType . value === 'FBA' ? form . fbaShipmentNo || undefined : undefined , trackingNo : form . deliveryMethod === 'CUSTOMER_DELIVERY' ? form . trackingNo || undefined : undefined , fbaBoxNos : customerCartonMarks . length ? customerCartonMarks . join ( ',' ) : undefined , remark : transferRemark || undefined , needRelabel : props . service === 'relabel' || form . relabel , labelDeadline : form . relabel ? form . labelDeadline : undefined , pickupContactName : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupContact : undefined , pickupContactPhone : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupPhone : undefined , relabelContactName : form . relabel ? form . relabelContact : undefined , relabelContactPhone : form . relabel ? form . relabelPhone : undefined , relabelDeliveryMethod : form . relabel && labelingMode . value !== 'sku' ? form . relabelDeliveryMethod as 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPRESS_PICKUP' : undefined , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , packageCount : item . quantity , shippingMark : transferCustomerMarks ( item ) . join ( ',' ) || undefined } ) ) , relabelItems : form . relabel ? transferRelabelRows . value . map ( ( item ) => ( { operationType , customerCartonMarks : transferCustomerMarks ( item ) , originalSku : item . sku , newSku : item . newSkuCode || undefined , quantity : item . quantity , fbaCartonMark : item . fbaCartonMark || undefined , destinationWarehouseCode : labelingMode . value === 'sku' ? undefined : form . destination || undefined , remark : item . remark || undefined } ) ) : undefined }
const payload : TransferCreate = { operatingWarehouseId : primaryWarehouse , destinationType : transferDestinationType . value , fbaWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseId : transferDestinationType . value === 'FBA' ? String ( selectedFba ? . id || '' ) || undefined : undefined , destinationWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseName : transferDestinationType . value === 'FBA' ? selectedFba ? . warehouseName : undefined , privateDestinationAddress : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationAddress : undefined , privateDestinationPostalCode : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPostalCode : undefined , privateDestinationContactName : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationContact : undefined , privateDestinationContactPhone : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPhone : undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , estimatedShipAt : form . estimatedShipAt || undefined , fbaShipmentNo : transferDestinationType . value === 'FBA' ? form . fbaShipmentNo || undefined : undefined , trackingNo : form . deliveryMethod === 'CUSTOMER_DELIVERY' ? form . trackingNo || undefined : undefined , fbaBoxNos : customerCartonMarks . length ? customerCartonMarks . join ( ',' ) : undefined , remark : transferRemark || undefined , needRelabel : props . service === 'relabel' || form . relabel , labelDeadline : form . relabel ? form . labelDeadline : undefined , pickupContactName : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupContact : undefined , pickupContactPhone : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupPhone : undefined , relabelContactName : form . relabel ? form . relabelContact : undefined , relabelContactPhone : form . relabel ? form . relabelPhone : undefined , relabelDeliveryMethod : form . relabel && labelingMode . value !== 'sku' ? form . relabelDeliveryMethod as 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPRESS_PICKUP' : undefined , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , packageCount : item . quantity , shippingMark : transferCustomerMarks ( item ) . join ( ',' ) || undefined } ) ) , relabelItems : form . relabel ? transferRelabelRows . value . map ( ( item ) => ( { operationType , customerCartonMarks : transferCustomerMarks ( item ) , originalSku : item . sku , newSku : item . newSkuCode || undefined , quantity : item . quantity , fbaCartonMark : item . fbaCartonMark || undefined , destinationWarehouseCode : transferRelabelDefaultDestination . value || undefined , remark : item . remark || undefined } ) ) : undefined }
const created = editingTransferNo . value
? await updateTransfer ( editingTransferNo . value , payload )
: await createTransfer ( payload )
@@ -912,9 +949,16 @@ async function selectOperationWarehouse() {
finally { relabelCandidatesLoading . value = false }
}
function selectRelabelInventory ( inboundOrderId : string ) {
cargoRows . value = relabelCandidates . value
. filter ( ( candidate ) => String ( candidate . inboundOrderId ) === inboundOrderId )
. flatMap ( ( candidate ) => candidate . positions . flatMap ( ( position ) => Array . from ( { length : Math . min ( position . availablePackages , 500 ) } , ( ) => ( {
const rows : CargoRow [ ] = [ ]
for ( const candidate of relabelCandidates . value . filter ( ( item ) => ( item . sourceType === 'RETURN_CARTON' ? ` RETURN_CARTON: ${ item . returnCartonId } ` : String ( item . inboundOrderId ) ) === inboundOrderId ) ) {
if ( candidate . sourceType === 'RETURN_CARTON' ) rows . push ( {
id : ++ cargoSequence , sku : candidate . skuCode || '' , name : candidate . productNameZh || '退货合箱库存' ,
included : true , quantity : 1 , availableQuantity : 1 , returnCartonId : String ( candidate . returnCartonId ) ,
sourceInboundOrderNo : candidate . inboundOrderNo , sourceWarehouseName : candidate . overseasWarehouseName ,
fnsku : '' , shipmentNo : candidate . inboundOrderNo || candidate . cartonMark || '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' ,
cartonMark : candidate . cartonMark || '' , fbaCartonMark : '' , destinationWarehouseId : candidate . destinationFbaWarehouseCode || '' , remark : '' ,
} )
else for ( const position of candidate . positions ) rows . push ( ... Array . from ( { length : Math . min ( position . availablePackages , 500 ) } , ( ) => ( {
id : ++ cargoSequence , sku : candidate . skuCode || '' , name : candidate . productNameZh || '库存货物' ,
included : false , quantity : 1 , availableQuantity : 1 ,
sourceInventoryBalanceId : String ( candidate . inventoryBalanceId ) , sourceInventoryPositionId : String ( position . positionId ) ,
@@ -923,7 +967,9 @@ function selectRelabelInventory(inboundOrderId: string) {
palletNo : position . palletNo , warehouseLocationCode : position . warehouseLocationCode ,
fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , cartonMark : '' , fbaCartonMark : '' ,
destinationWarehouseId : candidate . destinationFbaWarehouseCode || '' , remark : '' ,
} ) ) ) ) . slice ( 0 , 500 )
} ) ) )
}
cargoRows . value = rows . slice ( 0 , 500 )
if ( cargoRows . value . length ) cargoRows . value [ 0 ] . included = true
skuRelabelRows . value = [ ]
importedRelabelBindings . value = [ ]
@@ -975,6 +1021,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-else-if = "props.service === 'disposal'"><span>进仓编号</span><el-input v-model="form.inboundNo" readonly / > < / label >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库海外仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(operationWarehousesError)" clearable :placeholder="props.service === 'transfer' ? '请选择海外仓' : '请选择国外中转仓'" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint">{{ operationWarehousesError }}</small><small v-else-if="props.service === 'labeling'" class="warehouse-hint">选择后仅查询当前客户在该中转仓的可用库存。</small><small v-else-if="props.service === 'transfer'" class="warehouse-hint" > 本批货物将先进入该海外仓完成签收和入库 。 < / small > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择有库存的历史入仓单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前中转仓暂无已入库可用库存" placeholder="请选择曾经下单且当前仍有库存的入仓单" @change="selectRelabelInventory"><el-option v-for="option in relabelInboundOptions" :key="option.value" :label="`${option.inboundOrderNo}|入仓码 ${option.inboundCode}|可用 ${option.availablePackages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 仅显示当前客户在所选中转仓已经完成入库且仍有可用库存的历史入仓单 。 < / small > < / label >
< label v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>FBA 目的仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择本张申请统一的 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 整张贴标申请只能选择一个目的仓 , 全部明细统一使用该目的仓 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>货物如何送到海外仓</span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < small > 仓库上门提货 : 填写货物当前地址 ; 客户送货到仓 : 下方自动展示海外仓收货信息 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'" class="pickup-address-field"><span><i>*</i>提货地址</span><el-input v-model="form.address" placeholder="请输入仓库上门提货的详细地址" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人</span><el-input v-model="form.pickupContact" placeholder="请输入提货联系人" maxlength="128" / > < / label >
@@ -999,7 +1046,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-if = "props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>{{ props.service === 'transfer' ? '预计入仓时间' : '预计到达时间' }}</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" :placeholder="props.service === 'transfer' ? '请选择预计入仓时间' : '请选择预计到达时间'" / > < / label >
< label v-if = "props.service === 'transfer'"><span>预计发货时间</span><el-date-picker v-model="form.estimatedShipAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选填,可线下确认后补充" /><small class="warehouse-hint" > 暂未确定时可不填写 , 不影响货物先入海外仓 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否有目的去向</span><el-radio-group v-model="transferDestinationType" @change="selectTransferDestinationType"><el-radio-button value="NONE">暂未确定</el-radio-button><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="PRIVATE" > 私人仓 < / el -radio -button > < / el-radio-group > < small > 暂未确定也可以提交 ; 完成入库后可在新建出库单中批量绑定目的地 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span><i>*</i>目的 FBA 仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 整张转运单统一使用该目的仓 , 换标明细自动继承 。 < / small > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span><i>*</i>未换标货物 目的 FBA 仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 此处只表示不换标箱子的去向 ; 需要换箱唛的箱子在换标明细中单独选择目的仓 。 < / small > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="选填,可后续补充" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'" class="warehouse-span-2"><span><i>*</i>私人仓地址</span><el-input v-model="form.privateDestinationAddress" maxlength="1000" placeholder="请输入私人仓完整地址" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'"><span><i>*</i>邮编</span><el-input v-model="form.privateDestinationPostalCode" maxlength="32" placeholder="请输入邮编" / > < / label >
@@ -1047,15 +1094,15 @@ function selectRelabelInventory(inboundOrderId: string) {
< / div >
< div v-if = "importedRelabelBindings.length" class="warehouse-import-preview" >
< div class = "warehouse-section-heading" > < div > < strong > 待导入绑定关系 < / strong > < p > 可只勾选本次需要换标的部分 , 也可以全选后一次导入 。 < / p > < / div > < div > < el-checkbox : model -value = " importedRelabelBindings.every ( ( item ) = > item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div>
<div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头</th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th><th>FBA 目的仓</th>< /tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td><td>{{ warehouseDisplay(item.destinationWarehouseId) }}</td></tr></tbody></table></div>
<div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头</th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th></tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td></tr></tbody></table></div>
</div>
<div class=" warehouse - section - heading warehouse - tab - actions ">
<div><strong>绑定明细</strong><p>上传后在下方回显,也可以手动添加;SKU 与箱号关联通过仓库唛头合并。系统只提供大概库存位置,不代表箱唛已绑定具体托盘。</p></div>
<div v-if=" labelingMode === 'carton' "><el-button type=" primary " size=" small " @click=" addRelabelCargoRow "><Plus :size=" 15 " />手动添加行</el-button><el-button size=" small " @click=" clearRelabelRows ">清空</el-button></div>
<div v-else><el-button type=" primary " size=" small " @click=" addSkuRelabelRow "><Plus :size=" 15 " />添加 SKU 明细</el-button><el-button size=" small " @click=" skuRelabelRows = [ ] ">清空</el-button></div>
</div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - label - table "><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数</th><th>仓库唛头 *</th><th>新 FBA 箱唛 *</th><th>FBA 目的仓 *</th><th> 备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in cartonDetailRows " :key=" row . id "><td><el-input v-model=" row . sku " placeholder=" 选填 " /></td><td><el-input v-model=" row . name " placeholder=" 请输入中文品名 " /></td><td><el-input v-model=" row . shipmentNo " placeholder=" 请输入FBA货件编号 " /></td><td><strong>1 箱</strong></td><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-select v-model=" row . destinationWarehouseId " :loading=" fbaWarehousesLoading " :disabled=" Boolean ( fbaWarehousesError ) " placeholder=" 请选择 FBA 目的仓 "><el-option v-for=" warehouse in fbaWarehouses " :key=" warehouse . id " :label=" warehouseLabel ( warehouse ) " :value=" warehouse . warehouseCode " /></el-select></td><td><el- input v-model="row . remark " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入明细备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 移出本次明细 " @click=" removeCargoRow ( row . id ) "><Trash2 :size=" 16 " />移出</button></td></tr><tr v-if=" cartonDetailRows . length === 0 "><td colspan=" 9 "><div class=" warehouse - empty ">暂无本次换标明细,请上传关系后导入或手动添加</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - sku - label - table "><thead><tr><th>仓库唛头 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th v-if=" labelingMode === 'carton_and_sku' ">新 FBA 箱唛 *</th><th v-if=" labelingMode === 'carton_and_sku' ">FBA 目的仓 *</th><th >备注</th><th>操作</th></tr></thead><tbody><tr v-for="row in skuRelabelRows " :key=" row . id "><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " @change=" applyExistingCartonBinding ( row ) " /></td><td><el-input v-model=" row . originalSku " placeholder=" 请输入旧 SKU " @change=" applyExistingSkuBinding ( row ) " /></td><td><el-input v-model=" row . newSku " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入件数 " /></td><td v-if=" labelingMode === 'carton_and_sku' "><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td v-if=" labelingMode === 'carton_and_sku' "><el-select v-model=" row . destinationWarehouseId " :loading=" fbaWarehousesLoading " :disabled=" Boolean ( fbaWarehousesError ) " placeholder=" 请选择 FBA 目的仓 "><el-option v-for=" warehouse in fbaWarehouses " :key=" warehouse . id " :label=" warehouseLabel ( warehouse ) " :value=" warehouse . warehouseCode " /></el-select></td><td ><el-input v-model="row . remark " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除 " @click=" removeSkuRelabelRow ( row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" skuRelabelRows . length === 0 "><td :colspan=" labelingMode === 'carton_and_sku' ? 8 : 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - label - table "><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数</th><th>仓库唛头 *</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in cartonDetailRows " :key=" row . id "><td><el-input v-model=" row . sku " placeholder=" 选填 " /></td><td><el-input v-model=" row . name " placeholder=" 请输入中文品名 " /></td><td><el-input v-model=" row . shipmentNo " placeholder=" 请输入FBA货件编号 " /></td><td><strong>1 箱</strong></td><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入明细备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 移出本次明细 " @click=" removeCargoRow ( row . id ) "><Trash2 :size=" 16 " />移出</button></td></tr><tr v-if=" cartonDetailRows . length === 0 "><td colspan=" 8 "><div class=" warehouse - empty ">暂无本次换标明细,请上传关系后导入或手动添加</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - sku - label - table "><thead><tr><th>仓库唛头 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th v-if=" labelingMode === 'carton_and_sku' ">新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in skuRelabelRows " :key=" row . id "><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " @change=" applyExistingCartonBinding ( row ) " /></td><td><el-input v-model=" row . originalSku " placeholder=" 请输入旧 SKU " @change=" applyExistingSkuBinding ( row ) " /></td><td><el-input v-model=" row . newSku " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入件数 " /></td><td v-if=" labelingMode === 'carton_and_sku' "><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除 " @click=" removeSkuRelabelRow ( row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" skuRelabelRows . length === 0 "><td :colspan=" labelingMode === 'carton_and_sku' ? 7 : 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
</template>
<template v-else>
<div class=" warehouse - section - heading "><h3>货物明细</h3><div v-if=" props . service !== 'disposal' "><el-button type=" primary " size=" small " @click=" addCargoRow "><Plus :size=" 15 " />添加行</el-button><el-button size=" small " @click=" cargoRows = [ ] ">清空</el-button></div></div>
@@ -1064,11 +1111,11 @@ function selectRelabelInventory(inboundOrderId: string) {
</div>
<div v-if=" props . service === 'transfer' && form . relabel " class=" warehouse - card warehouse - form - section warehouse - transfer - relabel ">
<div class=" warehouse - section - heading warehouse - labeling - heading "><div><h3>货物明细</h3><p>换标明细与贴标申请保持一致;客户只填写自己的箱唛,我方箱唛由系统生成。</p></div><div class=" warehouse - template - actions "><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'sku' ) "><Download :size=" 15 " />下载 SKU 关联模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" skuTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传 SKU 关联</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'carton' ) "><Download :size=" 15 " />下载箱号关联模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" cartonTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传箱号关联</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'mixed' ) "><Download :size=" 15 " />下载 SKU 混箱模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" mixedTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传 SKU 混箱</el-button><input ref=" skuTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'sku' ) "><input ref=" cartonTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'carton' ) "><input ref=" mixedTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'mixed' ) "></div></div>
<div class=" warehouse - form - grid warehouse - transfer - relabel - fields "><label><span><i>*</i>换标截止时间</span><el-date-picker v-model=" form . labelDeadline " type=" datetime " value-format=" YYYY - MM - DDTHH : mm : ss " placeholder=" 请选择截止时间 " /></label><label><span><i>*</i>换标联系人</span><el-input v-model=" form . relabelContact " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 128 " /></label><label><span><i>*</i>换标联系人电话</span><el-input v-model=" form . relabelPhone " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 64 " /></label><label v-if=" labelingMode !== 'sku' "><span><i>*</i>换标后派送方式</span><el-select v-model=" form . relabelDeliveryMethod " placeholder=" 请选择派送方式 "><el-option label=" 卡车派送 " value=" TRUCK " /><el-option label=" 快递派送 " value=" EXPRESS " /><el-option label=" 卡车自提 " value=" TRUCK _PICKUP " /><el-option label=" 快递自提 " value=" EXPRESS _PICKUP " /></el-select></label><label class=" warehouse - span - 2 "><span>贴标要求</span><el-input v-model=" form . requirement " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入贴标要求 ( 选填 ) " /></label></div>
<div class=" warehouse - form - grid warehouse - transfer - relabel - fields "><label><span><i>*</i>换标截止时间</span><el-date-picker v-model=" form . labelDeadline " type=" datetime " value-format=" YYYY - MM - DDTHH : mm : ss " placeholder=" 请选择截止时间 " /></label><label><span><i>*</i>换标联系人</span><el-input v-model=" form . relabelContact " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 128 " /></label><label><span><i>*</i>换标联系人电话</span><el-input v-model=" form . relabelPhone " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 64 " /></label><label v-if=" labelingMode !== 'sku' "><span><i>*</i>换标后派送方式</span><el-select v-model=" form . relabelDeliveryMethod " placeholder=" 请选择派送方式 "><el-option label=" 卡车派送 " value=" TRUCK " /><el-option label=" 快递派送 " value=" EXPRESS " /><el-option label=" 卡车自提 " value=" TRUCK _PICKUP " /><el-option label=" 快递自提 " value=" EXPRESS _PICKUP " /></el-select></label><label class=" warehouse - span - 2 "><span><i>*</i>整票换标货物目的 FBA 仓</span><el-select v-model=" transferRelabelDefaultDestination " :loading=" fbaWarehousesLoading " :disabled=" Boolean ( fbaWarehousesError ) " filterable placeholder=" 请选择整票货物的目的仓 "><el-option v-for=" warehouse in fbaWarehouses " :key=" warehouse . id " :label=" warehouseLabel ( warehouse ) " :value=" warehouse . warehouseCode " /></el-select><small class=" warehouse - hint ">本票所有换标货物统一前往该目的仓,暂不支持一票多个目的仓。</small></label><label class=" warehouse - span - 2 "><span> 贴标要求</span><el-input v-model="form . requirement " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入贴标要求 ( 选填 ) " /></label></div>
<div class=" warehouse - labeling - mode "><span>换标类型</span><el-radio-group v-model=" labelingMode " aria-label=" 换标类型 "><el-radio-button value=" carton ">只换箱唛</el-radio-button><el-radio-button value=" sku ">只换 SKU 标签</el-radio-button><el-radio-button value=" carton _and _sku ">SKU 和箱唛都换</el-radio-button></el-radio-group><p>{{ labelingModeHint }}</p></div>
<div v-if=" importedRelabelBindings . length " class=" warehouse - import - preview "><div class=" warehouse - section - heading "><div><strong>待导入绑定关系</strong><p>可只勾选本次需要换标的部分,也可以全选后一次导入。</p></div><div><el-checkbox :model-value=" importedRelabelBindings . every ( ( item ) => item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div><div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头 </th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th></tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td></tr></tbody></table></div></div>
<div v-if=" importedRelabelBindings . length " class=" warehouse - import - preview "><div class=" warehouse - section - heading "><div><strong>待导入绑定关系</strong><p>可只勾选本次需要换标的部分,也可以全选后一次导入。</p></div><div><el-checkbox :model-value=" importedRelabelBindings . every ( ( item ) => item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div><div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>客户箱唛号 </th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th></tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td></tr></tbody></table></div></div>
<div class=" warehouse - section - heading warehouse - tab - actions "><div><strong>绑定明细</strong><p>上传后在下方回显,也可以手动添加;客户箱唛作为仓库唛头来源,系统我方箱唛不需要手填。</p></div><div><el-button type=" primary " size=" small " @click=" addTransferRelabelRow "><Plus :size=" 15 " />添加 SKU 明细</el-button><el-button size=" small " @click=" clearTransferRelabelRows ">清空</el-button></div></div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 5 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 5 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加换标 明细”</div></td></tr></tbody></table></div>
<div v-else-if=" labelingMode === 'sku' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . newSkuCode " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入箱数 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . newSkuCode " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入数量 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 7 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
</div>
@@ -1084,6 +1131,10 @@ function selectRelabelInventory(inboundOrderId: string) {
<div class=" warehouse - section - heading "><h3>货物明细</h3><el-button v-if=" selectedDetail ? . status !== 'DRAFT' " native-type=" button " type=" primary " @click=" printTransferCartons ( String ( selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || '' ) ) "><Printer :size=" 16 " />打印箱唛</el-button></div>
<div class=" warehouse - table - scroll "><table class=" warehouse - edit - table "><thead><tr><th>商品 SKU</th><th>中文品名</th><th>预计箱数</th><th>客户箱唛号</th></tr></thead><tbody><tr v-for=" ( item , index ) in selectedDetail ? . items || [ ] " :key=" ` ${ item . skuCode } - ${ index } ` "><td>{{ item.skuCode || '—' }}</td><td>{{ item.productNameZh || '—' }}</td><td>{{ item.packageCount ?? '—' }}</td><td>{{ item.shippingMark || '—' }}</td></tr><tr v-if=" ! selectedDetail ? . items ? . length "><td colspan=" 4 "><div class=" warehouse - empty ">暂无货物明细</div></td></tr></tbody></table></div>
</div>
<div v-if=" props . service === 'transfer' && selectedDetail ? . needRelabel " class=" warehouse - card warehouse - form - section ">
<h3>换标箱级去向</h3>
<div class=" warehouse - table - scroll "><table><thead><tr><th>换标类型</th><th>客户箱唛号</th><th>旧 SKU</th><th>新 SKU</th><th>新 FBA 箱唛</th><th>实际 FBA 目的仓</th><th>数量</th><th>备注</th></tr></thead><tbody><tr v-for=" ( binding , index ) in selectedDetail ? . relabelBindings || [ ] " :key=" ` ${ binding . customerCartonMark } - ${ index } ` "><td>{{ binding.operationType === 'CARTON_AND_SKU' ? 'SKU 和箱唛都换' : binding.operationType === 'SKU_LABEL' ? '只换 SKU' : '只换箱唛' }}</td><td>{{ binding.customerCartonMark || '—' }}</td><td>{{ binding.originalSku || '—' }}</td><td>{{ binding.newSku || '—' }}</td><td>{{ binding.fbaCartonMark || '—' }}</td><td>{{ binding.operationType === 'SKU_LABEL' ? '沿用原箱去向' : warehouseDisplay(binding.destinationWarehouseCode) }}</td><td>{{ binding.quantity ?? '—' }}</td><td>{{ binding.remark || '—' }}</td></tr><tr v-if=" ! selectedDetail ? . relabelBindings ? . length "><td colspan=" 8 "><div class=" warehouse - empty ">暂无换标绑定明细</div></td></tr></tbody></table></div>
</div>
<div v-if=" props . service === 'transfer' " class=" warehouse - card warehouse - form - section warehouse - transfer - times "><h3>发货时间</h3><div class=" warehouse - detail - grid "><div><span>预计发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div></div><p>实际发货时间由仓库出库或 PDA 完成后回填,客户不能手动修改。</p></div>
<div v-if=" props . service === 'labeling' " class=" warehouse - card warehouse - form - section ">
<div class=" warehouse - section - heading "><h3>换标完成与出库</h3></div>