74 lines
6.8 KiB
Vue
74 lines
6.8 KiB
Vue
<script setup lang="ts">
|
||
import '@/styles/adminWorkbench.css'
|
||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||
import { useRouter, onBeforeRouteLeave } from 'vue-router'
|
||
import { ElMessage } from 'element-plus'
|
||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||
import QueryToolbar, { type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import { ApiResponseError, getApiErrorDetails } from '@/shared/api/http'
|
||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||
import { listPickupCandidates, createPickupTrip, type PickupCandidate, type PickupPlan } from '../api/pickups'
|
||
import { overseasMasterDataPermissions as permission } from '../permissions'
|
||
|
||
const router = useRouter(), access = useAccessPreviewStore()
|
||
const rows = ref<PickupCandidate[]>([]), selected = ref<PickupCandidate[]>([]), page = ref(1), total = ref(0), loading = ref(false)
|
||
const filters = ref<QueryModel>({ keyword: '' }), applied = ref(''), error = ref(''), open = ref(false), busy = ref(false)
|
||
const departure = ref(''), end = ref(''), remark = ref(''), snapshot = ref<PickupCandidate[]>([]), pending = ref<PickupPlan | null>(null)
|
||
let sequence = 0
|
||
const columns: DataTableColumn[] = [
|
||
{ key: 'selection', type: 'selection', width: 45 }, { key: 'transferNo', prop: 'transferNo', label: '仓储转运单', minWidth: 220 },
|
||
{ key: 'lastlegOrderNo', prop: 'lastlegOrderNo', label: '尾程单号', minWidth: 200 },
|
||
{ key: 'warehouseId', prop: 'warehouseId', label: '入仓仓库', minWidth: 165, slotName: 'warehouse' },
|
||
{ key: 'pickupAddress', prop: 'pickupAddress', label: '取货地址', minWidth: 280 },
|
||
{ key: 'contactName', prop: 'contactName', label: '联系人', width: 110 }, { key: 'contactPhone', prop: 'contactPhone', label: '电话', minWidth: 150 },
|
||
{ key: 'packageCount', prop: 'packageCount', label: '预报箱数', width: 100 }, { key: 'expectedArrivalAt', prop: 'expectedArrivalAt', label: '期望入仓时间', minWidth: 175 },
|
||
]
|
||
/** 分页或条件变化时清除旧选择,迟到响应不能覆盖新页。 */
|
||
async function load() {
|
||
const current = ++sequence; loading.value = true; error.value = ''; selected.value = []
|
||
try { const value = await listPickupCandidates(page.value, 20, applied.value || undefined); if (current === sequence) { rows.value = value.records; total.value = value.total } }
|
||
catch (cause) { if (current === sequence) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '待取货需求加载失败').message } }
|
||
finally { if (current === sequence) loading.value = false }
|
||
}
|
||
/** 查询只采用明确提交的条件。 */
|
||
function search() { applied.value = String(filters.value.keyword || '').trim(); if (page.value !== 1) page.value = 1; else void load() }
|
||
/** 安排弹框固定本次选单快照。 */
|
||
function arrange() {
|
||
if (!selected.value.length) { ElMessage.warning('请勾选待取货订单'); return }
|
||
if (new Set(selected.value.map(row => row.warehouseId)).size !== 1) { ElMessage.warning('同车取货必须送往同一海外仓'); return }
|
||
snapshot.value = [...selected.value]; pending.value = null; departure.value = ''; end.value = ''; remark.value = ''; error.value = ''; open.value = true
|
||
}
|
||
async function save() {
|
||
if (busy.value) return
|
||
if (!pending.value && (!departure.value || !end.value)) { ElMessage.warning('请填写取货发车时间和预计任务结束时间'); return }
|
||
pending.value ||= { sources: snapshot.value.map(({ customerId, transferNo }) => ({ customerId, transferNo })), plannedDepartureAt: new Date(departure.value).toISOString(), expectedDriverEndAt: new Date(end.value).toISOString(), remark: remark.value.trim(), idempotencyKey: crypto.randomUUID() }
|
||
busy.value = true
|
||
try { const value = await createPickupTrip(pending.value); open.value = false; pending.value = null; await router.push({ name: 'overseas-master-data-trip-detail', params: { tripNo: value.trip.tripNo } }) }
|
||
catch (cause) { error.value = getApiErrorDetails(cause, '安排取货未完成').message; if (cause instanceof ApiResponseError) pending.value = null }
|
||
finally { pending.value = null; busy.value = false }
|
||
}
|
||
function close(done: () => void) { if (busy.value || pending.value) { ElMessage.warning('操作正在提交,请稍候'); return }; done() }
|
||
onBeforeRouteLeave(() => { if (busy.value || pending.value) { ElMessage.warning('请先确认本次安排结果'); return false }; return true })
|
||
watch(page, load); onMounted(load); onBeforeUnmount(() => { sequence++ })
|
||
</script>
|
||
<template>
|
||
<div class="workbench-table"><DataTable v-model:page="page" :page-size="20" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="transferNo" empty-text="暂无已审核的待取货订单" @selection-change="selected = $event">
|
||
<template #filters><QueryToolbar ariaLabel="待取货需求查询" v-model="filters" :fields="[{ key: 'keyword', type: 'input', label: '关键词', placeholder: '转运单号 / 取货地址', width: 300 }]" @search="search" @reset="search" /></template>
|
||
<template #toolbar-actions><el-button v-if="access.can(permission.tripCreate)" type="primary" :disabled="!selected.length" @click="arrange">安排取货({{ selected.length }})</el-button></template>
|
||
<template #notices><LoadErrorState v-if="error && !open" title="待取货需求加载失败" :message="error" @retry="load" /></template>
|
||
<template #warehouse="{ row }">{{ row.warehouseName || '海外仓' }} · {{ row.warehouseCode || row.warehouseId }}</template>
|
||
</DataTable></div>
|
||
<AppDialog v-model="open" class="admin-density-dialog admin-workbench is-dialog" :before-close="close" title="安排仓储转运取货" width="780px" :confirm-text="'创建取货车次'" :confirm-loading="busy" @confirm="save">
|
||
<p>已选 {{ snapshot.length }} 单:{{ snapshot.map(row => row.transferNo).join('、') }}</p>
|
||
<el-form label-position="top" class="pickup-plan-form" :disabled="busy || !!pending">
|
||
<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-item label="调度备注"><el-input v-model="remark" maxlength="1000" placeholder="请输入调度备注(选填)" /></el-form-item>
|
||
</el-form>
|
||
<el-alert v-if="error" :title="error" type="error" :closable="false" />
|
||
</AppDialog>
|
||
</template>
|
||
<style scoped>.pickup-plan-form{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}.pickup-plan-form :deep(.el-date-editor){width:100%}.pickup-plan-form>.el-form-item:last-child{grid-column:1/-1}</style>
|