Files
admin-web/src/modules/overseas-master-data/components/PickupDemandPanel.vue
T

74 lines
6.8 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 '@/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>