Files
admin-web/src/modules/container-loading/components/ContainerSimulationCreate.vue
T

109 lines
9.5 KiB
Vue
Raw Normal View History

2026-09-09 15:26:43 +08:00
<script setup lang="ts">
2026-09-14 15:33:35 +08:00
import '@/styles/adminDensity.css'
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'
2026-09-15 10:38:03 +08:00
import { CONTAINER_SPECIFICATIONS, DEFAULT_CONTAINER_TYPE, applyContainerSpecification, containerSpecification } from '@/shared/domain/containerSpecifications'
2026-09-09 15:26:43 +08:00
import { createContainerSimulation } from '../api/platformContainerLoading'
import type { ContainerSimulationCreateRequest } from '../types'
import { SMART_LOADING_CHANNEL_GROUPS } from '../smartLoadingChannelGroups'
2026-09-09 15:26:43 +08:00
const props = withDefaults(defineProps<{
loadWarehouseOptions?: () => Promise<PlatformWarehouseSummaryResource[]>
successMessage?: string
2026-09-14 15:33:35 +08:00
dense?: boolean
2026-09-09 15:26:43 +08:00
}>(), { 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-15 10:38:03 +08:00
const simulationForm = reactive<ContainerSimulationCreateRequest>({ channelGroupCode: '', warehouseId: '', containerType: DEFAULT_CONTAINER_TYPE, capacityCbm: 68, maxWeightKg: 19500 })
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' }],
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-15 10:38:03 +08:00
Object.assign(simulationForm, { channelGroupCode: '', warehouseId: '', containerType: DEFAULT_CONTAINER_TYPE, capacityCbm: 68, maxWeightKg: 19500 })
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 || '')
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>
2026-09-14 15:33:35 +08:00
<AppDialog :class="{ 'admin-density-dialog': dense }" 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" />
<section :class="{ 'density-dialog-section': dense }" data-tone="work"> <el-form ref="simulationFormRef" :model="simulationForm" :rules="simulationRules" :label-position="dense ? 'left' : 'top'" :label-width="dense ? '120px' : undefined" class="simulation-form" :class="{ 'density-form': dense }"><template v-if="dense"><table :class="{ 'density-table': dense, 'density-table--editable': dense }" aria-label="模拟柜资料"><tbody><tr><td><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></td><td><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></td></tr><tr><td><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></td><td><el-form-item label="统一规格"><span>{{ `${containerSpecification(simulationForm.containerType)?.recommendedMinCbm || '—'}–${containerSpecification(simulationForm.containerType)?.capacityCbm || '—'} m³ / 货重 ≤ ${containerSpecification(simulationForm.containerType)?.maxWeightKg || '—'} kg` }}</span><small :class="{ 'density-dialog-note': dense }">{{ containerSpecification(simulationForm.containerType)?.description }}</small></el-form-item></td></tr></tbody></table></template><template v-else><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><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><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)?.recommendedMinCbm || '—'}–${containerSpecification(simulationForm.containerType)?.capacityCbm || '—'} m³ / 货重 ≤ ${containerSpecification(simulationForm.containerType)?.maxWeightKg || '—'} kg`" disabled /><small>{{ containerSpecification(simulationForm.containerType)?.description }}</small></el-form-item></template></el-form></section>
2026-09-09 15:26:43 +08:00
<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>