优化
This commit is contained in:
@@ -15,6 +15,15 @@ export interface PageResult<T> {
|
||||
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<PageResult<MerchantWarehouse>>({ method: 'GET', url: '/merchant/v1/warehouses', params: { page: 1, size: 200, status: 'ACTIVE' } })
|
||||
export function getMerchantWarehouses(params: MerchantWarehouseQuery = {}) {
|
||||
return apiRequest<PageResult<MerchantWarehouse>>({
|
||||
method: 'GET',
|
||||
url: '/merchant/v1/warehouses',
|
||||
params: { page: 1, size: 200, status: 'ACTIVE', ...params },
|
||||
})
|
||||
}
|
||||
|
||||
export function getMerchantWarehouseDetail(id: number | string) {
|
||||
|
||||
@@ -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 : ''/)
|
||||
|
||||
@@ -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<ClientWarehouseType[]>([])
|
||||
const warehouseTypeRequests = new Map<ClientWarehouseType, Promise<ClientWarehouse[]>>()
|
||||
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 }
|
||||
})
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -81,6 +81,7 @@ const inboundBillingUnavailableMessage = ref('入库及费用结果尚未发布'
|
||||
const inboundBillingError = ref('')
|
||||
const supportingDataLoading = ref(false)
|
||||
const supportingDataErrors = ref<string[]>([])
|
||||
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) {
|
||||
<div class="address-book-control"><el-select v-model="selectedPrivateAddressId" clearable filterable placeholder="选择当前账号保存的私人地址" @change="selectPrivateAddress"><el-option v-for="address in privateAddresses" :key="address.id" :label="address.label" :value="address.id" /></el-select><el-button @click="savePrivateAddress">保存当前地址</el-button><el-button v-if="selectedPrivateAddressId" link type="danger" @click="removePrivateAddress">移除</el-button></div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.destinationType !== 'PRIVATE'" :label="form.destinationType === 'FBA' ? 'FBA 仓' : '海外仓'" prop="destinationWarehouseId" required :rules="requiredSelectRules('请选择平台维护的目的仓')" class="prototype-form-row"><el-select v-model="form.destinationWarehouseId" filterable placeholder="请选择当前可送达的目的仓" @change="selectDestinationWarehouse"><el-option v-for="warehouse in availableDestinationWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.code}`" :value="warehouse.id" /></el-select><small v-if="availableDestinationWarehouses.length === 0" class="field-guidance">平台暂未维护可用的{{ form.destinationType === 'FBA' ? ' FBA 仓' : '海外仓' }},请联系平台管理员。</small></el-form-item>
|
||||
<el-form-item v-if="form.destinationType !== 'PRIVATE'" :label="form.destinationType === 'FBA' ? 'FBA 仓' : '海外仓'" prop="destinationWarehouseId" required :rules="requiredSelectRules('请选择平台维护的目的仓')" class="prototype-form-row"><el-select v-model="form.destinationWarehouseId" filterable :loading="destinationWarehousesLoading" placeholder="请选择当前可送达的目的仓" @change="selectDestinationWarehouse"><el-option v-for="warehouse in availableDestinationWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.code}`" :value="warehouse.id" /></el-select><small v-if="!destinationWarehousesLoading && availableDestinationWarehouses.length === 0" class="field-guidance">平台暂未维护可用的{{ form.destinationType === 'FBA' ? ' FBA 仓' : '海外仓' }},请联系平台管理员。</small></el-form-item>
|
||||
<div v-if="form.destinationType !== 'PRIVATE' && selectedDestinationWarehouse" class="selected-destination"><MapPin :size="17" /><div><span>{{ selectedDestinationWarehouse.name }} · {{ selectedDestinationWarehouse.code }} · {{ selectedDestinationWarehouse.countryCode }}</span><small v-if="selectedDestinationWarehouse.address">仓库地址:{{ selectedDestinationWarehouse.province }}{{ selectedDestinationWarehouse.city }} {{ selectedDestinationWarehouse.address }}</small><small v-if="selectedDestinationWarehouse.contactName || selectedDestinationWarehouse.contactPhone">联系人:{{ selectedDestinationWarehouse.contactName || '-' }} {{ selectedDestinationWarehouse.contactPhone }}</small></div></div>
|
||||
<template v-if="form.destinationType === 'PRIVATE' && form.consigneePartySnapshot">
|
||||
<el-form-item label="私人地址名称" prop="destinationWarehouseName" required class="prototype-form-row"><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="例如:洛杉矶客户自有仓" @input="syncPrivateDestinationName" /></el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user