优化
This commit is contained in:
@@ -150,12 +150,13 @@ export interface FirstLegOrderSaveRequest {
|
||||
}
|
||||
|
||||
export interface MerchantFirstLegOrder {
|
||||
id: number | string
|
||||
id?: number | string
|
||||
orderId?: number | string
|
||||
orderNo: string | null
|
||||
customerId: number | string | null
|
||||
customerName: string
|
||||
status: ApiFirstLegStatus
|
||||
transportMode: ApiTransportMode
|
||||
transportMode?: ApiTransportMode
|
||||
tradeTerm: string | null
|
||||
destinationCountryCode: string
|
||||
destinationWarehouseId: number | string | null
|
||||
@@ -191,6 +192,7 @@ export interface MerchantFirstLegOrder {
|
||||
export interface MerchantFirstLegOrderCargo extends FirstLegOrderCargoPayload {
|
||||
id: number | string
|
||||
itemNo: number
|
||||
productUsage?: string
|
||||
}
|
||||
|
||||
export interface MerchantFirstLegOrderParty extends FirstLegOrderPartyPayload {
|
||||
|
||||
@@ -19,6 +19,8 @@ test('declares the documented merchant warehouse and first-leg endpoints', () =>
|
||||
|
||||
test('maps transport and status fields instead of treating container mode as transport mode', () => {
|
||||
assert.match(storeSource, /order\.transportMode === 'SEA'.*order\.containerMode === 'FULL_CONTAINER' \? 'FCL' : 'LCL'/s)
|
||||
assert.match(storeSource, /order\.id \?\? order\.orderId \?\? ''/)
|
||||
assert.match(storeSource, /order\.transportMode \? clientTransport\(order\) : null/)
|
||||
assert.match(storeSource, /draft\.transportMode === 'ROAD'\) return 'TRUCK'/)
|
||||
assert.match(storeSource, /status === 'SUBMITTED'\) return 'PENDING_REVIEW'/)
|
||||
assert.match(storeSource, /status === 'REJECTED'\) return 'PENDING_REVIEW'/)
|
||||
@@ -88,6 +90,16 @@ test('sends backend-required channel, pricing, customs, destination and cargo fi
|
||||
assert.match(storeSource, /destinationWarehouseId: draft\.destinationType === 'PRIVATE' \? undefined : draft\.destinationWarehouseId/)
|
||||
assert.match(storeSource, /draft\.pickupMethod === 'PICKUP'.*draft\.shipperPartySnapshot/s)
|
||||
assert.match(storeSource, /draft\.destinationType === 'PRIVATE'.*draft\.consigneePartySnapshot/s)
|
||||
for (const field of [
|
||||
'customsHsCode', 'lengthCm', 'widthCm', 'heightCm', 'unitsPerPackage', 'totalQuantity',
|
||||
'netWeightKg', 'unitDeclaredValue', 'material', 'usage', 'model', 'brand',
|
||||
'productLengthCm', 'productWidthCm', 'productHeightCm', 'shipmentId', 'referenceId',
|
||||
'productImageFileId',
|
||||
]) {
|
||||
assert.match(storeSource, new RegExp(`${field}: item\\.`))
|
||||
}
|
||||
assert.match(apiSource, /POST[^\n]*product-images|method: 'POST', url: '\/merchant\/v1\/files\/product-images'/)
|
||||
assert.match(apiSource, /GET[^\n]*product-images|method: 'GET', url: `\/merchant\/v1\/files\/product-images\/\$\{fileId\}`/)
|
||||
})
|
||||
|
||||
test('separates domestic receiving warehouses from public destinations', () => {
|
||||
|
||||
@@ -279,8 +279,9 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
|
||||
const shipper = detail ? partySnapshot(detail, 'SENDER') : null
|
||||
const consignee = detail ? partySnapshot(detail, 'RECEIVER') : null
|
||||
return {
|
||||
id: String(order.id), orderNo: order.orderNo, referenceNo: order.referenceId ?? '', transportMode: clientTransport(order),
|
||||
businessType: 'EXPORT', origin: shipper?.city ?? '', destination: order.destinationWarehouseName || order.destinationCountryCode,
|
||||
id: String(order.id ?? order.orderId ?? ''), orderNo: order.orderNo, referenceNo: order.referenceId ?? '',
|
||||
transportMode: order.transportMode ? clientTransport(order) : null,
|
||||
businessType: 'EXPORT', 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 ?? '',
|
||||
@@ -294,11 +295,11 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
|
||||
customsHsCode: item.customsHsCode ?? '', lengthCm: item.lengthCm ?? 0, widthCm: item.widthCm ?? 0,
|
||||
heightCm: item.heightCm ?? 0, unitsPerPackage: item.unitsPerPackage ?? 1,
|
||||
pieces: item.totalQuantity ?? item.packageCount, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg,
|
||||
volumeCbm: item.volumeCbm, containerType: '', containerCount: 0, remark: item.cargoRemark ?? '',
|
||||
volumeCbm: item.volumeCbm, remark: item.cargoRemark ?? '',
|
||||
packageType: item.packageType, hsCode: item.hsCode ?? '', declaredValue: item.declaredValue ?? 0,
|
||||
currency: item.currency ?? 'USD', dangerousGoods: item.dangerousGoods ?? false,
|
||||
netWeightKg: item.netWeightKg ?? 0, unitDeclaredValue: item.unitDeclaredValue ?? 0,
|
||||
material: item.material ?? '', usage: item.usage ?? '', model: item.model ?? '', brand: item.brand ?? '',
|
||||
material: item.material ?? '', usage: item.usage ?? item.productUsage ?? '', model: item.model ?? '', brand: item.brand ?? '',
|
||||
productLengthCm: item.productLengthCm ?? 0, productWidthCm: item.productWidthCm ?? 0,
|
||||
productHeightCm: item.productHeightCm ?? 0, shipmentId: item.shipmentId ?? order.shipmentId ?? '',
|
||||
referenceId: item.referenceId ?? order.referenceId ?? '',
|
||||
|
||||
@@ -39,8 +39,6 @@ export interface FirstLegCargoItem {
|
||||
productHeightCm: number
|
||||
shipmentId: string
|
||||
referenceId: string
|
||||
containerType: string
|
||||
containerCount: number
|
||||
remark: string
|
||||
packageType: string
|
||||
hsCode: string
|
||||
|
||||
@@ -52,7 +52,7 @@ function validDraft() {
|
||||
destinationType: 'FBA',
|
||||
amazonWarehouseCode: 'ONT8',
|
||||
shipmentId: 'FBA-001',
|
||||
items: [{ ...createEmptyCargoItem('cargo-1'), sku: 'SKU-001', upc: '012345678905', cargoNameCn: '家居用品', pieces: 100, grossWeightKg: 1200, volumeCbm: 3.5, containerType: '40HQ', containerCount: 1 }],
|
||||
items: [{ ...createEmptyCargoItem('cargo-1'), sku: 'SKU-001', upc: '012345678905', cargoNameCn: '家居用品', pieces: 100, grossWeightKg: 1200, volumeCbm: 3.5, shipmentId: 'FBA-001', referenceId: 'REF-001' }],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,13 +81,10 @@ test('validates the confirmed minimum submission fields', () => {
|
||||
const invalid = validDraft()
|
||||
invalid.receivingWarehouseId = ''
|
||||
invalid.items[0].grossWeightKg = 0
|
||||
invalid.items[0].containerType = ''
|
||||
invalid.items[0].containerCount = 0
|
||||
|
||||
const errors = validateFirstLegOrderDraft(invalid)
|
||||
assert.ok(errors.includes('请选择平台收货仓'))
|
||||
assert.ok(errors.includes('第 1 条货物毛重必须大于 0'))
|
||||
assert.ok(errors.includes('第 1 条货物需要填写箱型和柜量'))
|
||||
})
|
||||
|
||||
test('only requires sender for pickup and receiver for a private destination', () => {
|
||||
@@ -111,20 +108,22 @@ test('only requires sender for pickup and receiver for a private destination', (
|
||||
assert.ok(validateFirstLegOrderDraft(privateDraft).includes('请填写收件人姓名'))
|
||||
})
|
||||
|
||||
test('requires FBA reference, warehouse, shipment, SKU and UPC fields', () => {
|
||||
test('requires FBA warehouse and cargo-level shipment, reference, SKU and UPC fields', () => {
|
||||
const draft = validDraft()
|
||||
draft.referenceNo = ''
|
||||
draft.amazonWarehouseCode = ''
|
||||
draft.shipmentId = ''
|
||||
draft.items[0].sku = ''
|
||||
draft.items[0].upc = ''
|
||||
draft.items[0].shipmentId = ''
|
||||
draft.items[0].referenceId = ''
|
||||
|
||||
const errors = validateFirstLegOrderDraft(draft)
|
||||
assert.ok(errors.includes('FBA 订单请填写客户参考号'))
|
||||
assert.ok(errors.includes('FBA 订单请填写 Amazon 仓库编码'))
|
||||
assert.ok(errors.includes('FBA 订单请填写 Shipment ID'))
|
||||
assert.ok(errors.includes('第 1 条货物缺少客户 SKU'))
|
||||
assert.ok(errors.includes('第 1 条货物缺少 UPC'))
|
||||
assert.ok(errors.includes('第 1 条货物缺少 Shipment ID'))
|
||||
assert.ok(errors.includes('第 1 条货物缺少 Reference ID'))
|
||||
})
|
||||
|
||||
test('requires public destination ids while keeping private addresses detached from public warehouses', () => {
|
||||
@@ -141,15 +140,6 @@ test('requires public destination ids while keeping private addresses detached f
|
||||
assert.ok(validateFirstLegOrderDraft(privateDraft).includes('私人地址不能引用平台公共仓库'))
|
||||
})
|
||||
|
||||
test('does not require container details for non-FCL cargo', () => {
|
||||
const draft = validDraft()
|
||||
draft.transportMode = 'AIR'
|
||||
draft.items[0].containerType = ''
|
||||
draft.items[0].containerCount = 0
|
||||
|
||||
assert.deepEqual(validateFirstLegOrderDraft(draft), [])
|
||||
})
|
||||
|
||||
test('summarizes cargo values from line items', () => {
|
||||
const first = validDraft().items[0]
|
||||
const second = { ...first, id: 'cargo-2', pieces: 40, packageCount: 4, grossWeightKg: 300, volumeCbm: 1.25 }
|
||||
|
||||
@@ -78,8 +78,6 @@ export function createEmptyCargoItem(id = `cargo-${Date.now()}`): FirstLegCargoI
|
||||
productHeightCm: 0,
|
||||
shipmentId: '',
|
||||
referenceId: '',
|
||||
containerType: '',
|
||||
containerCount: 0,
|
||||
remark: '',
|
||||
packageType: 'CARTON',
|
||||
hsCode: '',
|
||||
@@ -132,9 +130,6 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) {
|
||||
if (!Number.isInteger(item.unitsPerPackage) || item.unitsPerPackage <= 0) errors.push(`${line}每箱数量必须为正整数`)
|
||||
if (item.grossWeightKg <= 0) errors.push(`${line}毛重必须大于 0`)
|
||||
if (item.volumeCbm <= 0) errors.push(`${line}体积必须大于 0`)
|
||||
if (draft.transportMode === 'FCL' && (!item.containerType || item.containerCount <= 0)) {
|
||||
errors.push(`${line}需要填写箱型和柜量`)
|
||||
}
|
||||
})
|
||||
|
||||
return errors
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
const allowedTypes = new Set(['image/jpeg', 'image/png'])
|
||||
const allowedExtensions = new Set(['jpg', 'jpeg', 'png'])
|
||||
const allowedImageTypes: Readonly<Record<string, ReadonlySet<string>>> = {
|
||||
'image/jpeg': new Set(['jpg', 'jpeg']),
|
||||
'image/png': new Set(['png']),
|
||||
}
|
||||
|
||||
export const PRODUCT_IMAGE_MAX_SIZE = 5 * 1024 * 1024
|
||||
|
||||
export function validateProductImageFile(file: Pick<File, 'name' | 'size' | 'type'>) {
|
||||
const extension = file.name.split('.').pop()?.toLowerCase() ?? ''
|
||||
if (!allowedTypes.has(file.type.toLowerCase()) || !allowedExtensions.has(extension)) {
|
||||
const allowedExtensions = allowedImageTypes[file.type.toLowerCase()]
|
||||
if (!allowedExtensions?.has(extension)) {
|
||||
return '产品图片仅支持 JPG、JPEG、PNG 格式'
|
||||
}
|
||||
if (file.size <= 0) return '产品图片内容为空'
|
||||
|
||||
@@ -215,8 +215,9 @@ test('distills order creation into the confirmed prototype sections with server
|
||||
assert.ok(source.indexOf('<h3>目的地信息</h3>') < source.indexOf('<h3>货物明细</h3>'))
|
||||
assert.match(source, /class="segmented-options"/)
|
||||
assert.match(source, /class="prototype-form-row prototype-form-row--tabs"/)
|
||||
assert.match(source, /class="cargo-primary-grid"/)
|
||||
assert.match(source, /title="更多申报信息"/)
|
||||
for (const heading of ['商品信息', '箱规、数量与重量', '报关与产品资料', '货件与备注']) {
|
||||
assert.match(source, new RegExp(`<h4>${heading}</h4>`))
|
||||
}
|
||||
assert.match(source, /参考运费/)
|
||||
assert.match(source, /仅供参考,最终以实际结算为准/)
|
||||
assert.doesNotMatch(source, /v-model="form\.(?:businessType|origin|destination|priority|tradeTerm)"/)
|
||||
@@ -268,7 +269,7 @@ test('shows field-level validation and concise guidance for required order input
|
||||
assert.match(source, /prop="consigneePartySnapshot\.address" required/)
|
||||
assert.match(source, /:prop="`items\.\$\{index\}\.cargoNameCn`" required/)
|
||||
assert.match(source, /:prop="`items\.\$\{index\}\.grossWeightKg`" required/)
|
||||
assert.match(source, /label="UPC".*required.*v-model="item\.upc"/)
|
||||
assert.match(source, /label="UPC NO\."[^>]*:required="form\.destinationType === 'FBA'"[^>]*>\s*<el-input v-model="item\.upc"/)
|
||||
assert.match(source, /placeholder="例如:蓝牙音箱"/)
|
||||
assert.match(source, /\.order-form :deep\(\.el-form-item__error\).*position: static/)
|
||||
})
|
||||
@@ -297,7 +298,12 @@ test('shows the full customer-safe order detail without platform-only controls',
|
||||
for (const heading of ['订单与运输', '收发货信息', '关键业务摘要', '履约快递信息', '货物明细']) {
|
||||
assert.match(source, new RegExp(`<h3>${heading}<\\/h3>`))
|
||||
}
|
||||
for (const field of ['贸易条款', '发货方式', '报关方式', '运输渠道', '收货要求', '目的仓类型', '海关查验', '提交时间', '完成时间', 'SKU / UPC', 'HS 编码', '申报价值', '危险品']) {
|
||||
for (const field of [
|
||||
'贸易条款', '发货方式', '报关方式', '运输渠道', '收货要求', '目的仓类型', '海关查验',
|
||||
'提交时间', '完成时间', 'SKU NO.', 'UPC NO.', '报关 HS CODE', '清关 HS CODE', '每箱数量',
|
||||
'总净重', '清关申报单价', '中英文材质', '中英文用途', '产品规格', 'Shipment ID',
|
||||
'Reference ID', '危险品',
|
||||
]) {
|
||||
assert.match(source, new RegExp(field))
|
||||
}
|
||||
assert.doesNotMatch(detailSource, /承运商|待平台指派/)
|
||||
|
||||
@@ -394,7 +394,10 @@ watch(isCreatePage, (isCreate, wasCreate) => {
|
||||
}, { immediate: true })
|
||||
|
||||
watch(detailOpen, (open) => {
|
||||
if (!open) resetInboundBillingState()
|
||||
if (!open) {
|
||||
resetInboundBillingState()
|
||||
if (detailOrder.value) releaseProductImagePreviews(detailOrder.value.items)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -942,7 +945,8 @@ function removeCargoItem(index: number) {
|
||||
ElMessage.warning('订单至少需要保留一条货物明细')
|
||||
return
|
||||
}
|
||||
form.items.splice(index, 1)
|
||||
const [removed] = form.items.splice(index, 1)
|
||||
if (removed) releaseProductImagePreviews([removed])
|
||||
resetEditorValidation()
|
||||
}
|
||||
|
||||
@@ -1083,7 +1087,7 @@ async function searchOrders(model: QueryModel) {
|
||||
<template #order="{ row }">
|
||||
<button class="order-number-link" type="button" @click="openDetail(row as FirstLegOrder)">{{ (row as FirstLegOrder).orderNo || '未提交草稿' }}</button>
|
||||
<span v-if="(row as FirstLegOrder).businessSummary.inboundCode" class="order-reference">入仓码 {{ (row as FirstLegOrder).businessSummary.inboundCode }}</span>
|
||||
<span class="order-reference">{{ (row as FirstLegOrder).referenceNo || '无客户参考号' }}</span>
|
||||
<span class="order-reference">{{ (row as FirstLegOrder).referenceNo || '无 Reference ID' }}</span>
|
||||
</template>
|
||||
<template #route="{ row }"><strong>{{ (row as FirstLegOrder).destination || '目的地待更新' }}</strong><span class="order-reference">{{ (row as FirstLegOrder).transportMode ? getFirstLegTransportModeLabel((row as FirstLegOrder).transportMode) : (row as FirstLegOrder).channelName || '渠道待更新' }}</span></template>
|
||||
<template #cargo="{ row }">{{ getOrderCargoSummary(row as FirstLegOrder) }}</template>
|
||||
@@ -1101,7 +1105,7 @@ async function searchOrders(model: QueryModel) {
|
||||
<article v-for="order in filteredOrders" :key="order.id" class="mobile-order-card">
|
||||
<header><button type="button" @click="openDetail(order)">{{ order.orderNo || '未提交草稿' }}</button><el-tag :type="getFirstLegStatusTone(order.status)" effect="light">{{ getFirstLegStatusLabel(order.status) }}</el-tag></header>
|
||||
<span v-if="order.businessSummary.inboundCode" class="order-reference">入仓码 {{ order.businessSummary.inboundCode }}</span>
|
||||
<span class="order-reference">{{ order.referenceNo || '无客户参考号' }}</span>
|
||||
<span class="order-reference">{{ order.referenceNo || '无 Reference ID' }}</span>
|
||||
<dl><div><dt>目的地</dt><dd>{{ order.destination || '待更新' }}</dd></div><div><dt>收货仓</dt><dd>{{ order.receivingWarehouseName || '待更新' }}</dd></div><div><dt>货物摘要</dt><dd>{{ getOrderCargoSummary(order) }}</dd></div><div><dt>运输渠道</dt><dd>{{ order.transportMode ? getFirstLegTransportModeLabel(order.transportMode) : order.channelName || '待更新' }}</dd></div></dl>
|
||||
<footer><span>更新于 {{ formatDateTime(order.updatedAt) }}</span><div><el-button link type="primary" @click="openDetail(order)"><Eye :size="15" />详情</el-button><el-button v-if="canEditOrder && canClientEditFirstLegOrder(order.status)" link type="primary" :loading="openingEditor" @click="openEdit(order)"><Pencil :size="15" />编辑</el-button></div></footer>
|
||||
</article>
|
||||
@@ -1243,13 +1247,6 @@ async function searchOrders(model: QueryModel) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="form.transportMode === 'FCL'" class="cargo-field-section">
|
||||
<h4>整柜信息</h4>
|
||||
<div class="cargo-form-grid">
|
||||
<el-form-item label="箱型" :prop="`items.${index}.containerType`" required :rules="requiredSelectRules('请选择箱型')"><el-select v-model="item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ']" :key="type" :label="type" :value="type" /></el-select></el-form-item>
|
||||
<el-form-item label="柜量" :prop="`items.${index}.containerCount`" required :rules="positiveNumberRules('柜量必须为大于 0 的整数', true)"><el-input-number v-model="item.containerCount" :min="1" controls-position="right" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
<aside class="price-estimate" aria-live="polite">
|
||||
<div class="price-estimate__heading"><div><strong>参考运费</strong><span>仅供参考,最终以实际结算为准</span></div><el-button v-if="priceEstimateError" link type="primary" @click="loadPriceEstimate">重新估价</el-button></div>
|
||||
@@ -1279,7 +1276,6 @@ async function searchOrders(model: QueryModel) {
|
||||
<h3>订单与运输</h3>
|
||||
<dl class="order-detail-grid">
|
||||
<div v-if="detailOrder.businessSummary.inboundCode"><dt>入仓码</dt><dd>{{ detailOrder.businessSummary.inboundCode }}</dd></div>
|
||||
<div><dt>客户参考号</dt><dd>{{ displayValue(detailOrder.referenceNo) }}</dd></div>
|
||||
<div><dt>创建时间</dt><dd>{{ formatDateTime(detailOrder.createdAt) }}</dd></div>
|
||||
<div><dt>运输方式</dt><dd>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }}</dd></div>
|
||||
<div><dt>贸易条款</dt><dd>{{ displayValue(detailOrder.tradeTerm) }}</dd></div>
|
||||
@@ -1291,7 +1287,6 @@ async function searchOrders(model: QueryModel) {
|
||||
<div><dt>目的仓类型</dt><dd>{{ destinationTypeLabels[detailOrder.destinationType] }}</dd></div>
|
||||
<div><dt>目的国家 / 仓库</dt><dd>{{ detailOrder.destinationCountryCode || '-' }} · {{ detailOrder.destinationWarehouseName || '-' }}</dd></div>
|
||||
<div v-if="detailOrder.destinationType === 'FBA'"><dt>Amazon 仓库代码</dt><dd>{{ displayValue(detailOrder.amazonWarehouseCode) }}</dd></div>
|
||||
<div v-if="detailOrder.destinationType === 'FBA'"><dt>Shipment ID</dt><dd>{{ displayValue(detailOrder.shipmentId) }}</dd></div>
|
||||
<div><dt>期望出运</dt><dd>{{ displayValue(detailOrder.expectedDepartureDate) }}</dd></div>
|
||||
<div><dt>海关查验</dt><dd>{{ detailOrder.customsInspection ? '需要' : '不需要' }}</dd></div>
|
||||
<div><dt>提交时间</dt><dd>{{ detailOrder.submittedAt ? formatDateTime(detailOrder.submittedAt) : '-' }}</dd></div>
|
||||
@@ -1317,7 +1312,30 @@ async function searchOrders(model: QueryModel) {
|
||||
<div v-if="detailOrder.expressCompanyName || detailOrder.trackingNumber" class="express-information"><PackageCheck :size="20" /><div><span>快递公司</span><strong>{{ detailOrder.expressCompanyName || '待平台补充' }}</strong></div><div><span>快递单号</span><strong>{{ detailOrder.trackingNumber || '待平台补充' }}</strong></div></div>
|
||||
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>平台尚未维护快递信息</strong><span>快递公司和快递单号由平台后台维护,客户端仅只读展示。</span></div></div>
|
||||
</section>
|
||||
<section class="detail-section"><h3>货物明细</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><div v-if="detailOrder.items.length" class="cargo-detail-list"><article v-for="(item, index) in detailOrder.items" :key="item.id"><header><span>货物 {{ index + 1 }}</span><strong>{{ item.cargoNameCn || '未命名货物' }}</strong><small>{{ item.cargoNameEn || '' }}</small></header><div v-if="item.productImageFileId" class="cargo-detail-image"><img v-if="item.productImagePreviewUrl" :src="item.productImagePreviewUrl" :alt="`${item.cargoNameCn || `货物 ${index + 1}`}产品图片`" /><span v-else-if="item.productImageUploading">图片加载中...</span><span v-else>{{ item.productImageError || '产品图片暂不可用' }}</span></div><dl><div><dt>SKU / UPC</dt><dd>{{ item.sku || '-' }} / {{ item.upc || '-' }}</dd></div><div><dt>HS 编码</dt><dd>{{ item.hsCode || '-' }}</dd></div><div><dt>包装 / 箱数</dt><dd>{{ packageTypeLabels[item.packageType] || item.packageType || '-' }} · {{ item.packageCount }} 箱</dd></div><div><dt>数量</dt><dd>{{ item.pieces }} 件</dd></div><div><dt>毛重</dt><dd>{{ item.grossWeightKg }} kg</dd></div><div><dt>体积</dt><dd>{{ item.volumeCbm }} m³</dd></div><div><dt>申报价值</dt><dd>{{ item.declaredValue ? `${item.currency} ${item.declaredValue.toLocaleString()}` : '-' }}</dd></div><div><dt>危险品</dt><dd>{{ item.dangerousGoods ? '是' : '否' }}</dd></div></dl><p v-if="item.remark">{{ item.remark }}</p></article></div><el-empty v-else description="暂无货物明细" :image-size="56" /></section>
|
||||
<section class="detail-section">
|
||||
<h3>货物明细</h3>
|
||||
<div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div>
|
||||
<div v-if="detailOrder.items.length" class="cargo-detail-list">
|
||||
<article v-for="(item, index) in detailOrder.items" :key="item.id">
|
||||
<header><span>货物 {{ index + 1 }}</span><strong>{{ item.cargoNameCn || '未命名货物' }}</strong><small>{{ item.cargoNameEn || '' }}</small></header>
|
||||
<div v-if="item.productImageFileId" class="cargo-detail-image"><img v-if="item.productImagePreviewUrl" :src="item.productImagePreviewUrl" :alt="`${item.cargoNameCn || `货物 ${index + 1}`}产品图片`" /><span v-else-if="item.productImageUploading">图片加载中...</span><span v-else>{{ item.productImageError || '产品图片暂不可用' }}</span></div>
|
||||
<dl>
|
||||
<div><dt>SKU NO.</dt><dd>{{ item.sku || '-' }}</dd></div><div><dt>UPC NO.</dt><dd>{{ item.upc || '-' }}</dd></div>
|
||||
<div><dt>报关 HS CODE</dt><dd>{{ item.hsCode || '-' }}</dd></div><div><dt>清关 HS CODE</dt><dd>{{ item.customsHsCode || '-' }}</dd></div>
|
||||
<div><dt>箱规(长×宽×高)</dt><dd>{{ item.lengthCm || '-' }} × {{ item.widthCm || '-' }} × {{ item.heightCm || '-' }} cm</dd></div>
|
||||
<div><dt>每箱数量</dt><dd>{{ item.unitsPerPackage }} 件</dd></div><div><dt>箱数</dt><dd>{{ item.packageCount }} 箱</dd></div><div><dt>总数量</dt><dd>{{ item.pieces }} 件</dd></div>
|
||||
<div><dt>总毛重</dt><dd>{{ item.grossWeightKg }} kg</dd></div><div><dt>总净重</dt><dd>{{ item.netWeightKg ? `${item.netWeightKg} kg` : '-' }}</dd></div><div><dt>总体积</dt><dd>{{ item.volumeCbm }} m³</dd></div>
|
||||
<div><dt>清关申报单价</dt><dd>{{ item.unitDeclaredValue ? `USD ${item.unitDeclaredValue.toLocaleString()}` : '-' }}</dd></div><div><dt>清关申报金额</dt><dd>{{ item.declaredValue ? `USD ${item.declaredValue.toLocaleString()}` : '-' }}</dd></div>
|
||||
<div><dt>中英文材质</dt><dd>{{ item.material || '-' }}</dd></div><div><dt>中英文用途</dt><dd>{{ item.usage || '-' }}</dd></div><div><dt>型号</dt><dd>{{ item.model || '-' }}</dd></div><div><dt>品牌</dt><dd>{{ item.brand || '-' }}</dd></div>
|
||||
<div><dt>产品规格(长×宽×高)</dt><dd>{{ item.productLengthCm || '-' }} × {{ item.productWidthCm || '-' }} × {{ item.productHeightCm || '-' }} cm</dd></div>
|
||||
<div><dt>Shipment ID</dt><dd>{{ item.shipmentId || '-' }}</dd></div><div><dt>Reference ID</dt><dd>{{ item.referenceId || '-' }}</dd></div>
|
||||
<div><dt>包装类型</dt><dd>{{ packageTypeLabels[item.packageType] || item.packageType || '-' }}</dd></div><div><dt>危险品</dt><dd>{{ item.dangerousGoods ? '是' : '否' }}</dd></div>
|
||||
</dl>
|
||||
<p v-if="item.remark">{{ item.remark }}</p>
|
||||
</article>
|
||||
</div>
|
||||
<el-empty v-else description="暂无货物明细" :image-size="56" />
|
||||
</section>
|
||||
<OrderProgressPanel :order="detailOrder" />
|
||||
<InboundBillingPanel
|
||||
:data="inboundBilling"
|
||||
@@ -1514,6 +1532,8 @@ async function searchOrders(model: QueryModel) {
|
||||
}
|
||||
@media (min-width: 721px) and (max-width: 1050px) {
|
||||
.first-leg-query-toolbar :deep(.query-toolbar__filters) { grid-template-columns: repeat(2, minmax(140px, 1fr)); }
|
||||
.cargo-form-grid, .cargo-form-grid--basic { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.product-image-field { grid-row: auto; }
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.first-leg-query-toolbar { flex-wrap: wrap; }
|
||||
|
||||
Reference in New Issue
Block a user