69 lines
4.8 KiB
Vue
69 lines
4.8 KiB
Vue
<script setup lang="ts">
|
||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||
import QueryToolbar, { type QueryModel, type QueryField } from '@/shared/components/QueryToolbar.vue'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||
import { listExternalAppointmentCandidates, type ExternalAppointmentCandidate, type PlanningSource } from './planningApi'
|
||
|
||
defineProps<{ canArrange: boolean; selectedKeys: string[]; busy: boolean }>()
|
||
const emit = defineEmits<{ select: [source: PlanningSource]; followup: [row: ExternalAppointmentCandidate] }>()
|
||
const page = ref(1), size = ref(20), total = ref(0), loading = ref(false)
|
||
const rows = ref<ExternalAppointmentCandidate[]>([]), error = ref<ApiErrorDetails | null>(null)
|
||
const query = ref<QueryModel>({ keyword: '' })
|
||
const fields: QueryField[] = [{ key: 'keyword', type: 'input', label: '外接货', placeholder: '登记号、车次号、客户、来货号', width: 300 }]
|
||
const columns: DataTableColumn[] = [
|
||
{ key: 'actions', label: '操作', slotName: 'actions', width: 160, fixed: 'left' },
|
||
{ key: 'booking', label: '原登记号', slotName: 'booking', minWidth: 190 },
|
||
{ prop: 'customerName', label: '客户', minWidth: 130 },
|
||
{ prop: 'tripNo', label: '车次号', minWidth: 190 },
|
||
{ key: 'origin', label: '接仓点', slotName: 'origin', minWidth: 140 },
|
||
{ key: 'destination', label: '目的FBA仓', slotName: 'destination', width: 110 },
|
||
{ key: 'packages', label: '箱数', slotName: 'packages', width: 90 },
|
||
{ key: 'coverage', label: '预约情况', slotName: 'coverage', minWidth: 180 },
|
||
]
|
||
let request = 0
|
||
let controller: AbortController | undefined
|
||
async function load(reset = false) {
|
||
if (reset) page.value = 1
|
||
const current = ++request
|
||
controller?.abort(); controller = new AbortController()
|
||
loading.value = true; error.value = null
|
||
try {
|
||
const result = await listExternalAppointmentCandidates({ page: page.value, size: size.value, keyword: String(query.value.keyword || '').trim() || undefined }, controller.signal)
|
||
if (current === request) { rows.value = result.records; total.value = result.total }
|
||
} catch (cause) {
|
||
if (current === request) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '外接候选加载失败') }
|
||
} finally { if (current === request) loading.value = false }
|
||
}
|
||
onMounted(() => load())
|
||
onBeforeUnmount(() => { ++request; controller?.abort() })
|
||
</script>
|
||
|
||
<template>
|
||
<DataTable v-model:page="page" v-model:page-size="size" appearance="integrated" :data="rows" :columns="columns" :total="total" :loading="loading" :results-visible="!error" @update:page="load()" @update:page-size="load(true)">
|
||
<template #toolbar-actions><slot name="toolbar-actions" /></template>
|
||
<template #filters>
|
||
<section class="integrated-query-group" aria-label="外接货物类型与筛选">
|
||
<slot name="query-tabs" />
|
||
<QueryToolbar v-model="query" ariaLabel="外接预约候选查询" :fields="fields" :loading="loading" @search="load(true)" />
|
||
</section>
|
||
</template>
|
||
<template #notices>
|
||
<slot name="notices" />
|
||
<LoadErrorState v-if="error" title="外接候选加载失败" :message="error.message" @retry="load()"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||
<p v-else>已确认承运的美国FBA货物,来货前后保留同一原登记;箱数未知可先保存待确认安排。</p>
|
||
</template>
|
||
<template #actions="{ row }"><template v-if="row.source && row.coverage">
|
||
<el-button v-if="canArrange" link type="primary" :disabled="busy || selectedKeys.includes(row.source.sourceKey) || !row.source.canFollowup || row.coverage.appointments.length > 0" @click="emit('select', row.source)">{{ selectedKeys.includes(row.source.sourceKey) ? '已加入' : '加入合批' }}</el-button>
|
||
<el-button link type="primary" @click="emit('followup', row as ExternalAppointmentCandidate)">跟进</el-button>
|
||
</template></template>
|
||
<template #booking="{ row }">{{ row.source?.bookingNo || '—' }}</template>
|
||
<template #origin="{ row }">{{ row.source?.originWarehouseName || '—' }}</template>
|
||
<template #destination="{ row }">{{ row.source?.destinationWarehouseCode || '—' }}</template>
|
||
<template #packages="{ row }">{{ row.source?.packages ?? '待核实' }}</template>
|
||
<template #coverage="{ row }"><span v-if="row.source && row.coverage">{{ row.coverage.appointments.length ? (row.coverage.changeReviewRequired ? '已有预约,待复核' : '已有预约') : (row.source.canFollowup ? '待安排' : row.source.blockReasons.join(';')) }}</span></template>
|
||
</DataTable>
|
||
</template>
|