@@ -14,9 +14,9 @@ import { getMerchantWarehouses, type MerchantWarehouse } from '@/modules/first-l
type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'detail'
type LabelingMode = 'carton' | 'sku'
type LabelingMode = 'carton' | 'sku' | 'carton_and_sku'
interface CargoRow { id : number ; sku : string ; name : string ; quantity : number ; availableQuantity ? : number ; sourceOrderId ? : string ; sourceInventoryBalanceId ? : string ; sourceInventoryPositionId ? : string ; sourceInboundOrderId ? : string ; sourceInboundOrderNo ? : string ; sourceInboundCode ? : string ; sourceWarehouseName ? : string ; palletNo ? : string ; warehouseLocationCode ? : string ; fnsku : string ; shipmentNo : string ; newSkuCode : string ; shippingMark : string ; cartonMark : string ; fbaCartonMark : string ; destinationWarehouseId : string ; remark : string }
interface SkuRelabelRow { id : number ; sourceRowId : number | null ; cartonMark : string ; originalSku : string ; newSku : string ; quantity : number ; changeCarton : boolean ; 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 ServiceConfig { title : string ; createTitle : string ; numberLabel : string ; emptyText : string ; columns : string [ ] }
const props = defineProps < { service : ServiceType } > ( )
@@ -38,10 +38,11 @@ const form = reactive({ customerNo: '', inboundNo: '', sourceWarehouse: '', ware
const cargoRows = ref < CargoRow [ ] > ( [ ] )
const labelingMode = ref < LabelingMode > ( 'carton' )
const skuRelabelRows = ref < SkuRelabelRow [ ] > ( [ ] )
const cartonMarkOptions = computed ( ( ) => cargoRows . value . map ( ( item ) => ( {
value : item . id ,
label : ` ${ item . cartonMark || item . sku || '库存货物' } |可用 ${ item . availableQuantity || item . quantity } 箱 ` ,
} ) ) )
const labelingModeHint = computed ( ( ) => labelingMode . value === 'carton'
? '只替换外箱箱唛,不修改箱内商品 SKU 标签;按箱填写新的 FBA 箱唛和目的仓。'
: labelingMode . value === 'sku'
? '只替换箱内商品 SKU 标签,保留原箱唛;同一箱唛可以添加多条 SKU 明细。'
: '同一箱同时替换商品 SKU 标签和外箱箱唛;同一箱唛的多条 SKU 使用同一个新 FBA 箱唛和目的仓。' )
const submittedRows = ref < Record < string , string > [ ] > ( [ ] )
const selectedDetail = ref < Record < string , string > | null > ( null )
const selectedRelabelDetail = ref < RelabelDetail | null > ( null )
@@ -77,7 +78,7 @@ let cargoSequence = 0
let skuRelabelSequence = 0
watch ( ( ) => props . service , ( ) => { mode . value = 'list' ; resetFilters ( ) ; resetForm ( ) ; void load ( ) } )
watch ( labelingMode , ( value ) => { if ( value = == 'sku ' && ! skuRelabelRows . value . length ) addSkuRelabelRow ( ) } )
watch ( labelingMode , ( value ) => { if ( value ! == 'carton ' && ! skuRelabelRows . value . length ) addSkuRelabelRow ( ) } )
onMounted ( ( ) => { void load ( ) } )
function resetFilters ( ) { Object . assign ( filters , { number : '' , inboundNo : '' , warehouse : '' , destination : '' , status : '' , start : '' , end : '' } ) }
function resetForm ( ) {
@@ -101,36 +102,36 @@ async function openCreate() {
finally { discardCandidatesLoading . value = false }
}
function addCargoRow ( ) { cargoRows . value . push ( { id : ++ cargoSequence , sku : '' , name : '' , quantity : 1 , fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , cartonMark : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } ) }
function addSkuRelabelRow ( ) { skuRelabelRows . value . push ( { id : ++ skuRelabelSequence , sourceRowId : null , cartonMark : '' , originalSku : '' , newSku : '' , quantity : 1 , changeCarton : false , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' } ) }
function removeSkuRelabelRow ( id : number ) { skuRelabelRows . value = skuRelabelRows . value . filter ( ( row ) => row . id !== id ) }
function selectSkuSource ( row : SkuRelabelRow ) {
const source = cargoRows . value . find ( ( item ) => item . id === row . sourceRowId )
if ( ! source ) return
row . cartonMark = source . cartonMark || ''
row . originalSku = row . originalSku || source . sku
row . destinationWarehouseId = row . destinationWarehouseId || source . destinationWarehouseId
function addSkuRelabelRow ( ) {
const source = cargoRows . value [ Math . min ( skuRelabelRows . value . length , Math . max ( 0 , cargoRows . value . length - 1 ) ) ]
skuRelabelRows . value . push ( { id : ++ skuRelabelSequence , sourceRowId : source ? . id || null ,
cartonMark : '' , originalSku : source ? . sku || '' , newSku : '' , quantity : 1 ,
fbaCartonMark : '' , destinationWarehouseId : source ? . destinationWarehouseId || '' , remark : '' } )
}
function removeSkuRelabelRow ( id : number ) { skuRelabelRows . value = skuRelabelRows . value . filter ( ( row ) => row . id !== id ) }
function buildSkuRelabelItems ( ) : RelabelCreate [ 'items' ] {
const groups = new Map < number , SkuRelabelRow [ ] > ( )
const groups = new Map < string , SkuRelabelRow [ ] > ( )
for ( const row of skuRelabelRows . value ) {
if ( row . sourceRowId == null ) throw new Error ( '请选择每条 SKU 明细对应的来源库存箱' )
const rows = groups . get ( row . sourceRowId ) || [ ]
const cartonMark = row . cartonMark . trim ( ) . toUpperCase ( )
if ( ! cartonMark ) throw new Error ( '请填写每个箱子的我方箱唛' )
const rows = groups . get ( cartonMark ) || [ ]
rows . push ( row )
groups . set ( row . sourceRowId , rows )
groups . set ( cartonMark , rows )
}
return [ ... groups . entries ( ) ] . map ( ( [ sourceRowId , rows ] ) => {
const source = cargoRows . value . find ( ( item ) => item . id === sourceRowId )
const anchors = cargoRows . value . flatMap ( ( item ) => Array . from (
{ length : Number ( item . availableQuantity || 0 ) } , ( ) => item ,
) )
if ( ! anchors . length ) throw new Error ( '请先选择有库存的历史入仓单' )
if ( groups . size > anchors . length ) {
throw new Error ( '填写的箱唛数量超过当前入仓单可用库存箱数' )
}
return [ ... groups . entries ( ) ] . map ( ( [ _ , rows ] , groupIndex ) => {
const source = anchors [ groupIndex ]
if ( ! source ? . sourceInventoryBalanceId || ! source . sourceInventoryPositionId || ! source . sourceInboundOrderId ) {
throw new Error ( '来源库存已变化,请重新选择有库存的历史入仓单' )
}
const cartonMark = rows [ 0 ] . cartonMark . trim ( )
if ( ! cartonMark || rows . some ( ( row ) => row . cartonMark . trim ( ) !== cartonMark ) ) {
throw new Error ( '同一来源库存箱的 SKU 明细必须使用同一个我方箱唛' )
}
const changeCarton = rows [ 0 ] . changeCarton
if ( rows . some ( ( row ) => row . changeCarton !== changeCarton ) ) {
throw new Error ( '同一我方箱唛的 SKU 明细必须统一是否同时更换箱唛' )
}
const changeCarton = labelingMode . value === 'carton_and_sku'
const fbaCartonMark = rows [ 0 ] . fbaCartonMark . trim ( )
const destinationWarehouseId = rows [ 0 ] . destinationWarehouseId
if ( changeCarton && ( ! fbaCartonMark || ! destinationWarehouseId
@@ -143,8 +144,8 @@ function buildSkuRelabelItems(): RelabelCreate['items'] {
sourceInboundOrderId : source . sourceInboundOrderId ,
skuCode : source . sku || rows [ 0 ] . originalSku ,
productNameZh : source . name ,
totalPackageCount : source . quantity ,
notifiedPackageCount : source . quantity ,
totalPackageCount : 1 ,
notifiedPackageCount : 1 ,
shipmentNo : source . shipmentNo || cartonMark ,
trackingCode : form . trackingNo || undefined ,
newSkuCode : rows [ 0 ] . newSku ,
@@ -169,8 +170,8 @@ function clearRelabelRows() { cargoRows.value = []; selectedRelabelInboundOrderI
function csvCell ( value : string | number | undefined ) { return ` " ${ String ( value ? ? '' ) . replaceAll ( '"' , '""' ) } " ` }
function downloadRelabelTemplate ( ) {
if ( ! cargoRows . value . length ) { ElMessage . warning ( '请先选择有库存的历史入仓单' ) ; return }
const header = [ '明细行标识' , '本次箱数' , '商品SKU' , '中文品名' , 'FBA货件编号' , '换标新SKU' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' , '备注' ]
const rows = cargoRows . value . map ( ( item ) => [ item . id , item . quantity , item . sku , item . name , item . shipmentNo , item . newSkuCode ,
const header = [ '明细行标识' , '本次箱数' , '商品SKU' , '中文品名' , 'FBA货件编号' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' , '备注' ]
const rows = cargoRows . value . map ( ( item ) => [ item . id , item . quantity , item . sku , item . name , item . shipmentNo ,
item . cartonMark , item . fbaCartonMark , item . destinationWarehouseId , item . remark ] )
const blob = new Blob ( [ ` \ uFEFF ${ [ header , ... rows ] . map ( ( row ) => row . map ( csvCell ) . join ( ',' ) ) . join ( '\r\n' ) } \ r \ n ` ] , { type : 'text/csv;charset=utf-8' } )
const url = URL . createObjectURL ( blob )
@@ -193,13 +194,13 @@ async function importRelabelTemplate(event: Event) {
const lines = ( await file . text ( ) ) . replace ( /^\uFEFF/ , '' ) . split ( /\r?\n/ ) . filter ( ( line ) => line . trim ( ) )
if ( lines . length < 2 ) { ElMessage . error ( '模板中没有可导入的明细' ) ; return }
const headers = parseCsvLine ( lines [ 0 ] )
const required = [ '明细行标识' , '本次箱数' , 'FBA货件编号' , '换标新SKU' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' ]
const required = [ '明细行标识' , '本次箱数' , 'FBA货件编号' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' ]
if ( required . some ( ( name ) => ! headers . includes ( name ) ) ) { ElMessage . error ( '模板格式不正确,请重新下载模板填写' ) ; return }
const imported = lines . slice ( 1 ) . map ( parseCsvLine )
. map ( ( values ) => Object . fromEntries ( headers . map ( ( key , index ) => [ key , values [ index ] || '' ] ) ) )
. filter ( ( item ) => item [ '明细行标识' ] )
if ( ! imported . length || imported . some ( ( item ) => required . some ( ( key ) => ! item [ key ] ) ) ) {
ElMessage . error ( '模板中的明细行、箱数、货件、换标 SKU、 箱唛和 FBA 目的仓必须填写完整' ) ; return
ElMessage . error ( '模板中的明细行、箱数、货件、箱唛和 FBA 目的仓必须填写完整' ) ; return
}
if ( new Set ( imported . map ( ( item ) => item [ '明细行标识' ] ) ) . size !== imported . length ) {
ElMessage . error ( '模板中的明细行标识不能重复' ) ; return
@@ -216,7 +217,6 @@ async function importRelabelTemplate(event: Event) {
}
row . quantity = quantity
row . shipmentNo = item [ 'FBA货件编号' ]
row . newSkuCode = item [ '换标新SKU' ]
row . cartonMark = item [ '我方箱唛' ]
row . fbaCartonMark = item [ 'FBA箱唛' ]
row . destinationWarehouseId = item [ 'FBA目的仓' ]
@@ -285,13 +285,13 @@ async function load() {
function query ( ) { void load ( ) }
async function submit ( ) {
const primaryWarehouse = props . service === 'relabel' ? form . sourceWarehouse : form . warehouse
const relabelRows = labelingMode . value === 'sku ' ? skuRelabelRows . value : cargoRows . value
const relabelRows = labelingMode . value === 'carton ' ? cargoRows . 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 ) )
: relabelRows . length === 0 || ( props . service === 'labeling' && labelingMode . value = == 'sku '
? skuRelabelRows . value . some ( ( item ) => ! item . sourceRowId || ! item . cartonMark || ! item . originalSku || ! item . newSku || item . quantity < 1
|| ( item . changeCarton && ( ! item . fbaCartonMark || ! item . destinationWarehouseId ) ) )
: relabelRows . length === 0 || ( props . service === 'labeling' && labelingMode . value ! == 'carton '
? skuRelabelRows . value . some ( ( item ) => ! item . cartonMark || ! item . originalSku || ! item . newSku || item . quantity < 1
|| ( labelingMode . value === 'carton_and_sku' && ( ! item . fbaCartonMark || ! item . destinationWarehouseId ) ) )
: cargoRows . value . some ( ( item ) => ( props . service !== 'labeling' && ! item . sku ) || ! item . name || item . quantity < 1 ) )
if ( ( props . service !== 'labeling' && ! form . customerNo && ! form . inboundNo ) || ( props . service !== 'disposal' && ! primaryWarehouse ) || invalidCargo ) {
ElMessage . warning ( '请填写必填信息和完整的货物明细' ) ; return
@@ -299,7 +299,7 @@ async function submit() {
if ( props . service === 'labeling' && ( ! form . labelDeadline || ! form . contact || ! form . phone || form . chargeAmount <= 0 ) ) {
ElMessage . warning ( '请填写贴标截止时间、联系人和贴标费用' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cargoRows . value . some ( ( item ) => ! item . shipmentNo || ! item . newSkuCode || ! item . cartonMark || ! item . fbaCartonMark ) ) {
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cargoRows . value . some ( ( item ) => ! item . shipmentNo || ! item . cartonMark || ! item . fbaCartonMark ) ) {
ElMessage . warning ( '请上传关联面单,确保每箱的我方箱唛和 FBA 箱唛完整' ) ; return
}
if ( props . service === 'labeling' && labelingMode . value === 'carton' && cargoRows . value . some ( ( item ) => ! item . destinationWarehouseId || ! item . sourceInventoryPositionId ) ) {
@@ -318,7 +318,7 @@ async function submit() {
if ( props . service === 'labeling' ) {
const sourceInboundOrders = [ ... new Set ( cargoRows . value . map ( ( item ) => item . sourceInboundOrderNo || item . sourceInboundCode ) . filter ( ( value ) : value is string => Boolean ( value ) ) ) ] . join ( '、' ) . slice ( 0 , 128 )
const items = labelingMode . value === 'carton'
? cargoRows . value . map ( ( item ) => ( { sourceInventoryBalanceId : item . sourceInventoryBalanceId ! , sourceInventoryPositionId : item . sourceInventoryPositionId ! , sourceInboundOrderId : item . sourceInboundOrderId ! , skuCode : item . sku , productNameZh : item . name , totalPackageCount : item . quantity , notifiedPackageCount : item . quantity , shipmentNo : item . shipmentNo , trackingCode : form . trackingNo || undefined , newSkuCode : item . newSkuCode , labelRequirement : form . requirement || undefined , cartonMark : item . cartonMark , fbaCartonMark : item . fbaCartonMark , destinationFbaWarehouseCode : item . destinationWarehouseId , operationType : 'CARTON_LABEL' as const , skuItems : [ ] , remark : item . remark || undefined } ) )
? cargoRows . value . map ( ( item ) => ( { sourceInventoryBalanceId : item . sourceInventoryBalanceId ! , sourceInventoryPositionId : item . sourceInventoryPositionId ! , sourceInboundOrderId : item . sourceInboundOrderId ! , skuCode : item . sku , productNameZh : item . name , totalPackageCount : item . quantity , notifiedPackageCount : item . quantity , shipmentNo : item . shipmentNo , trackingCode : form . trackingNo || undefined , newSkuCode : undefined , labelRequirement : form . requirement || undefined , cartonMark : item . cartonMark , fbaCartonMark : item . fbaCartonMark , destinationFbaWarehouseCode : item . destinationWarehouseId , operationType : 'CARTON_LABEL' as const , skuItems : [ ] , remark : item . remark || undefined } ) )
: buildSkuRelabelItems ( )
await createRelabel ( { customerOrderNo : sourceInboundOrders || '贴标申请' , operationWarehouseId : form . warehouse , labelDeadline : form . labelDeadline , contactName : form . contact , contactPhone : form . phone , chargeAmount : form . chargeAmount , remark : form . remark || undefined , idempotencyKey , items } )
} else if ( props . service === 'disposal' ) {
@@ -397,6 +397,7 @@ function selectRelabelInventory(inboundOrderId: string) {
}
} ) )
skuRelabelRows . value = [ ]
if ( labelingMode . value !== 'carton' ) addSkuRelabelRow ( )
}
< / script >
@@ -450,21 +451,28 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-if = "props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求" / > < / label >
< label v-else-if = "props.service !== 'disposal'"><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-checkbox-field warehouse-span-2"><span>是否换标</span><el-checkbox v-model="form.relabel" > 需要换标 < / el -checkbox > < / div >
< / div >
< / div >
< div class = "warehouse-card warehouse-form-section" >
< template v-if = "props.service === 'labeling'" >
< h3 > 货物明细 < / h3 >
< el-tabs v-model = "labelingMode" class=" warehouse-labeling-tabs " >
< el -tab -pane label = "箱唛换标" name = "carton" >
< div class = "warehouse-section-heading warehouse-tab-actions" > < p > 按箱关联我方箱唛与新的 FBA 箱唛 , 提交后按所选库存箱数冻结 。 < / p > < div > < el-button size = "small" @click ="downloadRelabelTemplate" > < Download :size = "15" / > 下载关联模板 < / el-button > < el-button size = "small" :disabled = "!cargoRows.length" @click ="templateInput?.click()" > < Upload :size = "15" / > 上传关联面单 < / el-button > < input ref = "templateInput" class = "warehouse-hidden-input" type = "file" accept = ".csv,text/csv" @change ="importRelabelTemplate" > < el -button type = "primary" size = "small" @click ="addRelabelCargoRow" > < Plus :size = "15" / > 手动添加行 < / el-button > < el-button size = "small" @click ="clearRelabelRows" > 清空 < / el -button > < / div > < / div >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table warehouse-label-table" > < thead > < tr > < th > 商品 SKU < / th > < th > 中文品名 * < / th > < th > FBA 货件编号 * < / th > < th > 换标新 SKU * < / th > < th > 箱数 * < / th > < th > 我方箱唛 < / th > < th > FBA 箱唛 < / th > < th > FBA 目的仓 < / th > < th > 备注 < / th > < th > 操作 < / th > < / tr > < / thead > < tbody > < tr v-for = "row in cargoRows" :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><el-input v-model="row.newSkuCode" placeholder="请输入换标新SKU" /></td><td><el-input-number v-model="row.quantity" :min="1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td><el-input v-model="row.cartonMark" placeholder="请输入我方箱唛" /></td><td><el-input v-model="row.fbaCartonMark" placeholder="请输入 FBA 箱唛" /></td><td><el-select v-model="row.destinationWarehouseId" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td><td><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td colspan="10"><div class="warehouse-empty" > 请先选择仍有库存的历史入仓单 , 系统将带出可用库存明细 < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / el -tab -pane >
< el-tab-pane label = "SKU 换标" name = "sku" >
< div class = "warehouse-section-heading warehouse-tab-actions" > < p > 指定我方箱唛后 , 可为同一箱唛添加多条 SKU 换标明细 , 数量按件填写 。 < / p > < div > < 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 class = "warehouse-table-scroll" > < table class = "warehouse-edit-table warehouse-sku-label-table" > < thead > < tr > < th > 来源库存箱 * < / th > < th > 我方箱唛 * < / th > < th > 原 SKU * < / th > < th > 换标后 SKU * < / th > < th > 换标数量 ( 件 ) * < / th > < th > 同时更换箱唛 < / th > < th > 新 FBA 箱唛 < / th > < th > FBA 目的仓 < / th > < th > 备注 < / th > < th > 操作 < / th > < / tr > < / thead > < tbody > < tr v-for = "row in skuRelabelRows" :key="row.id"><td><el-select v-model="row.sourceRowId" filterable placeholder="选择来源库存箱" @change="selectSkuSource(row)"><el-option v-for="source in cartonMarkOptions" :key="source.value" :label="source.label" :value="source.value" /></el-select></td><td><el-input v-model="row.cartonMark" placeholder="请输入或核对我方箱唛" /></td><td><el-input v-model="row.originalSku" placeholder="请输入原 SKU" /></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><el-checkbox v-model="row.changeCarton">换箱唛</el-checkbox></td><td><el-input v-model="row.fbaCartonMark" :disabled="!row.changeCarton" placeholder="组合换标时填写" /></td><td><el-select v-model="row.destinationWarehouseId" :loading="fbaWarehousesLoading" :disabled="!row.changeCarton || Boolean(fbaWarehousesError)" placeholder="组合换标时选择"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select></td><td><el-input v-model="row.remark" placeholder="请输入备注" /></td><td><button type="button" class="warehouse-delete" title="删除" @click="removeSkuRelabelRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="skuRelabelRows.length === 0"><td colspan="10"><div class="warehouse-empty" > 暂无 SKU 换标明细 , 请点击 “ 添加 SKU 明细 ” < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / el -tab -pane >
< / el-tabs >
< 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-section-heading warehouse-tab-actions" >
< p > 系统只提供历史入仓单的大概库存位置 , 不代表箱唛已绑定具体托盘 。 < / p >
< div v-if = "labelingMode === 'carton'"><el-button size="small" @click="downloadRelabelTemplate"><Download :size="15" />下载关联模板</el-button><el-button size="small" :disabled="!cargoRows.length" @click="templateInput?.click()"><Upload :size="15" />上传关联面单</el-button><input ref="templateInput" class="warehouse-hidden-input" type="file" accept=".csv,text/csv" @change="importRelabelTemplate"><el-button type="primary" size="small" @click="addRelabelCargoRow"><Plus :size="15" />手动添加行</el-button><el-button size="small" @click="clearRelabelRows" > 清空 < / el -button > < / div >
< div v-else > < el -button type = "primary" size = "small" @click ="addSkuRelabelRow" > < Plus :size = "15" / > 添加 SKU 明细 < / el-button > < el-button size = "small" @click ="skuRelabelRows = []" > 清空 < / el -button > < / div >
< / div >
< div v-if = "labelingMode === 'carton'" class="warehouse-table-scroll"><table class="warehouse-edit-table warehouse-label-table"><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数 *</th><th>我方箱唛 *</th><th>新 FBA 箱唛 *</th><th>FBA 目的仓 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :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><el-input-number v-model="row.quantity" :min="1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td><el-input v-model="row.cartonMark" placeholder="请输入我方箱唛" /></td><td><el-input v-model="row.fbaCartonMark" placeholder="请输入新的 FBA 箱唛" /></td><td><el-select v-model="row.destinationWarehouseId" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td><td><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td colspan="9"><div class="warehouse-empty" > 请先选择仍有库存的历史入仓单 , 系统将带出可用库存明细 < / div > < / td > < / tr > < / tbody > < / table > < / div >
< div v-else class = "warehouse-table-scroll" > < table class = "warehouse-edit-table warehouse-sku-label-table" > < thead > < tr > < th > 我方箱唛 * < / th > < th > 原 SKU * < / th > < th > 换标后 SKU * < / th > < th > 换标数量 ( 件 ) * < / th > < th v-if = "labelingMode === 'carton_and_sku'">新 FBA 箱唛 *</th><th v-if="labelingMode === 'carton_and_sku'">FBA 目的仓 *</th><th>备注</th><th>操作</th></tr></thead><tbody><tr v-for="row in skuRelabelRows" :key="row.id"><td><el-input v-model="row.cartonMark" placeholder="请输入我方箱唛" /></td><td><el-input v-model="row.originalSku" placeholder="请输入原 SKU" /></td><td><el-input v-model="row.newSku" placeholder="请输入换标后 SKU" /></td><td><el-input-number v-model="row.quantity" :min="1" :precision="0" controls-position="right" placeholder="请输入件数" /></td><td v-if="labelingMode === 'carton_and_sku'"><el-input v-model="row.fbaCartonMark" placeholder="请输入新的 FBA 箱唛" /></td><td v-if="labelingMode === 'carton_and_sku'"><el-select v-model="row.destinationWarehouseId" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" /></el-select></td><td><el-input v-model="row.remark" placeholder="请输入备注" /></td><td><button type="button" class="warehouse-delete" title="删除" @click="removeSkuRelabelRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="skuRelabelRows.length === 0"><td :colspan="labelingMode === 'carton_and_sku' ? 8 : 6"><div class="warehouse-empty" > 暂无换标明细 , 请点击 “ 添加 SKU 明细 ” < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / 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 >
@@ -492,8 +500,9 @@ function selectRelabelInventory(inboundOrderId: string) {
< 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 - 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 - 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 - unmaintained { margin - left : 8 px ; color : # e6a23c ; font - size : 12 px ; font - style : normal }
. warehouse - labeling - tabs { margin - top : - 8 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 - tabs { margin - top : - 8 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 }
< / style >