fix(client): pass quote charge mode
This commit is contained in:
@@ -441,6 +441,7 @@ export interface MerchantPriceEstimateRequest {
|
||||
destinationWarehouseId?: number | string
|
||||
destinationName?: string
|
||||
pricingTaxMode: ApiPricingTaxMode
|
||||
chargeMode?: 'KG' | 'CBM'
|
||||
actualWeightKg: number
|
||||
actualVolumeCbm: number
|
||||
}
|
||||
|
||||
@@ -977,6 +977,7 @@ function createEstimateRequest(): MerchantPriceEstimateRequest | null {
|
||||
destinationWarehouseId: form.destinationType === 'OVERSEAS' ? form.destinationWarehouseId : undefined,
|
||||
destinationName: form.destinationWarehouseName || undefined,
|
||||
pricingTaxMode: form.pricingTaxMode,
|
||||
chargeMode: form.pricingTaxMode === 'TAX_INCLUDED' ? 'KG' : 'CBM',
|
||||
actualWeightKg: summary.grossWeightKg,
|
||||
actualVolumeCbm: summary.volumeCbm,
|
||||
}
|
||||
|
||||
@@ -17,9 +17,18 @@ const hasInput = computed(() => Boolean(originWarehouseId.value && destinationWa
|
||||
// every channel on the same calculation path when one field is omitted.
|
||||
const normalizedWeightKg = computed(() => weight.value || (volume.value ? volume.value * 167 : 0))
|
||||
const normalizedVolumeCbm = 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))
|
||||
type QuoteCard = { quote: MerchantPriceEstimate; channel: MerchantPricingChannel }
|
||||
const allQuoteCards = computed<QuoteCard[]>(() => quotes.value.map((quote) => ({ quote, channel: channels.value.find((item) => item.channelCode === quote.channelCode) })).filter((item): item is QuoteCard => Boolean(item.channel)))
|
||||
const priceWinner = computed<QuoteCard | undefined>(() => allQuoteCards.value.reduce<QuoteCard | undefined>((winner, item) => !winner || item.quote.estimatedAmount < winner.quote.estimatedAmount ? item : winner, undefined))
|
||||
const timeWinner = computed<QuoteCard | undefined>(() => allQuoteCards.value.reduce<QuoteCard | undefined>((winner, item) => { const days = transitDays(item.channel.referenceTransit); if (days === null) return winner; return !winner || days < transitDays(winner.channel.referenceTransit)! ? item : winner }, undefined))
|
||||
const quoteCards = computed(() => {
|
||||
const prioritized: QuoteCard[] = []
|
||||
if (timeWinner.value) prioritized.push(timeWinner.value)
|
||||
if (priceWinner.value && priceWinner.value.quote.channelCode !== timeWinner.value?.quote.channelCode) prioritized.push(priceWinner.value)
|
||||
const prioritizedCodes = new Set(prioritized.map((item) => item.quote.channelCode))
|
||||
const remaining = [...allQuoteCards.value].filter((item) => !prioritizedCodes.has(item.quote.channelCode)).sort((a, b) => a.quote.estimatedAmount - b.quote.estimatedAmount)
|
||||
return [...prioritized, ...remaining].slice(0, 4)
|
||||
})
|
||||
const resultSummary = computed(() => {
|
||||
const quoteUnit = (volume.value || 0) >= (weight.value || 0) / 167 ? 'CBM' : 'KG'
|
||||
const quoteQuantity = quoteUnit === 'CBM' ? volume.value : weight.value
|
||||
@@ -28,10 +37,10 @@ const resultSummary = computed(() => {
|
||||
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 }
|
||||
function recommendation(item: QuoteCard) { if (timeWinner.value?.quote.channelCode === item.quote.channelCode) return { label: '时间优选', className: 'time' }; if (priceWinner.value?.quote.channelCode === item.quote.channelCode) return { label: '价格优选', className: 'price' }; 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: normalizedWeightKg.value, actualVolumeCbm: normalizedVolumeCbm.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, chargeMode: taxMode.value === 'TAX_INCLUDED' ? 'KG' : 'CBM', actualWeightKg: normalizedWeightKg.value, actualVolumeCbm: normalizedVolumeCbm.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('请先查询该渠道价格')
|
||||
@@ -68,7 +77,7 @@ watch(originWarehouseId, () => { void loadChannels() }); onMounted(() => { void
|
||||
|
||||
<style scoped>
|
||||
.pricing-page { display:flex; flex-direction:column; gap:16px; }.pricing-heading { display:flex; justify-content:space-between; align-items:flex-start; padding:0 2px; }.eyebrow { color:#5b82c9; font-size:11px; font-weight:750; letter-spacing:.14em; }.pricing-heading h1 { margin:6px 0 5px; color:#1f2f46; font-size:26px; line-height:1.25; letter-spacing:-.025em; }.pricing-heading p { margin:0; color:#7d8da2; font-size:13px; }.refresh-button { margin-top:3px; border-color:#d9e3ef; border-radius:9px; background:#fff; color:#61738b; box-shadow:0 3px 8px rgba(26,55,91,.04); }.pricing-inquiry { position:relative; overflow:hidden; padding:24px; border:1px solid rgba(99,135,255,.36); border-radius:18px; background:linear-gradient(112deg,#152754 0%,#203d8f 42%,#315bd9 75%,#4c70f5 100%); box-shadow:0 14px 36px rgba(31,61,140,.2); }.pricing-inquiry::before { content:''; position:absolute; right:-75px; top:-165px; width:410px; height:410px; border:48px solid rgba(255,255,255,.07); border-radius:50%; background:radial-gradient(circle,rgba(255,255,255,.07),transparent 64%); pointer-events:none; }.inquiry-copy { position:relative; z-index:1; margin-bottom:19px; }.inquiry-copy > span { color:rgba(255,255,255,.7); font-size:10px; font-weight:850; letter-spacing:.18em; }.inquiry-copy h2 { margin:5px 0 4px; color:#fff; font-size:22px; font-weight:850; }.inquiry-copy p { margin:0; color:rgba(255,255,255,.7); font-size:13px; }.pricing-form { position:relative; z-index:1; display:grid; grid-template-columns:minmax(130px,.9fr) minmax(120px,.75fr) minmax(120px,.75fr) minmax(170px,1.1fr) minmax(125px,.8fr) 128px; align-items:end; gap:10px; }.pricing-form :deep(.el-form-item) { margin-bottom:0; }.pricing-form :deep(.el-form-item__label) { height:auto; margin-bottom:8px; color:rgba(255,255,255,.82); font-size:12px; font-weight:750; line-height:1.2; }.pricing-form :deep(.el-input__wrapper), .pricing-form :deep(.el-select__wrapper) { min-height:48px; border:1px solid #d4e0ed; border-radius:11px; background:#fff; box-shadow:0 4px 13px rgba(45,72,105,.04); }.pricing-form :deep(.el-input__inner), .pricing-form :deep(.el-select__selected-item) { color:#24354d; font-size:14px; font-weight:600; }.unit-input { display:flex; min-height:48px; overflow:hidden; border:1px solid #d4e0ed; border-radius:11px; background:#fff; }.unit-input :deep(.el-input__wrapper) { min-height:46px; border:0; box-shadow:none; }.unit-input span { display:flex; align-items:center; padding:0 13px 0 4px; color:#7690ad; font-size:12px; font-weight:750; }.pricing-form :deep(.el-radio-group) { display:flex; min-height:48px; align-items:center; padding:0 4px; border-radius:11px; background:#fff; }.pricing-form :deep(.el-radio-button__inner) { height:40px; padding:12px 12px; border:0; border-radius:8px; color:#61738b; box-shadow:none; }.pricing-form :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { border-color:#3d70e8; background:#3d70e8; color:#fff; box-shadow:0 5px 12px rgba(37,99,235,.2); }.query-item :deep(.el-form-item__content) { display:block; }.query-item :deep(.el-button) { width:100%; min-height:48px; border:0; border-radius:11px; background:linear-gradient(135deg,#ffca4b,#ffb82e); color:#48350b; box-shadow:0 9px 20px rgba(255,185,46,.25); font-size:14px; font-weight:850; }.query-item :deep(.el-button:hover) { background:linear-gradient(135deg,#ffd363,#ffc13e); color:#48350b; transform:translateY(-1px); }.pricing-results { padding:4px; overflow:hidden; border:1px solid #dce4ef; border-radius:16px; background:#fff; box-shadow:0 5px 16px rgba(31,56,88,.045); }.section-title { display:flex; align-items:center; justify-content:space-between; padding:17px 18px 13px; }.section-title h2 { margin:0 0 4px; color:#20324a; font-size:16px; }.section-title span { color:#7d8da2; font-size:12px; }.section-title svg { color:#3d70e8; }.pricing-results small { display:block; margin-top:3px; color:#7d8da2; font-size:11px; }.pricing-results :deep(.el-table) { --el-table-header-bg-color:#f4f7fb; --el-table-row-hover-bg-color:#eaf2ff; --el-table-border-color:#edf1f6; color:#53647a; }.pricing-results :deep(.el-table th.el-table__cell) { padding:13px 16px; color:#607089; font-size:12px; font-weight:750; }.pricing-results :deep(.el-table td.el-table__cell) { height:58px; padding:13px 16px; font-size:13px; }.pricing-results :deep(.el-table__row:nth-child(even)) { background:#f8fbff; }.pricing-results :deep(.el-button--link) { font-weight:750; }.muted { color:#94a0af; }
|
||||
.quote-dialog { --el-dialog-bg-color:transparent; }.quote-dialog :deep(.el-dialog) { overflow:hidden; border-radius:18px; background:linear-gradient(125deg,#182e69 0%,#274cbd 55%,#416bf1 100%); box-shadow:0 18px 52px rgba(11,31,89,.3); }.quote-dialog :deep(.el-dialog__header) { margin:0; padding:0; }.quote-dialog :deep(.el-dialog__body) { padding:0 14px 16px; }.quote-dialog-head { display:flex; align-items:flex-start; justify-content:space-between; padding:16px 18px 12px; color:#fff; }.quote-dialog-head h3 { margin:0 0 2px; font-size:19px; }.quote-dialog-head span { color:rgba(255,255,255,.72); font-size:12px; }.quote-close { width:36px; height:36px; border:1px solid #cfddf2; border-radius:10px; background:#fff; color:#8392a8; font-size:26px; line-height:29px; cursor:pointer; }.quote-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; }.quote-card { position:relative; min-height:240px; overflow:hidden; padding:18px 17px 14px; border:1px solid #d5deee; border-radius:15px; background:#f8f9fd; color:#263a55; box-shadow:0 6px 16px rgba(22,48,117,.1); }.quote-card.price { border:2px solid #ffae3c; }.quote-card.time { border:2px solid #27c28c; }.quote-ribbon { position:absolute; right:-43px; top:13px; width:130px; padding:5px 0; transform:rotate(43deg); background:#ff8d14; color:#fff; text-align:center; font-size:10px; font-weight:850; }.quote-card.time .quote-ribbon { background:#0db886; }.quote-card-title { display:flex; align-items:flex-start; justify-content:space-between; gap:6px; }.quote-card-title strong { font-size:16px; line-height:1.25; }.quote-card-title span { flex:none; padding:5px 7px; border-radius:15px; background:#edf5ff; color:#367ae0; font-size:10px; font-weight:750; }.quote-price { display:flex; align-items:baseline; gap:6px; margin:17px 0 15px; }.quote-price b { color:#ed5a2a; font-size:26px; line-height:1; }.quote-price small { color:#96a4b8; font-size:12px; }.quote-meta { display:grid; grid-template-columns:1fr 1fr; gap:11px 10px; }.quote-meta span { display:block; margin-bottom:3px; color:#93a0b2; font-size:10px; }.quote-meta strong { display:block; color:#344961; font-size:12px; line-height:1.35; }.quote-order { width:100%; min-height:32px; margin-top:14px; border-radius:7px; font-size:12px; }.quote-card:not(.price):not(.time) .quote-order { background:#fff; }
|
||||
.quote-dialog { --el-dialog-bg-color:transparent; }.quote-dialog :deep(.el-dialog) { overflow:hidden; border-radius:18px; background:linear-gradient(125deg,#182e69 0%,#274cbd 55%,#416bf1 100%); box-shadow:0 18px 52px rgba(11,31,89,.3); }.quote-dialog :deep(.el-dialog__header) { margin:0; padding:0; }.quote-dialog :deep(.el-dialog__body) { padding:0 14px 16px; }.quote-dialog-head { display:flex; align-items:flex-start; justify-content:space-between; padding:16px 18px 12px; color:#fff; }.quote-dialog-head h3 { margin:0 0 2px; font-size:19px; }.quote-dialog-head span { color:rgba(255,255,255,.72); font-size:12px; }.quote-close { width:36px; height:36px; border:1px solid #cfddf2; border-radius:10px; background:#fff; color:#8392a8; font-size:26px; line-height:29px; cursor:pointer; }.quote-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; align-items:stretch; }.quote-card { position:relative; display:flex; height:100%; min-height:240px; box-sizing:border-box; flex-direction:column; overflow:hidden; padding:18px 17px 14px; border:1px solid #d5deee; border-radius:15px; background:#f8f9fd; color:#263a55; box-shadow:0 6px 16px rgba(22,48,117,.1); }.quote-card.price { border:2px solid #ffae3c; }.quote-card.time { border:2px solid #27c28c; }.quote-ribbon { position:absolute; right:-43px; top:13px; width:130px; padding:5px 0; transform:rotate(43deg); background:#ff8d14; color:#fff; text-align:center; font-size:10px; font-weight:850; }.quote-card.time .quote-ribbon { background:#0db886; }.quote-card-title { display:flex; min-width:0; align-items:flex-start; justify-content:space-between; gap:6px; }.quote-card.price .quote-card-title, .quote-card.time .quote-card-title { padding-right:38px; }.quote-card-title strong { min-width:0; overflow-wrap:anywhere; font-size:16px; line-height:1.25; word-break:break-word; }.quote-card-title span { flex:none; padding:5px 7px; border-radius:15px; background:#edf5ff; color:#367ae0; font-size:10px; font-weight:750; white-space:nowrap; }.quote-price { display:flex; flex-shrink:0; align-items:baseline; gap:6px; margin:17px 0 15px; }.quote-price b { color:#ed5a2a; font-size:26px; line-height:1; white-space:nowrap; }.quote-price small { flex-shrink:0; color:#96a4b8; font-size:12px; }.quote-meta { display:grid; min-width:0; flex:1; grid-template-columns:minmax(0,1fr) minmax(0,1fr); align-content:start; gap:11px 10px; }.quote-meta > div { min-width:0; }.quote-meta span { display:block; margin-bottom:3px; color:#93a0b2; font-size:10px; }.quote-meta strong { display:block; min-width:0; overflow-wrap:anywhere; color:#344961; font-size:12px; line-height:1.35; word-break:break-word; }.quote-order { width:100%; min-height:32px; flex-shrink:0; margin-top:16px; border-radius:7px; font-size:12px; }.quote-card:not(.price):not(.time) .quote-order { background:#fff; }
|
||||
@media (max-width:1200px) { .pricing-form { grid-template-columns:repeat(3,minmax(0,1fr)); }.quote-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
|
||||
@media (max-width:720px) { .quote-grid { grid-template-columns:1fr; }.pricing-form { grid-template-columns:1fr; } }
|
||||
:global(.quote-dialog.el-dialog) { overflow:hidden; border-radius:18px; background:linear-gradient(125deg,#182e69 0%,#274cbd 55%,#416bf1 100%); box-shadow:0 18px 52px rgba(11,31,89,.3); }
|
||||
|
||||
Reference in New Issue
Block a user