Files
admin-web/src/modules/overseas-warehouse/components/TransportTaskCreateDialog.vue
T
2026-10-20 21:33:46 +08:00

73 lines
6.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { ApiResponseError, getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { listPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
import type { PickupCandidate, PickupPlan } from '@/modules/overseas-master-data'
import { pickupCandidates, truckCandidates, createTransportPickup, createTransportTruck, type TruckCandidate, type TruckPlan, type TransportType } from '../api/transportTasks'
const props = defineProps<{ modelValue: boolean; type: TransportType }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean]; created: [] }>()
const router = useRouter(), pickups = ref<PickupCandidate[]>([]), trucks = ref<TruckCandidate[]>([]), warehouses = ref<PlatformWarehouseResource[]>([])
const selected = ref<PickupCandidate[]>([]), truck = ref(''), warehouse = ref(''), departure = ref(''), end = ref('')
const loading = ref(false), busy = ref(false), error = ref<ApiErrorDetails | null>(null)
const pending = ref<{ type: TransportType; body: PickupPlan | TruckPlan } | null>(null)
let sequence = 0
async function load() {
const current = ++sequence; loading.value = true; error.value = null
try {
if (props.type === 'PICKUP') { const value = await pickupCandidates(); if (current === sequence) pickups.value = value.records }
else { const [orders, directory] = await Promise.all([truckCandidates(), listPlatformWarehouses({ page: 1, size: 200, warehouseType: 'OVERSEAS', status: 'ACTIVE' })]); if (current === sequence) { trucks.value = orders; warehouses.value = directory.records } }
} catch (cause) { if (current === sequence) error.value = getApiErrorDetails(cause, '运输需求加载失败') }
finally { if (current === sequence) loading.value = false }
}
watch(() => props.modelValue, value => { if (value) { selected.value = []; truck.value = ''; warehouse.value = ''; pending.value = null; void load() } else sequence++ })
/** 结果未知时保留相同原请求,禁止重复选择来源另建单。 */
async function save() {
if (busy.value) return
if (!pending.value) {
if (!departure.value || !end.value || Date.parse(end.value) <= Date.parse(departure.value)) { ElMessage.warning('请填写有效的计划发车和预计任务结束时间'); return }
const common = { plannedDepartureAt: departure.value, expectedDriverEndAt: end.value, idempotencyKey: crypto.randomUUID() }
if (props.type === 'PICKUP') {
if (!selected.value.length || new Set(selected.value.map(row => row.warehouseId)).size !== 1) { ElMessage.warning('请选择送往同一个仓库的取货单'); return }
pending.value = { type: 'PICKUP', body: { ...common, sources: selected.value.map(({ customerId, transferNo }) => ({ customerId, transferNo })) } }
} else {
const order = trucks.value.find(row => `${row.customerId}:${row.orderNo}` === truck.value)
if (!order || !warehouse.value) { ElMessage.warning('请选择本土卡车原单和调度仓库'); return }
pending.value = { type: 'LOCAL_TRUCK', body: { ...common, customerId: order.customerId, orderNo: order.orderNo, warehouseId: warehouse.value } }
}
}
busy.value = true; error.value = null
try {
const request = pending.value
const detail = request.type === 'PICKUP' ? await createTransportPickup(request.body as PickupPlan) : await createTransportTruck(request.body as TruckPlan)
pending.value = null; emit('created'); emit('update:modelValue', false); ElMessage.success(`已生成排车单 ${detail.dispatchNo}`)
await router.push({ name: 'overseas-dispatch-detail', params: { dispatchNo: detail.dispatchNo } })
} catch (cause) { error.value = getApiErrorDetails(cause, '安排排车未完成'); if (cause instanceof ApiResponseError) pending.value = null }
finally { busy.value = false }
}
</script>
<template>
<AppDialog class="admin-density-dialog" :model-value="modelValue" :title="type === 'PICKUP' ? '安排到仓取货' : '安排本土卡车'" width="1100px" :close-on-click-modal="!busy && !pending" :close-on-press-escape="!busy && !pending" :show-close="!busy && !pending" @update:model-value="emit('update:modelValue', $event)">
<LoadErrorState v-if="error" title="排车安排未完成" :message="error.message" @retry="pending ? save() : load()"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<el-form label-position="top" class="density-form" :disabled="busy || !!pending" v-loading="loading">
<template v-if="type === 'PICKUP'">
<p>审核通过的仓储转运上门取货单自动进入候选;同车货物必须送往同一个海外仓。</p>
<DataTable :data="pickups" row-key="transferNo" :columns="[{ key: 'selection', type: 'selection', width: 45 }, { key: 'transferNo', prop: 'transferNo', label: '转运单号', minWidth: 190 }, { key: 'warehouseName', prop: 'warehouseName', label: '送达仓库', minWidth: 140 }, { key: 'pickupAddress', prop: 'pickupAddress', label: '取货地址', minWidth: 240 }, { key: 'packageCount', prop: 'packageCount', label: '预报箱数', width: 100 }]" :pagination="false" @selection-change="selected = $event as PickupCandidate[]" />
</template>
<template v-else>
<el-form-item label="本土卡车原单" required><el-select v-model="truck" filterable><el-option v-for="row in trucks" :key="`${row.customerId}:${row.orderNo}`" :value="`${row.customerId}:${row.orderNo}`" :label="`${row.orderNo} · ${row.origin} → ${row.destination} · ${row.palletCount}托`" /></el-select></el-form-item>
<el-form-item label="调度仓库" required><el-select v-model="warehouse" filterable><el-option v-for="row in warehouses" :key="String(row.id)" :value="String(row.id)" :label="row.warehouseName" /></el-select></el-form-item>
</template>
<el-form-item label="计划发车时间" required><el-date-picker v-model="departure" type="datetime" value-format="YYYY-MM-DDTHH:mm:ssZ" /></el-form-item>
<el-form-item label="预计司机任务结束时间" required><el-date-picker v-model="end" type="datetime" value-format="YYYY-MM-DDTHH:mm:ssZ" /></el-form-item>
</el-form>
<template #footer><el-button v-if="!pending" :disabled="busy" @click="emit('update:modelValue', false)">取消</el-button><el-button type="primary" :loading="busy" @click="save">{{ pending ? '按原请求重试' : '生成排车单' }}</el-button></template>
</AppDialog>
</template>