From bbb408d7b1ee7d50ee68663f0b32b9276fdaa5df Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 8 Aug 2026 15:25:52 +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 --- .../ocean-bookings/pages/OceanBookingDetailView.test.ts | 2 +- .../ocean-bookings/pages/OceanBookingDetailView.vue | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts b/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts index 608c5a2c..53170334 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts +++ b/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts @@ -17,6 +17,6 @@ describe('OceanBookingDetailView form contract', () => { expect(source).toContain("bookingAgentName: [{ required: true") expect(source).toContain(':disabled="!isNew"') expect(source).toContain(':disabled="!editable"') - expect(source).toContain("containerType: '40HC', quantity: 1") + expect(source).toContain("containerType: '40HC', containerKind: 'FCL', quantity: 1") }) }) diff --git a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue index 00e256ff..d64fa38d 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue +++ b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue @@ -15,7 +15,7 @@ const id = computed(() => String(route.params.bookingId || '')); const isNew = c const detail = ref(null); const loading = ref(false); const saving = ref(false) const error = ref(null); const actionError = ref(null); const formRef = ref() const blankParty = (partyType: string): OceanBookingParty => ({ partyType, companyName: '', contactName: '', phone: '', email: '', address: '' }) -const blankData = (): OceanBookingDraftData => ({ customerId: '', customerName: '', orderNo: '', parties: [blankParty('SHIPPER'), blankParty('CONSIGNEE'), blankParty('NOTIFY_PARTY')], containerRequirements: [{ containerType: '40HC', quantity: 1 }], shippingCompanyName: '', bookingAgentName: '', departurePortName: '', destinationPortName: '', vesselName: '', voyageNo: '', estimatedSailingAt: '', estimatedArrivalAt: '', cutoffAt: '', soNo: '', goodsDescription: '', packageCount: null, grossWeight: '', volume: '', remark: '' }) +const blankData = (): OceanBookingDraftData => ({ customerId: '', customerName: '', orderNo: '', parties: [blankParty('SHIPPER'), blankParty('CONSIGNEE'), blankParty('NOTIFY_PARTY')], containerRequirements: [{ containerType: '40HC', containerKind: 'FCL', quantity: 1 }], shippingCompanyName: '', bookingAgentName: '', departurePortName: '', destinationPortName: '', vesselName: '', voyageNo: '', estimatedSailingAt: '', estimatedArrivalAt: '', cutoffAt: '', soNo: '', goodsDescription: '', packageCount: null, grossWeight: '', volume: '', remark: '' }) const form = reactive({ expectedVersion: null, data: blankData() }) const rules: FormRules = { customerId: [{ required: true, message: '请输入客户 ID', trigger: 'blur' }], customerName: [{ required: true, message: '请输入委托单位', trigger: 'blur' }], departurePortName: [{ required: true, message: '请输入起运港 POL', trigger: 'blur' }], destinationPortName: [{ required: true, message: '请输入目的港 POD', trigger: 'blur' }], shippingCompanyName: [{ required: true, message: '请输入船公司', trigger: 'blur' }], bookingAgentName: [{ required: true, message: '请输入订舱代理', trigger: 'blur' }], vesselName: [{ required: true, message: '请输入船名', trigger: 'blur' }], voyageNo: [{ required: true, message: '请输入航次', trigger: 'blur' }], estimatedSailingAt: [{ type: 'string', message: '请输入有效日期', trigger: 'change' }], packageCount: [{ type: 'number', min: 0, message: '件数不能小于 0', trigger: 'change' }] } const actionStatuses: OceanBookingStatus[] = ['SUBMITTED', 'CONFIRMED', 'RELEASED', 'FAILED', 'CHANGE_PENDING', 'CANCELLED'] @@ -23,11 +23,11 @@ const actionByStatus: Partial> = const terminal = computed(() => ['RELEASED', 'CANCELLED', 'FAILED'].includes(detail.value?.status || '')) const editable = computed(() => isNew.value || (!terminal.value && access.can('platform:firstleg:ocean-booking:edit'))) const party = (type: string) => form.data.parties.find((item) => item.partyType === type) || form.data.parties[0] -const containerValid = computed(() => form.data.containerRequirements.length > 0 && form.data.containerRequirements.every((item) => item.containerType.trim() && Number(item.quantity) > 0)) +const containerValid = computed(() => form.data.containerRequirements.length > 0 && form.data.containerRequirements.every((item) => item.containerType.trim() && (item.containerKind || 'FCL').trim() && Number(item.quantity) > 0)) function setParty(type: string, value: string) { const target = form.data.parties.find((item) => item.partyType === type); if (target) target.companyName = value } function setDate(key: 'estimatedSailingAt' | 'estimatedArrivalAt' | 'cutoffAt', value: string | null) { form.data[key] = value || null } -async function load() { if (!id.value || isNew.value) return; loading.value = true; error.value = null; try { detail.value = await getOceanBooking(id.value); const source = detail.value.oceanBooking || detail.value; form.expectedVersion = Number((source as OceanBookingDetail & { expectedVersion?: number }).expectedVersion ?? 0); form.data = { ...blankData(), ...(source as unknown as Partial), customerId: String(source.customerId || ''), customerName: source.customerName || '', parties: detail.value.parties?.length ? detail.value.parties : blankData().parties, containerRequirements: detail.value.containerRequirements?.length ? detail.value.containerRequirements : blankData().containerRequirements } } catch (e) { error.value = getApiErrorDetails(e, '订舱详情加载失败,请稍后重试') } finally { loading.value = false } } -async function save() { if (!editable.value) return; const valid = await formRef.value?.validate().catch(() => false); if (!valid || !containerValid.value) { if (!containerValid.value) ElMessage.error('请至少填写一条有效的箱型箱量'); return } saving.value = true; actionError.value = null; try { if (isNew.value) { const created = await createOceanBooking(form); await router.replace(`/ocean-bookings/${created}`) } else { await updateOceanBooking(id.value, form); ElMessage.success('订舱已保存'); await load() } } catch (e) { actionError.value = getApiErrorDetails(e, '保存订舱失败,请稍后重试') } finally { saving.value = false } } +async function load() { if (!id.value || isNew.value) return; loading.value = true; error.value = null; try { detail.value = await getOceanBooking(id.value); const source = (detail.value.oceanBooking || detail.value) as OceanBookingDetail & { estimatedSailingAt?: string | null; estimatedArrivalAt?: string | null }; form.expectedVersion = Number((source as OceanBookingDetail & { expectedVersion?: number }).expectedVersion ?? 0); const parties = (detail.value.parties || []).map((item) => { const raw = item as OceanBookingParty & { partyName?: string | null; contactPhone?: string | null }; return { ...item, companyName: raw.companyName || raw.partyName || '', phone: raw.phone || raw.contactPhone || '' } }); form.data = { ...blankData(), ...(source as unknown as Partial), customerId: String(source.customerId || ''), customerName: source.customerName || '', estimatedSailingAt: source.estimatedSailingAt ? String(source.estimatedSailingAt).slice(0, 10) : '', estimatedArrivalAt: source.estimatedArrivalAt ? String(source.estimatedArrivalAt).slice(0, 10) : '', parties: parties.length ? parties : blankData().parties, containerRequirements: detail.value.containerRequirements?.length ? detail.value.containerRequirements : blankData().containerRequirements } } catch (e) { error.value = getApiErrorDetails(e, '订舱详情加载失败,请稍后重试') } finally { loading.value = false } } +async function save() { if (!editable.value) return; const valid = await formRef.value?.validate().catch(() => false); const partiesValid = form.data.parties.length >= 3 && ['SHIPPER', 'CONSIGNEE', 'NOTIFY_PARTY'].every((type) => Boolean(party(type)?.companyName.trim())); if (!valid || !containerValid.value || !partiesValid) { if (!partiesValid) ElMessage.error('请完整填写托运人、收货人和通知人'); else if (!containerValid.value) ElMessage.error('请至少填写一条有效的箱型箱量'); return } saving.value = true; actionError.value = null; try { const payload = { expectedVersion: form.expectedVersion, data: { customerId: Number(form.data.customerId), customerName: form.data.customerName, shippingCompanyId: form.data.shippingCompanyId ? Number(form.data.shippingCompanyId) : null, shippingCompanyName: form.data.shippingCompanyName || null, bookingAgentId: form.data.bookingAgentId ? Number(form.data.bookingAgentId) : null, bookingAgentName: form.data.bookingAgentName || null, departurePortCode: form.data.departurePortCode || null, departurePortName: form.data.departurePortName || null, destinationPortCode: form.data.destinationPortCode || null, destinationPortName: form.data.destinationPortName || null, vesselName: form.data.vesselName || null, voyageNo: form.data.voyageNo || null, estimatedSailingAt: form.data.estimatedSailingAt ? `${String(form.data.estimatedSailingAt).slice(0, 10)}T00:00:00` : null, estimatedArrivalAt: form.data.estimatedArrivalAt ? `${String(form.data.estimatedArrivalAt).slice(0, 10)}T00:00:00` : null, remark: form.data.remark || null, parties: form.data.parties.map((item) => ({ partyType: item.partyType, partyName: item.companyName, address: item.address || null, contactName: item.contactName || null, contactPhone: item.phone || null })), containerRequirements: form.data.containerRequirements.map((item) => ({ containerType: item.containerType, containerKind: item.containerKind || 'FCL', quantity: Number(item.quantity) })) } } as unknown as OceanBookingSaveRequest; if (isNew.value) { const created = await createOceanBooking(payload); await router.replace(`/ocean-bookings/${created}`) } else { await updateOceanBooking(id.value, payload); ElMessage.success('订舱已保存'); await load() } } catch (e) { actionError.value = getApiErrorDetails(e, '保存订舱失败,请稍后重试') } finally { saving.value = false } } async function action(status: OceanBookingStatus) { const actionName = actionByStatus[status]; if (!actionName || !detail.value || terminal.value) return; actionError.value = null; try { await performOceanBookingAction(id.value, actionName, { expectedVersion: form.expectedVersion, reason: null }); await load() } catch (e) { actionError.value = getApiErrorDetails(e, '状态操作失败,请稍后重试') } } onMounted(load)