fix(client): allow single-dimension sailing quote input
This commit is contained in:
@@ -11,7 +11,10 @@ const warehouses = ref<MerchantWarehouse[]>([]); const destinationWarehouses = r
|
||||
const quoteDialogOpen = ref(false); const originWarehouseId = ref<string | number>(''); const destinationWarehouseId = ref<string | number>(''); const weight = ref<number | undefined>(); const volume = ref<number | undefined>(); 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 hasMeasureInput = computed(() => Number(weight.value || 0) > 0 || Number(volume.value || 0) > 0)
|
||||
const hasInput = computed(() => Boolean(originWarehouseId.value && destinationWarehouseId.value && hasMeasureInput.value))
|
||||
const orderWeightKg = computed(() => weight.value || (volume.value ? volume.value * 167 : 0))
|
||||
const orderVolumeCbm = computed(() => volume.value || (weight.value ? weight.value / 167 : 0))
|
||||
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))
|
||||
@@ -26,7 +29,7 @@ function formatUnitPrice(item: MerchantPriceEstimate) { const currency = item.cu
|
||||
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 } }
|
||||
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 || 0, actualVolumeCbm: volume.value || 0 }))); 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('请先查询该渠道价格')
|
||||
@@ -45,8 +48,8 @@ function placeOrder(channelCode: string, channelName: string) {
|
||||
amazonWarehouseCode: selectedDestinationWarehouse.value!.warehouseCode,
|
||||
destinationCountryCode: selectedDestinationWarehouse.value!.countryCode || 'US',
|
||||
pricingTaxMode: taxMode.value,
|
||||
grossWeightKg: String(weight.value),
|
||||
volumeCbm: String(volume.value),
|
||||
grossWeightKg: String(orderWeightKg.value),
|
||||
volumeCbm: String(orderVolumeCbm.value),
|
||||
cargo: JSON.stringify([{ sku: '', packageCount: 1 }]),
|
||||
},
|
||||
})
|
||||
@@ -55,7 +58,7 @@ watch(originWarehouseId, () => { void loadChannels() }); onMounted(() => { void
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pricing-page"><section class="pricing-inquiry"><div class="inquiry-copy"><span>SAILING PRICE INQUIRY</span><h2>船期价格查询</h2><p>输入货物重量和目的仓,快速获取匹配渠道、船期与预估运费</p></div><el-form class="pricing-form" label-position="top"><el-form-item label="起运仓"><el-select v-model="originWarehouseId" filterable :loading="loading" placeholder="选择起运仓"><el-option v-for="item in warehouses" :key="String(item.id)" :label="`${item.warehouseName} · ${item.warehouseCode}`" :value="item.id" /></el-select></el-form-item><el-form-item label="货物重量"><div class="unit-input"><el-input v-model.number="weight" type="number" min="0" placeholder="请输入重量" /><span>KG</span></div></el-form-item><el-form-item label="货物立方数"><div class="unit-input"><el-input v-model.number="volume" type="number" min="0" placeholder="请输入体积" /><span>CBM</span></div></el-form-item><el-form-item label="目的 FBA 仓"><el-select v-model="destinationWarehouseId" filterable :loading="loading" placeholder="输入或选择仓库代码"><el-option v-for="item in destinationWarehouses" :key="String(item.id)" :label="item.warehouseCode" :value="item.id" /></el-select></el-form-item><el-form-item label="报价方式"><el-radio-group v-model="taxMode"><el-radio-button value="TAX_INCLUDED" label="包税" /><el-radio-button value="TAX_EXCLUDED" label="不包税" /></el-radio-group></el-form-item><el-form-item class="query-item"><el-button type="primary" :loading="querying" @click="query"><Search :size="16" />立即查询</el-button></el-form-item></el-form>
|
||||
<div class="pricing-page"><section class="pricing-inquiry"><div class="inquiry-copy"><span>SAILING PRICE INQUIRY</span><h2>船期价格查询</h2><p>重量或体积填写一项即可,系统会按计价规则自动换算</p></div><el-form class="pricing-form" label-position="top"><el-form-item label="起运仓"><el-select v-model="originWarehouseId" filterable :loading="loading" placeholder="选择起运仓"><el-option v-for="item in warehouses" :key="String(item.id)" :label="`${item.warehouseName} · ${item.warehouseCode}`" :value="item.id" /></el-select></el-form-item><el-form-item label="货物重量"><div class="unit-input"><el-input v-model.number="weight" type="number" min="0" placeholder="可选,输入重量" /><span>KG</span></div></el-form-item><el-form-item label="货物立方数"><div class="unit-input"><el-input v-model.number="volume" type="number" min="0" placeholder="可选,输入体积" /><span>CBM</span></div></el-form-item><el-form-item label="目的 FBA 仓"><el-select v-model="destinationWarehouseId" filterable :loading="loading" placeholder="输入或选择仓库代码"><el-option v-for="item in destinationWarehouses" :key="String(item.id)" :label="item.warehouseCode" :value="item.id" /></el-select></el-form-item><el-form-item label="报价方式"><el-radio-group v-model="taxMode"><el-radio-button value="TAX_INCLUDED" label="包税" /><el-radio-button value="TAX_EXCLUDED" label="不包税" /></el-radio-group></el-form-item><el-form-item class="query-item"><el-button type="primary" :loading="querying" @click="query"><Search :size="16" />立即查询</el-button></el-form-item></el-form>
|
||||
<el-dialog v-model="quoteDialogOpen" class="quote-dialog" :show-close="false" width="min(1080px, calc(100vw - 48px))" top="6vh"><template #header><div class="quote-dialog-head"><div><h3>查询结果</h3><span>{{ resultSummary }}</span></div><button type="button" class="quote-close" aria-label="关闭" @click="quoteDialogOpen = false">×</button></div></template><div class="quote-grid"><article v-for="item in quoteCards" :key="item.quote.channelCode" class="quote-card" :class="recommendation(item)?.className"><div v-if="recommendation(item)" class="quote-ribbon">{{ recommendation(item)?.label }}</div><div class="quote-card-title"><strong>{{ item.channel.channelName }}</strong><span>{{ item.quote.pricingTaxMode === 'TAX_INCLUDED' ? '包税' : '不包税' }} / {{ item.quote.chargeUnit || 'CBM' }}</span></div><div class="quote-price"><b>{{ formatMoney(item.quote) }}</b><small>预估运费</small></div><div class="quote-meta"><div><span>单价</span><strong>{{ formatUnitPrice(item.quote) }}</strong></div><div><span>参考时效</span><strong>{{ item.channel.referenceTransit || '待更新' }}</strong></div><div><span>最晚进仓</span><strong>{{ item.channel.latestWarehouseEntry || '—' }}</strong></div><div><span>船期</span><strong>{{ item.channel.estimatedDepartureText || item.channel.estimatedDepartureDate || '待更新' }}</strong></div></div><el-button type="primary" plain class="quote-order" @click="placeOrder(item.quote.channelCode, item.channel.channelName)">下单</el-button></article></div></el-dialog></section>
|
||||
<section class="pricing-results"><div class="section-title"><div><h2>匹配渠道</h2><span>{{ channels.length }} 个渠道 · 参考价格以服务端估价为准</span></div><Calculator :size="20" /></div><el-skeleton v-if="loading || querying" :rows="4" animated /><el-alert v-else-if="errorMessage" :title="errorMessage" type="error" show-icon /><el-empty v-else-if="!channels.length" description="暂无可用船期价格" /><el-table v-else :data="channels" stripe><el-table-column label="渠道" min-width="160"><template #default="{ row }"><strong>{{ row.channelName }}</strong><small>{{ row.channelCode }}</small></template></el-table-column><el-table-column label="参考船期" min-width="150"><template #default="{ row }">{{ row.estimatedDepartureText || row.estimatedDepartureDate || '待更新' }}</template></el-table-column><el-table-column label="最晚进仓" min-width="130"><template #default="{ row }">{{ row.latestWarehouseEntry || '—' }}</template></el-table-column><el-table-column label="参考时效" width="120"><template #default="{ row }">{{ row.referenceTransit || '—' }}</template></el-table-column><el-table-column label="预估运费" width="150"><template #default="{ row }"><span v-if="quotes.find((quote) => quote.channelCode === row.channelCode)">{{ formatMoney(quotes.find((quote) => quote.channelCode === row.channelCode)!) }}</span><span v-else class="muted">查询后显示</span></template></el-table-column><el-table-column label="附加费说明" min-width="180" prop="surchargeDescription" /><el-table-column label="操作" width="100" fixed="right"><template #default="{ row }"><el-button link type="primary" :disabled="!quotes.some((quote) => quote.channelCode === row.channelCode)" @click="placeOrder(row.channelCode, row.channelName)">下单</el-button></template></el-table-column></el-table></section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user