Files
client-web/src/modules/order-management/views/DeliveryOrdersView.vue
T
2026-10-19 17:56:27 +08:00

170 lines
7.5 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { listDeliveryOrders, type DeliveryQuery, type DeliveryRow } from '../api/deliveryOrders'
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
import type { TableAction } from '@/components/common/tableActions'
const router = useRouter()
const loading = ref(false)
const errorMessage = ref('')
const records = ref<DeliveryRow[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const filters = reactive({ documentNo: '', dispatchNo: '', waybillNo: '', status: '', deliveryMethod: '', plannedRange: [] as string[] })
let requestSequence = 0
const deliveryMethodOptions = [
{ value: 'FBA_PICKUP', label: 'FBA 自提' },
{ value: 'CUSTOMER_PICKUP', label: '客户自提' },
{ value: 'TRUCK_PALLET', label: '卡派打托' },
{ value: 'TRUCK_FLOOR', label: '卡派地板' },
{ value: 'EXPRESS', label: '快递' },
{ value: 'OUTSOURCED', label: '外配' },
{ value: 'DEDICATED_TRUCK', label: '专车直送' },
{ value: 'LTL', label: '零担派送' },
{ value: 'TRUCK', label: '卡车派送' },
]
const deliveryMethodLabels: Record<string, string> = Object.fromEntries(deliveryMethodOptions.map(item => [item.value, item.label]))
const statusOptions = [
{ value: 'WAIT_DELIVERY', label: '待派送' },
{ value: 'DELIVERING', label: '派送中' },
{ value: 'DELIVERED', label: '已签收' },
{ value: 'REJECTED', label: '已拒收' },
{ value: 'RETURN_PENDING', label: '待退回' },
{ value: 'RETURNED', label: '已退回' },
{ value: 'CANCELLED', label: '已取消' },
]
const statusLabels: Record<string, string> = Object.fromEntries(statusOptions.map((item) => [item.value, item.label]))
const queryModel = computed<QueryModel>({ get: () => ({ ...filters }), set: (value) => Object.assign(filters, value) })
const queryFields: QueryField[] = [
{ key: 'documentNo', label: '派送单 / 出库单', type: 'input', placeholder: '请输入派送单号或出库单号' },
{ key: 'dispatchNo', label: '排车单号', type: 'input', placeholder: '请输入排车单号' },
{ key: 'waybillNo', label: '运单号', type: 'input', placeholder: '请输入运单号' },
{ key: 'status', label: '派送状态', type: 'select', options: statusOptions },
{ key: 'deliveryMethod', label: '派送方式', type: 'select', options: deliveryMethodOptions },
{ key: 'plannedRange', label: '计划时间', type: 'daterange' },
]
const tableColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', slotName: 'actions', width: 90, fixed: 'left' }, { prop: 'deliveryNo', label: '派送单号', minWidth: 180 },
{ prop: 'dispatchNo', label: '排车单号', minWidth: 170 }, { prop: 'outboundNo', label: '出库单号', minWidth: 170 },
{ key: 'status', label: '状态', slotName: 'status', width: 110 }, { prop: 'overseasWarehouseName', label: '海外仓', minWidth: 160 },
{ prop: 'deliveryMethod', label: '派送方式', width: 120, formatter: (_row: object, _column: unknown, value: string | null | undefined) => value ? deliveryMethodLabels[value] ?? value : '—' }, { prop: 'waybillNo', label: '运单号', minWidth: 150 },
{ prop: 'carrierName', label: '承运商', minWidth: 140 }, { key: 'planned', label: '计划派送', slotName: 'planned', width: 160 },
{ key: 'arrival', label: '预计到达', slotName: 'arrival', width: 160 }, { key: 'createdAt', label: '创建时间', slotName: 'createdAt', width: 160 },
]
function display(value: string | number | null | undefined) {
return value === null || value === undefined || value === '' ? '—' : String(value)
}
function formatDateTime(value: string | null | undefined) {
return value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '—'
}
function statusLabel(status: string) {
return statusLabels[status] ?? status
}
function statusType(status: string) {
if (status === 'DELIVERED') return 'success'
if (status === 'CANCELLED') return 'info'
if (status === 'DELIVERING' || status === 'RETURN_PENDING') return 'warning'
if (status === 'REJECTED') return 'danger'
return 'primary'
}
function buildQuery(): DeliveryQuery {
return {
page: page.value,
size: pageSize.value,
documentNo: filters.documentNo.trim() || undefined,
dispatchNo: filters.dispatchNo.trim() || undefined,
waybillNo: filters.waybillNo.trim() || undefined,
status: filters.status || undefined,
deliveryMethod: filters.deliveryMethod || undefined,
plannedFrom: filters.plannedRange[0] || undefined,
plannedTo: filters.plannedRange[1] || undefined,
}
}
async function loadOrders() {
const sequence = ++requestSequence
loading.value = true
errorMessage.value = ''
try {
const result = await listDeliveryOrders(buildQuery())
if (sequence !== requestSequence) return
records.value = result.records
total.value = result.total
} catch (error) {
if (sequence !== requestSequence) return
records.value = []
total.value = 0
errorMessage.value = error instanceof Error ? error.message : '派送单加载失败,请重试'
} finally {
if (sequence === requestSequence) loading.value = false
}
}
function search() {
if (page.value === 1) void loadOrders()
else page.value = 1
}
function reset() {
Object.assign(filters, { documentNo: '', dispatchNo: '', waybillNo: '', status: '', deliveryMethod: '', plannedRange: [] })
search()
}
function changePage(nextPage: number) {
page.value = nextPage
void loadOrders()
}
function changePageSize(nextSize: number) {
pageSize.value = nextSize
page.value = 1
void loadOrders()
}
function openDetail(row: DeliveryRow) {
if (!row.deliveryNo) return
void router.push({ name: 'orders-delivery-detail', params: { deliveryNo: row.deliveryNo } })
}
function rowActions(candidate: object): TableAction[] {
const row = candidate as DeliveryRow
return [{ key: 'detail', label: '详情', disabled: !row.deliveryNo, run: () => openDetail(row) }]
}
onMounted(loadOrders)
watch([page, pageSize], ([nextPage, nextSize], [previousPage, previousSize]) => {
if (nextSize !== previousSize && nextPage !== 1) { page.value = 1; return }
void loadOrders()
})
</script>
<template>
<section class="delivery-page" aria-label="派送订单">
<QueryToolbar v-model="queryModel" layout="grid" ariaLabel="派送订单筛选" :fields="queryFields" :search-on-reset="false" @search="search" @reset="reset" />
<div class="delivery-card delivery-table-card">
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="records" :columns="tableColumns" :loading="loading" :total="total" :page-sizes="[10, 20, 50, 100]" :row-actions="rowActions" row-key="deliveryNo" empty-text="暂无派送单">
<template v-if="errorMessage" #error><div class="delivery-state delivery-state--error" role="alert"><p>{{ errorMessage }}</p><el-button @click="loadOrders">重试</el-button></div></template>
<template #status="{ row }"><el-tag :type="statusType(row.status)" effect="light">{{ statusLabel(row.status) }}</el-tag></template>
<template #planned="{ row }">{{ formatDateTime(row.plannedDeliveryAt) }}</template>
<template #arrival="{ row }">{{ formatDateTime(row.expectedArrivalAt) }}</template>
<template #createdAt="{ row }">{{ formatDateTime(row.createdAt) }}</template>
</DataTable>
</div>
</section>
</template>
<style scoped src="./deliveryOrders.css"></style>