@@ -26,16 +26,14 @@ import {
} from '@/modules/first-leg/api/firstLeg'
import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination'
import { createIdempotencyKey } from '@/utils/idempotencyKey'
import { createReturnRelabel , uploadReturnRelabelFile } from '../api/returnOrders'
type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
type RelabelTemplateType = 'carton' | 'sku' | 'mixed'
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceType ? : 'DROPSHIP' | 'RETURN_CARTON' ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; returnCartonId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface CargoRow { id : number ; included ? : boolean ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceType ? : 'DROPSHIP' | 'RETURN_CARTON' | 'RETURN_PARCEL' ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; returnCartonId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; customerCartonMarks : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface 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 }
@@ -189,16 +187,21 @@ const selectedDiscardOrderId = ref('')
const discardCandidatesLoading = ref ( false )
const relabelCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const selectedRelabelInboundOrderId = ref ( '' )
const orderInfoCollapsed = ref ( false )
const relabelCandidatesLoading = ref ( false )
const transferInventoryCandidates = ref < RelabelInventoryCandidate [ ] > ( [ ] )
const selectedTransferInventory = ref ( '' )
// Snowflake IDs exceed JavaScript's safe integer range. Keep them as strings
// all the way to JSON so the backend receives the exact source order ID.
const selectedTransferSourceOrderIds = ref < string [ ] > ( [ ] )
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' )
const hideReturnSkuCartonMark = computed ( ( ) => isReturnRelabelSource . value && labelingMode . value === 'sku' )
const selectedReturnCandidates = computed ( ( ) => relabelCandidates . value . filter ( item => Boolean ( item . returnCartonId ) && item . inboundOrderNo === selectedReturnOrderNo . value ) )
const returnBoxCandidates = computed ( ( ) => selectedReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_CARTON' ) )
const returnLooseCandidates = computed ( ( ) => selectedReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_PARCEL' ) )
const returnSkuOnly = computed ( ( ) => isReturnRelabelSource . value && returnLooseCandidates . value . length > 0 && returnBoxCandidates . value . length === 0 )
const requiresBothTotalFiles = computed ( ( ) => ( props . service === 'labeling' || ( props . service === 'transfer' && form . relabel ) ) && labelingMode . value === 'carton_and_sku' )
function inventoryQuantity ( value : number | string | null | undefined ) {
const quantity = Number ( value )
return Number . isFinite ( quantity ) && quantity > 0 ? quantity : 0
@@ -207,7 +210,7 @@ const relabelInboundOptions = computed(() => {
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number ; relabelledPackages : number ; quantityUnit : ' 箱 ' | ' 件 ' } > ( )
for ( const candidate of relabelCandidates . value ) {
const returnOrderNo = candidate . inboundOrderNo || String ( candidate . returnCartonId || '' )
const value = candidate . sourceType === 'RETURN_CARTON' ? ` RETURN_ORDER: ${ returnOrderNo } ` : String ( candidate . inboundOrderId )
const value = candidate . returnCartonId ? ` RETURN_ORDER: ${ returnOrderNo } ` : String ( candidate . inboundOrderId )
const current = grouped . get ( value )
if ( current ) {
current . availablePackages += inventoryQuantity ( candidate . availablePackages )
@@ -215,11 +218,11 @@ const relabelInboundOptions = computed(() => {
}
else grouped . set ( value , {
value ,
inboundOrderNo : candidate . sourceType === 'RETURN_CARTON' ? ` 退货单 ${ candidate . inboundOrderNo || '—' } ` : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . sourceType === 'RETURN_CARTON' ? '已签收退货库存' : ( candidate . inboundCode || '—' ) ,
inboundOrderNo : candidate . returnCartonId ? ` 退货单 ${ candidate . inboundOrderNo || '—' } ` : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . returnCartonId ? '已签收退货库存' : ( candidate . inboundCode || '—' ) ,
availablePackages : inventoryQuantity ( candidate . availablePackages ) ,
relabelledPackages : inventoryQuantity ( candidate . relabelledPackages ) ,
quantityUnit : candidate . sourceType === 'RETURN_CARTON' ? '件' : '箱' ,
quantityUnit : candidate . returnCartonId ? '件' : '箱' ,
} )
}
return [ ... grouped . values ( ) ]
@@ -263,7 +266,10 @@ watch(() => cargoRows.value.map((row) => row.sku), (skus) => {
if ( sourceSku ) row . sku = sourceSku
} )
} )
watch ( labelingMode , ( value ) => { if ( value !== 'carton' && ! skuRelabelRows . value . length ) addSkuRelabelRow ( ) } )
watch ( [ returnSkuOnly , labelingMode ] , ( [ skuOnly , value ] ) => {
if ( skuOnly && value !== 'sku' ) { labelingMode . value = 'sku' ; return }
if ( value !== 'carton' && ! skuRelabelRows . value . length ) addSkuRelabelRow ( )
} )
watch ( ( ) => form . destination , ( value ) => {
if ( props . service !== 'labeling' ) return
for ( const row of cargoRows . value ) row . destinationWarehouseId = value
@@ -293,6 +299,7 @@ onMounted(() => {
onBeforeUnmount ( ( ) => { clearCompletionPhotos ( ) ; clearMeasurementPhotos ( ) } )
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' , reviewStart : '' , reviewEnd : '' , completedStart : '' , completedEnd : '' , measuredStart : '' , measuredEnd : '' , outboundStart : '' , outboundEnd : '' } ) }
function resetForm ( ) {
orderInfoCollapsed . value = false
Object . assign ( form , { customerNo : '' , inboundNo : '' , sourceWarehouse : '' , warehouse : '' , destination : '' , privateDestinationAddress : '' , privateDestinationPostalCode : '' , privateDestinationContact : '' , privateDestinationPhone : '' , address : '' , contact : '' , phone : '' , pickupContact : '' , pickupPhone : '' , relabelContact : '' , relabelPhone : '' , deliveryMethod : '' , relabelDeliveryMethod : '' , trackingNo : '' , reason : '' , requirement : '' , relabel : false , remark : '' , labelDeadline : '' , estimatedArrivalAt : '' , estimatedShipAt : '' , fbaShipmentNo : '' , volumeCbm : 0.01 } )
cargoRows . value = [ ]
transferRelabelRows . value = [ ]
@@ -490,7 +497,7 @@ function clearTransferRelabelRows() {
function addSkuRelabelRow ( ) {
const source = cargoRows . value [ Math . min ( skuRelabelRows . value . length , Math . max ( 0 , cargoRows . value . length - 1 ) ) ]
skuRelabelRows . value . push ( { id : ++ skuRelabelSequence , sourceRowId : source ? . id || null ,
cartonMark : '' , originalSku : source ? . sku || '' , newSku : '' , quantity : 1 ,
cartonMark : hideReturnSkuCartonMark . value ? source ? . cartonMark || '' : '' , originalSku : source ? . sku || '' , newSku : '' , quantity : 1 ,
fbaCartonMark : '' , destinationWarehouseId : form . destination , remark : '' } )
}
function removeSkuRelabelRow ( id : number ) { skuRelabelRows . value = skuRelabelRows . value . filter ( ( row ) => row . id !== id ) }
@@ -532,6 +539,16 @@ function chooseCreateTotalFile(scope: 'CARTON_TOTAL' | 'SKU_TOTAL') {
function buildSkuRelabelItems ( ) : RelabelCreate [ 'items' ] {
const groups = new Map < string , SkuRelabelRow [ ] > ( )
for ( const row of skuRelabelRows . value ) {
if ( hideReturnSkuCartonMark . value ) {
const source = cargoRows . value . find ( item => item . id === row . sourceRowId )
const candidate = selectedReturnCandidates . value . find ( item => String ( item . returnCartonId ) === source ? . returnCartonId )
if ( candidate ? . skuItems ? . some ( item => item . skuCode === row . originalSku . trim ( ) ) ) row . cartonMark = source ? . cartonMark || ''
else {
const matches = selectedReturnCandidates . value . filter ( item => item . skuItems ? . some ( sku => sku . skuCode === row . originalSku . trim ( ) ) )
if ( matches . length !== 1 ) throw new Error ( ` 请从上方库存明细选择 SKU ${ row . originalSku } 的来源库存 ` )
row . cartonMark = matches [ 0 ] . cartonMark || ''
}
}
const cartonMark = row . cartonMark . trim ( ) . toUpperCase ( )
if ( ! cartonMark ) throw new Error ( '请填写每个箱子的我方箱唛' )
const rows = groups . get ( cartonMark ) || [ ]
@@ -546,7 +563,20 @@ function buildSkuRelabelItems(): RelabelCreate['items'] {
throw new Error ( '填写的箱唛数量超过当前入仓单可用库存箱数' )
}
return [ ... groups . entries ( ) ] . map ( ( [ _ , rows ] , groupIndex ) => {
const source = cargoRows . value . find ( ( item ) => item . id === rows [ 0 ] . sourceRowId ) || anchors [ groupIndex ]
const source = isReturnRelabelSource . value
? cargoRows . value . find ( item => item . cartonMark === rows [ 0 ] . cartonMark . trim ( ) )
: cargoRows . value . find ( ( item ) => item . id === rows [ 0 ] . sourceRowId ) || anchors [ groupIndex ]
if ( isReturnRelabelSource . value && ! source ) throw new Error ( '请选择该退货单实际可用的来源箱号' )
if ( isReturnRelabelSource . value ) {
const candidate = selectedReturnCandidates . value . find ( item => String ( item . returnCartonId ) === source ? . returnCartonId )
if ( candidate ? . sourceType === 'RETURN_PARCEL' && labelingMode . value !== 'sku' ) throw new Error ( '散件库存只能贴 SKU,请先合箱后申请箱唛或混贴' )
const requested = new Map < string , number > ( )
for ( const row of rows ) {
const quantity = ( requested . get ( row . originalSku ) || 0 ) + row . quantity
requested . set ( row . originalSku , quantity )
if ( quantity > Number ( candidate ? . skuItems ? . find ( item => item . skuCode === row . originalSku ) ? . quantity || 0 ) ) throw new Error ( ` SKU ${ row . originalSku } 超过来源箱可用数量 ` )
}
}
if ( ! source ? . returnCartonId && ( ! source ? . sourceInventoryBalanceId || ! source . sourceInventoryPositionId || ! source . sourceInboundOrderId ) ) {
throw new Error ( '来源库存已变化,请重新选择有库存的历史入仓单' )
}
@@ -949,48 +979,18 @@ function listRowActions(candidate: object): TableAction[] {
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 : createIdempotencyKey ( ) ,
} )
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'
? ! selectedDiscardOrderId . value || cargoRows . value . every ( ( item ) => item . quantity < 1 )
|| cargoRows . value . some ( ( item ) => item . quantity < 0 || item . quantity > Number ( item . availableQuantity || 0 ) )
: ( props . service === 'labeling' || props . service === 'relabel' ) && ( relabelRows . length === 0 || ( props . service === 'labeling' && labelingMode . value !== 'carton'
? skuRelabelRows . value . some ( ( item ) => ! item . cartonMark || ! item . originalSku || ! item . newSku || item . quantity < 1
? skuRelabelRows . value . some ( ( item ) => ( ! hideReturnSkuCartonMark . value && ! item . cartonMark ) || ! item . originalSku || ! item . newSku || item . quantity < 1
|| ( labelingMode . value === 'carton_and_sku' && ! item . fbaCartonMark ) )
: cargoRows . value . some ( ( item ) => ( props . service !== 'labeling' && ! item . sku ) || ! item . name
|| ( props . service !== 'labeling' && item . quantity < 1 ) ) ) )
@@ -1019,7 +1019,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . shipmentNo || ! item . cartonMark || ! item . fbaCartonMark ) ) {
ElMessage . warning ( '请上传关联面单,确保每箱的我方箱唛和 FBA 箱唛完整' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . sourceInventoryPositionId ) ) {
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cartonDetailRows . value . some ( ( item ) => ! item . returnCartonId && ! item . sourceInventoryPositionId ) ) {
ElMessage . warning ( '请选择有库存的历史入仓单' ) ; return
}
if ( props . service === 'disposal' && ! form . reason ) {
@@ -1399,34 +1399,17 @@ function selectRelabelInventory(inboundOrderId: string) {
const rows : CargoRow [ ] = [ ]
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
return ( item . returnCartonId ? ` 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 : 0 ,
} )
}
}
returnRelabelSkuRows . value = [ ... grouped . values ( ) ]
labelingMode . value = 'sku'
for ( const candidate of selectedCandidates ) rows . push ( {
id : ++ cargoSequence , sku : candidate . skuItems ? . [ 0 ] ? . skuCode || candidate . skuCode || '' ,
name : candidate . productNameZh || '退货库存' , included : false , quantity : 1 , availableQuantity : 1 ,
returnCartonId : String ( candidate . returnCartonId ) , sourceInboundOrderNo : candidate . inboundOrderNo ,
fnsku : '' , shipmentNo : candidate . inboundOrderNo || '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' ,
cartonMark : candidate . cartonMark || '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' ,
} )
} 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 || '库存货物' ,
@@ -1440,7 +1423,7 @@ function selectRelabelInventory(inboundOrderId: string) {
} ) ) )
}
cargoRows . value = rows . slice ( 0 , 500 )
if ( cargoRows . value . length ) cargoRows . value [ 0 ] . included = true
if ( cargoRows . value . length && ! inboundOrderId . startsWith ( 'RETURN_ORDER:' ) ) cargoRows . value [ 0 ] . included = true
skuRelabelRows . value = [ ]
importedRelabelBindings . value = [ ]
shippingLabelFile . value = null
@@ -1448,6 +1431,22 @@ function selectRelabelInventory(inboundOrderId: string) {
skuTotalFile . value = null
if ( ! isReturnRelabelSource . value && labelingMode . value !== 'carton' ) addSkuRelabelRow ( )
}
function addReturnSkuBinding ( source : RelabelInventoryCandidate , skuCode : string ) {
const sku = source . skuItems ? . find ( item => item . skuCode === skuCode )
if ( ! sku ) return
const row = skuRelabelRows . value . find ( item => item . cartonMark === source . cartonMark && item . originalSku === skuCode )
if ( row ) return
const sourceRow = cargoRows . value . find ( item => item . returnCartonId === String ( source . returnCartonId ) )
skuRelabelRows . value . push ( { id : ++ skuRelabelSequence , sourceRowId : sourceRow ? . id || null , cartonMark : source . cartonMark || '' , originalSku : skuCode , newSku : '' , quantity : Math . max ( 1 , Number ( sku . quantity ) ) , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } )
}
function addReturnBoxBinding ( source : RelabelInventoryCandidate ) {
if ( labelingMode . value !== 'carton' ) {
for ( const sku of source . skuItems || [ ] ) addReturnSkuBinding ( source , sku . skuCode )
return
}
if ( cartonDetailRows . value . some ( item => item . cartonMark === source . cartonMark ) ) return
cargoRows . value . push ( { id : ++ cargoSequence , included : true , sku : source . skuItems ? . map ( item => item . skuCode ) . join ( ',' ) || '' , name : source . productNameZh || '退货箱' , shipmentNo : selectedReturnOrderNo . value , cartonMark : source . cartonMark || '' , fbaCartonMark : '' , remark : '' , sourceInventoryBalanceId : undefined , sourceInventoryPositionId : undefined , sourceInboundOrderId : undefined , returnCartonId : String ( source . returnCartonId ) , quantity : 1 , fnsku : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , destinationWarehouseId : '' } )
}
< / script >
< template >
@@ -1467,8 +1466,17 @@ function selectRelabelInventory(inboundOrderId: string) {
< template v-else-if = "mode === 'create' || mode === 'edit'" >
< 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-f orm-grid " >
< div class = "warehouse-card warehouse-form-section warehouse-transfer-main-card" >
< div v-if = "props.service === 'labeling'" class=" warehouse-order-info-heading " >
< h3 > 订单信息 < / h3 >
< el-button text type = "primary" native -type = " button " :aria-expanded = "!orderInfoCollapsed" @click ="orderInfoCollapsed = !orderInfoCollapsed" > {{ orderInfoCollapsed ? ' 展开信息 ' : ' 收起信息 ' }} < / el -button >
< / div >
< h3 v-else > {{ props.service = = = ' transfer ' ? ' 转运信息 ' : props.service = = = ' relabel ' ? ' 退货信息 ' : ' 基础信息 ' }} < / h3 >
< div v-if = "props.service === 'labeling' && orderInfoCollapsed" class="warehouse-order-info-summary" >
< span > { { operationWarehouses . find ( item => String ( item . id ) === form . warehouse ) ? . warehouseName || '未选择操作仓' } } < / span >
< span > { { relabelInboundOptions . find ( item => item . value === selectedRelabelInboundOrderId ) ? . inboundOrderNo || '未选择来源单据' } } < / span >
< / div >
< div v-show = "props.service !== 'labeling' || !orderInfoCollapsed" class="warehouse-form-grid" >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>操作仓</span><el-select v-model="form.warehouse" filterable clearable :loading="fbaWarehousesLoading" :disabled="Boolean(operationWarehousesError)" placeholder="请选择海外仓" @change="selectDiscardWarehouse"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint">{{ operationWarehousesError }}</small><small v-else class="warehouse-hint" > 请先选择本次废弃货物所在的海外仓 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidates" :key="candidate.orderId" :label="candidate.sourceType === 'RETURN_CARTON' ? `退货|${candidate.inboundOrderNo || '—'}| ${candidate.cartonMark || '—'}|可用 ${candidate.availablePackages}` : `${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}|可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 显示当前操作仓内有可用数量的普通入库库存和退货库存 。 < / small > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
@@ -1520,7 +1528,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< div class = "warehouse-section-heading warehouse-labeling-heading" >
< div > < h3 > 货物明细 < / h3 > < / div >
< / div >
< div class = "warehouse-file-groups" : class = "{ ' warehouse-file-groups--single': isReturnRelabelSource } ">
< div class = "warehouse-file-groups warehouse-file-groups--compact " >
< 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" >
@@ -1529,7 +1537,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< div class = "warehouse-file-option" : class = "{ 'is-selected': Boolean(skuTotalFile) }" > < div class = "warehouse-file-option__name" > < strong > SKU 总文件 < / strong > < / div > < el-button native -type = " 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" >
< section 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 >
@@ -1540,30 +1548,34 @@ function selectRelabelInventory(inboundOrderId: string) {
< / 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')" > < 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 :model-value="row.selected" @change="row.selected = Boolean($event)" / ></ td><td><strong>{{ row .skuCode }}</strong></ td><td>{{ row.productNameZh || '—' }}</td><td><strong>{{ row.availableQ uantity }}</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="returnRelabelSkuRow s.length === 0 "><td colspan="5"><div class="warehouse-empty" > 该退货单暂无可申请贴标的 SKU 库存 < / div > < / td > < / tr > < / tbody >
< / table >
< template v-if = "returnLooseCandidates.length " >
< h3 > 可用散件 SKU < / h3 >
< table class = "warehouse-edit-table" > < thead > < tr > < th > SKU < / th > < th > 可用数量 ( 件 ) < / th > < th > 操作 < / th > < / tr > < / thead > < tbody > < template v-for = "source in returnLooseCandidates" :key="source.returnCartonId"><tr v-for="sku in source.skuItems" :key="sku.skuCode" ><td>{{ sku .skuCode }}</td><td>{{ sku.q uantity }}</td><td><el-button size="small" :disabled="labelingMode !== 'sku'" @click="addReturnSkuBinding(source, sku.skuCode)">添加贴标</el-button></td></tr></template ><tr v-if="! returnLooseCandidate s.length"><td colspan="3" > 暂无可用散件库存 < / td > < / tr > < / tbody > < / table >
< / template >
< template v-if = "returnBoxCandidates.length" >
< h3 > 可用箱子 < / h3 >
< table class = "warehouse-edit-table" > < thead > < tr > < th > 系统箱唛号 < / th > < th > 箱内 SKU < / th > < th > 可用数量 ( 件 ) < / th > < th > 操作 < / th > < / tr > < / thead > < tbody > < tr v-for = "source in returnBoxCandidates" :key="source.returnCartonId"><td><strong>{{ source.cartonMark }}</strong></td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.skuCode }}</div></td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.quantity }}</div></td><td><el-button size="small" @click="addReturnBoxBinding(source)">添加贴标</el-button></td></tr><tr v-if="!returnBoxCandidates.length"><td colspan="4" > 暂无可用箱子库存 < / td > < / tr > < / tbody > < / table >
< / template >
< / div >
< div v-if = "!isReturnRelabelSource" class=" warehouse-labeling-mode" >
< 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 = "carton" :disabled = "returnSkuOnly" > 只换箱唛 < / 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-button value = "carton_and_sku" :disabled = "returnSkuOnly" > SKU 和箱唛都换 < / el-radio-button >
< / el-radio-group >
< / div >
< div v-if = "!isReturnRelabelSource && importedRelabelBindings.length" class="warehouse-import-preview" >
< div v-if = "importedRelabelBindings.length" class="warehouse-import-preview" >
< div class = "warehouse-section-heading" > < div > < strong > 待导入绑定关系 < / strong > < p > 可只勾选本次需要换标的部分 , 也可以全选后一次导入 。 < / p > < / div > < div > < el-checkbox : model -value = " importedRelabelBindings.every ( ( item ) = > item . selected ) " @change=" selectAllImportedBindings ( Boolean ( $event ) ) ">全选</el-checkbox><el-button type=" primary " size=" small " @click=" importSelectedBindingsToDetails ">导入到明细</el-button></div></div>
<div class=" warehouse - table - scroll "><table class=" warehouse - binding - preview - table "><thead><tr><th>选择</th><th>关系类型</th><th>仓库唛头</th><th>旧 SKU</th><th>新 SKU</th><th>数量</th><th>新 FBA 箱唛</th></tr></thead><tbody><tr v-for=" item in importedRelabelBindings " :key=" item . id "><td><el-checkbox v-model=" item . selected " /></td><td>{{ item.templateType === 'mixed' ? 'SKU 混箱换标' : item.templateType === 'carton' ? '箱号关联' : 'SKU 关联' }}</td><td>{{ item.cartonMark }}</td><td>{{ item.originalSku || '—' }}</td><td>{{ item.newSku || '—' }}</td><td>{{ item.quantity }}</td><td>{{ item.fbaCartonMark || '—' }}</td></tr></tbody></table></div>
</div>
<div v-if=" ! isReturnRelabelSource " class="warehouse - section - heading warehouse - tab - actions ">
<div 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=" ! 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>
<div v-if=" labelingMode === 'carton' " class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - label - table "><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数</th><th>仓库唛头 *</th><th>新 FBA 箱唛 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for=" row in cartonDetailRows " :key=" row . id "><td><el-input v-model=" row . sku " placeholder=" 选填 " /></td><td><el-input v-model=" row . name " placeholder=" 请输入中文品名 " /></td><td><el-input v-model=" row . shipmentNo " placeholder=" 请输入FBA货件编号 " /></td><td><strong>1 箱</strong></td><td><el-input v-model=" row . cartonMark " placeholder=" 请输入仓库唛头 " /></td><td><el-input v-model=" row . fbaCartonMark " placeholder=" 请输入新的 FBA 箱唛 " /></td><td><el-input v-model=" row . remark " type=" textarea " :rows=" 2 " maxlength=" 1000 " show-word-limit placeholder=" 请输入明细备注 " /></td><td><button type=" button " class=" warehouse - delete " title=" 移出本次明细 " @click=" removeCargoRow ( row . id ) "><Trash2 :size=" 16 " />移出</button></td></tr><tr v-if=" cartonDetailRows . length === 0 "><td colspan=" 8 "><div class=" warehouse - empty ">暂无本次换标明细,请上传关系后导入或手动添加</div></td></tr></tbody></table></div>
<div v-else class=" warehouse - table - scroll "><table class=" warehouse - edit - table warehouse - sku - label - table "><thead><tr><th v-if=" ! hideReturnSkuCartonMark " >仓库唛头 *</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 v-if=" ! hideReturnSkuCartonMark " ><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=" hideReturnSkuCartonMark ? 5 : 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 " :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>
@@ -1574,7 +1586,7 @@ function selectRelabelInventory(inboundOrderId: string) {
<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></div>
<div class=" warehouse - transfer - file - groups ">
<div class=" warehouse - transfer - file - groups warehouse - file - groups -- compact ">
<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 ">
@@ -1652,6 +1664,65 @@ function selectRelabelInventory(inboundOrderId: string) {
< / template >
< style scoped >
. warehouse - order - info - heading { display : flex ; align - items : center ; justify - content : space - between ; max - width : 1296 px ; margin - bottom : 16 px ; border - bottom : 1 px solid # edf0f4 ; padding - bottom : 10 px ; }
. warehouse - form - section . warehouse - order - info - heading h3 { margin : 0 ; padding : 0 ; border : 0 ; }
. warehouse - order - info - summary { display : flex ; flex - wrap : wrap ; gap : 10 px 24 px ; color : # 64748 b ; font - size : 12 px ; }
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - labeling - mode {
width : fit - content ;
max - width : 1296 px ;
box - sizing : border - box ;
border : 1 px solid # e2e8f0 ;
border - radius : 8 px ;
background : # f8fafc ;
padding : 8 px 12 px ;
gap : 14 px ;
margin : 20 px 0 22 px ;
}
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - labeling - mode > span {
color : # 475569 ;
font - size : 12 px ;
}
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - table - scroll th {
background : # f0f5fc ;
color : # 385579 ;
border - bottom : 1 px solid # dce6f2 ;
height : 38 px ;
font - weight : 600 ;
}
. warehouse - service - page -- return - relabel . warehouse - labeling - mode : deep ( . el - radio - button ) {
-- el - color - primary : # 2563 eb ;
-- el - radio - button - checked - bg - color : # 2563 eb ;
-- el - radio - button - checked - border - color : # 2563 eb ;
}
. warehouse - service - page -- return - relabel . warehouse - labeling - mode : deep ( . el - radio - button : not ( . is - disabled ) . el - radio - button _ _inner ) {
border - color : # cbdaf0 ;
}
. warehouse - service - page -- return - relabel . warehouse - labeling - mode : deep ( . el - radio - group ) { gap : 6 px ; }
. warehouse - service - page -- return - relabel . warehouse - labeling - mode : deep ( . el - radio - button . el - radio - button _ _inner ) {
padding : 8 px 12 px ;
border : 1 px solid # dce3ec ;
border - radius : 5 px ;
box - shadow : none ;
font - size : 12 px ;
}
. warehouse - service - page -- return - relabel . warehouse - labeling - mode : deep ( . el - radio - button . is - disabled . el - radio - button _ _inner ) {
color : # a3adbb ;
background : # f1f4f8 ;
border - color : # e5eaf0 ;
}
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - table - scroll td { padding : 10 px 12 px ; }
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - table - scroll h3 {
font - size : 14 px ;
margin - bottom : 12 px ;
padding - bottom : 10 px ;
}
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - sku - label - table : deep ( . el - input _ _wrapper ) {
min - height : 30 px ;
box - shadow : 0 0 0 1 px # dce3ec inset ;
border - radius : 5 px ;
}
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - sku - label - table : deep ( . el - input - number ) { width : 120 px ; }
. warehouse - service - page -- return - relabel . warehouse - transfer - cargo - card . warehouse - tab - actions { margin - bottom : 10 px ; }
. 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 }
@@ -1667,7 +1738,42 @@ function selectRelabelInventory(inboundOrderId: string) {
. 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 - file - groups -- compact {
width : 100 % ;
max - width : 1296 px ;
grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1 fr ) ;
gap : 16 px ;
align - items : stretch ;
}
. warehouse - file - groups -- compact . warehouse - file - group _ _heading {
padding : 10 px 12 px ;
align - items : center ;
}
. warehouse - file - groups -- compact . warehouse - file - options {
padding : 10 px ;
grid - template - columns : minmax ( 0 , 1 fr ) 20 px minmax ( 0 , 1 fr ) ;
}
. warehouse - file - groups -- compact . warehouse - file - option {
padding : 8 px ;
gap : 8 px ;
}
. warehouse - file - groups -- compact . warehouse - file - option _ _name strong { font - size : 12 px ; }
. warehouse - file - groups -- compact . warehouse - file - option : deep ( . el - button ) {
padding : 6 px 8 px ;
font - size : 12 px ;
}
. warehouse - file - groups -- compact . warehouse - template - list { padding : 4 px 12 px ; }
. warehouse - file - groups -- compact . warehouse - template - list > div {
grid - template - columns : minmax ( 0 , 1 fr ) auto auto ;
padding : 7 px 0 ;
}
@ media ( max - width : 1180 px ) {
. warehouse - file - groups -- compact { grid - template - columns : minmax ( 0 , 1 fr ) ; }
}
@ media ( max - width : 720 px ) {
. warehouse - file - groups -- compact . warehouse - file - options { grid - template - columns : minmax ( 0 , 1 fr ) ; }
. warehouse - file - groups -- compact . warehouse - template - list > div { grid - template - columns : minmax ( 0 , 1 fr ) auto ; }
}
. warehouse - native - file { min - height : 32 px ; padding : 5 px 8 px ; border : 1 px solid # dcdfe6 ; border - radius : 4 px ; background : # fff ; color : # 606266 } . warehouse - fulfillment - grid { margin - bottom : 18 px } . warehouse - evidence { display : flex ; align - items : flex - start ; gap : 16 px ; padding : 14 px ; border : 1 px solid # e7ebf1 ; border - radius : 4 px ; background : # f8fafc } . warehouse - evidence > span { padding - top : 6 px ; color : # 71809 a ; font - size : 12 px ; white - space : nowrap } . warehouse - evidence > strong { padding - top : 6 px ; color : # 8 a95a5 ; font - size : 12 px ; font - weight : 400 } . warehouse - photo - grid { display : grid ; grid - template - columns : repeat ( 4 , 140 px ) ; gap : 12 px } . warehouse - photo - item { width : 140 px ; height : 105 px ; overflow : hidden ; border : 1 px solid # dce3ed ; border - radius : 5 px ; background : # fff } . warehouse - photo - item : deep ( . el - image ) { display : block ; width : 100 % ; height : 100 % ; cursor : zoom - in } . warehouse - photo - placeholder { display : flex ; width : 100 % ; height : 100 % ; align - items : center ; justify - content : center ; padding : 10 px ; border : 0 ; background : # f4f7fb ; color : # 8792 a2 ; font - size : 12 px ; text - align : center } . warehouse - photo - retry { cursor : pointer } . warehouse - completion - remark { margin : 10 px 0 0 ; color : # 596579 ; font - size : 12 px }
. warehouse - measurement - evidence { margin - top : 16 px }
. warehouse - measurement - photo - grid { flex : 1 ; grid - template - columns : repeat ( auto - fill , minmax ( 240 px , 1 fr ) ) }