|
|
|
@@ -31,6 +31,31 @@ const loadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
const stage = ref<'' | 'BEFORE_LOADING' | 'LOADING' | 'AFTER_LOADING' | 'CANCELLED'>('')
|
|
|
|
|
const stages = [
|
|
|
|
|
{ value: '', label: '全部' }, { value: 'BEFORE_LOADING', label: '装车前' },
|
|
|
|
|
{ value: 'LOADING', label: '装车中' }, { value: 'AFTER_LOADING', label: '装车后' },
|
|
|
|
|
{ value: 'CANCELLED', label: '已取消' },
|
|
|
|
|
] as const
|
|
|
|
|
const section = ref<'vehicle' | 'dispatchReview' | 'loadingReview'>('vehicle')
|
|
|
|
|
const sections = [
|
|
|
|
|
{ value: 'vehicle', label: '车辆信息' },
|
|
|
|
|
{ value: 'dispatchReview', label: '调度审核' },
|
|
|
|
|
{ value: 'loadingReview', label: '理货员审核' },
|
|
|
|
|
] as const
|
|
|
|
|
const reviewLabels = { PENDING: '待审核', APPROVED: '已通过', REJECTED: '已打回' }
|
|
|
|
|
let loadSequence = 0
|
|
|
|
|
function switchStage(value: typeof stage.value) {
|
|
|
|
|
if (stage.value === value) return
|
|
|
|
|
stage.value = value
|
|
|
|
|
filters.value.status = ''
|
|
|
|
|
page.value = 1
|
|
|
|
|
void load()
|
|
|
|
|
}
|
|
|
|
|
function openTrip(row: DispatchListItem, tab: 'overview' | 'loading') {
|
|
|
|
|
if (!row.workflow || !access.can('platform:fleet:trip:list')) return
|
|
|
|
|
void router.push({ name: 'overseas-master-data-trip-detail', params: { tripNo: row.workflow.tripNo }, query: { tab } })
|
|
|
|
|
}
|
|
|
|
|
const filters = ref<QueryModel>({
|
|
|
|
|
dispatchNo: '',
|
|
|
|
|
vehiclePlate: '',
|
|
|
|
@@ -168,6 +193,9 @@ const fields = computed<QueryField[]>(() => [
|
|
|
|
|
const columns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'actions', label: '操作', width: 380, fixed: 'left', slotName: 'actions' },
|
|
|
|
|
{ key: 'dispatchNo', prop: 'dispatchNo', label: '排车单号', minWidth: 170, fixed: 'left' },
|
|
|
|
|
{ key: 'workflow', label: '关联车次', minWidth: 210, slotName: 'workflow' },
|
|
|
|
|
{ key: 'dispatchReview', label: '调度审核', minWidth: 180, slotName: 'dispatchReview', singleLine: false },
|
|
|
|
|
{ key: 'loadingReview', label: '理货审核(整车)', minWidth: 180, slotName: 'loadingReview', singleLine: false },
|
|
|
|
|
{ key: 'vehiclePlate', prop: 'vehiclePlate', label: '车牌号', minWidth: 130 },
|
|
|
|
|
{ key: 'driverName', prop: 'driverName', label: '司机', minWidth: 120 },
|
|
|
|
|
{ key: 'driverPhone', prop: 'driverPhone', label: '联系电话', minWidth: 140 },
|
|
|
|
@@ -228,6 +256,13 @@ const columns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 170 },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const visibleColumns = computed(() => {
|
|
|
|
|
if (section.value === 'vehicle') return columns.filter(column => !['dispatchReview', 'loadingReview'].includes(String(column.key)))
|
|
|
|
|
const keys = ['actions', 'dispatchNo', 'workflow', section.value, 'vehiclePlate', 'driverName', 'route', 'palletCount', 'packageCount', 'status']
|
|
|
|
|
return keys.map(key => columns.find(column => column.key === key)!)
|
|
|
|
|
})
|
|
|
|
|
const tablePreferenceKey = computed(() => section.value === 'vehicle' ? 'overseas-dispatches' : `overseas-dispatches-${section.value}`)
|
|
|
|
|
|
|
|
|
|
function text(value: unknown) {
|
|
|
|
|
const normalized = String(value ?? '').trim()
|
|
|
|
|
return normalized || '—'
|
|
|
|
@@ -263,6 +298,7 @@ function appointmentLabel(numberValue: unknown, statusValue: unknown) {
|
|
|
|
|
|
|
|
|
|
function toListItem(dto: DispatchSummaryDto): DispatchListItem {
|
|
|
|
|
return {
|
|
|
|
|
workflow: dto.workflow,
|
|
|
|
|
id: String(dto.id),
|
|
|
|
|
dispatchNo: text(dto.dispatchNo),
|
|
|
|
|
vehiclePlate: text(dto.vehiclePlate),
|
|
|
|
@@ -295,6 +331,7 @@ function dispatchError(error: unknown) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load(model: QueryModel = filters.value) {
|
|
|
|
|
const sequence = ++loadSequence
|
|
|
|
|
Object.assign(filters.value, model)
|
|
|
|
|
loading.value = true
|
|
|
|
|
loadError.value = null
|
|
|
|
@@ -302,21 +339,24 @@ async function load(model: QueryModel = filters.value) {
|
|
|
|
|
const result = await listDispatches({
|
|
|
|
|
page: page.value,
|
|
|
|
|
size: pageSize.value,
|
|
|
|
|
stage: stage.value || undefined,
|
|
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
if (sequence !== loadSequence) return
|
|
|
|
|
dispatchSnapshots.clear()
|
|
|
|
|
result.records.forEach((item) => dispatchSnapshots.set(String(item.dispatchNo), item))
|
|
|
|
|
rows.value = result.records.map(toListItem)
|
|
|
|
|
total.value = result.total
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (sequence !== loadSequence) return
|
|
|
|
|
rows.value = []
|
|
|
|
|
total.value = 0
|
|
|
|
|
loadError.value = dispatchError(error)
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
if (sequence === loadSequence) loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -501,6 +541,13 @@ async function cancel(row: DispatchListItem) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function rowActions(row: DispatchListItem): RowActionItem[] {
|
|
|
|
|
if (section.value !== 'vehicle') {
|
|
|
|
|
const reviewPermission = section.value === 'dispatchReview' ? 'platform:fleet:trip:dispatch-review' : 'platform:fleet:trip:loading-review'
|
|
|
|
|
return [
|
|
|
|
|
{ key: 'review', label: access.can(reviewPermission) ? '前往审核' : '查看审核', priority: 400, visible: !!row.workflow && access.can('platform:fleet:trip:list') },
|
|
|
|
|
{ key: 'detail', label: '查看', priority: 300 },
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
return [
|
|
|
|
|
{ key: 'detail', label: '查看', priority: 300 },
|
|
|
|
|
{ key: 'bol', label: '打印BOL', priority: 200, loading: bolLoadingNo.value === row.dispatchNo },
|
|
|
|
@@ -510,7 +557,8 @@ function rowActions(row: DispatchListItem): RowActionItem[] {
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
function handleRowAction(command: string, row: DispatchListItem) {
|
|
|
|
|
if (command === 'detail') openDetail(row)
|
|
|
|
|
if (command === 'review') openTrip(row, section.value === 'dispatchReview' ? 'overview' : 'loading')
|
|
|
|
|
else if (command === 'detail') openDetail(row)
|
|
|
|
|
else if (command === 'bol') void openBol(row)
|
|
|
|
|
else if (command === 'edit') openEdit(row)
|
|
|
|
|
else if (command === 'transfer') openTransfer(row)
|
|
|
|
@@ -589,11 +637,30 @@ onMounted(load)
|
|
|
|
|
<template>
|
|
|
|
|
<AdminPage class="admin-density"><ListPageLayout aria-label="海外仓排车列表">
|
|
|
|
|
<OperationErrorToast :details="mutationError" />
|
|
|
|
|
<DataTable v-model:page="page" v-model:page-size="pageSize" appearance="integrated" :stripe="false" :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()">
|
|
|
|
|
<DataTable v-model:page="page" v-model:page-size="pageSize" appearance="integrated" :stripe="false" :data="rows" :columns="visibleColumns" :loading="loading" :total="total" :preference-key="tablePreferenceKey" row-key="id" empty-text="暂无排车记录" @update:page="load()" @update:page-size="page = 1; load()">
|
|
|
|
|
<template #toolbar-start><h1>排车列表</h1></template>
|
|
|
|
|
<template #filters><QueryToolbar v-model="filters" :fields="fields" :loading="loading" ariaLabel="排车列表筛选" preference-key="overseas-dispatches-query" :search-on-reset="false" @search="search" @reset="reset" /></template>
|
|
|
|
|
<template #filters><div class="integrated-query-group">
|
|
|
|
|
<div class="density-status-tabs" role="tablist" aria-label="排车执行阶段">
|
|
|
|
|
<button v-for="item in stages" :key="item.value" role="tab" :aria-selected="stage === item.value" :class="{ 'is-active': stage === item.value }" @click="switchStage(item.value)">{{ item.label }}</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="density-status-tabs" role="tablist" aria-label="排车业务分区">
|
|
|
|
|
<button v-for="item in sections" :key="item.value" role="tab" :aria-selected="section === item.value" :class="{ 'is-active': section === item.value }" @click="section = item.value">{{ item.label }}</button>
|
|
|
|
|
</div>
|
|
|
|
|
<QueryToolbar v-model="filters" :fields="fields" :loading="loading" ariaLabel="排车列表筛选" preference-key="overseas-dispatches-query" :search-on-reset="false" @search="search" @reset="reset" /></div></template>
|
|
|
|
|
<template v-if="loadError" #state><LoadErrorState title="排车列表加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
|
|
|
|
|
<template #actions="{ row }"><RowActions text-actions :actions="rowActions(row as DispatchListItem)" :more-label="`更多操作 · ${(row as DispatchListItem).dispatchNo || '当前记录'}`" @action="handleRowAction($event, row as DispatchListItem)" /></template>
|
|
|
|
|
<template #workflow="{ row }">
|
|
|
|
|
<el-button v-if="row.workflow && access.can('platform:fleet:trip:list')" link type="primary" @click="openTrip(row as DispatchListItem, 'overview')">{{ row.workflow.tripNo }}</el-button>
|
|
|
|
|
<span v-else>{{ row.workflow?.tripNo || '未关联车次' }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-for="review in (['dispatchReview', 'loadingReview'] as const)" :key="review" #[review]="{ row }">
|
|
|
|
|
<template v-if="(row as DispatchListItem).workflow">
|
|
|
|
|
<div>{{ reviewLabels[(row as DispatchListItem).workflow![review].status] }}</div>
|
|
|
|
|
<div v-if="row.workflow[review].status === 'REJECTED'">{{ row.workflow[review].reason || '—' }}</div>
|
|
|
|
|
<div v-if="row.workflow[review].reviewedAt">{{ formatOverseasDateTime(row.workflow[review].reviewedAt) }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
<span v-else>未关联车次</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template #status="{ row }"><StatusBadge :status="String((row as DispatchListItem).status || '—')" /></template>
|
|
|
|
|
<template #pagination-left>共 {{ total }} 条排车记录</template>
|
|
|
|
|
</DataTable>
|
|
|
|
|