@@ -1,17 +1,18 @@
< script setup lang = "ts" >
import { computed , nextTick , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { ElMessage , ElMessageBox } from 'element-plus'
import { ArrowLeft , Download , Eye , Plus , Printer , Search , Trash2 , Upload } from '@lucide/vue'
import { ArrowLeft , Download , Eye , Plus , Printer , Search , Trash2 , Upload , X } from '@lucide/vue'
import SingleLineText from '@/components/common/SingleLineText.vue'
import {
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer ,
getTransferCartonLabel ,
confirmRelabelCompletion , confirmTransferMeasurement , downloadRelabelCompletionPhoto , uploadRelabelShippingLabel ,
getTransferCartonLabel , submitTransfer , updateTransfer ,
confirmRelabelCompletion , confirmTransferMeasurement , downloadRelabelCompletionPhoto , downloadTransferMeasurementPhoto , uploadRelabelShippingLabel ,
uploadRelabelFile ,
listDiscardCandidates , listDiscards , listRelabelInventoryCandidates , listRelabels , listTransfers , cancelDiscard ,
type RelabelInventoryCandidate ,
type RelabelCreate ,
type RelabelDetail ,
type TransferCreate ,
} from '../api/warehouseServices'
import {
getMerchantWarehouseDetail ,
@@ -22,7 +23,7 @@ import {
import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination'
type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'detail'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
type RelabelTemplateType = 'carton' | 'sku' | 'mixed'
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
@@ -43,11 +44,19 @@ const statusOptions = computed(() => props.service === 'labeling'
? [ { label : '待处理' , value : 'PENDING_PROCESSING' } , { label : '处理中' , value : 'PROCESSING' } , { label : '换标完成' , value : 'COMPLETED' } ]
: 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 : '已通过' , value : 'APPROVED' } , { label : '待分配' , value : 'PENDING_ASSIGNMENT' } , { label : '已分配' , value : 'ALLOCATED' } , { label : '已出库' , value : 'OUTBOUNDED' } , { label : '部分收货' , value : 'PARTIALLY_RECEIVED' } , { label : '已收货' , value : 'RECEIVED' } , { label : '已拒绝' , value : 'REJECTED' } ] )
: props . service === 'transfer'
? [ { label : '草稿' , value : 'DRAFT' } , { label : '待审核' , value : 'PENDING_REVIEW' } ,
{ label : '待PDA收货' , value : 'WAIT_PDA_RECEIVING' } , { label : '入库测量中' , value : 'INBOUND_MEASURING' } ,
{ label : '待客户确认实测' , value : 'WAIT_CUSTOMER_CONFIRMATION' } , { label : '待换标' , value : 'WAIT_RELABEL' } ,
{ label : '待确认目的地' , value : 'WAIT_FBA_DESTINATION' } , { label : '待排车' , value : 'READY_FOR_DISPATCH' } ,
{ label : '待出库' , value : 'WAIT_OUTBOUND' } , { label : '已出库' , value : 'OUTBOUNDED' } ,
{ label : '部分收货' , value : 'PARTIALLY_RECEIVED' } , { label : '已收货' , value : 'RECEIVED' } ,
{ label : '审核驳回' , value : 'REJECTED' } ]
: [ { label : '待审核' , value : 'PENDING_REVIEW' } , { label : '已通过' , value : 'APPROVED' } , { label : '待分配' , value : 'PENDING_ASSIGNMENT' } , { label : '已分配' , value : 'ALLOCATED' } , { label : '已出库' , value : 'OUTBOUNDED' } , { label : '部分收货' , value : 'PARTIALLY_RECEIVED' } , { label : '已收货' , value : 'RECEIVED' } , { label : '已拒绝' , value : 'REJECTED' } ] )
const mode = ref < PageMode > ( 'list' )
const filters = reactive ( { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' } )
const form = reactive ( { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , address : '' , contact : '' , phone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
const transferHasFbaDestination = ref ( false )
const form = reactive ( { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , privateDestinationAddress : '' , privateDestinationPostalCode : '' , privateDestinationContact : '' , privateDestinationPhone : '' , address : '' , contact : '' , phone : '' , pickupContact : '' , pickupPhone : '' , relabelContact : '' , relabelPhone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
const transferDestinationType = ref < 'NONE' | 'FBA' | 'PRIVATE' > ( 'NONE' )
const selectedOperationWarehouse = ref < MerchantWarehouseDetail | null > ( null )
const operationWarehouseDetailLoading = ref ( false )
const operationWarehouseAddress = computed ( ( ) => {
@@ -72,7 +81,22 @@ const submittedRows = ref<Record<string, string>[]>([])
const page = ref ( 1 )
const pageSize = ref ( 15 )
const total = ref ( 0 )
interface ServiceDetail extends Record < string , unknown > { transferNo ? : string ; status ? : string ; estimatedShipAt ? : string ; actualShipAt ? : string ; cartons ? : TransferCarton [ ] }
const editingTransferNo = ref ( '' )
interface TransferPallet { id ? : string | number ; sequenceNo ? : number ; palletNo ? : string ; warehouseLocationCode ? : string ; cartonCount ? : number ; lengthCm ? : number ; widthCm ? : number ; heightCm ? : number ; grossWeightKg ? : number ; volumeCbm ? : number ; status ? : string ; completedAt ? : string }
interface TransferMeasurementPhoto { fileId : string | number ; fileName ? : string ; contentType ? : string ; sizeBytes ? : number ; uploadedAt ? : string }
interface ServiceDetail extends Record < string , unknown > {
transferNo ? : string ; status ? : string ; operatingWarehouseId ? : string ; fbaWarehouseCode ? : string
destinationType ? : 'FBA' | 'PRIVATE' ; destinationWarehouseCode ? : string ; destinationWarehouseName ? : string
privateDestinationAddress ? : string ; privateDestinationPostalCode ? : string ; privateDestinationContactName ? : string ; privateDestinationContactPhone ? : string
deliveryAddress ? : string ; pickupContactName ? : string ; pickupContactPhone ? : string
packageCount ? : number ; volumeCbm ? : number ; deliveryMethod ? : string ; estimatedArrivalAt ? : string
estimatedShipAt ? : string ; actualShipAt ? : string ; fbaShipmentNo ? : string ; trackingNo ? : string
remark ? : string ; needRelabel ? : boolean ; labelDeadline ? : string ; relabelContactName ? : string
relabelContactPhone ? : string ; relabelDeliveryMethod ? : string
items ? : Array < { skuCode ? : string ; productNameZh ? : string ; packageCount ? : number ; shippingMark ? : 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
}
const selectedDetail = ref < ServiceDetail | null > ( null )
const selectedRelabelDetail = ref < RelabelDetail | null > ( null )
const completionPhotoUrls = reactive < Record < string , string > > ( { } )
@@ -80,6 +104,11 @@ const completionPhotoLoading = reactive<Record<string, boolean>>({})
const completionPhotoErrors = reactive < Record < string , boolean > > ( { } )
const completionPhotoPreviewUrls = computed ( ( ) => ( selectedRelabelDetail . value ? . completionEvidence ? . photoFileIds || [ ] )
. map ( ( fileId ) => completionPhotoUrls [ fileId ] ) . filter ( Boolean ) )
const measurementPhotoUrls = reactive < Record < string , string > > ( { } )
const measurementPhotoLoading = reactive < Record < string , boolean > > ( { } )
const measurementPhotoErrors = reactive < Record < string , boolean > > ( { } )
const measurementPhotoPreviewUrls = computed ( ( ) => ( selectedDetail . value ? . measurementPhotos || [ ] )
. map ( ( photo ) => measurementPhotoUrls [ photo . fileId ] ) . filter ( Boolean ) )
const fbaWarehouses = ref < MerchantWarehouse [ ] > ( [ ] )
const operationWarehouses = ref < MerchantWarehouse [ ] > ( [ ] )
const fbaWarehousesLoading = ref ( false )
@@ -123,7 +152,7 @@ let cargoSequence = 0
let skuRelabelSequence = 0
watch ( ( ) => props . service , ( ) => { mode . value = 'list' ; page . value = 1 ; resetFilters ( ) ; resetForm ( ) ; void loadPage ( ) } )
watch ( mode , ( value ) => { if ( value !== 'detail' ) clearCompletionPhotos ( ) } )
watch ( mode , ( value ) => { if ( value !== 'detail' ) { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) } } )
watch ( ( ) => form . relabel , ( value ) => {
if ( props . service === 'transfer' && value ) {
labelingMode . value = 'carton_and_sku'
@@ -146,11 +175,12 @@ async function loadPage() {
await load ( )
}
onMounted ( ( ) => { void loadPage ( ) ; void nextTick ( refreshWarehouseMarkLabels ) } )
onBeforeUnmount ( clearCompletionPhotos )
onBeforeUnmount ( ( ) => { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) } )
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' } ) }
function resetForm ( ) {
Object . assign ( form , { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , address : '' , contact : '' , phone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
Object . assign ( form , { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , privateDestinationAddress : '' , privateDestinationPostalCode : '' , privateDestinationContact : '' , privateDestinationPhone : '' , address : '' , contact : '' , phone : '' , pickupContact : '' , pickupPhone : '' , relabelContact : '' , relabelPhone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
cargoRows . value = [ ]
transferRelabelRows . value = [ ]
labelingMode . value = 'carton'
skuRelabelRows . value = [ ]
importedRelabelBindings . value = [ ]
@@ -158,8 +188,9 @@ function resetForm() {
selectedDiscardOrderId . value = ''
relabelCandidates . value = [ ]
selectedRelabelInboundOrderId . value = ''
transferHasFbaDestination . value = false
transferDestinationType . value = 'NONE'
selectedOperationWarehouse . value = null
editingTransferNo . value = ''
}
async function openCreate ( ) {
resetForm ( ) ; mode . value = 'create'
@@ -167,11 +198,55 @@ async function openCreate() {
if ( props . service === 'labeling' ) return
if ( props . service !== 'disposal' ) { addCargoRow ( ) ; return }
}
async function openEdit ( transferNo : string ) {
try {
resetForm ( )
await loadWarehouses ( )
const detail = await getTransfer ( transferNo ) as ServiceDetail
if ( detail . status !== 'DRAFT' ) throw new Error ( '只有草稿状态的转运单可以编辑' )
editingTransferNo . value = transferNo
Object . assign ( form , {
warehouse : detail . operatingWarehouseId || '' , destination : detail . destinationWarehouseCode || detail . fbaWarehouseCode || '' ,
privateDestinationAddress : detail . privateDestinationAddress || '' , privateDestinationPostalCode : detail . privateDestinationPostalCode || '' ,
privateDestinationContact : detail . privateDestinationContactName || '' , privateDestinationPhone : detail . privateDestinationContactPhone || '' ,
address : detail . deliveryAddress || '' , pickupContact : detail . pickupContactName || '' ,
pickupPhone : detail . pickupContactPhone || '' , deliveryMethod : detail . deliveryMethod || '' ,
estimatedArrivalAt : detail . estimatedArrivalAt || '' , estimatedShipAt : detail . estimatedShipAt || '' ,
fbaShipmentNo : detail . fbaShipmentNo || '' , trackingNo : detail . trackingNo || '' , remark : detail . remark || '' ,
volumeCbm : Number ( detail . volumeCbm ? ? 0 ) , relabel : Boolean ( detail . needRelabel ) ,
labelDeadline : detail . labelDeadline || '' , relabelContact : detail . relabelContactName || '' ,
relabelPhone : detail . relabelContactPhone || '' , relabelDeliveryMethod : detail . relabelDeliveryMethod || '' ,
} )
transferDestinationType . value = detail . destinationType || ( detail . fbaWarehouseCode ? 'FBA' : 'NONE' )
cargoRows . value = ( detail . items || [ ] ) . map ( ( item ) => ( {
... createCargoRow ( ) , sku : item . skuCode || '' , name : item . productNameZh || '' ,
quantity : Number ( item . packageCount || 0 ) , shippingMark : item . shippingMark || '' ,
customerCartonMarks : item . shippingMark || '' ,
} ) )
const bindings = detail . relabelBindings || [ ]
if ( bindings . length ) {
const operationType = bindings [ 0 ] . operationType
labelingMode . value = operationType === 'SKU_LABEL' ? 'sku' : operationType === 'CARTON_LABEL' ? 'carton' : 'carton_and_sku'
transferRelabelRows . value = bindings . map ( ( binding ) => ( {
... createCargoRow ( ) , sku : binding . originalSku || '' , newSkuCode : binding . newSku || '' ,
quantity : Number ( binding . quantity || 1 ) , customerCartonMarks : binding . customerCartonMark || '' ,
fbaCartonMark : binding . fbaCartonMark || '' , destinationWarehouseId : binding . destinationWarehouseCode || '' ,
remark : binding . remark || '' ,
} ) )
}
if ( form . warehouse ) {
try { selectedOperationWarehouse . value = await getMerchantWarehouseDetail ( form . warehouse ) } catch { selectedOperationWarehouse . value = null }
}
mode . value = 'edit'
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '草稿加载失败' ) }
}
async function selectDiscardWarehouse ( ) {
selectedDiscardOrderId . value = ''
form . inboundNo = ''
discardCandidates . value = [ ]
cargoRows . value = [ ]
transferRelabelRows . value = [ ]
if ( ! form . warehouse ) return
discardCandidatesLoading . value = true
try {
@@ -212,6 +287,13 @@ function applyExistingCartonBinding(row: SkuRelabelRow) {
function selectShippingLabel ( event : Event ) { shippingLabelFile . value = ( event . target as HTMLInputElement ) . files ? . [ 0 ] || null }
function selectCartonTotalFile ( event : Event ) { cartonTotalFile . value = ( event . target as HTMLInputElement ) . files ? . [ 0 ] || null }
function selectSkuTotalFile ( event : Event ) { skuTotalFile . value = ( event . target as HTMLInputElement ) . files ? . [ 0 ] || null }
function clearCreateTotalFile ( scope : 'CARTON_TOTAL' | 'SKU_TOTAL' ) {
if ( saving . value ) return
const file = scope === 'CARTON_TOTAL' ? cartonTotalFile : skuTotalFile
const input = scope === 'CARTON_TOTAL' ? cartonTotalCreateInput : skuTotalCreateInput
file . value = null
if ( input . value ) input . value . value = ''
}
function chooseCreateTotalFile ( scope : 'CARTON_TOTAL' | 'SKU_TOTAL' ) {
; ( scope === 'CARTON_TOTAL' ? cartonTotalCreateInput : skuTotalCreateInput ) . value ? . click ( )
}
@@ -477,14 +559,27 @@ function warehouseDisplay(value?: string) {
function statusLabel ( value : string ) {
if ( props . service === 'labeling' ) {
if ( value === 'PROCESSING' ) return '处理中'
return [ 'READY_FOR_OUTBOUND' , 'COMPLETED' ] . includes ( value ) ? '换标完成' : '待处理'
return [ 'CUSTOMER_CONFIRM_PENDING' , ' READY_FOR_OUTBOUND', 'COMPLETED' ] . includes ( value ) ? '换标完成' : '待处理'
}
if ( props . service === 'transfer' ) {
const transferLabels : Record < string , string > = {
DRAFT : '草稿' , PENDING _REVIEW : '待审核' , APPROVED : '审核通过' , REJECTED : '审核驳回' ,
WAIT _PDA _RECEIVING : '待PDA收货' , INBOUND _MEASURING : '入库测量中' ,
WAIT _CUSTOMER _CONFIRMATION : '待客户确认实测' , WAIT _RELABEL : '待换标' ,
WAIT _FBA _DESTINATION : '待确认目的地' , READY _FOR _DISPATCH : '待排车' ,
WAIT _OUTBOUND : '待出库' , PENDING _ASSIGNMENT : '待分配' , ALLOCATED : '已分配库存' ,
OUTBOUNDED : '已出库' , PARTIALLY _RECEIVED : '部分收货' , RECEIVED : '已收货' ,
INBOUND _COMPLETED : '入库完成' , COMPLETED : '已完成' ,
}
if ( transferLabels [ value ] ) return transferLabels [ value ]
}
const labels : Record < string , string > = {
PENDING _PROCESSING : '待处理' , PROCESSING : '处理中' , CUSTOMER _CONFIRM _PENDING : '待客户确认' ,
READY _FOR _OUTBOUND : '换标完成' , COMPLETED : '已完成' , CANCELLED : '已取消' ,
PENDING _REVIEW : '待审核' , APPROVED : '待 PDA 废弃' , PDA _COMPLETED : '待仓库确认完成' , REJECTED : '已拒绝' , PENDING _ASSIGNMENT : '待分配' ,
ALLOCATED : '已分配 ' , OUTBOUNDED : '已出库 ' , PARTIALLY _RECEIVED : '部分收货 ' , RECEIVED : '已收货 ' ,
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 : '待出库 ',
ALLOCATED : '已分配' , OUTBOUNDED : '已出库' , PARTIALLY _RECEIVED : '部分收货' , RECEIVED : '已收货' , INBOUND _COMPLETED : '入库完成' , PENDING _DELIVERY : '待派送' , DELIVERING : '派送中' , DELIVERED : '派送完成' ,
}
return labels [ value ] || value || '—'
@@ -509,7 +604,7 @@ async function load() {
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
submittedRows . value = result . records . map ( ( item ) => {
const destinationWarehouses = warehouseDisplay ( item . overseasWarehouseId )
return { ... row ( [ item . relabelNo , item . customerOrderNo , destinationWarehouses , '—' , item . itemCount , item . totalPackageCount , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status }
return { ... row ( [ item . relabelNo , item . customerOrderNo , destinationWarehouses , '—' , item . itemCount , item . totalPackageCount , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _confirmedAt : item . customerConfirmedAt || '' }
} )
} 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 ) } )
@@ -521,7 +616,7 @@ async function load() {
submittedRows . value = result . records . map ( ( item ) => {
const operationWarehouse = warehouseDisplay ( item . operatingWarehouseId )
return props . service === 'transfer'
? row ( [ item . transferNo , item . packageCount , operationWarehouse , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是 · 待绑定换标单' : '否' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] )
? { ... row ( [ item . transferNo , item . packageCount , operationWarehouse , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是 · 待绑定换标单' : '否' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status }
: row ( [ item . transferNo , '—' , item . fbaWarehouseCode , '—' , item . packageCount , '是' , item . relabelNo ? '已生成' : '待生成' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] )
} )
}
@@ -531,7 +626,7 @@ async function load() {
function query ( ) { page . value = 1 ; void load ( ) }
function changePage ( value : number ) { page . value = value ; void load ( ) }
function changePageSize ( value : number ) { pageSize . value = value ; page . value = 1 ; void load ( ) }
async function submit ( ) {
async function submit ( action : 'draft' | 'submit' = 'submit' ) {
const primaryWarehouse = props . service === 'relabel' ? form . sourceWarehouse : form . warehouse
const relabelRows = labelingMode . value === 'carton' ? cartonDetailRows . value : skuRelabelRows . value
const invalidCargo = props . service === 'disposal'
@@ -567,23 +662,29 @@ async function submit() {
if ( props . service === 'relabel' && ( ! form . destination || ! form . address || ! form . deliveryMethod || ! form . estimatedArrivalAt || ! form . fbaShipmentNo || form . volumeCbm <= 0 ) ) {
ElMessage . warning ( '请填写完整的派送信息、体积、预计到达时间和 FBA 货件号' ) ; return
}
if ( props . service === 'transfer' && ( ! form . warehouse || ! form . deliveryMethod || ! form . address || ! form . estimatedArrivalAt || ! form . estimatedShipAt || form . volumeCbm < 0 || ( form . deliveryMethod === 'WAREHOUSE_PICKUP' && ( ! form . contact || ! form . phone ) ) ) ) {
const missing = [ ! form . warehouse && '海外仓' , ! form . deliveryMethod && '送货方式' , ! form . address && '地址' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . contact && '提货联系人' , form . deliveryMethod === 'WAREHOUSE_PICKUP' && ! form . phone && '联系人电话' , ! form . estimatedArrivalAt && '预计入仓时间' , ! form . estimatedShipAt && '预计发货时间' , form . volumeCbm < 0 && '预估总体积' ] . filter ( Boolean ) . join ( '、' )
if ( props . service === 'transfer' && ( ! form . warehouse || ! form . deliveryMethod || ! form . address || ! form . estimatedArrivalAt || form . volumeCbm < 0 || ( 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 ( '、' )
ElMessage . warning ( ` 请填写: ${ missing || '必填信息' } ` ) ; return
}
if ( props . service === 'transfer' && transferHasFbaDestination . value && ( ! form . destination || ! form . fbaShipmentNo ) ) {
ElMessage . warning ( '已 选择有 FBA 去向,请填写 FBA 目的仓和货件编号 ' ) ; return
if ( props . service === 'transfer' && transferDestinationType . value === 'FBA' && ! form . destination ) {
ElMessage . warning ( '请 选择 FBA 目的仓' ) ; return
}
if ( props . service === 'transfer' && transferDestinationType . value === 'PRIVATE' && ( ! form . privateDestinationAddress || ! form . privateDestinationPostalCode || ! form . privateDestinationContact || ! form . privateDestinationPhone ) ) {
ElMessage . warning ( '请完整填写私人仓地址、邮编、联系人和电话' ) ; return
}
if ( props . service === 'transfer' && form . relabel ) {
if ( ! form . labelDeadline || ! form . contact || ! form . phone || ( labelingMode . value !== 'sku' && ! form . relabelDeliveryMethod ) ) { ElMessage . warning ( '请填写换标截止时间、联系人和换标后派送方式' ) ; return }
if ( ! form . labelDeadline || ! form . relabelContact || ! form . relabelPhone || ( labelingMode . value !== 'sku' && ! form . relabelDeliveryMethod ) ) { ElMessage . warning ( '请填写换标截止时间、换标 联系人和换标后派送方式' ) ; return }
const relabelRows = transferRelabelRows . value
const allMarks = relabelRows . flatMap ( transferCustomerMarks )
if ( ! relabelRows . length || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . length !== row . quantity ) ) { ElMessage . warning ( '每个换标明细的客户箱唛数量必须与换标数量一致' ) ; return }
if ( new Set ( allMarks . map ( ( mark ) => mark . toUpperCase ( ) ) ) . size !== allMarks . length ) { ElMessage . warning ( '客户箱唛号不能重复' ) ; return }
if ( allMarks . join ( ',' ) . length > 2000 || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . join ( ',' ) . length > 255 ) ) { ElMessage . warning ( '客户箱唛内容过长,请减少单次提交箱数或缩短箱唛号' ) ; return }
if ( relabelRows . some ( ( row ) => row . quantity < 1 || ( labelingMode . value !== 'carton' && ! row . newSkuCode . trim ( ) ) || ( labelingMode . value !== 'sku' && ( ! row . fbaCartonMark . trim ( ) || ! row . destinationWarehouseId . trim ( ) ) ) ) ) { ElMessage . warning ( '请按当前换标类型完整填写换标明细 ') ; return }
if ( labelingMode . value !== 'sku' && ! form . destination ) { ElMessage . warning ( '箱唛换标需要先在入库与转运信息中选择目的 FBA 仓 ') ; return }
if ( relabelRows . some ( ( row ) => row . quantity < 1 || ( labelingMode . value !== 'carton' && ( ! row . sku . trim ( ) || ! row . newSkuCode . trim ( ) ) ) || ( labelingMode . value !== 'sku' && ! row . fbaCartonMark . trim ( ) ) ) ) { ElMessage . warning ( '请按当前换标类型完整填写换标明细' ) ; return }
}
if ( props . service !== 'transfer' || action === 'submit' ) {
try { await ElMessageBox . confirm ( ` 确认提交 ${ config . value . title } 吗? ` , '提交确认' , { confirmButtonText : '提交' , cancelButtonText : '取消' } ) } catch { return }
}
try { await ElMessageBox . confirm ( ` 确认提交 ${ config . value . title } 吗? ` , '提交确认' , { confirmButtonText : '提交' , cancelButtonText : '取消' } ) } catch { return }
saving . value = true
try {
const idempotencyKey = crypto . randomUUID ( )
@@ -595,9 +696,18 @@ async function submit() {
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' ,
printShippingLabel : labelingMode . value !== 'sku' && ( form . deliveryMethod === 'EXPRESS' || form . deliveryMethod === 'EXPRESS_PICKUP' ) && form . relabel } )
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 ( shippingLabelFile . value ) await uploadRelabelShippingLabel ( created . application . relabelNo , shippingLabelFile . value )
try {
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 ( shippingLabelFile . value ) await uploadRelabelShippingLabel ( created . application . relabelNo , shippingLabelFile . value )
} catch ( error ) {
// Creation already committed: leave the creation form even if the detail reload fails.
mode . value = 'list'
await load ( )
await openDetail ( { '0' : created . application . relabelNo } )
await ElMessageBox . alert ( ` 申请 ${ created . application . relabelNo } 已创建,部分附件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请在原单详情补传失败附件,不要重复下单。 ` , '申请已创建,附件待补传' , { confirmButtonText : '知道了' } )
return
}
} else if ( props . service === 'disposal' ) {
const items = cargoRows . value . filter ( ( item ) => item . quantity > 0 ) . map ( ( item ) => {
if ( ! item . sourceOrderId || item . sourceOrderId !== selectedDiscardOrderId . value || item . quantity > Number ( item . availableQuantity || 0 ) ) {
@@ -611,9 +721,21 @@ async function submit() {
const customerCartonMarks = ( form . relabel ? transferRelabelRows . value : cargoRows . value ) . flatMap ( transferCustomerMarks )
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'
await createTransfer ( { operatingWarehouseId : primaryWarehouse , fbaWarehouseCode : form . destination || undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , estimatedShipAt : form . estimatedShipAt || undefined , fbaShipmentNo : form . fbaShipmentNo || 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 , relabelContactName : ( form . relabel || form . deliveryMethod === 'WAREHOUSE_PICKUP' ) ? form . contact : undefined , relabelContactPhone : ( form . relabel || form . deliveryMethod === 'WAREHOUSE_PICKUP' ) ? form . phone : 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 : item . destinationWarehouseId || undefined , remark : item . remark || undefined } ) ) : undefined } )
const selectedFba = fbaWarehouses . value . find ( ( warehouse ) => warehouse . warehouseCode === form . destination )
const payload : TransferCreate = { operatingWarehouseId : primaryWarehouse , destinationType : transferDestinationType . value , fbaWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseId : transferDestinationType . value === 'FBA' ? String ( selectedFba ? . id || '' ) || undefined : undefined , destinationWarehouseCode : transferDestinationType . value === 'FBA' ? form . destination || undefined : undefined , destinationWarehouseName : transferDestinationType . value === 'FBA' ? selectedFba ? . warehouseName : undefined , privateDestinationAddress : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationAddress : undefined , privateDestinationPostalCode : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPostalCode : undefined , privateDestinationContactName : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationContact : undefined , privateDestinationContactPhone : transferDestinationType . value === 'PRIVATE' ? form . privateDestinationPhone : undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , estimatedShipAt : form . estimatedShipAt || undefined , fbaShipmentNo : transferDestinationType . value === 'FBA' ? form . fbaShipmentNo || undefined : undefined , trackingNo : form . deliveryMethod === 'CUSTOMER_DELIVERY' ? form . trackingNo || undefined : undefined , fbaBoxNos : customerCartonMarks . length ? customerCartonMarks . join ( ',' ) : undefined , remark : transferRemark || undefined , needRelabel : props . service === 'relabel' || form . relabel , labelDeadline : form . relabel ? form . labelDeadline : undefined , pickupContactName : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupContact : undefined , pickupContactPhone : form . deliveryMethod === 'WAREHOUSE_PICKUP' ? form . pickupPhone : undefined , relabelContactName : form . relabel ? form . relabelContact : undefined , relabelContactPhone : form . relabel ? form . relabelPhone : undefined , relabelDeliveryMethod : form . relabel && labelingMode . value !== 'sku' ? form . relabelDeliveryMethod as 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPRESS_PICKUP' : undefined , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , packageCount : item . quantity , shippingMark : transferCustomerMarks ( item ) . join ( ',' ) || undefined } ) ) , relabelItems : form . relabel ? transferRelabelRows . value . map ( ( item ) => ( { operationType , customerCartonMarks : transferCustomerMarks ( item ) , originalSku : item . sku , newSku : item . newSkuCode || undefined , quantity : item . quantity , fbaCartonMark : item . fbaCartonMark || undefined , destinationWarehouseCode : labelingMode . value === 'sku' ? undefined : form . destination || undefined , remark : item . remark || undefined } ) ) : undefined }
const created = editingTransferNo . value
? await updateTransfer ( editingTransferNo . value , payload )
: await createTransfer ( payload )
const transferNo = String ( created . transferNo || '' )
if ( ! transferNo ) throw new Error ( '转运草稿已保存,但未返回转运单号' )
if ( action === 'submit' ) {
await submitTransfer ( transferNo )
ElMessage . success ( '转运单已提交审核' )
await openDetail ( { '0' : transferNo } )
return
}
}
ElMessage . success ( ` ${ config . value . title } 提交成功 ` ) ; mode . value = 'list' ; await load ( )
ElMessage . success ( props . service === 'transfer' ? '转运单草稿保存成功' : ` ${ config . value . title } 提交成功 ` ) ; mode . value = 'list' ; await load ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '提交失败' ) }
finally { saving . value = false }
}
@@ -625,26 +747,27 @@ async function openDetail(value: Record<string, string>) {
selectedRelabelDetail . value = props . service === 'labeling' ? detail as RelabelDetail : null
mode . value = 'detail'
if ( props . service === 'labeling' ) void loadCompletionPhotos ( )
if ( props . service === 'transfer' ) void loadMeasurementPhotos ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '详情加载失败' ) }
}
async function confirmTransferMeasurements ( ) {
const transferNo = String ( selectedDetail . value ? . transferNo || selectedDetail . value ? . [ '0' ] || '' )
if ( ! transferNo ) return
try { await ElMessageBox . confirm ( '确认 PDA 回传的箱数、尺寸、重量和打托数据吗?确认后将按实测数据入库 。' , '确认实 测数据' , { confirmButtonText : '确认入库 ' , cancelButtonText : '取消' } ) } catch { return }
try { selectedDetail . value = await confirmTransferMeasurement ( transferNo ) as ServiceDetail ; ElMessage . success ( '实测数据已确认并入库 ' ) }
try { await ElMessageBox . confirm ( '请核对 PDA 回传的箱数、尺寸、重量和打托数据。' , '确认测量 数据' , { confirmButtonText : '确认数据 ' , cancelButtonText : '取消' } ) } catch { return }
try { selectedDetail . value = await confirmTransferMeasurement ( transferNo ) as ServiceDetail ; ElMessage . success ( '仓库 实测数据已确认' ) }
catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
async function printTransferCartons ( ) {
async function printTransferCartons ( transferNo : string ) {
if ( props . service !== 'transfer' ) return
const printWindow = window . open ( '' , '_blank' )
if ( ! printWindow ) { ElMessage . warning ( '浏览器阻止了打印窗口,请允许弹窗后重试' ) ; return }
printWindow . opener = null
try {
const detail = selectedDetail . value
const transferNo = String ( detail ? . transferNo || detail ? . [ '0' ] || '' )
if ( ! transferNo ) { printWindow . close ( ) ; return }
const label = await getTransferCartonLabel ( transferNo )
if ( ! label . cartons . length ) { ElMessage . warning ( '暂无可打印的箱唛' ) ; return }
const printWindow = window . open ( '' , '_blank' )
if ( ! printWindow ) { ElMessage . warning ( '浏览器阻止了打印窗口,请允许弹窗后重试' ) ; return }
printWindow . opener = null ; printWindow . document . open ( ) ; printWindow . document . write ( label . printableHtml ) ; printWindow . document . close ( ) ; printWindow . focus ( ) ; printWindow . print ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '箱唛加载失败' ) }
if ( ! label . cartons . length ) { printWindow . close ( ) ; ElMessage . warning ( '暂无可打印的箱唛' ) ; return }
printWindow . document . open ( ) ; printWindow . document . write ( label . printableHtml ) ; printWindow . document . close ( ) ; printWindow . focus ( ) ; printWindow . print ( )
} catch ( error ) { printWindow . close ( ) ; ElMessage . error ( error instanceof Error ? error . message : '箱唛加载失败' ) }
}
function clearCompletionPhotos ( ) {
for ( const url of Object . values ( completionPhotoUrls ) ) URL . revokeObjectURL ( url )
@@ -652,6 +775,11 @@ function clearCompletionPhotos() {
for ( const key of Object . keys ( completionPhotoLoading ) ) delete completionPhotoLoading [ key ]
for ( const key of Object . keys ( completionPhotoErrors ) ) delete completionPhotoErrors [ key ]
}
async function confirmTransferFromList ( transferNo : string ) {
try { await ElMessageBox . confirm ( '请核对 PDA 回传的箱数、尺寸、重量和打托数据。' , '确认测量数据' , { confirmButtonText : '确认数据' , cancelButtonText : '取消' } ) } catch { return }
try { await confirmTransferMeasurement ( transferNo ) ; ElMessage . success ( '仓库实测数据已确认' ) ; await load ( ) }
catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '确认失败' ) }
}
async function loadCompletionPhoto ( fileId : string ) {
const relabelNo = selectedRelabelDetail . value ? . application . relabelNo
if ( ! relabelNo || completionPhotoLoading [ fileId ] ) return
@@ -670,6 +798,29 @@ async function loadCompletionPhotos() {
clearCompletionPhotos ( )
await Promise . all ( ( selectedRelabelDetail . value ? . completionEvidence ? . photoFileIds || [ ] ) . map ( loadCompletionPhoto ) )
}
function clearMeasurementPhotos ( ) {
for ( const url of Object . values ( measurementPhotoUrls ) ) URL . revokeObjectURL ( url )
for ( const key of Object . keys ( measurementPhotoUrls ) ) delete measurementPhotoUrls [ key ]
for ( const key of Object . keys ( measurementPhotoLoading ) ) delete measurementPhotoLoading [ key ]
for ( const key of Object . keys ( measurementPhotoErrors ) ) delete measurementPhotoErrors [ key ]
}
async function loadMeasurementPhoto ( fileId : string | number ) {
const transferNo = selectedDetail . value ? . transferNo
if ( ! transferNo || measurementPhotoLoading [ fileId ] ) return
measurementPhotoLoading [ fileId ] = true
measurementPhotoErrors [ fileId ] = false
try {
const blob = await downloadTransferMeasurementPhoto ( transferNo , fileId )
if ( mode . value !== 'detail' || props . service !== 'transfer' || selectedDetail . value ? . transferNo !== transferNo ) return
if ( measurementPhotoUrls [ fileId ] ) URL . revokeObjectURL ( measurementPhotoUrls [ fileId ] )
measurementPhotoUrls [ fileId ] = URL . createObjectURL ( blob )
} catch { if ( mode . value === 'detail' && props . service === 'transfer' && selectedDetail . value ? . transferNo === transferNo ) measurementPhotoErrors [ fileId ] = true }
finally { measurementPhotoLoading [ fileId ] = false }
}
async function loadMeasurementPhotos ( ) {
clearMeasurementPhotos ( )
await Promise . all ( ( selectedDetail . value ? . measurementPhotos || [ ] ) . map ( ( photo ) => loadMeasurementPhoto ( photo . fileId ) ) )
}
async function confirmRelabelFromList ( relabelNo : string ) {
try {
await ElMessageBox . confirm ( '确认仓库上传的换标结果无误并继续出库流程吗?' , '换标完成确认' , { confirmButtonText : '确认并继续' , cancelButtonText : '取消' } )
@@ -720,16 +871,18 @@ async function loadWarehouses() {
function warehouseLabel ( value : MerchantWarehouse ) { return ` ${ value . warehouseName } · ${ value . warehouseCode } ` }
async function selectTransferWarehouse ( ) {
selectedOperationWarehouse . value = null
form . contact = ''
form . phone = ''
form . pickupContact = ''
form . pickupPhone = ''
form . relabelContact = ''
form . relabelPhone = ''
if ( form . deliveryMethod === 'CUSTOMER_DELIVERY' ) form . address = ''
if ( ! form . warehouse ) return
operationWarehouseDetailLoading . value = true
try {
const detail = await getMerchantWarehouseDetail ( form . warehouse )
selectedOperationWarehouse . value = detail
form . contact = detail . warehouse . contactName || ''
form . phone = detail . warehouse . contactPhone || ''
form . relabelContact = detail . warehouse . contactName || ''
form . relabelPhone = detail . warehouse . contactPhone || ''
if ( form . deliveryMethod === 'CUSTOMER_DELIVERY' ) form . address = operationWarehouseAddress . value
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '中转仓联系信息加载失败' )
@@ -738,10 +891,9 @@ async function selectTransferWarehouse() {
function selectTransferInboundMethod ( value : string | number | boolean | undefined ) {
form . address = value === 'CUSTOMER_DELIVERY' ? operationWarehouseAddress . value : ''
}
function selectTransferFbaAvailability ( value : string | number | boolean | undefined ) {
if ( value === true ) return
form . destination = ''
form . fbaShipmentNo = ''
function selectTransferDestinationType ( value : string | number | boolean | undefined ) {
if ( value ! == 'FBA' ) { form . destination = '' ; form . fbaShipmentNo = '' }
if ( value !== 'PRIVATE' ) { form . privateDestinationAddress = '' ; form . privateDestinationPostalCode = '' ; form . privateDestinationContact = '' ; form . privateDestinationPhone = '' }
}
async function selectOperationWarehouse ( ) {
cargoRows . value = [ ]
@@ -800,7 +952,8 @@ function selectRelabelInventory(inboundOrderId: string) {
< div class = "warehouse-filter-actions" > < el-button type = "primary" @click ="query" > < Search :size = "16" / > 查询 < / el-button > < el-button @click ="resetFilters(); query()" > 重置 < / el -button > < / div >
< / div >
< div v-loading = "loading" class="warehouse-card warehouse-table-card" >
< div class = "warehouse-table-scroll" > < table > < thead > < tr > < th v-for = "column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><div class="warehouse-list-actions"><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button><el-button v-if="props.service === 'labeling' && row .__status === 'CUSTOMER_CONFIRM_PENDING'" size="small" type="primary" @click="confirmRelabelFromList(row['0'])">换标完成确认</el-button></div></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty" > {{ config.emptyText }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< div v-if = "props.service === 'transfer' && submittedRows.some((item) => item .__status === 'WAIT_ CUSTOMER_CONFIRMATION')" class="warehouse-hint" > 部分入库完成订单已有 PDA 实测数据 , 请在对应行确认测量数据 。 < / div >
< div class = "warehouse-table-scroll" > < table > < thead > < tr > < th v-for = "column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><div class="warehouse-list-actions"><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button><el-button v-if="props.service === 'transfer' && row.__status === 'DRAFT'" native-type="button" size="small" type="primary" @click="openEdit(row['0'])">编辑</el-button><el-button v-if="props.service === 'transfer' && row.__status === 'WAIT_CUSTOMER_CONFIRMATION'" size="small" type="primary" @click="confirmTransferFromList(row['0'])">确认测量数据</el-button><el-button v-if="props.service === 'labeling' && row.__status === 'CUSTOMER_CONFIRM_PENDING' && !row.__confirmedAt" size="small" type="primary" @click="confirmRelabelFromList(row['0'])">换标完成确认</el-button></div></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty" > {{ config.emptyText }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< div class = "warehouse-pagination" >
< span class = "warehouse-pagination-total" > 共 { { total } } 条 < / span >
< el-select v-model = "pageSize" class="warehouse-page-size-select" aria-label="每页条数" @change="changePageSize"><el-option v-for="size in [15, 30, 50, 100]" :key="size" :label="`${size}条/页`" :value="size" / > < / el-select >
@@ -809,10 +962,10 @@ function selectRelabelInventory(inboundOrderId: string) {
< / div >
< / template >
< template v-else-if = "mode === 'create'" >
< div class = "warehouse-breadcrumb" > < button type = "button" @click ="mode = 'list'" > {{ config.title }} < / button > < b > / < / b > < strong > 新增 < / strong > < / div >
< div class = "warehouse-page-heading" > < h2 > { { config . createTitle } } < / h2 > < el-button @click ="mode = 'list'" > < ArrowLeft :size = "16" / > 返回列表 < / el-button > < / div >
< form : class = "{ 'warehouse-transfer-form': props.service === 'transfer', 'warehouse-transfer-form--relabel': props.service === 'transfer' && form.relabel }" @submit.prevent ="submit" >
< template v-else-if = "mode === 'create' || mode === 'edit' " >
< div class = "warehouse-breadcrumb" > < button type = "button" @click ="mode = 'list'" > {{ config.title }} < / button > < b > / < / b > < strong > { { mode === 'edit' ? '编辑' : '新增' } } < / strong > < / div >
< div class = "warehouse-page-heading" > < h2 > { { mode === 'edit' ? ` 编辑转运草稿 ${ editingTransferNo } ` : config . createTitle } } < / h2 > < el-button @click ="mode = 'list'" > < ArrowLeft :size = "16" / > 返回列表 < / el-button > < / div >
< form : class = "{ 'warehouse-transfer-form': props.service === 'transfer', 'warehouse-transfer-form--relabel': props.service === 'transfer' && form.relabel }" @submit.prevent ="submit('submit') " >
< div class = "warehouse-card warehouse-form-section warehouse-transfer-main-card" > < h3 > { { props . service === 'transfer' ? '入库与转运信息' : props . service === 'relabel' ? '退货信息' : props . service === 'labeling' ? '订单信息' : '基础信息' } } < / h3 >
< div class = "warehouse-form-grid" >
< label v-if = "props.service === 'disposal'" class="warehouse-span-2"><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 >
@@ -824,13 +977,13 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择有库存的历史入仓单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前中转仓暂无已入库可用库存" placeholder="请选择曾经下单且当前仍有库存的入仓单" @change="selectRelabelInventory"><el-option v-for="option in relabelInboundOptions" :key="option.value" :label="`${option.inboundOrderNo}|入仓码 ${option.inboundCode}|可用 ${option.availablePackages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 仅显示当前客户在所选中转仓已经完成入库且仍有可用库存的历史入仓单 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>货物如何送到海外仓</span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < small > 仓库上门提货 : 填写货物当前地址 ; 客户送货到仓 : 下方自动展示海外仓收货信息 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'" class="pickup-address-field"><span><i>*</i>提货地址</span><el-input v-model="form.address" placeholder="请输入仓库上门提货的详细地址" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人</span><el-input v-model="form.c ontact" placeholder="请输入提货联系人" maxlength="128" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>联系人电话</span><el-input v-model="form.phone" placeholder="请输入联系人电话" maxlength="64" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人</span><el-input v-model="form.pickupC ontact" placeholder="请输入提货联系人" maxlength="128" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货 联系人电话</span><el-input v-model="form.pickupP hone" 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 > < span > 海外仓收货信息 < / span > < strong > { { operationWarehouseAddress || '该海外仓暂未维护收货地址' } } < / strong > < / div >
< label class = "warehouse-span-2" > < span > < i > * < / i > 送货地址 < / span > < el-input v-model = "form.address" placeholder="请输入实际送货地址" / > < / label >
< div > < span > 收货联系人 < / span > < strong > { { form . contact || '未维护' } } < / strong > < / div >
< div > < span > 联系电话 < / span > < strong > { { form . phone || '未维护' } } < / strong > < / div >
< div > < span > 收货联系人 < / span > < strong > { { selectedOperationWarehouse ? . warehouse . contactName || '未维护' } } < / strong > < / div >
< div > < span > 联系电话 < / span > < strong > { { selectedOperationWarehouse ? . warehouse . contactPhone || '未维护' } } < / strong > < / div >
< / div >
< label v-if = "props.service === 'relabel'"><span><i>*</i>目的 FBA 仓</span><el-select v-model="form.destination" :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>派送方式</span><el-select v-model="form.deliveryMethod" placeholder="请选择"><el-option label="卡车派送" value="卡车派送" /><el-option label="快递派送" value="快递派送" / > < / el-select > < / label >
@@ -844,10 +997,14 @@ function selectRelabelInventory(inboundOrderId: string) {
< 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>预估总体积(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'"><span><i>*</i> 预计发货时间</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><i>*</i>是否已有 FBA 去向</span><el-radio-group v-model="transferHasFba Destination" @change="selectTransferFbaAvailability "><el-radio-button : value="false ">暂未确定</el-radio-button><el-radio-button : value="true" > 已有 FBA 去向 < / el -radio -button > < / el-radio-group > < small > 暂未确定也可以提交 , 货物先正常入海外仓 , 后续再补充转运指令 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && transferHasFba Destination && !form.relabel "><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' && transferHasFba Destination && !form.relabel"><span><i>*</i >FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号 " / > < / label >
< label v-if = "props.service === 'transfer'"><span>预计发货时间</span><el-date-picker v-model="form.estimatedShipAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选填,可线下确认后补充" /><small class="warehouse-hint" > 暂未确定时可不填写 , 不影响货物先入海外仓 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否有目的 去向</span><el-radio-group v-model="transferDestinationType " @change="selectTransferDestinationType "><el-radio-button value="NONE ">暂未确定</el-radio-button><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="PRIVATE" > 私人仓 < / el -radio -button > < / el-radio-group > < small > 暂未确定也可以提交 ; 完成入库后可在新建出库单中批量绑定目的地 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA' "><span><i>*</i>目的 FBA 仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 整张转运单统一使用该目的仓 , 换标明细自动继承 。 < / small > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span >FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="选填,可后续补充 " / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'" class="warehouse-span-2"><span><i>*</i>私人仓地址</span><el-input v-model="form.privateDestinationAddress" maxlength="1000" placeholder="请输入私人仓完整地址" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'"><span><i>*</i>邮编</span><el-input v-model="form.privateDestinationPostalCode" maxlength="32" placeholder="请输入邮编" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'"><span><i>*</i>私人仓联系人</span><el-input v-model="form.privateDestinationContact" maxlength="128" placeholder="请输入联系人" / > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'PRIVATE'"><span><i>*</i>联系电话</span><el-input v-model="form.privateDestinationPhone" maxlength="64" placeholder="请输入联系电话" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号" / > < / label >
< label v-if = "props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求" / > < / label >
< label v-else-if = "props.service === 'relabel' || (props.service === 'transfer' && form.deliveryMethod === 'CUSTOMER_DELIVERY')"><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号(选填)" / > < / label >
@@ -875,7 +1032,10 @@ function selectRelabelInventory(inboundOrderId: string) {
< input ref = "skuTotalCreateInput" class = "warehouse-hidden-input" type = "file" accept = "application/pdf,.pdf" @change ="selectSkuTotalFile" >
< / div >
< / div >
< div class = "warehouse-upload-summary" > < span > 箱唛总文件 : { { cartonTotalFile ? . name || '未选择' } } < / span > < span > SKU 总文件 : { { skuTotalFile ? . name || '未选择' } } < / span > < / div >
< div class = "warehouse-upload-summary" >
< span > 箱唛总文件 : { { cartonTotalFile ? . name || '未选择' } } < button v-if = "cartonTotalFile" type="button" class="warehouse-file-clear" title="清除箱唛总文件" aria-label="清除箱唛总文件" :disabled="saving" @click="clearCreateTotalFile('CARTON_TOTAL')"><X :size="14" / > < / button > < / span >
< span > SKU 总文件 : { { skuTotalFile ? . name || '未选择' } } < button v-if = "skuTotalFile" type="button" class="warehouse-file-clear" title="清除 SKU 总文件" aria-label="清除 SKU 总文件" :disabled="saving" @click="clearCreateTotalFile('SKU_TOTAL')"><X :size="14" / > < / button > < / span >
< / div >
< div class = "warehouse-labeling-mode" >
< span > 换标类型 < / span >
< el-radio-group v-model = "labelingMode" aria-label="换标类型" >
@@ -899,35 +1059,32 @@ function selectRelabelInventory(inboundOrderId: string) {
</template>
<template v-else>
<div class=" warehouse - section - heading "><h3>货物明细</h3><div v-if=" props . service !== 'disposal' "><el-button type=" primary " size=" small " @click=" addCargoRow "><Plus :size=" 15 " />添加行</el-button><el-button size=" small " @click=" cargoRows = [ ] ">清空</el-button></div></div>
<div class=" warehouse - table - scroll "><table class=" warehouse - edit - table "><thead><tr><th>商品 SKU *</th><th>中文品名 * </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=" 请输入商品 SKU " /></td><td><el-input v-model=" row . name " :readonly=" props . service === 'disposal' " placeholder=" 请输入中文品名 " /></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 - 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>
</template>
</div>
<div v-if=" props . service === 'transfer' && form . relabel " class=" warehouse - card warehouse - form - section warehouse - transfer - relabel ">
<div class=" warehouse - section - heading warehouse - labeling - heading "><div><h3>货物明细</h3><p>换标明细与贴标申请保持一致;客户只填写自己的箱唛,我方箱唛由系统生成。</p></div><div class=" warehouse - template - actions "><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'sku' ) "><Download :size=" 15 " />下载 SKU 关联模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" skuTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传 SKU 关联</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'carton' ) "><Download :size=" 15 " />下载箱号关联模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" cartonTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传箱号关联</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'mixed' ) "><Download :size=" 15 " />下载 SKU 混箱模板</el-button><el-button size=" small " :disabled=" ! cargoRows . length " @click=" mixedTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传 SKU 混箱</el-button><input ref=" skuTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'sku' ) "><input ref=" cartonTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'carton' ) "><input ref=" mixedTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'mixed' ) "></div></div>
<div class=" warehouse - form - grid warehouse - transfer - relabel - fields "><label><span><i>*</i>换标截止时间</span><el-date-picker v-model=" form . labelDeadline " type=" datetime " value-format=" YYYY - MM - DDTHH : mm : ss " placeholder=" 请选择截止时间 " /></label><label><span><i>*</i>联系人</span><el-input v-model=" form . contact " placeholder=" 请输入联系人 " /></label><label><span><i>*</i>联系电话</span><el-input v-model=" form . phone " placeholder=" 请输入联系电话 " /></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><span>FBA 货件编号</span><el-input v-model=" form . fbaShipmentNo " placeholder=" 选填 , 未填则使用转运单号 " /></label><label class="warehouse - span - 2 "><span>贴标要求</span><el-input v-model=" form . requirement " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入贴标要求 ( 选填 ) " /></label></div>
<div class=" warehouse - form - grid warehouse - transfer - relabel - fields "><label><span><i>*</i>换标截止时间</span><el-date-picker v-model=" form . labelDeadline " type=" datetime " value-format=" YYYY - MM - DDTHH : mm : ss " placeholder=" 请选择截止时间 " /></label><label><span><i>*</i>换标 联系人</span><el-input v-model=" form . relabelContact " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 128 " /></label><label><span><i>*</i>换标 联系人 电话</span><el-input v-model=" form . relabelPhone " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 64 " /></label><label v-if=" labelingMode !== 'sku' "><span><i>*</i>换标后派送方式</span><el-select v-model=" form . relabelDeliveryMethod " placeholder=" 请选择派送方式 "><el-option label=" 卡车派送 " value=" TRUCK " /><el-option label=" 快递派送 " value=" EXPRESS " /><el-option label=" 卡车自提 " value=" TRUCK _PICKUP " /><el-option label=" 快递自提 " value=" EXPRESS _PICKUP " /></el-select></label><label class=" warehouse - span - 2 "><span>贴标要求</span><el-input v-model=" form . requirement " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入贴标要求 ( 选填 ) " /></label></div>
<div class=" warehouse - labeling - mode "><span>换标类型</span><el-radio-group v-model=" labelingMode " aria-label=" 换标类型 "><el-radio-button value=" carton ">只换箱唛</el-radio-button><el-radio-button value=" sku ">只换 SKU 标签</el-radio-button><el-radio-button value=" carton _and _sku ">SKU 和箱唛都换</el-radio-button></el-radio-group><p>{{ labelingModeHint }}</p></div>
<div v-if=" importedRelabelBindings . length " class=" warehouse - import - preview "><div class=" warehouse - section - heading "><div><strong>待导入绑定关系</strong><p>可只勾选本次需要换标的部分,也可以全选后一次导入。</p></div><div><el-checkbox :model-value=" importedRelabelBindings . every ( ( item ) => item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div><div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头</th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th><th>FBA 目的仓</th>< /tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td><td>{{ warehouseDisplay(item.destinationWarehouseId) }}</td></tr></tbody></table></div></div>
<div v-if=" importedRelabelBindings . length " class=" warehouse - import - preview "><div class=" warehouse - section - heading "><div><strong>待导入绑定关系</strong><p>可只勾选本次需要换标的部分,也可以全选后一次导入。</p></div><div><el-checkbox :model-value=" importedRelabelBindings . every ( ( item ) => item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div><div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头</th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th></tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td></tr></tbody></table></div></div>
<div class=" warehouse - section - heading warehouse - tab - actions "><div><strong>绑定明细</strong><p>上传后在下方回显,也可以手动添加;客户箱唛作为仓库唛头来源,系统我方箱唛不需要手填。</p></div><div><el-button type=" primary " size=" small " @click=" addTransferRelabelRow "><Plus :size=" 15 " />添加 SKU 明细</el-button><el-button size=" small " @click=" clearTransferRelabelRows ">清空</el-button></div></div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU</th><th>新 FBA 箱唛 *</th><th>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-select v-model=" row . destinationWarehouseId " :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></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-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 5 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-else-if=" labelingMode === 'sku' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . newSkuCode " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入箱数 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>新 FBA 箱唛 *</th><th>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-select v-model=" row . destinationWarehouseId " :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></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=" 8 "><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>
<div class=" warehouse - submit - bar warehouse - transfer - submit - bar "><el-button @click=" resetForm ">重置</el-button><el-button 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>
</template>
<template v-else>
<div class=" warehouse - breadcrumb "><button type=" button " @click=" mode = 'list' ">{{ config.title }}</button><b>/</b><strong>订单详情</strong></div>
<div class=" warehouse - page - heading "><h2>{{ config.title }}详情</h2><div><el-button v-if=" props . service === 'disposal' && selectedDetail ? . status === 'PENDING_REVIEW' " type=" danger " @click=" cancelSelectedDiscard ">取消申请</el-button><el-button v-if=" props . service === 'transfer' && selectedDetail ? . status === 'WAIT_CUSTOMER_CONFIRMATION' " type=" primary " @click=" confirmTransferMeasurements ">确认实测并入库 </el-button><el-button @click=" mode = 'list' "><ArrowLeft :size=" 16 " />返回列表</el-button></div></div>
<div class=" warehouse - page - heading "><h2>{{ config.title }}详情</h2><div><el-button v-if=" props . service === 'disposal' && selectedDetail ? . status === 'PENDING_REVIEW' " type=" danger " @click=" cancelSelectedDiscard ">取消申请</el-button><el-button v-if=" props . service === 'transfer' && selectedDetail ? . status === 'DRAFT' " type=" primary " @click=" openEdit ( String ( selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || '' ) ) ">编辑</el-button><el-button v-if=" props . service === 'transfer' && selectedDetail ? . status === 'WAIT_CUSTOMER_CONFIRMATION' " type=" primary " @click=" confirmTransferMeasurements ">确认测量数据 </el-button><el-button @click=" mode = 'list' "><ArrowLeft :size=" 16 " />返回列表</el-button></div></div>
<div class=" warehouse - card warehouse - form - section "><h3>订单概览</h3><div 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 === 'transfer' " class=" warehouse - card warehouse - form - section warehouse - transfer - times "><h3>发货时间</h3><div class=" warehouse - detail - grid "><div><span>预计发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div></div><p>实际发货时间由仓库出库或 PDA 完成后回填,客户不能手动修改。</p></div >
<div v-if=" props . service = == 'transfer' " class=" warehouse - card warehouse - form - section " >
<div class=" warehouse - section - heading "><h3>箱唛明细</h3><el-button type=" primary " @click ="printTransferCartons "><Printer :size=" 16 " />打印箱唛</el-button ></div>
<div class=" warehouse - table - scroll "><table><thead><tr><th>系统箱唛</th><th>客户箱唛</th><th>箱序</th><th>状态</th></tr></thead><tbody>
<tr v-for=" carton in ( selectedDetail ? . cartons || [ ] ) " :key=" carton . cartonMark "><td><strong>{{ carton.cartonMark }}</strong></td><td>{{ carton.customerCartonMark || '—' }}</td><td>{{ carton.cartonIndex }} / {{ carton.totalCartons }}</td><td>{{ carton.status }}</td></tr>
<tr v-if=" ! ( selectedDetail ? . cartons || [ ] ) . length "><td colspan=" 4 "><div class=" warehouse - empty ">暂无箱唛明细</div></td></tr>
</tbody></table></div>
<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 - table - scroll "><table class=" warehouse - edit - table "><thead><tr><th>商品 SKU</th><th>中文品名</th><th>预计箱数</th><th>客户箱唛号</th></tr></thead><tbody><tr v-for=" ( item , index ) in selectedDetail ? . items || [ ] " :key=" ` ${ item . skuCode } - ${ index } ` "><td>{{ item.skuCode || '—' }}</td><td>{{ item.productNameZh || '—' }}</td><td>{{ item.packageCount ?? '—' }}</td><td>{{ item.shippingMark || '—' }}</td></tr><tr v-if=" ! selectedDetail ? . items ? . length "><td colspan ="4 "><div class=" warehouse - empty ">暂无货物明细</div></td></tr></tbody></table ></div>
</div>
<div v-if=" props . service === 'transfer' " class=" warehouse - card warehouse - form - section warehouse - transfer - times "><h3>发货时间</h3><div class=" warehouse - detail - grid "><div><span>预计发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div></div><p>实际发货时间由仓库出库或 PDA 完成后回填,客户不能手动修改。</p></div>
<div v-if=" props . service === 'labeling' " class=" warehouse - card warehouse - form - section ">
<div class=" warehouse - section - heading "><h3>换标完成与出库</h3></div>
<div class=" warehouse - detail - grid warehouse - fulfillment - grid ">
@@ -946,6 +1103,12 @@ function selectRelabelInventory(inboundOrderId: string) {
<tr v-if=" ! selectedRelabelDetail ? . items ? . length "><td colspan=" 5 "><div class=" warehouse - empty ">暂无贴标明细</div></td></tr>
</tbody></table></div>
</div>
<div v-if=" props . service === 'transfer' " class=" warehouse - card warehouse - form - section ">
<div class=" warehouse - section - heading "><h3>PDA 打托实测数据</h3><span class=" warehouse - hint ">PDA 作业完成即显示入库完成,客户可确认仓库实测数据</span></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>
<div v-else class=" warehouse - empty ">暂无 PDA 打托测量数据</div>
<div class=" warehouse - evidence warehouse - measurement - evidence "><span>测量打托现场照片</span><div v-if=" selectedDetail ? . measurementPhotos ? . length " class=" warehouse - photo - grid "><div v-for=" ( photo , index ) in selectedDetail . measurementPhotos " :key=" photo . fileId " class=" warehouse - 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 === 'labeling' " class=" warehouse - card warehouse - form - section ">
<h3>换标任务明细</h3>
<div class=" warehouse - table - scroll "><table class=" warehouse - relabel - detail - table "><thead><tr><th>换标模式</th><th>我方箱唛</th><th>新 FBA 箱唛</th><th>FBA 目的仓</th><th>任务箱数</th><th>商品 SKU</th><th>SKU 换标明细</th><th>FBA 货件编号</th><th>贴标要求</th><th>备注</th></tr></thead><tbody>
@@ -963,10 +1126,15 @@ function selectRelabelInventory(inboundOrderId: string) {
. warehouse - choice - field { display : grid ; grid - template - columns : 150 px auto 1 fr ; align - items : center ; gap : 14 px ; min - height : 40 px ; padding : 12 px 14 px ; border : 1 px solid # e1e8f2 ; border - radius : 5 px ; background : # f8faff } . warehouse - choice - field > span { color : # 34445 c ; font - size : 13 px ; font - weight : 650 } . warehouse - choice - field > span i { margin - right : 3 px ; color : # e24b4b ; font - style : normal } . warehouse - choice - field > small { color : # 7 a879a ; font - size : 12 px } . warehouse - receiving - card { display : grid ; grid - template - columns : 2 fr 1 fr 1 fr ; gap : 22 px ; padding : 15 px 16 px ; border : 1 px solid # cfe0f5 ; border - radius : 5 px ; background : # f6faff } . warehouse - receiving - card div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 6 px } . warehouse - receiving - card span { color : # 7 c899c ; font - size : 12 px } . warehouse - receiving - card strong { overflow - wrap : anywhere ; color : # 314561 ; font - size : 13 px ; font - weight : 600 }
. warehouse - hidden - input { display : none } . warehouse - label - table { min - width : 1120 px } . warehouse - attachment - tip { margin : 10 px 0 12 px ; color : # 7 d8797 ; font - size : 12 px } . warehouse - mark - mapping { display : flex ; flex - wrap : wrap ; gap : 8 px ; margin : 4 px 0 8 px ; padding : 12 px ; border : 1 px solid # dce7f5 ; border - radius : 5 px ; background : # f7faff } . warehouse - mark - mapping strong { width : 100 % ; color : # 34445 c } . warehouse - mark - mapping span { padding : 5 px 9 px ; border - radius : 3 px ; background : # eaf2fc ; color : # 3 e6593 ; font - size : 12 px }
. warehouse - upload - summary { display : flex ; flex - wrap : wrap ; gap : 8 px 24 px ; margin : - 4 px 0 14 px ; padding : 9 px 12 px ; border : 1 px solid # dce7f5 ; border - radius : 4 px ; background : # f8fbff ; color : # 596579 ; font - size : 12 px }
. warehouse - upload - summary > span { display : inline - flex ; align - items : center ; gap : 6 px ; overflow - wrap : anywhere }
. warehouse - file - clear { display : inline - flex ; align - items : center ; justify - content : center ; flex - shrink : 0 ; width : 24 px ; height : 24 px ; border : 0 ; border - radius : 4 px ; background : transparent ; color : var ( -- muted ) ; cursor : pointer }
. warehouse - file - clear : hover : not ( : disabled ) { color : # dc2626 ; background : # fee2e2 }
. warehouse - file - clear : disabled { opacity : .5 ; cursor : not - allowed }
. warehouse - unmaintained { margin - left : 8 px ; color : # e6a23c ; font - size : 12 px ; font - style : normal }
. warehouse - labeling - tabs { margin - top : - 8 px } . warehouse - labeling - heading { align - items : flex - start } . warehouse - labeling - heading h3 { margin - bottom : 16 px } . warehouse - template - actions { display : flex ; gap : 8 px ; padding - bottom : 12 px } . warehouse - tab - actions { align - items : center ; margin : 2 px 0 14 px } . warehouse - tab - actions p { margin : 0 ; color : # 7 d8797 ; font - size : 12 px } . warehouse - tab - actions > div { display : flex ; gap : 8 px } . warehouse - sku - notice { margin : 0 0 14 px ; padding : 10 px 12 px ; border : 1 px solid # f2d39b ; border - radius : 4 px ; background : # fff8e8 ; color : # 8 a6116 ; font - size : 12 px } . warehouse - sku - label - table { min - width : 980 px } . warehouse - sku - label - table : deep ( . el - input - number ) { width : 160 px } : deep ( . warehouse - labeling - tabs > . el - tabs _ _header ) { margin - bottom : 18 px } : deep ( . warehouse - labeling - tabs . el - tabs _ _item ) { height : 42 px ; padding : 0 26 px ; font - weight : 600 } . warehouse - labeling - mode { display : flex ; align - items : center ; gap : 14 px ; margin : 0 0 16 px ; padding : 14 px 16 px ; border : 1 px solid # dce5f5 ; border - radius : 5 px ; background : # f7f9ff } . warehouse - labeling - mode > span { color : # 34445 c ; font - size : 13 px ; font - weight : 650 ; white - space : nowrap } . warehouse - labeling - mode p { margin : 0 ; color : # 71809 a ; font - size : 12 px } . warehouse - labeling - mode : deep ( . el - radio - group ) { display : flex ; flex - shrink : 0 } . warehouse - labeling - mode : deep ( . el - radio - button _ _inner ) { padding : 8 px 14 px }
. warehouse - relabel - detail - table { min - width : 1420 px } . warehouse - relabel - detail - table td strong { color : # 253 f67 }
. 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 - 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 - 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 auto 1 fr ; gap : 10 px } . warehouse - transfer - form . warehouse - choice - field > span { font - size : 12 px } . warehouse - transfer - form . warehouse - choice - field small { font - size : 11 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 p { font - size : 11 px } . warehouse - transfer - form . warehouse - labeling - mode : deep ( . el - radio - button _ _inner ) { padding : 6 px 9 px } . warehouse - transfer - form . warehouse - template - actions { 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 }