This commit is contained in:
2026-08-15 11:06:04 +08:00
parent ebbb4bc459
commit fb165b6347
9 changed files with 45 additions and 30 deletions
@@ -7,19 +7,22 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/trucking/compone
describe('TruckAppointmentForm interaction contract', () => {
it('uses searchable business selectors for shared resources', () => {
expect(source).toContain('listPlatformTenants')
expect(source).toContain('listPlatformPartners')
expect(source).toContain('listSuppliers')
expect(source).toContain('listContainers')
expect(source).toContain('listOceanBookings')
expect(source).toContain('listAllPlatformWarehouses')
expect(source).not.toContain('label="承运商 ID"')
expect(source).not.toContain('label="客户关联 ID"')
expect(source).toContain("listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' })")
expect(source).toContain("listSuppliers({ page: 1, size: 200, category: 'TRUCKING', status: 'ACTIVE' })")
expect(source).toContain('function customerLabel(customer: PlatformTenantResource) { return customer.tenantName }')
expect(source).not.toContain('customer.tenantCode')
})
it('synchronizes ids and names and recovers failed option loading', () => {
expect(source).toContain('selectCarrier')
expect(source).toContain("item.category === 'TRUCKING' && item.status === 'ACTIVE'")
expect(source).toContain('请选择有效的合作中拖车供应商')
expect(source).toContain('draft.carrierName')
expect(source).toContain('selectCustomer')
expect(source).toContain('draft.customerName')
@@ -29,7 +32,7 @@ describe('TruckAppointmentForm interaction contract', () => {
})
it('hides inactive options while preserving resources already bound to an appointment', () => {
expect(source).toContain("item.status === 'ACTIVE' || String(item.id) === String(draft.customerId)")
expect(source).toContain("item.status === 'ACTIVE'")
expect(source).toContain("item.status === 'ACTIVE' || String(item.id) === String(draft.warehouseId)")
expect(source).toContain('v-for="item in selectableCustomers"')
expect(source).toContain('v-for="item in selectableWarehouses"')
@@ -4,7 +4,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import { Eye, Save } from '@lucide/vue'
import type { SaveTruckAppointmentRequest, TruckAppointmentResource } from '../types'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
import { listSuppliers, type Supplier } from '@/modules/suppliers'
import { listContainers, type ContainerBookingResource } from '@/modules/container-loading'
import { listOceanBookings, type OceanBookingResource } from '@/modules/ocean-bookings'
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
@@ -27,7 +27,7 @@ const phoneRule = (label: string) => ({ validator: (_rule: unknown, value: unkno
else callback(new Error(`${label}格式不正确`))
}, trigger: 'blur' })
const rules: FormRules = {
carrierId: [{ required: true, message: '请选择承运商', trigger: 'change' }],
carrierId: [{ required: true, message: '请选择承运商', trigger: 'change' }, { validator: (_rule, value, callback) => callback(selectableCarriers.value.some((item) => String(item.id) === String(value)) ? undefined : new Error('请选择有效的合作中拖车供应商')), trigger: 'change' }],
plannedLoadingAt: [{ required: true, message: '请选择预计装箱时间', trigger: 'change' }],
fleetPhone: [phoneRule('车队电话')], driverPhone: [phoneRule('司机电话')], storePhone: [phoneRule('门店电话')],
packageCount: [nonNegative('件数', true)], palletCount: [nonNegative('托盘数量', true)], transitMinutes: [nonNegative('在途分钟', true)],
@@ -36,7 +36,7 @@ const rules: FormRules = {
}
const relationText = computed(() => [props.resource?.customerName, props.resource?.outboundOrderId, props.resource?.containerBookingId].filter(Boolean).join(' / ') || '由服务端关联资源返回')
const customers = ref<PlatformTenantResource[]>([])
const carriers = ref<PlatformPartnerResource[]>([])
const carriers = ref<Supplier[]>([])
const containers = ref<ContainerBookingResource[]>([])
const bookings = ref<OceanBookingResource[]>([])
const warehouses = ref<PlatformWarehouseSummaryResource[]>([])
@@ -49,24 +49,24 @@ const selectableWarehouses = computed(() => warehouses.value.filter((item) => (
item.status === 'ACTIVE' || String(item.id) === String(draft.warehouseId)
)))
const selectableCarriers = computed(() => carriers.value.filter((item) => (
item.status === 'ACTIVE' || String(item.id) === String(draft.carrierId)
item.status === 'ACTIVE'
)))
function customerLabel(customer: PlatformTenantResource) { return customer.tenantName }
function selectCustomer(customerId: string) { const customer = customers.value.find((item) => String(item.id) === customerId); draft.customerId = customerId || null; draft.customerName = customer ? customerLabel(customer) : null }
function selectCarrier(carrierId: string) { draft.carrierId = carrierId || null; draft.carrierName = carriers.value.find((item) => String(item.id) === carrierId)?.partnerName || null }
function selectCarrier(carrierId: string) { const carrier = selectableCarriers.value.find((item) => String(item.id) === carrierId); draft.carrierId = carrier?.id || null; draft.carrierName = carrier?.name || null }
function selectContainer(containerId: string) { const item = containers.value.find((entry) => entry.id === containerId); draft.containerBookingId = containerId || null; if (item) { draft.loadingPlanId = item.planId; draft.warehouseId = item.warehouseId } }
function selectBooking(bookingId: string) { const item = bookings.value.find((entry) => entry.id === bookingId); draft.oceanBookingId = bookingId || null; if (item?.customerId) selectCustomer(item.customerId) }
async function loadOptions() {
optionsLoading.value = true; optionsError.value = ''
const results = await Promise.allSettled([
listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' }),
listPlatformPartners({ page: 1, size: 200, type: 'CARRIER', status: 'ACTIVE' }),
listSuppliers({ page: 1, size: 200, category: 'TRUCKING', status: 'ACTIVE' }),
listContainers({ page: 1, size: 200 }),
listOceanBookings({ page: 1, size: 200 }),
listAllPlatformWarehouses(),
])
if (results[0].status === 'fulfilled') customers.value = results[0].value.records.filter((customer) => customer.status === 'ACTIVE')
if (results[1].status === 'fulfilled') carriers.value = results[1].value.records
if (results[1].status === 'fulfilled') carriers.value = results[1].value.records.filter((item) => item.category === 'TRUCKING' && item.status === 'ACTIVE')
if (results[2].status === 'fulfilled') containers.value = results[2].value.records
if (results[3].status === 'fulfilled') bookings.value = results[3].value.records
if (results[4].status === 'fulfilled') warehouses.value = results[4].value
@@ -93,7 +93,7 @@ watch(() => props.readonly, (readonly) => { if (!readonly && !customers.value.le
<el-col :xs="24" :sm="12"><el-form-item label="仓库"><el-select v-model="draft.warehouseId" filterable clearable placeholder="请选择仓库" :loading="optionsLoading" style="width:100%"><el-option v-for="item in selectableWarehouses" :key="String(item.id)" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col v-if="resource" :xs="24" :sm="12"><el-form-item label="派车单号"><el-input :model-value="resource.appointmentNo || ''" disabled /></el-form-item></el-col>
<el-col :xs="24"><el-divider content-position="left">运输与时间计划</el-divider></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="承运商" prop="carrierId"><el-select :model-value="draft.carrierId" filterable placeholder="请选择承运商" :loading="optionsLoading" style="width:100%" @update:model-value="selectCarrier(String($event || ''))"><el-option v-if="draft.carrierId && !selectableCarriers.some((item) => String(item.id) === String(draft.carrierId))" :label="draft.carrierName || '当前承运商'" :value="String(draft.carrierId)" /><el-option v-for="item in selectableCarriers" :key="String(item.id)" :label="item.partnerName" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="承运商" prop="carrierId"><el-select :model-value="draft.carrierId" filterable clearable placeholder="请选择承运商" :loading="optionsLoading" no-data-text="暂无可用拖车供应商" style="width:100%" @update:model-value="selectCarrier(String($event || ''))"><el-option v-if="draft.carrierId && !selectableCarriers.some((item) => String(item.id) === String(draft.carrierId))" :label="draft.carrierName || '当前承运商(需重新选择)'" :value="String(draft.carrierId)" disabled /><el-option v-for="item in selectableCarriers" :key="String(item.id)" :label="item.name" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="车队联系人"><el-input v-model="draft.fleetContact" placeholder="请输入车队联系人" /></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="车队电话" prop="fleetPhone"><el-input v-model="draft.fleetPhone" placeholder="请输入车队联系电话" maxlength="32" show-word-limit /></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="司机"><el-input v-model="draft.driverName" placeholder="请输入司机姓名" /></el-form-item></el-col>