@@ -1,19 +1,20 @@
< script setup lang = "ts" >
< script setup lang = "ts" >
import { computed , nextTick , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { computed , nextTick , onActivated , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { useRoute , useRouter } from 'vue-router'
import { useRoute , useRouter } from 'vue-router'
import { ElMessage , ElMessageBox } from 'element-plus'
import { ElMessage , ElMessageBox } from 'element-plus'
import { ArrowLeft , Download , Plus , Printer , Trash2 , Upload , X } from '@lucide/vue'
import { ArrowLeft , ChevronDown , Download , Plus , Printer , Trash2 , Upload , X } from '@lucide/vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/components/common/QueryToolbar.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/components/common/QueryToolbar.vue'
import DataTable , { type DataTableColumn } from '@/components/common/DataTable.vue'
import DataTable , { type DataTableColumn } from '@/components/common/DataTable.vue'
import type { TableAction } from '@/components/common/tableActions'
import type { TableAction } from '@/components/common/tableActions'
import { useAuthStore } from '@/modules/auth/public'
import { useAuthStore } from '@/modules/auth/public'
import CompletionConfirmationDialog from '../components/CompletionConfirmationDialog.vue'
import { returnRelabelCandidate } from '../utils/returnRelabelCandidates'
import { returnRelabelCandidate } from '../utils/returnRelabelCandidates'
import {
import {
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer , resubmitRelabel ,
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer , resubmitRelabel ,
getTransferCartonLabel , submitTransfer , updateTransfer ,
getTransferCartonLabel , submitTransfer , updateTransfer , increaseTransferForecastCartons , getTransferSourceCartons , type TransferSourceCarton ,
confirmRelabelCompletion , confirmTransferMeasurement , downloadRelabelCompletionPhoto , downloadTransferMeasurementPhoto , downloadDiscardPhoto , type DiscardPhoto , uploadRelabelShippingLabel ,
downloadRelabelCompletionPhoto , downloadTransferMeasurementPhoto , downloadDiscardPhoto , type DiscardPhoto , uploadRelabelShippingLabel ,
uploadRelabelFile ,
uploadRelabelFile ,
listDiscards , listRelabelInventoryCandidates , listRelabelHistoricalOrders , listRelabels , listTransfers , cancelDiscard ,
listDiscards , listDiscardCandidates , listRelabelInventoryCandidates , listRelabelHistoricalOrders , listRelabels , listTransfers , cancelDiscard ,
type RelabelInventoryCandidate ,
type RelabelInventoryCandidate ,
type RelabelCreate ,
type RelabelCreate ,
type RelabelDetail ,
type RelabelDetail ,
@@ -33,17 +34,18 @@ type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
type RelabelTemplateType = 'carton' | 'sku' | 'mixed'
type RelabelTemplateType = 'carton' | 'sku' | 'mixed'
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceType ? : 'DROPSHIP' | 'RETURN_CARTON' | 'RETURN_PARCEL' ; 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 CargoRow { discardCartonIds ? : string [ ] ; id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; expectedPackageCount ? : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceType ? : 'DROPSHIP' | 'RETURN_CARTON' | 'RETURN_PARCEL' | 'INVENTORY' ; 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 ; shipmentNo : string ; destinationWarehouseId : string ; remark : string }
interface SkuRelabelRow { id : number ; sourceRowId : number | null ; cartonMark : string ; originalSku : string ; newSku : string ; quantity : number ; fbaCartonMark : string ; shipmentNo : string ; destinationWarehouseId : string ; remark : string }
interface ImportedRelabelBinding extends SkuRelabelRow { templateType : RelabelTemplateType ; selected : boolean ; shipmentNo : string }
interface ImportedRelabelBinding extends SkuRelabelRow { templateType : RelabelTemplateType ; selected : boolean ; shipmentNo : string }
interface ServiceConfig { title : string ; createTitle : string ; numberLabel : string ; emptyText : string ; columns : string [ ] }
interface ServiceConfig { title : string ; createTitle : string ; numberLabel : string ; emptyText : string ; columns : string [ ] }
interface TransferCarton { cartonMark : string ; customerCartonMark ? : string ; cartonIndex : number ; totalCartons : number ; status : string }
interface TransferCarton { items ? : Array < { transferItemId : string | number ; skuCode : string ; quantity : number } > ; cartonMark : string ; customerCartonMark ? : string ; cartonIndex : number ; totalCartons : number ; status : string ; transferItemId ? : string | number ; transferPalletId ? : string | number ; length ? : number ; width ? : number ; height ? : number ; weight ? : number ; quantity ? : number ; discrepancyReason ? : string ; countedAt ? : string }
const props = withDefaults ( defineProps < { service : ServiceType ; createPage ? : boolean ; detailPage ? : boolean ; detailNo ? : string } > ( ) , { createPage : false , detailPage : false , detailNo : '' } )
const props = withDefaults ( defineProps < { service : ServiceType ; createPage ? : boolean ; detailPage ? : boolean ; detailNo ? : string } > ( ) , { createPage : false , detailPage : false , detailNo : '' } )
const auth = useAuthStore ( )
const auth = useAuthStore ( )
const router = useRouter ( )
const router = useRouter ( )
const route = useRoute ( )
const route = useRoute ( )
const detailNumber = computed ( ( ) => props . detailNo || String ( route . params . detailNo || '' ) )
/ / 缓 存 列 表 不 订 阅 其 他 详 情 页 的 路 由 参 数 , 避 免 离 开 时 清 空 筛 选 并 重 复 请 求 。
const detailNumber = computed ( ( ) => props . detailPage ? props . detailNo || String ( route . params . detailNo || '' ) : '' )
const canCreate = computed ( ( ) => auth . hasPermission ( ( {
const canCreate = computed ( ( ) => auth . hasPermission ( ( {
labeling : 'merchant:overseas-warehouse:relabel:create' ,
labeling : 'merchant:overseas-warehouse:relabel:create' ,
transfer : 'merchant:overseas-warehouse:transfer:create' ,
transfer : 'merchant:overseas-warehouse:transfer:create' ,
@@ -53,10 +55,22 @@ const canCreate = computed(() => auth.hasPermission(({
const canManageRelabelFiles = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:relabel:files' ) )
const canManageRelabelFiles = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:relabel:files' ) )
const canConfirmRelabel = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:relabel:confirm' ) )
const canConfirmRelabel = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:relabel:confirm' ) )
const canConfirmTransfer = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:transfer:confirm' ) )
const canConfirmTransfer = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:transfer:confirm' ) )
const canConfirmDiscard = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:discard:confirm' ) )
const completionConfirmOpen = ref ( false )
const completionConfirmNo = ref ( '' )
const completionConfirmKind = ref < 'disposal' | 'labeling' | 'transfer' > ( 'disposal' )
function openCompletionConfirmation ( no : string , kind : 'disposal' | 'labeling' | 'transfer' ) {
if ( ! no ) return
completionConfirmNo . value = no ; completionConfirmKind . value = kind ; completionConfirmOpen . value = true
}
async function refreshAfterCompletion ( no : string ) {
if ( mode . value === 'detail' ) await openDetail ( { '0' : no } )
else await load ( )
}
const canCancelDiscard = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:discard:cancel' ) )
const canCancelDiscard = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:discard:cancel' ) )
const configs : Record < ServiceType , ServiceConfig > = {
const configs : Record < ServiceType , ServiceConfig > = {
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '关联业务单号' , '海外操作仓' , 'SKU 数(种)' , '总箱数/包裹' , '换标类型' , '状态' , '审核时间' , '贴标完成时间' , '贴标出库时间' , '创建时间' ] } ,
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '关联业务单号' , '海外操作仓' , 'SKU 数(种)' , '总箱数/包裹' , '换标类型' , '状态' , '审核时间' , '贴标完成时间' , '贴标出库时间' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '箱数' , '海外仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '换标单号' , '换标状态' , '状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '预计 箱数' , '海外仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '换标单号' , '换标状态' , '状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
disposal : { title : '货物废弃' , createTitle : '新增货物废弃申请' , numberLabel : '废弃申请单号' , emptyText : '暂无货物废弃申请' , columns : [ '操作' , '废弃申请单号' , '废弃出库单号' , '进仓编号' , '操作仓' , 'SKU 汇总' , '废弃数量' , '预估费用' , '状态' , '废弃完成时间' , '创建时间' ] } ,
disposal : { title : '货物废弃' , createTitle : '新增货物废弃申请' , numberLabel : '废弃申请单号' , emptyText : '暂无货物废弃申请' , columns : [ '操作' , '废弃申请单号' , '废弃出库单号' , '进仓编号' , '操作仓' , 'SKU 汇总' , '废弃数量' , '预估费用' , '状态' , '废弃完成时间' , '创建时间' ] } ,
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
}
}
@@ -81,7 +95,7 @@ const listQueryModel = computed<QueryModel>({
const statusOptions = computed ( ( ) => props . service === 'labeling'
const statusOptions = computed ( ( ) => props . service === 'labeling'
? [ { label : '待审核' , value : 'PENDING_REVIEW' } , { label : '待处理' , value : 'PENDING_PROCESSING' } , { label : '处理中' , value : 'PROCESSING' } , { label : '换标完成' , value : 'COMPLETED' } , { label : '已驳回' , value : 'REJECTED' } ]
? [ { label : '待审核' , value : 'PENDING_REVIEW' } , { label : '待处理' , value : 'PENDING_PROCESSING' } , { label : '处理中' , value : 'PROCESSING' } , { label : '换标完成' , value : 'COMPLETED' } , { label : '已驳回' , value : 'REJECTED' } ]
: props . service === 'disposal'
: props . service === 'disposal'
? [ { label : '待审核' , value : 'PENDING_REVIEW' } , { label : '待 PDA 废弃' , value : 'APPROVED' } , { label : '待仓库确认完成 ' , value : 'PDA_COMPLETED' } , { label : '已完成' , value : 'COMPLETED' } , { label : '已拒绝' , value : 'REJECTED' } , { label : '已取消' , value : 'CANCELLED' } ]
? [ { label : '待审核' , value : 'PENDING_REVIEW' } , { label : '待 PDA 废弃' , value : 'APPROVED' } , { label : '待客户确认 ' , value : 'PDA_COMPLETED' } , { label : '已完成' , value : 'COMPLETED' } , { label : '已拒绝' , value : 'REJECTED' } , { label : '已取消' , value : 'CANCELLED' } ]
: props . service === 'transfer'
: props . service === 'transfer'
? [ { label : '草稿' , value : 'DRAFT' } , { label : '待审核' , value : 'PENDING_REVIEW' } ,
? [ { label : '草稿' , value : 'DRAFT' } , { label : '待审核' , value : 'PENDING_REVIEW' } ,
{ label : '待PDA收货' , value : 'WAIT_PDA_RECEIVING' } , { label : '入库测量中' , value : 'INBOUND_MEASURING' } ,
{ label : '待PDA收货' , value : 'WAIT_PDA_RECEIVING' } , { label : '入库测量中' , value : 'INBOUND_MEASURING' } ,
@@ -130,11 +144,67 @@ interface ServiceDetail extends Record<string, unknown> {
relabelContactPhone ? : string ; relabelDeliveryMethod ? : string ; relabelNo ? : string
relabelContactPhone ? : string ; relabelDeliveryMethod ? : string ; relabelNo ? : string
relabelStatus ? : string ; relabelFulfillmentStatus ? : string ; createdAt ? : string
relabelStatus ? : string ; relabelFulfillmentStatus ? : string ; createdAt ? : string
inboundCode ? : string ; sourceOrders ? : Array < { lastlegOrderId ? : string | number } >
inboundCode ? : string ; sourceOrders ? : Array < { lastlegOrderId ? : string | number } >
items ? : Array < { skuCode ? : string ; productNameZh ? : string ; packageCount ? : number ; shippingMark ? : string } >
items ? : Array < { id ? : string | number ; cartonMarks ? : string [ ] ; inboundCode ? : string ; productName ? : string ; discardPackages ? : number ; skuCode ? : string ; productNameZh ? : string ; quantity ? : number | null ; packageCount ? : number ; shippingMark ? : string } >
relabelBindings ? : Array < { operationType ? : string ; customerCartonMark ? : string ; originalSku ? : string ; newSku ? : string ; quantity ? : number ; fbaCartonMark ? : string ; destinationWarehouseCode ? : string ; remark ? : string } >
relabelBindings ? : Array < { operationType ? : string ; customerCartonMark ? : string ; originalSku ? : string ; newSku ? : string ; quantity ? : number ; fbaCartonMark ? : string ; destinationWarehouseCode ? : string ; remark ? : string } >
cartons ? : TransferCarton [ ] ; pallets ? : TransferPallet [ ] ; measurementPhotos ? : TransferMeasurementPhoto [ ] ; customerConfirmedAt ? : string
cartons ? : TransferCarton [ ] ; pallets ? : TransferPallet [ ] ; measurementPhotos ? : TransferMeasurementPhoto [ ] ; customerConfirmedAt ? : string
}
}
const selectedDetail = ref < ServiceDetail | null > ( null )
const selectedDetail = ref < ServiceDetail | null > ( null )
/ / 预 报 箱 唛 在 提 交 后 即 可 打 印 , 与 P D A 实 收 清 点 明 细 分 别 展 示 。
const showForecastCartons = ref ( false )
const increaseForecastOpen = ref ( false )
const increasingForecast = ref ( false )
const forecastSnapshot = ref < ServiceDetail | null > ( null )
const increaseForecastForm = reactive ( { itemId : '' , additionalCount : 1 } )
const canIncreaseForecast = computed ( ( ) => canCreate . value && selectedDetail . value ? . transferNo === selectedDetail . value ? . inboundCode && [ 'PENDING_REVIEW' , 'WAIT_PDA_RECEIVING' ] . includes ( selectedDetail . value ? . status || '' ) )
/** 保存原预报快照,防止弹窗打开后其他设备的修改被覆盖。 */
function openIncreaseForecast ( ) {
if ( ! canIncreaseForecast . value || ! selectedDetail . value ) return
forecastSnapshot . value = selectedDetail . value
increaseForecastForm . itemId = String ( selectedDetail . value . items ? . [ 0 ] ? . id || '' )
increaseForecastForm . additionalCount = 1 ; increaseForecastOpen . value = true
}
/** 只补箱唛,SKU预报件数不随包装箱数自动改变。 */
async function saveIncreaseForecast ( ) {
if ( increasingForecast . value || ! forecastSnapshot . value ? . transferNo ) return
const snapshot = forecastSnapshot . value
const item = snapshot . items ? . find ( row => String ( row . id ) === increaseForecastForm . itemId )
if ( ! item || ! Number . isSafeInteger ( increaseForecastForm . additionalCount ) || increaseForecastForm . additionalCount < 1 ) { ElMessage . warning ( '请选择SKU并填写新增箱数' ) ; return }
increasingForecast . value = true
try {
const result = await increaseTransferForecastCartons ( String ( snapshot . transferNo ) , { expectedPackageCount : Number ( snapshot . packageCount ) , transferItemId : String ( item . id ) , expectedItemPackageCount : Number ( item . packageCount ) , packageCount : Number ( item . packageCount ) + increaseForecastForm . additionalCount } )
selectedDetail . value = result as ServiceDetail ; increaseForecastOpen . value = false
ElMessage . success ( '预报箱数已增加,新增箱唛可打印;请通知PDA刷新后继续清点' )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '增加预报箱数失败' ) }
finally { increasingForecast . value = false }
}
const showTransferCartons = ref ( false )
function transferCartonItem ( carton : TransferCarton ) {
return selectedDetail . value ? . items ? . find ( ( item ) => carton . transferItemId != null && String ( item . id ) === String ( carton . transferItemId ) )
}
function transferCartonLines ( carton : TransferCarton ) {
if ( carton . items ? . length ) return carton . items . map ( line => ( { ... line , productNameZh : selectedDetail . value ? . items ? . find ( item => String ( item . id ) === String ( line . transferItemId ) ) ? . productNameZh || '—' } ) )
const item = transferCartonItem ( carton )
return [ { transferItemId : carton . transferItemId , skuCode : item ? . skuCode || '—' , productNameZh : item ? . productNameZh || '—' , quantity : Number ( carton . quantity || 0 ) } ]
}
function palletCartons ( pallet : TransferPallet ) {
return ( selectedDetail . value ? . cartons || [ ] ) . filter ( ( carton ) => carton . status === 'COUNTED' && pallet . id != null && carton . transferPalletId != null && String ( carton . transferPalletId ) === String ( pallet . id ) )
}
function palletSkuRows ( pallet : TransferPallet ) {
const summary = new Map < string , number > ( )
for ( const carton of palletCartons ( pallet ) ) {
for ( const line of transferCartonLines ( carton ) ) summary . set ( line . skuCode , ( summary . get ( line . skuCode ) || 0 ) + Number ( line . quantity ) )
}
return [ ... summary . entries ( ) ] . map ( ( [ sku , quantity ] ) => ( { sku , quantity } ) )
}
function palletQuantity ( pallet : TransferPallet ) { return palletCartons ( pallet ) . reduce ( ( sum , carton ) => sum + Number ( carton . quantity || 0 ) , 0 ) }
function countedTransferCartons ( ) { return ( selectedDetail . value ? . cartons || [ ] ) . filter ( ( carton ) => carton . status === 'COUNTED' ) }
function receivedItemCartons ( itemId : string | number | undefined ) {
return countedTransferCartons ( ) . filter ( carton => itemId != null && transferCartonLines ( carton ) . some ( line => String ( line . transferItemId ) === String ( itemId ) ) )
}
function receivedItemQuantity ( itemId : string | number | undefined ) {
return receivedItemCartons ( itemId ) . reduce ( ( sum , carton ) => sum + transferCartonLines ( carton ) . filter ( line => String ( line . transferItemId ) === String ( itemId ) ) . reduce ( ( total , line ) => total + Number ( line . quantity ) , 0 ) , 0 )
}
const detailError = ref ( '' )
const detailError = ref ( '' )
const selectedRelabelDetail = ref < RelabelDetail | null > ( null )
const selectedRelabelDetail = ref < RelabelDetail | null > ( null )
const completionPhotoUrls = reactive < Record < string , string > > ( { } )
const completionPhotoUrls = reactive < Record < string , string > > ( { } )
@@ -201,7 +271,48 @@ const listColumns = computed<DataTableColumn[]>(() => config.value.columns.map((
: { key : String ( index - 1 ) , prop : String ( index - 1 ) , label , minWidth : label === '关联业务单号' ? 300 : index === 1 || label === '换标单号' ? 190 : 130 ,
: { key : String ( index - 1 ) , prop : String ( index - 1 ) , label , minWidth : label === '关联业务单号' ? 300 : index === 1 || label === '换标单号' ? 190 : 130 ,
singleLine : label !== '关联业务单号' , className : label === '关联业务单号' ? 'related-business-number-cell' : undefined ,
singleLine : label !== '关联业务单号' , className : label === '关联业务单号' ? 'related-business-number-cell' : undefined ,
slotName : props . service === 'labeling' && label === '换标类型' ? 'labelingType' : props . service === 'labeling' && label === '状态' ? 'labelingStatus' : props . service === 'transfer' && label === '换标状态' ? 'transferRelabelStatus' : props . service === 'transfer' && label === '状态' ? 'transferStatus' : undefined } ) )
slotName : props . service === 'labeling' && label === '换标类型' ? 'labelingType' : props . service === 'labeling' && label === '状态' ? 'labelingStatus' : props . service === 'transfer' && label === '换标状态' ? 'transferRelabelStatus' : props . service === 'transfer' && label === '状态' ? 'transferStatus' : undefined } ) )
const discardSelectedBoxKeys = ref < string [ ] > ( [ ] )
const discardBoxSearch = ref ( '' )
const discardCandidates = ref < import ( ' .. / api / warehouseServices ' ) .DiscardCandidate [ ] > ( [ ] )
const discardCandidates = ref < import ( ' .. / api / warehouseServices ' ) .DiscardCandidate [ ] > ( [ ] )
const discardSelectedCandidate = computed ( ( ) => discardCandidates . value . find ( item => item . orderId === selectedDiscardOrderId . value ) )
const discardSelectionSources = computed ( ( ) => {
const current = discardSelectedCandidate . value
if ( ! current ) return [ ]
if ( current . sourceType !== 'RETURN_CARTON' ) return [ current ]
return discardCandidates . value . filter ( item => item . sourceType === current . sourceType && item . inboundCode === current . inboundCode && item . overseasWarehouseId === current . overseasWarehouseId )
} )
const discardSelectableBoxes = computed ( ( ) => discardSelectionSources . value . flatMap ( source => ( source . cartons || [ ] ) . map ( box => ( { ... box , key : ` ${ source . sourceType } : ${ box . cartonId } ` , source } ) ) ) )
const discardVisibleBoxes = computed ( ( ) => discardSelectableBoxes . value . filter ( box => ! discardBoxSearch . value . trim ( ) || box . cartonMark . includes ( discardBoxSearch . value . trim ( ) ) || box . skus . some ( sku => sku . skuCode . includes ( discardBoxSearch . value . trim ( ) ) ) ) )
function toggleDiscardBox ( key : string , selected : boolean ) {
discardSelectedBoxKeys . value = selected ? [ ... new Set ( [ ... discardSelectedBoxKeys . value , key ] ) ] : discardSelectedBoxKeys . value . filter ( value => value !== key )
syncDiscardSelection ( )
}
function selectAllDiscardBoxes ( selected : boolean ) {
discardSelectedBoxKeys . value = selected ? discardSelectableBoxes . value . map ( box => box . key ) : [ ]
syncDiscardSelection ( )
}
function syncDiscardSelection ( ) {
const selected = discardSelectableBoxes . value . filter ( box => discardSelectedBoxKeys . value . includes ( box . key ) )
const groups = new Map < string , CargoRow > ( )
for ( const box of selected ) {
const source = box . source
const skus = source . sourceType === 'RETURN_CARTON' ? source . skus : source . sourceType === 'INVENTORY' ? source . skus . slice ( 0 , 1 ) : box . skus . slice ( 0 , 1 )
for ( const sku of skus ) {
const key = ` ${ source . orderId } : ${ sku . skuCode } `
let row = groups . get ( key )
if ( ! row ) {
row = { ... createCargoRow ( ) , sku : sku . skuCode , name : sku . productName , quantity : 0 , availableQuantity : source . sourceType === 'RETURN_CARTON' ? sku . availablePackages : source . availablePackages ,
sourceOrderId : source . orderId , sourceType : source . sourceType , sourceInventoryPositionId : source . inventoryPositionId , returnCartonId : source . returnCartonId ,
palletNo : source . palletNo , discardCartonIds : source . sourceType === 'RETURN_CARTON' ? undefined : [ ] }
groups . set ( key , row )
}
row . quantity += source . sourceType === 'RETURN_CARTON' ? sku . availablePackages : 1
row . discardCartonIds ? . push ( box . cartonId )
}
}
cargoRows . value = [ ... groups . values ( ) ]
}
const selectedDiscardOrderId = ref ( '' )
const selectedDiscardOrderId = ref ( '' )
const discardCandidatesLoading = ref ( false )
const discardCandidatesLoading = ref ( false )
const relabelCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const relabelCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
@@ -212,6 +323,40 @@ const availableSkusCollapsed = ref(false)
const relabelCandidatesLoading = ref ( false )
const relabelCandidatesLoading = ref ( false )
const transferInventoryCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const transferInventoryCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const selectedTransferInventory = ref ( '' )
const selectedTransferInventory = ref ( '' )
const inventoryTransfer = computed ( ( ) => props . service === 'transfer' && Boolean ( selectedTransferInventory . value ) )
const transferSourceCartons = ref < TransferSourceCarton [ ] > ( [ ] )
const transferSourceLoading = ref ( false )
const transferSourceError = ref ( '' )
let transferSourceRequest = 0
async function loadTransferSourceCartons ( ) {
const request = ++ transferSourceRequest
transferSourceCartons . value = [ ] ; transferSourceError . value = ''
if ( ! inventoryTransfer . value ) return
transferSourceLoading . value = true
try {
/ / 退 货 候 选 本 身 就 是 逐 箱 库 存 , 使 用 与 下 拉 一 致 的 实 物 箱 数 据 , 不 能 按 普 通 入 仓 快 照 回 查 。
if ( selectedTransferInventory . value . startsWith ( 'RETURN_ORDER:' ) ) {
const returnNo = selectedTransferInventory . value . slice ( 'RETURN_ORDER:' . length )
transferSourceCartons . value = transferInventoryCandidates . value
. filter ( item => ( item . sourceType === 'RETURN_CARTON' || item . sourceType === 'RETURN_PARCEL' )
&& String ( item . inboundOrderNo || '' ) . trim ( ) === returnNo && Number ( item . cartonAvailablePackages || 0 ) > 0 )
. filter ( item => Boolean ( item . cartonMark ) )
. map ( item => {
const contents = item . cartonSkuItems ? . length ? item . cartonSkuItems : item . skuItems || [ ]
return { cartonMark : item . cartonMark ! , productNameZh : item . productNameZh ,
quantity : contents . reduce ( ( total , sku ) => total + Number ( sku . quantity || 0 ) , 0 ) ,
destinationWarehouseCode : item . destinationFbaWarehouseCode ,
destinationWarehouseName : item . destinationFbaWarehouseName ,
items : contents . map ( sku => ( { skuCode : sku . skuCode , quantity : Number ( sku . quantity || 0 ) } ) ) }
} )
if ( ! transferSourceCartons . value . length ) throw new Error ( '当前退货单没有可转运箱,请刷新库存后重试' )
return
}
const cartons = await getTransferSourceCartons ( form . warehouse , selectedTransferInventory . value )
if ( request === transferSourceRequest ) transferSourceCartons . value = cartons
} catch ( error ) { if ( request === transferSourceRequest ) transferSourceError . value = error instanceof Error ? error . message : '原单箱明细加载失败' }
finally { if ( request === transferSourceRequest ) transferSourceLoading . value = false }
}
/ / S n o w f l a k e I D s e x c e e d J a v a S c r i p t ' s s a f e i n t e g e r r a n g e . K e e p t h e m a s s t r i n g s
/ / S n o w f l a k e I D s e x c e e d J a v a S c r i p t ' s s a f e i n t e g e r r a n g e . K e e p t h e m a s s t r i n g s
/ / a l l t h e w a y t o J S O N s o t h e b a c k e n d r e c e i v e s t h e e x a c t s o u r c e o r d e r I D .
/ / a l l t h e w a y t o J S O N s o t h e b a c k e n d r e c e i v e s t h e e x a c t s o u r c e o r d e r I D .
const selectedTransferSourceOrderIds = ref < string [ ] > ( [ ] )
const selectedTransferSourceOrderIds = ref < string [ ] > ( [ ] )
@@ -340,6 +485,11 @@ onMounted(() => {
else void loadPage ( )
else void loadPage ( )
void nextTick ( refreshWarehouseMarkLabels )
void nextTick ( refreshWarehouseMarkLabels )
} )
} )
let activatedOnce = false
onActivated ( ( ) => {
if ( activatedOnce && ! props . createPage && ! props . detailPage && mode . value === 'list' ) void loadPage ( )
activatedOnce = true
} )
onBeforeUnmount ( ( ) => { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) ; clearDiscardPhotos ( ) } )
onBeforeUnmount ( ( ) => { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) ; clearDiscardPhotos ( ) } )
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' , reviewStart : '' , reviewEnd : '' , completedStart : '' , completedEnd : '' , measuredStart : '' , measuredEnd : '' , outboundStart : '' , outboundEnd : '' } ) }
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' , reviewStart : '' , reviewEnd : '' , completedStart : '' , completedEnd : '' , measuredStart : '' , measuredEnd : '' , outboundStart : '' , outboundEnd : '' } ) }
function resetForm ( ) {
function resetForm ( ) {
@@ -399,6 +549,14 @@ function returnToList() {
function serviceListRouteName ( service : ServiceType ) {
function serviceListRouteName ( service : ServiceType ) {
return ( { labeling : 'overseas-labeling' , transfer : 'overseas-storage-transfer' , disposal : 'overseas-cargo-disposal' , relabel : 'overseas-return-relabel' } as Record < ServiceType , string > ) [ service ]
return ( { labeling : 'overseas-labeling' , transfer : 'overseas-storage-transfer' , disposal : 'overseas-cargo-disposal' , relabel : 'overseas-return-relabel' } as Record < ServiceType , string > ) [ service ]
}
}
async function goToServiceList ( ) {
if ( props . createPage || props . detailPage ) {
await router . push ( { name : serviceListRouteName ( props . service ) } )
return
}
mode . value = 'list'
await loadPage ( )
}
async function openEdit ( transferNo : string ) {
async function openEdit ( transferNo : string ) {
try {
try {
@@ -422,7 +580,7 @@ async function openEdit(transferNo: string) {
transferDestinationType . value = detail . destinationType || ( detail . fbaWarehouseCode ? 'FBA' : 'NONE' )
transferDestinationType . value = detail . destinationType || ( detail . fbaWarehouseCode ? 'FBA' : 'NONE' )
cargoRows . value = ( detail . items || [ ] ) . map ( ( item ) => ( {
cargoRows . value = ( detail . items || [ ] ) . map ( ( item ) => ( {
... createCargoRow ( ) , sku : item . skuCode || '' , name : item . productNameZh || '' ,
... createCargoRow ( ) , sku : item . skuCode || '' , name : item . productNameZh || '' ,
quantity : Number ( item . packageCount || 0 ) , shippingMark : item . shippingMark || '' ,
quantity : Number ( item . quantity || 0 ) , expectedPackageCount : Number ( item . packageCount || 0 ) , shippingMark : item . shippingMark || '' ,
customerCartonMarks : item . shippingMark || '' ,
customerCartonMarks : item . shippingMark || '' ,
} ) )
} ) )
const bindings = detail . relabelBindings || [ ]
const bindings = detail . relabelBindings || [ ]
@@ -450,6 +608,8 @@ async function openEdit(transferNo: string) {
const sourceInboundCode = String ( detail . inboundCode || '' )
const sourceInboundCode = String ( detail . inboundCode || '' )
if ( transferInventoryOptions . value . some ( ( option ) => option . value === sourceInboundCode ) ) {
if ( transferInventoryOptions . value . some ( ( option ) => option . value === sourceInboundCode ) ) {
selectedTransferInventory . value = sourceInboundCode
selectedTransferInventory . value = sourceInboundCode
await loadTransferSourceCartons ( )
if ( transferDestinationType . value === 'NONE' ) transferDestinationType . value = 'FBA'
form . inboundNo = sourceInboundCode
form . inboundNo = sourceInboundCode
selectedTransferSourceOrderIds . value = ( detail . sourceOrders || [ ] )
selectedTransferSourceOrderIds . value = ( detail . sourceOrders || [ ] )
. map ( ( item ) => String ( item . lastlegOrderId || '' ) ) . filter ( Boolean )
. map ( ( item ) => String ( item . lastlegOrderId || '' ) ) . filter ( Boolean )
@@ -459,6 +619,8 @@ async function openEdit(transferNo: string) {
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '草稿加载失败' ) }
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '草稿加载失败' ) }
}
}
async function selectDiscardWarehouse ( ) {
async function selectDiscardWarehouse ( ) {
discardSelectedBoxKeys . value = [ ]
discardBoxSearch . value = ''
selectedDiscardOrderId . value = ''
selectedDiscardOrderId . value = ''
form . inboundNo = ''
form . inboundNo = ''
discardCandidates . value = [ ]
discardCandidates . value = [ ]
@@ -467,72 +629,18 @@ async function selectDiscardWarehouse() {
if ( ! form . warehouse ) return
if ( ! form . warehouse ) return
discardCandidatesLoading . value = true
discardCandidatesLoading . value = true
try {
try {
const inventory = await listRelabelInventoryCandidates ( form . warehouse )
const warehouse = form . warehouse
const orderByInbound = new Map < string , string > ( )
const candidates = await listDiscardCandidates ( )
const inboundCodes = [ ... new Set ( inventory . map ( ( candidate ) => String ( candidate . inboundCode || '' ) ) . filter ( Boolean ) ) ]
if ( form . warehouse !== warehouse ) return
const historicalGroups = await Promise . all ( inboundCodes . map ( async ( inboundCode ) => ( { inboundCode , orders : await listRelabelHistoricalOrders ( form . warehouse , inboundCode ) } ) ) )
discardCandidates . value = candidates
for ( const { inboundCode , orders } of historicalGroups ) {
. filter ( ( candidate ) => String ( candidate . overseasWarehouseId ) === warehouse )
for ( const order of orders ) {
. filter ( ( candidate ) => candidate . sourceType === 'DROPSHIP' ? Boolean ( candidate . orderId ) : candidate . sourceType === 'RETURN_CARTON' ? Boolean ( candidate . returnCartonId ) : candidate . sourceType === 'INVENTORY' && Boolean ( candidate . inventoryPositionId ) )
if ( order . id ) orderByInbound . set ( inboundCode , String ( order . id ) )
. map ( ( candidate ) => ( { ... candidate , orderId : candidate . sourceType === 'RETURN_CARTON' ? ` return: ${ candidate . returnCartonId } ` : candidate . sourceType === 'INVENTORY' ? ` inventory: ${ candidate . inventoryPositionId } ` : String ( candidate . orderId ) } ) )
}
}
const grouped = new Map < string , import ( ' .. / api / warehouseServices ' ) .DiscardCandidate > ( )
for ( const candidate of inventory ) {
const returnCartonId = String ( candidate . returnCartonId || '' )
const sourceType = candidate . sourceType === 'RETURN_CARTON' ? 'RETURN_CARTON' : 'DROPSHIP'
const inboundCode = String ( candidate . inboundCode || '' )
const orderId = sourceType === 'RETURN_CARTON' ? ` return: ${ returnCartonId } ` : ( orderByInbound . get ( inboundCode ) || '' )
if ( ! orderId || ( sourceType === 'RETURN_CARTON' && ! returnCartonId ) ) continue
const current = grouped . get ( orderId ) || { orderId , inboundCode , inboundOrderNo : candidate . inboundOrderNo || '' , overseasWarehouseId : String ( candidate . overseasWarehouseId || form . warehouse ) , overseasWarehouseName : candidate . overseasWarehouseName || '' , availablePackages : 0 , skus : [ ] }
current . sourceType = sourceType
current . returnCartonId = returnCartonId || undefined
current . cartonMark = candidate . cartonMark || undefined
const packages = inventoryQuantity ( candidate . availablePackages )
current . availablePackages += packages
const skuCode = String ( candidate . skuCode || '' ) . trim ( )
if ( skuCode ) {
const sku = current . skus . find ( ( item ) => item . skuCode === skuCode )
if ( sku ) sku . availablePackages += packages
else current . skus . push ( { skuCode , productName : candidate . productNameZh || '' , availablePackages : packages } )
}
grouped . set ( orderId , current )
}
discardCandidates . value = [ ... grouped . values ( ) ] . filter ( ( candidate ) => String ( candidate . overseasWarehouseId ) === form . warehouse )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '可废弃入库单加载失败' ) }
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '可废弃入库单加载失败' ) }
finally { discardCandidatesLoading . value = false }
finally { discardCandidatesLoading . value = false }
}
}
function createCargoRow ( ) : CargoRow { return { id : ++ cargoSequence , sku : '' , name : '' , quantity : 1 , fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , cartonMark : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } }
function createCargoRow ( ) : CargoRow { return { id : ++ cargoSequence , sku : '' , name : '' , quantity : 1 , expectedPackageCount : 1 , fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , cartonMark : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } }
function addCargoRow ( ) { cargoRows . value . push ( createCargoRow ( ) ) }
function addCargoRow ( ) { cargoRows . value . push ( createCargoRow ( ) ) }
function escapePrintHtml ( value : string ) {
return value . replaceAll ( '&' , '&' ) . replaceAll ( '<' , '<' ) . replaceAll ( '>' , '>' ) . replaceAll ( '"' , '"' ) . replaceAll ( "'" , ''' )
}
function printTransferDraftCartonMarks ( ) {
if ( props . service !== 'transfer' ) return
const validRows = cargoRows . value . filter ( ( row ) => row . sku . trim ( ) && Number ( row . quantity ) > 0 )
if ( ! validRows . length || validRows . some ( ( row ) => ! Number . isInteger ( Number ( row . quantity ) ) ) ) {
ElMessage . warning ( '请先填写商品 SKU 和对应的整数箱数' ) ; return
}
const totalCartons = validRows . reduce ( ( sum , row ) => sum + Number ( row . quantity ) , 0 )
if ( totalCartons > 500 ) { ElMessage . warning ( '单次最多打印 500 张箱唛' ) ; return }
const batch = new Date ( ) . toISOString ( ) . replace ( /\D/g , '' ) . slice ( 2 , 14 )
const labels : Array < { mark : string ; sku : string ; index : number } > = [ ]
let cartonIndex = 0
for ( const [ rowIndex , row ] of validRows . entries ( ) ) {
let marks = transferCustomerMarks ( row )
if ( marks . length !== Number ( row . quantity ) ) {
marks = Array . from ( { length : Number ( row . quantity ) } , ( _ , index ) => ` CT- ${ batch } - ${ String ( rowIndex + 1 ) . padStart ( 2 , '0' ) } - ${ String ( index + 1 ) . padStart ( 3 , '0' ) } ` )
row . customerCartonMarks = marks . join ( '\n' )
}
for ( const mark of marks ) labels . push ( { mark , sku : row . sku . trim ( ) , index : ++ cartonIndex } )
}
const printWindow = window . open ( '' , '_blank' )
if ( ! printWindow ) { ElMessage . warning ( '浏览器阻止了打印窗口,请允许弹窗后重试' ) ; return }
printWindow . opener = null
const sections = labels . map ( ( label ) => ` <section class="label"><div class="title">仓储转运客户箱唛</div><div class="mark"> ${ escapePrintHtml ( label . mark ) } </div><div class="sku">SKU: ${ escapePrintHtml ( label . sku ) } </div><div class="meta"> ${ label . index } / ${ labels . length } </div></section> ` ) . join ( '' )
printWindow . document . open ( )
printWindow . document . write ( ` <!doctype html><html><head><meta charset="utf-8"><title>仓储转运客户箱唛</title><style>@page{size:100mm 100mm;margin:8mm}body{margin:0;font-family:Arial,'Microsoft YaHei',sans-serif;color:#111}.label{height:84mm;display:flex;flex-direction:column;align-items:center;justify-content:center;page-break-after:always;border:1px solid #222;box-sizing:border-box;padding:8mm}.title{font-size:18px;font-weight:700}.mark{margin:11mm 0 6mm;font-size:26px;font-weight:700;letter-spacing:1px;word-break:break-all;text-align:center}.sku{font-size:17px;font-weight:650}.meta{font-size:12px;margin-top:4mm}</style></head><body> ${ sections } </body></html> ` )
printWindow . document . close ( ) ; printWindow . focus ( ) ; printWindow . print ( )
}
function addTransferRelabelRow ( ) {
function addTransferRelabelRow ( ) {
const source = cargoRows . value [ Math . min ( transferRelabelRows . value . length , Math . max ( 0 , cargoRows . value . length - 1 ) ) ]
const source = cargoRows . value [ Math . min ( transferRelabelRows . value . length , Math . max ( 0 , cargoRows . value . length - 1 ) ) ]
transferRelabelRows . value . push ( { ... createCargoRow ( ) , sku : source ? . sku || '' } )
transferRelabelRows . value . push ( { ... createCargoRow ( ) , sku : source ? . sku || '' } )
@@ -677,7 +785,7 @@ function downloadTransferCartonMarkTemplate() {
ElMessage . warning ( '请先填写完整的 SKU 明细' ) ; return
ElMessage . warning ( '请先填写完整的 SKU 明细' ) ; return
}
}
const header = [ 'SKU' , '客户箱唛号' ]
const header = [ 'SKU' , '客户箱唛号' ]
const rows = cargoRows . value . flatMap ( ( row ) => Array . from ( { length : row . quantity } , ( ) => [ row . sku , '' ] ) )
const rows = cargoRows . value . flatMap ( ( row ) => Array . from ( { length : Number ( row . expectedPackageCount || 0 ) } , ( ) => [ row . sku , '' ] ) )
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 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' )
const url = URL . createObjectURL ( blob ) ; const anchor = document . createElement ( 'a' )
anchor . href = url ; anchor . download = '仓储转运-客户箱唛模板.csv' ; anchor . click ( ) ; URL . revokeObjectURL ( url )
anchor . href = url ; anchor . download = '仓储转运-客户箱唛模板.csv' ; anchor . click ( ) ; URL . revokeObjectURL ( url )
@@ -865,14 +973,17 @@ function importSelectedBindingsToDetails() {
ElMessage . success ( ` 已将 ${ selected . length } 条绑定关系导入本次换标明细 ` )
ElMessage . success ( ` 已将 ${ selected . length } 条绑定关系导入本次换标明细 ` )
}
}
function selectDiscardInbound ( orderId : string ) {
function selectDiscardInbound ( orderId : string ) {
discardSelectedBoxKeys . value = [ ]
discardBoxSearch . value = ''
const candidate = discardCandidates . value . find ( ( item ) => item . orderId === orderId )
const candidate = discardCandidates . value . find ( ( item ) => item . orderId === orderId )
if ( ! candidate ) { cargoRows . value = [ ] ; return }
if ( ! candidate ) { cargoRows . value = [ ] ; return }
form . inboundNo = candidate . inboundCode || candidate . inboundOrderNo
form . inboundNo = candidate . inboundCode || candidate . inboundOrderNo
form . warehouse = candidate . overseasWarehouseId
form . warehouse = candidate . overseasWarehouseId
if ( candidate . cartonSelectionSupported ) { selectAllDiscardBoxes ( true ) ; return }
cargoRows . value = candidate . skus . map ( ( sku ) => ( {
cargoRows . value = candidate . skus . map ( ( sku ) => ( {
id : ++ cargoSequence , sku : sku . skuCode , name : sku . productName , quantity : 0 ,
id : ++ cargoSequence , sku : sku . skuCode , name : sku . productName , quantity : sku . availablePackages ,
availableQuantity : sku . availablePackages , sourceOrderId : candidate . orderId , sourceType : candidate . sourceType ,
availableQuantity : sku . availablePackages , sourceOrderId : candidate . orderId , sourceType : candidate . sourceType ,
returnCartonId : candidate . returnCartonId , cartonMark : candidate . cartonMark || '' ,
returnCartonId : candidate . returnCartonId , sourceInventoryPositionId : candidate . inventoryPositionId , palletNo : candidate . palletNo , cartonMark : candidate . cartonMark || '' ,
fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' ,
fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' ,
} ) )
} ) )
}
}
@@ -910,7 +1021,7 @@ function statusLabel(value: string) {
PENDING _PROCESSING : '待处理' , PROCESSING : '处理中' , CUSTOMER _CONFIRM _PENDING : '待客户确认' ,
PENDING _PROCESSING : '待处理' , PROCESSING : '处理中' , CUSTOMER _CONFIRM _PENDING : '待客户确认' ,
READY _FOR _OUTBOUND : '换标完成' , COMPLETED : '已完成' , CANCELLED : '已取消' ,
READY _FOR _OUTBOUND : '换标完成' , COMPLETED : '已完成' , CANCELLED : '已取消' ,
DRAFT : '草稿' , PENDING _REVIEW : '待审核' , APPROVED : '待 PDA 废弃' , PDA _COMPLETED : '待仓库确认完成 ' , REJECTED : '已拒绝' , PENDING _ASSIGNMENT : '待分配' ,
DRAFT : '草稿' , PENDING _REVIEW : '待审核' , APPROVED : '待 PDA 废弃' , PDA _COMPLETED : '待客户确认 ' , REJECTED : '已拒绝' , PENDING _ASSIGNMENT : '待分配' ,
WAIT _PDA _RECEIVING : '待PDA收货' , WAIT _CUSTOMER _CONFIRMATION : '待客户确认实测' , WAIT _FBA _DESTINATION : '待确认目的地' , WAIT _RELABEL : '待换标' , READY _FOR _DISPATCH : '待排车' , WAIT _OUTBOUND : '待出库' ,
WAIT _PDA _RECEIVING : '待PDA收货' , WAIT _CUSTOMER _CONFIRMATION : '待客户确认实测' , WAIT _FBA _DESTINATION : '待确认目的地' , WAIT _RELABEL : '待换标' , READY _FOR _DISPATCH : '待排车' , WAIT _OUTBOUND : '待出库' ,
ALLOCATED : '已分配' , OUTBOUNDED : '已出库' , PARTIALLY _RECEIVED : '部分收货' , RECEIVED : '已收货' , INBOUND _COMPLETED : '入库完成' , PENDING _DELIVERY : '待派送' , DELIVERING : '派送中' , DELIVERED : '派送完成' ,
ALLOCATED : '已分配' , OUTBOUNDED : '已出库' , PARTIALLY _RECEIVED : '部分收货' , RECEIVED : '已收货' , INBOUND _COMPLETED : '入库完成' , PENDING _DELIVERY : '待派送' , DELIVERING : '派送中' , DELIVERED : '派送完成' ,
@@ -981,8 +1092,8 @@ function transferRelabelStatusTagType(status: string): StatusTagType {
}
}
function deliveryMethodLabel ( value : string ) {
function deliveryMethodLabel ( value : string ) {
const labels : Record < string , string > = {
const labels : Record < string , string > = {
TRUCK : '卡车派送' , EXPRESS : '快递派送' , TRUCK _PICKUP : '卡车 自提' , EXPRESS _PICKUP : '快递 自提' ,
TRUCK : '卡车派送' , EXPRESS : '快递派送' , AMAZON _PICKUP : '亚马逊 自提' , CUSTOMER _PICKUP : '客户 自提' ,
WAREHOUSE _PICKUP : '仓库上门提货' , CUSTOMER _DELIVERY : '客户送货到仓' ,
INVENTORY _TRANSFER : '库存转运' , WAREHOUSE _PICKUP : '仓库上门提货' , CUSTOMER _DELIVERY : '客户送货到仓' ,
/ / K e e p t h e l e g a c y v a l u e r e a d a b l e f o r h i s t o r i c a l a p p l i c a t i o n s c r e a t e d b e f o r e t h e f o u r - w a y s p l i t .
/ / K e e p t h e l e g a c y v a l u e r e a d a b l e f o r h i s t o r i c a l a p p l i c a t i o n s c r e a t e d b e f o r e t h e f o u r - w a y s p l i t .
PICKUP : '仓库自提' ,
PICKUP : '仓库自提' ,
}
}
@@ -1009,7 +1120,7 @@ async function load() {
} else if ( props . service === 'disposal' ) {
} else if ( props . service === 'disposal' ) {
const result = await listDiscards ( { page : page . value , size : pageSize . value , discardNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , overseasWarehouseId : filters . warehouse || undefined , status : filters . status || undefined , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , completedFrom : dateTime ( filters . completedStart ) , completedTo : dateTime ( filters . completedEnd , true ) } )
const result = await listDiscards ( { page : page . value , size : pageSize . value , discardNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , overseasWarehouseId : filters . warehouse || undefined , status : filters . status || undefined , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , completedFrom : dateTime ( filters . completedStart ) , completedTo : dateTime ( filters . completedEnd , true ) } )
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
submittedRows . value = result . records . map ( ( item ) => row ( [ item . discardNo , item . outboundNo || '—' , item . inboundCodes , item . overseasWarehouseName || warehouseDisplay ( item . overseasWarehouseId ) || '—' , '—' , item . totalPackages , ` ${ item . currency } ${ item . totalFee } ` , statusLabel ( item . status ) , formatDateTime ( item . completedAt ) , formatDateTime ( item . createdAt ) ] ) )
submittedRows . value = result . records . map ( ( item ) => ( { ... row ( [ item . discardNo , item . outboundNo || '—' , item . inboundCodes , item . overseasWarehouseName || warehouseDisplay ( item . overseasWarehouseId ) || '—' , '—' , item . totalPackages , ` ${ item . currency } ${ item . totalFee } ` , statusLabel ( item . status ) , formatDateTime ( item . customerConfirmedAt ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status } ) )
} else {
} else {
const result = await listTransfers ( { page : page . value , size : pageSize . value , transferNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , operatingWarehouseId : filters . warehouse || undefined , fbaWarehouseCode : filters . destination || undefined , status : filters . status || undefined , ... ( props . service === 'relabel' ? { needRelabel : true } : { } ) , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , reviewedFrom : dateTime ( filters . reviewStart ) , reviewedTo : dateTime ( filters . reviewEnd , true ) , measuredFrom : props . service === 'relabel' ? dateTime ( filters . measuredStart ) : dateTime ( filters . completedStart ) , measuredTo : props . service === 'relabel' ? dateTime ( filters . measuredEnd , true ) : dateTime ( filters . completedEnd , true ) , outboundFrom : dateTime ( filters . outboundStart ) , outboundTo : dateTime ( filters . outboundEnd , true ) } )
const result = await listTransfers ( { page : page . value , size : pageSize . value , transferNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , operatingWarehouseId : filters . warehouse || undefined , fbaWarehouseCode : filters . destination || undefined , status : filters . status || undefined , ... ( props . service === 'relabel' ? { needRelabel : true } : { } ) , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , reviewedFrom : dateTime ( filters . reviewStart ) , reviewedTo : dateTime ( filters . reviewEnd , true ) , measuredFrom : props . service === 'relabel' ? dateTime ( filters . measuredStart ) : dateTime ( filters . completedStart ) , measuredTo : props . service === 'relabel' ? dateTime ( filters . measuredEnd , true ) : dateTime ( filters . completedEnd , true ) , outboundFrom : dateTime ( filters . outboundStart ) , outboundTo : dateTime ( filters . outboundEnd , true ) } )
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
@@ -1032,6 +1143,7 @@ function listRowActions(candidate: object): TableAction[] {
if ( canCreate . value && props . service === 'transfer' && [ 'DRAFT' , 'REJECTED' ] . includes ( item . _ _status ) ) actions . push ( { key : 'edit' , label : '编辑' , run : ( ) => openEdit ( item [ '0' ] ) } )
if ( canCreate . value && props . service === 'transfer' && [ 'DRAFT' , 'REJECTED' ] . includes ( item . _ _status ) ) actions . push ( { key : 'edit' , label : '编辑' , run : ( ) => openEdit ( item [ '0' ] ) } )
if ( canConfirmTransfer . value && props . service === 'transfer' && item . _ _status === 'WAIT_CUSTOMER_CONFIRMATION' ) actions . push ( { key : 'confirm-measurement' , label : '确认测量' , run : ( ) => confirmTransferFromList ( item [ '0' ] ) } )
if ( canConfirmTransfer . value && props . service === 'transfer' && item . _ _status === 'WAIT_CUSTOMER_CONFIRMATION' ) actions . push ( { key : 'confirm-measurement' , label : '确认测量' , run : ( ) => confirmTransferFromList ( item [ '0' ] ) } )
if ( canConfirmRelabel . value && props . service === 'labeling' && item . _ _status === 'CUSTOMER_CONFIRM_PENDING' && ! item . _ _confirmedAt ) actions . push ( { key : 'confirm-relabel' , label : '确认换标' , run : ( ) => confirmRelabelFromList ( item [ '0' ] ) } )
if ( canConfirmRelabel . value && props . service === 'labeling' && item . _ _status === 'CUSTOMER_CONFIRM_PENDING' && ! item . _ _confirmedAt ) actions . push ( { key : 'confirm-relabel' , label : '确认换标' , run : ( ) => confirmRelabelFromList ( item [ '0' ] ) } )
if ( canConfirmDiscard . value && props . service === 'disposal' && item . _ _status === 'PDA_COMPLETED' ) actions . push ( { key : 'confirm-discard' , label : '废弃确认' , run : ( ) => openCompletionConfirmation ( item [ '0' ] , 'disposal' ) } )
return actions
return actions
}
}
watch ( [ page , pageSize ] , ( [ nextPage , nextSize ] , [ previousPage , previousSize ] ) => {
watch ( [ page , pageSize ] , ( [ nextPage , nextSize ] , [ previousPage , previousSize ] ) => {
@@ -1047,7 +1159,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
: ( props . service === 'labeling' || props . service === 'relabel' ) && ( relabelRows . length === 0 || ( props . service === 'labeling' && labelingMode . value !== 'carton'
: ( 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
? skuRelabelRows . value . some ( ( item ) => ! item . cartonMark || ! item . originalSku || ! item . newSku || item . quantity < 1
|| ( labelingMode . value === 'carton_and_sku' && ( ! item . fbaCartonMark || ! item . shipmentNo . trim ( ) ) ) )
|| ( labelingMode . value === 'carton_and_sku' && ( ! item . fbaCartonMark || ! item . shipmentNo . trim ( ) ) ) )
: cargoRows . value . some ( ( item ) => ( props . service !== 'labeling' && ! item . sku ) || ! item . name
: ( props . service === 'labeling' ? cartonDetailRows . value : cargoRows . value ) . some ( ( item ) => ( props . service !== 'labeling' && ! item . sku ) || ! item . name
|| ( props . service !== 'labeling' && item . quantity < 1 ) ) ) )
|| ( props . service !== 'labeling' && item . quantity < 1 ) ) ) )
if ( ( props . service === 'relabel' && ! form . inboundNo ) || ! primaryWarehouse || invalidCargo ) {
if ( ( props . service === 'relabel' && ! form . inboundNo ) || ! primaryWarehouse || invalidCargo ) {
ElMessage . warning ( '请填写必填信息和完整的货物明细' ) ; return
ElMessage . warning ( '请填写必填信息和完整的货物明细' ) ; return
@@ -1067,7 +1179,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ! form . destination ) {
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ! form . destination ) {
ElMessage . warning ( '请选择本张贴标申请统一使用的 FBA 目的仓' ) ; return
ElMessage . warning ( '请选择本张贴标申请统一使用的 FBA 目的仓' ) ; return
}
}
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ( form . deliveryMethod === 'EXPRESS' || form . deliveryMethod === 'EXPRESS_PICKUP' )
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && form . deliveryMethod === 'EXPRESS'
&& form . relabel && ! shippingLabelFile . value ) {
&& form . relabel && ! shippingLabelFile . value ) {
ElMessage . warning ( '选择打印面单时必须上传面单文件' ) ; return
ElMessage . warning ( '选择打印面单时必须上传面单文件' ) ; return
}
}
@@ -1083,10 +1195,16 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'relabel' && ( ! form . destination || ! form . address || ! form . deliveryMethod || ! form . estimatedArrivalAt || ! form . fbaShipmentNo || form . volumeCbm <= 0 ) ) {
if ( props . service === 'relabel' && ( ! form . destination || ! form . address || ! form . deliveryMethod || ! form . estimatedArrivalAt || ! form . fbaShipmentNo || form . volumeCbm <= 0 ) ) {
ElMessage . warning ( '请填写完整的派送信息、体积、预计到达时间和 FBA 货件号' ) ; return
ElMessage . warning ( '请填写完整的派送信息、体积、预计到达时间和 FBA 货件号' ) ; return
}
}
if ( props . service === 'transfer' && ( ! form . warehouse || ! form . deliveryMethod || ! form . address || ! form . estimatedArrivalAt || form . volumeCbm < 0 || ( form . deliveryMethod === 'WAREHOUSE_PICKUP' && ( ! form . pickupContact || ! form . pickupPhone ) ) ) ) {
if ( props . service === 'transfer' && ! inventoryTransfer . value && ( ! form . warehouse || ! form . deliveryMethod || ! form . address || ! form . estimatedArrivalAt || ( form . deliveryMethod === 'WAREHOUSE_PICKUP' && ( ! form . pickupContact || ! form . pickupPhone ) ) ) ) {
const missing = [ ! form . warehouse && '海外仓' , ! form . deliveryMethod && '送货方式' , ! form . address && '地址' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . pickupContact && '提货联系人' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . pickupPhone && '联系人电话' , ! form . estimatedArrivalAt && '预计入仓时间' , form . volumeCbm < 0 && '预估总体积' ] . filter ( Boolean ) . join ( '、' )
const missing = [ ! form . warehouse && '海外仓' , ! form . deliveryMethod && '送货方式' , ! form . address && '地址' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . pickupContact && '提货联系人' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . pickupPhone && '联系人电话' , ! form . estimatedArrivalAt && '预计入仓时间' ] . filter ( Boolean ) . join ( '、' )
ElMessage . warning ( ` 请填写: ${ missing || '必填信息' } ` ) ; return
ElMessage . warning ( ` 请填写: ${ missing || '必填信息' } ` ) ; return
}
}
if ( props . service === 'transfer' && ( ! cargoRows . value . length || cargoRows . value . some ( ( row ) =>
! Number . isSafeInteger ( row . quantity ) || row . quantity < 1 || ! Number . isSafeInteger ( row . expectedPackageCount ) || Number ( row . expectedPackageCount ) < 1 ) ) ) {
ElMessage . warning ( '请填写每个 SKU 的数量(件)和预计箱数,均须为正整数' ) ; return
}
if ( inventoryTransfer . value && ( transferSourceLoading . value || transferSourceError . value ) ) { ElMessage . warning ( '请等待原单箱明细加载成功后再保存' ) ; return }
if ( inventoryTransfer . value && transferDestinationType . value === 'NONE' ) { ElMessage . warning ( '库存转运请选择 FBA 仓或私人仓' ) ; return }
if ( props . service === 'transfer' && transferDestinationType . value === 'FBA' && ! form . destination ) {
if ( props . service === 'transfer' && transferDestinationType . value === 'FBA' && ! form . destination ) {
ElMessage . warning ( '请选择 FBA 目的仓' ) ; return
ElMessage . warning ( '请选择 FBA 目的仓' ) ; return
}
}
@@ -1100,6 +1218,9 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( action === 'submit' && ! requiresBothTotalFiles . value && ! cartonTotalFile . value && ! skuTotalFile . value ) { ElMessage . warning ( '请上传箱唛总文件或 SKU 总文件(二选一)' ) ; return }
if ( action === 'submit' && ! requiresBothTotalFiles . value && ! cartonTotalFile . value && ! skuTotalFile . value ) { ElMessage . warning ( '请上传箱唛总文件或 SKU 总文件(二选一)' ) ; return }
const relabelRows = transferRelabelRows . value
const relabelRows = transferRelabelRows . value
const allMarks = relabelRows . flatMap ( transferCustomerMarks )
const allMarks = relabelRows . flatMap ( transferCustomerMarks )
if ( inventoryTransfer . value && allMarks . some ( mark => ! transferSourceCartons . value . some ( box => box . cartonMark === mark ) ) ) {
ElMessage . error ( '当前箱子不存在或不属于当前订单' ) ; return
}
if ( ! relabelRows . length || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . length !== row . quantity ) ) { ElMessage . warning ( '每个换标明细的客户箱唛数量必须与换标数量一致' ) ; return }
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 ( 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 ( allMarks . join ( ',' ) . length > 2000 || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . join ( ',' ) . length > 255 ) ) { ElMessage . warning ( '客户箱唛内容过长,请减少单次提交箱数或缩短箱唛号' ) ; return }
@@ -1119,39 +1240,39 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
const selectedOperationType = labelingMode . value === 'carton' ? 'CARTON_LABEL' : labelingMode . value === 'sku' ? 'SKU_LABEL' : 'CARTON_AND_SKU'
const selectedOperationType = labelingMode . value === 'carton' ? 'CARTON_LABEL' : labelingMode . value === 'sku' ? 'SKU_LABEL' : 'CARTON_AND_SKU'
if ( items . some ( item => item . operationType !== selectedOperationType ) ) throw new Error ( '一张申请只能选择一种换标类型,请核对绑定明细' )
if ( items . some ( item => item . operationType !== selectedOperationType ) ) throw new Error ( '一张申请只能选择一种换标类型,请核对绑定明细' )
const created = await createRelabel ( { customerOrderNo : sourceInboundOrders || '贴标申请' , operationWarehouseId : form . warehouse , labelDeadline : form . labelDeadline , contactName : form . contact , contactPhone : form . phone , remark : form . remark || undefined , idempotencyKey , items ,
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' ,
deliveryMethod : labelingMode . value === 'sku' ? undefined : form . deliveryMethod as 'TRUCK' | 'EXPRESS' | 'AMAZON _PICKUP' | 'CUSTOMER _PICKUP' ,
printShippingLabel : labelingMode . value !== 'sku' && ( form . deliveryMethod === 'EXPRESS' || form . deliveryMethod === 'EXPRESS_PICKUP' ) && form . relabel } )
printShippingLabel : labelingMode . value !== 'sku' && form . deliveryMethod === 'EXPRESS' && form . relabel } )
try {
try {
if ( cartonTotalFile . value ) await uploadRelabelFile ( created . application . relabelNo , 'CARTON_TOTAL' , cartonTotalFile . value )
if ( cartonTotalFile . value ) await uploadRelabelFile ( created . application . relabelNo , 'CARTON_TOTAL' , cartonTotalFile . value )
if ( skuTotalFile . value ) await uploadRelabelFile ( created . application . relabelNo , 'SKU_TOTAL' , skuTotalFile . value )
if ( skuTotalFile . value ) await uploadRelabelFile ( created . application . relabelNo , 'SKU_TOTAL' , skuTotalFile . value )
if ( shippingLabelFile . value ) await uploadRelabelShippingLabel ( created . application . relabelNo , shippingLabelFile . value )
if ( shippingLabelFile . value ) await uploadRelabelShippingLabel ( created . application . relabelNo , shippingLabelFile . value )
} catch ( error ) {
} catch ( error ) {
/ / C r e a t i o n a l r e a d y c o m m i t t e d : r e t u r n t o t h e l i s t e v e n w h e n a n a t t a c h m e n t s t i l l n e e d s r e t r y i n g .
/ / C r e a t i o n a l r e a d y c o m m i t t e d : r e t u r n t o t h e l i s t e v e n w h e n a n a t t a c h m e n t s t i l l n e e d s r e t r y i n g .
if ( props . createPage ) await router . push ( { name : serviceListRouteName ( props . service ) } )
await goToServiceList ( )
else { mode . value = 'list' ; await load ( ) ; await openDetail ( { '0' : created . application . relabelNo } ) }
await ElMessageBox . alert ( ` 申请 ${ created . application . relabelNo } 已创建,部分附件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请在原单详情补传失败附件,不要重复下单。 ` , '申请已创建,附件待补传' , { confirmButtonText : '知道了' } )
await ElMessageBox . alert ( ` 申请 ${ created . application . relabelNo } 已创建,部分附件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请在原单详情补传失败附件,不要重复下单。 ` , '申请已创建,附件待补传' , { confirmButtonText : '知道了' } )
return
return
}
}
ElMessage . success ( ` ${ config . value . title } 提交成功 ` )
ElMessage . success ( ` ${ config . value . title } 提交成功 ` )
await router . push ( { name : serviceListRouteName ( props . service ) } )
await goToServiceList ( )
return
return
} else if ( props . service === 'disposal' ) {
} else if ( props . service === 'disposal' ) {
const items = cargoRows . value . filter ( ( item ) => item . quantity > 0 ) . map ( ( item ) => {
const items = cargoRows . value . filter ( ( item ) => item . quantity > 0 ) . map ( ( item ) => {
if ( ! item . sourceOrderId || item . sourceOrderId ! == selectedDiscardOrderId . value || item . quantity > Number ( item . availableQuantity || 0 ) ) {
if ( ! item . sourceOrderId || ! discardSelectionSources . value . some ( source => source . orderId = == item . sourceOrderId ) || item . quantity > Number ( item . availableQuantity || 0 ) ) {
throw new Error ( ` SKU ${ item . sku } 的可废弃库存不足 ` )
throw new Error ( ` SKU ${ item . sku } 的可废弃库存不足 ` )
}
}
return { orderId : item . sourceType === 'RETURN_CARTON ' ? undefined : item . sourceOrderId ,
return { orderId : item . sourceType === 'DROPSHIP ' ? item . sourceOrderId : undefined ,
inventoryPositionId : item . sourceType === 'INVENTORY' ? item . sourceInventoryPositionId : undefined ,
returnCartonId : item . sourceType === 'RETURN_CARTON' ? item . returnCartonId : undefined ,
returnCartonId : item . sourceType === 'RETURN_CARTON' ? item . returnCartonId : undefined ,
skuCode : item . sku , discardPackages : item . quantity }
skuCode : item . sku , discardPackages : item . quantity , cartonIds : item . discardCartonIds }
} )
} )
await createDiscard ( { items , idempotencyKey , remark : [ form . reason , form . remark ] . filter ( Boolean ) . join ( '; ' ) || undefined } )
await createDiscard ( { items , idempotencyKey , remark : [ form . reason , form . remark ] . filter ( Boolean ) . join ( '; ' ) || undefined } )
} else {
} else {
const packageCount = cargoRows . value . reduce ( ( sum , item ) => sum + item . quantity , 0 )
const packageCount = cargoRows . value . reduce ( ( sum , item ) => sum + Number ( item . expectedPackageCount || 0 ) , 0 )
const customerCartonMarks = ( form . relabel ? transferRelabelRows . value : cargoRows . value ) . flatMap ( transferCustomerMarks )
const customerCartonMarks = ( form . relabel ? transferRelabelRows . value : cargoRows . value ) . flatMap ( transferCustomerMarks )
const transferRemark = [ form . remark , form . relabel && form . requirement ? ` 换标要求: ${ form . requirement } ` : '' ] . filter ( Boolean ) . join ( '; ' )
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 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 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 : transferRelabelDefaultDestination . value || 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 : inventoryTransfer . value ? undefined : form . address , packageCount , volumeCbm : 0 , deliveryMethod : inventoryTransfer . value ? undefined : form . deliveryMethod , estimatedArrivalAt : inventoryTransfer . value ? undefined : form . estimatedArrivalAt , estimatedShipAt : form . estimatedShipAt || undefined , fbaShipmentNo : transferDestinationType . value === 'FBA' ? form . fbaShipmentNo || undefined : undefined , trackingNo : ! inventoryTransfer . value && 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 : ! inventoryTransfer . value && form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupContact : undefined , pickupContactPhone : ! inventoryTransfer . value && 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' | 'AMAZON _PICKUP' | 'CUSTOMER _PICKUP' : undefined , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , quantity : item . quantity , packageCount : Number ( item . expectedPackageCount ) , 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 }
if ( props . service === 'transfer' && selectedTransferInventory . value ) {
if ( props . service === 'transfer' && selectedTransferInventory . value ) {
; ( payload as TransferCreate ) . inboundCode = selectedTransferInventory . value
; ( payload as TransferCreate ) . inboundCode = selectedTransferInventory . value
; ( payload as TransferCreate ) . sourceLastlegOrderIds = selectedTransferSourceOrderIds . value
; ( payload as TransferCreate ) . sourceLastlegOrderIds = selectedTransferSourceOrderIds . value
@@ -1168,7 +1289,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( ! relabelNo ) {
if ( ! relabelNo ) {
ElMessage . success ( '转运单已提交审核;PDA 入库确认后将自动生成关联换标单' )
ElMessage . success ( '转运单已提交审核;PDA 入库确认后将自动生成关联换标单' )
await ElMessageBox . alert ( ` 转运单 ${ transferNo } 已提交。关联换标单会在 PDA 完成入库、客户确认实测库存后自动生成;届时请进入关联换标单详情上传贴标总文件。 ` , '转运单已提交' , { confirmButtonText : '知道了' } )
await ElMessageBox . alert ( ` 转运单 ${ transferNo } 已提交。关联换标单会在 PDA 完成入库、客户确认实测库存后自动生成;届时请进入关联换标单详情上传贴标总文件。 ` , '转运单已提交' , { confirmButtonText : '知道了' } )
await router . push ( { name : serviceListRouteName ( 'transfer' ) } )
await goToServiceList ( )
return
return
}
}
try {
try {
@@ -1176,17 +1297,17 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( skuTotalFile . value ) await uploadRelabelFile ( relabelNo , 'SKU_TOTAL' , skuTotalFile . value )
if ( skuTotalFile . value ) await uploadRelabelFile ( relabelNo , 'SKU_TOTAL' , skuTotalFile . value )
} catch ( error ) {
} catch ( error ) {
await ElMessageBox . alert ( ` 转运单 ${ transferNo } 已提交,但换标总文件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请进入关联换标单详情补传,不要重复提交转运单。 ` , '转运单已提交,附件待补传' , { confirmButtonText : '知道了' } )
await ElMessageBox . alert ( ` 转运单 ${ transferNo } 已提交,但换标总文件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请进入关联换标单详情补传,不要重复提交转运单。 ` , '转运单已提交,附件待补传' , { confirmButtonText : '知道了' } )
await openDetail ( { '0' : transferNo } )
await goToServiceList ( )
return
return
}
}
}
}
ElMessage . success ( '转运单已提交审核' )
ElMessage . success ( '转运单已提交审核' )
await router . push ( { name : serviceListRouteName ( 'transfer' ) } )
await goToServiceList ( )
return
return
}
}
}
}
ElMessage . success ( props . service === 'transfer' ? '转运单草稿保存成功' : ` ${ config . value . title } 提交成功 ` )
ElMessage . success ( props . service === 'transfer' ? '转运单草稿保存成功' : ` ${ config . value . title } 提交成功 ` )
await router . push ( { name : serviceListRouteName ( props . service ) } )
await goToServiceList ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '提交失败' ) }
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '提交失败' ) }
finally { saving . value = false }
finally { saving . value = false }
}
}
@@ -1200,6 +1321,7 @@ async function openDetail(value: Record<string, string>) {
try {
try {
const detail = props . service === 'labeling' ? await getRelabel ( value [ '0' ] ) : props . service === 'disposal' ? await getDiscard ( value [ '0' ] ) : await getTransfer ( value [ '0' ] )
const detail = props . service === 'labeling' ? await getRelabel ( value [ '0' ] ) : props . service === 'disposal' ? await getDiscard ( value [ '0' ] ) : await getTransfer ( value [ '0' ] )
const discardSummary = props . service === 'disposal' ? ( detail as { summary ? : ServiceDetail } ) . summary : undefined
const discardSummary = props . service === 'disposal' ? ( detail as { summary ? : ServiceDetail } ) . summary : undefined
showTransferCartons . value = false
selectedDetail . value = { ... value , ... ( detail as ServiceDetail ) , ... ( discardSummary || { } ) }
selectedDetail . value = { ... value , ... ( detail as ServiceDetail ) , ... ( discardSummary || { } ) }
selectedRelabelDetail . value = props . service === 'labeling' ? detail as RelabelDetail : null
selectedRelabelDetail . value = props . service === 'labeling' ? detail as RelabelDetail : null
mode . value = 'detail'
mode . value = 'detail'
@@ -1215,9 +1337,7 @@ async function loadDetailPage() {
async function confirmTransferMeasurements ( ) {
async function confirmTransferMeasurements ( ) {
const transferNo = String ( selectedDetail . value ? . transferNo || selectedDetail . value ? . [ '0' ] || '' )
const transferNo = String ( selectedDetail . value ? . transferNo || selectedDetail . value ? . [ '0' ] || '' )
if ( ! transferNo ) return
if ( ! transferNo ) return
try { await ElMessageBox . confirm ( '请核对 PDA 回传的箱数、尺寸、重量和打托数据。' , '确认测量数据' , { confirmButtonText : '确认数据' , cancelButtonText : '取消' } ) } catch { return }
openCompletionConfirmation ( transferNo , 'transfer' )
try { selectedDetail . value = await confirmTransferMeasurement ( transferNo ) as ServiceDetail ; ElMessage . success ( '仓库实测数据已确认' ) }
catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
}
async function printTransferCartons ( transferNo : string ) {
async function printTransferCartons ( transferNo : string ) {
if ( props . service !== 'transfer' ) return
if ( props . service !== 'transfer' ) return
@@ -1238,9 +1358,7 @@ function clearCompletionPhotos() {
for ( const key of Object . keys ( completionPhotoErrors ) ) delete completionPhotoErrors [ key ]
for ( const key of Object . keys ( completionPhotoErrors ) ) delete completionPhotoErrors [ key ]
}
}
async function confirmTransferFromList ( transferNo : string ) {
async function confirmTransferFromList ( transferNo : string ) {
try { await ElMessageBox . confirm ( '请核对 PDA 回传的箱数、尺寸、重量和打托数据。' , '确认测量数据' , { confirmButtonText : '确认数据' , cancelButtonText : '取消' } ) } catch { return }
openCompletionConfirmation ( transferNo , 'transfer' )
try { await confirmTransferMeasurement ( transferNo ) ; ElMessage . success ( '仓库实测数据已确认' ) ; await load ( ) }
catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
}
async function loadCompletionPhoto ( fileId : string ) {
async function loadCompletionPhoto ( fileId : string ) {
const relabelNo = selectedRelabelDetail . value ? . application . relabelNo
const relabelNo = selectedRelabelDetail . value ? . application . relabelNo
@@ -1284,12 +1402,7 @@ async function loadMeasurementPhotos() {
await Promise . all ( ( selectedDetail . value ? . measurementPhotos || [ ] ) . map ( ( photo ) => loadMeasurementPhoto ( photo . fileId ) ) )
await Promise . all ( ( selectedDetail . value ? . measurementPhotos || [ ] ) . map ( ( photo ) => loadMeasurementPhoto ( photo . fileId ) ) )
}
}
async function confirmRelabelFromList ( relabelNo : string ) {
async function confirmRelabelFromList ( relabelNo : string ) {
try {
openCompletionConfirmation ( relabelNo , 'labeling' )
await ElMessageBox . confirm ( '确认仓库上传的换标结果无误并继续出库流程吗?' , '换标完成确认' , { confirmButtonText : '确认并继续' , cancelButtonText : '取消' } )
await confirmRelabelCompletion ( relabelNo )
ElMessage . success ( '已确认换标完成,订单已进入后续出库流程' )
await load ( )
} catch ( error ) { if ( error !== 'cancel' ) ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
}
async function cancelSelectedDiscard ( ) {
async function cancelSelectedDiscard ( ) {
const no = String ( selectedDetail . value ? . discardNo || selectedDetail . value ? . [ '0' ] || '' )
const no = String ( selectedDetail . value ? . discardNo || selectedDetail . value ? . [ '0' ] || '' )
@@ -1336,7 +1449,11 @@ async function loadTransferInventoryCandidates(warehouseId: string) {
if ( ! warehouseId ) return
if ( ! warehouseId ) return
relabelCandidatesLoading . value = true
relabelCandidatesLoading . value = true
try {
try {
transferInventoryCandidates . value = await listRelabelInventoryCandidates ( warehouseId )
/ / 共 用 接 口 包 含 退 货 历 史 及 占 用 中 的 箱 子 , 转 运 只 展 示 当 前 可 整 箱 使 用 的 来 源 。
transferInventoryCandidates . value = ( await listRelabelInventoryCandidates ( warehouseId ) ) . filter ( item =>
item . sourceType === 'RETURN_CARTON' || item . sourceType === 'RETURN_PARCEL'
? Number ( item . cartonAvailablePackages || 0 ) > 0
: Number ( item . availablePackages || 0 ) > 0 )
} catch ( error ) {
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '当前海外仓库存来源加载失败' )
ElMessage . error ( error instanceof Error ? error . message : '当前海外仓库存来源加载失败' )
} finally { relabelCandidatesLoading . value = false }
} finally { relabelCandidatesLoading . value = false }
@@ -1363,13 +1480,7 @@ async function selectTransferWarehouse() {
}
}
async function confirmSelectedRelabel ( ) {
async function confirmSelectedRelabel ( ) {
const relabelNo = selectedRelabelDetail . value ? . application . relabelNo
const relabelNo = selectedRelabelDetail . value ? . application . relabelNo
if ( ! relabelNo ) return
if ( relabelNo ) openCompletionConfirmation ( relabelNo , 'labeling' )
try {
await ElMessageBox . confirm ( '确认仓库上传的换标结果无误并继续出库流程吗?' , '换标完成确认' , { confirmButtonText : '确认并继续' , cancelButtonText : '取消' } )
await confirmRelabelCompletion ( relabelNo )
ElMessage . success ( '已确认换标完成,订单已进入后续出库流程' )
await openDetail ( { '0' : relabelNo } )
} catch ( error ) { if ( error !== 'cancel' ) ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
}
async function resubmitRejectedRelabel ( ) {
async function resubmitRejectedRelabel ( ) {
const application = selectedRelabelDetail . value ? . application
const application = selectedRelabelDetail . value ? . application
@@ -1385,7 +1496,7 @@ async function resubmitRejectedRelabel() {
idempotencyKey : createIdempotencyKey ( ) ,
idempotencyKey : createIdempotencyKey ( ) ,
} )
} )
ElMessage . success ( '贴标单已重新提交审核' )
ElMessage . success ( '贴标单已重新提交审核' )
await router . push ( { name : serviceListRouteName ( 'labeling' ) } )
await goToServiceList ( )
} catch ( error ) {
} catch ( error ) {
if ( error !== 'cancel' && error !== 'close' ) ElMessage . error ( error instanceof Error ? error . message : '重新提交失败' )
if ( error !== 'cancel' && error !== 'close' ) ElMessage . error ( error instanceof Error ? error . message : '重新提交失败' )
}
}
@@ -1393,39 +1504,57 @@ async function resubmitRejectedRelabel() {
const transferInventoryOptions = computed ( ( ) => {
const transferInventoryOptions = computed ( ( ) => {
const grouped = new Map < string , { value : string ; label : string ; packages : number } > ( )
const grouped = new Map < string , { value : string ; label : string ; packages : number } > ( )
for ( const item of transferInventoryCandidates . value ) {
for ( const item of transferInventoryCandidates . value ) {
const value = String ( item . inboundCode || '' )
/ / 退 货 库 存 没 有 普 通 入 仓 码 , 使 用 带 前 缀 的 退 货 来 源 编 码 , 避 免 提 交 时 被 当 作 普 通 入 仓 单 查 询 。
const returnNo = String ( item . inboundOrderNo || '' ) . trim ( )
const value = item . sourceType === 'RETURN_CARTON' || item . sourceType === 'RETURN_PARCEL'
? ( returnNo ? ` RETURN_ORDER: ${ returnNo } ` : '' )
: String ( item . inboundCode || '' )
if ( ! value ) continue
if ( ! value ) continue
const packages = inventoryQuantity ( item . availablePackages )
const packages = item . sourceType === 'RETURN_CARTON' || item . sourceType === 'RETURN_PARCEL'
? inventoryQuantity ( item . cartonAvailablePackages || 0 )
: inventoryQuantity ( item . availablePackages )
const current = grouped . get ( value )
const current = grouped . get ( value )
if ( current ) current . packages += packages
if ( current ) current . packages += packages
else grouped . set ( value , { value , label : ` ${ item . inboundOrderNo || value } | ${ value } ` , packages } )
else grouped . set ( value , { value , label : value . startsWith ( 'RETURN_ORDER:' ) ? ` 退货单| ${ returnNo } ` : ` ${ item . inboundOrderNo || value } | ${ value } ` , packages } )
}
}
return [ ... grouped . values ( ) ]
return [ ... grouped . values ( ) ]
} )
} )
async function selectTransferInventory ( value : string ) {
async function selectTransferInventory ( value : string ) {
selectedTransferInventory . value = value || ''
selectedTransferInventory . value = value || ''
selectedTransferSourceOrderIds . value = [ ]
selectedTransferSourceOrderIds . value = [ ]
if ( ! value ) return
transferSourceCartons . value = [ ] ; transferSourceError . value = '' ; ++ transferSourceRequest
if ( ! value ) { form . inboundNo = '' ; cargoRows . value = [ createCargoRow ( ) ] ; transferSourceLoading . value = false ; return }
if ( transferDestinationType . value === 'NONE' ) transferDestinationType . value = 'FBA'
void loadTransferSourceCartons ( )
form . inboundNo = value
form . inboundNo = value
/ / T h e i n v e n t o r y - c a n d i d a t e s r e s p o n s e a l r e a d y c o n t a i n s t h e m e r c h a n t - v i s i b l e
/ / T h e i n v e n t o r y - c a n d i d a t e s r e s p o n s e a l r e a d y c o n t a i n s t h e m e r c h a n t - v i s i b l e
/ / S K U / n a m e / p a c k a g e d a t a . D o n o t c a l l t h e p l a t f o r m - o n l y l a s t - l e g o r d e r s A P I
/ / S K U / n a m e / p a c k a g e d a t a . D o n o t c a l l t h e p l a t f o r m - o n l y l a s t - l e g o r d e r s A P I
/ / h e r e : m e r c h a n t s a r e n o t a u t h o r i z e d f o r t h a t e n d p o i n t ( A 0 4 0 3 ) , a n d t h e
/ / h e r e : m e r c h a n t s a r e n o t a u t h o r i z e d f o r t h a t e n d p o i n t ( A 0 4 0 3 ) , a n d t h e
/ / t r a n s f e r s e r v i c e c a n d e r i v e s o u r c e o r d e r s f r o m i n b o u n d C o d e w h e n I D s a r e
/ / t r a n s f e r s e r v i c e c a n d e r i v e s o u r c e o r d e r s f r o m i n b o u n d C o d e w h e n I D s a r e
/ / o m i t t e d . A g g r e g a t e c a n d i d a t e s f o r t h e s e l e c t e d i n b o u n d i n t o c a r g o r o w s .
/ / o m i t t e d . A g g r e g a t e c a n d i d a t e s f o r t h e s e l e c t e d i n b o u n d i n t o c a r g o r o w s .
const candidates = transferInventoryCandidates . value . filter ( ( item ) => String ( item . inboundCode || '' ) === value )
const isReturnSource = value . startsWith ( 'RETURN_ORDER:' )
const returnNo = isReturnSource ? value . slice ( 'RETURN_ORDER:' . length ) : ''
const candidates = transferInventoryCandidates . value . filter ( ( item ) => isReturnSource
? ( item . sourceType === 'RETURN_CARTON' || item . sourceType === 'RETURN_PARCEL' ) && String ( item . inboundOrderNo || '' ) === returnNo
: String ( item . inboundCode || '' ) === value )
const grouped = new Map < string , CargoRow > ( )
const grouped = new Map < string , CargoRow > ( )
for ( const candidate of candidates ) {
for ( const candidate of candidates ) {
const sku = String ( candidate . skuCode || '' )
const candidateRows = isReturnSource
const current = grouped . get ( sku )
? ( candidate . cartonSkuItems ? . length ? candidate . cartonSkuItems : candidate . skuItems || [ ] ) . map ( item => ( { sku : item . skuCode , name : candidate . productNameZh || '' , quantity : inventoryQuantity ( item . quantity ) , packageCount : inventoryQuantity ( candidate . cartonAvailablePackages || 0 ) } ) )
const quantity = inventoryQuantity ( candidate . availablePackages )
: [ { sku : String ( candidate . skuCode || '' ) , name : candidate . productNameZh || '' , quantity : inventoryQuantity ( candidate . availablePackages ) , packageCount : inventoryQuantity ( candidate . availablePackages ) } ]
if ( current ) current . quantity += quantity
for ( const row of candidateRows ) {
else grouped . set ( sku , { ... createCargoRow ( ) , sku , name : candidate . productNameZh || '' , quantity , availableQuantity : quantity } )
if ( ! row . sku ) continue
const current = grouped . get ( row . sku )
if ( current ) { current . quantity += row . quantity ; current . expectedPackageCount = Number ( current . expectedPackageCount || 0 ) + row . packageCount ; current . availableQuantity = Number ( current . availableQuantity || 0 ) + row . quantity }
else grouped . set ( row . sku , { ... createCargoRow ( ) , sku : row . sku , name : row . name , quantity : row . quantity , expectedPackageCount : row . packageCount , availableQuantity : row . quantity } )
}
}
}
cargoRows . value = [ ... grouped . values ( ) ]
cargoRows . value = [ ... grouped . values ( ) ]
try {
try {
if ( isReturnSource ) return
const historical = await listRelabelHistoricalOrders ( form . warehouse , value )
const historical = await listRelabelHistoricalOrders ( form . warehouse , value )
const sourceIds = historical . map ( ( item ) => String ( item . id || '' ) ) . filter ( Boolean )
const sourceIds = historical . map ( ( item ) => String ( item . id || '' ) ) . filter ( Boolean )
selectedTransferSourceOrderIds . value = sourceIds
if ( selectedTransferInventory . value === value ) selectedTransferSourceOrderIds . value = sourceIds
} catch ( error ) {
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '库存来源订单加载失败' )
ElMessage . error ( error instanceof Error ? error . message : '库存来源订单加载失败' )
}
}
@@ -1501,12 +1630,12 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
return
return
}
}
if ( cartonDetailRows . value . some ( item => item . cartonMark === source . cartonMark ) ) return
if ( cartonDetailRows . value . some ( item => item . cartonMark === source . cartonMark ) ) return
cargoRows . value . push ( { id : ++ cargoSequence , included : true , sku : source . skuItems ? . map ( item => item . skuCode ) . join ( ',' ) || '' , name : source . productNameZh || '' , shipmentNo : '' , cartonMark : source . cartonMark || '' , fbaCartonMark : '' , remark : '' , sourceInventoryBalanceId : undefined , sourceInventoryPositionId : undefined , sourceInboundOrderId : undefined , returnCartonId : String ( source . returnCartonId ) , quantity : 1 , fnsku : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , destinationWarehouseId : '' } )
cargoRows . value . push ( { id : ++ cargoSequence , included : true , sku : source . skuItems ? . map ( item => item . skuCode ) . join ( ',' ) || '' , name : source . productNameZh || '' , shipmentNo : '' , cartonMark : source . cartonMark || '' , fbaCartonMark : '' , remark : '' , sourceInventoryBalanceId : undefined , sourceInventoryPositionId : undefined , sourceInboundOrderId : undefined , returnCartonId : String ( source . returnCartonId ) , quantity : 1 , expectedPackageCount : 1 , fnsku : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , destinationWarehouseId : '' } )
}
}
< / script >
< / script >
< template >
< template >
< section class = "warehouse-service-page" : class = "{ 'warehouse-service-page--detail': mode === 'detail', 'warehouse-service-page--return-relabel': isReturnRelabelSource }" >
< section class = "warehouse-service-page" : class = "{ 'warehouse-service-page--detail': mode === 'detail', 'warehouse-service-page--transfer': props.service === 'transfer', 'warehouse-service-page--return-relabel': isReturnRelabelSource }" >
< template v-if = "mode === 'list'" >
< template v-if = "mode === 'list'" >
< QueryToolbar v-model = "listQueryModel" :class="{ 'labeling-query': props.service === 'labeling', 'transfer-query': props.service === 'transfer' || props.service === 'disposal', 'disposal-query': props.service === 'disposal' }" layout="grid" :ariaLabel="`${config.title}筛选`" :fields="listQueryFields" :search-on-reset="false" @search="query" @reset="resetListQuery" / >
< QueryToolbar v-model = "listQueryModel" :class="{ 'labeling-query': props.service === 'labeling', 'transfer-query': props.service === 'transfer' || props.service === 'disposal', 'disposal-query': props.service === 'disposal' }" layout="grid" :ariaLabel="`${config.title}筛选`" :fields="listQueryFields" :search-on-reset="false" @search="query" @reset="resetListQuery" / >
< div class = "warehouse-card warehouse-table-card" >
< div class = "warehouse-card warehouse-table-card" >
@@ -1536,19 +1665,19 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< / div >
< / div >
< div v-show = "props.service !== 'labeling' || !orderInfoCollapsed" class="warehouse-form-grid" >
< div v-show = "props.service !== 'labeling' || !orderInfoCollapsed" class="warehouse-form-grid" >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>操作仓</span><el-select v-model="form.warehouse" filterable clearable :loading="fbaWarehousesLoading" :disabled="Boolean(operationWarehousesError)" placeholder="请选择海外仓" @change="selectDiscardWarehouse"><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 class="warehouse-hint" > 请先选择本次废弃货物所在的海外仓 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>操作仓</span><el-select v-model="form.warehouse" filterable clearable :loading="fbaWarehousesLoading" :disabled="Boolean(operationWarehousesError)" placeholder="请选择海外仓" @change="selectDiscardWarehouse"><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 class="warehouse-hint" > 请先选择本次废弃货物所在的海外仓 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidates" :key="candidate.orderId" :label="candidate.sourceType === 'RETURN_CARTON' ? `退货|${candidate.inboundOrderNo || '—'}| ${candidate.cartonMark || '—'}|可用 ${candidate.availablePackages}` : `${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}|可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 显示当前操作仓内有可用数量的普通入库库存和退货库存 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidates" :key="candidate.orderId" :label="candidate.sourceType === 'RETURN_CARTON' ? `退货|${candidate.inboundOrderNo || '—'}| ${candidate.cartonMark || '—'}|可用 ${candidate.availablePackages}` : `${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}| ${candidate.palletNo || '散箱'}| 可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 显示当前操作仓内有可用库存的单据 ; 带出箱唛后可勾选部分箱子 , 按整箱废弃 。 < / small > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :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 > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :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 > < / label >
< label v-else-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>废弃原因</span><el-select v-model="form.reason" placeholder="请选择"><el-option label="客户退货" value="客户退货" /><el-option label="亚马逊移除" value="亚马逊移除" /><el-option label="残损不可售" value="残损不可售" / > < / el-select > < / label >
< label v-else-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>废弃原因</span><el-select v-model="form.reason" placeholder="请选择"><el-option label="客户退货" value="客户退货" /><el-option label="亚马逊移除" value="亚马逊移除" /><el-option label="残损不可售" value="残损不可售" / > < / el-select > < / 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 > < / 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 > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2" ><span><i>*</i>选择入仓单或退货单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :filter-method="(value: string) => relabelInventorySearch = value" :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="暂无匹配的可选库存" placeholder="搜索单号或选择可用库存" @change="selectRelabelInventory"><el-option v-for="option in visibleRelabelInboundOptions.filter(item => !relabelInventorySearch.trim() || item.inboundOrderNo.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()) || item.inboundCode.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()))" :key="option.value" :label="relabelOptionLabel(option)" :value="option.value" :disabled="option.availablePackages <= 0" /></el-select><small class="warehouse-hint" > 在仓数量包含被占用货物 ; 本次可选数量才可加入申请 。 合箱后的库存按当前包裹 / 箱展示 , 来源退货单保留 。 < / small > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>选择入仓单或退货单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :filter-method="(value: string) => relabelInventorySearch = value" :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="暂无匹配的可选库存" placeholder="搜索单号或选择可用库存" @change="selectRelabelInventory"><el-option v-for="option in visibleRelabelInboundOptions.filter(item => !relabelInventorySearch.trim() || item.inboundOrderNo.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()) || item.inboundCode.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()))" :key="option.value" :label="relabelOptionLabel(option)" :value="option.value" :disabled="option.availablePackages <= 0" / > < / el-select > < / label >
< label v-if = "props.service === 'transfer'"><span>库存转运来源(可选)</span><el-select v-model="selectedTransferInventory" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可用库存" placeholder="不选则按新货转运;选择后免 PDA 测量" @change="selectTransferInventory"><el-option v-for="option in transferInventoryOptions" :key="option.value" :label="`${option.label}|可用 ${option.packages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 库存转运可直接进入目的仓 、 换标和排车流程 , PDA 可拆托 、 重打或同目的仓合托 。 < / small > < / label >
< label v-if = "props.service === 'transfer'"><span>库存转运来源(可选)</span><el-select v-model="selectedTransferInventory" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可用库存" placeholder="不选则按新货转运;选择后免 PDA 测量" @change="selectTransferInventory"><el-option v-for="option in transferInventoryOptions" :key="option.value" :label="`${option.label}|可用 ${option.packages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 库存转运可直接进入目的仓 、 换标和排车流程 , PDA 可拆托 、 重打或同目的仓合托 。 < / 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 > < / 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 > < / 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 > < / div >
< div v-if = "props.service === 'transfer' && !inventoryTransfer " 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 > < / 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' && !inventoryTransfer && 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 >
< label v-if = "props.service === 'transfer' && !inventoryTransfer && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人</span><el-input v-model="form.pickupContact" placeholder="请输入提货联系人" maxlength="128" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人电话</span><el-input v-model="form.pickupPhone" placeholder="请输入提货联系人电话" maxlength="64" / > < / label >
< label v-if = "props.service === 'transfer' && !inventoryTransfer && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人电话</span><el-input v-model="form.pickupPhone" placeholder="请输入提货联系人电话" maxlength="64" / > < / label >
< div v-if = "props.service === 'transfer' && form.deliveryMethod === 'CUSTOMER_DELIVERY'" v-loading="operationWarehouseDetailLoading" class="warehouse-receiving-card warehouse-span-2" >
< div v-if = "props.service === 'transfer' && !inventoryTransfer && form.deliveryMethod === 'CUSTOMER_DELIVERY'" v-loading="operationWarehouseDetailLoading" class="warehouse-receiving-card warehouse-span-2" >
< div > < span > 海外仓收货信息 < / span > < strong > { { operationWarehouseAddress || '该海外仓暂未维护收货地址' } } < / strong > < / div >
< div > < span > 海外仓收货信息 < / span > < strong > { { operationWarehouseAddress || '该海外仓暂未维护收货地址' } } < / strong > < / div >
< label class = "warehouse-span-2" > < span > < i > * < / i > 送货地址 < / span > < el-input v-model = "form.address" placeholder="请输入实际送货地址" / > < / label >
< label class = "warehouse-span-2" > < span > < i > * < / i > 送货地址 < / span > < el-input v-model = "form.address" placeholder="请输入实际送货地址" / > < / label >
< div > < span > 收货联系人 < / span > < strong > { { selectedOperationWarehouse ? . warehouse . contactName || '未维护' } } < / strong > < / div >
< div > < span > 收货联系人 < / span > < strong > { { selectedOperationWarehouse ? . warehouse . contactName || '未维护' } } < / strong > < / div >
@@ -1560,14 +1689,14 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓签收联系人<em v-if="form.warehouse && !form.contact" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.contact" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓签收联系人<em v-if="form.warehouse && !form.contact" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.contact" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓联系电话<em v-if="form.warehouse && !form.phone" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.phone" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓联系电话<em v-if="form.warehouse && !form.phone" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.phone" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>贴标截止时间</span><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>贴标截止时间</span><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" / > < / label >
< label v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>换标后派送方式</span><el-select v-model="form.deliveryMethod" 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 v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>换标后派送方式</span><el-select v-model="form.deliveryMethod" placeholder="请选择派送方式"><el-option label="卡车派送" value="TRUCK" /><el-option label="快递派送" value="EXPRESS" /><el-option label="亚马逊 自提" value="AMAZON _PICKUP" /><el-option label="客户 自提" value="CUSTOMER _PICKUP" / > < / el-select > < / label >
< div v-if = "props.service === 'labeling' && labelingMode !== 'sku' && ( form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') " class="warehouse-checkbox-field"><span>是否打印面单</span><el-checkbox v-model="form.relabel" > 需要仓库打印面单 < / el -checkbox > < / div >
< div v-if = "props.service === 'labeling' && labelingMode !== 'sku' && form.deliveryMethod === 'EXPRESS'" class="warehouse-checkbox-field"><span>是否打印面单</span><el-checkbox v-model="form.relabel" > 需要仓库打印面单 < / el -checkbox > < / div >
< label v-if = "canManageRelabelFiles && props.service === 'labeling' && labelingMode !== 'sku' && ( form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel" > < / label >
< label v-if = "canManageRelabelFiles && props.service === 'labeling' && labelingMode !== 'sku' && form.deliveryMethod === 'EXPRESS' && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel" > < / label >
< label v-if = "props.service === 'relabel'" class="warehouse-span-2"><span><i>*</i>详细派送地址</span><el-input v-model="form.address" placeholder="请输入派送地址" / > < / label >
< label v-if = "props.service === 'relabel'" class="warehouse-span-2"><span><i>*</i>详细派送地址</span><el-input v-model="form.address" placeholder="请输入派送地址" / > < / label >
< label v-if = "props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>{{ props.service === 'transfer' ? '预估体积(CBM) ' : ' 预估总体积(CBM) ' }} </span><el-input-number v-model="form.volumeCbm" :min="0" :precision="4" :step="0.0001" controls-position="right" placeholder="请输入预估总体积" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i> 预估总体积(CBM) </span><el-input-number v-model="form.volumeCbm" :min="0" :precision="4" :step="0.0001" controls-position="right" placeholder="请输入预估总体积" / > < / label >
< 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' && !inventoryTransfer) || 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="选填" / > < / 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="选填" / > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span>目的去向</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 > < / div >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span>目的去向</span><el-radio-group v-model="transferDestinationType" @change="selectTransferDestinationType"><el-radio-button v-if="!inventoryTransfer" 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 > < / 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 > < / 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 > < / 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 === 'FBA'"><span>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="选填,可后续补充" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'"><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.privateDestinationAddress" maxlength="1000" placeholder="请输入私人仓完整地址" / > < / label >
@@ -1639,13 +1768,31 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< 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" :readonly="isReturnRelabelSource" placeholder="请输入仓库唛头" @change="applyExistingCartonBinding(row)" /></td><td><el-input v-model="row.originalSku" :readonly="isReturnRelabelSource" 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-input v-model="row.shipmentNo" 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' ? 8 : 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-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" :readonly="isReturnRelabelSource" placeholder="请输入仓库唛头" @change="applyExistingCartonBinding(row)" /></td><td><el-input v-model="row.originalSku" :readonly="isReturnRelabelSource" 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-input v-model="row.shipmentNo" 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' ? 8 : 6"><div class="warehouse-empty" > 暂无绑定明细 , 请点击 “ 添加 SKU 明细 ” < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / template >
< / template >
< template v-else >
< template v-else >
< div class = "warehouse-section-heading" : class = "{ 'warehouse-step-heading': props.service === 'transfer' }" > < div > < h3 > < span v-if = "props.service === 'transfer'" class="warehouse-step-number">1</span>货物明细</h3></div><div v-if="props.service !== 'disposal'" class="warehouse-heading-actions"><el-button v-if="props.service === 'transfer'" size="small" :disabled="!cargoRows.some((row) => row.sku.trim() && row.quantity > 0)" @click="printTransferDraftCartonMarks"><Printer :size="15" />打印唛头</el-button><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []" > 清空 < / el -button > < / div > < / div >
< section v-if = "props.service === 'disposal' && discardSelectedCandidate" class="warehouse-discard-boxes" >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table "> < thead > < tr > < th > 商品 SKU { { props . service === 'transfer' ? '' : ' *' } } < / th > < th > 中文品名 { { props . service === 'transfer' ? '' : ' *' } } < / th > < th v-if = "props.service === 'disposal'">可用箱数</th><th v-if="props.service === 'relabel'">FNSKU / 箱唛</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : '预计箱数' }} *</th><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" :placeholder="props.service === 'transfer' ? '选填' : '请输入商品 SKU'" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" :placeholder="props.service === 'transfer' ? '选填' : '请输入中文品名'" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-if="props.service === 'relabel'"><el-input v-model="row.shippingMark" placeholder="请输入FNSKU或箱唛" /></td><td><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'disposal' ? 4 : props.service === 'relabel' ? 5 : 4"><div class="warehouse-empty" > {{ props.service = = = ' disposal ' ? ' 请先选择入库单 , 系统将自动带出 SKU 和可用箱数 ' : ' 暂无货物明细 , 请点击 “ 添加行 ” ' }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< div class = "warehouse-section-heading " > < div > < h3 > 选择废弃箱子 < / h3 > < small class = "warehouse-hint" > 选中箱子整体废弃 , 箱内所有 SKU 一起出库 ; 未选箱子保留库存 。 < / small > < / div > < div v-if = "discardSelectedCandidate.cartonSelectionSupported" class="warehouse-heading-actions"><el-button size="small" @click="selectAllDiscardBoxes(true)">全选</el-button><el-button size="small" @click="selectAllDiscardBoxes(false)" > 清空选择 < / el -button > < / div > < / div >
< template v-if = "discardSelectedCandidate.cartonSelectionSupported" >
< div class = "warehouse-discard-box-toolbar" > < el-input v-model = "discardBoxSearch" clearable placeholder="搜索箱唛或 SKU" / > < strong > 已选 { { discardSelectedBoxKeys . length } } / { { discardSelectableBoxes . length } } 箱 < / strong > < / div >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table" > < thead > < tr > < th > 选择 < / th > < th > 箱唛 < / th > < th > 所属托盘 < / th > < th > 箱内 SKU < / th > < th > 中文品名 < / th > < / tr > < / thead > < tbody > < tr v-for = "box in discardVisibleBoxes" :key="box.key"><td><el-checkbox :model-value="discardSelectedBoxKeys.includes(box.key)" :aria-label="`选择废弃箱子 ${box.cartonMark}`" @change="toggleDiscardBox(box.key, Boolean($event))" /></td><td><strong>{{ box.cartonMark }}</strong></td><td>{{ box.palletNo || '散箱' }}</td><td>{{ box.skus.map(sku => sku.skuCode).join('、') }}</td><td>{{ box.skus.map(sku => sku.productName || '—').join('、') }}</td></tr><tr v-if="!discardVisibleBoxes.length"><td colspan="5"><div class="warehouse-empty" > 暂无匹配箱唛 < / div > < / td > < / tr > < / tbody > < / table > < / div >
< small v-if = "!discardSelectedBoxKeys.length" class="warehouse-hint" > 请至少选择一个箱子 。 < / small >
< / template >
< el-alert v-else type = "info" :closable = "false" : title = "discardSelectedCandidate.cartonSelectionBlockReason || '当前库存缺少完整箱级记录,暂时只能选择全部库存废弃。'" / >
< / section >
< section v-if = "inventoryTransfer" v-loading="transferSourceLoading" >
< div class = "warehouse-section-heading" > < h3 > 原单箱明细 < small > 共 { { transferSourceCartons . length } } 箱 · 只读 < / small > < / h3 > < / div >
< el-alert v-if = "transferSourceError" :title="transferSourceError" type="error" :closable="false"><el-button link @click="loadTransferSourceCartons" > 重新加载 < / el -button > < / el-alert >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table" > < thead > < tr > < th > 箱号 < / th > < th > 商品 SKU < / th > < th > 中文品名 < / th > < th > 箱内数量 ( 件 ) < / th > < th > 原目的仓 < / th > < / tr > < / thead > < tbody >
< tr v-for = "box in transferSourceCartons" :key="box.cartonMark" > < td > { { box . cartonMark } } < / td > < td > { { box . items . map ( item => item . skuCode || '—' ) . join ( ' / ' ) } } < / td > < td > { { box . productNameZh || '—' } } < / td > < td > { { box . quantity ? ? '未记录' } } < / td > < td > { { [ box . destinationWarehouseCode , box . destinationWarehouseName ] . filter ( Boolean ) . join ( ' · ' ) || '未指定' } } < / td > < / tr >
< tr v-if = "!transferSourceLoading && !transferSourceCartons.length"><td colspan="5"><div class="warehouse-empty" > {{ transferSourceError ? ' 加载失败 , 请重试 ' : ' 原库存单据未记录逐箱明细 , 不虚构箱号 ; 按原库存汇总转运 ' }} < / div > < / td > < / tr >
< / tbody > < / table > < / div >
< div v-if = "!transferSourceLoading && !transferSourceError && !transferSourceCartons.length" 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="row in cargoRows" :key="row.id" > < td > { { row . sku || '—' } } < / td > < td > { { row . name || '—' } } < / td > < td > { { row . expectedPackageCount } } < / td > < td > { { transferInventoryCandidates . filter ( item => item . inboundCode === selectedTransferInventory && item . skuCode === row . sku ) . map ( item => item . destinationFbaWarehouseCode || item . destinationFbaWarehouseName ) . filter ( Boolean ) . join ( ' / ' ) || '未指定' } } < / td > < / tr > < / tbody > < / table > < / div >
< / section >
< div v-if = "!inventoryTransfer && (props.service !== 'disposal' || !discardSelectedCandidate?.cartonSelectionSupported)" class="warehouse-section-heading" :class="{ 'warehouse-step-heading': props.service === 'transfer' }"><div><h3><span v-if="props.service === 'transfer'" class="warehouse-step-number">1</span>货物明细</h3></div><div v-if="props.service !== 'disposal'" class="warehouse-heading-actions"><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []" > 清空 < / el -button > < / div > < / div >
< div v-if = "!inventoryTransfer && (props.service !== 'disposal' || !discardSelectedCandidate?.cartonSelectionSupported)" class="warehouse-table-scroll"><table class="warehouse-edit-table"><thead><tr><th>商品 SKU{{ props.service === 'transfer' ? '' : ' *' }}</th><th>中文品名{{ props.service === 'transfer' ? '' : ' *' }}</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-if="props.service === 'relabel'">FNSKU / 箱唛</th><th v-if="props.service === 'transfer'">SKU 数量(件)*</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : '预计箱数' }} *</th><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" :placeholder="props.service === 'transfer' ? '选填' : '请输入商品 SKU'" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" :placeholder="props.service === 'transfer' ? '选填' : '请输入中文品名'" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-if="props.service === 'relabel'"><el-input v-model="row.shippingMark" placeholder="请输入FNSKU或箱唛" /></td><td v-if="props.service === 'transfer'"><el-input-number v-model="row.quantity" :min="1" :precision="0" controls-position="right" placeholder="请输入件数" /></td><td v-if="props.service === 'transfer'"><el-input-number v-model="row.expectedPackageCount" :min="1" :precision="0" controls-position="right" placeholder="请输入预计箱数" /></td><td v-else><el-input-number v-model="row.quantity" :disabled="props.service === 'disposal'" :min="props.service === 'disposal' ? 0 : 1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'disposal' ? 4 : props.service === 'relabel' || props.service === 'transfer' ? 5 : 4"><div class="warehouse-empty" > {{ props.service = = = ' disposal ' ? ' 请先选择入库单 , 系统将自动带出 SKU 和可用箱数 ' : ' 暂无货物明细 , 请点击 “ 添加行 ” ' }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / template >
< / template >
< / div >
< / div >
< div v-if = "props.service === 'transfer' && form.relabel" class="warehouse-card warehouse-form-section warehouse-transfer-relabel" >
< 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 warehouse-step-heading" > < div > < h3 > < span class = "warehouse-step-number" > 2 < / span > 换标申请 < / h3 > < / div > < / div >
< div class = "warehouse-section-heading warehouse-labeling-heading warehouse-step-heading" > < div > < h3 > < span class = "warehouse-step-number" > 2 < / span > 换标申请 < / h3 > < / 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><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-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="AMAZON _PICKUP" /><el-option label="客户 自提" value="CUSTOMER _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><span>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号(选填)" /></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 > < / 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 > < / div >
< div class = "warehouse-transfer-file-groups warehouse-file-groups--compact" >
< div class = "warehouse-transfer-file-groups warehouse-file-groups--compact" >
< section v-if = "canManageRelabelFiles" class="warehouse-file-group warehouse-transfer-total-files" >
< section v-if = "canManageRelabelFiles" class="warehouse-file-group warehouse-transfer-total-files" >
@@ -1662,11 +1809,11 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< 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')" >
< 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')" >
< / section >
< / section >
< / 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 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 warehouse-binding-heading" > < div > < strong > < span class = "warehouse-step-number" > 4 < / span > 绑定明细 < / strong > < / 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 class = "warehouse-section-heading warehouse-tab-actions warehouse-binding-heading" > < div > < strong > < span class = "warehouse-step-number" > 4 < / span > 绑定明细 < / strong > < / 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" > 暂无绑定明细 , 请点击 “ 添加换标明细 ” < / 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" 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-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" 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 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" 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 >
< / div >
< div class = "warehouse-submit-bar warehouse-transfer-submit-bar" > < el-button v-if = "mode === 'create'" native-type="button" @click="resetForm">重置</el-button><el-button v-if="props.service === 'transfer'" native-type="button" :loading="saving" @click="submit('draft')">保存草稿</el-button><el-button v-if="props.service === 'transfer'" type="primary" native-type="submit" :loading="saving">提交订单</el-button><el-button v-if="props.service !== 'transfer'" type="primary" native-type="submit" :loading="saving" > 提交 {{ config.title }} < / el -button > < / div >
< div class = "warehouse-submit-bar warehouse-transfer-submit-bar" > < el-button v-if = "mode === 'create'" native-type="button" @click="resetForm">重置</el-button><el-button v-if="props.service === 'transfer'" native-type="button" :loading="saving" @click="submit('draft')">保存草稿</el-button><el-button v-if="props.service === 'transfer'" type="primary" native-type="submit" :loading="saving">提交订单</el-button><el-button v-if="props.service !== 'transfer'" type="primary" native-type="submit" :loading="saving" > 提交 {{ config.title }} < / el -button > < / div >
< / form >
< / form >
@@ -1676,27 +1823,69 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< header class = "warehouse-detail-header" >
< header class = "warehouse-detail-header" >
< button type = "button" class = "warehouse-detail-back" @click ="returnToList" > < ArrowLeft :size = "16" / > 返回列表 < / button >
< button type = "button" class = "warehouse-detail-back" @click ="returnToList" > < ArrowLeft :size = "16" / > 返回列表 < / button >
< div class = "warehouse-detail-title" > < strong > { { props . service === 'labeling' ? selectedRelabelDetail ? . application . relabelNo : selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || ` ${ config . title } 详情 ` } } < / strong > < span > { { config . title } } 详情 < / span > < / div >
< div class = "warehouse-detail-title" > < strong > { { props . service === 'labeling' ? selectedRelabelDetail ? . application . relabelNo : selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || ` ${ config . title } 详情 ` } } < / strong > < span > { { config . title } } 详情 < / span > < / div >
< div class = "warehouse-detail-actions" > < el-button v-if = "canCreate && props.service === 'labeling' && selectedRelabelDetail?.application.status === 'REJECTED'" type="primary" @click="resubmitRejectedRelabel">修改后重新提交</el-button><el-button v-if="canConfirmRelabel && props.service === 'labeling' && selectedRelabelDetail?.application.status === 'CUSTOMER_CONFIRM_PENDING' && !selectedRelabelDetail?.application.customerConfirmedAt" type="primary" @click="confirmSelectedRelabel">确认换标完成</el-button><el-button v-if="canCancelDiscard && props.service === 'disposal' && selectedDetail?.status === 'PENDING_REVIEW'" type="danger" @click="cancelSelectedDiscard">取消申请</el-button><el-button v-if="canCreate && props.service === 'transfer' && ['DRAFT', 'REJECTED'].includes(selectedDetail?.status || '')" type="primary" @click="openEdit(String(selectedDetail?.transferNo || selectedDetail?.['0'] || ''))">编辑</el-button><el-button v-if="canConfirmTransfer && props.service === 'transfer' && selectedDetail?.status === 'WAIT_CUSTOMER_CONFIRMATION'" type="primary" @click="confirmTransferMeasurements">确认测量数据</el-button><el-tag :type="props.service === 'labeling' ? relabelStatusTagType(selectedRelabelDetail?.application.status || '') : undefined" effect="light" > {{ statusLabel ( props.service = = = ' labeling ' ? selectedRelabelDetail ? .application.status | | ' ' : String ( selectedDetail ? .status | | ' ' ) ) }} < / el -tag > < / div >
< div class = "warehouse-detail-actions" > < el-button v-if = "canCreate && props.service === 'labeling' && selectedRelabelDetail?.application.status === 'REJECTED'" type="primary" @click="resubmitRejectedRelabel">修改后重新提交</el-button><el-button v-if="canConfirmRelabel && props.service === 'labeling' && selectedRelabelDetail?.application.status === 'CUSTOMER_CONFIRM_PENDING' && !selectedRelabelDetail?.application.customerConfirmedAt" type="primary" @click="confirmSelectedRelabel">确认换标完成</el-button><el-button v-if="canConfirmDiscard && props.service === 'disposal' && selectedDetail?.status === 'PDA_COMPLETED'" type="primary" @click="openCompletionConfirmation(String(selectedDetail?.discardNo || ''), 'disposal')">废弃确认</el-button><el-button v-if="canCancelDiscard && props.service === 'disposal' && selectedDetail?.status === 'PENDING_REVIEW'" type="danger" @click="cancelSelectedDiscard">取消申请</el-button><el-button v-if="canCreate && props.service === 'transfer' && ['DRAFT', 'REJECTED'].includes(selectedDetail?.status || '')" type="primary" @click="openEdit(String(selectedDetail?.transferNo || selectedDetail?.['0'] || ''))">编辑</el-button><el-button v-if="canConfirmTransfer && props.service === 'transfer' && selectedDetail?.status === 'WAIT_CUSTOMER_CONFIRMATION'" type="primary" @click="confirmTransferMeasurements">确认测量数据</el-button><el-tag v-if="props.service !== 'transfer'" :type="props.service === 'labeling' ? relabelStatusTagType(selectedRelabelDetail?.application.status || '') : undefined" effect="light" > {{ statusLabel ( props.service = = = ' labeling ' ? selectedRelabelDetail ? .application.status | | ' ' : String ( selectedDetail ? .status | | ' ' ) ) }} < / el -tag > < / div >
< / header >
< / header >
< div v-if = "detailError" class="warehouse-detail-error" role="alert"><strong>{{ detailError }}</strong><el-button @click="loadDetailPage" > 重新加载 < / el -button > < / div >
< div v-if = "detailError" class="warehouse-detail-error" role="alert"><strong>{{ detailError }}</strong><el-button @click="loadDetailPage" > 重新加载 < / el -button > < / div >
< el-alert v-else-if = "props.service === 'labeling' && selectedRelabelDetail?.application.status === 'REJECTED'" class="warehouse-rejected-alert" type="error" :closable="false" show-icon title="贴标单审核已驳回" :description="selectedRelabelDetail.application.reviewRemark || '平台未填写驳回备注'" / >
< el-alert v-else-if = "props.service === 'labeling' && selectedRelabelDetail?.application.status === 'REJECTED'" class="warehouse-rejected-alert" type="error" :closable="false" show-icon title="贴标单审核已驳回" :description="selectedRelabelDetail.application.reviewRemark || '平台未填写驳回备注'" / >
< div v-else class = "warehouse-card warehouse-form-section" > < h3 > 订单概览 < / h3 > < div v-if = "props.service === 'labeling'" class="warehouse-detail-grid"><div><span>操作仓</span><strong>{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}</strong></div><div><span>客户参考号</span><strong>{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}</strong></div><div><span>SKU 数(种)</span><strong>{{ selectedRelabelDetail ? relabelSkuCount(selectedRelabelDetail.items) : '—' }}</strong></div><div><span>总箱数</span><strong>{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}</strong></div><div><span>贴标截止时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}</strong></div><div><span>仓库联系人</span><strong>{{ selectedRelabelDetail?.application.contactName || '—' }}</strong></div><div><span>联系电话</span><strong>{{ selectedRelabelDetail?.application.contactPhone || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}</strong></div></div><div v-else-if="props.service === 'transfer'" class="warehouse-detail-grid warehouse-transfer-overview-grid"><div><span>状态</span><el-tag :type="transferStatusTagType(selectedDetail?.status || '')" effect="light">{{ statusLabel(selectedDetail?.status || '') }}</el-tag></div><div><span>目的去向</span><strong>{{ destinationTypeLabel(selectedDetail?.destinationType, selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if="selectedDetail?.destinationType !== 'PRIVATE'"><span>FBA 目的仓</span><strong>{{ selectedDetail?.destinationWarehouseName || warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if="selectedDetail?.destinationType !== 'PRIVATE'"><span>FBA 货件号</span><strong>{{ selectedDetail?.fbaShipmentNo || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'" class="warehouse-overview-wide"><span>私人仓地址</span><strong>{{ selectedDetail?.privateDestinationAddress || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>邮编</span><strong>{{ selectedDetail?.privateDestinationPostalCode || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>私人仓联系人</span><strong>{{ selectedDetail?.privateDestinationContactName || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>联系电话</span><strong>{{ selectedDetail?.privateDestinationContactPhone || '—' }}</strong></div><div><span>海外仓</span><strong>{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}</strong></div><div><span>箱数</span><strong>{{ selectedDetail?.packageCount ?? '—' }}</strong></div><div><span>入仓方式</span><strong>{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}</strong></div><div><span>期望发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div><div><span>是否换标</span><strong>{{ selectedDetail?.needRelabel ? '是' : '否' }}</strong></div><div><span>换标状态</span><el-tag :type="transferRelabelStatusTagType(transferRelabelStatus(selectedDetail || {}).code)" effect="light">{{ transferRelabelStatus(selectedDetail || {}).label }}</el-tag></div><div><span>换标单号</span><strong>{{ selectedDetail?.relabelNo || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedDetail?.createdAt) }}</strong></div><div><span>转运单号</span><strong>{{ selectedDetail?.transferNo || '—' }}</strong></div></div><div v-else class="warehouse-detail-grid"><div v-for="(column, index) in config.columns.slice(1)" :key="column" > < span > { { column } } < / span > < strong > { { selectedDetail ? . [ String ( index ) ] || '—' } } < / strong > < / div > < / div > < / div >
< div v-else class = "warehouse-card warehouse-form-section" > < h3 > 订单概览 < / h3 > < div v-if = "props.service === 'labeling'" class="warehouse-detail-grid"><div><span>操作仓</span><strong>{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}</strong></div><div><span>客户参考号</span><strong>{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}</strong></div><div><span>SKU 数(种)</span><strong>{{ selectedRelabelDetail ? relabelSkuCount(selectedRelabelDetail.items) : '—' }}</strong></div><div><span>总箱数</span><strong>{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}</strong></div><div><span>贴标截止时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}</strong></div><div><span>仓库联系人</span><strong>{{ selectedRelabelDetail?.application.contactName || '—' }}</strong></div><div><span>联系电话</span><strong>{{ selectedRelabelDetail?.application.contactPhone || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}</strong></div></div><div v-else-if="props.service === 'transfer'" class="warehouse-detail-grid warehouse-transfer-overview-grid"><div><span>状态</span><el-tag :type="transferStatusTagType(selectedDetail?.status || '')" effect="light">{{ statusLabel(selectedDetail?.status || '') }}</el-tag></div><div><span>目的去向</span><strong>{{ destinationTypeLabel(selectedDetail?.destinationType, selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if="selectedDetail?.destinationType !== 'PRIVATE'"><span>FBA 目的仓</span><strong>{{ selectedDetail?.destinationWarehouseName || warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if="selectedDetail?.destinationType !== 'PRIVATE'"><span>FBA 货件号</span><strong>{{ selectedDetail?.fbaShipmentNo || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'" class="warehouse-overview-wide"><span>私人仓地址</span><strong>{{ selectedDetail?.privateDestinationAddress || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>邮编</span><strong>{{ selectedDetail?.privateDestinationPostalCode || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>私人仓联系人</span><strong>{{ selectedDetail?.privateDestinationContactName || '—' }}</strong></div><div v-if="selectedDetail?.destinationType === 'PRIVATE'"><span>联系电话</span><strong>{{ selectedDetail?.privateDestinationContactPhone || '—' }}</strong></div><div><span>海外仓</span><strong>{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}</strong></div><div><span>预报 SKU 种数</span><strong>{{ new Set((selectedDetail?.items || []).map(item => item.skuCode).filter(Boolean)).size }}</strong></div><div><span>预计箱数</span><strong>{{ selectedDetail?.packageCount ?? '—' }}</strong></div><div><span>实际签收箱数</span><strong>{{ selectedDetail?.cartons?.filter((carton) => carton.status === 'COUNTED').length ?? '—' }}</strong></div><div><span>入仓方式</span><strong>{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}</strong></div><div><span>期望发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div><div><span>是否换标</span><strong>{{ selectedDetail?.needRelabel ? '是' : '否' }}</strong></div><div><span>换标状态</span><el-tag :type="transferRelabelStatusTagType(transferRelabelStatus(selectedDetail || {}).code)" effect="light">{{ transferRelabelStatus(selectedDetail || {}).label }}</el-tag></div><div><span>换标单号</span><strong>{{ selectedDetail?.relabelNo || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedDetail?.createdAt) }}</strong></div></div><div v-else class="warehouse-detail-grid"><div v-for="(column, index) in config.columns.slice(1)" :key="column" > < span > { { column } } < / span > < strong > { { selectedDetail ? . [ String ( index ) ] || '—' } } < / strong > < / div > < / div > < / div >
< div v-if = "props.service === 'disposal'" class="warehouse-card warehouse-form-section" >
< div v-if = "props.service === 'disposal'" class="warehouse-card warehouse-form-section" >
< h3 > 本次废弃箱子 < / h3 > < div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table" > < thead > < tr > < th > 入仓码 < / th > < th > SKU < / th > < th > 品名 < / th > < th > 申请数量 < / th > < th > 所选箱唛 < / th > < / tr > < / thead > < tbody > < tr v-for = "(item, index) in selectedDetail?.items || []" :key="index" > < td > { { item . inboundCode || '—' } } < / td > < td > { { item . skuCode || '—' } } < / td > < td > { { item . productName || '—' } } < / td > < td > { { item . discardPackages ? ? '—' } } < / td > < td > { { item . cartonMarks ? . join ( '、' ) || '全部库存(历史记录无箱唛快照)' } } < / td > < / tr > < / tbody > < / table > < / div >
< h3 > 废弃现场照片 < / h3 > < div v-if = "selectedDetail?.photos?.length" class="warehouse-photo-grid"><div v-for="photo in selectedDetail.photos" :key="photo.fileId" class="warehouse-photo-item"><el-image v-if="discardPhotoUrls[photo.fileId]" :src="discardPhotoUrls[photo.fileId]" :preview-src-list="discardPhotoPreviewUrls" :initial-index="discardPhotoPreviewUrls.indexOf(discardPhotoUrls[photo.fileId])" fit="cover" preview-teleported /><div v-else-if="discardPhotoLoading[photo.fileId]" class="warehouse-photo-placeholder">照片加载中…</div><button v-else class="warehouse-photo-placeholder warehouse-photo-retry" type="button" @click="loadDiscardPhoto(photo.fileId)">{{ discardPhotoErrors[photo.fileId] ? '加载失败,点击重试' : '加载照片' }}</button><small>{{ formatDateTime(photo.submittedAt) }}</small></div></div><div v-else class="warehouse-empty" > 暂无已提交的废弃现场照片 < / div >
< h3 > 废弃现场照片 < / h3 > < div v-if = "selectedDetail?.photos?.length" class="warehouse-photo-grid"><div v-for="photo in selectedDetail.photos" :key="photo.fileId" class="warehouse-photo-item"><el-image v-if="discardPhotoUrls[photo.fileId]" :src="discardPhotoUrls[photo.fileId]" :preview-src-list="discardPhotoPreviewUrls" :initial-index="discardPhotoPreviewUrls.indexOf(discardPhotoUrls[photo.fileId])" fit="cover" preview-teleported /><div v-else-if="discardPhotoLoading[photo.fileId]" class="warehouse-photo-placeholder">照片加载中…</div><button v-else class="warehouse-photo-placeholder warehouse-photo-retry" type="button" @click="loadDiscardPhoto(photo.fileId)">{{ discardPhotoErrors[photo.fileId] ? '加载失败,点击重试' : '加载照片' }}</button><small>{{ formatDateTime(photo.submittedAt) }}</small></div></div><div v-else class="warehouse-empty" > 暂无已提交的废弃现场照片 < / div >
< / div >
< / div >
< div v-if = "props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-measurement-card " >
< div v-if = "props.service === 'transfer' && selectedDetail?.status !== 'DRAFT' " class="warehouse-card warehouse-form-section" >
< div class = "warehouse-section-heading" > < h3 > PDA 打托实测数据 < / h3 > < span class = "warehouse-hint" > PDA 作业完成即显示入库完成 , 客户可确认仓库实测数据 < / span > < / div >
< div class = "warehouse-section-heading warehouse-transfer-receipt-heading" > < h3 > 预报箱唛 ( { { selectedDetail ? . packageCount || 0 } } 箱 ) < / h3 > < div class = "warehouse-transfer-receipt-actions" > < el-button v-if = "canIncreaseForecast" type="primary" size="small" @click="openIncreaseForecast"><Plus :size="14" />增加预报箱数</el-button><el-button type="primary" size="small" @click="printTransferCartons(String(selectedDetail?.transferNo || ''))"><Printer :size="14" />打印预报箱唛</el-button><el-button size="small" @click="showForecastCartons = true" > 查看预报箱唛 < / el -button > < / div > < / div >
< div v-if = "selectedDetail?.pallets?.length" class="warehouse-table-scroll"><table><thead><tr><th>托盘号</th><th>库位</th><th>箱数</th><th>长 × 宽 × 高(cm)</th><th>毛重(kg)</th><th>体积(m³)</th><th>状态</th><th>完成时间</th></tr></thead><tbody><tr v-for="pallet in selectedDetail.pallets" :key="String(pallet.id || pallet.sequenceNo)" > < td > { { pallet . palletNo || ` 第 ${ pallet . sequenceNo || '—' } 托 ` } } < / td > < td > { { pallet . warehouseLocationCode || '—' } } < / td > < td > { { pallet . cartonCount ? ? '—' } } < / td > < td > { { [ pallet . lengthCm , pallet . widthCm , pallet . heightCm ] . every ( ( value ) => value != null ) ? ` ${ pallet . lengthCm } × ${ pallet . widthCm } × ${ pallet . heightCm } ` : '—' } } < / td > < td > { { pallet . grossWeightKg ? ? '—' } } < / td > < td > { { pallet . volumeCbm ? ? '—' } } < / td > < td > { { statusLabel ( pallet . status || '' ) } } < / td > < td > { { formatDateTime ( pallet . completedAt ) } } < / td > < / tr > < / tbody > < / table > < / div >
< el-dialog v-model = "showForecastCartons" title="预报箱唛明细" width="900px" align-center destroy-on-close append-to-body><el-table :data="selectedDetail?.cartons || []" size="small" border><el-table-column prop="cartonMark" label="仓库唛头" min-width="300" /><el-table-column label="预报SKU" min-width="240"><template #default="{ row }">{{ selectedDetail?.items?.find(item => String(item.id) === String(row.transferItemId))?.skuCode || '—' }}</template></el-table-column><el-table-column label="清点状态" width="130"><template #default="{ row }">{{ row.status === 'COUNTED' ? '已清点' : row.status === 'NOT_RECEIVED' ? '未收到' : '待清点' }}</template></el-table-column></el-table><template #footer><el-button @click="showForecastCartons = false" > 关闭 < / el -button > < / template > < / el-dialog >
< div v-else class = "warehouse-empty" > 暂无 PDA 打托测量数据 < / div >
< el-dialog v-model = "increaseForecastOpen" title="增加预报箱数" width="560px" align-center destroy-on-close append-to-body :close-on-click-modal="!increasingForecast" :show-close="!increasingForecast" >
< div class = "warehouse-evidence warehouse-measurement-evidence" > < span > 测量打托现场照片 < / span > < div v-if = "selectedDetail?.measurementPhotos?.length" class="warehouse-photo-grid warehouse-measurement-photo-grid"><div v-for="(photo, index) in selectedDetail.measurementPhotos" :key="photo.fileId" class="warehouse-photo-item warehouse-measurement-photo-item" :title="photo.file Name || `测量照片 ${index + 1}`" ><el-image v-if="measurementPhotoUrls[photo.fileId]" :src="measurementPhotoUrls[photo.fileId]" :preview-src-list="measurementPhotoPreviewUrls " :initial-index="measurementPhotoPreviewUrls.indexOf(measurementPhotoUrls[photo.fileId])" :alt="photo.fileName || `测量照片 ${index + 1}`" fit="cover" preview-teleported /><div v-else-if="measurementPhotoLoading[photo.fileId]" class="warehouse-photo-placeholder">照片 {{ index + 1 }} 加载中…</div><button v-else class="warehouse-photo-placeholder warehouse-photo-retry" type="button" @click="loadMeasurementPhoto(photo.fileId)" > {{ measurementPhotoErrors [ photo.fileId ] ? ` 照片 $ { index + 1 } 加载失败 , 点击重试 ` : ` 加载照片 $ { index + 1 } ` }} < / button > < / div > < / div > < strong v-else > 仓库尚未上传测量打托照片 < / strong > < / div >
< el-form label -width = " 110px " > < el-form-item label = "预报SKU" > < el-select v-model = "increaseForecastForm.itemId" :disabled="increasingForecast" style="width:100%"><el-option v-for="item in forecastSnapshot?.items || []" :key="String(item.id)" :value="String(item.id)" :label="item.skuCode || item.product NameZh || '未命名SKU'" /></el-select ></ el-form-item><el-form-item label="新增箱数"><el-input-number v-model="increaseForecastForm.additionalCount" placeholder="请输入新增箱数" :disabled="increasingForecast " :m in="1" :max="Math.max(1, 5000 - Number(forecastSnapshot?.packageCount || 0))" :precision="0" /></el-form-item></el-form><p class="warehouse-form-hint" > 保留原箱唛 , 仅补生成新增箱唛 ; SKU预报件数保持不变 。 < / p >
< template # footer > < el-button :disabled = "increasingForecast" @click ="increaseForecastOpen = false" > 取消 < / el -button > < el-button type = "primary" :loading = "increasingForecast" @click ="saveIncreaseForecast" > 确认增加 < / el -button > < / template >
< / el-dialog >
< / div >
< / div >
< div v-if = "props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-cargo-card" >
< div v-if = "props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-cargo-card" >
< 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-section-heading" > < h3 > 预报与实际签收 < / h3 > < / 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 class = "warehouse-table-scroll" > < table class = "warehouse-edit-table warehouse-transfer-comparison-table" > < colgroup > < col style = "width:500px" / > < col style = "width:140px" / > < col style = "width:130px" / > < col style = "width:100px" / > < col style = "width:130px" / > < col style = "width:130px" / > < / colgroup > < thead > < tr > < th > 商品 SKU < / th > < th > 中文品名 < / th > < th > 预报数量 ( 件 ) < / th > < th > 预计箱数 < / th > < th > 实际签收件数 < / th > < th > 实际签收箱数 < / th > < / tr > < / thead > < tbody > < tr v-for = "(item, index) in selectedDetail?.items || []" :key="String(item.id || index) "><td>{{ item.skuCode || '—' }}</td><td>{{ item.productNameZh || '—' }}</td><td>{{ item.quantity ?? '—' }}</td><td>{{ item.packageCount ?? '—' }}</td><td>{{ receivedItemQuantity(item.id) }}</td><td>{{ receivedItemCartons(item.id).length }}</td></tr><tr v-if="!selectedDetail?.items?.length"><td colspan="6 "><div class="warehouse-empty" > 暂无货物明细 < / div > < / td > < / tr > < / tbody > < / table > < / div >
< details class = "warehouse-transfer-pallet-group" open >
< summary class = "warehouse-section-heading warehouse-transfer-pallet-heading warehouse-transfer-pallet-summary" > < h3 > PDA 打托实测数据 < / h3 > < span class = "warehouse-transfer-pallet-count" > { { selectedDetail ? . pallets ? . length || 0 } } 托 < / span > < span class = "warehouse-transfer-pallet-toggle" > < span class = "when-collapsed" > 展开托盘 < / span > < span class = "when-expanded" > 收起托盘 < / span > < ChevronDown :size = "14" / > < / span > < / summary >
< div v-if = "selectedDetail?.pallets?.length" class="warehouse-transfer-pallet-list" >
< article v-for = "pallet in selectedDetail.pallets" :key="String(pallet.id || pallet.sequenceNo)" class="warehouse-transfer-pallet-card" >
< header class = "warehouse-transfer-pallet-header" >
< strong > < span > 托盘号 < / span > { { pallet . palletNo || ` 第 ${ pallet . sequenceNo || '—' } 托 ` } } < / strong >
< dl class = "warehouse-transfer-pallet-metrics" >
< div > < dt > 箱数 < / dt > < dd > { { palletCartons ( pallet ) . length } } 箱 < / dd > < / div > < div > < dt > 件数 < / dt > < dd > { { palletQuantity ( pallet ) } } 件 < / dd > < / div > < div > < dt > SKU 种数 < / dt > < dd > { { palletSkuRows ( pallet ) . length } } 种 < / dd > < / div > < div > < dt > 毛重 ( kg ) < / dt > < dd > { { pallet . grossWeightKg ? ? '—' } } < / dd > < / div > < div > < dt > 体积 ( m³ ) < / dt > < dd > { { pallet . volumeCbm ? ? '—' } } < / dd > < / div >
< / dl >
< / header >
< div class = "warehouse-transfer-pallet-skus" > < span > SKU 汇总 < / span > < div > < div v-for = "item in palletSkuRows(pallet)" :key="item.sku" class="warehouse-transfer-pallet-sku"><strong>{{ item.sku }}</strong><span>{{ item.quantity }} 件</span></div><span v-if="!palletSkuRows(pallet).length" > 暂无已清点 SKU < / span > < / div > < / div >
< div class = "warehouse-transfer-pallet-meta" > < span > 库位 : { { pallet . warehouseLocationCode || '—' } } < / span > < span > 完成时间 : { { formatDateTime ( pallet . completedAt ) } } < / span > < / div >
< / article >
< / div >
< div v-else class = "warehouse-empty" > 暂无 PDA 打托测量数据 < / div >
< / details >
< div class = "warehouse-evidence warehouse-measurement-evidence" > < span > 测量打托现场照片 < / span > < div v-if = "selectedDetail?.measurementPhotos?.length" class="warehouse-photo-grid warehouse-measurement-photo-grid"><div v-for="(photo, index) in selectedDetail.measurementPhotos" :key="photo.fileId" class="warehouse-photo-item warehouse-measurement-photo-item" :title="photo.fileName || `测量照片 ${index + 1}`"><el-image v-if="measurementPhotoUrls[photo.fileId]" :src="measurementPhotoUrls[photo.fileId]" :preview-src-list="measurementPhotoPreviewUrls" :initial-index="measurementPhotoPreviewUrls.indexOf(measurementPhotoUrls[photo.fileId])" :alt="photo.fileName || `测量照片 ${index + 1}`" fit="cover" preview-teleported /><div v-else-if="measurementPhotoLoading[photo.fileId]" class="warehouse-photo-placeholder">照片 {{ index + 1 }} 加载中…</div><button v-else class="warehouse-photo-placeholder warehouse-photo-retry" type="button" @click="loadMeasurementPhoto(photo.fileId)" > {{ measurementPhotoErrors [ photo.fileId ] ? ` 照片 $ { index + 1 } 加载失败 , 点击重试 ` : ` 加载照片 $ { index + 1 } ` }} < / button > < / div > < / div > < strong v-else > 仓库尚未上传测量打托照片 < / strong > < / div >
< / div >
< div v-if = "props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-received-card" >
< div class = "warehouse-section-heading warehouse-transfer-receipt-heading" > < h3 > 签收箱号明细 ( { { countedTransferCartons ( ) . length } } 箱 ) < / h3 > < div class = "warehouse-transfer-receipt-actions" > < el-button type = "primary" size = "small" :disabled = "!countedTransferCartons().length" @click ="showTransferCartons = true" > 查看箱号明细 < / el -button > < / div > < / div >
< div v-if = "!countedTransferCartons().length" class="warehouse-empty" > 暂无实际签收箱 , 等待 PDA 清点 。 < / div >
< el-dialog v-model = "showTransferCartons" title="签收箱号明细" width="1120px" align -center destroy -on -close append -to -body >
< div class = "warehouse-cartons-dialog-body" >
< el-collapse >
< el-collapse-item v-for = "carton in countedTransferCartons()" :key="carton.cartonMark" :name="carton.cartonMark" >
< template # title > < strong > { { carton . cartonMark } } < / strong > < span class = "warehouse-carton-summary" > 已确认 · { { transferCartonLines ( carton ) . length } } 种 SKU · { { carton . quantity ? ? '—' } } 件 < / span > < / template >
< div class = "warehouse-carton-measurements" > 所属托盘 : { { selectedDetail ? . pallets ? . find ( ( pallet ) => carton . transferPalletId != null && String ( pallet . id ) === String ( carton . transferPalletId ) ) ? . palletNo || '散箱' } } · 清点时间 : { { formatDateTime ( carton . countedAt ) } } < / div >
< div class = "warehouse-carton-measurements" > 长 { { carton . length ? ? '—' } } cm · 宽 { { carton . width ? ? '—' } } cm · 高 { { carton . height ? ? '—' } } cm · 重量 { { carton . weight ? ? '—' } } kg · 体积 { { [ carton . length , carton . width , carton . height ] . every ( value => value != null ) ? ( Number ( carton . length ) * Number ( carton . width ) * Number ( carton . height ) / 1000000 ) . toFixed ( 6 ) : '—' } } m³ < / div >
< el-table :data = "transferCartonLines(carton)" size = "small" border >
< el-table-column prop = "skuCode" label = "SKU" min -width = " 220 " / >
< el-table-column prop = "productNameZh" label = "中文品名" min -width = " 180 " / >
< el-table-column prop = "quantity" label = "实际签收数量" width = "130" / >
< / el-table >
< p v-if = "carton.discrepancyReason" class="warehouse-carton-measurements" > 差异说明 : {{ carton.discrepancyReason }} < / p >
< / el-collapse-item >
< / el-collapse >
< / div >
< template # footer > < el-button @click ="showTransferCartons = false" > 关闭 < / el -button > < / template >
< / el-dialog >
< / div >
< / div >
< div v-if = "props.service === 'transfer' && selectedDetail?.needRelabel" class="warehouse-card warehouse-form-section" >
< div v-if = "props.service === 'transfer' && selectedDetail?.needRelabel" class="warehouse-card warehouse-form-section" >
< h3 > 换标箱级去向 < / h3 >
< 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 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 >
< div v-if = "props.service === 'labeling' && canManageRelabelFiles" class="warehouse-card warehouse-form-section" >
< div v-if = "props.service === 'labeling' && canManageRelabelFiles" class="warehouse-card warehouse-form-section" >
< div class = "warehouse-section-heading" > < h3 > 换标完成与出库 < / h3 > < / div >
< div class = "warehouse-section-heading" > < h3 > 换标完成与出库 < / h3 > < / div >
@@ -1725,6 +1914,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< / div >
< / div >
< / template >
< / template >
< / section >
< / section >
< CompletionConfirmationDialog v-model = "completionConfirmOpen" :kind="completionConfirmKind" :document-no="completionConfirmNo" @confirmed="refreshAfterCompletion" / >
< / template >
< / template >
< style scoped >
< style scoped >
@@ -1868,7 +2058,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
. warehouse - native - file { min - height : 32 px ; padding : 5 px 8 px ; border : 1 px solid # dcdfe6 ; border - radius : 4 px ; background : # fff ; color : # 606266 } . warehouse - fulfillment - grid { margin - bottom : 18 px } . warehouse - evidence { display : flex ; align - items : flex - start ; gap : 16 px ; padding : 14 px ; border : 1 px solid # e7ebf1 ; border - radius : 4 px ; background : # f8fafc } . warehouse - evidence > span { padding - top : 6 px ; color : # 71809 a ; font - size : 12 px ; white - space : nowrap } . warehouse - evidence > strong { padding - top : 6 px ; color : # 8 a95a5 ; font - size : 12 px ; font - weight : 400 } . warehouse - photo - grid { display : grid ; grid - template - columns : repeat ( 4 , 140 px ) ; gap : 12 px } . warehouse - photo - item { width : 140 px ; height : 105 px ; overflow : hidden ; border : 1 px solid # dce3ed ; border - radius : 5 px ; background : # fff } . warehouse - photo - item : deep ( . el - image ) { display : block ; width : 100 % ; height : 100 % ; cursor : zoom - in } . warehouse - photo - placeholder { display : flex ; width : 100 % ; height : 100 % ; align - items : center ; justify - content : center ; padding : 10 px ; border : 0 ; background : # f4f7fb ; color : # 8792 a2 ; font - size : 12 px ; text - align : center } . warehouse - photo - retry { cursor : pointer } . warehouse - completion - remark { margin : 10 px 0 0 ; color : # 596579 ; font - size : 12 px }
. warehouse - native - file { min - height : 32 px ; padding : 5 px 8 px ; border : 1 px solid # dcdfe6 ; border - radius : 4 px ; background : # fff ; color : # 606266 } . warehouse - fulfillment - grid { margin - bottom : 18 px } . warehouse - evidence { display : flex ; align - items : flex - start ; gap : 16 px ; padding : 14 px ; border : 1 px solid # e7ebf1 ; border - radius : 4 px ; background : # f8fafc } . warehouse - evidence > span { padding - top : 6 px ; color : # 71809 a ; font - size : 12 px ; white - space : nowrap } . warehouse - evidence > strong { padding - top : 6 px ; color : # 8 a95a5 ; font - size : 12 px ; font - weight : 400 } . warehouse - photo - grid { display : grid ; grid - template - columns : repeat ( 4 , 140 px ) ; gap : 12 px } . warehouse - photo - item { width : 140 px ; height : 105 px ; overflow : hidden ; border : 1 px solid # dce3ed ; border - radius : 5 px ; background : # fff } . warehouse - photo - item : deep ( . el - image ) { display : block ; width : 100 % ; height : 100 % ; cursor : zoom - in } . warehouse - photo - placeholder { display : flex ; width : 100 % ; height : 100 % ; align - items : center ; justify - content : center ; padding : 10 px ; border : 0 ; background : # f4f7fb ; color : # 8792 a2 ; font - size : 12 px ; text - align : center } . warehouse - photo - retry { cursor : pointer } . warehouse - completion - remark { margin : 10 px 0 0 ; color : # 596579 ; font - size : 12 px }
. warehouse - measurement - evidence { margin - top : 16 px }
. warehouse - measurement - evidence { margin - top : 16 px }
. warehouse - measurement - photo - grid { flex : 1 ; grid - template - columns : repeat ( auto - fill , minmax ( 240 px , 1 fr ) ) }
. warehouse - measurement - photo - grid { flex : 1 ; grid - template - columns : repeat ( auto - fill , minmax ( 240 px , 1 fr ) ) }
. warehouse - measurement - photo - item { width : 100 % ; height : 180 px }
. warehouse - measurement - photo - item { width : 100 % ; height : 120 px }
. warehouse - template - actions { flex - wrap : wrap ; justify - content : flex - end } . warehouse - import - preview { margin : 0 0 16 px ; padding : 14 px ; border : 1 px solid # cfe0f5 ; border - radius : 5 px ; background : # f8fbff } . warehouse - import - preview . warehouse - section - heading { margin - bottom : 10 px } . warehouse - import - preview . warehouse - section - heading > div { display : flex ; align - items : center ; gap : 10 px } . warehouse - import - preview p { margin : 0 ; color : # 7 d8797 ; font - size : 12 px } . warehouse - binding - preview - table { min - width : 980 px ; background : # fff }
. warehouse - template - actions { flex - wrap : wrap ; justify - content : flex - end } . warehouse - import - preview { margin : 0 0 16 px ; padding : 14 px ; border : 1 px solid # cfe0f5 ; border - radius : 5 px ; background : # f8fbff } . warehouse - import - preview . warehouse - section - heading { margin - bottom : 10 px } . warehouse - import - preview . warehouse - section - heading > div { display : flex ; align - items : center ; gap : 10 px } . warehouse - import - preview p { margin : 0 ; color : # 7 d8797 ; font - size : 12 px } . warehouse - binding - preview - table { min - width : 980 px ; background : # fff }
. warehouse - transfer - relabel . warehouse - labeling - heading > div : first - child { min - width : 0 } . warehouse - transfer - relabel . warehouse - labeling - heading h3 { margin - bottom : 6 px } . warehouse - transfer - relabel . warehouse - labeling - heading p { margin : 0 0 16 px ; color : # 7 d8797 ; font - size : 12 px } . warehouse - transfer - relabel - fields { margin - bottom : 18 px } . warehouse - transfer - file - groups { display : grid ; grid - template - columns : minmax ( 0 , 1.15 fr ) minmax ( 360 px , .85 fr ) ; align - items : stretch ; gap : 14 px ; margin - bottom : 14 px } . warehouse - transfer - total - files , . warehouse - transfer - template - group { height : 100 % ; margin : 0 } . warehouse - transfer - mark - table { min - width : 1420 px } . warehouse - transfer - mark - table td { vertical - align : top } . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 220 px } . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 150 px } . warehouse - count - error { color : # d64545 }
. warehouse - transfer - relabel . warehouse - labeling - heading > div : first - child { min - width : 0 } . warehouse - transfer - relabel . warehouse - labeling - heading h3 { margin - bottom : 6 px } . warehouse - transfer - relabel . warehouse - labeling - heading p { margin : 0 0 16 px ; color : # 7 d8797 ; font - size : 12 px } . warehouse - transfer - relabel - fields { margin - bottom : 18 px } . warehouse - transfer - file - groups { display : grid ; grid - template - columns : minmax ( 0 , 1.15 fr ) minmax ( 360 px , .85 fr ) ; align - items : stretch ; gap : 14 px ; margin - bottom : 14 px } . warehouse - transfer - total - files , . warehouse - transfer - template - group { height : 100 % ; margin : 0 } . warehouse - transfer - mark - table { min - width : 1420 px } . warehouse - transfer - mark - table td { vertical - align : top } . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 220 px } . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 150 px } . warehouse - count - error { color : # d64545 }
. warehouse - transfer - form { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; gap : 12 px } . warehouse - transfer - form -- relabel { grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1.35 fr ) ; align - items : start } . warehouse - transfer - form -- relabel . warehouse - transfer - main - card { grid - column : 1 ; grid - row : 1 } . warehouse - transfer - form -- relabel . warehouse - transfer - cargo - card { grid - column : 1 ; grid - row : 2 } . warehouse - transfer - form -- relabel . warehouse - transfer - relabel { grid - column : 2 ; grid - row : 1 / span 2 ; margin - bottom : 0 } . warehouse - transfer - form -- relabel . warehouse - transfer - submit - bar { grid - column : 1 / - 1 } . warehouse - transfer - form : deep ( . el - input _ _wrapper ) , . warehouse - transfer - form : deep ( . el - textarea _ _inner ) , . warehouse - transfer - form : deep ( . el - select _ _wrapper ) , . warehouse - transfer - form : deep ( . el - date - editor . el - input _ _wrapper ) { min - height : 32 px } . warehouse - transfer - form . warehouse - form - section { padding : 16 px } . warehouse - transfer - form . warehouse - form - section h3 { margin - bottom : 12 px ; padding - bottom : 9 px ; font - size : 14 px } . warehouse - transfer - form . warehouse - form - grid { gap : 12 px 16 px } . warehouse - transfer - form . warehouse - choice - field { min - height : 34 px ; padding : 8 px 10 px ; grid - template - columns : 118 px minmax ( 0 , 1 fr ) ; gap : 10 px } . warehouse - transfer - form . warehouse - choice - field > span { font - size : 12 px } . warehouse - transfer - form . warehouse - receiving - card { gap : 12 px ; padding : 10 px 12 px } . warehouse - transfer - form . warehouse - section - heading { margin - bottom : 8 px } . warehouse - transfer - form . warehouse - edit - table { font - size : 11 px } . warehouse - transfer - form . warehouse - edit - table th { height : 36 px ; padding : 0 9 px } . warehouse - transfer - form . warehouse - edit - table td { height : 42 px ; padding : 6 px 7 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 150 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 112 px } . warehouse - transfer - form . warehouse - hint { margin - top : 5 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - labeling - mode { gap : 9 px ; margin - bottom : 10 px ; padding : 9 px 10 px } . warehouse - transfer - form . warehouse - labeling - mode > span { font - size : 12 px } . warehouse - transfer - form . warehouse - labeling - mode : deep ( . el - radio - button _ _inner ) { padding : 6 px 9 px } . warehouse - transfer - form . warehouse - template - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 5 px ; padding - bottom : 8 px } . warehouse - transfer - form . warehouse - template - actions : deep ( . el - button ) { padding : 6 px 8 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - submit - bar { padding : 4 px 0 18 px } . warehouse - transfer - form . warehouse - submit - bar : deep ( . el - button ) { min - height : 32 px ; padding : 7 px 14 px } . warehouse - transfer - form . warehouse - transfer - relabel { overflow : hidden } . warehouse - transfer - form . warehouse - transfer - relabel - fields { margin - bottom : 10 px } . warehouse - step - heading { align - items : flex - start } . warehouse - step - heading h3 { display : flex ; align - items : center ; gap : 9 px } . warehouse - step - number { display : inline - grid ; flex : 0 0 auto ; width : 24 px ; height : 24 px ; border - radius : 50 % ; background : # 3478 c8 ; color : # fff ; font - size : 12 px ; place - items : center } . warehouse - heading - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 8 px } . warehouse - transfer - template - group { margin : 0 0 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # fbfcfe } . warehouse - binding - heading { margin - top : 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # f8faff } . warehouse - binding - heading strong { display : flex ; align - items : center ; gap : 8 px ; color : # 34445 c ; font - size : 13 px }
. warehouse - transfer - form { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; gap : 12 px } . warehouse - transfer - form -- relabel { grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1.35 fr ) ; align - items : start } . warehouse - transfer - form -- relabel . warehouse - transfer - main - card { grid - column : 1 ; grid - row : 1 } . warehouse - transfer - form -- relabel . warehouse - transfer - cargo - card { grid - column : 1 ; grid - row : 2 } . warehouse - transfer - form -- relabel . warehouse - transfer - relabel { grid - column : 2 ; grid - row : 1 / span 2 ; margin - bottom : 0 } . warehouse - transfer - form -- relabel . warehouse - transfer - submit - bar { grid - column : 1 / - 1 } . warehouse - transfer - form : deep ( . el - input _ _wrapper ) , . warehouse - transfer - form : deep ( . el - textarea _ _inner ) , . warehouse - transfer - form : deep ( . el - select _ _wrapper ) , . warehouse - transfer - form : deep ( . el - date - editor . el - input _ _wrapper ) { min - height : 32 px } . warehouse - transfer - form . warehouse - form - section { padding : 16 px } . warehouse - transfer - form . warehouse - form - section h3 { margin - bottom : 12 px ; padding - bottom : 9 px ; font - size : 14 px } . warehouse - transfer - form . warehouse - form - grid { gap : 12 px 16 px } . warehouse - transfer - form . warehouse - choice - field { min - height : 34 px ; padding : 8 px 10 px ; grid - template - columns : 118 px minmax ( 0 , 1 fr ) ; gap : 10 px } . warehouse - transfer - form . warehouse - choice - field > span { font - size : 12 px } . warehouse - transfer - form . warehouse - receiving - card { gap : 12 px ; padding : 10 px 12 px } . warehouse - transfer - form . warehouse - section - heading { margin - bottom : 8 px } . warehouse - transfer - form . warehouse - edit - table { font - size : 11 px } . warehouse - transfer - form . warehouse - edit - table th { height : 36 px ; padding : 0 9 px } . warehouse - transfer - form . warehouse - edit - table td { height : 42 px ; padding : 6 px 7 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 150 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 112 px } . warehouse - transfer - form . warehouse - hint { margin - top : 5 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - labeling - mode { gap : 9 px ; margin - bottom : 10 px ; padding : 9 px 10 px } . warehouse - transfer - form . warehouse - labeling - mode > span { font - size : 12 px } . warehouse - transfer - form . warehouse - labeling - mode : deep ( . el - radio - button _ _inner ) { padding : 6 px 9 px } . warehouse - transfer - form . warehouse - template - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 5 px ; padding - bottom : 8 px } . warehouse - transfer - form . warehouse - template - actions : deep ( . el - button ) { padding : 6 px 8 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - submit - bar { padding : 4 px 0 18 px } . warehouse - transfer - form . warehouse - submit - bar : deep ( . el - button ) { min - height : 32 px ; padding : 7 px 14 px } . warehouse - transfer - form . warehouse - transfer - relabel { overflow : hidden } . warehouse - transfer - form . warehouse - transfer - relabel - fields { margin - bottom : 10 px } . warehouse - step - heading { align - items : flex - start } . warehouse - step - heading h3 { display : flex ; align - items : center ; gap : 9 px } . warehouse - step - number { display : inline - grid ; flex : 0 0 auto ; width : 24 px ; height : 24 px ; border - radius : 50 % ; background : # 3478 c8 ; color : # fff ; font - size : 12 px ; place - items : center } . warehouse - heading - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 8 px } . warehouse - transfer - template - group { margin : 0 0 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # fbfcfe } . warehouse - binding - heading { margin - top : 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # f8faff } . warehouse - binding - heading strong { display : flex ; align - items : center ; gap : 8 px ; color : # 34445 c ; font - size : 13 px }
@@ -1881,6 +2071,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
. warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 6 px 28 px ; padding : 10 px 16 px 12 px } . warehouse - service - page -- detail . warehouse - transfer - overview - grid > div { min - height : 26 px ; padding : 2 px 0 ; border : 0 } . warehouse - service - page -- detail . warehouse - transfer - overview - grid span { flex : 0 0 auto ; min - width : 76 px } . warehouse - service - page -- detail . warehouse - transfer - overview - grid : deep ( . el - tag ) { height : 24 px }
. warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 6 px 28 px ; padding : 10 px 16 px 12 px } . warehouse - service - page -- detail . warehouse - transfer - overview - grid > div { min - height : 26 px ; padding : 2 px 0 ; border : 0 } . warehouse - service - page -- detail . warehouse - transfer - overview - grid span { flex : 0 0 auto ; min - width : 76 px } . warehouse - service - page -- detail . warehouse - transfer - overview - grid : deep ( . el - tag ) { height : 24 px }
. warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid { grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 6 px 28 px ; padding : 10 px 16 px 12 px } . warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid > div { min - height : 26 px ; padding : 2 px 0 ; border : 0 } . warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid span { flex : 0 0 auto ; min - width : 76 px }
. warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid { grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 6 px 28 px ; padding : 10 px 16 px 12 px } . warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid > div { min - height : 26 px ; padding : 2 px 0 ; border : 0 } . warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - detail - grid span { flex : 0 0 auto ; min - width : 76 px }
. warehouse - service - page -- detail . warehouse - transfer - overview - grid > . warehouse - overview - wide { grid - column : span 2 }
. warehouse - service - page -- detail . warehouse - transfer - overview - grid > . warehouse - overview - wide { grid - column : span 2 }
. warehouse - transfer - received - card { margin - top : 14 px } . warehouse - cartons - dialog - body { max - height : 65 vh ; overflow : auto } . warehouse - carton - summary { margin - left : 16 px ; color : var ( -- muted ) ; font - size : 12 px } . warehouse - carton - measurements { margin : 10 px 0 ; color : var ( -- muted ) ; font - size : 13 px ; line - height : 1.8 }
@ media ( max - width : 1100 px ) { . warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : repeat ( 2 , minmax ( 180 px , 1 fr ) ) } } @ media ( max - width : 720 px ) { . warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : 1 fr ; gap : 4 px ; padding : 10 px 14 px } }
@ media ( max - width : 1100 px ) { . warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : repeat ( 2 , minmax ( 180 px , 1 fr ) ) } } @ media ( max - width : 720 px ) { . warehouse - service - page -- detail . warehouse - transfer - overview - grid { grid - template - columns : 1 fr ; gap : 4 px ; padding : 10 px 14 px } }
/* Keep the count and controls visible together on narrow list pages. */
/* Keep the count and controls visible together on narrow list pages. */
. warehouse - pagination { justify - content : flex - end ; }
. warehouse - pagination { justify - content : flex - end ; }
@@ -1896,4 +2087,55 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
. warehouse - transfer - cargo - card . warehouse - section - heading , . warehouse - transfer - cargo - card . warehouse - table - scroll { max - width : none ; }
. warehouse - transfer - cargo - card . warehouse - section - heading , . warehouse - transfer - cargo - card . warehouse - table - scroll { max - width : none ; }
. warehouse - transfer - form . warehouse - choice - field -- relabel { grid - template - columns : 1 fr ; align - items : start ; }
. warehouse - transfer - form . warehouse - choice - field -- relabel { grid - template - columns : 1 fr ; align - items : start ; }
}
}
< / style >
< style scoped >
. warehouse - discard - boxes { margin - bottom : 24 px ; }
. warehouse - discard - box - toolbar { display : flex ; align - items : center ; gap : 20 px ; margin - bottom : 16 px ; }
. warehouse - discard - box - toolbar . el - input { max - width : 340 px ; }
. warehouse - service - page -- detail . warehouse - table - scroll . warehouse - transfer - comparison - table { width : 1130 px ; min - width : 1130 px ; table - layout : fixed }
. warehouse - transfer - comparison - table td { white - space : normal ; overflow - wrap : anywhere }
. warehouse - service - page -- detail . warehouse - transfer - comparison - table th { height : 36 px ; padding : 0 10 px }
. warehouse - service - page -- detail . warehouse - transfer - comparison - table td { height : 38 px ; padding : 7 px 10 px }
. warehouse - transfer - comparison - table th : nth - child ( n + 3 ) , . warehouse - transfer - comparison - table td : nth - child ( n + 3 ) { text - align : center }
. warehouse - transfer - comparison - table td : first - child { white - space : nowrap ; overflow : hidden ; text - overflow : ellipsis }
. warehouse - service - page -- detail . warehouse - transfer - cargo - card . warehouse - transfer - pallet - heading { margin - top : 12 px }
. warehouse - transfer - pallet - list { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; gap : 12 px ; padding : 0 14 px ; margin - bottom : 12 px ; align - items : start }
. warehouse - transfer - pallet - card { padding : 14 px ; border : 1 px solid var ( -- client - border ) ; border - radius : 10 px ; background : # fff ; min - width : 0 }
. warehouse - transfer - pallet - header { display : flex ; align - items : center ; flex - wrap : wrap ; gap : 10 px 20 px }
. warehouse - transfer - pallet - header > strong { font - size : 13 px ; color : var ( -- navy ) ; overflow - wrap : anywhere ; line - height : 1.6 }
. warehouse - transfer - pallet - header strong > span { display : inline - block ; margin - right : 8 px ; padding : 2 px 7 px ; border - radius : 4 px ; background : # eef1ff ; color : var ( -- primary ) ; font - size : 12 px ; font - weight : 500 }
. warehouse - transfer - pallet - metrics { display : flex ; flex - wrap : wrap ; gap : 8 px ; margin : 0 }
. warehouse - transfer - pallet - metrics > div { display : flex ; align - items : baseline ; gap : 8 px ; padding : 6 px 10 px ; background : # f5f7fc ; border : 1 px solid # e9edf5 ; border - radius : 6 px }
. warehouse - transfer - pallet - metrics dt { font - size : 12 px ; color : var ( -- muted ) }
. warehouse - transfer - pallet - metrics dd { margin : 0 ; font - size : 13 px ; font - weight : 600 ; color : var ( -- navy ) }
. warehouse - transfer - pallet - summary { cursor : pointer ; list - style : none ; border - radius : 4 px }
. warehouse - transfer - pallet - summary : : - webkit - details - marker { display : none }
. warehouse - transfer - pallet - summary : focus - visible { outline : 2 px solid var ( -- primary ) ; outline - offset : 4 px }
. warehouse - transfer - pallet - toggle { display : flex ; align - items : center ; gap : 8 px ; flex - shrink : 0 ; margin - left : 6 px }
. warehouse - transfer - pallet - count { font - size : 12 px ; color : var ( -- muted ) }
. warehouse - service - page -- detail . warehouse - service - page -- transfer > . warehouse - detail - header { grid - template - columns : auto minmax ( 0 , max - content ) auto ; justify - content : start ; column - gap : 14 px }
. warehouse - service - page -- detail . warehouse - transfer - pallet - heading h3 { flex : 0 0 auto }
. warehouse - service - page -- detail . warehouse - transfer - pallet - heading . warehouse - transfer - pallet - count { flex : 0 0 auto }
. warehouse - service - page -- detail . warehouse - transfer - cargo - card . warehouse - transfer - pallet - heading { display : flex ; align - items : center ; justify - content : flex - start ; gap : 10 px }
. warehouse - transfer - pallet - toggle { color : var ( -- primary ) ; font - size : 12 px ; gap : 4 px }
. warehouse - transfer - pallet - group . when - expanded { display : none }
. warehouse - transfer - pallet - group [ open ] . when - expanded { display : inline }
. warehouse - transfer - pallet - group [ open ] . when - collapsed { display : none }
. warehouse - transfer - pallet - group [ open ] . warehouse - transfer - pallet - toggle svg { transform : rotate ( 180 deg ) }
. warehouse - transfer - pallet - meta { display : flex ; flex - wrap : wrap ; gap : 4 px 20 px ; margin - top : 8 px ; color : var ( -- muted ) ; font - size : 12 px ; line - height : 1.7 ; overflow - wrap : anywhere }
. warehouse - transfer - pallet - skus { display : grid ; grid - template - columns : 64 px minmax ( 0 , 1 fr ) ; align - items : start ; gap : 10 px ; margin - top : 10 px ; padding - top : 10 px ; border - top : 1 px solid var ( -- client - border ) }
. warehouse - transfer - pallet - skus > span { font - size : 12 px ; color : var ( -- muted ) ; padding - top : 5 px }
. warehouse - transfer - pallet - skus > div { display : flex ; flex - wrap : wrap ; gap : 6 px ; min - width : 0 }
. warehouse - transfer - pallet - sku { display : inline - flex ; align - items : center ; gap : 8 px ; max - width : 100 % ; box - sizing : border - box ; padding : 4 px 8 px ; background : # f5f7ff ; border : 1 px solid # e5e9f8 ; border - radius : 5 px ; font - size : 12 px ; line - height : 1.6 }
. warehouse - transfer - pallet - sku strong { font - weight : 500 ; overflow - wrap : anywhere ; min - width : 0 ; color : var ( -- navy ) }
. warehouse - transfer - pallet - sku > span { flex - shrink : 0 ; padding : 0 7 px ; border : 1 px solid # e0e6ff ; border - radius : 4 px ; background : # eef1ff ; color : var ( -- primary ) ; font - weight : 600 ; white - space : nowrap }
. warehouse - service - page -- detail . warehouse - transfer - received - card . warehouse - transfer - receipt - heading { justify - content : flex - start ; flex - wrap : wrap ; gap : 10 px 16 px }
. warehouse - service - page -- detail . warehouse - transfer - receipt - heading h3 { flex : 0 0 auto ; margin : 0 ; padding : 0 ; border : 0 }
. warehouse - transfer - receipt - actions { display : flex ; flex - wrap : wrap ; gap : 8 px } . warehouse - transfer - receipt - actions : deep ( . el - button + . el - button ) { margin - left : 0 }
. warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - transfer - overview - grid { width : 100 % ; max - width : 1440 px ; box - sizing : border - box ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; column - gap : 18 px ; row - gap : 4 px ; padding : 8 px 14 px 10 px }
. warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - transfer - overview - grid > div { min - height : 24 px ; gap : 8 px ; padding : 1 px 0 }
. warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card . warehouse - transfer - overview - grid > div > span { flex : 0 0 84 px ; min - width : 0 }
< / style >
< / style >