2026-09-09 15:26:43 +08:00
|
|
|
<script setup lang="ts">
|
2026-09-10 19:25:11 +08:00
|
|
|
import { computed, reactive, ref } from 'vue'
|
2026-09-09 15:26:43 +08:00
|
|
|
import { Plus } from '@lucide/vue'
|
|
|
|
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
|
|
|
|
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
|
|
|
|
|
import { listWarehouseLogisticsChannels, type OceanLogisticsChannel } from '@/modules/ocean-bookings'
|
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
import { CONTAINER_SPECIFICATIONS, applyContainerSpecification, containerSpecification } from '@/shared/domain/containerSpecifications'
|
|
|
|
|
import { createContainerSimulation } from '../api/platformContainerLoading'
|
|
|
|
|
import type { ContainerSimulationCreateRequest } from '../types'
|
2026-09-10 19:25:11 +08:00
|
|
|
import { SMART_LOADING_CHANNEL_GROUPS } from '../smartLoadingChannelGroups'
|
2026-09-09 15:26:43 +08:00
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<{
|
|
|
|
|
loadWarehouseOptions?: () => Promise<PlatformWarehouseSummaryResource[]>
|
|
|
|
|
successMessage?: string
|
|
|
|
|
}>(), { successMessage: '模拟柜已创建,可先排货后关联真实订舱柜' })
|
|
|
|
|
const emit = defineEmits<{ created: [] }>()
|
|
|
|
|
const warehouses = ref<PlatformWarehouseSummaryResource[]>([])
|
|
|
|
|
const warehouseLoading = ref(false)
|
|
|
|
|
let warehousesLoaded = false
|
|
|
|
|
let channelSequence = 0
|
|
|
|
|
const simulationOpen = ref(false)
|
|
|
|
|
const simulationSaving = ref(false)
|
|
|
|
|
const simulationError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const simulationChannels = ref<OceanLogisticsChannel[]>([])
|
|
|
|
|
const simulationChannelsLoading = ref(false)
|
|
|
|
|
const simulationFormRef = ref<FormInstance>()
|
2026-09-10 19:25:11 +08:00
|
|
|
const simulationForm = reactive<ContainerSimulationCreateRequest>({ channelGroupCode: '', warehouseId: '', containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000 })
|
|
|
|
|
const simulationChannelGroups = computed(() => {
|
|
|
|
|
const activeCodes = new Set(simulationChannels.value.map((channel) => channel.channelCode))
|
|
|
|
|
return SMART_LOADING_CHANNEL_GROUPS.filter((group) => group.channelCodes.some((code) => activeCodes.has(code)))
|
|
|
|
|
})
|
2026-09-09 15:26:43 +08:00
|
|
|
const simulationRules: FormRules<ContainerSimulationCreateRequest> = {
|
|
|
|
|
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
2026-09-10 19:25:11 +08:00
|
|
|
channelGroupCode: [{ required: true, message: '请选择物流渠道', trigger: 'change' }],
|
2026-09-09 15:26:43 +08:00
|
|
|
containerType: [{ required: true, message: '请选择柜型', trigger: 'change' }],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openSimulation() {
|
2026-09-10 19:25:11 +08:00
|
|
|
Object.assign(simulationForm, { channelGroupCode: '', warehouseId: '', containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000 })
|
2026-09-09 15:26:43 +08:00
|
|
|
simulationChannels.value = []
|
|
|
|
|
simulationError.value = null
|
|
|
|
|
simulationOpen.value = true
|
|
|
|
|
channelSequence++
|
|
|
|
|
simulationChannelsLoading.value = false
|
|
|
|
|
simulationFormRef.value?.clearValidate()
|
|
|
|
|
if (warehousesLoaded) return
|
|
|
|
|
warehouseLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
warehouses.value = props.loadWarehouseOptions
|
|
|
|
|
? await props.loadWarehouseOptions()
|
|
|
|
|
: (await listAllPlatformWarehouses({ status: 'ACTIVE' })).filter((warehouse) => warehouse.warehouseType === 'DOMESTIC')
|
|
|
|
|
warehousesLoaded = true
|
|
|
|
|
} catch (error) { simulationError.value = getApiErrorDetails(error, '装柜仓库加载失败') }
|
|
|
|
|
finally { warehouseLoading.value = false }
|
|
|
|
|
}
|
|
|
|
|
function selectSimulationContainerType(containerType: string) {
|
|
|
|
|
applyContainerSpecification(simulationForm, containerType)
|
|
|
|
|
}
|
|
|
|
|
async function selectSimulationWarehouse(value: unknown) {
|
|
|
|
|
const sequence = ++channelSequence
|
|
|
|
|
simulationChannelsLoading.value = false
|
|
|
|
|
simulationForm.warehouseId = String(value || '')
|
2026-09-10 19:25:11 +08:00
|
|
|
simulationForm.channelGroupCode = ''
|
2026-09-09 15:26:43 +08:00
|
|
|
simulationChannels.value = []
|
|
|
|
|
simulationError.value = null
|
|
|
|
|
if (!simulationForm.warehouseId) return
|
|
|
|
|
simulationChannelsLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const channels = await listWarehouseLogisticsChannels(simulationForm.warehouseId)
|
|
|
|
|
if (sequence === channelSequence) simulationChannels.value = channels
|
|
|
|
|
}
|
|
|
|
|
catch (error) { if (sequence === channelSequence) simulationError.value = getApiErrorDetails(error, '物流渠道加载失败') }
|
|
|
|
|
finally { if (sequence === channelSequence) simulationChannelsLoading.value = false }
|
|
|
|
|
}
|
|
|
|
|
async function saveSimulation() {
|
|
|
|
|
if (simulationSaving.value || warehouseLoading.value || simulationChannelsLoading.value) return
|
|
|
|
|
if (!(await simulationFormRef.value?.validate().catch(() => false))) return
|
|
|
|
|
simulationSaving.value = true
|
|
|
|
|
simulationError.value = null
|
|
|
|
|
try {
|
|
|
|
|
await createContainerSimulation({ ...simulationForm })
|
|
|
|
|
simulationOpen.value = false
|
|
|
|
|
ElMessage.success(props.successMessage)
|
|
|
|
|
emit('created')
|
|
|
|
|
} catch (error) { simulationError.value = getApiErrorDetails(error, '模拟柜创建失败') }
|
|
|
|
|
finally { simulationSaving.value = false }
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<el-button type="primary" @click="openSimulation"><Plus :size="16" />新增模拟柜</el-button>
|
|
|
|
|
<AppDialog v-model="simulationOpen" title="新增模拟柜" width="min(560px, calc(100vw - 28px))" confirm-text="创建模拟柜" cancel-text="取消" :confirm-loading="simulationSaving" @confirm="saveSimulation">
|
|
|
|
|
<el-alert title="模拟柜用于先排货,服务端会生成稳定的柜次号;此处不填写集装箱号和船期。" type="info" :closable="false" />
|
|
|
|
|
<el-form ref="simulationFormRef" :model="simulationForm" :rules="simulationRules" label-position="top" class="simulation-form">
|
|
|
|
|
<el-form-item label="装柜仓库" prop="warehouseId"><el-select :model-value="simulationForm.warehouseId" filterable placeholder="请选择国内装柜仓库" :loading="warehouseLoading" @update:model-value="selectSimulationWarehouse"><el-option v-for="warehouse in warehouses" :key="String(warehouse.id)" :label="warehouse.warehouseName" :value="String(warehouse.id)" /></el-select></el-form-item>
|
2026-09-10 19:25:11 +08:00
|
|
|
<el-form-item label="物流渠道" prop="channelGroupCode"><el-select v-model="simulationForm.channelGroupCode" filterable :disabled="!simulationForm.warehouseId || simulationChannelsLoading" :loading="simulationChannelsLoading" :placeholder="simulationForm.warehouseId ? '请选择物流渠道' : '请先选择装柜仓库'" no-data-text="当前仓库暂无有效渠道"><el-option v-for="group in simulationChannelGroups" :key="group.code" :label="group.name" :value="group.code" /></el-select></el-form-item>
|
2026-09-09 15:26:43 +08:00
|
|
|
<el-form-item label="柜型" prop="containerType"><el-select :model-value="simulationForm.containerType" @update:model-value="selectSimulationContainerType(String($event || ''))"><el-option v-for="item in CONTAINER_SPECIFICATIONS" :key="item.type" :label="item.type" :value="item.type" /></el-select></el-form-item>
|
|
|
|
|
<el-form-item label="固定规格"><el-input :model-value="`${containerSpecification(simulationForm.containerType)?.capacityCbm || '—'} m³ / ${containerSpecification(simulationForm.containerType)?.maxWeightKg || '—'} kg`" disabled /></el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<LoadErrorState v-if="simulationError" compact title="模拟柜创建失败" :message="simulationError.message" :show-retry="false"><template #details><ApiErrorMeta :details="simulationError" /></template></LoadErrorState>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.simulation-form :deep(.el-select), .simulation-form :deep(.el-input-number) { width: 100%; }
|
|
|
|
|
</style>
|