@@ -189,13 +189,24 @@ 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货件编号' , '我方箱唛' , '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 cartonTemplate = labelingMode . value === 'carton'
const combinedTemplate = labelingMode . value === 'carton_and_sku'
const header = cartonTemplate
? [ '明细行标识' , '本次箱数' , '商品SKU' , '中文品名' , 'FBA货件编号' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' , '备注' ]
: [ '我方箱唛' , '旧SKU标签' , '新SKU标签' , '换标数量(件)' ,
... ( combinedTemplate ? [ '新FBA箱唛' , 'FBA目的仓' ] : [ ] ) , '备注' ]
const rows = cartonTemplate
? cargoRows . value . map ( ( item ) => [ item . id , item . quantity , item . sku , item . name , item . shipmentNo ,
item . cartonMark , item . fbaCartonMark , item . destinationWarehouseId , item . remark ] )
: skuRelabelRows . value . map ( ( item ) => [ item . cartonMark , item . originalSku , item . newSku , item . quantity ,
... ( combinedTemplate ? [ 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 )
const anchor = document . createElement ( 'a' )
anchor . href = url ; anchor . download = '贴标申请-关联面单模板.csv' ; anchor . click ( ) ; URL . revokeObjectURL ( url )
anchor . href = url
anchor . download = cartonTemplate ? '贴标申请-箱唛换标模板.csv'
: combinedTemplate ? '贴标申请-SKU和箱唛换标模板.csv' : '贴标申请-SKU换标模板.csv'
anchor . click ( ) ; URL . revokeObjectURL ( url )
}
function parseCsvLine ( line : string ) {
const values : string [ ] = [ ] ; let value = '' ; let quoted = false
@@ -213,6 +224,41 @@ 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 ] )
if ( labelingMode . value !== 'carton' ) {
const combinedTemplate = labelingMode . value === 'carton_and_sku'
const required = [ '我方箱唛' , '旧SKU标签' , '新SKU标签' , '换标数量(件)' ,
... ( combinedTemplate ? [ '新FBA箱唛' , 'FBA目的仓' ] : [ ] ) ]
if ( required . some ( ( name ) => ! headers . includes ( name ) ) ) {
ElMessage . error ( 'SKU 换标模板格式不正确,请重新下载当前换标类型的模板' ) ; return
}
const imported = lines . slice ( 1 ) . map ( parseCsvLine )
. map ( ( values ) => Object . fromEntries ( headers . map ( ( key , index ) => [ key , values [ index ] || '' ] ) ) )
. filter ( ( item ) => Object . values ( item ) . some ( Boolean ) )
if ( ! imported . length || imported . some ( ( item ) => required . some ( ( key ) => ! item [ key ] ) ) ) {
ElMessage . error ( '我方箱唛、旧 SKU 标签、新 SKU 标签和换标数量必须填写完整' ) ; return
}
const rows : SkuRelabelRow [ ] = [ ]
for ( const item of imported ) {
const quantity = Number ( item [ '换标数量(件)' ] )
if ( ! Number . isInteger ( quantity ) || quantity < 1 ) {
ElMessage . error ( ` 箱唛 ${ item [ '我方箱唛' ] } 的换标数量必须是大于 0 的整数 ` ) ; return
}
rows . push ( {
id : ++ skuRelabelSequence ,
sourceRowId : null ,
cartonMark : item [ '我方箱唛' ] . trim ( ) ,
originalSku : item [ '旧SKU标签' ] . trim ( ) ,
newSku : item [ '新SKU标签' ] . trim ( ) ,
quantity ,
fbaCartonMark : combinedTemplate ? item [ '新FBA箱唛' ] . trim ( ) : '' ,
destinationWarehouseId : combinedTemplate ? item [ 'FBA目的仓' ] . trim ( ) : '' ,
remark : item [ '备注' ] || '' ,
} )
}
skuRelabelRows . value = rows
ElMessage . success ( ` 已导入 ${ rows . length } 条 SKU 换标明细 ` )
return
}
const required = [ '明细行标识' , '本次箱数' , 'FBA货件编号' , '我方箱唛' , 'FBA箱唛' , 'FBA目的仓' ]
if ( required . some ( ( name ) => ! headers . includes ( name ) ) ) { ElMessage . error ( '模板格式不正确,请重新下载模板填写' ) ; return }
const imported = lines . slice ( 1 ) . map ( parseCsvLine )
@@ -548,7 +594,14 @@ function selectRelabelInventory(inboundOrderId: string) {
< / div >
< div class = "warehouse-card warehouse-form-section" >
< template v-if = "props.service === 'labeling'" >
< h3 > 货物明细 < / h3 >
< div class = "warehouse-section-heading warehouse-labeling-heading" >
< h3 > 货物明细 < / h3 >
< div class = "warehouse-template-actions" >
< el-button size = "small" :disabled = "!cargoRows.length" @click ="downloadRelabelTemplate" > < Download :size = "15" / > 下载 { { labelingMode === 'carton' ? '箱唛' : 'SKU' } } 模板 < / el-button >
< el-button size = "small" :disabled = "!cargoRows.length" @click ="templateInput?.click()" > < Upload :size = "15" / > 上传 { { labelingMode === 'carton' ? '箱唛' : 'SKU' } } 模板 < / el-button >
< input ref = "templateInput" class = "warehouse-hidden-input" type = "file" accept = ".csv,text/csv" @change ="importRelabelTemplate" >
< / div >
< / div >
< div class = "warehouse-labeling-mode" >
< span > 换标类型 < / span >
< el-radio-group v-model = "labelingMode" aria-label="换标类型" >
@@ -560,7 +613,7 @@ function selectRelabelInventory(inboundOrderId: string) {
< / 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-if = "labelingMode === 'carton'"><el-button type="primary" size="small" @click="addRelabelCargoRow"><Plus :size="15" />手动添加行</el-button><el-button size="small" @click="clearRelabelRows" > 清空 < / el -button > < / div >
< div v-else > < el -button type = "primary" size = "small" @click ="addSkuRelabelRow" > < Plus :size = "15" / > 添加 SKU 明细 < / el-button > < el-button size = "small" @click ="skuRelabelRows = []" > 清空 < / el -button > < / div >
< / div >
< div v-if = "labelingMode === 'carton'" class="warehouse-table-scroll"><table class="warehouse-edit-table warehouse-label-table"><thead><tr><th>商品 SKU</th><th>中文品名 *</th><th>FBA 货件编号 *</th><th>箱数 *</th><th>我方箱唛 *</th><th>新 FBA 箱唛 *</th><th>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 >
@@ -606,7 +659,7 @@ function selectRelabelInventory(inboundOrderId: string) {
. warehouse - choice - field { display : grid ; grid - template - columns : 150 px auto 1 fr ; align - items : center ; gap : 14 px ; min - height : 40 px ; padding : 12 px 14 px ; border : 1 px solid # e1e8f2 ; border - radius : 5 px ; background : # f8faff } . warehouse - choice - field > span { color : # 34445 c ; font - size : 13 px ; font - weight : 650 } . warehouse - choice - field > span i { margin - right : 3 px ; color : # e24b4b ; font - style : normal } . warehouse - choice - field > small { color : # 7 a879a ; font - size : 12 px } . warehouse - receiving - card { display : grid ; grid - template - columns : 2 fr 1 fr 1 fr ; gap : 22 px ; padding : 15 px 16 px ; border : 1 px solid # cfe0f5 ; border - radius : 5 px ; background : # f6faff } . warehouse - receiving - card div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 6 px } . warehouse - receiving - card span { color : # 7 c899c ; font - size : 12 px } . warehouse - receiving - card strong { overflow - wrap : anywhere ; color : # 314561 ; font - size : 13 px ; font - weight : 600 }
. warehouse - hidden - input { display : none } . warehouse - label - table { min - width : 1120 px } . warehouse - attachment - tip { margin : 10 px 0 12 px ; color : # 7 d8797 ; font - size : 12 px } . warehouse - mark - mapping { display : flex ; flex - wrap : wrap ; gap : 8 px ; margin : 4 px 0 8 px ; padding : 12 px ; border : 1 px solid # dce7f5 ; border - radius : 5 px ; background : # f7faff } . warehouse - mark - mapping strong { width : 100 % ; color : # 34445 c } . warehouse - mark - mapping span { padding : 5 px 9 px ; border - radius : 3 px ; background : # eaf2fc ; color : # 3 e6593 ; font - size : 12 px }
. warehouse - 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 - 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 - 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 - 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 : center ; gap : 16 px ; padding : 14 px ; border : 1 px solid # e7ebf1 ; border - radius : 4 px ; background : # f8fafc } . warehouse - evidence > span { color : # 71809 a ; font - size : 12 px } . warehouse - evidence > strong { color : # 8 a95a5 ; font - size : 12 px ; font - weight : 400 } . warehouse - photo - actions { display : flex ; flex - wrap : wrap ; gap : 8 px } . warehouse - completion - remark { margin : 10 px 0 0 ; color : # 596579 ; font - size : 12 px }
< / style >