优化
This commit is contained in:
@@ -2,15 +2,15 @@ import { describe, expect, it } from 'vitest'
|
||||
import { containerRequirementError, createBlankContainerRequirement } from './containerRequirements'
|
||||
|
||||
describe('ocean booking container requirements', () => {
|
||||
it('does not guess capacity or weight for a new container type', () => {
|
||||
it('uses the fixed platform specification for a new container type', () => {
|
||||
expect(createBlankContainerRequirement()).toEqual({
|
||||
containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1,
|
||||
containerType: '40HC', containerKind: 'FCL', capacityCbm: 76, maxWeightKg: 28000, quantity: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects missing or non-positive capacity, weight and quantity', () => {
|
||||
expect(containerRequirementError([createBlankContainerRequirement()])).toContain('容量、限重和数量必须大于 0')
|
||||
expect(containerRequirementError([{ containerType: 'UNKNOWN', containerKind: 'FCL', capacityCbm: 1, maxWeightKg: 1, quantity: 0 }])).toContain('UNKNOWN')
|
||||
expect(containerRequirementError([{ containerType: 'UNKNOWN', containerKind: 'FCL', capacityCbm: 1, maxWeightKg: 1, quantity: 1 }])).toBeNull()
|
||||
expect(containerRequirementError([{ ...createBlankContainerRequirement(), quantity: 0 }])).toContain('固定规格')
|
||||
expect(containerRequirementError([{ containerType: 'UNKNOWN', containerKind: 'FCL', capacityCbm: 1, maxWeightKg: 1, quantity: 1 }])).toContain('UNKNOWN')
|
||||
expect(containerRequirementError([createBlankContainerRequirement()])).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import type { OceanBookingContainer } from './types'
|
||||
import { applyContainerSpecification, containerSpecification } from '@/shared/domain/containerSpecifications'
|
||||
|
||||
export function createBlankContainerRequirement(): OceanBookingContainer {
|
||||
return { containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1 }
|
||||
return { containerType: '40HC', containerKind: 'FCL', capacityCbm: 76, maxWeightKg: 28000, quantity: 1 }
|
||||
}
|
||||
|
||||
export function selectContainerRequirementType(requirement: OceanBookingContainer, containerType: string) {
|
||||
applyContainerSpecification(requirement, containerType)
|
||||
}
|
||||
|
||||
export function containerRequirementError(requirements: OceanBookingContainer[]) {
|
||||
const invalid = requirements.find((item) => !(Number(item.capacityCbm) > 0) || !(Number(item.maxWeightKg) > 0) || !(Number(item.quantity) > 0))
|
||||
return invalid ? `箱型 ${invalid.containerType || '未命名'} 的容量、限重和数量必须大于 0,请补充后再提交` : null
|
||||
const invalid = requirements.find((item) => {
|
||||
const specification = containerSpecification(item.containerType)
|
||||
return !specification || Number(item.capacityCbm) !== specification.capacityCbm
|
||||
|| Number(item.maxWeightKg) !== specification.maxWeightKg || !(Number(item.quantity) > 0)
|
||||
})
|
||||
return invalid ? `箱型 ${invalid.containerType || '未命名'} 必须使用平台固定规格,请重新选择柜型` : null
|
||||
}
|
||||
|
||||
@@ -89,14 +89,14 @@ describe('OceanBookingDetailView interaction contract', () => {
|
||||
it('uses source-backed free text terms and keeps capacity snapshots behind one specification control', () => {
|
||||
expect(source).not.toContain('serviceTermOptions')
|
||||
expect(source).not.toContain('freightTermOptions')
|
||||
expect(source).toContain("const containerTypeOptions = ['20GP', '40GP', '40HC', '40NR', '45HC']")
|
||||
expect(source).toContain('const containerTypeOptions = CONTAINER_SPECIFICATIONS')
|
||||
expect(source).toContain('label="主要运输条款 Service Term" prop="serviceTerm"')
|
||||
expect(source).toContain('<el-input v-model="form.data.serviceTerm" clearable maxlength="32" placeholder="请输入 Service Term"')
|
||||
expect(source).toContain('label="运费条款 Freight Term" prop="freightTerm"')
|
||||
expect(source).toContain('<el-input v-model="form.data.freightTerm" clearable maxlength="32" placeholder="请输入 Freight Term"')
|
||||
expect(source).toContain('<el-select v-model="container.containerType"')
|
||||
expect(source).toContain('<el-select :model-value="container.containerType"')
|
||||
expect(source).toContain('<el-select v-model="container.containerKind"')
|
||||
expect(source).toContain(':aria-label="`设置箱型 ${index + 1} 的容量与限重`"')
|
||||
expect(source).toContain('规格(必填)')
|
||||
expect(source).toContain('function containerSpecificationLabel(')
|
||||
expect(source).not.toContain("['PREPAID', 'COLLECT']")
|
||||
expect(source).not.toContain('<el-input v-model="container.containerType"')
|
||||
@@ -117,15 +117,15 @@ describe('OceanBookingDetailView interaction contract', () => {
|
||||
expect(source).toContain('form.data.warehouseName = warehouses.value.find')
|
||||
expect(source).toContain('@update:model-value="selectWarehouse"')
|
||||
expect(source).not.toContain('@update:model-value="(value) =>')
|
||||
expect(source).toContain('v-model="container.capacityCbm"')
|
||||
expect(source).toContain('v-model="container.maxWeightKg"')
|
||||
expect(source).toContain('selectContainerRequirementType(container')
|
||||
expect(source).not.toContain('v-model="container.capacityCbm"')
|
||||
expect(source).not.toContain('v-model="container.maxWeightKg"')
|
||||
expect(source).toContain('releaseOceanBooking(id.value, { expectedVersion: detail.value.version')
|
||||
expect(source).toContain('title="确认放舱"')
|
||||
expect(source).toContain('createBlankContainerRequirement()')
|
||||
expect(source).toContain('containerRequirementError(form.data.containerRequirements)')
|
||||
expect(source).toContain('function validateContainerRequirements()')
|
||||
expect(source).not.toContain('capacityCbm: 68')
|
||||
expect(source).not.toContain('maxWeightKg: 28000')
|
||||
expect(source).toContain('containerSpecificationLabel(container)')
|
||||
})
|
||||
|
||||
it('keeps every container requirement removable without collapsing its fields', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, Plus, Save, SlidersHorizontal, Trash2 } from '@lucide/vue'
|
||||
import { ArrowLeft, Plus, Save, Trash2 } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { getApiErrorDetails } from '@/shared/api/http'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
@@ -12,6 +12,7 @@ import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import PageToolbar from '@/shared/components/PageToolbar.vue'
|
||||
import { CONTAINER_SPECIFICATIONS } from '@/shared/domain/containerSpecifications'
|
||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
||||
import { listSuppliers, type Supplier } from '@/modules/suppliers'
|
||||
@@ -34,14 +35,14 @@ import type {
|
||||
OceanBookingStatus,
|
||||
} from '../types'
|
||||
import { oceanBookingStatusLabel } from '../status'
|
||||
import { containerRequirementError, createBlankContainerRequirement } from '../containerRequirements'
|
||||
import { containerRequirementError, createBlankContainerRequirement, selectContainerRequirementType } from '../containerRequirements'
|
||||
|
||||
const partyTypes = [
|
||||
{ value: 'SHIPPER', label: '发货人' },
|
||||
{ value: 'CONSIGNEE', label: '收货人' },
|
||||
{ value: 'NOTIFY_PARTY', label: '通知人' },
|
||||
] as const
|
||||
const containerTypeOptions = ['20GP', '40GP', '40HC', '40NR', '45HC']
|
||||
const containerTypeOptions = CONTAINER_SPECIFICATIONS
|
||||
const containerKindOptions = [
|
||||
{ value: 'FCL', label: '整箱 FCL' },
|
||||
{ value: 'LCL', label: '拼箱 LCL' },
|
||||
@@ -338,7 +339,6 @@ function validateContainerRequirements() {
|
||||
return true
|
||||
}
|
||||
function containerSpecificationLabel(container: OceanBookingDraftData['containerRequirements'][number]) {
|
||||
if (!(Number(container.capacityCbm) > 0) || !(Number(container.maxWeightKg) > 0)) return '设置规格'
|
||||
return `${container.capacityCbm} m³ / ${container.maxWeightKg} kg`
|
||||
}
|
||||
function addContainer() { form.data.containerRequirements.push(createBlankContainerRequirement()) }
|
||||
@@ -414,18 +414,9 @@ onMounted(() => { void load(); void loadOptions() })
|
||||
</el-tooltip>
|
||||
<span v-else aria-hidden="true">—</span>
|
||||
</td>
|
||||
<td><el-select v-model="container.containerType" :disabled="!editable" placeholder="请选择箱型"><el-option v-for="value in containerTypeOptions" :key="value" :label="value" :value="value" /></el-select></td>
|
||||
<td><el-select :model-value="container.containerType" :disabled="!editable" placeholder="请选择箱型" @update:model-value="selectContainerRequirementType(container, String($event || ''))"><el-option v-for="item in containerTypeOptions" :key="item.type" :label="item.type" :value="item.type" /></el-select></td>
|
||||
<td><el-select v-model="container.containerKind" :disabled="!editable" placeholder="请选择箱种"><el-option v-for="item in containerKindOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></td>
|
||||
<td class="container-table__specification">
|
||||
<el-popover v-if="editable" placement="bottom" :width="300" trigger="click">
|
||||
<template #reference><el-button size="small" plain :type="Number(container.capacityCbm) > 0 && Number(container.maxWeightKg) > 0 ? 'default' : 'warning'" :aria-label="`设置箱型 ${index + 1} 的容量与限重`"><SlidersHorizontal :size="14" />{{ containerSpecificationLabel(container) }}</el-button></template>
|
||||
<div class="container-specification-fields">
|
||||
<label><span>容量(m³)</span><el-input-number v-model="container.capacityCbm" :min="0.01" :precision="2" :controls="false" placeholder="请输入容量" /></label>
|
||||
<label><span>限重(kg)</span><el-input-number v-model="container.maxWeightKg" :min="0.01" :precision="2" :controls="false" placeholder="请输入限重" /></label>
|
||||
</div>
|
||||
</el-popover>
|
||||
<span v-else>{{ containerSpecificationLabel(container) }}</span>
|
||||
</td>
|
||||
<td class="container-table__specification"><span>{{ containerSpecificationLabel(container) }}</span></td>
|
||||
<td><el-input-number v-model="container.quantity" :disabled="!editable" :min="1" :max="999" :controls="false" placeholder="请输入数量" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user