diff --git a/src/modules/first-leg/views/SailingPricesView.vue b/src/modules/first-leg/views/SailingPricesView.vue index 60d2409..2a18bfa 100644 --- a/src/modules/first-leg/views/SailingPricesView.vue +++ b/src/modules/first-leg/views/SailingPricesView.vue @@ -7,13 +7,22 @@ import { estimateMerchantFirstLegPrice, getMerchantPricingChannels, getMerchantW const loading = ref(false); const querying = ref(false); const errorMessage = ref('') const router = useRouter() -const warehouses = ref([]); const channels = ref([]); const quotes = ref([]) -const originWarehouseId = ref(''); const destinationCode = ref(''); const weight = ref(); const volume = ref(); const taxMode = ref<'TAX_INCLUDED' | 'TAX_EXCLUDED'>('TAX_INCLUDED') -const hasInput = computed(() => Boolean(originWarehouseId.value && destinationCode.value.trim() && weight.value && volume.value)) -function formatMoney(item: MerchantPriceEstimate) { return `${item.currency} ${item.estimatedAmount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` } -async function loadBase() { loading.value = true; errorMessage.value = ''; try { const result = await getMerchantWarehouses({ warehouseType: 'DOMESTIC' }); warehouses.value = result.records; if (result.records[0]) { originWarehouseId.value = result.records[0].id; await loadChannels() } } catch (error) { errorMessage.value = error instanceof Error ? error.message : '船期价格加载失败' } finally { loading.value = false } } -async function loadChannels() { if (!originWarehouseId.value) return; channels.value = await getMerchantPricingChannels(originWarehouseId.value) } -async function query() { if (!hasInput.value) { ElMessage.warning('请填写起运仓、目的仓、重量和体积'); return }; querying.value = true; quotes.value = []; try { const results = await Promise.allSettled(channels.value.map((channel) => estimateMerchantFirstLegPrice({ originWarehouseId: originWarehouseId.value, channelCode: channel.channelCode, destinationCode: destinationCode.value.trim(), destinationType: 'FBA', pricingTaxMode: taxMode.value, actualWeightKg: weight.value!, actualVolumeCbm: volume.value! }))); quotes.value = results.flatMap((item) => item.status === 'fulfilled' ? [item.value] : []) } finally { querying.value = false } } +const warehouses = ref([]); const destinationWarehouses = ref([]); const channels = ref([]); const quotes = ref([]) +const quoteDialogOpen = ref(false); const originWarehouseId = ref(''); const destinationWarehouseId = ref(''); const weight = ref(); const volume = ref(); const taxMode = ref<'TAX_INCLUDED' | 'TAX_EXCLUDED'>('TAX_INCLUDED') +const selectedDestinationWarehouse = computed(() => destinationWarehouses.value.find((item) => String(item.id) === String(destinationWarehouseId.value))) +const destinationCode = computed(() => selectedDestinationWarehouse.value?.warehouseCode || '') +const hasInput = computed(() => Boolean(originWarehouseId.value && destinationWarehouseId.value && weight.value && volume.value)) +const quoteCards = computed(() => quotes.value.map((quote) => ({ quote, channel: channels.value.find((item) => item.channelCode === quote.channelCode) })).filter((item): item is { quote: MerchantPriceEstimate; channel: MerchantPricingChannel } => Boolean(item.channel)).sort((a, b) => a.quote.estimatedAmount - b.quote.estimatedAmount).slice(0, 4)) +const priceWinner = computed(() => quoteCards.value.reduce((winner, item) => !winner || item.quote.estimatedAmount < winner.quote.estimatedAmount ? item : winner, undefined as (typeof quoteCards.value)[number] | undefined)) +const timeWinner = computed(() => quoteCards.value.reduce((winner, item) => { const days = transitDays(item.channel.referenceTransit); if (days === null) return winner; return !winner || days < transitDays(winner.channel.referenceTransit)! ? item : winner }, undefined as (typeof quoteCards.value)[number] | undefined)) +const resultSummary = computed(() => `${volume.value || 0} CBM · ${destinationCode.value || '—'} · 匹配 ${quoteCards.value.length} 个渠道`) +function transitDays(value: string | null) { const match = value?.match(/\d+/); return match ? Number(match[0]) : null } +function formatMoney(item: MerchantPriceEstimate) { const currency = item.currency === 'CNY' ? '¥' : `${item.currency} `; return `${currency}${item.estimatedAmount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` } +function formatUnitPrice(item: MerchantPriceEstimate) { const currency = item.currency === 'CNY' ? '¥' : `${item.currency} `; return `${currency}${item.unitPrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}/${item.chargeUnit || 'CBM'}` } +function recommendation(item: { quote: MerchantPriceEstimate; channel: MerchantPricingChannel }) { if (priceWinner.value?.quote.channelCode === item.quote.channelCode) return { label: '价格优选', className: 'price' }; if (timeWinner.value?.quote.channelCode === item.quote.channelCode) return { label: '时间优选', className: 'time' }; return undefined } +async function loadBase() { loading.value = true; errorMessage.value = ''; try { const [originResult, destinationResult] = await Promise.all([getMerchantWarehouses({ warehouseType: 'DOMESTIC' }), getMerchantWarehouses({ warehouseType: 'FBA' })]); warehouses.value = originResult.records; destinationWarehouses.value = destinationResult.records; if (originResult.records[0]) originWarehouseId.value = originResult.records[0].id; if (destinationResult.records[0]) destinationWarehouseId.value = destinationResult.records[0].id; await loadChannels() } catch (error) { errorMessage.value = error instanceof Error ? error.message : '船期价格加载失败' } finally { loading.value = false } } +async function loadChannels() { if (!originWarehouseId.value) return; try { channels.value = await getMerchantPricingChannels(originWarehouseId.value) } catch (error) { channels.value = []; errorMessage.value = error instanceof Error ? error.message : '船期渠道加载失败' } } +async function query() { const destination = selectedDestinationWarehouse.value; if (!hasInput.value || !destination) { ElMessage.warning('请选择目的 FBA 仓,并填写重量和体积'); return }; if (!channels.value.length) { ElMessage.warning('暂无可查询的船期渠道'); return }; querying.value = true; quotes.value = []; try { const results = await Promise.allSettled(channels.value.map((channel) => estimateMerchantFirstLegPrice({ originWarehouseId: originWarehouseId.value, channelCode: channel.channelCode, destinationCode: destination.warehouseCode, destinationType: 'FBA', destinationWarehouseId: destination.id, destinationName: destination.warehouseName, pricingTaxMode: taxMode.value, actualWeightKg: weight.value!, actualVolumeCbm: volume.value! }))); quotes.value = results.flatMap((item) => item.status === 'fulfilled' ? [item.value] : []); if (!quotes.value.length) { ElMessage.warning('当前条件暂无匹配报价'); return }; quoteDialogOpen.value = true } finally { querying.value = false } } function placeOrder(channelCode: string, channelName: string) { if (!quotes.value.some((quote) => quote.channelCode === channelCode)) { ElMessage.warning('请先查询该渠道价格') @@ -27,9 +36,10 @@ function placeOrder(channelCode: string, channelName: string) { channelCode, channelName, destinationType: 'FBA', - destinationWarehouseName: destinationCode.value.trim().toUpperCase(), - amazonWarehouseCode: destinationCode.value.trim().toUpperCase(), - destinationCountryCode: 'US', + destinationWarehouseId: String(selectedDestinationWarehouse.value!.id), + destinationWarehouseName: selectedDestinationWarehouse.value!.warehouseName, + amazonWarehouseCode: selectedDestinationWarehouse.value!.warehouseCode, + destinationCountryCode: selectedDestinationWarehouse.value!.countryCode || 'US', pricingTaxMode: taxMode.value, grossWeightKg: String(weight.value), volumeCbm: String(volume.value), @@ -41,12 +51,15 @@ watch(originWarehouseId, () => { void loadChannels() }); onMounted(() => { void