From e33d620d8764decbf1e8be0f3538c2ed2a07e7cc Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 6 Aug 2026 14:55:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/first-leg/api/firstLeg.ts | 17 ++++++- .../first-leg/store/firstLegOrders.test.mjs | 7 ++- src/modules/first-leg/store/firstLegOrders.ts | 50 +++++++++++++++---- .../views/FirstLegOrdersView.test.mjs | 3 ++ .../first-leg/views/FirstLegOrdersView.vue | 27 +++++++++- 5 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/modules/first-leg/api/firstLeg.ts b/src/modules/first-leg/api/firstLeg.ts index 23135d9..9c350d3 100644 --- a/src/modules/first-leg/api/firstLeg.ts +++ b/src/modules/first-leg/api/firstLeg.ts @@ -15,6 +15,15 @@ export interface PageResult { pages: number } +export type MerchantWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS' + +export interface MerchantWarehouseQuery { + page?: number + size?: number + status?: 'ACTIVE' | 'INACTIVE' + warehouseType?: MerchantWarehouseType +} + export interface MerchantWarehouse { id: number | string warehouseCode: string @@ -186,8 +195,12 @@ export interface FirstLegOrderQuery { size?: number } -export function getMerchantWarehouses() { - return apiRequest>({ method: 'GET', url: '/merchant/v1/warehouses', params: { page: 1, size: 200, status: 'ACTIVE' } }) +export function getMerchantWarehouses(params: MerchantWarehouseQuery = {}) { + return apiRequest>({ + method: 'GET', + url: '/merchant/v1/warehouses', + params: { page: 1, size: 200, status: 'ACTIVE', ...params }, + }) } export function getMerchantWarehouseDetail(id: number | string) { diff --git a/src/modules/first-leg/store/firstLegOrders.test.mjs b/src/modules/first-leg/store/firstLegOrders.test.mjs index 59f9224..3fc77e5 100644 --- a/src/modules/first-leg/store/firstLegOrders.test.mjs +++ b/src/modules/first-leg/store/firstLegOrders.test.mjs @@ -26,7 +26,7 @@ test('maps transport and status fields instead of treating container mode as tra test('prefers unified reads and only falls back to the real legacy API when C02 is unavailable', () => { assert.match(storeSource, /orders\.value = \[\]/) assert.match(storeSource, /warehouses\.value = \[\]/) - assert.match(storeSource, /Promise\.allSettled\(\[getOrdersForClient\(query\), getMerchantWarehouses\(\)\]\)/) + assert.match(storeSource, /Promise\.allSettled\(\[getOrdersForClient\(query\), loadWarehouseType\('DOMESTIC', true\)\]\)/) assert.match(storeSource, /getMerchantUnifiedOrders\(unifiedQuery\)/) assert.match(storeSource, /isUnifiedOrderQueryUnavailable\(error\)/) assert.match(storeSource, /getMerchantFirstLegOrders\(legacyQuery\(query\)\)/) @@ -83,6 +83,11 @@ test('sends backend-required channel, customs, destination and cargo fields', () }) test('separates domestic receiving warehouses from public destinations', () => { + assert.match(apiSource, /warehouseType\?: MerchantWarehouseType/) + assert.match(apiSource, /params: \{ page: 1, size: 200, status: 'ACTIVE', \.\.\.params \}/) + assert.match(storeSource, /getMerchantWarehouses\(\{ warehouseType: warehouseType as MerchantWarehouseType, page, size: 200 \}\)/) + assert.match(storeSource, /while \(page <= pages\)/) + assert.match(storeSource, /loadWarehouseType\('DOMESTIC', true\)/) assert.match(storeSource, /warehouse\.warehouseType !== 'DOMESTIC'/) assert.match(storeSource, /item\.warehouseType === draft\.destinationType/) assert.match(storeSource, /draft\.amazonWarehouseCode = draft\.destinationType === 'FBA' \? destinationWarehouse\.code : ''/) diff --git a/src/modules/first-leg/store/firstLegOrders.ts b/src/modules/first-leg/store/firstLegOrders.ts index 3f0dbb4..3a85fdd 100644 --- a/src/modules/first-leg/store/firstLegOrders.ts +++ b/src/modules/first-leg/store/firstLegOrders.ts @@ -14,6 +14,7 @@ import { type MerchantFirstLegOrder, type MerchantFirstLegOrderDetail, type MerchantWarehouse, + type MerchantWarehouseType, } from '@/modules/first-leg/api/firstLeg' import { getMerchantUnifiedOrder, @@ -386,27 +387,58 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () const loadError = ref('') const warehouseLoadError = ref('') const draftLoadError = ref('') + const loadedWarehouseTypes = ref([]) + const warehouseTypeRequests = new Map>() const activeCount = computed(() => orders.value.filter((item) => item.status !== 'DRAFT' && item.status !== 'DELIVERED').length) const pendingCount = computed(() => orders.value.filter((item) => item.status === 'PENDING_REVIEW').length) function getById(id: string) { return orders.value.find((item) => item.id === id) } + async function loadWarehouseType(warehouseType: ClientWarehouseType, force = false) { + if (!force && loadedWarehouseTypes.value.includes(warehouseType)) return + const pending = warehouseTypeRequests.get(warehouseType) + if (pending) return pending + + const request = (async () => { + let page = 1 + const records: MerchantWarehouse[] = [] + let pages = 1 + do { + const response = await getMerchantWarehouses({ warehouseType: warehouseType as MerchantWarehouseType, page, size: 200 }) + records.push(...response.records) + pages = Number(response.pages) || page + page += 1 + } while (page <= pages) + + return records + .filter((item) => item.status === 'ACTIVE' && item.warehouseType === warehouseType) + .map(mapWarehouse) + })() + warehouseTypeRequests.set(warehouseType, request) + try { + const nextWarehouses = await request + warehouses.value = [...warehouses.value.filter((item) => item.warehouseType !== warehouseType), ...nextWarehouses] + loadedWarehouseTypes.value = [...loadedWarehouseTypes.value.filter((item) => item !== warehouseType), warehouseType] + } catch (error) { + if (warehouseType === 'DOMESTIC') warehouseLoadError.value = error instanceof Error ? error.message : '公共仓库数据加载失败' + throw error + } finally { + if (warehouseTypeRequests.get(warehouseType) === request) warehouseTypeRequests.delete(warehouseType) + } + } + async function load(query: ClientOrderReadQuery = {}) { loading.value = true loadedFromApi.value = true loadError.value = '' warehouseLoadError.value = '' draftLoadError.value = '' + loadedWarehouseTypes.value = [] + warehouseTypeRequests.clear() orders.value = [] warehouses.value = [] - const [orderResult, warehouseResult] = await Promise.allSettled([getOrdersForClient(query), getMerchantWarehouses()]) - if (warehouseResult.status === 'fulfilled') { - warehouses.value = warehouseResult.value.records - .filter((item) => item.status === 'ACTIVE' && ['DOMESTIC', 'FBA', 'OVERSEAS'].includes(item.warehouseType)) - .map(mapWarehouse) - } else { - warehouseLoadError.value = warehouseResult.reason instanceof Error ? warehouseResult.reason.message : '公共仓库数据加载失败' - } + const [orderResult, warehouseResult] = await Promise.allSettled([getOrdersForClient(query), loadWarehouseType('DOMESTIC', true)]) + if (warehouseResult.status === 'rejected' && !warehouseLoadError.value) warehouseLoadError.value = warehouseResult.reason instanceof Error ? warehouseResult.reason.message : '公共仓库数据加载失败' if (orderResult.status === 'fulfilled') { if (orderResult.value.source === 'unified') { draftLoadError.value = orderResult.value.draftError @@ -494,5 +526,5 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () return loadUnifiedDetail(order.id) } - return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, saveDraft, submit } + return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, loadWarehouseType, saveDraft, submit } }) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs index df9e0ea..32358c0 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs +++ b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs @@ -150,6 +150,9 @@ test('requires an enabled platform receiving warehouse in the order form', () => test('selects platform destinations by type and caches private addresses per user', () => { assert.match(source, /warehouse\.warehouseType === form\.destinationType/) + assert.match(source, /await store\.loadWarehouseType\(type\)/) + assert.match(source, /await loadDestinationWarehouses\(form\.destinationType\)/) + assert.match(source, /:loading="destinationWarehousesLoading"/) assert.match(source, /v-model="form\.destinationWarehouseId"/) assert.match(source, /form\.amazonWarehouseCode = form\.destinationType === 'FBA' \? warehouse\.code : ''/) assert.match(source, /cc:first-leg:private-addresses:\$\{auth\.user\?\.id/) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index fd51950..ccc571c 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -81,6 +81,7 @@ const inboundBillingUnavailableMessage = ref('入库及费用结果尚未发布' const inboundBillingError = ref('') const supportingDataLoading = ref(false) const supportingDataErrors = ref([]) +const destinationWarehousesLoading = ref(false) const editorBaseline = ref('') let cargoSequence = 0 let inboundBillingRequestSequence = 0 @@ -292,9 +293,13 @@ async function loadPageData() { const companyRequest = canViewCompanyProfile.value ? tenantStore.loadCompanyContext() : Promise.resolve() - const [ordersResult, companyResult] = await Promise.allSettled([ + const destinationRequest = isCreatePage.value && form.destinationType !== 'PRIVATE' + ? loadDestinationWarehouses(form.destinationType) + : Promise.resolve() + const [ordersResult, companyResult, destinationResult] = await Promise.allSettled([ store.load(), companyRequest, + destinationRequest, ]) if (canViewCompanyProfile.value && companyResult.status === 'fulfilled') { companyAuditLoaded.value = true @@ -304,10 +309,21 @@ async function loadPageData() { if (ordersResult.status === 'rejected' && !store.loadError) supportingDataErrors.value.push(`订单加载失败:${errorMessage(ordersResult.reason)}`) if (store.warehouseLoadError) supportingDataErrors.value.push(`公共仓库加载失败:${store.warehouseLoadError}`) if (store.draftLoadError) supportingDataErrors.value.push(`订单草稿加载失败:${store.draftLoadError}`) + if (destinationResult.status === 'rejected') supportingDataErrors.value.push(`目的仓加载失败:${errorMessage(destinationResult.reason)}`) await autofillWarehouseSelections() supportingDataLoading.value = false } +async function loadDestinationWarehouses(type: 'FBA' | 'OVERSEAS' | 'PRIVATE') { + if (type === 'PRIVATE') return + destinationWarehousesLoading.value = true + try { + await store.loadWarehouseType(type) + } finally { + destinationWarehousesLoading.value = false + } +} + function errorMessage(error: unknown) { return error instanceof Error ? error.message : '服务暂时不可用' } @@ -426,6 +442,11 @@ async function changeDestinationType() { form.consigneePartyId = '' form.consigneePartySnapshot = form.destinationType === 'PRIVATE' ? createEmptyPartySnapshot('CONSIGNEE') : null form.consignee = '' + try { + await loadDestinationWarehouses(form.destinationType) + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '目的仓数据加载失败') + } if (form.destinationType !== 'PRIVATE' && availableDestinationWarehouses.value.length === 1) { await selectDestinationWarehouse(availableDestinationWarehouses.value[0].id) } @@ -533,6 +554,7 @@ async function openCreate() { replaceForm(emptyDraft()) resetEditorValidation() await router.push({ name: 'first-leg-order-create' }) + await loadDestinationWarehouses(form.destinationType) await autofillWarehouseSelections() } @@ -542,6 +564,7 @@ async function openEdit(order: FirstLegOrder) { try { const detailedOrder = await store.loadDetail(order.id) const draft = draftFromOrder(detailedOrder) + if (draft.destinationType !== 'PRIVATE') await loadDestinationWarehouses(draft.destinationType) const warehouseAvailable = availableWarehouses.value.some((item) => item.id === detailedOrder.receivingWarehouseId) if (!warehouseAvailable) { draft.receivingWarehouseId = '' @@ -893,7 +916,7 @@ async function searchOrders(model: QueryModel) {
保存当前地址移除
- 平台暂未维护可用的{{ form.destinationType === 'FBA' ? ' FBA 仓' : '海外仓' }},请联系平台管理员。 + 平台暂未维护可用的{{ form.destinationType === 'FBA' ? ' FBA 仓' : '海外仓' }},请联系平台管理员。
{{ selectedDestinationWarehouse.name }} · {{ selectedDestinationWarehouse.code }} · {{ selectedDestinationWarehouse.countryCode }}仓库地址:{{ selectedDestinationWarehouse.province }}{{ selectedDestinationWarehouse.city }} {{ selectedDestinationWarehouse.address }}联系人:{{ selectedDestinationWarehouse.contactName || '-' }} {{ selectedDestinationWarehouse.contactPhone }}