This commit is contained in:
2026-08-07 19:23:46 +08:00
parent aa6c349fce
commit 0b2380689c
10 changed files with 77 additions and 52 deletions
@@ -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; }