优化
This commit is contained in:
@@ -24,7 +24,7 @@ test('maps transport and status fields instead of treating container mode as tra
|
||||
test('uses real API data without falling back to demo warehouses or orders', () => {
|
||||
assert.match(storeSource, /orders\.value = \[\]/)
|
||||
assert.match(storeSource, /warehouses\.value = \[\]/)
|
||||
assert.match(storeSource, /Promise\.all\(\[getMerchantFirstLegOrders\(\), getMerchantWarehouses\(\)\]\)/)
|
||||
assert.match(storeSource, /Promise\.allSettled\(\[getMerchantFirstLegOrders\(\), getMerchantWarehouses\(\)\]\)/)
|
||||
assert.doesNotMatch(storeSource, /DEMO_PLATFORM_RECEIVING_WAREHOUSES|initialOrders/)
|
||||
})
|
||||
|
||||
@@ -35,6 +35,13 @@ test('sends backend-required channel, customs, destination and cargo fields', ()
|
||||
assert.match(storeSource, /containerMode: draft\.transportMode === 'FCL' \? 'FULL_CONTAINER'/)
|
||||
assert.match(storeSource, /customerId: draft\.shipperPartyId/)
|
||||
assert.match(storeSource, /customerName: customerName\.trim\(\) \|\| draft\.shipperPartySnapshot\.name/)
|
||||
assert.match(storeSource, /destinationWarehouseId: draft\.destinationType === 'PRIVATE' \? undefined : draft\.destinationWarehouseId/)
|
||||
})
|
||||
|
||||
test('separates domestic receiving warehouses from public destinations', () => {
|
||||
assert.match(storeSource, /warehouse\.warehouseType !== 'DOMESTIC'/)
|
||||
assert.match(storeSource, /item\.warehouseType === draft\.destinationType/)
|
||||
assert.match(storeSource, /draft\.amazonWarehouseCode = draft\.destinationType === 'FBA' \? destinationWarehouse\.code : ''/)
|
||||
})
|
||||
|
||||
test('only blocks submission when a known company audit status is not active', () => {
|
||||
@@ -67,6 +74,7 @@ test('maps backend warehouse snapshots and fulfillment courier fields', () => {
|
||||
|
||||
test('keeps failed order loads distinct from empty results', () => {
|
||||
assert.match(storeSource, /const loadError = ref\(''\)/)
|
||||
assert.match(storeSource, /loadError\.value = error instanceof Error/)
|
||||
assert.match(storeSource, /return \{ orders, warehouses, loading, loadedFromApi, loadError/)
|
||||
assert.match(storeSource, /orderResult\.reason instanceof Error/)
|
||||
assert.match(storeSource, /warehouseLoadError/)
|
||||
assert.match(storeSource, /return \{ orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError/)
|
||||
})
|
||||
|
||||
@@ -21,6 +21,12 @@ import type { FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegPar
|
||||
import type { FirstLegOrder, FirstLegOrderDraft, FirstLegOrderTimelineItem } from '@/modules/first-leg/types/firstLegOrder'
|
||||
import { canClientEditFirstLegOrder } from '@/modules/first-leg/utils/firstLegOrder'
|
||||
|
||||
export type ClientWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS'
|
||||
export type ClientWarehouse = PlatformReceivingWarehouse & {
|
||||
warehouseType: ClientWarehouseType
|
||||
countryCode: string
|
||||
}
|
||||
|
||||
const countryCodes: Readonly<Record<string, string>> = {
|
||||
中国: 'CN', 美国: 'US', 德国: 'DE', 英国: 'GB', 加拿大: 'CA', 日本: 'JP', 澳大利亚: 'AU',
|
||||
}
|
||||
@@ -60,7 +66,7 @@ function partyPayload(snapshot: FirstLegPartySnapshot): FirstLegOrderPartyPayloa
|
||||
}
|
||||
}
|
||||
|
||||
function savePayload(draft: FirstLegOrderDraft, warehouse: PlatformReceivingWarehouse, customerName: string): FirstLegOrderSaveRequest {
|
||||
function savePayload(draft: FirstLegOrderDraft, warehouse: ClientWarehouse, customerName: string): FirstLegOrderSaveRequest {
|
||||
if (!draft.shipperPartySnapshot || !draft.consigneePartySnapshot) throw new Error('请完整填写收发货信息')
|
||||
return {
|
||||
// 后端把 customerId 作为订单主往来单位校验并以服务端名称覆盖 customerName;
|
||||
@@ -70,6 +76,7 @@ function savePayload(draft: FirstLegOrderDraft, warehouse: PlatformReceivingWare
|
||||
transportMode: apiTransport(draft),
|
||||
tradeTerm: draft.tradeTerm || undefined,
|
||||
destinationCountryCode: draft.destinationCountryCode.trim().toUpperCase(),
|
||||
destinationWarehouseId: draft.destinationType === 'PRIVATE' ? undefined : draft.destinationWarehouseId,
|
||||
destinationWarehouseName: draft.destinationWarehouseName,
|
||||
expectedShipDate: draft.expectedDepartureDate || undefined,
|
||||
customerRemark: draft.customerRemark || undefined,
|
||||
@@ -167,26 +174,29 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
|
||||
})),
|
||||
timeline: detail ? timeline(detail) : [], createdAt: order.createdAt, updatedAt: order.updatedAt,
|
||||
tradeTerm: order.tradeTerm ?? '', destinationCountryCode: order.destinationCountryCode,
|
||||
destinationWarehouseId: order.destinationWarehouseId ? String(order.destinationWarehouseId) : '',
|
||||
destinationWarehouseName: order.destinationWarehouseName, pickupMethod: order.pickupMethod, channelCode: order.channelCode,
|
||||
channelName: order.channelName, taxMode: order.taxMode, customsInspection: order.customsInspection,
|
||||
destinationType: order.destinationType, amazonWarehouseCode: order.amazonWarehouseCode ?? '', shipmentId: order.shipmentId ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
function mapWarehouse(item: MerchantWarehouse): PlatformReceivingWarehouse {
|
||||
function mapWarehouse(item: MerchantWarehouse): ClientWarehouse {
|
||||
return {
|
||||
id: String(item.id), code: item.warehouseCode, name: item.warehouseName, province: '',
|
||||
city: '', address: '', contactName: item.contactName ?? '', contactPhone: item.contactPhone ?? '',
|
||||
businessHours: '', status: item.status, remark: item.remark ?? '', updatedAt: item.updatedAt ?? '',
|
||||
warehouseType: item.warehouseType as ClientWarehouseType, countryCode: item.countryCode,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => {
|
||||
const orders = ref<FirstLegOrder[]>([])
|
||||
const warehouses = ref<PlatformReceivingWarehouse[]>([])
|
||||
const warehouses = ref<ClientWarehouse[]>([])
|
||||
const loading = ref(false)
|
||||
const loadedFromApi = ref(false)
|
||||
const loadError = ref('')
|
||||
const warehouseLoadError = ref('')
|
||||
const activeCount = computed(() => orders.value.filter((item) => ['PENDING_REVIEW', 'ACCEPTED', 'BOOKED', 'IN_TRANSIT'].includes(item.status)).length)
|
||||
const pendingCount = computed(() => orders.value.filter((item) => item.status === 'PENDING_REVIEW').length)
|
||||
|
||||
@@ -196,16 +206,23 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
||||
loading.value = true
|
||||
loadedFromApi.value = true
|
||||
loadError.value = ''
|
||||
warehouseLoadError.value = ''
|
||||
orders.value = []
|
||||
warehouses.value = []
|
||||
try {
|
||||
const [orderPage, warehousePage] = await Promise.all([getMerchantFirstLegOrders(), getMerchantWarehouses()])
|
||||
warehouses.value = warehousePage.records.filter((item) => item.status === 'ACTIVE').map(mapWarehouse)
|
||||
orders.value = orderPage.records.map((item) => mapOrder(item, warehouses.value))
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : '订单与仓库数据加载失败'
|
||||
throw error
|
||||
} finally { loading.value = false }
|
||||
const [orderResult, warehouseResult] = await Promise.allSettled([getMerchantFirstLegOrders(), 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 : '公共仓库数据加载失败'
|
||||
}
|
||||
if (orderResult.status === 'fulfilled') {
|
||||
orders.value = orderResult.value.records.map((item) => mapOrder(item, warehouses.value))
|
||||
} else {
|
||||
loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function loadDetail(id: string) {
|
||||
@@ -235,7 +252,16 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
||||
|
||||
async function saveDraft(draft: FirstLegOrderDraft, customerName: string, id?: string) {
|
||||
const warehouse = warehouses.value.find((item) => item.id === draft.receivingWarehouseId)
|
||||
if (!warehouse) throw new Error('所选收货仓当前不可用')
|
||||
if (!warehouse || warehouse.warehouseType !== 'DOMESTIC') throw new Error('所选国内收货仓当前不可用')
|
||||
if (draft.destinationType !== 'PRIVATE') {
|
||||
const destinationWarehouse = warehouses.value.find((item) => item.id === draft.destinationWarehouseId && item.warehouseType === draft.destinationType)
|
||||
if (!destinationWarehouse) throw new Error('所选目的仓当前不可用,请重新选择')
|
||||
draft.destinationWarehouseName = destinationWarehouse.name
|
||||
draft.destinationCountryCode = destinationWarehouse.countryCode
|
||||
draft.amazonWarehouseCode = draft.destinationType === 'FBA' ? destinationWarehouse.code : ''
|
||||
} else {
|
||||
draft.destinationWarehouseId = ''
|
||||
}
|
||||
if (id) {
|
||||
const order = getById(id)
|
||||
if (!order || !canClientEditFirstLegOrder(order.status)) throw new Error('当前订单状态不允许编辑')
|
||||
@@ -253,5 +279,5 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
||||
return loadDetail(order.id)
|
||||
}
|
||||
|
||||
return { orders, warehouses, loading, loadedFromApi, loadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, saveDraft, submit }
|
||||
return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, saveDraft, submit }
|
||||
})
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface FirstLegOrder {
|
||||
updatedAt: string
|
||||
tradeTerm: string
|
||||
destinationCountryCode: string
|
||||
destinationWarehouseId: string
|
||||
destinationWarehouseName: string
|
||||
pickupMethod: 'SELF_EXPRESS' | 'PICKUP' | 'SELF_TRUCK'
|
||||
channelCode: string
|
||||
@@ -101,6 +102,7 @@ export type FirstLegOrderDraft = Pick<
|
||||
| 'items'
|
||||
| 'tradeTerm'
|
||||
| 'destinationCountryCode'
|
||||
| 'destinationWarehouseId'
|
||||
| 'destinationWarehouseName'
|
||||
| 'pickupMethod'
|
||||
| 'channelCode'
|
||||
|
||||
@@ -41,6 +41,7 @@ function validDraft() {
|
||||
customerRemark: '',
|
||||
tradeTerm: 'FOB',
|
||||
destinationCountryCode: 'US',
|
||||
destinationWarehouseId: 'WH-FBA-ONT8',
|
||||
destinationWarehouseName: 'ONT8',
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
channelCode: 'SEA-US-FBA',
|
||||
@@ -113,6 +114,20 @@ test('requires FBA reference, warehouse, shipment, SKU and UPC fields', () => {
|
||||
assert.ok(errors.includes('第 1 条货物缺少 UPC'))
|
||||
})
|
||||
|
||||
test('requires public destination ids while keeping private addresses detached from public warehouses', () => {
|
||||
const publicDraft = validDraft()
|
||||
publicDraft.destinationWarehouseId = ''
|
||||
assert.ok(validateFirstLegOrderDraft(publicDraft).includes('请选择平台维护的目的仓'))
|
||||
|
||||
const privateDraft = validDraft()
|
||||
privateDraft.destinationType = 'PRIVATE'
|
||||
privateDraft.destinationWarehouseId = 'WH-FBA-ONT8'
|
||||
privateDraft.amazonWarehouseCode = ''
|
||||
privateDraft.shipmentId = ''
|
||||
privateDraft.referenceNo = ''
|
||||
assert.ok(validateFirstLegOrderDraft(privateDraft).includes('私人地址不能引用平台公共仓库'))
|
||||
})
|
||||
|
||||
test('does not require container details for non-FCL cargo', () => {
|
||||
const draft = validDraft()
|
||||
draft.transportMode = 'AIR'
|
||||
|
||||
@@ -66,6 +66,8 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) {
|
||||
if (!draft.destination.trim()) errors.push('请填写目的地')
|
||||
if (!/^[A-Za-z]{2}$/.test(draft.destinationCountryCode.trim())) errors.push('请输入两位英文字母的目的国代码')
|
||||
if (!draft.destinationWarehouseName.trim()) errors.push('请填写目的仓名称')
|
||||
if (draft.destinationType !== 'PRIVATE' && !draft.destinationWarehouseId) errors.push('请选择平台维护的目的仓')
|
||||
if (draft.destinationType === 'PRIVATE' && draft.destinationWarehouseId) errors.push('私人地址不能引用平台公共仓库')
|
||||
if (!draft.channelCode.trim() || !draft.channelName.trim()) errors.push('请填写渠道编码和渠道名称')
|
||||
if (!draft.receivingWarehouseId) errors.push('请选择平台收货仓')
|
||||
if (!draft.shipperPartyId || !draft.shipperPartySnapshot || !draft.shipper.trim()) errors.push('请选择发货单位')
|
||||
|
||||
@@ -96,7 +96,7 @@ test('does not expose arbitrary status mutation controls to customers', () => {
|
||||
})
|
||||
|
||||
test('requires an enabled platform receiving warehouse in the order form', () => {
|
||||
assert.match(source, /computed\(\(\) => store\.warehouses\)/)
|
||||
assert.match(source, /store\.warehouses\.filter\(\(warehouse\) => warehouse\.warehouseType === 'DOMESTIC'\)/)
|
||||
assert.match(source, /availableWarehouses\.value\.length === 0/)
|
||||
assert.match(source, /label="平台收货仓"[^>]*required/)
|
||||
assert.match(source, /v-model="form\.receivingWarehouseId"/)
|
||||
@@ -111,6 +111,15 @@ test('requires an enabled platform receiving warehouse in the order form', () =>
|
||||
assert.match(source, /loadSelectedWarehouse\(detailedOrder\.receivingWarehouseId\)/)
|
||||
})
|
||||
|
||||
test('selects platform destinations by type and caches private addresses per user', () => {
|
||||
assert.match(source, /warehouse\.warehouseType === form\.destinationType/)
|
||||
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/)
|
||||
assert.match(source, /localStorage\.setItem\(privateAddressStorageKey\(\)/)
|
||||
assert.match(source, /私人地址不能引用平台公共仓库|form\.destinationWarehouseId = ''/)
|
||||
})
|
||||
|
||||
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}"`))
|
||||
@@ -191,4 +200,5 @@ test('loads complete order detail before editing and rejects an unavailable hist
|
||||
assert.match(source, /draftFromOrder\(detailedOrder\)/)
|
||||
assert.match(source, /draft\.receivingWarehouseId = ''/)
|
||||
assert.match(source, /原平台收货仓当前不可用,请重新选择可用收货仓/)
|
||||
assert.match(source, /原目的仓当前不可用,请重新选择平台维护的目的仓/)
|
||||
})
|
||||
|
||||
@@ -34,7 +34,8 @@ const companyAuditLoaded = ref(false)
|
||||
const companySubmissionBlocked = computed(() => companyAuditLoaded.value && tenantStore.company.auditStatus !== 'ACTIVE')
|
||||
const canCreateOrder = computed(() => auth.hasPermission('order:first-leg:create'))
|
||||
const canEditOrder = computed(() => auth.hasPermission('order:first-leg:edit'))
|
||||
const availableWarehouses = computed(() => store.warehouses)
|
||||
const availableWarehouses = computed(() => store.warehouses.filter((warehouse) => warehouse.warehouseType === 'DOMESTIC'))
|
||||
const availableDestinationWarehouses = computed(() => store.warehouses.filter((warehouse) => warehouse.warehouseType === form.destinationType))
|
||||
const availableShippers = ref(firstLegPartySource.listAvailable('SHIPPER'))
|
||||
const availableConsignees = ref(firstLegPartySource.listAvailable('CONSIGNEE'))
|
||||
const filters = ref<QueryModel>({ keyword: '', status: '', transportMode: '', createdRange: [] })
|
||||
@@ -77,6 +78,37 @@ const supportingDataErrors = ref<string[]>([])
|
||||
const editorBaseline = ref('')
|
||||
let cargoSequence = 0
|
||||
|
||||
interface CachedPrivateAddress {
|
||||
id: string
|
||||
label: string
|
||||
consigneePartyId: string
|
||||
consigneePartySnapshot: NonNullable<FirstLegOrderDraft['consigneePartySnapshot']>
|
||||
consignee: string
|
||||
destination: string
|
||||
destinationCountryCode: string
|
||||
destinationWarehouseName: string
|
||||
}
|
||||
|
||||
const privateAddresses = ref<CachedPrivateAddress[]>([])
|
||||
const selectedPrivateAddressId = ref('')
|
||||
|
||||
function privateAddressStorageKey() {
|
||||
return `cc:first-leg:private-addresses:${auth.user?.id ?? 'anonymous'}`
|
||||
}
|
||||
|
||||
function loadPrivateAddresses() {
|
||||
try {
|
||||
const value = JSON.parse(localStorage.getItem(privateAddressStorageKey()) ?? '[]') as unknown
|
||||
privateAddresses.value = Array.isArray(value) ? value.filter((item): item is CachedPrivateAddress => Boolean(item && typeof item === 'object' && 'id' in item && 'consigneePartySnapshot' in item)) : []
|
||||
} catch {
|
||||
privateAddresses.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function persistPrivateAddresses() {
|
||||
localStorage.setItem(privateAddressStorageKey(), JSON.stringify(privateAddresses.value))
|
||||
}
|
||||
|
||||
const orderFormRules: FormRules = {
|
||||
transportMode: [{ required: true, message: '请选择运输方式', trigger: 'change' }],
|
||||
businessType: [{ required: true, message: '请选择业务类型', trigger: 'change' }],
|
||||
@@ -141,6 +173,7 @@ function emptyDraft(): FirstLegOrderDraft {
|
||||
customerRemark: '',
|
||||
tradeTerm: 'FOB',
|
||||
destinationCountryCode: 'US',
|
||||
destinationWarehouseId: '',
|
||||
destinationWarehouseName: '',
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
channelCode: 'SEA-US-FBA',
|
||||
@@ -221,9 +254,8 @@ async function loadPageData() {
|
||||
} else if (companyResult.status === 'rejected') {
|
||||
supportingDataErrors.value.push(`企业资料加载失败:${errorMessage(companyResult.reason)}`)
|
||||
}
|
||||
if (ordersResult.status === 'rejected' && !store.loadError) {
|
||||
supportingDataErrors.value.push(`订单与仓库加载失败:${errorMessage(ordersResult.reason)}`)
|
||||
}
|
||||
if (ordersResult.status === 'rejected' && !store.loadError) supportingDataErrors.value.push(`订单加载失败:${errorMessage(ordersResult.reason)}`)
|
||||
if (store.warehouseLoadError) supportingDataErrors.value.push(`公共仓库加载失败:${store.warehouseLoadError}`)
|
||||
supportingDataLoading.value = false
|
||||
}
|
||||
|
||||
@@ -239,6 +271,7 @@ function handleBeforeUnload(event: BeforeUnloadEvent) {
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
loadPrivateAddresses()
|
||||
void loadPageData()
|
||||
})
|
||||
|
||||
@@ -297,6 +330,7 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
|
||||
customerRemark: order.customerRemark,
|
||||
tradeTerm: order.tradeTerm,
|
||||
destinationCountryCode: order.destinationCountryCode,
|
||||
destinationWarehouseId: order.destinationWarehouseId,
|
||||
destinationWarehouseName: order.destinationWarehouseName,
|
||||
pickupMethod: order.pickupMethod,
|
||||
channelCode: order.channelCode,
|
||||
@@ -347,8 +381,67 @@ function selectChannel(code: string) {
|
||||
form.channelName = channel?.name ?? ''
|
||||
}
|
||||
|
||||
function syncAmazonWarehouse(value: string) {
|
||||
if (form.destinationType === 'FBA') form.destinationWarehouseName = value.trim()
|
||||
function selectDestinationWarehouse(id: string) {
|
||||
const warehouse = availableDestinationWarehouses.value.find((item) => item.id === id)
|
||||
if (!warehouse) return
|
||||
form.destinationWarehouseId = warehouse.id
|
||||
form.destinationWarehouseName = warehouse.name
|
||||
form.destinationCountryCode = warehouse.countryCode.toUpperCase()
|
||||
form.destination = warehouse.name
|
||||
form.amazonWarehouseCode = form.destinationType === 'FBA' ? warehouse.code : ''
|
||||
}
|
||||
|
||||
function changeDestinationType() {
|
||||
form.destinationWarehouseId = ''
|
||||
form.destinationWarehouseName = ''
|
||||
form.amazonWarehouseCode = ''
|
||||
selectedPrivateAddressId.value = ''
|
||||
resetEditorValidation()
|
||||
}
|
||||
|
||||
function selectPrivateAddress(id: string) {
|
||||
const address = privateAddresses.value.find((item) => item.id === id)
|
||||
if (!address) return
|
||||
form.consigneePartyId = address.consigneePartyId
|
||||
form.consigneePartySnapshot = { ...address.consigneePartySnapshot }
|
||||
form.consignee = address.consignee
|
||||
form.destination = address.destination
|
||||
form.destinationCountryCode = address.destinationCountryCode
|
||||
form.destinationWarehouseId = ''
|
||||
form.destinationWarehouseName = address.destinationWarehouseName
|
||||
}
|
||||
|
||||
function savePrivateAddress() {
|
||||
if (!form.consigneePartySnapshot || !form.consigneePartyId || !form.destinationWarehouseName.trim() || !form.destinationCountryCode.trim()) {
|
||||
ElMessage.warning('请先完整填写收货单位、私人地址名称和目的国代码')
|
||||
return
|
||||
}
|
||||
const snapshot = { ...form.consigneePartySnapshot }
|
||||
const existing = privateAddresses.value.find((item) => item.consigneePartyId === form.consigneePartyId && item.consigneePartySnapshot.address === snapshot.address)
|
||||
const saved: CachedPrivateAddress = {
|
||||
id: existing?.id ?? globalThis.crypto?.randomUUID?.() ?? `private-${Date.now()}`,
|
||||
label: `${form.destinationWarehouseName.trim()} · ${snapshot.address}`,
|
||||
consigneePartyId: form.consigneePartyId,
|
||||
consigneePartySnapshot: snapshot,
|
||||
consignee: form.consignee,
|
||||
destination: form.destination,
|
||||
destinationCountryCode: form.destinationCountryCode.trim().toUpperCase(),
|
||||
destinationWarehouseName: form.destinationWarehouseName.trim(),
|
||||
}
|
||||
privateAddresses.value = existing
|
||||
? privateAddresses.value.map((item) => item.id === existing.id ? saved : item)
|
||||
: [saved, ...privateAddresses.value]
|
||||
selectedPrivateAddressId.value = saved.id
|
||||
persistPrivateAddresses()
|
||||
ElMessage.success(existing ? '私人地址已更新' : '私人地址已保存到当前账号')
|
||||
}
|
||||
|
||||
function removePrivateAddress() {
|
||||
if (!selectedPrivateAddressId.value) return
|
||||
privateAddresses.value = privateAddresses.value.filter((item) => item.id !== selectedPrivateAddressId.value)
|
||||
selectedPrivateAddressId.value = ''
|
||||
persistPrivateAddresses()
|
||||
ElMessage.success('已从当前账号的私人地址中移除')
|
||||
}
|
||||
|
||||
function selectShipper(id: string) {
|
||||
@@ -399,6 +492,15 @@ async function openEdit(order: FirstLegOrder) {
|
||||
draft.receivingWarehouseId = ''
|
||||
ElMessage.warning('原平台收货仓当前不可用,请重新选择可用收货仓')
|
||||
}
|
||||
if (draft.destinationType !== 'PRIVATE') {
|
||||
const destinationAvailable = store.warehouses.some((item) => item.id === draft.destinationWarehouseId && item.warehouseType === draft.destinationType)
|
||||
if (!destinationAvailable) {
|
||||
draft.destinationWarehouseId = ''
|
||||
draft.destinationWarehouseName = ''
|
||||
draft.amazonWarehouseCode = ''
|
||||
ElMessage.warning('原目的仓当前不可用,请重新选择平台维护的目的仓')
|
||||
}
|
||||
}
|
||||
editingId.value = detailedOrder.id
|
||||
replaceForm(draft)
|
||||
if (warehouseAvailable) void loadSelectedWarehouse(detailedOrder.receivingWarehouseId)
|
||||
@@ -658,7 +760,14 @@ async function searchOrders(model: QueryModel) {
|
||||
|
||||
<section class="order-form-section prototype-section destination-section">
|
||||
<div class="order-section-heading"><div><h3>发货与目的地</h3><p>单位信息会保存为本订单快照,后续主数据变更不会影响本单。</p></div></div>
|
||||
<el-form-item label="目的仓类型" prop="destinationType" required><el-radio-group v-model="form.destinationType" class="segmented-options"><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="OVERSEAS">海外仓</el-radio-button><el-radio-button value="PRIVATE">私人仓</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item label="目的仓类型" prop="destinationType" required><el-radio-group v-model="form.destinationType" class="segmented-options" @change="changeDestinationType"><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="OVERSEAS">海外仓</el-radio-button><el-radio-button value="PRIVATE">私人地址</el-radio-button></el-radio-group></el-form-item>
|
||||
<div v-if="form.destinationType === 'PRIVATE'" class="private-address-toolbar">
|
||||
<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" type="danger" plain @click="removePrivateAddress">移除缓存</el-button>
|
||||
</div>
|
||||
<div class="party-select-grid">
|
||||
<div class="party-panel">
|
||||
<h4>发货信息</h4>
|
||||
@@ -682,9 +791,11 @@ async function searchOrders(model: QueryModel) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="destination-fields">
|
||||
<el-form-item label="目的国代码" prop="destinationCountryCode" required><el-input v-model="form.destinationCountryCode" maxlength="2" placeholder="例如:US" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="亚马逊仓代码" prop="amazonWarehouseCode" required :rules="requiredTextRules('请输入 Amazon 仓库编码')"><el-input v-model="form.amazonWarehouseCode" maxlength="32" placeholder="例如:ONT8" @input="syncAmazonWarehouse" /></el-form-item>
|
||||
<el-form-item v-else label="目的仓名称" prop="destinationWarehouseName" required><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="请输入海外仓或私人仓名称" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType !== 'PRIVATE'" label="平台目的仓" prop="destinationWarehouseId" required :rules="requiredSelectRules('请选择平台维护的目的仓')"><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 label="目的国代码" prop="destinationCountryCode" required><el-input v-model="form.destinationCountryCode" :readonly="form.destinationType !== 'PRIVATE'" maxlength="2" placeholder="例如:US" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="亚马逊仓代码" prop="amazonWarehouseCode" required :rules="requiredTextRules('请选择有效的 FBA 仓')"><el-input v-model="form.amazonWarehouseCode" readonly placeholder="选择 FBA 仓后自动带出" /></el-form-item>
|
||||
<el-form-item v-else-if="form.destinationType === 'PRIVATE'" label="私人地址名称" prop="destinationWarehouseName" required><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="例如:洛杉矶客户自有仓" /></el-form-item>
|
||||
<el-form-item v-else label="目的仓名称"><el-input v-model="form.destinationWarehouseName" readonly placeholder="选择海外仓后自动带出" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="货件编号" prop="shipmentId" required :rules="requiredTextRules('请输入 Shipment ID')"><el-input v-model="form.shipmentId" maxlength="64" placeholder="Shipment ID" /></el-form-item>
|
||||
<el-form-item label="跟踪编号" prop="referenceNo" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入 Reference ID') : undefined"><el-input v-model="form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? 'Reference ID' : '选填,贵司内部跟踪编号'" /></el-form-item>
|
||||
</div>
|
||||
@@ -820,6 +931,8 @@ async function searchOrders(model: QueryModel) {
|
||||
.party-panel :deep(.el-alert) { margin-bottom: 14px; }
|
||||
.party-contact-grid, .destination-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; }
|
||||
.destination-fields { margin-top: 16px; }
|
||||
.private-address-toolbar { display: grid; grid-template-columns: minmax(220px, 1fr) auto auto; gap: 10px; align-items: center; margin-bottom: 16px; }
|
||||
.field-guidance { display: block; margin-top: 5px; color: var(--muted); line-height: 1.45; }
|
||||
.cargo-entry { padding: 16px; border: 1px solid var(--border); background: #fbfcfc; }
|
||||
.cargo-entry + .cargo-entry { margin-top: 12px; }
|
||||
.cargo-entry > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
@@ -887,7 +1000,7 @@ async function searchOrders(model: QueryModel) {
|
||||
.order-table-toolbar .el-button { width: 100%; min-height: 40px; }
|
||||
.order-eligibility { flex-wrap: wrap; }
|
||||
.order-eligibility .el-button { width: 100%; min-height: 44px; }
|
||||
.order-form-grid, .order-detail-grid, .party-select-grid, .party-contact-grid, .destination-fields, .cargo-primary-grid, .cargo-secondary-grid { grid-template-columns: 1fr; }
|
||||
.order-form-grid, .order-detail-grid, .party-select-grid, .party-contact-grid, .destination-fields, .private-address-toolbar, .cargo-primary-grid, .cargo-secondary-grid { grid-template-columns: 1fr; }
|
||||
.segmented-options { width: 100%; align-items: stretch; flex-direction: column; }
|
||||
.segmented-options :deep(.el-radio-button__inner) { border: 1px solid var(--border); border-radius: 6px !important; box-shadow: none !important; }
|
||||
.warehouse-options { display: grid; width: 100%; grid-template-columns: 1fr; }
|
||||
|
||||
Reference in New Issue
Block a user