diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index 348de66..08792ed 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -429,6 +429,12 @@ function quickOrderDraft() { } catch { // Keep the standard empty row when quick-order data is malformed. } + const pricingTaxMode = String(route.query.pricingTaxMode ?? '') + if (pricingTaxMode === 'TAX_INCLUDED' || pricingTaxMode === 'TAX_EXCLUDED') draft.pricingTaxMode = pricingTaxMode + const grossWeightKg = Number(route.query.grossWeightKg) + const volumeCbm = Number(route.query.volumeCbm) + if (Number.isFinite(grossWeightKg) && grossWeightKg > 0) draft.items[0].grossWeightKg = grossWeightKg + if (Number.isFinite(volumeCbm) && volumeCbm > 0) draft.items[0].volumeCbm = volumeCbm return draft } diff --git a/src/modules/first-leg/views/SailingPricesView.vue b/src/modules/first-leg/views/SailingPricesView.vue index b40e8a3..ee76c31 100644 --- a/src/modules/first-leg/views/SailingPricesView.vue +++ b/src/modules/first-leg/views/SailingPricesView.vue @@ -2,9 +2,11 @@ import { computed, onMounted, ref, watch } from 'vue' import { Calculator, RefreshCcw, Search } from '@lucide/vue' import { ElMessage } from 'element-plus' +import { useRouter } from 'vue-router' 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 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)) @@ -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 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 } } +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() }) diff --git a/src/navigation/clientNavigation.test.mjs b/src/navigation/clientNavigation.test.mjs index 5da4aa3..494f794 100644 --- a/src/navigation/clientNavigation.test.mjs +++ b/src/navigation/clientNavigation.test.mjs @@ -42,7 +42,7 @@ test('keeps account and notification routes as fixed-header utilities', () => { ) }) -test('organises customer-relevant business pages under five task-oriented sidebar entries', () => { +test('keeps standalone business pages at the client sidebar root', () => { assert.deepEqual( clientNavigation.filter((node) => !node.hidden).map((node) => ({ title: node.title, @@ -52,8 +52,10 @@ test('organises customer-relevant business pages under five task-oriented sideba { title: '首页', children: undefined }, { title: '订单中心', - children: ['头程订单', '报关资料', '船期价格', '海外采购', '美国本土卡', '美本土散板'], + children: ['头程订单', '海外采购', '美国本土卡', '美本土散板'], }, + { title: '报关资料', children: undefined }, + { title: '船期价格', children: undefined }, { title: '海外仓', children: ['贴标申请', '仓储转运', '货物废弃', '退货换标'], @@ -86,11 +88,11 @@ test('flattens visible leaves with unique paths', () => { assert.deepEqual(paths, [ '/', '/orders/first-leg', - '/first-leg/customs-documents', - '/first-leg/sailing-prices', '/cross-border/procurement', '/cross-border/domestic-trucking', '/cross-border/pallet-shipping', + '/first-leg/customs-documents', + '/first-leg/sailing-prices', '/overseas-warehouse/labeling', '/overseas-warehouse/storage-transfer', '/overseas-warehouse/cargo-disposal', @@ -112,7 +114,7 @@ test('filters inaccessible leaves and empty navigation groups', () => { const visible = filterNavigationByPermissions(clientNavigation, ['account:profile:view', 'order:first-leg:list', 'order:first-leg:customs-list', 'order:first-leg:pricing-list']) assert.deepEqual(getNavigationLeaves(visible).map((item) => item.path), ['/orders/first-leg', '/first-leg/customs-documents', '/first-leg/sailing-prices']) - assert.deepEqual(visible.map((item) => item.title), ['订单中心']) + assert.deepEqual(visible.map((item) => item.title), ['订单中心', '报关资料', '船期价格']) assert.equal(getFirstAccessiblePath(['account:profile:view']), '/account/profile') assert.equal(getFirstAccessiblePath([]), '/forbidden') }) diff --git a/src/navigation/clientNavigation.ts b/src/navigation/clientNavigation.ts index 4d3319a..be4b6c3 100644 --- a/src/navigation/clientNavigation.ts +++ b/src/navigation/clientNavigation.ts @@ -52,13 +52,13 @@ export const clientNavigation: ClientNavigationNode[] = [ icon: ClipboardList, children: [ requireLeaf('first-leg-orders', '头程订单'), - requireLeaf('first-leg-customs-documents', '报关资料'), - requireLeaf('first-leg-sailing-prices', '船期价格'), requireLeaf('cross-border-procurement'), requireLeaf('cross-border-domestic-trucking'), requireLeaf('cross-border-pallet-shipping'), ], }, + requireLeaf('first-leg-customs-documents', '报关资料'), + requireLeaf('first-leg-sailing-prices', '船期价格'), { key: 'overseas-warehouse-center', title: '海外仓',