@@ -1,8 +1,11 @@
< script setup lang = "ts" >
import { computed , nextTick , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { useRoute , useRouter } from 'vue-router'
import { ElMessage , ElMessageBox } from 'element-plus'
import { ArrowLeft , Download , Eye , Plus , Printer , Search , Trash2 , Upload , X } from '@lucide/vue'
import SingleLineText from '@/components/common/SingleLineText .vue'
import { ArrowLeft , Download , Plus , Printer , Trash2 , Upload , X } from '@lucide/vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/components/common/QueryToolbar .vue'
import DataTable , { type DataTableColumn } from '@/components/common/DataTable.vue'
import type { TableAction } from '@/components/common/tableActions'
import { useAuthStore } from '@/modules/auth/public'
import {
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer ,
@@ -22,6 +25,7 @@ import {
type MerchantWarehouseDetail ,
} from '@/modules/first-leg/api/firstLeg'
import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination'
import { createReturnRelabel , uploadReturnRelabelFile } from '../api/returnOrders'
type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
@@ -30,11 +34,15 @@ 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 ; returnCartonId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface SkuRelabelRow { id : number ; sourceRowId : number | null ; cartonMark : string ; originalSku : string ; newSku : string ; quantity : number ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface ImportedRelabelBinding extends SkuRelabelRow { templateType : RelabelTemplateType ; selected : boolean ; shipmentNo : string }
interface ReturnRelabelSkuRow { skuCode : string ; productNameZh : string ; availableQuantity : number ; selected : boolean ; quantity : number }
interface ServiceConfig { title : string ; createTitle : string ; numberLabel : string ; emptyText : string ; columns : string [ ] }
interface TransferCarton { cartonMark : string ; customerCartonMark ? : string ; cartonIndex : number ; totalCartons : number ; status : string }
const props = defineProps < { service : ServiceType } > ( )
const props = withDefaults ( defineProps < { service : ServiceType ; createPage ? : boolean ; detailPage ? : boolean ; detailNo ? : string } > ( ) , { createPage : false , detailPage : false , detailNo : '' } )
const auth = useAuthStore ( )
const router = useRouter ( )
const route = useRoute ( )
const detailNumber = computed ( ( ) => props . detailNo || String ( route . params . detailNo || '' ) )
const canCreate = computed ( ( ) => auth . hasPermission ( ( {
labeling : 'merchant:overseas-warehouse:relabel:create' ,
transfer : 'merchant:overseas-warehouse:transfer:create' ,
@@ -52,6 +60,15 @@ const configs: Record<ServiceType, ServiceConfig> = {
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '创建时间' ] } ,
}
const config = computed ( ( ) => configs [ props . service ] )
const listQueryModel = computed < QueryModel > ( {
get : ( ) => ( { ... filters , createdDates : [ filters . start , filters . end ] . filter ( Boolean ) } ) ,
set : ( value ) => Object . assign ( filters , {
number : String ( value . number || '' ) , inboundNo : String ( value . inboundNo || '' ) , warehouse : String ( value . warehouse || '' ) ,
destination : String ( value . destination || '' ) , status : String ( value . status || '' ) ,
start : Array . isArray ( value . createdDates ) ? String ( value . createdDates [ 0 ] || '' ) : '' ,
end : Array . isArray ( value . createdDates ) ? String ( value . createdDates [ 1 ] || '' ) : '' ,
} ) ,
} )
const statusOptions = computed ( ( ) => props . service === 'labeling'
? [ { label : '待处理' , value : 'PENDING_PROCESSING' } , { label : '处理中' , value : 'PROCESSING' } , { label : '换标完成' , value : 'COMPLETED' } ]
: props . service === 'disposal'
@@ -65,7 +82,7 @@ const statusOptions = computed(() => props.service === 'labeling'
{ 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 mode = ref < PageMode > ( props . detailPage ? 'detail' : props . createPage ? 'create' : 'list' )
const filters = reactive ( { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' } )
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' )
@@ -85,11 +102,6 @@ const shippingLabelFile = ref<File | null>(null)
const skuRelabelRows = ref < SkuRelabelRow [ ] > ( [ ] )
const importedRelabelBindings = ref < ImportedRelabelBinding [ ] > ( [ ] )
const cartonDetailRows = computed ( ( ) => cargoRows . value . filter ( ( row ) => row . included ) )
const labelingModeHint = computed ( ( ) => labelingMode . value === 'carton'
? '只替换外箱箱唛,不修改箱内商品 SKU 标签;整张申请统一使用订单信息中的 FBA 目的仓。'
: labelingMode . value === 'sku'
? '只替换箱内商品 SKU 标签,保留原箱唛;同一箱唛可以添加多条 SKU 明细。'
: '同一箱同时替换商品 SKU 标签和外箱箱唛;整张申请统一使用订单信息中的 FBA 目的仓。' )
const submittedRows = ref < Record < string , string > [ ] > ( [ ] )
const page = ref ( 1 )
const pageSize = ref ( 15 )
@@ -106,12 +118,12 @@ interface ServiceDetail extends Record<string, unknown> {
estimatedShipAt ? : string ; actualShipAt ? : string ; fbaShipmentNo ? : string ; trackingNo ? : string
remark ? : string ; needRelabel ? : boolean ; labelDeadline ? : string ; relabelContactName ? : string
relabelContactPhone ? : string ; relabelDeliveryMethod ? : string
createdAt ? : 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 detailError = ref ( '' )
const selectedRelabelDetail = ref < RelabelDetail | null > ( null )
const completionPhotoUrls = reactive < Record < string , string > > ( { } )
const completionPhotoLoading = reactive < Record < string , boolean > > ( { } )
@@ -128,23 +140,52 @@ const operationWarehouses = ref<MerchantWarehouse[]>([])
const fbaWarehousesLoading = ref ( false )
const fbaWarehousesError = ref ( '' )
const operationWarehousesError = ref ( '' )
const listQueryFields = computed < QueryField [ ] > ( ( ) => {
const fields : QueryField [ ] = [
{ key : 'number' , label : config . value . numberLabel , type : 'input' , placeholder : ` 请输入 ${ config . value . numberLabel } ` } ,
]
if ( props . service !== 'labeling' && props . service !== 'relabel' ) fields . push ( { key : 'inboundNo' , label : '进仓编号' , type : 'input' , placeholder : '请输入进仓编号' } )
fields . push ( {
key : 'warehouse' , label : props . service === 'relabel' ? '来源 FBA 仓' : '操作仓' , type : 'select' ,
options : ( props . service === 'relabel' ? fbaWarehouses . value : operationWarehouses . value ) . map ( ( warehouse ) => ( { label : warehouseLabel ( warehouse ) , value : String ( warehouse . id ) } ) ) ,
} )
if ( props . service === 'transfer' || props . service === 'relabel' ) fields . push ( { key : 'destination' , label : '目的 FBA 仓' , type : 'input' , placeholder : '请输入仓代码' } )
fields . push (
{ key : 'status' , label : '订单状态' , type : 'select' , options : statusOptions . value } ,
{ key : 'createdDates' , label : '创建时间' , type : 'daterange' } ,
)
return fields
} )
const listColumns = computed < DataTableColumn [ ] > ( ( ) => config . value . columns . map ( ( label , index ) => index === 0
? { key : 'actions' , label , slotName : 'actions' , width : props . service === 'transfer' ? 210 : 160 , fixed : 'left' }
: { key : String ( index - 1 ) , prop : String ( index - 1 ) , label , minWidth : index === 1 ? 190 : 130 } ) )
const discardCandidates = ref < import ( ' .. / api / warehouseServices ' ) .DiscardCandidate [ ] > ( [ ] )
const selectedDiscardOrderId = ref ( '' )
const discardCandidatesLoading = ref ( false )
const relabelCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const selectedRelabelInboundOrderId = ref ( '' )
const relabelCandidatesLoading = ref ( false )
const returnRelabelSkuRows = ref < ReturnRelabelSkuRow [ ] > ( [ ] )
const isReturnRelabelSource = computed ( ( ) => props . service === 'labeling' && selectedRelabelInboundOrderId . value . startsWith ( 'RETURN_ORDER:' ) )
const selectedReturnOrderNo = computed ( ( ) => isReturnRelabelSource . value ? selectedRelabelInboundOrderId . value . slice ( 'RETURN_ORDER:' . length ) : '' )
const requiresBothTotalFiles = computed ( ( ) => ( props . service === 'labeling' || ( props . service === 'transfer' && form . relabel ) ) && ! isReturnRelabelSource . value && labelingMode . value === 'carton_and_sku' )
function inventoryQuantity ( value : number | string | null | undefined ) {
const quantity = Number ( value )
return Number . isFinite ( quantity ) && quantity > 0 ? quantity : 0
}
const relabelInboundOptions = computed ( ( ) => {
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number } > ( )
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number ; quantityUnit : ' 箱 ' | ' 件 ' } > ( )
for ( const candidate of relabelCandidates . value ) {
const value = candidate . sourceType === 'RETURN_CARTON' ? ` RETURN_CARTON: ${ candidate . returnCartonId } ` : String ( candidate . inboundOrderId )
const returnOrderNo = candidate . inboundOrderNo || String ( candidate . returnCartonId || '' )
const value = candidate . sourceType === 'RETURN_CARTON' ? ` RETURN_ORDER: ${ returnOrderNo } ` : String ( candidate . inboundOrderId )
const current = grouped . get ( value )
if ( current ) current . availablePackages += candidate . availablePackages
if ( current ) current . availablePackages += inventoryQuantity ( candidate . availablePackages )
else grouped . set ( value , {
value ,
inboundOrderNo : candidate . sourceType === 'RETURN_CARTON' ? ` 退货合箱 ${ candidate . cartonMark || '—' } ` : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . sourceType === 'RETURN_CARTON' ? '仓库唛头 库存' : ( candidate . inboundCode || '—' ) ,
availablePackages : candidate . availablePackages ,
inboundOrderNo : candidate . sourceType === 'RETURN_CARTON' ? ` 退货单 ${ candidate . inboundOrderNo || '—' } ` : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . sourceType === 'RETURN_CARTON' ? '已签收退货 库存' : ( candidate . inboundCode || '—' ) ,
availablePackages : inventoryQuantity ( candidate . availablePackages ) ,
quantityUnit : candidate . sourceType === 'RETURN_CARTON' ? '件' : '箱' ,
} )
}
return [ ... grouped . values ( ) ]
@@ -165,7 +206,15 @@ const skuTotalCreateInput = ref<HTMLInputElement | null>(null)
let cargoSequence = 0
let skuRelabelSequence = 0
watch ( ( ) => props . service , ( ) => { mode . value = 'list' ; page . value = 1 ; resetFilters ( ) ; resetForm ( ) ; void loadPage ( ) } )
watch ( ( ) => [ props . service , props . createPage , props . detailPage , detailNumber . value ] as const , ( ) => {
mode . value = props . detailPage ? 'detail' : props . createPage ? 'create' : 'list'
page . value = 1
resetFilters ( )
resetForm ( )
if ( props . createPage ) void openCreate ( )
else if ( props . detailPage && detailNumber . value ) void loadDetailPage ( )
else void loadPage ( )
} )
watch ( mode , ( value ) => { if ( value !== 'detail' ) { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) } } )
watch ( ( ) => form . relabel , ( value ) => {
if ( props . service === 'transfer' && value ) {
@@ -194,7 +243,12 @@ async function loadPage() {
if ( props . service === 'labeling' || props . service === 'relabel' || props . service === 'transfer' || props . service === 'disposal' ) await loadWarehouses ( )
await load ( )
}
onMounted ( ( ) => { void loadPage ( ) ; void nextTick ( refreshWarehouseMarkLabels ) } )
onMounted ( ( ) => {
if ( props . createPage ) void openCreate ( )
else if ( props . detailPage && detailNumber . value ) void loadDetailPage ( )
else void loadPage ( )
void nextTick ( refreshWarehouseMarkLabels )
} )
onBeforeUnmount ( ( ) => { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) } )
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' } ) }
function resetForm ( ) {
@@ -209,17 +263,44 @@ function resetForm() {
selectedDiscardOrderId . value = ''
relabelCandidates . value = [ ]
selectedRelabelInboundOrderId . value = ''
cartonTotalFile . value = null
skuTotalFile . value = null
shippingLabelFile . value = null
if ( cartonTotalCreateInput . value ) cartonTotalCreateInput . value . value = ''
if ( skuTotalCreateInput . value ) skuTotalCreateInput . value . value = ''
transferDestinationType . value = 'NONE'
selectedOperationWarehouse . value = null
editingTransferNo . value = ''
}
async function openCreate ( ) {
resetForm ( ) ; mode . value = 'create'
if ( props . service === 'labeling' || props . service === 'relabel' || props . service === 'transfer' ) void loadWarehouses ( )
if ( props . service === 'labeling' ) return
if ( props . service === 'labeling' || props . service === 'relabel' || props . service === 'transfer' ) await loadWarehouses ( )
if ( props . service === 'labeling' ) {
const requestedReturnNo = String ( route . query . returnNo || '' ) . trim ( )
const requestedWarehouseId = String ( route . query . warehouseId || '' ) . trim ( )
if ( requestedReturnNo && requestedWarehouseId && operationWarehouses . value . some ( warehouse => String ( warehouse . id ) === requestedWarehouseId ) ) {
form . warehouse = requestedWarehouseId
await selectOperationWarehouse ( )
const sourceValue = ` RETURN_ORDER: ${ requestedReturnNo } `
if ( relabelInboundOptions . value . some ( option => option . value === sourceValue ) ) {
selectedRelabelInboundOrderId . value = sourceValue
selectRelabelInventory ( sourceValue )
} else ElMessage . warning ( ` 退货单 ${ requestedReturnNo } 暂无可申请贴标的库存 ` )
}
return
}
if ( props . service !== 'disposal' ) { addCargoRow ( ) ; return }
}
function returnToList ( ) {
if ( props . createPage || props . detailPage ) {
const routeName = ( { labeling : 'overseas-labeling' , transfer : 'overseas-storage-transfer' , disposal : 'overseas-cargo-disposal' , relabel : 'overseas-return-relabel' } as Record < ServiceType , string > ) [ props . service ]
void router . push ( { name : routeName } )
return
}
mode . value = 'list'
}
async function openEdit ( transferNo : string ) {
try {
resetForm ( )
@@ -280,6 +361,36 @@ async function selectDiscardWarehouse() {
}
function createCargoRow ( ) : CargoRow { return { id : ++ cargoSequence , sku : '' , name : '' , quantity : 1 , fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , cartonMark : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } }
function addCargoRow ( ) { cargoRows . value . push ( createCargoRow ( ) ) }
function escapePrintHtml ( value : string ) {
return value . replaceAll ( '&' , '&' ) . replaceAll ( '<' , '<' ) . replaceAll ( '>' , '>' ) . replaceAll ( '"' , '"' ) . replaceAll ( "'" , ''' )
}
function printTransferDraftCartonMarks ( ) {
if ( props . service !== 'transfer' ) return
const validRows = cargoRows . value . filter ( ( row ) => row . sku . trim ( ) && Number ( row . quantity ) > 0 )
if ( ! validRows . length || validRows . some ( ( row ) => ! Number . isInteger ( Number ( row . quantity ) ) ) ) {
ElMessage . warning ( '请先填写商品 SKU 和对应的整数箱数' ) ; return
}
const totalCartons = validRows . reduce ( ( sum , row ) => sum + Number ( row . quantity ) , 0 )
if ( totalCartons > 500 ) { ElMessage . warning ( '单次最多打印 500 张箱唛' ) ; return }
const batch = new Date ( ) . toISOString ( ) . replace ( /\D/g , '' ) . slice ( 2 , 14 )
const labels : Array < { mark : string ; sku : string ; index : number } > = [ ]
let cartonIndex = 0
for ( const [ rowIndex , row ] of validRows . entries ( ) ) {
let marks = transferCustomerMarks ( row )
if ( marks . length !== Number ( row . quantity ) ) {
marks = Array . from ( { length : Number ( row . quantity ) } , ( _ , index ) => ` CT- ${ batch } - ${ String ( rowIndex + 1 ) . padStart ( 2 , '0' ) } - ${ String ( index + 1 ) . padStart ( 3 , '0' ) } ` )
row . customerCartonMarks = marks . join ( '\n' )
}
for ( const mark of marks ) labels . push ( { mark , sku : row . sku . trim ( ) , index : ++ cartonIndex } )
}
const printWindow = window . open ( '' , '_blank' )
if ( ! printWindow ) { ElMessage . warning ( '浏览器阻止了打印窗口,请允许弹窗后重试' ) ; return }
printWindow . opener = null
const sections = labels . map ( ( label ) => ` <section class="label"><div class="title">仓储转运客户箱唛</div><div class="mark"> ${ escapePrintHtml ( label . mark ) } </div><div class="sku">SKU: ${ escapePrintHtml ( label . sku ) } </div><div class="meta"> ${ label . index } / ${ labels . length } </div></section> ` ) . join ( '' )
printWindow . document . open ( )
printWindow . document . write ( ` <!doctype html><html><head><meta charset="utf-8"><title>仓储转运客户箱唛</title><style>@page{size:100mm 100mm;margin:8mm}body{margin:0;font-family:Arial,'Microsoft YaHei',sans-serif;color:#111}.label{height:84mm;display:flex;flex-direction:column;align-items:center;justify-content:center;page-break-after:always;border:1px solid #222;box-sizing:border-box;padding:8mm}.title{font-size:18px;font-weight:700}.mark{margin:11mm 0 6mm;font-size:26px;font-weight:700;letter-spacing:1px;word-break:break-all;text-align:center}.sku{font-size:17px;font-weight:650}.meta{font-size:12px;margin-top:4mm}</style></head><body> ${ sections } </body></html> ` )
printWindow . document . close ( ) ; printWindow . focus ( ) ; printWindow . print ( )
}
function addTransferRelabelRow ( ) { transferRelabelRows . value . push ( createCargoRow ( ) ) }
function clearTransferRelabelRows ( ) {
transferRelabelRows . value = [ ]
@@ -308,8 +419,14 @@ function applyExistingCartonBinding(row: SkuRelabelRow) {
if ( ! row . destinationWarehouseId ) row . destinationWarehouseId = mapping . destinationWarehouseId
}
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 selectCartonTotalFile ( event : Event ) {
cartonTotalFile . value = ( event . target as HTMLInputElement ) . files ? . [ 0 ] || null
if ( cartonTotalFile . value && ! requiresBothTotalFiles . value ) clearCreateTotalFile ( 'SKU_TOTAL' )
}
function selectSkuTotalFile ( event : Event ) {
skuTotalFile . value = ( event . target as HTMLInputElement ) . files ? . [ 0 ] || null
if ( skuTotalFile . value && ! requiresBothTotalFiles . value ) clearCreateTotalFile ( 'CARTON_TOTAL' )
}
function clearCreateTotalFile ( scope : 'CARTON_TOTAL' | 'SKU_TOTAL' ) {
if ( saving . value ) return
const file = scope === 'CARTON_TOTAL' ? cartonTotalFile : skuTotalFile
@@ -606,7 +723,8 @@ function warehouseDisplay(value?: string) {
function statusLabel ( value : string ) {
if ( props . service === 'labeling' ) {
if ( value === 'PROCESSING' ) return '处理中'
return [ 'CUSTOMER_CONFIRM_PENDING' , 'READY_FOR_OUTBOUND' , 'COMPLETED' ] . includes ( value ) ? '换标完成' : '待处理 '
if ( value === 'CUSTOMER_CONFIRM_PENDING' ) return '待客户确认 '
return [ 'READY_FOR_OUTBOUND' , 'COMPLETED' ] . includes ( value ) ? '换标完成' : '待处理'
}
if ( props . service === 'transfer' ) {
const transferLabels : Record < string , string > = {
@@ -670,10 +788,51 @@ async function load() {
} catch ( error ) { submittedRows . value = [ ] ; total . value = 0 ; ElMessage . error ( error instanceof Error ? error . message : '列表加载失败' ) }
finally { loading . value = false }
}
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 ( ) }
function query ( ) { if ( page . value === 1 ) void load ( ) ; else page . value = 1 }
function resetListQuery ( ) { resetFilters ( ) ; query ( ) }
function listRowActions ( candidate : object ) : TableAction [ ] {
const item = candidate as Record < string , string >
const actions : TableAction [ ] = [ { key : 'detail' , label : '详情' , run : ( ) => openDetail ( item ) } ]
if ( canCreate . value && props . service === 'transfer' && item . _ _status === 'DRAFT' ) actions . push ( { key : 'edit' , label : '编辑' , run : ( ) => openEdit ( item [ '0' ] ) } )
if ( canConfirmTransfer . value && props . service === 'transfer' && item . _ _status === 'WAIT_CUSTOMER_CONFIRMATION' ) actions . push ( { key : 'confirm-measurement' , label : '确认测量' , run : ( ) => confirmTransferFromList ( item [ '0' ] ) } )
if ( canConfirmRelabel . value && props . service === 'labeling' && item . _ _status === 'CUSTOMER_CONFIRM_PENDING' && ! item . _ _confirmedAt ) actions . push ( { key : 'confirm-relabel' , label : '确认换标' , run : ( ) => confirmRelabelFromList ( item [ '0' ] ) } )
return actions
}
async function submitReturnRelabel ( ) {
const items = returnRelabelSkuRows . value . filter ( item => item . selected )
. map ( item => ( { skuCode : item . skuCode , quantity : Number ( item . quantity ) } ) )
if ( ! selectedReturnOrderNo . value || ! items . length ) { ElMessage . warning ( '请至少选择一个需要贴标的 SKU' ) ; return }
if ( items . some ( item => item . quantity < 1 || item . quantity > ( returnRelabelSkuRows . value . find ( row => row . skuCode === item . skuCode ) ? . availableQuantity || 0 ) ) ) {
ElMessage . warning ( '本次贴标数量必须大于 0,且不能超过该 SKU 的库存总数' ) ; return
}
const destination = fbaWarehouses . value . find ( warehouse => warehouse . warehouseCode === form . destination )
if ( ! destination ) { ElMessage . warning ( '请选择 FBA 目的仓' ) ; return }
const selectedFile = cartonTotalFile . value || skuTotalFile . value
const fileScope = cartonTotalFile . value ? 'CARTON_TOTAL' as const : 'SKU_TOTAL' as const
if ( ! selectedFile ) { ElMessage . warning ( '请上传箱唛总文件或 SKU 总文件(二选一)' ) ; return }
try { await ElMessageBox . confirm ( ` 确认提交退货单 ${ selectedReturnOrderNo . value } 的贴标申请吗? ` , '提交确认' , { confirmButtonText : '提交' , cancelButtonText : '取消' } ) } catch { return }
saving . value = true
try {
const created = await createReturnRelabel ( selectedReturnOrderNo . value , {
items ,
fileScope ,
destinationFbaWarehouseId : String ( destination . id ) ,
destinationFbaWarehouseCode : destination . warehouseCode ,
cartonRemark : form . remark . trim ( ) || undefined ,
idempotencyKey : crypto . randomUUID ( ) ,
} )
await uploadReturnRelabelFile ( created . relabelNo , fileScope , selectedFile )
ElMessage . success ( ` 贴标申请 ${ created . relabelNo } 已提交,等待审核 ` )
await router . push ( { name : 'overseas-labeling' } )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '退货贴标申请提交失败,请重试' ) }
finally { saving . value = false }
}
watch ( [ page , pageSize ] , ( [ nextPage , nextSize ] , [ previousPage , previousSize ] ) => {
if ( nextSize !== previousSize && nextPage !== 1 ) { page . value = 1 ; return }
void load ( )
} )
async function submit ( action : 'draft' | 'submit' = 'submit' ) {
if ( isReturnRelabelSource . value ) { await submitReturnRelabel ( ) ; return }
const primaryWarehouse = props . service === 'relabel' ? form . sourceWarehouse : form . warehouse
const relabelRows = labelingMode . value === 'carton' ? cartonDetailRows . value : skuRelabelRows . value
const invalidCargo = props . service === 'disposal'
@@ -690,6 +849,12 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' && ( ! form . labelDeadline || ! form . contact || ! form . phone ) ) {
ElMessage . warning ( '请填写贴标截止时间和海外仓联系人信息' ) ; return
}
if ( props . service === 'labeling' && requiresBothTotalFiles . value && ( ! cartonTotalFile . value || ! skuTotalFile . value ) ) {
ElMessage . warning ( 'SKU 和箱唛都换时,必须同时上传箱唛总文件和 SKU 总文件' ) ; return
}
if ( props . service === 'labeling' && ! requiresBothTotalFiles . value && ! cartonTotalFile . value && ! skuTotalFile . value ) {
ElMessage . warning ( '请上传箱唛总文件或 SKU 总文件(二选一)' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value !== 'sku' && ! form . deliveryMethod ) {
ElMessage . warning ( '请选择换标完成后的派送方式' ) ; return
}
@@ -725,6 +890,8 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'transfer' && form . relabel ) {
if ( ! form . labelDeadline || ! form . relabelContact || ! form . relabelPhone || ( labelingMode . value !== 'sku' && ! form . relabelDeliveryMethod ) ) { ElMessage . warning ( '请填写换标截止时间、换标联系人和换标后派送方式' ) ; return }
if ( ! transferRelabelDefaultDestination . value ) { ElMessage . warning ( '请选择整票换标货物的 FBA 目的仓' ) ; return }
if ( action === 'submit' && requiresBothTotalFiles . value && ( ! cartonTotalFile . value || ! skuTotalFile . value ) ) { ElMessage . warning ( 'SKU 和箱唛都换时,必须同时上传箱唛总文件和 SKU 总文件' ) ; return }
if ( action === 'submit' && ! requiresBothTotalFiles . value && ! cartonTotalFile . value && ! skuTotalFile . value ) { ElMessage . warning ( '请上传箱唛总文件或 SKU 总文件(二选一)' ) ; return }
const relabelRows = transferRelabelRows . value
const allMarks = relabelRows . flatMap ( transferCustomerMarks )
if ( ! relabelRows . length || relabelRows . some ( ( row ) => transferCustomerMarks ( row ) . length !== row . quantity ) ) { ElMessage . warning ( '每个换标明细的客户箱唛数量必须与换标数量一致' ) ; return }
@@ -752,12 +919,14 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
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 } )
if ( props . createPage ) await router . push ( { name : 'overseas-labeling-detail' , params : { detailNo : created . application . relabelNo } } )
else { mode . value = 'list' ; await load ( ) ; await openDetail ( { '0' : created . application . relabelNo } ) }
await ElMessageBox . alert ( ` 申请 ${ created . application . relabelNo } 已创建,部分附件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请在原单详情补传失败附件,不要重复下单。 ` , '申请已创建,附件待补传' , { confirmButtonText : '知道了' } )
return
}
ElMessage . success ( ` ${ config . value . title } 提交成功 ` )
await router . push ( { name : 'overseas-labeling-detail' , params : { detailNo : created . application . relabelNo } } )
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 ) ) {
@@ -779,17 +948,37 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
const transferNo = String ( created . transferNo || '' )
if ( ! transferNo ) throw new Error ( '转运草稿已保存,但未返回转运单号' )
if ( action === 'submit' ) {
await submitTransfer ( transferNo )
const submitted = await submitTransfer ( transferNo )
if ( form . relabel ) {
const relabelNo = String ( submitted . relabelNo || created . relabelNo || '' )
try {
if ( ! relabelNo ) throw new Error ( '转运单已提交,但未返回关联换标单号' )
if ( cartonTotalFile . value ) await uploadRelabelFile ( relabelNo , 'CARTON_TOTAL' , cartonTotalFile . value )
if ( skuTotalFile . value ) await uploadRelabelFile ( relabelNo , 'SKU_TOTAL' , skuTotalFile . value )
} catch ( error ) {
await ElMessageBox . alert ( ` 转运单 ${ transferNo } 已提交,但换标总文件上传失败: ${ error instanceof Error ? error . message : '上传失败' } 。请进入关联换标单详情补传,不要重复提交转运单。 ` , '转运单已提交,附件待补传' , { confirmButtonText : '知道了' } )
await openDetail ( { '0' : transferNo } )
return
}
}
ElMessage . success ( '转运单已提交审核' )
await openDetail ( { '0' : transferNo } )
return
}
}
ElMessage . success ( props . service === 'transfer' ? '转运单草稿保存成功' : ` ${ config . value . title } 提交成功 ` ) ; mode . value = 'list' ; await load ( )
ElMessage . success ( props . service === 'transfer' ? '转运单草稿保存成功' : ` ${ config . value . title } 提交成功 ` )
if ( props . createPage ) await router . push ( { name : props . service === 'relabel' ? 'overseas-return-relabel' : 'overseas-labeling' } )
else { mode . value = 'list' ; await load ( ) }
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '提交失败' ) }
finally { saving . value = false }
}
async function openDetail ( value : Record < string , string > ) {
if ( [ 'labeling' , 'transfer' , 'disposal' ] . includes ( props . service ) && ! props . detailPage ) {
const routeName = ( { labeling : 'overseas-labeling-detail' , transfer : 'overseas-storage-transfer-detail' , disposal : 'overseas-cargo-disposal-detail' } as Partial < Record < ServiceType , string > > ) [ props . service ]
if ( routeName ) void router . push ( { name : routeName , params : { detailNo : value [ '0' ] } } )
return
}
detailError . value = ''
try {
const detail = props . service === 'labeling' ? await getRelabel ( value [ '0' ] ) : props . service === 'disposal' ? await getDiscard ( value [ '0' ] ) : await getTransfer ( value [ '0' ] )
const discardSummary = props . service === 'disposal' ? ( detail as { summary ? : ServiceDetail } ) . summary : undefined
@@ -798,7 +987,11 @@ async function openDetail(value: Record<string, string>) {
mode . value = 'detail'
if ( props . service === 'labeling' ) void loadCompletionPhotos ( )
if ( props . service === 'transfer' ) void loadMeasurementPhotos ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '详情加载失败' ) }
} catch ( error ) { selectedDetail . value = null ; selectedRelabelDetail . value = null ; detailError . value = error instanceof Error ? error . message : '详情加载失败' ; mode . value = 'detail' ; ElMessage . error ( detailError . value ) }
}
async function loadDetailPage ( ) {
await loadWarehouses ( )
await openDetail ( { '0' : detailNumber . value } )
}
async function confirmTransferMeasurements ( ) {
const transferNo = String ( selectedDetail . value ? . transferNo || selectedDetail . value ? . [ '0' ] || '' )
@@ -963,15 +1156,31 @@ async function selectOperationWarehouse() {
}
function selectRelabelInventory ( inboundOrderId : string ) {
const rows : CargoRow [ ] = [ ]
for ( const candidate of relabelCandidates . value . filter ( ( item ) => ( item . sourceType === 'RETURN_CARTON' ? ` RETURN_CARTON: ${ item . returnCartonId } ` : String ( item . inboundOrderId ) ) === inboundOrderId ) ) {
if ( candidate . sourceType === 'RETURN_CARTON' ) rows . push ( {
id : ++ cargoSequence , sku : candidate . skuCode || '' , name : candidate . productNameZh || '退货合箱库存' ,
included : true , quantity : 1 , availableQuantity : 1 , returnCartonId : String ( candidate . returnCartonId ) ,
sourceInboundOrderNo : candidate . inboundOrderNo , sourceWarehouseName : candidate . overseasWarehouseName ,
fnsku : '' , shipmentNo : candidate . inboundOrderNo || candidate . cartonMark || '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' ,
cartonMark : candidate . cartonMark || '' , fbaCartonMark : '' , destinationWarehouseId : candidate . destinationFbaWarehouseCode || '' , remark : '' ,
} )
else for ( const position of candidate . positions ) rows . push ( ... Array . from ( { length : Math . min ( position . availablePackages , 500 ) } , ( ) => ( {
const selectedCandidates = relabelCandidates . value . filter ( ( item ) => {
const returnOrderNo = item . inboundOrderNo || String ( item . returnCartonId || '' )
return ( item . sourceType === 'RETURN_CARTON' ? ` RETURN_ORDER: ${ returnOrderNo } ` : String ( item . inboundOrderId ) ) === inboundOrderId
} )
returnRelabelSkuRows . value = [ ]
if ( inboundOrderId . startsWith ( 'RETURN_ORDER:' ) ) {
const grouped = new Map < string , ReturnRelabelSkuRow > ( )
for ( const candidate of selectedCandidates ) {
const skuItems = candidate . skuItems ? . length
? candidate . skuItems
: candidate . skuCode ? [ { skuCode : candidate . skuCode , quantity : inventoryQuantity ( candidate . availablePackages ) } ] : [ ]
for ( const item of skuItems ) {
const skuCode = item . skuCode . trim ( )
if ( ! skuCode ) continue
const key = skuCode . toUpperCase ( )
const current = grouped . get ( key )
const quantity = inventoryQuantity ( item . quantity )
if ( current ) current . availableQuantity += quantity
else grouped . set ( key , { skuCode , productNameZh : candidate . skuCode ? . trim ( ) . toUpperCase ( ) === key ? ( candidate . productNameZh || '' ) : '' , availableQuantity : quantity , selected : false , quantity } )
}
}
returnRelabelSkuRows . value = [ ... grouped . values ( ) ]
labelingMode . value = 'sku'
} else for ( const candidate of selectedCandidates ) {
for ( const position of candidate . positions ) rows . push ( ... Array . from ( { length : Math . min ( inventoryQuantity ( position . availablePackages ) , 500 ) } , ( ) => ( {
id : ++ cargoSequence , sku : candidate . skuCode || '' , name : candidate . productNameZh || '库存货物' ,
included : false , quantity : 1 , availableQuantity : 1 ,
sourceInventoryBalanceId : String ( candidate . inventoryBalanceId ) , sourceInventoryPositionId : String ( position . positionId ) ,
@@ -989,53 +1198,36 @@ function selectRelabelInventory(inboundOrderId: string) {
shippingLabelFile . value = null
cartonTotalFile . value = null
skuTotalFile . value = null
if ( labelingMode . value !== 'carton' ) addSkuRelabelRow ( )
if ( ! isReturnRelabelSource . value && labelingMode . value !== 'carton' ) addSkuRelabelRow ( )
}
< / script >
< template >
< section class = "warehouse-service-page" >
< section class = "warehouse-service-page" : class = "{ 'warehouse-service-page--detail': mode === 'detail', 'warehouse-service-page--return-relabel': isReturnRelabelSource }" >
< template v-if = "mode === 'list'" >
< div class = "warehouse-breadcrumb" > < span > 首页 < / span > < b > / < / b > < strong > { { config . title } } < / strong > < / div >
< div class = "warehouse-page-heading" > < h2 > { { config . title } } < / h2 > < el-button v-if = "canCreate" type="primary" @click="openCreate"><Plus :size="16" / > { { props . service === 'relabel' ? '新建退货换标单' : props . service === 'transfer' ? '新增订单' : ` 新增 ${ config . title } ` } } < / el-button > < / div >
< div class = "warehouse-card warehouse-filter-card ">
< div class = "warehouse-filter-grid" >
< label > < span > { { config . numberLabel } } < / span > < el-input v-model = "filters.number" :placeholder="`请输入${config.numberLabel}`" clearable / > < / label >
< label v-if = "props.service !== 'labeling' && props.service !== 'relabel'"><span>进仓编号</span><el-input v-model="filters.inboundNo" placeholder="请输入进仓编号" clearable / > < / label >
< label > < span > { { props . service === 'relabel' ? '来源 FBA 仓' : '操作仓' } } < / span > < el-select v-model = "filters.warehouse" clearable :loading="fbaWarehousesLoading" placeholder="全部"><el-option label="全部" value="" /><el-option v-for="warehouse in (props.service === 'relabel' ? fbaWarehouses : operationWarehouses)" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" / > < / el-select > < / label >
< label v-if = "props.service === 'transfer' || props.service === 'relabel'"><span>目的 FBA 仓</span><el-input v-model="filters.destination" placeholder="请输入仓代码" clearable / > < / label >
< label > < span > 订单状态 < / span > < el-select v-model = "filters.status" placeholder="全部状态" clearable><el-option v-for="option in statusOptions" :key="option.value" :label="option.label" :value="option.value" / > < / el-select > < / label >
< label > < span > 创建时间 < / span > < el-date-picker v-model = "filters.start" type="date" placeholder="开始日期" value-format="YYYY-MM-DD" / > < / label >
< label > < span > & nbsp ; < / span > < el-date-picker v-model = "filters.end" type="date" placeholder="结束日期" value-format="YYYY-MM-DD" / > < / label >
< / div >
< 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 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="canCreate && props.service === 'transfer' && row.__status === 'DRAFT'" native-type="button" size="small" type="primary" @click="openEdit(row['0'])">编辑</el-button><el-button v-if="canConfirmTransfer && props.service === 'transfer' && row.__status === 'WAIT_CUSTOMER_CONFIRMATION'" size="small" type="primary" @click="confirmTransferFromList(row['0'])">确认测量数据</el-button><el-button v-if="canConfirmRelabel && 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 >
< el-pagination v -model :current-page = "page" :page-size = "pageSize" :total = "total" :hide-on-single-page = "false" layout = "prev, pager, next" @ current -change = " changePage " / >
< / div >
< QueryToolbar v-model = "listQueryModel" layout="grid" :ariaLabel="`${config.title}筛选`" :fields="listQueryFields" :search-on-reset="false" @search="query" @reset="resetListQuery" / >
< div class = "warehouse-card warehouse-table-card" >
< DataTable v -model :page = "page" v -model :page-size = "pageSize" :data = "submittedRows" :columns = "listColumns" :loading = "loading" :total = "total" : page -sizes = " [ 15 , 30 , 50 , 100 ] " :row-actions = "listRowActions" :empty-text = "config.emptyText ">
< template # toolbar -actions > < el-button v-if = "canCreate" type="primary" @click="openCreate"><Plus :size="16" / > { { props . service === 'relabel' ? '新建退货换标单' : props . service === 'transfer' ? '新增订单' : ` 新增 ${ config . title } ` } } < / el-button > < / template >
< template v-if = "props.service === 'transfer' && submittedRows.some((item) => item.__status === 'WAIT_CUSTOMER_CONFIRMATION')" #before-table><div class="warehouse-list-notice" > 部分入库完成订单已有 PDA 实测数据 , 请在对应行确认测量数据 。 < / div > < / template >
< / DataTable >
< / div >
< / template >
< 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 >
< div class = "warehouse-page-heading" > < h2 > { { mode === 'edit' ? ` 编辑转运草稿 ${ editingTransferNo } ` : config . createTitle } } < / h2 > < el-button @click ="returnToList" > < 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-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 >
< label v-if = "props.service === 'disposal'" class="warehouse-span-2"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidates" :key="candidate.orderId" :label="`${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}|可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 仅显示当前操作仓下一件代发中仍有可用箱数的入库单 。 < / small > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-else-if = "props.service === 'disposal'"><span>进仓编号</span><el-input v-model="form.inboundNo" readonly / > < / label >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库海外仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(operationWarehousesError)" clearable :placeholder="props.service === 'transfer' ? '请选择海外仓' : '请选择国外中转仓'" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint">{{ operationWarehousesError }}</small><small v-else-if="props.service === 'labeling'" class="warehouse-hint">选择后仅查询当前客户在该中转仓的可用库存。</small><small v-else-if="props.service === 'transfer'" class="warehouse-hint" > 本批货物将先进入该海外仓完成签收和入库 。 < / small > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择有库存的历史 入仓单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前中转仓暂无已入库 可用库存" placeholder="请选择曾经下单且 当前仍有库存的入仓单" @change="selectRelabelInventory"><el-option v-for="option in relabelInboundOptions" :key="option.value" :label="`${option.inboundOrderNo}| 入仓码 ${option.inboundCode}|可用 ${option.availablePackages} 箱 `" :value="option.value" /></el-select><small class="warehouse-hint" > 仅显示当前客户在所选中转仓已经完成入库且仍有可用库存的历史入仓单 。 < / small > < / label >
< label v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>FBA 目的仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择本张申请统一的 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 整张贴标申请只能选择一个目的仓 , 全部明细统一使用该目的仓 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>货物如何送到海外仓 </span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < small > 仓库上门提货 : 填写货物当前地址 ; 客户送货到仓 : 下方自动展示海外仓收货信息 。 < / small > < / div >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库海外仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(operationWarehousesError)" clearable :placeholder="props.service === 'transfer' ? '请选择海外仓' : '请选择国外中转仓'" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint" > {{ operationWarehousesError }} < / small > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择有库存的入仓单或退货 单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :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} ${option.quantityUnit} `" :value="option.value" / > < / el-select > < / label >
< label v-if = "props.service === 'labeling' && (isReturnRelabelSource || labelingMode !== 'sku') "><span><i>*</i>FBA 目的仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择本张申请统一的 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>入仓方式 </span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < / div >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'" class="pickup-address-field"><span><i>*</i>提货地址</span><el-input v-model="form.address" placeholder="请输入仓库上门提货的详细地址" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人</span><el-input v-model="form.pickupContact" placeholder="请输入提货联系人" maxlength="128" / > < / label >
< label v-if = "props.service === 'transfer' && form.deliveryMethod === 'WAREHOUSE_PICKUP'"><span><i>*</i>提货联系人电话</span><el-input v-model="form.pickupPhone" placeholder="请输入提货联系人电话" maxlength="64" / > < / label >
@@ -1051,15 +1243,15 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓签收联系人<em v-if="form.warehouse && !form.contact" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.contact" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>海外仓联系电话<em v-if="form.warehouse && !form.phone" class="warehouse-unmaintained">未维护</em></span><el-input v-model="form.phone" placeholder="选择海外操作仓后自动带出" / > < / label >
< label v-if = "props.service === 'labeling'"><span><i>*</i>贴标截止时间</span><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" / > < / label >
< label v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>换标后派送方式</span><el-select v-model="form.deliveryMethod" placeholder="请选择派送方式"><el-option label="卡车派送" value="TRUCK" /><el-option label="快递派送" value="EXPRESS" /><el-option label="卡车自提" value="TRUCK_PICKUP" /><el-option label="快递自提" value="EXPRESS_PICKUP" /></el-select><small class="warehouse-hint" > 卡车派送进入预排车 ; 其余方式确认换标完成后进入换标出库 。 < / small > < / label >
< label v-if = "props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>换标后派送方式</span><el-select v-model="form.deliveryMethod" placeholder="请选择派送方式"><el-option label="卡车派送" value="TRUCK" /><el-option label="快递派送" value="EXPRESS" /><el-option label="卡车自提" value="TRUCK_PICKUP" /><el-option label="快递自提" value="EXPRESS_PICKUP" / > < / el-select > < / label >
< div v-if = "props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP')" class="warehouse-checkbox-field"><span>是否打印面单</span><el-checkbox v-model="form.relabel" > 需要仓库打印面单 < / el -checkbox > < / div >
< label v-if = "canManageRelabelFiles && props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel"><small class="warehouse-hint" > 支持 PDF 或图片 , 最大 20 MB 。 < / small > < / label >
< label v-if = "canManageRelabelFiles && props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel" > < / label >
< label v-if = "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' ? '预估体积(CBM) ' : ' 预估总体积(CBM) ' }} </span><el-input-number v-model="form.volumeCbm" :min="0" :precision="4" :step="0.0001" controls-position="right" placeholder="请输入预估总体积" / > < / label >
< label v-if = "props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>{{ props.service === 'transfer' ? '预计入仓时间' : '预计到达时间' }}</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" :placeholder="props.service === 'transfer' ? '请选择预计入仓时间' : '请选择预计到达时间'" / > < / label >
< label v-if = "props.service === 'transfer'"><span>预计发货时间</span><el-date-picker v-model="form.estimatedShipAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选填,可线下确认后补充" /><small class="warehouse-hint" > 暂未确定时可不填写 , 不影响货物先入海外仓 。 < / small > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否有 目的去向</span><el-radio-group v-model="transferDestinationType" @change="selectTransferDestinationType"><el-radio-button value="NONE">暂未确定</el-radio-button><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="PRIVATE" > 私人仓 < / el -radio -button > < / el-radio-group > < small > 暂未确定也可以提交 ; 完成入库后可在新建出库单中批量绑定目的地 。 < / small > < / div >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span><i>*</i>未换标货物目的 FBA 仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select><small class="warehouse-hint" > 此处只表示不换标箱子的去向 ; 需要换箱唛的箱子在换标明细中单独选择目的仓 。 < / small > < / label >
< label v-if = "props.service === 'transfer'"><span>预计发货时间</span><el-date-picker v-model="form.estimatedShipAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选填" / > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><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 > < / div >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span><i>*</i>FBA 目的 仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-if = "props.service === 'transfer' && transferDestinationType === 'FBA'"><span>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 >
@@ -1068,66 +1260,86 @@ function selectRelabelInventory(inboundOrderId: string) {
< 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 >
< label class = "warehouse-span-2" > < span > 订单 备注< / span > < el-input v-model = "form.remark" type="textarea" :rows="3 " placeholder="请输入备注信息 " / > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否需要 换标</span><el-radio-group v-model="form.relabel"><el-radio-button :value="false">不需要换标 </el-radio-button><el-radio-button :value="true" > 需要换标 < / el -radio -button > < / el-radio-group > < small > { { form . relabel ? '本次提交将同时登记换标申请,入库完成后自动进入换标作业。' : '入库完成且已有目的仓后,可直接进入排车流程。' } } < / small > < / div >
< label class = "warehouse-span-2" > < span > 备注 < / span > < el-input v-model = "form.remark" type="textarea" :rows="2 " placeholder="选填 " / > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>换标服务 </span><el-radio-group v-model="form.relabel"><el-radio-button :value="false">不需要</el-radio-button><el-radio-button :value="true" > 需要 < / el -radio -button > < / el-radio-group > < / div >
< / div >
< / div >
< div class = "warehouse-card warehouse-form-section warehouse-transfer-cargo-card" >
< template v-if = "props.service === 'labeling'" >
< div class = "warehouse-section-heading warehouse-labeling-heading" >
< h3 > 货物明细 < / h3 >
< div class = "warehouse-template-actions" >
< el-button v-if = "canManageRelabelFiles" size="small" @click="chooseCreateTotalFile('CARTON_TOTAL')"><Upload :size="15" / > 上传箱唛总文件 < / el-button >
< el-button v-if = "canManageRelabelFiles" size="small" @click="chooseCreateTotalFile('SKU_TOTAL')"><Upload :size="15" / > 上传 SKU 总文件 < / el-button >
< 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')" >
< input ref = "cartonTotalCreateInput" class = "warehouse-hidden-input" type = "file" accept = "application/pdf,.pdf" @change ="selectCartonTotalFile" >
< input ref = "skuTotalCreateInput" class = "warehouse-hidden-input" type = "file" accept = "application/pdf,.pdf" @change ="selectSkuTotalFile" >
< / div >
< div > < h3 > 货物明细 < / h3 > < / div >
< / div >
< div v-if = "canManageRelabelFiles" 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 class = "warehouse-file-groups" : class = "{ 'warehouse-file-groups--single': isReturnRelabelSource } ">
< section v-if = "canManageRelabelFiles" class="warehouse-file-group warehouse-file-group--primary" >
< div class = "warehouse-file-group__heading" > < div > < strong > < span > 1 < / span > 贴标总文件 < / strong > < / div > < em > { { requiresBothTotalFiles ? '必传 · 两份文件' : '必传 · 二选一' } } < / em > < / div >
< div class = "warehouse-file-options" >
< div class = "warehouse-file-option" : class = "{ 'is-selected': Boolean(cartonTotalFile) }" > < div class = "warehouse-file-option__name" > < strong > 箱唛总文件 < / strong > < / div > < el-button : type = "cartonTotalFile ? 'primary' : 'default'" plain @click ="chooseCreateTotalFile('CARTON_TOTAL')" > < Upload :size = "15" / > { { cartonTotalFile ? '重新上传' : '选择文件' } } < / el-button > < div class = "warehouse-file-option__status" : class = "{ 'is-empty': !cartonTotalFile }" > < span > { { cartonTotalFile ? . name || '尚未选择文件' } } < / span > < button v-if = "cartonTotalFile" type="button" class="warehouse-file-clear" title="清除箱唛总文件" aria-label="清除箱唛总文件" :disabled="saving" @click="clearCreateTotalFile('CARTON_TOTAL')"><X :size="14" / > < / button > < / div > < / div >
< div class = "warehouse-file-choice-or" : class = "{ 'is-both': requiresBothTotalFiles }" > { { requiresBothTotalFiles ? '和' : '或' } } < / div >
< div class = "warehouse-file-option" : class = "{ 'is-selected': Boolean(skuTotalFile) }" > < div class = "warehouse-file-option__name" > < strong > SKU 总文件 < / strong > < / div > < el-button : type = "skuTotalFile ? 'primary' : 'default'" plain @click ="chooseCreateTotalFile('SKU_TOTAL')" > < Upload :size = "15" / > { { skuTotalFile ? '重新上传' : '选择文件' } } < / el-button > < div class = "warehouse-file-option__status" : class = "{ 'is-empty': !skuTotalFile }" > < span > { { skuTotalFile ? . name || '尚未选择文件' } } < / span > < 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 > < / div > < / div >
< / div >
< / section >
< section v-if = "!isReturnRelabelSource" class="warehouse-file-group" >
< div class = "warehouse-file-group__heading" > < div > < strong > < span > 2 < / span > 明细关联模板 < / strong > < / div > < em > 按需使用 < / em > < / div >
< div class = "warehouse-template-list" >
< div > < span > < strong > SKU 关联 < / strong > < / span > < el-button size = "small" :disabled = "!cargoRows.length" @click ="downloadRelabelTemplate('sku')" > < Download :size = "15" / > 下载模板 < / el-button > < el-button type = "primary" plain size = "small" :disabled = "!cargoRows.length" @click ="skuTemplateInput?.click()" > < Upload :size = "15" / > 上传文件 < / el-button > < / div >
< div > < span > < strong > 箱号关联 < / strong > < / span > < el-button size = "small" :disabled = "!cargoRows.length" @click ="downloadRelabelTemplate('carton')" > < Download :size = "15" / > 下载模板 < / el-button > < el-button type = "primary" plain size = "small" :disabled = "!cargoRows.length" @click ="cartonTemplateInput?.click()" > < Upload :size = "15" / > 上传文件 < / el-button > < / div >
< div > < span > < strong > SKU 混箱 < / strong > < / span > < el-button size = "small" :disabled = "!cargoRows.length" @click ="downloadRelabelTemplate('mixed')" > < Download :size = "15" / > 下载模板 < / el-button > < el-button type = "primary" plain size = "small" :disabled = "!cargoRows.length" @click ="mixedTemplateInput?.click()" > < Upload :size = "15" / > 上传文件 < / el-button > < / div >
< / div >
< / section >
< / div >
< div class = "warehouse-labeling-mode" >
< input ref = "skuTemplateInput" class = "warehouse-hidden-input" type = "file" accept = ".csv,text/csv" @change ="importRelabelTemplate($event, 'sku')" > < input ref = "cartonTemplateInput" class = "warehouse-hidden-input" type = "file" accept = ".csv,text/csv" @change ="importRelabelTemplate($event, 'carton')" > < input ref = "mixedTemplateInput" class = "warehouse-hidden-input" type = "file" accept = ".csv,text/csv" @change ="importRelabelTemplate($event, 'mixed')" > < input ref = "cartonTotalCreateInput" class = "warehouse-hidden-input" type = "file" accept = "application/pdf,.pdf" @change ="selectCartonTotalFile" > < input ref = "skuTotalCreateInput" class = "warehouse-hidden-input" type = "file" accept = "application/pdf,.pdf" @change ="selectSkuTotalFile" >
< div v-if = "isReturnRelabelSource" class="warehouse-table-scroll" >
< table class = "warehouse-edit-table warehouse-return-relabel-table" >
< thead > < tr > < th > 选择 < / th > < th > SKU < / th > < th > 中文品名 < / th > < th > 库存总数 < / th > < th > 本次贴标数量 < / th > < / tr > < / thead >
< tbody > < tr v-for = "row in returnRelabelSkuRows" :key="row.skuCode"><td><el-checkbox v-model="row.selected" /></td><td><strong>{{ row.skuCode }}</strong></td><td>{{ row.productNameZh || '—' }}</td><td><strong>{{ row.availableQuantity }}</strong></td><td><el-input-number v-model="row.quantity" :disabled="!row.selected" :min="1" :max="row.availableQuantity" :precision="0" controls-position="right" placeholder="请输入贴标数量" /></td></tr><tr v-if="returnRelabelSkuRows.length === 0"><td colspan="5"><div class="warehouse-empty" > 该退货单暂无可申请贴标的 SKU 库存 < / div > < / td > < / tr > < / tbody >
< / table >
< / div >
< div v-if = "!isReturnRelabelSource" 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 v-if = "!isReturnRelabelSource && 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>上传后在下方回显,也可以手动添加;SKU 与箱号关联通过仓库唛头合并。系统只提供大概库存位置,不代表箱唛已绑定具体托盘。</p>< /div>
<div v-if=" ! isReturnRelabelSource " class="warehouse - section - heading warehouse - tab - actions ">
<div><strong>绑定明细</strong></div>
<div v-if=" labelingMode === 'carton' "><el-button type=" primary " size=" small " @click=" addRelabelCargoRow "><Plus :size=" 15 " />手动添加行</el-button><el-button size=" small " @click=" clearRelabelRows ">清空</el-button></div>
<div v-else><el-button type=" primary " size=" small " @click=" addSkuRelabelRow "><Plus :size=" 15 " />添加 SKU 明细</el-button><el-button size=" small " @click=" skuRelabelRows = [ ] ">清空</el-button></div>
</div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - label - table "><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数</th><th>仓库唛头 *</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in cartonDetailRows " :key=" row . id "><td><el-input v-model=" row . sku " placeholder=" 选填 " /></td><td><el-input v-model=" row . name " placeholder=" 请输入中文品名 " /></td><td><el-input v-model=" row . shipmentNo " placeholder=" 请输入FBA货件编号 " /></td><td><strong>1 箱</strong></td><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入明细备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 移出本次明细 " @click=" removeCargoRow ( row . id ) "><Trash2 :size=" 16 " />移出</button></td></tr><tr v-if=" cartonDetailRows . length === 0 "><td colspan=" 8 "><div class=" warehouse - empty ">暂无本次换标明细,请上传关系后导入或手动添加</div></td></tr></tbody></table></div>
<div v-if=" ! isReturnRelabelSource && labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - label - table "><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数</th><th>仓库唛头 *</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in cartonDetailRows " :key=" row . id "><td><el-input v-model=" row . sku " placeholder=" 选填 " /></td><td><el-input v-model=" row . name " placeholder=" 请输入中文品名 " /></td><td><el-input v-model=" row . shipmentNo " placeholder=" 请输入FBA货件编号 " /></td><td><strong>1 箱</strong></td><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入明细备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 移出本次明细 " @click=" removeCargoRow ( row . id ) "><Trash2 :size=" 16 " />移出</button></td></tr><tr v-if=" cartonDetailRows . length === 0 "><td colspan=" 8 "><div class=" warehouse - empty ">暂无本次换标明细,请上传关系后导入或手动添加</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - sku - label - table "><thead><tr><th>仓库唛头 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th v-if=" labelingMode === 'carton_and_sku' ">新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in skuRelabelRows " :key=" row . id "><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " @change=" applyExistingCartonBinding ( row ) " /></td><td><el-input v-model=" row . originalSku " placeholder=" 请输入旧 SKU " @change=" applyExistingSkuBinding ( row ) " /></td><td><el-input v-model=" row . newSku " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入件数 " /></td><td v-if=" labelingMode === 'carton_and_sku' "><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除 " @click=" removeSkuRelabelRow ( row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" skuRelabelRows . length === 0 "><td :colspan=" labelingMode === 'carton_and_sku' ? 7 : 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
</template>
<template v-else>
<div class=" warehouse - section - heading "><h3 >货物明细</h3><div v-if=" props . service !== 'disposal' "><el-button type=" primary " size=" small " @click=" addCargoRow "><Plus :size=" 15 " />添加行</el-button><el-button size=" small " @click=" cargoRows = [ ] ">清空</el-button></div></div>
<div class=" warehouse - section - heading " :class=" { 'warehouse-step-heading' : props . service === 'transfer' } "><div><h3><span v-if=" props . service === 'transfer' " class=" warehouse - step - number ">1</span >货物明细</h3></div>< div v-if="props . service !== 'disposal' " class=" warehouse - heading - actions "><el-button v-if=" props . service === 'transfer' " size=" small " :disabled=" ! cargoRows . some ( ( row ) => row . sku . trim ( ) && row . quantity > 0 ) " @click=" printTransferDraftCartonMarks "><Printer :size=" 15 " />打印唛头</el-button ><el-button type="primary " size=" small " @click=" addCargoRow "><Plus :size=" 15 " />添加行</el-button><el-button size=" small " @click=" cargoRows = [ ] ">清空</el-button></div></div>
<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 - section - heading warehouse - labeling - heading warehouse - step - heading "><div><h3><span class=" warehouse - step - number ">2</span>换标申请</h3 ></div></div>
<div class=" warehouse - form - grid warehouse - transfer - relabel - fields "><label><span><i>*</i>换标截止时间</span><el-date-picker v-model=" form . labelDeadline " type=" datetime " value-format=" YYYY - MM - DDTHH : mm : ss " placeholder=" 请选择截止时间 " /></label><label><span><i>*</i>换标联系人</span><el-input v-model=" form . relabelContact " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 128 " /></label><label><span><i>*</i>换标联系人电话</span><el-input v-model=" form . relabelPhone " placeholder=" 选择海外仓后自动带出 , 也可手动填写 " maxlength=" 64 " /></label><label v-if=" labelingMode !== 'sku' "><span><i>*</i>换标后派送方式</span><el-select v-model=" form . relabelDeliveryMethod " placeholder=" 请选择派送方式 "><el-option label=" 卡车派送 " value=" TRUCK " /><el-option label=" 快递派送 " value=" EXPRESS " /><el-option label=" 卡车自提 " value=" TRUCK _PICKUP " /><el-option label=" 快递自提 " value=" EXPRESS _PICKUP " /></el-select></label><label class=" warehouse - span - 2 "><span><i>*</i>整票换标货物目的 FBA 仓</span><el-select v-model=" transferRelabelDefaultDestination " :loading=" fbaWarehousesLoading " :disabled=" Boolean ( fbaWarehousesError ) " filterable placeholder=" 请选择整票货物的目的仓 "><el-option v-for=" warehouse in fbaWarehouses " :key=" warehouse . id " :label=" warehouseLabel ( warehouse ) " :value=" warehouse . warehouseCode " /></el-select><small class=" warehouse - hint ">本票所有换标货物统一前往该目的仓,暂不支持一票多个目的仓。</small></label><label class=" warehouse - span - 2 "><span>贴标要求</span><el-input v-model=" form . requirement " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入贴标要求 ( 选填 ) " /></label></div>
<div class=" warehouse - 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 class=" warehouse - labeling - mode "><span>换标类型</span><el-radio-group v-model=" labelingMode " aria-label=" 换标类型 "><el-radio-button value=" carton ">只换箱唛</el-radio-button><el-radio-button value=" sku ">只换 SKU 标签</el-radio-button><el-radio-button value=" carton _and _sku ">SKU 和箱唛都换</el-radio-button></el-radio-group></div>
<div class=" warehouse - transfer - file - groups ">
<section v-if=" canManageRelabelFiles " class=" warehouse - file - group warehouse - transfer - total - files ">
<div class=" warehouse - file - group _ _heading "><div><strong>贴标总文件</strong></div><em>{{ requiresBothTotalFiles ? '必传 · 两份文件' : '必传 · 二选一' }}</em></div>
<div class=" warehouse - file - options ">
<div class=" warehouse - file - option " :class=" { 'is-selected' : Boolean ( cartonTotalFile ) } "><div class=" warehouse - file - option _ _name "><strong>箱唛总文件</strong></div><el-button :type=" cartonTotalFile ? 'primary' : 'default' " plain @click=" chooseCreateTotalFile ( 'CARTON_TOTAL' ) "><Upload :size=" 15 " />{{ cartonTotalFile ? '重新上传' : '选择文件' }}</el-button><div class=" warehouse - file - option _ _status " :class=" { 'is-empty' : ! cartonTotalFile } "><span>{{ cartonTotalFile?.name || '尚未选择文件' }}</span><button v-if=" cartonTotalFile " type=" button " class=" warehouse - file - clear " title=" 清除箱唛总文件 " aria-label=" 清除箱唛总文件 " :disabled=" saving " @click=" clearCreateTotalFile ( 'CARTON_TOTAL' ) "><X :size=" 14 " /></button></div></div>
<div class=" warehouse - file - choice - or " :class=" { 'is-both' : requiresBothTotalFiles } ">{{ requiresBothTotalFiles ? '和' : '或' }}</div>
<div class=" warehouse - file - option " :class=" { 'is-selected' : Boolean ( skuTotalFile ) } "><div class=" warehouse - file - option _ _name "><strong>SKU 总文件</strong></div><el-button :type=" skuTotalFile ? 'primary' : 'default' " plain @click=" chooseCreateTotalFile ( 'SKU_TOTAL' ) "><Upload :size=" 15 " />{{ skuTotalFile ? '重新上传' : '选择文件' }}</el-button><div class=" warehouse - file - option _ _status " :class=" { 'is-empty' : ! skuTotalFile } "><span>{{ skuTotalFile?.name || '尚未选择文件' }}</span><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></div></div>
</div>
<input ref=" cartonTotalCreateInput " class=" warehouse - hidden - input " type=" file " accept=" application / pdf , . pdf " @change=" selectCartonTotalFile "><input ref=" skuTotalCreateInput " class=" warehouse - hidden - input " type=" file " accept=" application / pdf , . pdf " @change=" selectSkuTotalFile ">
</section>
<section class=" warehouse - file - group warehouse - transfer - template - group ">
<div class=" warehouse - file - group _ _heading "><div><strong><span>3</span>明细关联模板</strong></div><em>按需使用</em></div><div class=" warehouse - template - list "><div><span><strong>SKU 关联</strong></span><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'sku' ) "><Download :size=" 15 " />下载模板</el-button><el-button type=" primary " plain size=" small " :disabled=" ! cargoRows . length " @click=" skuTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传文件</el-button></div><div><span><strong>箱号关联</strong></span><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'carton' ) "><Download :size=" 15 " />下载模板</el-button><el-button type=" primary " plain size=" small " :disabled=" ! cargoRows . length " @click=" cartonTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传文件</el-button></div><div><span><strong>SKU 混箱</strong></span><el-button size=" small " :disabled=" ! cargoRows . length " @click=" downloadRelabelTemplate ( 'mixed' ) "><Download :size=" 15 " />下载模板</el-button><el-button type=" primary " plain size=" small " :disabled=" ! cargoRows . length " @click=" mixedTemplateInput ? . click ( ) "><Upload :size=" 15 " />上传文件</el-button></div></div>
<input ref=" skuTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'sku' ) "><input ref=" cartonTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'carton' ) "><input ref=" mixedTemplateInput " class=" warehouse - hidden - input " type=" file " accept=" . csv , text / csv " @change=" importRelabelTemplate ( $event , 'mixed' ) ">
</section>
</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 class=" warehouse - section - heading warehouse - tab - actions warehouse - binding - heading "><div><strong><span class=" warehouse - step - number ">4</span> 绑定明细</strong></div><div><el-button type="primary " size=" small " @click=" addTransferRelabelRow "><Plus :size=" 15 " />添加 SKU 明细</el-button><el-button size=" small " @click=" clearTransferRelabelRows ">清空</el-button></div></div>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 5 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加换标明细”</div></td></tr></tbody></table></div>
<div v-else-if=" labelingMode === 'sku' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . newSkuCode " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入箱数 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 6 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - transfer - mark - table "><thead><tr><th>客户箱唛号 *</th><th>旧 SKU *</th><th>新 SKU *</th><th>换标数量(件)*</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in transferRelabelRows " :key=" row . id "><td><el-input v-model=" row . customerCartonMarks " type=" textarea " :rows=" 2 " placeholder=" 请输入客户箱唛号 " /></td><td><el-input :model-value=" row . sku " readonly /></td><td><el-input v-model=" row . newSkuCode " placeholder=" 请输入新 SKU " /></td><td><el-input-number v-model=" row . quantity " :min=" 1 " :precision=" 0 " controls-position=" right " placeholder=" 请输入数量 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " placeholder=" 请输入备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 删除该明细 " @click=" transferRelabelRows = transferRelabelRows . filter ( ( item ) => item . id !== row . id ) "><Trash2 :size=" 16 " />删除</button></td></tr><tr v-if=" transferRelabelRows . length === 0 "><td colspan=" 7 "><div class=" warehouse - empty ">暂无绑定明细,请点击“添加 SKU 明细”</div></td></tr></tbody></table></div>
@@ -1137,9 +1349,13 @@ function selectRelabelInventory(inboundOrderId: string) {
</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=" canCancelDiscard && props . service === 'disposal' && selectedDetail ? . status === 'PENDING_REVIEW' " type="danger " @ click=" cancelSelectedDiscard ">取消申请</el-button><el-button v-if=" canCreate && props . service === 'transfer' && selectedDetail ? . status === 'DRAFT' " type=" primary " @click=" openEdit ( String ( selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || '' ) ) ">编辑</el-button><el-button v-if=" canConfirmTransfer && props . service === 'transfer' && selectedDetail ? . status === 'WAIT_CUSTOMER_CONFIRMATION' " type=" primary " @click=" confirmTransferMeasurements ">确认测量数据</el-button><el-button @click=" mode = 'list' "><ArrowLeft :size=" 16 " />返回列表</el- button></div></div>
<div class=" warehouse - card warehouse - form - section "><h3>订单概览</h3><div v-if=" props . service === 'transfer' " class="warehouse - detail - grid "><div><span>转运单号</span><strong>{{ selectedDetail?.transferNo || '—' }}</strong></div><div><span>入库箱数</span><strong>{{ selectedDetail?.packageCount ?? '—' }}</strong></div><div><span>海外仓</span><strong>{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}</strong></div><div><span>FBA 目的仓</span><strong>{{ selectedDetail?.destinationWarehouseName || warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}</strong></div><div><span>入仓方式</span><strong>{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}</strong></div><div><span>是否换标</span><strong>{{ selectedDetail?.needRelabel ? '是' : '否' }}</strong></div><div><span>状态</span><strong>{{ statusLabel(selectedDetail?.status || '') }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedDetail?.createdAt) }}</strong></div></div><div v-else class=" warehouse - detail - grid "><div v-for=" ( column , index ) in config . columns . slice ( 1 ) " :key=" column "><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div ></div>
<header class=" warehouse - detail - header " >
<button type=" button " class=" warehouse - detail - back " @click=" returnToList "><ArrowLeft :size=" 16 " />返回列表</button>
<div class=" warehouse - detail - title "><strong>{{ props.service === 'labeling' ? selectedRelabelDetail?.application.relabelNo : selectedDetail?.transferNo || selectedDetail?.['0'] || `${config.title}详情` }}</strong><span>{{ config.title }}详情 </span ></div>
<div class=" warehouse - detail - actions "><el-button v-if=" canCancelDiscard && props . service === 'disposal' && selectedDetail ? . status === 'PENDING_REVIEW' " type=" danger " @click=" cancelSelectedDiscard ">取消申请</el-button><el-button v-if=" canCreate && props . service === 'transfer' && selectedDetail ? . status === 'DRAFT' " type=" primary " @click=" openEdit ( String ( selectedDetail ? . transferNo || selectedDetail ? . [ '0' ] || '' ) ) ">编辑</el-button><el-button v-if=" canConfirmTransfer && props . service === 'transfer' && selectedDetail ? . status === 'WAIT_CUSTOMER_CONFIRMATION' " type=" primary " @click=" confirmTransferMeasurements ">确认测量数据</el-button><el-tag effect=" light ">{{ statusLabel(props.service === 'labeling' ? selectedRelabelDetail?.application.status || '' : String(selectedDetail?.status || '')) }}</el-tag></div>
</header>
<div v-if=" detailError " class=" warehouse - detail - error " role=" alert "><strong>{{ detailError }}</strong><el-button @click=" loadDetailPage ">重新加载</el-button></div>
<div v-else class=" warehouse - card warehouse - form - section "><h3>订单概览</h3><div v-if=" props . service === 'labeling' " class=" warehouse - detail - grid "><div><span>操作仓</span><strong>{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}</strong></div><div><span>客户参考号</span><strong>{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}</strong></div><div><span>SKU 数</span><strong>{{ selectedRelabelDetail?.application.itemCount ?? '—' }}</strong></div><div><span>总箱数</span><strong>{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}</strong></div><div><span>贴标截止时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}</strong></div><div><span>仓库联系人</span><strong>{{ selectedRelabelDetail?.application.contactName || '—' }}</strong></div><div><span>联系电话</span><strong>{{ selectedRelabelDetail?.application.contactPhone || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}</strong></div></div><div v-else 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 - 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>
@@ -1153,7 +1369,7 @@ function selectRelabelInventory(inboundOrderId: string) {
<div class=" warehouse - section - heading "><h3>换标完成与出库</h3></div>
<div class=" warehouse - detail - grid warehouse - fulfillment - grid ">
<div><span>派送方式</span><strong>{{ deliveryMethodLabel(selectedRelabelDetail?.application.deliveryMethod || '') || '无需出库' }}</strong></div>
<div><span>履约状态</span><strong>{{ selectedRelabelDetail?.application.fulfillmentStatus || '— ' }}</strong></div>
<div><span>履约状态</span><strong>{{ statusLabel( selectedRelabelDetail?.application.fulfillmentStatus || '') }}</strong></div>
<div><span>客户确认时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.customerConfirmedAt) }}</strong></div>
</div>
<div class=" warehouse - evidence "><span>仓库完工照片</span><div v-if=" selectedRelabelDetail ? . completionEvidence ? . photoFileIds ? . length " class=" warehouse - photo - grid "><div v-for=" ( fileId , index ) in selectedRelabelDetail . completionEvidence . photoFileIds " :key=" fileId " class=" warehouse - photo - item "><el-image v-if=" completionPhotoUrls [ fileId ] " :src=" completionPhotoUrls [ fileId ] " :preview-src-list=" completionPhotoPreviewUrls " :initial-index=" completionPhotoPreviewUrls . indexOf ( completionPhotoUrls [ fileId ] ) " fit=" cover " preview-teleported /><div v-else-if=" completionPhotoLoading [ fileId ] " class=" warehouse - photo - placeholder ">照片 {{ index + 1 }} 加载中…</div><button v-else class=" warehouse - photo - placeholder warehouse - photo - retry " type=" button " @click=" loadCompletionPhoto ( fileId ) ">{{ completionPhotoErrors[fileId] ? `照片 ${index + 1} 加载失败,点击重试` : `加载照片 ${index + 1}` }}</button></div></div><strong v-else>仓库尚未上传完工照片</strong></div>
@@ -1185,24 +1401,29 @@ function selectRelabelInventory(inboundOrderId: string) {
< / template >
< style scoped >
. warehouse - service - page { color : # 25324 a } . warehouse - breadcrumb { display : flex ; align - items : center ; gap : 9 px ; margin : 0 0 14 px ; color : # 9 aa3b2 ; font - size : 12 px } . warehouse - breadcrumb b { font - weight : 400 } . warehouse - breadcrumb strong { color : # 606 b7c } . warehouse - breadcrumb button { padding : 0 ; border : 0 ; background : transparent ; color : # 4376 b8 ; cursor : pointer } . warehouse - page - heading { display : flex ; align - items : center ; justify - content : space - between ; margin - bottom : 16 px } . warehouse - page - heading h2 { margin : 0 ; color : # 17233 a ; font - size : 22 px ; letter - spacing : 0 } . warehouse - card { margin - bottom : 12 px ; border : 1 px solid # e7ebf1 ; border - radius : 4 px ; background : # fff ; box - shadow : 0 1 px 3 px rgba ( 25 , 42 , 70 , .04 ) } . warehouse - filter - card { padding : 20 px 22 px 16 px } . warehouse - filter - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 16 px 20 px } . warehouse - filter - grid label , . warehouse - form - grid label { display : flex ; min - width : 0 ; flex - direction : column ; gap : 7 px } . warehouse - filter - grid label > span , . warehouse - form - grid label > span { color : # 596579 ; font - size : 12 px } . warehouse - filter - actions { display : flex ; justify - content : flex - end ; gap : 8 px ; margin - top : 18 px } . warehouse - table - card { overflow : hidden } . warehouse - table - scroll { overflow - x : auto } table { width : 100 % ; min - width : 940 px ; border - collapse : collapse ; font - size : 12 px } th { height : 44 px ; padding : 0 14 px ; border - bottom : 1 px solid # e8edf3 ; background : # f7f9fc ; color : # 4 d5a6e ; font - weight : 650 ; text - align : left ; white - space : nowrap } td { height : 48 px ; padding : 8 px 14 px ; border - bottom : 1 px solid # eef1f5 ; color : # 526176 ; white - space : nowrap } . warehouse - empty { display : grid ; height : 150 px ; color : # a0a9b7 ; place - items : center } . warehouse - link , . warehouse - delete { display : inline - flex ; align - items : center ; gap : 5 px ; padding : 4 px ; border : 0 ; background : transparent ; color : # 2176 d2 ; cursor : pointer } . warehouse - delete { color : # d64545 } . warehouse - pagination { display : flex ; align - items : center ; justify - content : flex - end ; gap : 8 px ; padding : 14 px 18 px ; color : # 778398 ; font - size : 12 px } . warehouse - pagination span { margin - right : 8 px } . warehouse - page - number { width : 28 px ; height : 28 px ; border : 1 px solid # 2176 d2 ; border - radius : 3 px ; background : # 2176 d2 ; color : # fff } . warehouse - form - section { padding : 22 px } . warehouse - form - section h3 { margin : 0 0 18 px ; padding - bottom : 12 px ; border - bottom : 1 px solid # edf0f4 ; color : # 26364 f ; font - size : 15 px ; letter - spacing : 0 } . warehouse - form - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 240 px , 1 fr ) ) ; gap : 18 px 28 px } . warehouse - form - grid i { margin - right : 3 px ; color : # e24b4b ; font - style : normal } . warehouse - span - 2 { grid - column : 1 / - 1 } . warehouse - section - heading { display : flex ; align - items : center ; justify - content : space - between } . warehouse - section - heading h3 { flex : 1 } . warehouse - edit - table td { padding : 9 px 10 px } . warehouse - edit - table : deep ( . el - input - number ) { width : 130 px } . warehouse - hint { margin : 12 px 0 0 ; color : # 8792 a2 ; font - size : 12 px } . warehouse - submit - bar { display : flex ; justify - content : center ; gap : 10 px ; padding : 12 px 0 30 px } . warehouse - detail - grid { display : grid ; grid - template - columns : repeat ( 3 , 1 fr ) ; gap : 24 px } . warehouse - detail - grid div { display : flex ; flex - direction : column ; gap : 7 px } . warehouse - detail - grid span { color : # 8 a95a5 ; font - size : 12 px } . warehouse - detail - grid strong { color : # 334157 ; font - size : 13 px } : deep ( . el - button ) { border - radius : 4 px } : deep ( . el - button span ) { display : inline - flex ; align - items : center ; gap : 6 px } : deep ( . el - select ) , : deep ( . el - date - editor . el - input ) { width : 100 % } @ media ( max - width : 1100 px ) { . warehouse - filter - grid { grid - template - columns : repeat ( 2 , 1 fr ) } . warehouse - detail - grid { grid - template - columns : repeat ( 2 , 1 fr ) } }
. warehouse - service - page { display : grid ; gap : 12 px ; color : # 25324 a } . warehouse - breadcrumb { display : flex ; align - items : center ; gap : 9 px ; margin : 0 0 14 px ; color : # 9 aa3b2 ; font - size : 12 px } . warehouse - breadcrumb b { font - weight : 400 } . warehouse - breadcrumb strong { color : # 606 b7c } . warehouse - breadcrumb button { padding : 0 ; border : 0 ; background : transparent ; color : # 4376 b8 ; cursor : pointer } . warehouse - page - heading { display : flex ; align - items : center ; justify - content : space - between ; margin - bottom : 16 px } . warehouse - page - heading h2 { margin : 0 ; color : # 17233 a ; font - size : 22 px ; letter - spacing : 0 } . warehouse - card { margin - bottom : 0 ; border : 1 px solid var ( -- client - border ) ; border - radius : var ( -- client - radius ) ; background : # fff ; box - shadow : none } . warehouse - filter - card { padding : 20 px 22 px 16 px } . warehouse - filter - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 180 px , 1 fr ) ) ; gap : 16 px 20 px } . warehouse - filter - grid label , . warehouse - form - grid label { display : flex ; min - width : 0 ; flex - direction : column ; gap : 7 px } . warehouse - filter - grid label > span , . warehouse - form - grid label > span { color : # 596579 ; font - size : 12 px } . warehouse - filter - actions { display : flex ; justify - content : flex - end ; gap : 8 px ; margin - top : 18 px } . warehouse - table - card { overflow : hidden } . warehouse - list - notice { padding : 8 px 12 px ; border - bottom : 1 px solid var ( -- client - border ) ; background : # fff8e8 ; color : # 8 a6116 ; font - size : 12 px } . warehouse - table - scroll { overflow - x : auto } table { width : 100 % ; min - width : 940 px ; border - collapse : collapse ; font - size : 12 px } th { height : 44 px ; padding : 0 14 px ; border - bottom : 1 px solid # e8edf3 ; background : # f7f9fc ; color : # 4 d5a6e ; font - weight : 650 ; text - align : left ; white - space : nowrap } td { height : 48 px ; padding : 8 px 14 px ; border - bottom : 1 px solid # eef1f5 ; color : # 526176 ; white - space : nowrap } . warehouse - empty { display : grid ; height : 72 px ; color : # a0a9b7 ; place - items : center } . warehouse - link , . warehouse - delete { display : inline - flex ; align - items : center ; gap : 5 px ; padding : 4 px ; border : 0 ; background : transparent ; color : # 2176 d2 ; cursor : pointer } . warehouse - delete { color : # d64545 } . warehouse - pagination { display : flex ; align - items : center ; justify - content : flex - end ; gap : 8 px ; padding : 14 px 18 px ; color : # 778398 ; font - size : 12 px } . warehouse - pagination span { margin - right : 8 px } . warehouse - page - number { width : 28 px ; height : 28 px ; border : 1 px solid # 2176 d2 ; border - radius : 3 px ; background : # 2176 d2 ; color : # fff } . warehouse - form - section { padding : 22 px } . warehouse - form - section h3 { margin : 0 0 18 px ; padding - bottom : 12 px ; border - bottom : 1 px solid # edf0f4 ; color : # 26364 f ; font - size : 15 px ; letter - spacing : 0 } . warehouse - form - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 240 px , 1 fr ) ) ; gap : 18 px 28 px } . warehouse - form - grid i { margin - right : 3 px ; color : # e24b4b ; font - style : normal } . warehouse - span - 2 { grid - column : 1 / - 1 } . warehouse - section - heading { display : flex ; align - items : center ; justify - content : space - between } . warehouse - section - heading h3 { flex : 1 } . warehouse - edit - table td { padding : 9 px 10 px } . warehouse - edit - table : deep ( . el - input - number ) { width : 130 px } . warehouse - hint { margin : 12 px 0 0 ; color : # 8792 a2 ; font - size : 12 px } . warehouse - submit - bar { display : flex ; justify - content : center ; gap : 10 px ; padding : 12 px 0 30 px } . warehouse - detail - grid { display : grid ; grid - template - columns : repeat ( 3 , 1 fr ) ; gap : 24 px } . warehouse - detail - grid div { display : flex ; flex - direction : column ; gap : 7 px } . warehouse - detail - grid span { color : # 8 a95a5 ; font - size : 12 px } . warehouse - detail - grid strong { color : # 334157 ; font - size : 13 px } : deep ( . el - button ) { border - radius : 4 px } : deep ( . el - button span ) { display : inline - flex ; align - items : center ; gap : 6 px } : deep ( . el - select ) , : deep ( . el - date - editor . el - input ) { width : 100 % } @ media ( max - width : 1100 px ) { . warehouse - filter - grid { grid - template - columns : repeat ( 2 , 1 fr ) } . warehouse - detail - grid { grid - template - columns : repeat ( 2 , 1 fr ) } }
. warehouse - service - page -- detail { display : block ; overflow : hidden ; border : 1 px solid var ( -- client - border ) ; border - radius : var ( -- client - radius ) ; background : # fff } . warehouse - detail - header { display : grid ; grid - template - columns : auto minmax ( 0 , 1 fr ) auto ; align - items : center ; gap : 14 px ; min - height : 64 px ; padding : 10 px 16 px ; border - bottom : 1 px solid var ( -- client - border ) } . warehouse - detail - back { display : inline - flex ; align - items : center ; gap : 5 px ; padding : 5 px 0 ; border : 0 ; background : transparent ; color : var ( -- el - color - primary ) ; cursor : pointer } . warehouse - detail - title { display : flex ; min - width : 0 ; flex - direction : column ; gap : 3 px } . warehouse - detail - title strong { overflow : hidden ; color : # 1 f2d45 ; font - size : 18 px ; text - overflow : ellipsis ; white - space : nowrap } . warehouse - detail - title span { color : # 7 c899c ; font - size : 12 px } . warehouse - detail - actions { display : flex ; align - items : center ; gap : 8 px } . warehouse - service - page -- detail > . warehouse - card { border : 0 ; border - top : 1 px solid var ( -- client - border ) ; border - radius : 0 } . warehouse - service - page -- detail > . warehouse - detail - header + . warehouse - card { border - top : 0 } . warehouse - service - page -- detail > . warehouse - form - section { padding : 0 } . warehouse - service - page -- detail . warehouse - form - section h3 { min - height : 40 px ; margin : 0 ; padding : 11 px 16 px 9 px ; border - bottom : 1 px solid # edf1f5 ; font - size : 14 px } . warehouse - service - page -- detail . warehouse - detail - grid { grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; gap : 0 ; margin : 0 } . warehouse - service - page -- detail . warehouse - detail - grid > div { min - height : 48 px ; flex - direction : row ; align - items : center ; gap : 10 px ; padding : 8 px 14 px ; border - right : 1 px solid # edf1f5 ; border - bottom : 1 px solid # edf1f5 } . warehouse - service - page -- detail . warehouse - detail - grid span { flex : 0 0 90 px } . warehouse - service - page -- detail . warehouse - detail - grid strong { min - width : 0 ; overflow - wrap : anywhere } . warehouse - service - page -- detail . warehouse - fulfillment - grid { margin - bottom : 0 } . warehouse - service - page -- detail . warehouse - evidence { margin : 12 px 14 px } . warehouse - service - page -- detail . warehouse - completion - remark , . warehouse - service - page -- detail . warehouse - hint { margin : 10 px 14 px } . warehouse - service - page -- detail . warehouse - table - scroll table { margin : 0 } . warehouse - service - page -- detail . warehouse - empty { height : 64 px } @ media ( max - width : 1100 px ) { . warehouse - service - page -- detail . warehouse - detail - grid { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) } } @ media ( max - width : 720 px ) { . warehouse - detail - header { grid - template - columns : 1 fr auto } . warehouse - detail - back { grid - column : 1 / - 1 } . warehouse - detail - actions { justify - content : flex - end } }
. warehouse - detail - error { display : flex ; min - height : 220 px ; align - items : center ; justify - content : center ; gap : 12 px ; color : # b42318 }
. warehouse - checkbox - field { display : flex ; min - height : 32 px ; align - items : center ; justify - content : flex - start ; gap : 16 px } . warehouse - checkbox - field > span { color : # 596579 ; font - size : 12 px } . warehouse - checkbox - field : deep ( . el - checkbox ) { margin - right : 0 }
. 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 - groups { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1 fr ) ; gap : 16 px ; margin : 0 0 18 px } . warehouse - file - groups -- single { grid - template - columns : minmax ( 0 , 1 fr ) } . warehouse - file - group { min - width : 0 ; overflow : hidden ; border : 1 px solid # dde5f0 ; border - radius : 8 px ; background : # fbfcfe ; box - shadow : 0 1 px 2 px rgba ( 31 , 54 , 86 , .04 ) } . warehouse - file - group -- primary { border - color : # cbdcf2 ; background : linear - gradient ( 180 deg , # f7faff 0 , # fbfdff 100 % ) } . warehouse - file - group _ _heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 16 px ; padding : 14 px 16 px ; border - bottom : 1 px solid # e7edf5 ; background : rgba ( 255 , 255 , 255 , .68 ) } . warehouse - file - group _ _heading strong { display : flex ; align - items : center ; gap : 8 px ; color : # 2 d405c ; font - size : 14 px } . warehouse - file - group _ _heading strong > span { display : inline - grid ; width : 22 px ; height : 22 px ; border - radius : 50 % ; background : # 3478 c8 ; color : # fff ; font - size : 12 px ; place - items : center } . warehouse - file - group _ _heading em { flex - shrink : 0 ; padding : 4 px 8 px ; border - radius : 10 px ; background : # edf4fd ; color : # 4774 aa ; font - size : 11 px ; font - style : normal } . warehouse - file - options { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) 28 px minmax ( 0 , 1 fr ) ; align - items : stretch ; padding : 14 px } . warehouse - file - option { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) auto ; gap : 10 px 12 px ; align - items : center ; min - width : 0 ; padding : 12 px ; border : 1 px dashed # ced8e6 ; border - radius : 6 px ; background : # fff } . warehouse - file - option . is - selected { border - style : solid ; border - color : # 79 a8df ; background : # f5f9ff } . warehouse - file - option _ _name strong { color : # 34445 a ; font - size : 13 px } . warehouse - file - option _ _status { display : flex ; grid - column : 1 / - 1 ; align - items : center ; justify - content : space - between ; gap : 8 px ; min - width : 0 ; padding : 7 px 8 px ; border - radius : 4 px ; background : # eaf3ff ; color : # 376 da9 ; font - size : 11 px } . warehouse - file - option _ _status > span { overflow : hidden ; text - overflow : ellipsis ; white - space : nowrap } . warehouse - file - option _ _status . is - empty { background : # f4f6f8 ; color : # 9 aa4b2 } . warehouse - file - choice - or { display : grid ; color : # 9 ba6b5 ; font - size : 11 px ; place - items : center } . warehouse - file - choice - or . is - both { color : # 3478 c8 ; font - weight : 700 } . warehouse - template - list { display : flex ; flex - direction : column ; padding : 7 px 14 px } . warehouse - template - list > div { display : grid ; grid - template - columns : minmax ( 150 px , 1 fr ) auto auto ; align - items : center ; gap : 8 px ; padding : 9 px 0 ; border - bottom : 1 px solid # edf1f6 } . warehouse - template - list > div : last - child { border - bottom : 0 } . warehouse - template - list strong { color : # 3 f4e63 ; font - size : 12 px } . warehouse - transfer - template - group { margin : 0 0 14 px } @ media ( max - width : 1180 px ) { . warehouse - file - groups { grid - template - columns : minmax ( 0 , 1 fr ) } } @ media ( max - width : 720 px ) { . warehouse - file - options { grid - template - columns : minmax ( 0 , 1 fr ) } . warehouse - file - choice - or { height : 24 px } . warehouse - template - list > div { grid - template - columns : minmax ( 0 , 1 fr ) auto } . warehouse - template - list > div > span { grid - column : 1 / - 1 } }
. 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 - service - page -- return - relabel . warehouse - table - scroll : has ( > . warehouse - return - relabel - table ) ~ . warehouse - table - scroll { display : none }
. 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 }
. warehouse - transfer - relabel . warehouse - labeling - heading > div : first - child { min - width : 0 } . warehouse - transfer - relabel . warehouse - labeling - heading h3 { margin - bottom : 6 px } . warehouse - transfer - relabel . warehouse - labeling - heading p { margin : 0 0 16 px ; color : # 7 d8797 ; font - size : 12 px } . warehouse - transfer - relabel - fields { margin - bottom : 18 px } . warehouse - transfer - file - groups { display : grid ; grid - template - columns : minmax ( 0 , 1.15 fr ) minmax ( 360 px , .85 fr ) ; align - items : stretch ; gap : 14 px ; margin - bottom : 14 px } . warehouse - transfer - total - files , . warehouse - transfer - template - group { height : 100 % ; margin : 0 } . warehouse - transfer - mark - table { min - width : 1420 px } . warehouse - transfer - mark - table td { vertical - align : top } . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 220 px } . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 150 px } . warehouse - count - error { color : # d64545 }
. warehouse - transfer - form { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; gap : 12 px } . warehouse - transfer - form -- relabel { grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1.35 fr ) ; align - items : start } . warehouse - transfer - form -- relabel . warehouse - transfer - main - card { grid - column : 1 ; grid - row : 1 } . warehouse - transfer - form -- relabel . warehouse - transfer - cargo - card { grid - column : 1 ; grid - row : 2 } . warehouse - transfer - form -- relabel . warehouse - transfer - relabel { grid - column : 2 ; grid - row : 1 / span 2 ; margin - bottom : 0 } . warehouse - transfer - form -- relabel . warehouse - transfer - submit - bar { grid - column : 1 / - 1 } . warehouse - transfer - form : deep ( . el - input _ _wrapper ) , . warehouse - transfer - form : deep ( . el - textarea _ _inner ) , . warehouse - transfer - form : deep ( . el - select _ _wrapper ) , . warehouse - transfer - form : deep ( . el - date - editor . el - input _ _wrapper ) { min - height : 32 px } . warehouse - transfer - form . warehouse - form - section { padding : 16 px } . warehouse - transfer - form . warehouse - form - section h3 { margin - bottom : 12 px ; padding - bottom : 9 px ; font - size : 14 px } . warehouse - transfer - form . warehouse - form - grid { gap : 12 px 16 px } . warehouse - transfer - form . warehouse - choice - field { min - height : 34 px ; padding : 8 px 10 px ; grid - template - columns : 118 px minmax ( 0 , 1 fr ) ; gap : 10 px } . warehouse - transfer - form . warehouse - choice - field > span { font - size : 12 px } . warehouse - transfer - form . warehouse - receiving - card { gap : 12 px ; padding : 10 px 12 px } . warehouse - transfer - form . warehouse - section - heading { margin - bottom : 8 px } . warehouse - transfer - form . warehouse - edit - table { font - size : 11 px } . warehouse - transfer - form . warehouse - edit - table th { height : 36 px ; padding : 0 9 px } . warehouse - transfer - form . warehouse - edit - table td { height : 42 px ; padding : 6 px 7 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - textarea ) { min - width : 150 px } . warehouse - transfer - form . warehouse - transfer - mark - table : deep ( . el - input - number ) { width : 112 px } . warehouse - transfer - form . warehouse - hint { margin - top : 5 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - labeling - mode { gap : 9 px ; margin - bottom : 10 px ; padding : 9 px 10 px } . warehouse - transfer - form . warehouse - labeling - mode > span { font - size : 12 px } . warehouse - transfer - form . warehouse - labeling - mode : deep ( . el - radio - button _ _inner ) { padding : 6 px 9 px } . warehouse - transfer - form . warehouse - template - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 5 px ; padding - bottom : 8 px } . warehouse - transfer - form . warehouse - template - actions : deep ( . el - button ) { padding : 6 px 8 px ; font - size : 11 px } . warehouse - transfer - form . warehouse - submit - bar { padding : 4 px 0 18 px } . warehouse - transfer - form . warehouse - submit - bar : deep ( . el - button ) { min - height : 32 px ; padding : 7 px 14 px } . warehouse - transfer - form . warehouse - transfer - relabel { overflow : hidden } . warehouse - transfer - form . warehouse - transfer - relabel - fields { margin - bottom : 10 px } . warehouse - step - heading { align - items : flex - start } . warehouse - step - heading h3 { display : flex ; align - items : center ; gap : 9 px } . warehouse - step - number { display : inline - grid ; flex : 0 0 auto ; width : 24 px ; height : 24 px ; border - radius : 50 % ; background : # 3478 c8 ; color : # fff ; font - size : 12 px ; place - items : center } . warehouse - heading - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; gap : 8 px } . warehouse - transfer - template - group { margin : 0 0 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # fbfcfe } . warehouse - binding - heading { margin - top : 14 px ; padding : 12 px 14 px ; border : 1 px solid # dde5f0 ; border - radius : 7 px ; background : # f8faff } . warehouse - binding - heading strong { display : flex ; align - items : center ; gap : 8 px ; color : # 34445 c ; font - size : 13 px }
@ media ( max - width : 1250 px ) { . warehouse - transfer - form -- relabel { grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1 fr ) } . warehouse - transfer - form . warehouse - template - actions { justify - content : flex - start } . warehouse - transfer - form . warehouse - labeling - heading { align - items : flex - start ; flex - direction : column } }
@ media ( max - width : 1180 px ) { . warehouse - transfer - file - groups { grid - template - columns : minmax ( 0 , 1 fr ) } }
. warehouse - transfer - form . pickup - address - field { grid - column : span 1 }
/* Keep the count and controls visible together on narrow list pages. */
. warehouse - pagination { justify - content : flex - end ; }