feat: expose dropship palletization progress

This commit is contained in:
2026-09-08 17:24:51 +08:00
parent 2add4b001e
commit f76c5cfd09
13 changed files with 52 additions and 8 deletions
+1
View File
@@ -217,6 +217,7 @@ export interface MerchantFirstLegOrder {
customerId: number | string | null
customerName: string
fulfillmentType?: 'STANDARD' | 'ONE_CLICK_DROPSHIP' | null
lxCode?: string | null
status: ApiFirstLegStatus
transportMode?: ApiTransportMode
tradeTerm: string | null
@@ -35,3 +35,7 @@ test('keeps all backend Long identifiers as strings', () => {
assert.doesNotMatch(typesSource, /(?:id|Id): number/)
assert.doesNotMatch(typesSource, /internalNotes|statusHistory|files/)
})
test('accepts the one-click dropship palletization process status', () => {
assert.match(typesSource, /MerchantInboundProcessStatus[\s\S]*?'WAIT_PALLETIZE'[\s\S]*?'WAIT_SPLIT'/)
})
@@ -4,8 +4,11 @@ import test from 'node:test'
const source = readFileSync(new URL('./unifiedOrders.ts', import.meta.url), 'utf8')
test('keeps the merchant unified-order boundary and phase-two WAIT_SPLIT status', () => {
test('keeps the merchant unified-order boundary and one-click palletization status', () => {
assert.match(source, /const unifiedOrderBaseUrl = '\/merchant\/v1\/orders'/)
assert.match(source, /ApiUnifiedOrderMainStatus[\s\S]*?'WAIT_PALLETIZE'/)
assert.match(source, /ApiUnifiedOrderMainStatus[\s\S]*?'WAIT_SPLIT'/)
assert.match(source, /fulfillmentType\?: 'STANDARD' \| 'ONE_CLICK_DROPSHIP' \| null/)
assert.match(source, /lxCode\?: string \| null/)
assert.match(source, /mainStatus\?: ApiUnifiedOrderMainStatus/)
})
@@ -4,6 +4,7 @@ import type { ApiContainerMode, ApiDestinationType, ApiFirstLegStatus, ApiPickup
export type ApiUnifiedOrderMainStatus =
| 'PENDING_REVIEW'
| 'WAIT_MEASURE'
| 'WAIT_PALLETIZE'
| 'WAIT_SPLIT'
| 'LOADING'
| 'LOADED'
@@ -54,6 +55,8 @@ export interface MerchantUnifiedOrderRow {
orderId: number | string
tenantId: number | string
orderNo: string | null
fulfillmentType?: 'STANDARD' | 'ONE_CLICK_DROPSHIP' | null
lxCode?: string | null
mainStatus: ApiUnifiedOrderMainStatus
nextAction: string | null
internalOrderStatus: ApiFirstLegStatus | null
@@ -23,7 +23,7 @@ function stageState(index: number) {
<div class="progress-heading">
<div>
<h3 id="order-progress-title">订单进度</h3>
<span>国内装柜进度及后续履约同步</span>
<span>国内入仓、组托、装柜及后续履约同步</span>
</div>
<el-tag :type="order.status === 'DELIVERED' ? 'success' : order.status === 'DRAFT' || order.status === 'PENDING_REVIEW' ? 'warning' : 'info'" effect="light">
{{ getFirstLegStatusLabel(order.status) }}
@@ -80,6 +80,8 @@ test('declares the final C02 row and detail contract in the API adapter', () =>
assert.match(unifiedApiSource, /orderId: number \| string/)
assert.match(unifiedApiSource, /mainStatus: ApiUnifiedOrderMainStatus/)
assert.match(unifiedApiSource, /nextAction: string \| null/)
assert.match(unifiedApiSource, /fulfillmentType\?: 'STANDARD' \| 'ONE_CLICK_DROPSHIP' \| null/)
assert.match(unifiedApiSource, /lxCode\?: string \| null/)
assert.match(unifiedApiSource, /customerRemark: string \| null/)
for (const field of ['pickupMethod', 'requestedPickupAt', 'containerMode', 'destinationType', 'destinationCode', 'skuNos', 'totalQuantity', 'totalNetWeightKg', 'chargeableVolumeCbm', 'inboundCompletedAt', 'shipmentId', 'referenceId']) {
assert.match(unifiedApiSource, new RegExp(`${field}:`))
@@ -117,6 +119,10 @@ test('maps C02 authoritative status, transport, cargo and pickup snapshots', ()
assert.match(storeSource, /id: String\(summary\.orderId\)/)
assert.match(storeSource, /transportMode: unifiedTransport\(summary\.containerMode\)/)
assert.match(storeSource, /status: summary\.mainStatus/)
assert.match(storeSource, /fulfillmentType: summary\.fulfillmentType \?\? 'STANDARD'/)
assert.match(storeSource, /lxCode: summary\.lxCode \?\? ''/)
assert.match(storeSource, /mapped\.fulfillmentType = detail\.summary\.fulfillmentType \?\? mapped\.fulfillmentType/)
assert.match(storeSource, /mapped\.lxCode = detail\.summary\.lxCode \?\? mapped\.lxCode/)
assert.match(storeSource, /requestedPickupAt: summary\.requestedPickupAt \?\? ''/)
assert.match(storeSource, /chargeableVolumeCbm: summary\.chargeableVolumeCbm \?\? null/)
assert.match(storeSource, /inboundedAt: summary\.inboundCompletedAt \?\? ''/)
@@ -127,6 +133,7 @@ test('maps C02 authoritative status, transport, cargo and pickup snapshots', ()
assert.match(storeSource, /summary\?\.loading\?\.pickingVersion/)
assert.match(storeSource, /summary\?\.inboundId == null \? '' : String\(summary\.inboundId\)/)
assert.match(storeSource, /customerRemark: summary\.customerRemark \?\? ''/)
assert.match(storeSource, /PALLETIZE_DROPSHIP: '一件代发组托'/)
})
test('sends a manually entered old-system inbound code with the draft payload', () => {
@@ -231,6 +231,7 @@ const unifiedActionLabels: Readonly<Record<string, string>> = {
SUBMIT_ORDER: '订单已提交',
APPROVE_ORDER: '订单审核通过',
CONFIRM_MEASUREMENT: '测量数据已确认',
PALLETIZE_DROPSHIP: '一件代发组托',
SMART_SPLIT: '分柜方案已更新',
CONFIRM_PRELOAD: '预装清单已确认',
COMPLETE_LOADING: '装柜已完成',
@@ -279,7 +280,7 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
return {
id: String(order.id ?? order.orderId ?? ''), orderNo: order.orderNo, referenceNo: order.referenceId ?? '',
transportMode: order.transportMode ? clientTransport(order) : null,
businessType: 'EXPORT', fulfillmentType: order.fulfillmentType ?? 'STANDARD', origin: shipper?.city ?? '', destination: order.destinationWarehouseName || order.destinationCountryCode || '',
businessType: 'EXPORT', fulfillmentType: order.fulfillmentType ?? 'STANDARD', lxCode: order.lxCode ?? '', origin: shipper?.city ?? '', destination: order.destinationWarehouseName || order.destinationCountryCode || '',
receivingWarehouseId: String(order.receivingWarehouseId), receivingWarehouseName: order.receivingWarehouseName,
receivingWarehouseSnapshot: orderWarehouseSnapshot(order, warehouse), expectedDepartureDate: order.expectedShipDate ?? '', etd: '', eta: '',
shipperPartyId: shipper?.id ?? '', shipperPartySnapshot: shipper, shipper: shipper?.name ?? '',
@@ -328,7 +329,7 @@ function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: Platfo
const warehouse = warehouses.find((item) => item.id === String(summary.receivingWarehouseId))
return {
id: String(summary.orderId), orderNo: summary.orderNo, referenceNo: summary.referenceId ?? '', transportMode: unifiedTransport(summary.containerMode),
businessType: 'EXPORT', origin: '', destination: summary.destinationWarehouseName ?? '',
businessType: 'EXPORT', fulfillmentType: summary.fulfillmentType ?? 'STANDARD', lxCode: summary.lxCode ?? '', origin: '', destination: summary.destinationWarehouseName ?? '',
receivingWarehouseId: summary.receivingWarehouseId == null ? '' : String(summary.receivingWarehouseId),
receivingWarehouseName: summary.receivingWarehouseName ?? '', receivingWarehouseSnapshot: warehouseSnapshot(warehouse),
expectedDepartureDate: summary.expectedShipDate ?? '', etd: '', eta: '', shipperPartyId: '', shipperPartySnapshot: null,
@@ -381,6 +382,8 @@ function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: P
const mapped = mapOrder(detail.order.order, warehouses, detail.order)
mapped.id = String(detail.summary.orderId)
mapped.status = detail.summary.mainStatus
mapped.fulfillmentType = detail.summary.fulfillmentType ?? mapped.fulfillmentType
mapped.lxCode = detail.summary.lxCode ?? mapped.lxCode
mapped.businessSummary = businessSummary(detail.summary)
mapped.timeline = unifiedTimeline(detail)
const summary = mapUnifiedOrderRow(detail.summary, warehouses)
@@ -6,6 +6,7 @@ export type MerchantInboundProcessStatus =
| 'WAIT_PRICING'
| 'FEE_CONFIRMING'
| 'INBOUNDING'
| 'WAIT_PALLETIZE'
| 'WAIT_SPLIT'
| 'VOIDED'
@@ -5,6 +5,8 @@ import {
canClientCancelFirstLegOrder,
canClientEditFirstLegOrder,
createEmptyCargoItem,
FIRST_LEG_PROGRESS_STAGES,
getFirstLegProgressIndex,
getFirstLegStatusLabel,
getFirstLegTransportModeLabel,
summarizeFirstLegCargo,
@@ -58,12 +60,16 @@ function validDraft() {
test('uses the confirmed status and transport labels', () => {
assert.equal(getFirstLegStatusLabel('WAIT_MEASURE'), '待测量数据')
assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜')
assert.equal(getFirstLegStatusLabel('WAIT_PALLETIZE'), '待组托')
assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜')
assert.equal(getFirstLegStatusLabel('LOADING'), '装柜中')
assert.equal(getFirstLegStatusLabel('LOADED'), '装柜完成')
assert.equal(getFirstLegStatusLabel('DELIVERED'), '派送完成')
assert.equal(getFirstLegTransportModeLabel('FCL'), '海运整箱 FCL')
assert.deepEqual(FIRST_LEG_PROGRESS_STAGES.map((item) => item.code), [
'PENDING_REVIEW', 'WAIT_MEASURE', 'WAIT_PALLETIZE', 'WAIT_SPLIT', 'LOADING', 'LOADED', 'DELIVERED',
])
assert.equal(getFirstLegProgressIndex('WAIT_PALLETIZE'), 2)
})
test('only allows draft orders to be edited in the confirmed phase-one flow', () => {
@@ -72,6 +78,7 @@ test('only allows draft orders to be edited in the confirmed phase-one flow', ()
assert.equal(canClientCancelFirstLegOrder('DRAFT'), true)
assert.equal(canClientCancelFirstLegOrder('PENDING_REVIEW'), true)
assert.equal(canClientCancelFirstLegOrder('WAIT_MEASURE'), false)
assert.equal(canClientCancelFirstLegOrder('WAIT_PALLETIZE'), false)
assert.equal(canClientCancelFirstLegOrder('WAIT_SPLIT'), false)
})
@@ -199,7 +199,7 @@ test('shows one unified progress line and C02 business summaries without detail
assert.match(source, /summary\.expectedPickCount/)
assert.match(source, /summary\.deliveryTrackingNo/)
assert.match(progressSource, /FIRST_LEG_PROGRESS_STAGES/)
assert.match(progressSource, /国内装柜进度及后续履约同步/)
assert.match(progressSource, /国内入仓、组托、装柜及后续履约同步/)
assert.doesNotMatch(progressSource, /进度记录|el-timeline|timeline/)
assert.doesNotMatch(source, /<h3>报关回执<\/h3>/)
assert.match(source, /船期摘要/)
@@ -369,6 +369,12 @@ test('distills order creation into the confirmed prototype sections with server
assert.doesNotMatch(source, /v-model="form\.(?:businessType|origin|destination|priority|tradeTerm)"/)
})
test('shows an optional LX inbound order number for one-click dropship orders', () => {
assert.match(source, /v-if="form\.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode"/)
assert.match(source, /v-model="form\.lxCode"[^>]*placeholder="请输入LX入库单号"/)
assert.doesNotMatch(source, /label="LX入库单号"[^>]*required/)
})
test('loads only active server channels for the selected domestic warehouse', () => {
assert.match(source, /getMerchantPricingChannels\(warehouseId\)/)
assert.match(source, /const channelOptions = ref<MerchantPricingChannel\[\]>\(\[\]\)/)
@@ -1898,7 +1898,7 @@ async function searchOrders(model: QueryModel) {
<div class="order-section-heading"><div><h3>客户下单</h3><p>先选择货物如何送到国内收货仓,并填写本次运输安排。</p></div></div>
<div class="prototype-form-flow shipping-form-flow">
<el-form-item label="履约类型" prop="fulfillmentType" required class="prototype-form-row prototype-form-row--tabs"><el-radio-group v-model="form.fulfillmentType" class="segmented-options"><el-radio-button value="STANDARD">普通头程</el-radio-button><el-radio-button value="ONE_CLICK_DROPSHIP">一件代发</el-radio-button></el-radio-group><small v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" class="field-guidance">测量完成后不办理国内库存入库,将直接推送 PDA 合托。</small></el-form-item>
<el-form-item v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX 代码" prop="lxCode" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.lxCode" maxlength="128" placeholder="请输入领星入库单 LX 代码" /><small class="field-guidance">用于绑定领星入库单,提交后可在尾程订单、入库单和出库单中查询。</small></el-form-item>
<el-form-item v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.lxCode" maxlength="128" placeholder="请输入LX入库单号" /><small class="field-guidance">用于绑定领星入库单,提交后可在尾程订单、入库单和出库单中查询。</small></el-form-item>
<el-form-item label="取货方式" prop="pickupMethod" required class="prototype-form-row prototype-form-row--tabs"><el-radio-group v-model="form.pickupMethod" class="segmented-options" @change="changePickupMethod"><el-radio-button value="SELF_EXPRESS">快递自送</el-radio-button><el-radio-button value="PICKUP">我司提货</el-radio-button><el-radio-button value="SELF_TRUCK">卡车自送</el-radio-button></el-radio-group></el-form-item>
<template v-if="form.pickupMethod === 'PICKUP' && form.shipperPartySnapshot">
<el-form-item label="企业提货地址簿" prop="senderAddressId" required :rules="requiredSelectRules('请选择企业提货地址')" class="prototype-form-row prototype-form-row--wide address-book-row">
@@ -120,9 +120,11 @@ function warehouseDisplay(value?: string) {
}
function statusLabel(value: string) {
const labels: Record<string, string> = {
PENDING_PROCESSING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消',
PENDING_REVIEW: '待审核', APPROVED: '已通过', REJECTED: '已拒绝', PENDING_ASSIGNMENT: '待分配',
ALLOCATED: '已分配', OUTBOUNDED: '已出库', PARTIALLY_RECEIVED: '部分收货', RECEIVED: '已收货',
}
return labels[value] || value || '—'
}
@@ -231,7 +233,9 @@ async function openDetail(value: Record<string, string>) {
<div class="warehouse-filter-actions"><el-button type="primary" @click="query"><Search :size="16" />查询</el-button><el-button @click="resetFilters(); query()">重置</el-button></div>
</div>
<div v-loading="loading" class="warehouse-card warehouse-table-card">
<div class="warehouse-table-scroll"><table><thead><tr><th v-for="column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty">{{ config.emptyText }}</div></td></tr></tbody></table></div>
<div class="warehouse-pagination"><span>共 <strong>{{ submittedRows.length }}</strong> 条</span><el-button size="small" disabled>上一页</el-button><button type="button" class="warehouse-page-number">1</button><el-button size="small" disabled>下一页</el-button></div>
</div>
</template>
@@ -260,14 +264,18 @@ async function openDetail(value: Record<string, string>) {
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>总体积(CBM)</span><el-input-number v-model="form.volumeCbm" :min="0.0001" :precision="4" controls-position="right" placeholder="请输入总体积" /></label>
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>预计到达时间</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到达时间" /></label>
<label v-if="props.service === 'transfer' || 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 class="warehouse-span-2"><span>订单备注</span><el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" /></label>
</div>
</div>
<div class="warehouse-card warehouse-form-section"><div class="warehouse-section-heading"><h3>货物明细</h3><div v-if="props.service !== 'disposal'"><template v-if="props.service === 'labeling'"><el-button size="small" @click="downloadRelabelTemplate"><Download :size="15" />下载关联模板</el-button><el-button size="small" @click="templateInput?.click()"><Upload :size="15" />上传关联面单</el-button><input ref="templateInput" class="warehouse-hidden-input" type="file" accept=".csv,text/csv" @change="importRelabelTemplate"></template><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []">清空</el-button></div></div>
<p v-if="props.service === 'labeling'" class="warehouse-attachment-tip">模板每行代表一箱;上传后直接回显到下方明细。明细目的仓优先,留空时使用顶部统一目的海外仓。</p>
<div class="warehouse-table-scroll"><table class="warehouse-edit-table" :class="{ 'warehouse-label-table': props.service === 'labeling' }"><thead><tr><th>商品 SKU<span v-if="props.service !== 'labeling'"> *</span></th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th v-if="props.service !== 'labeling'">{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th><template v-if="props.service === 'labeling'"><th>我方箱唛</th><th>FBA 箱唛</th><th>目的仓</th><th>备注</th></template><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-if="props.service === 'labeling'" v-model="row.shipmentNo" placeholder="请输入 FBA 货件编号" /><el-input v-else v-model="row.shippingMark" placeholder="请输入 FNSKU 或箱唛" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" placeholder="请输入换标新 SKU" /></td><td v-if="props.service !== 'labeling'"><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" placeholder="请输入箱数" /></td><template v-if="props.service === 'labeling'"><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" clearable placeholder="使用顶部统一目的仓"><el-option label="美西海外仓-ONT8" value="ONT8" /><el-option label="美东海外仓-ABE8" value="ABE8" /><el-option label="美中海外仓-MDW2" value="MDW2" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td></template><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 === 'labeling' ? 8 : 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" :class="{ 'warehouse-label-table': props.service === 'labeling' }"><thead><tr><th v-if="props.service !== 'disposal'">操作</th><th>商品 SKU<span v-if="props.service !== 'labeling'"> *</span></th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th v-if="props.service !== 'labeling'">{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th><template v-if="props.service === 'labeling'"><th>我方箱唛</th><th>FBA 箱唛</th><th>目的仓</th><th>备注</th></template></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" /></button></td><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="选填" /></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-if="props.service === 'labeling'" v-model="row.shipmentNo" placeholder="请输入 FBA 货件编号" /><el-input v-else v-model="row.shippingMark" placeholder="请输入 FNSKU 或箱唛" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" placeholder="请输入换标新 SKU" /></td><td v-if="props.service !== 'labeling'"><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" placeholder="请输入箱数" /></td><template v-if="props.service === 'labeling'"><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" clearable placeholder="使用顶部统一目的仓"><el-option label="美西海外仓-ONT8" value="ONT8" /><el-option label="美东海外仓-ABE8" value="ABE8" /><el-option label="美中海外仓-MDW2" value="MDW2" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td></template></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'labeling' ? 9 : props.service === 'disposal' ? 4 : 5"><div class="warehouse-empty">{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}</div></td></tr></tbody></table></div>
</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>
</form>