114 lines
7.5 KiB
Vue
114 lines
7.5 KiB
Vue
<script setup lang="ts">
|
||
import '@/styles/adminDensity.css'
|
||
import { computed, reactive, ref } from 'vue'
|
||
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, DEFAULT_CONTAINER_TYPE, applyContainerSpecification, containerSpecification } from '@/shared/domain/containerSpecifications'
|
||
import { createContainerSimulation } from '../api/platformContainerLoading'
|
||
import type { ContainerSimulationCreateRequest } from '../types'
|
||
import { SMART_LOADING_CHANNEL_GROUPS } from '../smartLoadingChannelGroups'
|
||
|
||
const props = withDefaults(defineProps<{
|
||
loadWarehouseOptions?: () => Promise<PlatformWarehouseSummaryResource[]>
|
||
successMessage?: string
|
||
dense?: boolean
|
||
}>(), { 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>()
|
||
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)))
|
||
})
|
||
const simulationRules: FormRules<ContainerSimulationCreateRequest> = {
|
||
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
||
channelGroupCode: [{ required: true, message: '请选择物流渠道', trigger: 'change' }],
|
||
containerType: [{ required: true, message: '请选择柜型', trigger: 'change' }],
|
||
}
|
||
|
||
async function openSimulation() {
|
||
Object.assign(simulationForm, { channelGroupCode: '', warehouseId: '', containerType: DEFAULT_CONTAINER_TYPE, capacityCbm: 68, maxWeightKg: 19500 })
|
||
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 = ''
|
||
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 :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" />
|
||
<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>
|
||
<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>
|
||
</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>
|