优化
This commit is contained in:
@@ -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:150px auto 1fr;align-items:center;gap:14px;min-height:40px;padding:12px 14px;border:1px solid #e1e8f2;border-radius:5px;background:#f8faff}.warehouse-choice-field>span{color:#34445c;font-size:13px;font-weight:650}.warehouse-choice-field>span i{margin-right:3px;color:#e24b4b;font-style:normal}.warehouse-choice-field>small{color:#7a879a;font-size:12px}.warehouse-receiving-card{display:grid;grid-template-columns:2fr 1fr 1fr;gap:22px;padding:15px 16px;border:1px solid #cfe0f5;border-radius:5px;background:#f6faff}.warehouse-receiving-card div{display:flex;min-width:0;flex-direction:column;gap:6px}.warehouse-receiving-card span{color:#7c899c;font-size:12px}.warehouse-receiving-card strong{overflow-wrap:anywhere;color:#314561;font-size:13px;font-weight:600}
|
||||
.warehouse-hidden-input{display:none}.warehouse-label-table{min-width:1120px}.warehouse-attachment-tip{margin:10px 0 12px;color:#7d8797;font-size:12px}.warehouse-mark-mapping{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 8px;padding:12px;border:1px solid #dce7f5;border-radius:5px;background:#f7faff}.warehouse-mark-mapping strong{width:100%;color:#34445c}.warehouse-mark-mapping span{padding:5px 9px;border-radius:3px;background:#eaf2fc;color:#3e6593;font-size:12px}
|
||||
.warehouse-unmaintained{margin-left:8px;color:#e6a23c;font-size:12px;font-style:normal}
|
||||
.warehouse-labeling-tabs{margin-top:-8px}.warehouse-tab-actions{align-items:center;margin:2px 0 14px}.warehouse-tab-actions p{margin:0;color:#7d8797;font-size:12px}.warehouse-tab-actions>div{display:flex;gap:8px}.warehouse-sku-notice{margin:0 0 14px;padding:10px 12px;border:1px solid #f2d39b;border-radius:4px;background:#fff8e8;color:#8a6116;font-size:12px}.warehouse-sku-label-table{min-width:980px}.warehouse-sku-label-table :deep(.el-input-number){width:160px}:deep(.warehouse-labeling-tabs>.el-tabs__header){margin-bottom:18px}:deep(.warehouse-labeling-tabs .el-tabs__item){height:42px;padding:0 26px;font-weight:600}.warehouse-labeling-mode{display:flex;align-items:center;gap:14px;margin:0 0 16px;padding:14px 16px;border:1px solid #dce5f5;border-radius:5px;background:#f7f9ff}.warehouse-labeling-mode>span{color:#34445c;font-size:13px;font-weight:650;white-space:nowrap}.warehouse-labeling-mode p{margin:0;color:#71809a;font-size:12px}.warehouse-labeling-mode :deep(.el-radio-group){display:flex;flex-shrink:0}.warehouse-labeling-mode :deep(.el-radio-button__inner){padding:8px 14px}
|
||||
.warehouse-labeling-tabs{margin-top:-8px}.warehouse-labeling-heading{align-items:flex-start}.warehouse-labeling-heading h3{margin-bottom:16px}.warehouse-template-actions{display:flex;gap:8px;padding-bottom:12px}.warehouse-tab-actions{align-items:center;margin:2px 0 14px}.warehouse-tab-actions p{margin:0;color:#7d8797;font-size:12px}.warehouse-tab-actions>div{display:flex;gap:8px}.warehouse-sku-notice{margin:0 0 14px;padding:10px 12px;border:1px solid #f2d39b;border-radius:4px;background:#fff8e8;color:#8a6116;font-size:12px}.warehouse-sku-label-table{min-width:980px}.warehouse-sku-label-table :deep(.el-input-number){width:160px}:deep(.warehouse-labeling-tabs>.el-tabs__header){margin-bottom:18px}:deep(.warehouse-labeling-tabs .el-tabs__item){height:42px;padding:0 26px;font-weight:600}.warehouse-labeling-mode{display:flex;align-items:center;gap:14px;margin:0 0 16px;padding:14px 16px;border:1px solid #dce5f5;border-radius:5px;background:#f7f9ff}.warehouse-labeling-mode>span{color:#34445c;font-size:13px;font-weight:650;white-space:nowrap}.warehouse-labeling-mode p{margin:0;color:#71809a;font-size:12px}.warehouse-labeling-mode :deep(.el-radio-group){display:flex;flex-shrink:0}.warehouse-labeling-mode :deep(.el-radio-button__inner){padding:8px 14px}
|
||||
.warehouse-relabel-detail-table{min-width:1420px}.warehouse-relabel-detail-table td strong{color:#253f67}
|
||||
.warehouse-native-file{min-height:32px;padding:5px 8px;border:1px solid #dcdfe6;border-radius:4px;background:#fff;color:#606266}.warehouse-fulfillment-grid{margin-bottom:18px}.warehouse-evidence{display:flex;align-items:center;gap:16px;padding:14px;border:1px solid #e7ebf1;border-radius:4px;background:#f8fafc}.warehouse-evidence>span{color:#71809a;font-size:12px}.warehouse-evidence>strong{color:#8a95a5;font-size:12px;font-weight:400}.warehouse-photo-actions{display:flex;flex-wrap:wrap;gap:8px}.warehouse-completion-remark{margin:10px 0 0;color:#596579;font-size:12px}
|
||||
</style>
|
||||
|
||||
@@ -27,9 +27,9 @@ test('客户贴标表单不展示客户单号、托盘和内部库存来源列',
|
||||
|
||||
test('贴标申请保留关联模板下载和上传入口', () => {
|
||||
assert.match(view, /downloadRelabelTemplate/)
|
||||
assert.match(view, /下载关联模板/)
|
||||
assert.match(view, /下载\{\{ labelingMode === 'carton' \? '箱唛' : 'SKU' \}\}模板/)
|
||||
assert.match(view, /templateInput\?\.click\(\)/)
|
||||
assert.match(view, /上传关联面单/)
|
||||
assert.match(view, /上传\{\{ labelingMode === 'carton' \? '箱唛' : 'SKU' \}\}模板/)
|
||||
assert.match(view, /importRelabelTemplate/)
|
||||
assert.match(view, /明细行标识/)
|
||||
assert.doesNotMatch(view, /库存位置ID/)
|
||||
@@ -52,6 +52,9 @@ test('贴标货物明细通过单一换标类型选择三种模式', () => {
|
||||
assert.match(view, /原 SKU \*/)
|
||||
assert.match(view, /我方箱唛 \*/)
|
||||
assert.match(view, /换标数量(件)\*/)
|
||||
assert.match(view, /旧SKU标签/)
|
||||
assert.match(view, /新SKU标签/)
|
||||
assert.match(view, /已导入 \$\{rows\.length\} 条 SKU 换标明细/)
|
||||
assert.match(view, /buildSkuRelabelItems/)
|
||||
assert.match(view, /CARTON_AND_SKU/)
|
||||
assert.match(view, /changeCarton/)
|
||||
|
||||
@@ -23,3 +23,19 @@ test('仓储转运允许暂未确定 FBA 去向并显式选择是否换标', ()
|
||||
assert.match(api, /fbaWarehouseCode\?: string/)
|
||||
assert.match(api, /fbaShipmentNo\?: string/)
|
||||
})
|
||||
|
||||
test('仓储转运不要求客户填写内部入仓号和箱唛', () => {
|
||||
const transferConfig = view.match(/transfer:\s*\{[^\n]+/)?.[0] || ''
|
||||
const transferSubmit = view.match(/await createTransfer\(\{[^\n]+/)?.[0] || ''
|
||||
const transferCargoTable = view.match(/<template v-else>[\s\S]*?<\/template>\s*<\/div>\s*<div class="warehouse-submit-bar"/)?.[0] || ''
|
||||
assert.doesNotMatch(transferConfig, /进仓编号/)
|
||||
assert.doesNotMatch(transferSubmit, /inboundCode|sourceLastlegOrderIds|shippingMark/)
|
||||
assert.match(transferCargoTable, /预计箱数/)
|
||||
assert.doesNotMatch(transferCargoTable, /props\.service === 'transfer'.*FNSKU \/ 箱唛/)
|
||||
})
|
||||
|
||||
test('上门提货不提交物流号,需要换标仅提示入库后绑定', () => {
|
||||
assert.match(view, /trackingNo:\s*form\.deliveryMethod === 'CUSTOMER_DELIVERY'\s*\?[^:]+:\s*undefined/)
|
||||
assert.match(view, /完成入库后再绑定换标单/)
|
||||
assert.match(view, /form\.deliveryMethod === 'CUSTOMER_DELIVERY'.*物流跟踪号/s)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user