68 lines
6.1 KiB
Vue
68 lines
6.1 KiB
Vue
<script setup lang="ts">
|
|
import '@/styles/adminDensity.css'
|
|
import { appointmentReturnTarget, appointmentReturnQuery } from '@/modules/appointment-calendar'
|
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
|
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
|
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
|
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
import TripPlanDialog from '../components/TripPlanDialog.vue'
|
|
import PickupDemandPanel from '../components/PickupDemandPanel.vue'
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
import { listTrips, type TripQuery } from '../api/trips'
|
|
import { overseasMasterDataPermissions as permission } from '../permissions'
|
|
import { tripStatusLabels, type FleetTrip, type TripDetail } from '../tripTypes'
|
|
|
|
const route = useRoute(), router = useRouter(), access = useAccessPreviewStore()
|
|
const workTab = ref('trips'), pickupVisited = ref(false)
|
|
watch(workTab, value => { if (value === 'pickups') pickupVisited.value = true })
|
|
const rows = ref<FleetTrip[]>([]), total = ref(0), page = ref(1), size = ref(20), loading = ref(false), editorOpen = ref(false)
|
|
const error = ref<ApiErrorDetails | null>(null), filters = ref<QueryModel>({ keyword: '', status: '' })
|
|
let applied: Omit<TripQuery, 'page' | 'size'> = {}, sequence = 0
|
|
const fields: QueryField[] = [{ key: 'keyword', type: 'input', label: '关键词', placeholder: '车次号 / 车牌 / 司机', width: 250 },
|
|
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 150, options: Object.entries(tripStatusLabels).map(([value, label]) => ({ value, label })) }]
|
|
const columns: DataTableColumn[] = [
|
|
{ key: 'actions', label: '操作', width: 85, fixed: 'left', slotName: 'actions' }, { key: 'tripNo', label: '车次号', minWidth: 215 },
|
|
{ key: 'purpose', label: '任务类型', slotName: 'purpose', width: 110 }, { key: 'status', label: '状态', slotName: 'status', width: 120 }, { key: 'warehouseName', label: '起运仓', minWidth: 160 },
|
|
{ key: 'vehiclePlate', label: '车牌', minWidth: 130 }, { key: 'driverName', label: '司机', minWidth: 110 },
|
|
{ key: 'departure', label: '计划发车', slotName: 'departure', minWidth: 175 }, { key: 'end', label: '司机预计结束', slotName: 'end', minWidth: 175 },
|
|
{ key: 'route', label: '路线', slotName: 'route', minWidth: 240 }, { key: 'remark', label: '备注', minWidth: 180 },
|
|
]
|
|
const time = (value: string) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
|
|
async function load() {
|
|
const current = ++sequence; loading.value = true; error.value = null
|
|
try { const result = await listTrips({ ...applied, page: page.value, size: size.value }); if (current === sequence) { rows.value = result.records; total.value = result.total } }
|
|
catch (cause) { if (current === sequence) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '车次加载失败') } }
|
|
finally { if (current === sequence) loading.value = false }
|
|
}
|
|
function search() {
|
|
applied = { keyword: String(filters.value.keyword || '').trim() || undefined, status: (filters.value.status || undefined) as TripQuery['status'] }
|
|
if (page.value !== 1) page.value = 1; else void load()
|
|
}
|
|
function view(tripNo: string) { void router.push({ name: 'overseas-master-data-trip-detail', params: { tripNo }, query: appointmentReturnQuery(route.query) }) }
|
|
function created(detail: TripDetail) { view(detail.trip.tripNo) }
|
|
watch([page, size], load); onMounted(load); onBeforeUnmount(() => { sequence++ })
|
|
</script>
|
|
<template>
|
|
<AdminPage class="admin-density"><ListPageLayout aria-label="车次管理">
|
|
<el-tabs v-model="workTab" class="density-element-tabs"><el-tab-pane label="车次管理" name="trips" /><el-tab-pane label="待取货" name="pickups" /></el-tabs>
|
|
<PickupDemandPanel v-if="pickupVisited" v-show="workTab === 'pickups'" />
|
|
<DataTable v-show="workTab === 'trips'" appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" preference-key="fleet-trips-table" v-model:page="page" v-model:page-size="size" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="tripNo" empty-text="暂无车次,先建立计划再关联排车单">
|
|
<template #toolbar-start><el-button v-if="appointmentReturnTarget(route.query)" link @click="router.push(appointmentReturnTarget(route.query)!)">返回预约工作</el-button><h1>车次管理</h1></template>
|
|
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="车次查询" preference-key="fleet-trips-query" :loading="loading" @search="search" @reset="search" /></template>
|
|
<template #notices><LoadErrorState v-if="error" title="车次加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template>
|
|
<template #toolbar-actions><el-button v-if="access.can(permission.tripCreate)" type="primary" @click="editorOpen = true">新建车次</el-button></template>
|
|
<template #actions="{ row }"><div class="density-actions row-actions admin-text-actions"><el-button class="density-action" @click="view(row.tripNo)">详情</el-button></div></template>
|
|
<template #purpose="{ row }">{{ row.tripPurpose === 'PICKUP' ? '入仓取货' : '出库派送' }}</template>
|
|
<template #status="{ row }">{{ tripStatusLabels[row.status as keyof typeof tripStatusLabels] }}</template>
|
|
<template #departure="{ row }">{{ time(row.plannedDepartureAt) }}</template><template #end="{ row }">{{ time(row.expectedDriverEndAt) }}</template>
|
|
<template #route="{ row }">{{ row.stops.map((stop: FleetTrip['stops'][number]) => stop.name).join(' → ') }}</template>
|
|
</DataTable>
|
|
<TripPlanDialog v-model="editorOpen" :trip="null" @saved="created" />
|
|
</ListPageLayout></AdminPage>
|
|
</template>
|