| | |
| | | <script setup lang="tsx"> |
| | | import dayjs from 'dayjs' |
| | | import { computed, ref, watch } from 'vue' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { NButton, NDataTable, NDatePicker, NForm, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage } from 'naive-ui' |
| | | import { NButton, NDataTable, NForm, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage } from 'naive-ui' |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | |
| | | fetchTestPlanDetail, |
| | | fetchTestPlanDetailOrderPageList, |
| | | fetchTestPlanMonitorDetail, |
| | | fetchTestPlanVehiclePageList, |
| | | fetchTestPlanVehicleList, |
| | | interruptTestPlanDispatch, |
| | | resumeTestPlanDispatch, |
| | | } from '@/service/api/testPlanManagement' |
| | |
| | | |
| | | const searchModel = ref({ |
| | | orderNo: '', |
| | | vehicleId: null as number | string | null, |
| | | dispatchTimeRange: null as [number, number] | null, |
| | | startAddress: null as string | null, |
| | | endAddress: null as string | null, |
| | | plateNumber: null as string | null, |
| | | orderStatus: null as number | null, |
| | | }) |
| | | |
| | | const isVehicleSelectOpen = ref(false) |
| | | const orderCheckedRowKeys = ref<Array<number | string>>([]) |
| | | const orderCount = ref(0) |
| | | const orderPageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const vehicleCount = ref(0) |
| | | const vehiclePageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }) |
| | |
| | | const isAssignVehicleModalVisible = ref(false) |
| | | const isManageTestVehicleModalVisible = ref(false) |
| | | const currentAssignOrderId = ref<number | string | null>(null) |
| | | const pointOptions = ref<Array<{ label: string, value: string }>>([]) |
| | | |
| | | const PLAN_STATUS_STYLE_MAP: Record<number, { textColor: string, color: string }> = { |
| | | 1: { textColor: '#0068FF', color: '#E6F4FF' }, |
| | |
| | | const vehicleOptions = computed(() => |
| | | vehicleData.value.map(item => ({ |
| | | label: item.plateNo, |
| | | value: item.id, |
| | | value: item.plateNo, |
| | | })), |
| | | ) |
| | | |
| | | const orderStatusOptions = [ |
| | | { label: '已取消', value: 0 }, |
| | | { label: '待接单', value: 1 }, |
| | | { label: '待出发', value: 2 }, |
| | | { label: '接驾中', value: 3 }, |
| | | { label: '待客上车', value: 4 }, |
| | | { label: '乘客已上车', value: 5 }, |
| | | { label: '服务中', value: 6 }, |
| | | { label: '待客下车', value: 7 }, |
| | | { label: '待付款', value: 8 }, |
| | | { label: '待评价', value: 9 }, |
| | | { label: '已完成', value: 10 }, |
| | | ] |
| | | |
| | | const ORDER_STATUS_LABEL_MAP: Record<number, string> = { |
| | | 0: '已取消', |
| | | 1: '待接单', |
| | | 2: '待出发', |
| | | 3: '接驾中', |
| | | 4: '待客上车', |
| | | 5: '乘客已上车', |
| | | 6: '服务中', |
| | | 7: '待客下车', |
| | | 8: '待付款', |
| | | 9: '待评价', |
| | | 10: '已完成', |
| | | } |
| | | |
| | | const filterItems = computed(() => [ |
| | | { |
| | |
| | | render: () => <n-input v-model:value={searchModel.value.orderNo} placeholder="请输入" clearable />, |
| | | }, |
| | | { |
| | | label: '车辆', |
| | | path: 'vehicleId', |
| | | label: '起点', |
| | | path: 'startAddress', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.vehicleId} |
| | | v-model:value={searchModel.value.startAddress} |
| | | options={pointOptions.value} |
| | | placeholder="请选择" |
| | | clearable |
| | | /> |
| | | ), |
| | | }, |
| | | { |
| | | label: '终点', |
| | | path: 'endAddress', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.endAddress} |
| | | options={pointOptions.value} |
| | | placeholder="请选择" |
| | | clearable |
| | | /> |
| | | ), |
| | | }, |
| | | { |
| | | label: '车辆', |
| | | path: 'plateNumber', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.plateNumber} |
| | | options={vehicleOptions.value} |
| | | placeholder="请选择" |
| | | clearable |
| | |
| | | ), |
| | | }, |
| | | { |
| | | label: '下发时间', |
| | | path: 'dispatchTimeRange', |
| | | label: '订单状态', |
| | | path: 'orderStatus', |
| | | render: () => ( |
| | | <n-date-picker |
| | | v-model:value={searchModel.value.dispatchTimeRange} |
| | | type="datetimerange" |
| | | format="yyyy/MM/dd HH:mm:ss" |
| | | <n-select |
| | | v-model:value={searchModel.value.orderStatus} |
| | | options={orderStatusOptions} |
| | | placeholder="请选择" |
| | | clearable |
| | | class="w-full" |
| | | /> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | function getOrderStatusStyle(statusName: string) { |
| | | if (!statusName) |
| | | function getOrderStatusStyle(statusCode?: number, statusName?: string) { |
| | | const resolvedStatusName = statusName || (statusCode != null ? ORDER_STATUS_LABEL_MAP[statusCode] : '') |
| | | if (!resolvedStatusName) |
| | | return { textColor: '#8C9299', color: '#F5F5F5' } |
| | | if (statusName.includes('待下发')) |
| | | return { textColor: '#A034FF', color: '#F4E6FF' } |
| | | if (statusName.includes('暂停')) |
| | | return { textColor: '#F59E0B', color: '#FFF7E6' } |
| | | if (statusName.includes('完成') || statusName.includes('关闭')) |
| | | if (resolvedStatusName.includes('取消') || resolvedStatusName.includes('完成')) |
| | | return { textColor: '#8C9299', color: '#F5F5F5' } |
| | | if (statusName.includes('服务') || statusName.includes('待出发') || statusName.includes('待接单') || statusName.includes('待乘客')) |
| | | if (resolvedStatusName.includes('服务') || resolvedStatusName.includes('待接单') || resolvedStatusName.includes('待出发') || resolvedStatusName.includes('接驾') || resolvedStatusName.includes('待客') || resolvedStatusName.includes('上车')) |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | if (resolvedStatusName.includes('付款') || resolvedStatusName.includes('评价')) |
| | | return { textColor: '#F59E0B', color: '#FFF7E6' } |
| | | return { textColor: '#10B981', color: '#F6FFED' } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function buildOrderQueryParams() { |
| | | const timeRange = searchModel.value.dispatchTimeRange |
| | | return { |
| | | orderNumber: searchModel.value.orderNo.trim() || undefined, |
| | | vehicleId: searchModel.value.vehicleId || undefined, |
| | | dispatchStartTime: timeRange?.[0] ? dayjs(timeRange[0]).format('YYYY-MM-DD HH:mm:ss') : undefined, |
| | | dispatchEndTime: timeRange?.[1] ? dayjs(timeRange[1]).format('YYYY-MM-DD HH:mm:ss') : undefined, |
| | | startAddress: searchModel.value.startAddress || undefined, |
| | | endAddress: searchModel.value.endAddress || undefined, |
| | | plateNumber: searchModel.value.plateNumber || undefined, |
| | | orderStatus: searchModel.value.orderStatus ?? undefined, |
| | | testPlanId: currentPlanId.value, |
| | | } |
| | | } |
| | | |
| | | function buildPointOptions(points: unknown) { |
| | | if (!Array.isArray(points)) |
| | | return [] |
| | | const optionMap = new Map<string, string>() |
| | | points.forEach((item: any) => { |
| | | const pointName = item?.pointName || '' |
| | | if (!pointName || optionMap.has(pointName)) |
| | | return |
| | | optionMap.set(pointName, pointName) |
| | | }) |
| | | return [...optionMap.entries()].map(([value, label]) => ({ value, label })) |
| | | } |
| | | |
| | | async function fetchBaseDetail() { |
| | |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取测试计划详情失败') |
| | | } |
| | | planName.value = res.data?.planName || '-' |
| | | const detail = res.data || {} |
| | | planName.value = detail.planName || '-' |
| | | pointOptions.value = buildPointOptions(detail.points) |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '获取测试计划详情失败') |
| | | planName.value = '-' |
| | | pointOptions.value = [] |
| | | } |
| | | finally { |
| | | detailLoading.value = false |
| | |
| | | const pageData = res.data || {} |
| | | const records = Array.isArray(pageData.records) ? pageData.records : [] |
| | | orderData.value = records.map((item: any) => { |
| | | const statusName = item.orderStatusName || '-' |
| | | const statusStyle = getOrderStatusStyle(statusName) |
| | | const statusCode = item.orderStatus != null ? Number(item.orderStatus) : undefined |
| | | const statusName = item.orderStatusName || (statusCode != null ? ORDER_STATUS_LABEL_MAP[statusCode] : '-') || '-' |
| | | const statusStyle = getOrderStatusStyle(statusCode, statusName) |
| | | return { |
| | | orderId: item.orderId ?? item.id, |
| | | orderNo: item.orderNumber || '-', |
| | |
| | | |
| | | vehicleLoading.value = true |
| | | try { |
| | | const res = await fetchTestPlanVehiclePageList({ |
| | | pageNum: vehiclePageParams.value.pageNum, |
| | | pageSize: vehiclePageParams.value.pageSize, |
| | | const res = await fetchTestPlanVehicleList({ |
| | | testPlanId: currentPlanId.value, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取测试车辆失败') |
| | | } |
| | | const pageData = res.data || {} |
| | | const records = Array.isArray(pageData.records) ? pageData.records : [] |
| | | const rawData = res.data |
| | | const records = Array.isArray(rawData) |
| | | ? rawData |
| | | : Array.isArray(rawData?.records) |
| | | ? rawData.records |
| | | : [] |
| | | vehicleData.value = records.map((item: any) => { |
| | | const status = Number(item.vehicleStatus ?? item.paused ?? 0) |
| | | return { |
| | |
| | | statusColor: VEHICLE_STATUS_STYLE_MAP[status] || '#8C9299', |
| | | } |
| | | }) |
| | | vehicleCount.value = Number(pageData.total || 0) |
| | | } |
| | | catch (error: any) { |
| | | vehicleData.value = [] |
| | | vehicleCount.value = 0 |
| | | message.error(error?.message || '获取测试车辆失败') |
| | | } |
| | | finally { |
| | |
| | | |
| | | const orderColumns = computed<DataTableColumns<OrderRow>>(() => [ |
| | | { type: 'selection', width: 50, align: 'center' }, |
| | | { title: '订单编号', key: 'orderNo', width: 160 }, |
| | | { title: '订单编号', key: 'orderNo', width: 160 ,ellipsis: { tooltip: true } }, |
| | | { title: '下发时间', key: 'issueTime', width: 180 }, |
| | | { title: '起点', key: 'startPoint', width: 180, ellipsis: { tooltip: true } }, |
| | | { title: '终点', key: 'endPoint', width: 180, ellipsis: { tooltip: true } }, |
| | |
| | | function handleReset() { |
| | | searchModel.value = { |
| | | orderNo: '', |
| | | vehicleId: null, |
| | | dispatchTimeRange: null, |
| | | startAddress: null, |
| | | endAddress: null, |
| | | plateNumber: null, |
| | | orderStatus: null, |
| | | } |
| | | orderPageParams.value.pageNum = 1 |
| | | fetchOrderList() |
| | |
| | | orderPageParams.value.pageNum = size !== orderPageParams.value.pageSize ? 1 : page |
| | | orderPageParams.value.pageSize = size |
| | | fetchOrderList() |
| | | } |
| | | |
| | | function handleVehiclePageChange(page: number, size: number) { |
| | | vehiclePageParams.value.pageNum = size !== vehiclePageParams.value.pageSize ? 1 : page |
| | | vehiclePageParams.value.pageSize = size |
| | | fetchVehicleList() |
| | | } |
| | | |
| | | async function handleExportOrders() { |
| | |
| | | orderData.value = [] |
| | | vehicleData.value = [] |
| | | orderCount.value = 0 |
| | | vehicleCount.value = 0 |
| | | pointOptions.value = [] |
| | | return |
| | | } |
| | | await loadPageData() |
| | |
| | | </div> |
| | | </template> |
| | | </n-data-table> |
| | | <Pagination |
| | | :count="vehicleCount" |
| | | :page="vehiclePageParams.pageNum" |
| | | :page-size="vehiclePageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px shrink-0" |
| | | @change="handleVehiclePageChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |