|
|
|
@@ -11,6 +11,7 @@ import FirstLegOrderEditorShell from '@/modules/first-leg/components/FirstLegOrd
|
|
|
|
|
import InboundBillingPanel from '@/modules/first-leg/components/InboundBillingPanel.vue'
|
|
|
|
|
import OrderProgressPanel from '@/modules/first-leg/components/OrderProgressPanel.vue'
|
|
|
|
|
import { getMerchantInboundBilling } from '@/modules/first-leg/api/inboundBilling'
|
|
|
|
|
import { getMerchantCustomsByOrder, isMerchantCustomsUnavailable, type MerchantCustomsCaseDetail } from '@/modules/first-leg/api/customs'
|
|
|
|
|
import {
|
|
|
|
|
estimateMerchantFirstLegPrice,
|
|
|
|
|
downloadMerchantProductImage,
|
|
|
|
@@ -89,6 +90,9 @@ const inboundBillingLoading = ref(false)
|
|
|
|
|
const inboundBillingUnavailable = ref(false)
|
|
|
|
|
const inboundBillingUnavailableMessage = ref('入库及费用结果尚未发布')
|
|
|
|
|
const inboundBillingError = ref('')
|
|
|
|
|
const customsDetail = ref<MerchantCustomsCaseDetail | null>(null)
|
|
|
|
|
const customsLoading = ref(false)
|
|
|
|
|
const customsUnavailable = ref(false)
|
|
|
|
|
const supportingDataLoading = ref(false)
|
|
|
|
|
const supportingDataErrors = ref<string[]>([])
|
|
|
|
|
const destinationWarehousesLoading = ref(false)
|
|
|
|
@@ -300,7 +304,7 @@ const progressSummaryItems = computed(() => {
|
|
|
|
|
const summary = order.businessSummary
|
|
|
|
|
const formatMetric = (value: number | null | undefined, unit = '') => value == null ? '待更新' : `${value.toLocaleString()}${unit ? ` ${unit}` : ''}`
|
|
|
|
|
const preloadStatus = ({ PENDING: '待确认', CONFIRMING: '确认中', CONFIRMED: '已确认' } as Record<string, string>)[summary.preloadStatus] ?? summary.preloadStatus
|
|
|
|
|
const transportReference = [summary.containerNo, summary.cabinetNo, summary.vesselName, summary.voyageNo].filter(Boolean).join(' · ')
|
|
|
|
|
const transportReference = [summary.vesselName, summary.voyageNo].filter(Boolean).join(' · ')
|
|
|
|
|
const pickingProgress = summary.expectedPickCount == null
|
|
|
|
|
? '待更新'
|
|
|
|
|
: `${summary.pickedCount ?? 0} / ${summary.expectedPickCount}`
|
|
|
|
@@ -310,12 +314,18 @@ const progressSummaryItems = computed(() => {
|
|
|
|
|
{ label: '实测体积', value: formatMetric(summary.actualVolumeCbm ?? inbound?.actualVolumeCbm, 'm³') },
|
|
|
|
|
{ label: '预装清单', value: preloadStatus ? `${preloadStatus}${summary.preloadItemCount == null ? '' : ` · ${summary.preloadItemCount} 项`}` : '待更新' },
|
|
|
|
|
{ label: '拣货进度', value: pickingProgress },
|
|
|
|
|
{ label: '装柜资料', value: transportReference || summary.sealNo || '待更新' },
|
|
|
|
|
{ label: '作业证据', value: formatMetric(summary.evidenceCount, '项') },
|
|
|
|
|
{ label: '柜次进度', value: summary.cabinetNo || summary.containerNo || '待更新' },
|
|
|
|
|
{ label: '报关回执', value: customsDetail.value?.receipts.length ? `${customsDetail.value.receipts.length} 份回执` : customsUnavailable.value ? '暂无回执' : '查询中' },
|
|
|
|
|
{ label: '船期摘要', value: transportReference || '待更新' },
|
|
|
|
|
{ label: '计费重量', value: formatMetric(summary.chargeableWeightKg, 'kg') },
|
|
|
|
|
{ label: '派送信息', value: summary.deliveryTrackingNo || (summary.deliveryCompletedAt ? `完成于 ${formatDateTime(summary.deliveryCompletedAt)}` : '待更新') },
|
|
|
|
|
{ label: '后续履约', value: summary.deliveryTrackingNo || (summary.deliveryCompletedAt ? `海外回传于 ${formatDateTime(summary.deliveryCompletedAt)}` : '待海外同步') },
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
const cabinetProgressItems = computed(() => {
|
|
|
|
|
const summary = detailOrder.value?.businessSummary
|
|
|
|
|
if (!summary || (!summary.cabinetNo && !summary.containerNo)) return []
|
|
|
|
|
return [{ cabinetId: summary.cabinetNo || summary.containerNo, cabinetNo: summary.cabinetNo || summary.containerNo, status: summary.loadingStatus || '待更新', vesselName: summary.vesselName, voyageNo: summary.voyageNo, sailingAt: summary.sailingAt }]
|
|
|
|
|
})
|
|
|
|
|
const selectedWarehouse = computed(() => availableWarehouses.value.find((item) => item.id === form.receivingWarehouseId))
|
|
|
|
|
const selectedDestinationWarehouse = computed(() => store.warehouses.find((item) => item.id === form.destinationWarehouseId && item.warehouseType === form.destinationType))
|
|
|
|
|
const selectedPricingChannel = computed(() => channelOptions.value.find((item) => item.channelCode === form.channelCode))
|
|
|
|
@@ -396,6 +406,9 @@ watch(isCreatePage, (isCreate, wasCreate) => {
|
|
|
|
|
watch(detailOpen, (open) => {
|
|
|
|
|
if (!open) {
|
|
|
|
|
resetInboundBillingState()
|
|
|
|
|
customsDetail.value = null
|
|
|
|
|
customsLoading.value = false
|
|
|
|
|
customsUnavailable.value = false
|
|
|
|
|
if (detailOrder.value) releaseProductImagePreviews(detailOrder.value.items)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
@@ -838,6 +851,7 @@ async function openDetail(order: FirstLegOrder) {
|
|
|
|
|
detailLoading.value = true
|
|
|
|
|
detailError.value = ''
|
|
|
|
|
void loadInboundBilling(order)
|
|
|
|
|
void loadCustoms(order)
|
|
|
|
|
try {
|
|
|
|
|
const detailedOrder = await store.loadDetail(order.id)
|
|
|
|
|
await Promise.all(detailedOrder.items.map(ensureProductImagePreview))
|
|
|
|
@@ -901,6 +915,21 @@ async function loadInboundBilling(order: FirstLegOrder) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadCustoms(order: FirstLegOrder) {
|
|
|
|
|
customsDetail.value = null
|
|
|
|
|
customsUnavailable.value = false
|
|
|
|
|
if (order.status === 'DRAFT') return
|
|
|
|
|
customsLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
customsDetail.value = await getMerchantCustomsByOrder(order.id)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (isMerchantCustomsUnavailable(error)) customsUnavailable.value = true
|
|
|
|
|
else ElMessage.warning(error instanceof Error ? error.message : '报关回执加载失败')
|
|
|
|
|
} finally {
|
|
|
|
|
customsLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function retryInboundBilling() {
|
|
|
|
|
const order = detailId.value ? store.getById(detailId.value) : undefined
|
|
|
|
|
if (order) await loadInboundBilling(order)
|
|
|
|
@@ -1307,6 +1336,30 @@ async function searchOrders(model: QueryModel) {
|
|
|
|
|
<div v-for="item in progressSummaryItems" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div>
|
|
|
|
|
</dl>
|
|
|
|
|
</section>
|
|
|
|
|
<section v-if="detailOrder.status !== 'DRAFT'" class="detail-section">
|
|
|
|
|
<h3>柜次进度</h3>
|
|
|
|
|
<div v-if="cabinetProgressItems.length" class="cabinet-progress-list">
|
|
|
|
|
<article v-for="cabinet in cabinetProgressItems" :key="cabinet.cabinetId" class="cabinet-progress-card">
|
|
|
|
|
<header><strong>{{ cabinet.cabinetNo || '柜次待分配' }}</strong><el-tag size="small" effect="plain">{{ cabinet.status || '待更新' }}</el-tag></header>
|
|
|
|
|
<dl>
|
|
|
|
|
<div><dt>柜号</dt><dd>{{ cabinet.cabinetNo || '待分配' }}</dd></div>
|
|
|
|
|
<div><dt>船名 / 航次</dt><dd>{{ [cabinet.vesselName, cabinet.voyageNo].filter(Boolean).join(' · ') || '待更新' }}</dd></div>
|
|
|
|
|
<div><dt>预计船期</dt><dd>{{ cabinet.sailingAt ? formatDateTime(cabinet.sailingAt) : '待更新' }}</dd></div>
|
|
|
|
|
</dl>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
<el-empty v-else description="暂无柜次分配信息" :image-size="50" />
|
|
|
|
|
</section>
|
|
|
|
|
<section v-if="detailOrder.status !== 'DRAFT'" class="detail-section">
|
|
|
|
|
<h3>报关回执</h3>
|
|
|
|
|
<el-skeleton v-if="customsLoading" :rows="2" animated />
|
|
|
|
|
<el-empty v-else-if="customsUnavailable || !customsDetail?.receipts.length" description="暂无客户可见报关回执" :image-size="50" />
|
|
|
|
|
<div v-else class="customs-receipt-list">
|
|
|
|
|
<article v-for="receipt in customsDetail.receipts" :key="receipt.id">
|
|
|
|
|
<strong>{{ receipt.receiptType }}</strong><span>版本 {{ receipt.receiptVersion ?? '-' }}</span><span>{{ receipt.uploadedAt ? formatDateTime(receipt.uploadedAt) : '时间待更新' }}</span>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="detail-section">
|
|
|
|
|
<h3>履约快递信息</h3>
|
|
|
|
|
<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>
|
|
|
|
@@ -1488,6 +1541,16 @@ async function searchOrders(model: QueryModel) {
|
|
|
|
|
.fulfillment-summary > div:nth-last-child(-n + 3) { border-bottom: 0; }
|
|
|
|
|
.fulfillment-summary dt { color: var(--muted); font-size: 10px; }
|
|
|
|
|
.fulfillment-summary dd { margin: 5px 0 0; overflow-wrap: anywhere; color: var(--navy); font-size: 12px; font-weight: 650; }
|
|
|
|
|
.cabinet-progress-list { display: grid; gap: 10px; }
|
|
|
|
|
.cabinet-progress-card { padding: 14px 15px; border: 1px solid var(--border); background: #fbfcfc; }
|
|
|
|
|
.cabinet-progress-card header { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
|
|
|
.cabinet-progress-card header strong { color: var(--navy); font-size: 13px; }
|
|
|
|
|
.cabinet-progress-card dl { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin: 13px 0 0; }
|
|
|
|
|
.cabinet-progress-card dt { color: var(--muted); font-size: 10px; }
|
|
|
|
|
.cabinet-progress-card dd { margin: 4px 0 0; overflow-wrap: anywhere; color: var(--navy); font-size: 11px; font-weight: 600; }
|
|
|
|
|
.customs-receipt-list { display: grid; gap: 8px; }
|
|
|
|
|
.customs-receipt-list article { display: flex; flex-wrap: wrap; gap: 10px; padding: 11px 13px; border: 1px solid var(--border); color: var(--muted); font-size: 11px; }
|
|
|
|
|
.customs-receipt-list strong { color: var(--navy); }
|
|
|
|
|
.express-information { display: grid; grid-template-columns: auto repeat(2, minmax(0, 1fr)); align-items: center; gap: 16px; padding: 15px 16px; background: #f2f7f6; color: var(--primary); }
|
|
|
|
|
.express-information > div { display: flex; min-width: 0; flex-direction: column; gap: 4px; }
|
|
|
|
|
.express-information span { color: var(--muted); font-size: 11px; }
|
|
|
|
@@ -1592,6 +1655,7 @@ async function searchOrders(model: QueryModel) {
|
|
|
|
|
.cargo-detail-list header { grid-template-columns: 1fr; gap: 4px; }
|
|
|
|
|
.cargo-detail-list header small { text-align: left; }
|
|
|
|
|
.cargo-detail-list dl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
|
|
|
.cabinet-progress-card dl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
|
|
|
:global(.first-leg-editor), :global(.first-leg-detail) { width: 100% !important; }
|
|
|
|
|
}
|
|
|
|
|
@media (max-width: 360px) {
|
|
|
|
|