diff --git a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs index bfd5600..6fe32a9 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs +++ b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs @@ -120,6 +120,16 @@ test('selects platform destinations by type and caches private addresses per use assert.match(source, /私人地址不能引用平台公共仓库|form\.destinationWarehouseId = ''/) }) +test('auto-fills trusted warehouse and company data instead of asking customers to repeat it', () => { + assert.match(source, /async function selectDestinationWarehouse\(id: string\)/) + assert.match(source, /await store\.loadWarehouseDetail\(warehouse\.id\)/) + assert.match(source, /availableWarehouses\.value\.length === 1/) + assert.match(source, /availableDestinationWarehouses\.value\.length === 1/) + assert.match(source, /function createPickupPartySnapshot\(\)/) + assert.match(source, /contactName: tenantStore\.company\.contactName/) + assert.match(source, /selectedDestinationWarehouse\.address/) +}) + test('collects the backend-required first-leg fields', () => { for (const model of ['destinationCountryCode', 'destinationWarehouseName', 'channelCode', 'pickupMethod', 'taxMode', 'destinationType', 'packageType', 'upc']) { assert.match(source, new RegExp(`v-model="(?:form|item)\\.${model}"`)) @@ -128,9 +138,11 @@ test('collects the backend-required first-leg fields', () => { }) test('distills order creation into the confirmed prototype sections without fake pricing', () => { - assert.match(source, /

下单信息<\/h3>/) + assert.match(source, /

目的地信息<\/h3>/) + assert.match(source, /

发货信息<\/h3>/) assert.match(source, /

货物明细<\/h3>/) - assert.doesNotMatch(source, /

发货信息<\/h3>|

目的地信息<\/h3>/) + assert.ok(source.indexOf('

发货信息

') < source.indexOf('

目的地信息

')) + assert.ok(source.indexOf('

目的地信息

') < source.indexOf('

货物明细

')) assert.match(source, /class="segmented-options"/) assert.match(source, /class="prototype-form-row prototype-form-row--tabs"/) assert.match(source, /class="cargo-primary-grid"/) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index a961dd1..41767a1 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -139,6 +139,16 @@ function createEmptyPartySnapshot(role: FirstLegPartyRole): FirstLegPartySnapsho } } +function createPickupPartySnapshot(): FirstLegPartySnapshot { + return { + ...createEmptyPartySnapshot('SHIPPER'), + name: tenantStore.company.companyName, + contactName: tenantStore.company.contactName, + phone: tenantStore.company.contactPhone, + email: tenantStore.company.contactEmail, + } +} + function requiredTextRules(message: string): FormItemRule[] { return [{ required: true, message, trigger: 'blur' }] } @@ -226,6 +236,7 @@ const pagedOrders = computed(() => { const detailOrder = computed(() => detailId.value ? store.getById(detailId.value) : undefined) const detailSummary = computed(() => detailOrder.value ? summarizeFirstLegCargo(detailOrder.value.items) : null) const selectedWarehouse = computed(() => availableWarehouses.value.find((item) => item.id === form.receivingWarehouseId)) +const selectedDestinationWarehouse = computed(() => store.warehouses.find((item) => item.id === form.destinationWarehouseId && item.warehouseType === form.destinationType)) const hasActiveFilters = computed(() => { const values = appliedFilters.value return Boolean( @@ -255,6 +266,7 @@ async function loadPageData() { } if (ordersResult.status === 'rejected' && !store.loadError) supportingDataErrors.value.push(`订单加载失败:${errorMessage(ordersResult.reason)}`) if (store.warehouseLoadError) supportingDataErrors.value.push(`公共仓库加载失败:${store.warehouseLoadError}`) + await autofillWarehouseSelections() supportingDataLoading.value = false } @@ -348,7 +360,7 @@ function selectChannel(code: string) { form.channelName = channel?.name ?? '' } -function selectDestinationWarehouse(id: string) { +async function selectDestinationWarehouse(id: string) { const warehouse = availableDestinationWarehouses.value.find((item) => item.id === id) if (!warehouse) return form.destinationWarehouseId = warehouse.id @@ -356,9 +368,18 @@ function selectDestinationWarehouse(id: string) { form.destinationCountryCode = warehouse.countryCode.toUpperCase() form.destination = warehouse.name form.amazonWarehouseCode = form.destinationType === 'FBA' ? warehouse.code : '' + try { + const detail = await store.loadWarehouseDetail(warehouse.id) + form.destinationWarehouseName = detail.name + form.destinationCountryCode = detail.countryCode.toUpperCase() + form.destination = detail.name + form.amazonWarehouseCode = form.destinationType === 'FBA' ? detail.code : '' + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '目的仓详情加载失败') + } } -function changeDestinationType() { +async function changeDestinationType() { form.destinationWarehouseId = '' form.destinationWarehouseName = '' form.destination = '' @@ -367,12 +388,15 @@ function changeDestinationType() { form.consigneePartyId = '' form.consigneePartySnapshot = form.destinationType === 'PRIVATE' ? createEmptyPartySnapshot('CONSIGNEE') : null form.consignee = '' + if (form.destinationType !== 'PRIVATE' && availableDestinationWarehouses.value.length === 1) { + await selectDestinationWarehouse(availableDestinationWarehouses.value[0].id) + } resetEditorValidation() } function changePickupMethod() { if (form.pickupMethod === 'PICKUP') { - form.shipperPartySnapshot ??= createEmptyPartySnapshot('SHIPPER') + form.shipperPartySnapshot ??= createPickupPartySnapshot() } else { form.shipperPartyId = '' form.shipperPartySnapshot = null @@ -450,6 +474,18 @@ async function loadSelectedWarehouse(id: string | number | boolean | undefined) } } +async function autofillWarehouseSelections() { + if (!isCreatePage.value || editingId.value) return + if (!form.receivingWarehouseId && availableWarehouses.value.length === 1) { + form.receivingWarehouseId = availableWarehouses.value[0].id + await loadSelectedWarehouse(form.receivingWarehouseId) + } + if (form.destinationType !== 'PRIVATE' && !form.destinationWarehouseId && availableDestinationWarehouses.value.length === 1) { + await selectDestinationWarehouse(availableDestinationWarehouses.value[0].id) + } + markEditorClean() +} + async function openCreate() { if (availableWarehouses.value.length === 0) { ElMessage.warning('当前没有可用的平台收货仓,请联系平台维护仓库后再创建订单') @@ -459,6 +495,7 @@ async function openCreate() { replaceForm(emptyDraft()) resetEditorValidation() await router.push({ name: 'first-leg-order-create' }) + await autofillWarehouseSelections() } async function openEdit(order: FirstLegOrder) { @@ -724,7 +761,31 @@ async function searchOrders(model: QueryModel) {
-

下单信息

选择目的仓后,再填写本次运输安排。

+

发货信息

先选择货物如何送到国内收货仓,并填写本次运输安排。

+
+ 快递自送我司提货卡车自送 + + {{ warehouse.city ? `${warehouse.name} · ${warehouse.city}` : warehouse.name }} +
+
{{ selectedWarehouse.address ? `${selectedWarehouse.province}${selectedWarehouse.city} · ${selectedWarehouse.address}` : '仓库地址详情暂不可用' }}{{ selectedWarehouse.businessHours ? `收货时间:${selectedWarehouse.businessHours}` : '收货时间暂不可用' }}联系人:{{ selectedWarehouse.contactName || '-' }} {{ selectedWarehouse.contactPhone }}
+
+ + 买单报关退税报关 + {{ item.label }} + 是否 + + +
+
+ +
+

目的地信息

再选择货物最终送达的仓库或私人地址。

FBA 仓海外仓私人仓 @@ -733,7 +794,7 @@ async function searchOrders(model: QueryModel) { 平台暂未维护可用的{{ form.destinationType === 'FBA' ? ' FBA 仓' : '海外仓' }},请联系平台管理员。 -
{{ form.destinationWarehouseName }} · {{ form.amazonWarehouseCode || '海外仓' }} · {{ form.destinationCountryCode }}
+
{{ selectedDestinationWarehouse.name }} · {{ selectedDestinationWarehouse.code }} · {{ selectedDestinationWarehouse.countryCode }}仓库地址:{{ selectedDestinationWarehouse.province }}{{ selectedDestinationWarehouse.city }} {{ selectedDestinationWarehouse.address }}联系人:{{ selectedDestinationWarehouse.contactName || '-' }} {{ selectedDestinationWarehouse.contactPhone }}
- - - 快递自送我司提货卡车自送 - - {{ warehouse.city ? `${warehouse.name} · ${warehouse.city}` : warehouse.name }} -
-
{{ selectedWarehouse.address ? `${selectedWarehouse.province}${selectedWarehouse.city} · ${selectedWarehouse.address}` : '仓库地址详情暂不可用' }}{{ selectedWarehouse.businessHours ? `收货时间:${selectedWarehouse.businessHours}` : '收货时间暂不可用' }}
-
- - 买单报关退税报关 - {{ item.label }} - 是否 - -
@@ -905,8 +946,11 @@ async function searchOrders(model: QueryModel) { .address-book-control { display: grid; grid-template-columns: minmax(260px, 1fr) auto auto; gap: 10px; align-items: center; } .address-book-control :deep(.el-button) { min-height: 40px; margin-left: 0; } .field-guidance { display: block; margin-top: 5px; color: var(--muted); line-height: 1.45; } -.selected-destination { display: flex; width: min(100%, 720px); align-items: center; gap: 9px; margin: -4px 0 18px 140px; padding: 10px 12px; background: #f2f7f6; color: var(--primary); } +.selected-destination { display: flex; width: min(100%, 720px); align-items: flex-start; gap: 9px; margin: -4px 0 18px 140px; padding: 10px 12px; background: #f2f7f6; color: var(--primary); } +.selected-destination svg { flex: 0 0 17px; margin-top: 2px; } +.selected-destination div { display: flex; min-width: 0; flex-direction: column; gap: 2px; } .selected-destination span { color: var(--navy); font-size: 12px; font-weight: 600; } +.selected-destination small { color: var(--muted); font-size: 11px; line-height: 1.5; } .flow-divider { width: min(100%, 860px); margin: 4px 0 22px; border-top: 1px solid var(--border); } .compact-transport-grid { margin-top: 18px; } .cargo-entry { padding: 16px; border: 1px solid var(--border); background: #fbfcfc; }