@@ -2,9 +2,11 @@
import { computed , onMounted , ref , watch } from 'vue'
import { computed , onMounted , ref , watch } from 'vue'
import { Calculator , RefreshCcw , Search } from '@lucide/vue'
import { Calculator , RefreshCcw , Search } from '@lucide/vue'
import { ElMessage } from 'element-plus'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import { estimateMerchantFirstLegPrice , getMerchantPricingChannels , getMerchantWarehouses , type MerchantPriceEstimate , type MerchantPricingChannel , type MerchantWarehouse } from '../api/firstLeg'
import { estimateMerchantFirstLegPrice , getMerchantPricingChannels , getMerchantWarehouses , type MerchantPriceEstimate , type MerchantPricingChannel , type MerchantWarehouse } from '../api/firstLeg'
const loading = ref ( false ) ; const querying = ref ( false ) ; const errorMessage = ref ( '' )
const loading = ref ( false ) ; const querying = ref ( false ) ; const errorMessage = ref ( '' )
const router = useRouter ( )
const warehouses = ref < MerchantWarehouse [ ] > ( [ ] ) ; const channels = ref < MerchantPricingChannel [ ] > ( [ ] ) ; const quotes = ref < MerchantPriceEstimate [ ] > ( [ ] )
const warehouses = ref < MerchantWarehouse [ ] > ( [ ] ) ; const channels = ref < MerchantPricingChannel [ ] > ( [ ] ) ; const quotes = ref < MerchantPriceEstimate [ ] > ( [ ] )
const originWarehouseId = ref < string | number > ( '' ) ; const destinationCode = ref ( '' ) ; const weight = ref < number | undefined > ( ) ; const volume = ref < number | undefined > ( ) ; const taxMode = ref < 'TAX_INCLUDED' | 'TAX_EXCLUDED' > ( 'TAX_INCLUDED' )
const originWarehouseId = ref < string | number > ( '' ) ; const destinationCode = ref ( '' ) ; const weight = ref < number | undefined > ( ) ; const volume = ref < number | undefined > ( ) ; const taxMode = ref < 'TAX_INCLUDED' | 'TAX_EXCLUDED' > ( 'TAX_INCLUDED' )
const hasInput = computed ( ( ) => Boolean ( originWarehouseId . value && destinationCode . value . trim ( ) && weight . value && volume . value ) )
const hasInput = computed ( ( ) => Boolean ( originWarehouseId . value && destinationCode . value . trim ( ) && weight . value && volume . value ) )
@@ -12,13 +14,36 @@ function formatMoney(item: MerchantPriceEstimate) { return `${item.currency} ${i
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 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 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 } }
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 } }
function placeOrder ( channelCode : string , channelName : string ) {
if ( ! quotes . value . some ( ( quote ) => quote . channelCode === channelCode ) ) {
ElMessage . warning ( '请先查询该渠道价格' )
return
}
router . push ( {
name : 'first-leg-order-create' ,
query : {
quick : '1' ,
receivingWarehouseId : String ( originWarehouseId . value ) ,
channelCode ,
channelName ,
destinationType : 'FBA' ,
destinationWarehouseName : destinationCode . value . trim ( ) . toUpperCase ( ) ,
amazonWarehouseCode : destinationCode . value . trim ( ) . toUpperCase ( ) ,
destinationCountryCode : 'US' ,
pricingTaxMode : taxMode . value ,
grossWeightKg : String ( weight . value ) ,
volumeCbm : String ( volume . value ) ,
cargo : JSON . stringify ( [ { sku : '' , packageCount : 1 } ] ) ,
} ,
} )
}
watch ( originWarehouseId , ( ) => { void loadChannels ( ) } ) ; onMounted ( ( ) => { void loadBase ( ) } )
watch ( originWarehouseId , ( ) => { void loadChannels ( ) } ) ; onMounted ( ( ) => { void loadBase ( ) } )
< / script >
< / script >
< template >
< template >
< div class = "pricing-page" > < section class = "pricing-hero" > < div > < span class = "eyebrow" > SAILING PRICE INQUIRY < / span > < h1 > 船期价格查询 < / h1 > < p > 输入货物重量和目的仓 , 获取匹配渠道 、 参考船期与预估运费 。 < / p > < / div > < el-button :loading = "loading" @click ="loadBase" > < RefreshCcw :size = "15" / > 刷新 < / el-button > < / section >
< div class = "pricing-page" > < section class = "pricing-hero" > < div > < span class = "eyebrow" > SAILING PRICE INQUIRY < / span > < h1 > 船期价格查询 < / h1 > < p > 输入货物重量和目的仓 , 获取匹配渠道 、 参考船期与预估运费 。 < / p > < / div > < el-button :loading = "loading" @click ="loadBase" > < RefreshCcw :size = "15" / > 刷新 < / el-button > < / section >
< section class = "pricing-form" > < el-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="货物重量"><el-input v-model.number="weight" type="number" placeholder="请输入重量"><template #append>KG</template></el-input></el-form-item><el-form-item label="货物立方数"><el-input v-model.number="volume" type="number" placeholder="请输入体积"><template #append>CBM</template></el-input></el-form-item><el-form-item label="目的仓"><el-input v-model="destinationCode" placeholder="如 ABE4" /></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><el-button type="primary" :loading="querying" @click="query"><Search :size="16" / > 立即查询 < / el-button > < / el-form-item > < / el-form > < / section >
< section class = "pricing-form" > < el-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="货物重量"><el-input v-model.number="weight" type="number" placeholder="请输入重量"><template #append>KG</template></el-input></el-form-item><el-form-item label="货物立方数"><el-input v-model.number="volume" type="number" placeholder="请输入体积"><template #append>CBM</template></el-input></el-form-item><el-form-item label="目的仓"><el-input v-model="destinationCode" placeholder="如 ABE4" /></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><el-button type="primary" :loading="querying" @click="query"><Search :size="16" / > 立即查询 < / el-button > < / el-form-item > < / el-form > < / 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 > < / 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 >
< / div >
< / template >
< / template >