2026-08-21 11:20:09 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-08-26 15:48:34 +08:00
|
|
|
|
import { computed, onMounted, reactive, ref } from 'vue'
|
2026-09-14 11:05:47 +08:00
|
|
|
|
import { Eye, MoreHorizontal, Pencil, Printer, Trash2, Truck, XCircle } from '@lucide/vue'
|
2026-09-01 14:23:53 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
2026-08-21 18:16:42 +08:00
|
|
|
|
import { useRouter } from 'vue-router'
|
2026-08-21 11:20:09 +08:00
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
|
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
|
|
|
|
|
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
|
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
|
|
|
|
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
2026-09-01 14:23:53 +08:00
|
|
|
|
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
2026-09-14 11:05:47 +08:00
|
|
|
|
import { cancelDispatch, getDispatchBolPdf, listDispatches, removeDispatchItem, transferDispatchToDelivery, updateDispatch, type DispatchItemDto, type DispatchSummaryDto } from '../api/dispatches'
|
2026-08-25 20:57:51 +08:00
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
2026-08-21 11:20:09 +08:00
|
|
|
|
import type { DispatchListItem } from '../types'
|
2026-08-22 12:24:15 +08:00
|
|
|
|
import { formatOverseasDateTime } from '../lastMilePresentation'
|
2026-09-01 14:23:53 +08:00
|
|
|
|
import { overseasWarehousePermissions as permission } from '../permissions'
|
2026-08-21 11:20:09 +08:00
|
|
|
|
|
2026-08-21 18:16:42 +08:00
|
|
|
|
const router = useRouter()
|
2026-09-01 14:23:53 +08:00
|
|
|
|
const access = useAccessPreviewStore()
|
2026-08-21 11:20:09 +08:00
|
|
|
|
const rows = ref<DispatchListItem[]>([])
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const loadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
|
const page = ref(1)
|
2026-08-22 12:24:15 +08:00
|
|
|
|
const pageSize = ref(10)
|
2026-08-21 11:20:09 +08:00
|
|
|
|
const total = ref(0)
|
2026-09-13 21:10:05 +08:00
|
|
|
|
const filters = ref<QueryModel>({
|
|
|
|
|
|
dispatchNo: '',
|
|
|
|
|
|
vehiclePlate: '',
|
|
|
|
|
|
status: '',
|
|
|
|
|
|
fbaAppointmentNo: '',
|
|
|
|
|
|
})
|
|
|
|
|
|
const transferOpen = ref(false)
|
|
|
|
|
|
const transferRow = ref<DispatchListItem | null>(null)
|
|
|
|
|
|
const transferSaving = ref(false)
|
2026-09-01 14:23:53 +08:00
|
|
|
|
const mutationError = ref<ApiErrorDetails | null>(null)
|
2026-09-13 21:10:05 +08:00
|
|
|
|
const editOpen = ref(false)
|
|
|
|
|
|
const editRow = ref<DispatchListItem | null>(null)
|
|
|
|
|
|
const editSaving = ref(false)
|
|
|
|
|
|
const editItems = ref<DispatchItemDto[]>([])
|
|
|
|
|
|
const removingItemId = ref('')
|
2026-09-11 10:09:14 +08:00
|
|
|
|
const bolLoadingNo = ref('')
|
2026-09-01 14:23:53 +08:00
|
|
|
|
const dispatchSnapshots = new Map<string, DispatchSummaryDto>()
|
2026-09-13 21:10:05 +08:00
|
|
|
|
const editForm = reactive({
|
|
|
|
|
|
vehiclePlate: '',
|
|
|
|
|
|
driverName: '',
|
|
|
|
|
|
driverPhone: '',
|
|
|
|
|
|
carrierName: '',
|
|
|
|
|
|
route: '',
|
|
|
|
|
|
departAt: '',
|
|
|
|
|
|
fbaAppointmentNo: '',
|
|
|
|
|
|
fbaAppointmentStatus: '',
|
|
|
|
|
|
vehicleAppointmentStatus: '',
|
|
|
|
|
|
estimatedArrivalAt: '',
|
|
|
|
|
|
})
|
|
|
|
|
|
const transferForm = reactive({
|
|
|
|
|
|
plannedDeliveryAt: '',
|
|
|
|
|
|
deliveryMethod: '',
|
|
|
|
|
|
waybillNo: '',
|
|
|
|
|
|
carrierName: '',
|
|
|
|
|
|
pickupMethod: '',
|
|
|
|
|
|
loadingType: '',
|
|
|
|
|
|
vehicleType: '',
|
|
|
|
|
|
vehicleSize: '',
|
|
|
|
|
|
driverName: '',
|
|
|
|
|
|
driverPhone: '',
|
|
|
|
|
|
vehiclePlate: '',
|
|
|
|
|
|
appointmentAccount: '',
|
|
|
|
|
|
sellerName: '',
|
|
|
|
|
|
deliveryFee: undefined as number | undefined,
|
|
|
|
|
|
loadingFee: undefined as number | undefined,
|
|
|
|
|
|
appointmentFee: undefined as number | undefined,
|
2026-09-14 11:05:47 +08:00
|
|
|
|
cartonMarksJson: '',
|
2026-09-13 21:10:05 +08:00
|
|
|
|
remark: '',
|
|
|
|
|
|
})
|
2026-08-26 15:48:34 +08:00
|
|
|
|
|
|
|
|
|
|
const deliveryMethodOptions = [
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{ label: 'FBA自�', value: 'FBA_PICKUP' },
|
|
|
|
|
|
{ label: '客户自�', value: 'CUSTOMER_PICKUP' },
|
|
|
|
|
|
{ label: '�派打托', value: 'TRUCK_PALLET' },
|
|
|
|
|
|
{ label: '�派地�', value: 'TRUCK_FLOOR' },
|
|
|
|
|
|
{ label: '快递', value: 'EXPRESS' },
|
|
|
|
|
|
{ label: '外�', value: 'OUTSOURCED' },
|
|
|
|
|
|
{ label: '专车直�', value: 'DEDICATED_TRUCK' },
|
|
|
|
|
|
{ label: '零担派�', value: 'LTL' },
|
2026-08-26 15:48:34 +08:00
|
|
|
|
]
|
|
|
|
|
|
const pickupMethodOptions = [
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{ label: '一�一�', value: 'ONE_PICKUP_ONE_DROP' },
|
|
|
|
|
|
{ label: '一�多�', value: 'ONE_PICKUP_MULTI_DROP' },
|
|
|
|
|
|
{ label: '多�一�', value: 'MULTI_PICKUP_ONE_DROP' },
|
|
|
|
|
|
{ label: '多�多�', value: 'MULTI_PICKUP_MULTI_DROP' },
|
2026-08-26 15:48:34 +08:00
|
|
|
|
{ label: '仓库自�', value: 'WAREHOUSE_PICKUP' },
|
|
|
|
|
|
]
|
2026-09-13 21:10:05 +08:00
|
|
|
|
const loadingTypeOptions = [
|
|
|
|
|
|
{ label: '托盘', value: 'PALLET' },
|
|
|
|
|
|
{ label: '地�', value: 'FLOOR' },
|
|
|
|
|
|
{ label: '混装', value: 'MIXED' },
|
|
|
|
|
|
]
|
2026-08-26 15:48:34 +08:00
|
|
|
|
const vehicleTypeOptions = ['厢�货车', '平�车', '冷�车', '集装箱�车', '尾�车'].map((label) => ({ label, value: label }))
|
|
|
|
|
|
const vehicleSizeOptions = ['53尺大车(30托)', '26å°ºä¸è½¦ï¼ˆ14托)', '16å°ºå°�车(8托)', 'Cargo Van(4托)'].map((label) => ({ label, value: label }))
|
|
|
|
|
|
const appointmentAccountOptions = [
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{ label: '4�账�(美西)', value: '4' },
|
|
|
|
|
|
{ label: '8�账�(美东)', value: '8' },
|
|
|
|
|
|
{ label: '10å�·è´¦å�·ï¼ˆç¾Žä¸ï¼‰', value: '10' },
|
|
|
|
|
|
{ label: '12å�·è´¦å�·ï¼ˆåŠ æ‹¿å¤§ï¼‰', value: '12' },
|
|
|
|
|
|
{ label: '买约', value: 'BUY_APPOINTMENT' },
|
2026-08-26 15:48:34 +08:00
|
|
|
|
]
|
|
|
|
|
|
const isBuyAppointment = computed(() => transferForm.appointmentAccount === 'BUY_APPOINTMENT')
|
|
|
|
|
|
const needsLoadingFee = computed(() => ['FLOOR', 'MIXED'].includes(transferForm.loadingType))
|
2026-08-21 11:20:09 +08:00
|
|
|
|
|
|
|
|
|
|
const statusOptions = [
|
|
|
|
|
|
{ label: '待�车', value: 'PENDING_DEPARTURE' },
|
|
|
|
|
|
{ label: '拣货ä¸', value: 'PICKING' },
|
|
|
|
|
|
{ label: '待�车(拣货完�)', value: 'READY_TO_DEPART' },
|
|
|
|
|
|
{ label: '待�车(部分完�)', value: 'PARTIAL_READY_TO_DEPART' },
|
|
|
|
|
|
{ label: '已�车', value: 'DEPARTED' },
|
|
|
|
|
|
{ label: '已�车(部分完�)', value: 'PARTIAL_DEPARTED' },
|
|
|
|
|
|
{ label: '已�消', value: 'CANCELLED' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const fields = computed<QueryField[]>(() => [
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'dispatchNo',
|
|
|
|
|
|
type: 'input',
|
|
|
|
|
|
label: '排车��',
|
|
|
|
|
|
placeholder: '请输入排车��',
|
|
|
|
|
|
width: 220,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'vehiclePlate',
|
|
|
|
|
|
type: 'input',
|
|
|
|
|
|
label: '车牌�',
|
|
|
|
|
|
placeholder: '请输入车牌�',
|
|
|
|
|
|
width: 180,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'status',
|
|
|
|
|
|
type: 'select',
|
|
|
|
|
|
label: '车辆状�',
|
|
|
|
|
|
placeholder: '全部状�',
|
|
|
|
|
|
options: statusOptions,
|
|
|
|
|
|
width: 180,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'fbaAppointmentNo',
|
|
|
|
|
|
type: 'input',
|
|
|
|
|
|
label: 'FBA预约�',
|
|
|
|
|
|
placeholder: '请输入FBA预约�',
|
|
|
|
|
|
width: 220,
|
|
|
|
|
|
},
|
2026-08-21 11:20:09 +08:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const columns: DataTableColumn[] = [
|
2026-09-10 21:22:06 +08:00
|
|
|
|
{ key: 'actions', label: '�作', width: 380, fixed: 'left', slotName: 'actions' },
|
2026-08-21 11:20:09 +08:00
|
|
|
|
{ key: 'dispatchNo', prop: 'dispatchNo', label: '排车��', minWidth: 170, fixed: 'left' },
|
|
|
|
|
|
{ key: 'vehiclePlate', prop: 'vehiclePlate', label: '车牌�', minWidth: 130 },
|
|
|
|
|
|
{ key: 'driverName', prop: 'driverName', label: '�机', minWidth: 120 },
|
|
|
|
|
|
{ key: 'driverPhone', prop: 'driverPhone', label: '�系电�', minWidth: 140 },
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'carrierName',
|
|
|
|
|
|
prop: 'carrierName',
|
|
|
|
|
|
label: '托车公��称',
|
|
|
|
|
|
minWidth: 160,
|
|
|
|
|
|
},
|
2026-08-21 11:20:09 +08:00
|
|
|
|
{ key: 'orderNos', prop: 'orderNos', label: '关�订�', minWidth: 220 },
|
|
|
|
|
|
{ key: 'route', prop: 'route', label: '路线', minWidth: 180 },
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'overseasWarehouse',
|
|
|
|
|
|
prop: 'overseasWarehouse',
|
|
|
|
|
|
label: '海外仓',
|
|
|
|
|
|
minWidth: 160,
|
|
|
|
|
|
},
|
2026-08-21 11:20:09 +08:00
|
|
|
|
{ key: 'departAt', prop: 'departAt', label: '�车时间', minWidth: 170 },
|
2026-09-13 21:10:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'fbaAppointment',
|
|
|
|
|
|
prop: 'fbaAppointment',
|
|
|
|
|
|
label: 'FBA预约',
|
|
|
|
|
|
minWidth: 180,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'vehicleAppointment',
|
|
|
|
|
|
prop: 'vehicleAppointment',
|
|
|
|
|
|
label: '车辆预约',
|
|
|
|
|
|
minWidth: 130,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'estimatedArrivalAt',
|
|
|
|
|
|
prop: 'estimatedArrivalAt',
|
|
|
|
|
|
label: '预计到达时间',
|
|
|
|
|
|
minWidth: 170,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'palletCount',
|
|
|
|
|
|
prop: 'palletCount',
|
|
|
|
|
|
label: '托盘数',
|
|
|
|
|
|
width: 90,
|
|
|
|
|
|
align: 'right',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'packageCount',
|
|
|
|
|
|
prop: 'packageCount',
|
|
|
|
|
|
label: '箱数',
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
align: 'right',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'status',
|
|
|
|
|
|
prop: 'status',
|
|
|
|
|
|
label: '车辆状�',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
slotName: 'status',
|
|
|
|
|
|
},
|
2026-08-21 11:20:09 +08:00
|
|
|
|
{ key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 170 },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
function text(value: unknown) {
|
|
|
|
|
|
const normalized = String(value ?? '').trim()
|
|
|
|
|
|
return normalized || '—'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function number(value: unknown) {
|
|
|
|
|
|
const normalized = Number(value)
|
|
|
|
|
|
return Number.isFinite(normalized) ? normalized : 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function statusLabel(value: unknown) {
|
|
|
|
|
|
const status = String(value || '')
|
|
|
|
|
|
return statusOptions.find((option) => option.value === status)?.label || text(value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 17:17:11 +08:00
|
|
|
|
function appointmentStatusLabel(value: unknown) {
|
|
|
|
|
|
const code = String(value || '')
|
|
|
|
|
|
const labels: Record<string, string> = {
|
|
|
|
|
|
PENDING: '待预约',
|
|
|
|
|
|
BOOKED: '已预约',
|
|
|
|
|
|
CONFIRMED: '已确认',
|
|
|
|
|
|
CANCELLED: '已�消',
|
|
|
|
|
|
}
|
|
|
|
|
|
return labels[code] || text(value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 11:20:09 +08:00
|
|
|
|
function appointmentLabel(numberValue: unknown, statusValue: unknown) {
|
|
|
|
|
|
const appointmentNo = text(numberValue)
|
2026-09-03 17:17:11 +08:00
|
|
|
|
const appointmentStatus = appointmentStatusLabel(statusValue)
|
2026-08-21 11:20:09 +08:00
|
|
|
|
if (appointmentNo === '—') return appointmentStatus
|
|
|
|
|
|
return appointmentStatus === '—' ? appointmentNo : `${appointmentNo} · ${appointmentStatus}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toListItem(dto: DispatchSummaryDto): DispatchListItem {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: String(dto.id),
|
|
|
|
|
|
dispatchNo: text(dto.dispatchNo),
|
|
|
|
|
|
vehiclePlate: text(dto.vehiclePlate),
|
|
|
|
|
|
driverName: text(dto.driverName),
|
|
|
|
|
|
driverPhone: text(dto.driverPhone),
|
|
|
|
|
|
carrierName: text(dto.carrierName),
|
|
|
|
|
|
orderNos: text(dto.orderNos),
|
|
|
|
|
|
route: text(dto.route),
|
2026-08-30 17:43:03 +08:00
|
|
|
|
overseasWarehouse: text(dto.overseasWarehouseName || '仓库�称待补充'),
|
2026-08-28 14:33:57 +08:00
|
|
|
|
departAt: formatOverseasDateTime(dto.departAt),
|
2026-08-21 11:20:09 +08:00
|
|
|
|
fbaAppointment: appointmentLabel(dto.fbaAppointmentNo, dto.fbaAppointmentStatus),
|
2026-09-03 17:17:11 +08:00
|
|
|
|
vehicleAppointment: appointmentStatusLabel(dto.vehicleAppointmentStatus),
|
2026-08-28 14:33:57 +08:00
|
|
|
|
estimatedArrivalAt: formatOverseasDateTime(dto.estimatedArrivalAt),
|
2026-08-21 11:20:09 +08:00
|
|
|
|
status: statusLabel(dto.status),
|
2026-09-01 14:23:53 +08:00
|
|
|
|
statusCode: String(dto.status || ''),
|
2026-08-21 11:20:09 +08:00
|
|
|
|
palletCount: number(dto.palletCount),
|
|
|
|
|
|
packageCount: number(dto.packageCount),
|
2026-08-22 12:24:15 +08:00
|
|
|
|
createdAt: formatOverseasDateTime(dto.createdAt),
|
2026-08-21 11:20:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function dispatchError(error: unknown) {
|
|
|
|
|
|
const details = getApiErrorDetails(error, 'æŽ’è½¦åˆ—è¡¨åŠ è½½å¤±è´¥ï¼Œè¯·é‡�试')
|
|
|
|
|
|
return details.code === 'HTTP_404'
|
2026-09-13 21:10:05 +08:00
|
|
|
|
? {
|
|
|
|
|
|
...details,
|
|
|
|
|
|
message: '排车接å�£æœªå�¯ç”¨æˆ–ä¸�å˜åœ¨ï¼Œè¯·ç¡®è®¤æµ·å¤–尾程排车接å�£å·²å�‘布',
|
|
|
|
|
|
}
|
2026-08-21 11:20:09 +08:00
|
|
|
|
: details
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function load(model: QueryModel = filters.value) {
|
|
|
|
|
|
Object.assign(filters.value, model)
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
loadError.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await listDispatches({
|
|
|
|
|
|
page: page.value,
|
|
|
|
|
|
size: pageSize.value,
|
|
|
|
|
|
dispatchNo: String(filters.value.dispatchNo || '').trim() || undefined,
|
|
|
|
|
|
vehiclePlate: String(filters.value.vehiclePlate || '').trim() || undefined,
|
|
|
|
|
|
status: String(filters.value.status || '').trim() || undefined,
|
|
|
|
|
|
fbaAppointmentNo: String(filters.value.fbaAppointmentNo || '').trim() || undefined,
|
|
|
|
|
|
})
|
2026-09-01 14:23:53 +08:00
|
|
|
|
dispatchSnapshots.clear()
|
|
|
|
|
|
result.records.forEach((item) => dispatchSnapshots.set(String(item.dispatchNo), item))
|
2026-08-21 11:20:09 +08:00
|
|
|
|
rows.value = result.records.map(toListItem)
|
|
|
|
|
|
total.value = result.total
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
rows.value = []
|
|
|
|
|
|
total.value = 0
|
|
|
|
|
|
loadError.value = dispatchError(error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 21:10:05 +08:00
|
|
|
|
function search(model: QueryModel) {
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
void load(model)
|
|
|
|
|
|
}
|
|
|
|
|
|
function reset(model: QueryModel) {
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
void load(model)
|
|
|
|
|
|
}
|
|
|
|
|
|
function openDetail(row: DispatchListItem) {
|
|
|
|
|
|
void router.push({
|
|
|
|
|
|
name: 'overseas-dispatch-detail',
|
|
|
|
|
|
params: { dispatchNo: row.dispatchNo },
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-09-10 21:22:06 +08:00
|
|
|
|
async function openBol(row: DispatchListItem) {
|
|
|
|
|
|
if (bolLoadingNo.value) return
|
2026-09-11 10:09:14 +08:00
|
|
|
|
const pdfWindow = window.open('', '_blank')
|
|
|
|
|
|
if (!pdfWindow) { ElMessage.warning('�览器拦截了 BOL PDF 窗�,请�许本站打开新窗�'); return }
|
2026-09-10 21:22:06 +08:00
|
|
|
|
bolLoadingNo.value = row.dispatchNo
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
try {
|
2026-09-11 10:09:14 +08:00
|
|
|
|
const pdf = await getDispatchBolPdf(row.dispatchNo)
|
|
|
|
|
|
const url = URL.createObjectURL(pdf)
|
|
|
|
|
|
pdfWindow.location.href = url
|
|
|
|
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 60_000)
|
2026-09-10 21:22:06 +08:00
|
|
|
|
} catch (cause) {
|
2026-09-11 10:09:14 +08:00
|
|
|
|
pdfWindow.close()
|
|
|
|
|
|
mutationError.value = getApiErrorDetails(cause, 'BOL PDF 生�失败,请�试')
|
2026-09-10 21:22:06 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
bolLoadingNo.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-13 21:10:05 +08:00
|
|
|
|
function isPendingDeparture(row: DispatchListItem) {
|
|
|
|
|
|
return row.statusCode === 'PENDING_DEPARTURE'
|
|
|
|
|
|
}
|
|
|
|
|
|
function canEdit(row: DispatchListItem) {
|
|
|
|
|
|
return access.can(permission.dispatchEdit) && isPendingDeparture(row)
|
|
|
|
|
|
}
|
|
|
|
|
|
function canCancel(row: DispatchListItem) {
|
|
|
|
|
|
return access.can(permission.dispatchCancel) && row.statusCode === 'PENDING_DEPARTURE'
|
|
|
|
|
|
}
|
|
|
|
|
|
function canTransfer(row: DispatchListItem) {
|
|
|
|
|
|
return access.can(permission.dispatchTransferDelivery) && ['PENDING_DEPARTURE', 'PICKING', 'READY_TO_DEPART'].includes(row.statusCode)
|
|
|
|
|
|
}
|
|
|
|
|
|
function openEdit(row: DispatchListItem) {
|
|
|
|
|
|
if (!canEdit(row)) return
|
|
|
|
|
|
const source = dispatchSnapshots.get(row.dispatchNo)
|
|
|
|
|
|
editRow.value = row
|
|
|
|
|
|
editItems.value = [...(source?.items || [])]
|
|
|
|
|
|
Object.assign(editForm, {
|
|
|
|
|
|
vehiclePlate: source?.vehiclePlate || '',
|
|
|
|
|
|
driverName: source?.driverName || '',
|
|
|
|
|
|
driverPhone: source?.driverPhone || '',
|
|
|
|
|
|
carrierName: source?.carrierName || '',
|
|
|
|
|
|
route: source?.route || '',
|
|
|
|
|
|
departAt: source?.departAt || '',
|
|
|
|
|
|
fbaAppointmentNo: source?.fbaAppointmentNo || '',
|
|
|
|
|
|
fbaAppointmentStatus: source?.fbaAppointmentStatus || '',
|
|
|
|
|
|
vehicleAppointmentStatus: source?.vehicleAppointmentStatus || '',
|
|
|
|
|
|
estimatedArrivalAt: source?.estimatedArrivalAt || '',
|
|
|
|
|
|
})
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
editOpen.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
function openTransfer(row: DispatchListItem) {
|
|
|
|
|
|
if (!canTransfer(row)) return
|
|
|
|
|
|
transferRow.value = row
|
|
|
|
|
|
Object.assign(transferForm, {
|
|
|
|
|
|
plannedDeliveryAt: '',
|
|
|
|
|
|
deliveryMethod: '',
|
|
|
|
|
|
waybillNo: '',
|
|
|
|
|
|
carrierName: row.carrierName === '—' ? '' : row.carrierName,
|
|
|
|
|
|
pickupMethod: '',
|
|
|
|
|
|
loadingType: '',
|
|
|
|
|
|
vehicleType: '',
|
|
|
|
|
|
vehicleSize: '',
|
|
|
|
|
|
driverName: row.driverName === '—' ? '' : row.driverName,
|
|
|
|
|
|
driverPhone: row.driverPhone === '—' ? '' : row.driverPhone,
|
|
|
|
|
|
vehiclePlate: row.vehiclePlate === '—' ? '' : row.vehiclePlate,
|
|
|
|
|
|
appointmentAccount: '',
|
|
|
|
|
|
sellerName: '',
|
|
|
|
|
|
deliveryFee: undefined,
|
|
|
|
|
|
loadingFee: undefined,
|
|
|
|
|
|
appointmentFee: undefined,
|
2026-09-14 11:05:47 +08:00
|
|
|
|
cartonMarksJson: '',
|
2026-09-13 21:10:05 +08:00
|
|
|
|
remark: '',
|
|
|
|
|
|
})
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
transferOpen.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
async function submitEdit() {
|
|
|
|
|
|
if (!editRow.value || !canEdit(editRow.value) || editSaving.value) return
|
|
|
|
|
|
editSaving.value = true
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
await updateDispatch(editRow.value.dispatchNo, {
|
|
|
|
|
|
vehiclePlate: editForm.vehiclePlate.trim() || null,
|
|
|
|
|
|
driverName: editForm.driverName.trim() || null,
|
|
|
|
|
|
driverPhone: editForm.driverPhone.trim() || null,
|
|
|
|
|
|
carrierName: editForm.carrierName.trim() || null,
|
|
|
|
|
|
route: editForm.route.trim() || null,
|
|
|
|
|
|
departAt: editForm.departAt || null,
|
|
|
|
|
|
fbaAppointmentNo: editForm.fbaAppointmentNo.trim() || null,
|
|
|
|
|
|
fbaAppointmentStatus: editForm.fbaAppointmentStatus || null,
|
|
|
|
|
|
vehicleAppointmentStatus: editForm.vehicleAppointmentStatus || null,
|
|
|
|
|
|
estimatedArrivalAt: editForm.estimatedArrivalAt || null,
|
|
|
|
|
|
})
|
|
|
|
|
|
editOpen.value = false
|
|
|
|
|
|
ElMessage.success('排车信æ�¯å·²ä¿�å˜')
|
|
|
|
|
|
await load()
|
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
|
mutationError.value = getApiErrorDetails(cause, '排车信æ�¯ä¿�å˜å¤±è´¥ï¼Œè¯·é‡�试')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
editSaving.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
async function removePallet(itemValue: unknown) {
|
|
|
|
|
|
const item = itemValue as DispatchItemDto
|
|
|
|
|
|
if (!editRow.value || removingItemId.value) return
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(`确认从排车�移除托盘 ${text(item.palletNo)}?移除���新排车。`, '移除排车托盘', {
|
|
|
|
|
|
confirmButtonText: '确认移除',
|
|
|
|
|
|
cancelButtonText: '返回',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
removingItemId.value = String(item.id)
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const updated = await removeDispatchItem(editRow.value.dispatchNo, String(item.id))
|
|
|
|
|
|
editItems.value = [...(updated.items || [])]
|
|
|
|
|
|
dispatchSnapshots.set(editRow.value.dispatchNo, updated)
|
|
|
|
|
|
const row = rows.value.find((value) => value.dispatchNo === editRow.value?.dispatchNo)
|
|
|
|
|
|
if (row) {
|
|
|
|
|
|
row.palletCount = number(updated.palletCount)
|
|
|
|
|
|
row.packageCount = number(updated.packageCount)
|
|
|
|
|
|
row.orderNos = text(updated.orderNos)
|
|
|
|
|
|
}
|
|
|
|
|
|
ElMessage.success('托盘已移除,��新排车')
|
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
|
mutationError.value = getApiErrorDetails(cause, '托盘移除失败,请�试')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
removingItemId.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
async function cancel(row: DispatchListItem) {
|
|
|
|
|
|
if (!canCancel(row)) return
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(`确认�消排车� ${row.dispatchNo}?�消�将释放托盘�。`, '�消排车', {
|
|
|
|
|
|
confirmButtonText: '确认�消',
|
|
|
|
|
|
cancelButtonText: '返回',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
mutationError.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
await cancelDispatch(row.dispatchNo)
|
|
|
|
|
|
ElMessage.success('排车�已�消')
|
|
|
|
|
|
await load()
|
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
|
mutationError.value = getApiErrorDetails(cause, '排车�消失败,请�试')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
function handleMore(command: string, row: DispatchListItem) {
|
|
|
|
|
|
if (command === 'edit') openEdit(row)
|
|
|
|
|
|
else if (command === 'cancel') void cancel(row)
|
|
|
|
|
|
}
|
2026-08-26 15:48:34 +08:00
|
|
|
|
async function submitTransfer() {
|
2026-09-01 14:23:53 +08:00
|
|
|
|
if (!transferRow.value || !canTransfer(transferRow.value)) return
|
2026-09-13 21:10:05 +08:00
|
|
|
|
if (!transferForm.plannedDeliveryAt || !transferForm.deliveryMethod || !transferForm.pickupMethod || !transferForm.loadingType) {
|
|
|
|
|
|
ElMessage.warning('请填写预计派�时间�派�方���货方�和装车类型')
|
|
|
|
|
|
return
|
2026-09-08 20:27:47 +08:00
|
|
|
|
}
|
2026-09-13 21:10:05 +08:00
|
|
|
|
if (transferForm.deliveryFee == null) {
|
|
|
|
|
|
ElMessage.warning('请填写派�费用')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needsLoadingFee.value && transferForm.loadingFee == null) {
|
|
|
|
|
|
ElMessage.warning('当�装车类型需�填写装车费')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isBuyAppointment.value && (!transferForm.sellerName.trim() || transferForm.appointmentFee == null)) {
|
|
|
|
|
|
ElMessage.warning('买约时请填写�家姓�和买约费用')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const feeInputs = [
|
|
|
|
|
|
['DELIVERY_FEE', transferForm.deliveryFee, '派�费用'],
|
|
|
|
|
|
['LOADING_FEE', needsLoadingFee.value ? transferForm.loadingFee : undefined, '装车费'],
|
|
|
|
|
|
['APPOINTMENT_FEE', isBuyAppointment.value ? transferForm.appointmentFee : undefined, '买约费用'],
|
|
|
|
|
|
] as const
|
|
|
|
|
|
const fees = feeInputs
|
|
|
|
|
|
.filter(([, amount]) => amount != null)
|
|
|
|
|
|
.map(([feeType, amount, feeName]) => ({
|
|
|
|
|
|
feeType,
|
|
|
|
|
|
feeName,
|
|
|
|
|
|
direction: 'RECEIVABLE' as const,
|
|
|
|
|
|
currency: 'USD' as const,
|
|
|
|
|
|
amount: Number(amount),
|
|
|
|
|
|
remark: feeName,
|
|
|
|
|
|
}))
|
2026-09-08 20:27:47 +08:00
|
|
|
|
let palletCartonMarks: Record<string, string[]> | undefined
|
|
|
|
|
|
if (transferForm.cartonMarksJson.trim()) {
|
|
|
|
|
|
try { const parsed = JSON.parse(transferForm.cartonMarksJson); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error(); palletCartonMarks = Object.fromEntries(Object.entries(parsed).map(([pallet, marks]) => [pallet, Array.isArray(marks) ? marks.map((mark) => String(mark).trim()).filter(Boolean) : []])) } catch { ElMessage.warning('托盘箱唛分é…�æ ¼å¼�ä¸�æ£ç¡®ï¼Œè¯·ä½¿ç”¨ JSON:{"托盘å�·":["箱唛1","箱唛2"]}'); return }
|
|
|
|
|
|
}
|
2026-08-26 15:48:34 +08:00
|
|
|
|
transferSaving.value = true
|
2026-09-13 21:10:05 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await transferDispatchToDelivery(transferRow.value.dispatchNo, {
|
|
|
|
|
|
idempotencyKey: `web-transfer-${transferRow.value.dispatchNo}-${Date.now()}`,
|
|
|
|
|
|
plannedDeliveryAt: transferForm.plannedDeliveryAt,
|
|
|
|
|
|
deliveryMethod: transferForm.deliveryMethod,
|
|
|
|
|
|
waybillNo: transferForm.waybillNo.trim() || undefined,
|
|
|
|
|
|
carrierName: transferForm.carrierName.trim() || undefined,
|
|
|
|
|
|
pickupMethod: transferForm.pickupMethod,
|
|
|
|
|
|
loadingType: transferForm.loadingType,
|
|
|
|
|
|
vehicleType: transferForm.vehicleType || undefined,
|
|
|
|
|
|
vehicleSize: transferForm.vehicleSize || undefined,
|
|
|
|
|
|
driverName: transferForm.driverName.trim() || undefined,
|
|
|
|
|
|
driverPhone: transferForm.driverPhone.trim() || undefined,
|
|
|
|
|
|
vehiclePlate: transferForm.vehiclePlate.trim() || undefined,
|
|
|
|
|
|
appointmentAccount: transferForm.appointmentAccount || undefined,
|
|
|
|
|
|
sellerName: isBuyAppointment.value ? transferForm.sellerName.trim() : undefined,
|
|
|
|
|
|
remark: transferForm.remark.trim() || undefined,
|
|
|
|
|
|
fees,
|
2026-09-14 11:05:47 +08:00
|
|
|
|
palletCartonMarks,
|
2026-09-13 21:10:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
transferOpen.value = false
|
|
|
|
|
|
ElMessage.success('已生�出库�和派��')
|
|
|
|
|
|
await load()
|
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
|
mutationError.value = getApiErrorDetails(cause, '转派�失败,请�试')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
transferSaving.value = false
|
|
|
|
|
|
}
|
2026-08-26 15:48:34 +08:00
|
|
|
|
}
|
2026-08-21 11:20:09 +08:00
|
|
|
|
|
|
|
|
|
|
onMounted(load)
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<AdminPage><ListPageLayout aria-label="海外仓排车列表">
|
2026-09-01 14:23:53 +08:00
|
|
|
|
<OperationErrorToast :details="mutationError" />
|
2026-09-07 11:15:37 +08:00
|
|
|
|
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading" ariaLabel="排车列表ç›é€‰" preference-key="overseas-dispatches-query" :search-on-reset="false" @search="search" @reset="reset" /></template>
|
2026-08-21 11:20:09 +08:00
|
|
|
|
<template v-if="loadError" #state><LoadErrorState title="æŽ’è½¦åˆ—è¡¨åŠ è½½å¤±è´¥" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
|
|
|
|
|
|
<section v-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-dispatches" row-key="id" empty-text="æš‚æ— æŽ’è½¦è®°å½•" @update:page="load()" @update:page-size="page = 1; load()">
|
2026-09-10 21:22:06 +08:00
|
|
|
|
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row as DispatchListItem)"><Eye :size="15" />查看</el-button><el-button size="small" plain :loading="bolLoadingNo === (row as DispatchListItem).dispatchNo" @click="openBol(row as DispatchListItem)"><Printer :size="15" />打�BOL</el-button><el-button v-if="canTransfer(row as DispatchListItem)" size="small" type="success" plain @click="openTransfer(row as DispatchListItem)"><Truck :size="15" />转派�</el-button><el-dropdown v-if="canEdit(row as DispatchListItem) || canCancel(row as DispatchListItem)" trigger="click" @command="handleMore(String($event), row as DispatchListItem)"><el-button size="small" plain><MoreHorizontal :size="15" />更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item v-if="canEdit(row as DispatchListItem)" command="edit"><Pencil :size="15" />编辑</el-dropdown-item><el-dropdown-item v-if="canCancel(row as DispatchListItem)" command="cancel" divided><XCircle :size="15" />�消</el-dropdown-item></el-dropdown-menu></template></el-dropdown></template>
|
2026-08-21 11:20:09 +08:00
|
|
|
|
<template #status="{ row }"><StatusBadge :status="String((row as DispatchListItem).status || '—')" /></template>
|
|
|
|
|
|
<template #pagination-left>共 {{ total }} �排车记录</template>
|
|
|
|
|
|
</DataTable></section>
|
2026-09-08 20:27:47 +08:00
|
|
|
|
</ListPageLayout></AdminPage><AppDialog v-model="transferOpen" title="转派é€�" width="min(1120px, calc(100vw - 28px))" confirm-text="确认æ��交" :confirm-loading="transferSaving" @confirm="submitTransfer"><el-form label-position="top"><h3>æ´¾é€�ä¿¡æ�¯</h3><div class="transfer-grid"><el-form-item label="排车å�•å�·"><el-input :model-value="transferRow?.dispatchNo" readonly /></el-form-item><el-form-item label="预计派é€�æ—¶é—´" required><el-date-picker v-model="transferForm.plannedDeliveryAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计派é€�æ—¶é—´" /></el-form-item><el-form-item label="æ´¾é€�æ–¹å¼�" required><el-select v-model="transferForm.deliveryMethod" placeholder="请选择"><el-option v-for="option in deliveryMethodOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="快递/车队"><el-input v-model="transferForm.carrierName" placeholder="请输入快递或车队å��ç§°" /></el-form-item><el-form-item label="è¿�å�•å�·"><el-input v-model="transferForm.waybillNo" placeholder="请输入è¿�å�•å�·" /></el-form-item><el-form-item label="æ��è´§æ–¹å¼�" required><el-select v-model="transferForm.pickupMethod" placeholder="请选择"><el-option v-for="option in pickupMethodOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="装车类型" required><el-select v-model="transferForm.loadingType" placeholder="请选择"><el-option v-for="option in loadingTypeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="å�¸æœº/车队å��ç§°"><el-input v-model="transferForm.driverName" placeholder="请输入å�¸æœºæˆ–车队å��ç§°" /></el-form-item><el-form-item label="è�”系电è¯�"><el-input v-model="transferForm.driverPhone" placeholder="请输入è�”系电è¯�" /></el-form-item><el-form-item label="车牌å�·"><el-input v-model="transferForm.vehiclePlate" placeholder="请输入车牌å�·" /></el-form-item><el-form-item label="车辆类型"><el-select v-model="transferForm.vehicleType" clearable placeholder="请选择"><el-option v-for="option in vehicleTypeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="车辆大å°�"><el-select v-model="transferForm.vehicleSize" clearable placeholder="请选择"><el-option v-for="option in vehicleSizeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="预约账å�·"><el-select v-model="transferForm.appointmentAccount" clearable placeholder="请选择"><el-option v-for="option in appointmentAccountOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item v-if="isBuyAppointment" label="å�–å®¶å§“å��" required><el-input v-model="transferForm.sellerName" placeholder="请输入å�–å®¶å§“å��" /></el-form-item></div><el-form-item label="托盘箱唛确认"><el-input v-model="transferForm.cartonMarksJson" type="textarea" :rows="3" placeholder="按实际托盘填写 JSON,例如 {"PALLET-001":["箱唛1","箱唛2"]}" /><div class="form-hint">入库未é€�箱绑定时,在出库å‰�确认箱唛属于哪个实际托盘。</div></el-form-item><h3>费用信æ�¯ï¼ˆUSD)</h3><div class="transfer-grid"><el-form-item label="æ´¾é€�费用" required><el-input-number v-model="transferForm.deliveryFee" :min="0" :precision="2" controls-position="right" placeholder="请输入派é€�费用" /></el-form-item><el-form-item v-if="needsLoadingFee" label="装车费" required><el-input-number v-model="transferForm.loadingFee" :min="0" :precision="2" controls-position="right" placeholder="请输入装车费" /></el-form-item><el-form-item v-if="isBuyAppointment" label="买约费用" required><el-input-number v-model="transferForm.appointmentFee" :min="0" :precision="2" controls-position="right" placeholder="请输入买约费用" /></el-form-item></div><el-form-item label="备注"><el-input v-model="transferForm.remark" type="textarea" :rows="3" placeholder="请输入派é€�备注(选填)" /></el-form-item><el-alert title="费用ä
|
2026-09-14 11:05:47 +08:00
|
|
|
|
<AppDialog v-model="editOpen" title="编辑排车" width="min(900px, calc(100vw - 48px))" confirm-text="ä¿�å˜" :confirm-loading="editSaving" @confirm="submitEdit"><el-alert :title="`当å‰�状æ€�:${editRow?.status || '—'};普通编辑ä¸�会修改排车状æ€�;未生æˆ�出库å�•å‰�å�¯ç§»é™¤æ‰˜ç›˜ã€‚`" type="info" :closable="false" /><el-form label-position="top"><div class="edit-grid"><el-form-item label="车牌å�·"><el-input v-model="editForm.vehiclePlate" placeholder="请输入车牌å�·" /></el-form-item><el-form-item label="å�¸æœº"><el-input v-model="editForm.driverName" placeholder="请输入å�¸æœºå§“å��" /></el-form-item><el-form-item label="è�”系电è¯�"><el-input v-model="editForm.driverPhone" placeholder="请输入è�”系电è¯�" /></el-form-item><el-form-item label="拖车公å�¸å��ç§°"><el-input v-model="editForm.carrierName" placeholder="请输入拖车公å�¸å��ç§°" /></el-form-item><el-form-item label="路线"><el-input v-model="editForm.route" placeholder="请输入è¿�输路线" /></el-form-item><el-form-item label="å�‘车时间"><el-date-picker v-model="editForm.departAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item label="预计到达时间"><el-date-picker v-model="editForm.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item label="FBA预约å�·"><el-input v-model="editForm.fbaAppointmentNo" placeholder="请输入 FBA 预约å�·" /></el-form-item><el-form-item label="FBA预约状æ€�"><el-select v-model="editForm.fbaAppointmentStatus" clearable><el-option label="待预约" value="PENDING" /><el-option label="已预约" value="BOOKED" /><el-option label="已确认" value="CONFIRMED" /><el-option label="å·²å�–消" value="CANCELLED" /></el-select></el-form-item><el-form-item label="车辆预约状æ€�"><el-select v-model="editForm.vehicleAppointmentStatus" clearable><el-option label="待预约" value="PENDING" /><el-option label="已预约" value="BOOKED" /><el-option label="已确认" value="CONFIRMED" /><el-option label="å·²å�–消" value="CANCELLED" /></el-select></el-form-item></div></el-form><h3>已排车托盘</h3><el-table :data="editItems" border max-height="280" empty-text="æš‚æ— æ‰˜ç›˜"><el-table-column label="æ“�作" width="90" fixed="left"><template #default="{ row }"><el-button text type="danger" :loading="removingItemId === String(row.id)" :disabled="Boolean(removingItemId)" @click="removePallet(row)"><Trash2 :size="15" />移除</el-button></template></el-table-column><el-table-column prop="palletNo" label="托盘å�·" min-width="160" /><el-table-column prop="orderNo" label="订å�•å�·" min-width="160" /><el-table-column prop="inboundCode" label="入仓ç �" min-width="140" /><el-table-column prop="packageCount" label="箱数" width="80" align="right" /></el-table></AppDialog>
|
2026-08-21 11:20:09 +08:00
|
|
|
|
</template>
|
2026-09-13 21:10:05 +08:00
|
|
|
|
<style scoped>
|
|
|
|
|
|
.transfer-grid,
|
|
|
|
|
|
.edit-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.transfer-grid :deep(.el-select),
|
|
|
|
|
|
.transfer-grid :deep(.el-date-editor),
|
|
|
|
|
|
.transfer-grid :deep(.el-input-number),
|
|
|
|
|
|
.edit-grid :deep(.el-select),
|
|
|
|
|
|
.edit-grid :deep(.el-date-editor) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin: 4px 0 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
|
.transfer-grid,
|
|
|
|
|
|
.edit-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|