@@ -26,6 +26,7 @@ import {
} from '@/modules/first-leg/api/firstLeg'
import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination'
import { createIdempotencyKey } from '@/utils/idempotencyKey'
import { relabelSkuCodes , relabelSkuCount } from '../utils/relabelSkuDisplay'
type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel'
type PageMode = 'list' | 'create' | 'edit' | 'detail'
@@ -53,7 +54,7 @@ const canConfirmRelabel = computed(() => auth.hasPermission('merchant:overseas-w
const canConfirmTransfer = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:transfer:confirm' ) )
const canCancelDiscard = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:discard:cancel' ) )
const configs : Record < ServiceType , ServiceConfig > = {
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '客户单号' , '海外操作仓' , '贴标要求' , 'SKU 数' , '总箱数' , '状态' , '审核时间' , '贴标完成时间' , '贴标出库时间' , '创建时间' ] } ,
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '客户单号' , '海外操作仓' , '贴标要求' , 'SKU 数(种) ' , '总箱数' , '状态' , '审核时间' , '贴标完成时间' , '贴标出库时间' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '箱数' , '海外仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '换标单号' , '换标状态' , '状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
disposal : { title : '货物废弃' , createTitle : '新增货物废弃申请' , numberLabel : '废弃申请单号' , emptyText : '暂无货物废弃申请' , columns : [ '操作' , '废弃申请单号' , '废弃出库单号' , '进仓编号' , '操作仓' , 'SKU 汇总' , '废弃数量' , '预估费用' , '状态' , '废弃完成时间' , '创建时间' ] } ,
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
@@ -968,7 +969,7 @@ async function load() {
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
submittedRows . value = result . records . map ( ( item ) => {
const destinationWarehouses = warehouseDisplay ( item . overseasWarehouseId )
return { ... row ( [ item . relabelNo , item . customerOrderNo , destinationWarehouses , '—' , item . itemCount , item . totalPackageCount , statusLabel ( item . status ) , formatDateTime ( item . reviewedAt ) , formatDateTime ( item . relabelCompletedAt ) , formatDateTime ( item . outboundAt ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _confirmedAt : item . customerConfirmedAt || '' }
return { ... row ( [ item . relabelNo , item . customerOrderNo , destinationWarehouses , '—' , new Set ( item . skuCodes . split ( ',' ) . map ( code => code . trim ( ) ) . filter ( Boolean ) ) . size , item . totalPackageCount , statusLabel ( item . status ) , formatDateTime ( item . reviewedAt ) , formatDateTime ( item . relabelCompletedAt ) , formatDateTime ( item . outboundAt ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _confirmedAt : item . customerConfirmedAt || '' }
} )
} else if ( props . service === 'disposal' ) {
const result = await listDiscards ( { page : page . value , size : pageSize . value , discardNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , overseasWarehouseId : filters . warehouse || undefined , status : filters . status || undefined , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , completedFrom : dateTime ( filters . completedStart ) , completedTo : dateTime ( filters . completedEnd , true ) } )
@@ -1640,7 +1641,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
</header>
<div v-if=" detailError " class=" warehouse - detail - error " role=" alert "><strong>{{ detailError }}</strong><el-button @click=" loadDetailPage ">重新加载</el-button></div>
<el-alert v-else-if=" props . service === 'labeling' && selectedRelabelDetail ? . application . status === 'REJECTED' " class=" warehouse - rejected - alert " type=" error " :closable=" false " show-icon title=" 贴标单审核已驳回 " :description=" selectedRelabelDetail . application . reviewRemark || '平台未填写驳回备注' " />
<div v-else class=" warehouse - card warehouse - form - section "><h3>订单概览</h3><div v-if=" props . service === 'labeling' " class=" warehouse - detail - grid "><div><span>操作仓</span><strong>{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}</strong></div><div><span>客户参考号</span><strong>{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}</strong></div><div><span>SKU 数</span><strong>{{ selectedRelabelDetail?.application.itemCount ?? '—' }}</strong></div><div><span>总箱数</span><strong>{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}</strong></div><div><span>贴标截止时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}</strong></div><div><span>仓库联系人</span><strong>{{ selectedRelabelDetail?.application.contactName || '—' }}</strong></div><div><span>联系电话</span><strong>{{ selectedRelabelDetail?.application.contactPhone || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}</strong></div></div><div v-else-if=" props . service === 'transfer' " class=" warehouse - detail - grid warehouse - transfer - overview - grid "><div><span>状态</span><el-tag :type=" transferStatusTagType ( selectedDetail ? . status || '' ) " effect=" light ">{{ statusLabel(selectedDetail?.status || '') }}</el-tag></div><div><span>目的去向</span><strong>{{ destinationTypeLabel(selectedDetail?.destinationType, selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if=" selectedDetail ? . destinationType !== 'PRIVATE' "><span>FBA 目的仓</span><strong>{{ selectedDetail?.destinationWarehouseName || warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if=" selectedDetail ? . destinationType !== 'PRIVATE' "><span>FBA 货件号</span><strong>{{ selectedDetail?.fbaShipmentNo || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' " class=" warehouse - overview - wide "><span>私人仓地址</span><strong>{{ selectedDetail?.privateDestinationAddress || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>邮编</span><strong>{{ selectedDetail?.privateDestinationPostalCode || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>私人仓联系人</span><strong>{{ selectedDetail?.privateDestinationContactName || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>联系电话</span><strong>{{ selectedDetail?.privateDestinationContactPhone || '—' }}</strong></div><div><span>海外仓</span><strong>{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}</strong></div><div><span>箱数</span><strong>{{ selectedDetail?.packageCount ?? '—' }}</strong></div><div><span>入仓方式</span><strong>{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}</strong></div><div><span>期望发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div><div><span>是否换标</span><strong>{{ selectedDetail?.needRelabel ? '是' : '否' }}</strong></div><div><span>换标状态</span><el-tag :type=" transferRelabelStatusTagType ( transferRelabelStatus ( selectedDetail || { } ) . code ) " effect=" light ">{{ transferRelabelStatus(selectedDetail || {}).label }}</el-tag></div><div><span>换标单号</span><strong>{{ selectedDetail?.relabelNo || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedDetail?.createdAt) }}</strong></div><div><span>转运单号</span><strong>{{ selectedDetail?.transferNo || '—' }}</strong></div></div><div v-else class=" warehouse - detail - grid "><div v-for=" ( column , index ) in config . columns . slice ( 1 ) " :key=" column "><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div></div>
<div v-else class=" warehouse - card warehouse - form - section "><h3>订单概览</h3><div v-if=" props . service === 'labeling' " class=" warehouse - detail - grid "><div><span>操作仓</span><strong>{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}</strong></div><div><span>客户参考号</span><strong>{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}</strong></div><div><span>SKU 数(种) </span><strong>{{ selectedRelabelDetail ? relabelSkuCount(selectedRelabelDetail.items) : '—' }}</strong></div><div><span>总箱数</span><strong>{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}</strong></div><div><span>贴标截止时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}</strong></div><div><span>仓库联系人</span><strong>{{ selectedRelabelDetail?.application.contactName || '—' }}</strong></div><div><span>联系电话</span><strong>{{ selectedRelabelDetail?.application.contactPhone || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}</strong></div></div><div v-else-if=" props . service === 'transfer' " class=" warehouse - detail - grid warehouse - transfer - overview - grid "><div><span>状态</span><el-tag :type=" transferStatusTagType ( selectedDetail ? . status || '' ) " effect=" light ">{{ statusLabel(selectedDetail?.status || '') }}</el-tag></div><div><span>目的去向</span><strong>{{ destinationTypeLabel(selectedDetail?.destinationType, selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if=" selectedDetail ? . destinationType !== 'PRIVATE' "><span>FBA 目的仓</span><strong>{{ selectedDetail?.destinationWarehouseName || warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}</strong></div><div v-if=" selectedDetail ? . destinationType !== 'PRIVATE' "><span>FBA 货件号</span><strong>{{ selectedDetail?.fbaShipmentNo || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' " class=" warehouse - overview - wide "><span>私人仓地址</span><strong>{{ selectedDetail?.privateDestinationAddress || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>邮编</span><strong>{{ selectedDetail?.privateDestinationPostalCode || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>私人仓联系人</span><strong>{{ selectedDetail?.privateDestinationContactName || '—' }}</strong></div><div v-if=" selectedDetail ? . destinationType === 'PRIVATE' "><span>联系电话</span><strong>{{ selectedDetail?.privateDestinationContactPhone || '—' }}</strong></div><div><span>海外仓</span><strong>{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}</strong></div><div><span>箱数</span><strong>{{ selectedDetail?.packageCount ?? '—' }}</strong></div><div><span>入仓方式</span><strong>{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}</strong></div><div><span>期望发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div><div><span>是否换标</span><strong>{{ selectedDetail?.needRelabel ? '是' : '否' }}</strong></div><div><span>换标状态</span><el-tag :type=" transferRelabelStatusTagType ( transferRelabelStatus ( selectedDetail || { } ) . code ) " effect=" light ">{{ transferRelabelStatus(selectedDetail || {}).label }}</el-tag></div><div><span>换标单号</span><strong>{{ selectedDetail?.relabelNo || '—' }}</strong></div><div><span>创建时间</span><strong>{{ formatDateTime(selectedDetail?.createdAt) }}</strong></div><div><span>转运单号</span><strong>{{ selectedDetail?.transferNo || '—' }}</strong></div></div><div v-else class=" warehouse - detail - grid "><div v-for=" ( column , index ) in config . columns . slice ( 1 ) " :key=" column "><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div></div>
<div v-if=" props . service === 'transfer' " class=" warehouse - card warehouse - form - section warehouse - transfer - measurement - card ">
<div class=" warehouse - section - heading "><h3>PDA 打托实测数据</h3><span class=" warehouse - hint ">PDA 作业完成即显示入库完成,客户可确认仓库实测数据</span></div>
<div v-if=" selectedDetail ? . pallets ? . length " class=" warehouse - table - scroll "><table><thead><tr><th>托盘号</th><th>库位</th><th>箱数</th><th>长 × 宽 × 高(cm)</th><th>毛重(kg)</th><th>体积(m³)</th><th>状态</th><th>完成时间</th></tr></thead><tbody><tr v-for=" pallet in selectedDetail . pallets " :key=" String ( pallet . id || pallet . sequenceNo ) "><td>{{ pallet.palletNo || `第${pallet.sequenceNo || '—'}托` }}</td><td>{{ pallet.warehouseLocationCode || '—' }}</td><td>{{ pallet.cartonCount ?? '—' }}</td><td>{{ [pallet.lengthCm, pallet.widthCm, pallet.heightCm].every((value) => value != null) ? `${pallet.lengthCm} × ${pallet.widthCm} × ${pallet.heightCm}` : '—' }}</td><td>{{ pallet.grossWeightKg ?? '—' }}</td><td>{{ pallet.volumeCbm ?? '—' }}</td><td>{{ statusLabel(pallet.status || '') }}</td><td>{{ formatDateTime(pallet.completedAt) }}</td></tr></tbody></table></div>
@@ -1669,14 +1670,14 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
<div class=" warehouse - section - heading "><h3>贴标明细资料</h3><input ref=" relabelFileInput " class=" warehouse - hidden - input " type=" file " accept=" application / pdf , . pdf " @change=" uploadSelectedRelabelFile "></div>
<div class=" warehouse - hint ">需要补充或替换资料时,可在对应明细行上传 SKU 或箱唛文件。</div>
<div class=" warehouse - table - scroll "><table class=" warehouse - relabel - detail - table "><thead><tr><th>换标模式</th><th>仓库唛头</th><th>SKU</th><th>明细文件</th><th>操作</th></tr></thead><tbody>
<tr v-for=" item in selectedRelabelDetail ? . items || [ ] " :key=" item . id "><td>{{ item.operationType === 'CARTON_AND_SKU' ? '箱唛 + SKU 换标' : item.operationType === 'SKU_LABEL' ? 'SKU 换标' : '箱唛换标' }}</td><td>{{ item.cartonMark || '—' }}</td><td>{{ item.skuCode || '—' }}</td><td>{{ item.relabelFileName || '未上传' }}</td><td><el-button size=" small " @click=" chooseRelabelFile ( 'ITEM' , String ( item . id ) ) "><Upload :size=" 14 " />上传明细文件</el-button></td></tr>
<tr v-for=" item in selectedRelabelDetail ? . items || [ ] " :key=" item . id "><td>{{ item.operationType === 'CARTON_AND_SKU' ? '箱唛 + SKU 换标' : item.operationType === 'SKU_LABEL' ? 'SKU 换标' : '箱唛换标' }}</td><td>{{ item.cartonMark || '—' }}</td><td>{{ relabelSkuCodes(item).join('、') || '—' }}</td><td>{{ item.relabelFileName || '未上传' }}</td><td><el-button size=" small " @click=" chooseRelabelFile ( 'ITEM' , String ( item . id ) ) "><Upload :size=" 14 " />上传明细文件</el-button></td></tr>
<tr v-if=" ! selectedRelabelDetail ? . items ? . length "><td colspan=" 5 "><div class=" warehouse - empty ">暂无贴标明细</div></td></tr>
</tbody></table></div>
</div>
<div v-if=" props . service === 'labeling' " class=" warehouse - card warehouse - form - section ">
<h3>换标任务明细</h3>
<div class=" warehouse - table - scroll "><table class=" warehouse - relabel - detail - table "><thead><tr><th>换标模式</th><th>我方箱唛</th><th>新 FBA 箱唛</th><th>FBA 目的仓</th><th>任务箱数</th><th>商品 SKU</th><th>SKU 换标明细</th><th>FBA 货件编号</th><th>贴标要求</th><th>备注</th></tr></thead><tbody>
<tr v-for=" item in selectedRelabelDetail ? . items || [ ] " :key=" item . id "><td>{{ item.operationType === 'CARTON_AND_SKU' ? '箱唛 + SKU 换标' : item.operationType === 'SKU_LABEL' ? 'SKU 换标' : '箱唛换标' }}</td><td><strong>{{ item.cartonMark || '—' }}</strong></td><td><strong>{{ item.fbaCartonMark || '—' }}</strong></td><td>{{ warehouseDisplay(item.destinationFbaWarehouseCode) }}</td><td>{{ item.totalPackageCount }} 箱</td><td>{{ item.skuCode || '—' }}</td><td><template v-if=" item . skuItems ? . length "><div v-for=" skuItem in item . skuItems " :key=" ` ${ skuItem . skuCode } - ${ skuItem . newSkuCode } - ${ skuItem . quantity } ` ">{{ skuItem.skuCode }} → {{ skuItem.newSkuCode }} × {{ skuItem.quantity }} 件</div></template><span v-else>{{ item.newSkuCode || '—' }}</span></td><td>{{ item.shipmentNo || '—' }}</td><td>{{ item.labelRequirement || '—' }}</td><td>{{ item.remark || '—' }}</td></tr>
<tr v-for=" item in selectedRelabelDetail ? . items || [ ] " :key=" item . id "><td>{{ item.operationType === 'CARTON_AND_SKU' ? '箱唛 + SKU 换标' : item.operationType === 'SKU_LABEL' ? 'SKU 换标' : '箱唛换标' }}</td><td><strong>{{ item.cartonMark || '—' }}</strong></td><td><strong>{{ item.fbaCartonMark || '—' }}</strong></td><td>{{ warehouseDisplay(item.destinationFbaWarehouseCode) }}</td><td>{{ item.totalPackageCount }} 箱</td><td>{{ relabelSkuCodes(item).join('、') || '—' }}</td><td><template v-if=" item . skuItems ? . length "><div v-for=" skuItem in item . skuItems " :key=" ` ${ skuItem . skuCode } - ${ skuItem . newSkuCode } - ${ skuItem . quantity } ` ">{{ skuItem.skuCode }} → {{ skuItem.newSkuCode }} × {{ skuItem.quantity }} 件</div></template><span v-else>{{ item.newSkuCode || '—' }}</span></td><td>{{ item.shipmentNo || '—' }}</td><td>{{ item.labelRequirement || '—' }}</td><td>{{ item.remark || '—' }}</td></tr>
<tr v-if=" ! selectedRelabelDetail ? . items ? . length "><td colspan=" 10 "><div class=" warehouse - empty " > 暂无换标任务明细 < / div > < / td > < / tr >
< / tbody > < / table > < / div >
< / div >