@@ -6,7 +6,7 @@ import SingleLineText from '@/components/common/SingleLineText.vue'
import {
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer ,
confirmRelabelCompletion , downloadRelabelCompletionPhoto , uploadRelabelShippingLabel ,
listDiscardCandidates , listDiscards , listLastlegOrders , listRelabelInventoryCandidates , listRelabels , listTransfers ,
listDiscardCandidates , listDiscards , listRelabelInventoryCandidates , listRelabels , listTransfers ,
type RelabelInventoryCandidate ,
type RelabelCreate ,
type RelabelDetail ,
@@ -28,7 +28,7 @@ interface ServiceConfig { title: string; createTitle: string; numberLabel: strin
const props = defineProps < { service : ServiceType } > ( )
const configs : Record < ServiceType , ServiceConfig > = {
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '客户单号' , '海外操作仓' , '贴标要求' , 'SKU 数' , '总箱数' , '状态' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '进仓编号' , '入库箱数' , '中转仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '状态' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '入库箱数' , '中转仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '状态' , '创建时间' ] } ,
disposal : { title : '货物废弃' , createTitle : '新增货物废弃申请' , numberLabel : '废弃申请单号' , emptyText : '暂无货物废弃申请' , columns : [ '操作' , '废弃申请单号' , '进仓编号' , '操作仓' , 'SKU 汇总' , '废弃数量' , '预估费用' , '状态' , '创建时间' ] } ,
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '创建时间' ] } ,
}
@@ -301,7 +301,7 @@ async function load() {
} else {
const result = await listTransfers ( { page : 1 , size : 20 , transferNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , operatingWarehouseId : filters . warehouse || undefined , fbaWarehouseCode : filters . destination || undefined , status : filters . status || undefined , needRelabel : props . service === 'relabel' , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) } )
submittedRows . value = result . records . map ( ( item ) => props . service === 'transfer'
? row ( [ item . transferNo , item . inboundCode , item . packageCount , '—' , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是' : '否' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] )
? row ( [ item . transferNo , item . packageCount , '—' , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是 · 待绑定换标单 ' : '否' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] )
: row ( [ item . transferNo , '—' , item . fbaWarehouseCode , '—' , item . packageCount , '是' , item . relabelNo ? '已生成' : '待生成' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) )
}
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '列表加载失败' ) }
@@ -318,7 +318,7 @@ async function submit() {
? 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 ) {
if ( ( props . service = == 're label' && ! form . inboundNo ) || ( props . service !== 'disposal' && ! primaryWarehouse ) || invalidCargo ) {
ElMessage . warning ( '请填写必填信息和完整的货物明细' ) ; return
}
if ( props . service === 'labeling' && ( ! form . labelDeadline || ! form . contact || ! form . phone || form . chargeAmount <= 0 ) ) {
@@ -371,10 +371,8 @@ async function submit() {
} )
await createDiscard ( { items , idempotencyKey , remark : [ form . reason , form . remark ] . filter ( Boolean ) . join ( '; ' ) || undefined } )
} else {
const sources = await listLastlegOrders ( form . inboundNo )
if ( sources . records . length === 0 ) throw new Error ( '未找到该入仓码对应的尾程订单' )
const packageCount = cargoRows . value . reduce ( ( sum , item ) => sum + item . quantity , 0 )
await createTransfer ( { inboundCode : form . inboundNo , sourceLastlegOrderIds : sources . records . map ( ( item ) => item . id ) , operatingWarehouseId : primaryWarehouse , fbaWarehouseCode : form . destination || undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , fbaShipmentNo : form . fbaShipmentNo || undefined , trackingNo : form . trackingNo || undefined , remark : form . remark || undefined , needRelabel : props . service === 'relabel' || form . relabel , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , packageCount : item . quantity , shippingMark : item . shippingMark || undefined } ) ) } )
await createTransfer ( { operatingWarehouseId : primaryWarehouse , fbaWarehouseCode : form . destination || undefined , deliveryAddress : form . address , packageCount , volumeCbm : form . volumeCbm , deliveryMethod : form . deliveryMethod , estimatedArrivalAt : form . estimatedArrivalAt , fbaShipmentNo : form . fbaShipmentNo || undefined , trackingNo : form . deliveryMethod === 'CUSTOMER_DELIVERY' ? form . trackingNo || undefined : undefined , remark : form . remark || undefined , needRelabel : props . service === 'relabel' || form . relabel , idempotencyKey , items : cargoRows . value . map ( ( item ) => ( { skuCode : item . sku , productNameZh : item . name , packageCount : item . quantity } ) ) } )
}
ElMessage . success ( ` ${ config . value . title } 提交成功 ` ) ; mode . value = 'list' ; await load ( )
} catch ( error ) { ElMessage . error ( error instanceof Error ? error . message : '提交失败' ) }
@@ -512,7 +510,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< div class = "warehouse-card warehouse-form-section" > < h3 > { { props . service === 'transfer' ? '入库与转运信息' : props . service === 'relabel' ? '退货信息' : props . service === 'labeling' ? '订单信息' : '基础信息' } } < / h3 >
< div class = "warehouse-form-grid" >
< label v-if = "props.service === 'disposal'" class="warehouse-span-2"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable :loading="discardCandidatesLoading" placeholder="请选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidates" :key="candidate.orderId" :label="`${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}| ${candidate.overseasWarehouseName || '—'}|可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 仅显示一件代发中仍有可用箱数的入库单 。 < / small > < / label >
< label v-if = "props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-else-if = "props.service === 'disposal'"><span>进仓编号</span><el-input v-model="form.inboundNo" readonly / > < / label >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库中转仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(fbaWarehousesError)" clearable placeholder="请选择国外中转仓" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="props.service === 'labeling'" class="warehouse-hint">选择后仅查询当前客户在该中转仓的可用库存。</small><small v-else-if="props.service === 'transfer'" class="warehouse-hint" > 本批货物将先进入该仓完成签收和入库 。 < / small > < / label >
@@ -543,9 +541,9 @@ function selectRelabelInventory(inboundOrderId: string) {
< label v-if = "props.service === 'transfer' && transferHasFbaDestination"><span><i>*</i>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号" / > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号" / > < / label >
< label v-if = "props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求" / > < / label >
< label v-else-if = "props.service ! == 'disposal' "><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号" / > < / label >
< label v-else-if = "props.service = == 'relabel' || (props.service === 'transfer' && form.deliveryMethod === 'CUSTOMER_DELIVERY') "><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号(选填) " / > < / label >
< label class = "warehouse-span-2" > < span > 订单备注 < / span > < el-input v-model = "form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" / > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否需要换标</span><el-radio-group v-model="form.relabel"><el-radio-button :value="false">不需要换标</el-radio-button><el-radio-button :value="true">需要换标</el-radio-button></el-radio-group><small v-if="form.relabel" > 提交后将同时生成待处理的换标任务 。 < / small > < / div >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>是否需要换标</span><el-radio-group v-model="form.relabel"><el-radio-button :value="false">不需要换标</el-radio-button><el-radio-button :value="true">需要换标</el-radio-button></el-radio-group><small v-if="form.relabel" > 转运单将显示换标标识 , 完成入库后再绑定换标单 。 < / small > < / div >
< / div >
< / div >
< div class = "warehouse-card warehouse-form-section" >
@@ -570,7 +568,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< / template >
< template v-else >
< div class = "warehouse-section-heading" > < h3 > 货物明细 < / h3 > < div v-if = "props.service !== 'disposal'"><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []" > 清空 < / el -button > < / div > < / div >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table" > < thead > < tr > < th > 商品 SKU * < / th > < th > 中文品名 * < / th > < th v-if = "props.service === 'disposal'">可用箱数</th><th v-else >FNSKU / 箱唛</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : '箱数' }} *</th><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="请输入商品 SKU" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" placeholder="请输入中文品名" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-else ><el-input v-model="row.shippingMark" placeholder="请输入FNSKU或箱唛" /></td><td><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'disposal' ? 4 : 5 "><div class="warehouse-empty" > {{ props.service = = = ' disposal ' ? ' 请先选择入库单 , 系统将自动带出 SKU 和可用箱数 ' : ' 暂无货物明细 , 请点击 “ 添加行 ” ' }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< div class = "warehouse-table-scroll" > < table class = "warehouse-edit-table" > < thead > < tr > < th > 商品 SKU * < / th > < th > 中文品名 * < / th > < th v-if = "props.service === 'disposal'">可用箱数</th><th v-if="props.service === 'relabel'" >FNSKU / 箱唛</th><th>{{ props.service === 'disposal' ? '本次废弃箱数' : '预计 箱数' }} *</th><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="请输入商品 SKU" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" placeholder="请输入中文品名" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-if="props.service === 'relabel'" ><el-input v-model="row.shippingMark" placeholder="请输入FNSKU或箱唛" /></td><td><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="row.availableQuantity" controls-position="right" placeholder="请输入箱数" /></td><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" />删除</button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'disposal' ? 4 : props.service === 'relabel' ? 5 : 4 "><div class="warehouse-empty" > {{ props.service = = = ' disposal ' ? ' 请先选择入库单 , 系统将自动带出 SKU 和可用箱数 ' : ' 暂无货物明细 , 请点击 “ 添加行 ” ' }} < / div > < / td > < / tr > < / tbody > < / table > < / div >
< / template >
< / div >
< div class = "warehouse-submit-bar" > < el-button @click ="resetForm" > 重置 < / el -button > < el-button type = "primary" native -type = " submit " :loading = "saving" > 提交 { { config . title } } < / el-button > < / div >