| | |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/start', data) |
| | | } |
| | | |
| | | export function fetchTestPlanMonitorDetail(testPlanId: number | string) { |
| | | return request.Get<Service.ResponseResult<any>>(`/operation/api/v1/test-plan-demo/detail/monitor/${testPlanId}`) |
| | | } |
| | | |
| | | export function interruptTestPlanDispatch(planId: number | string) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/dispatch/interrupt', null, { |
| | | params: { planId }, |
| | | }) |
| | | } |
| | | |
| | | export function resumeTestPlanDispatch(planId: number | string) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/dispatch/resume', null, { |
| | | params: { planId }, |
| | | }) |
| | | } |
| | | |
| | | export function fetchTestPlanDetailOrderPageList(data: { |
| | | dispatchEndTime?: string |
| | | dispatchStartTime?: string |
| | | dispatchStatus?: number |
| | | orderNumber?: string |
| | | pageNum: number |
| | | pageSize: number |
| | | testPlanId: number | string |
| | | vehicleId?: number | string |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/detail/pageOrders', data) |
| | | } |
| | | |
| | | export function exportTestPlanDetailOrders(data: { |
| | | dispatchEndTime?: string |
| | | dispatchStartTime?: string |
| | | dispatchStatus?: number |
| | | orderNumber?: string |
| | | pageNum?: number |
| | | pageSize?: number |
| | | testPlanId: number | string |
| | | vehicleId?: number | string |
| | | }) { |
| | | const method = request.Post<Blob>('/operation/api/v1/test-plan-demo/detail/exportOrders', data, { |
| | | responseType: 'blob', |
| | | }) |
| | | method.meta = { ...method.meta, isBlob: true } |
| | | return method |
| | | } |
| | | |
| | | export function cancelTestPlanOrder(orderId: number | string, testPlanId: number | string) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/detail/order/cancel', null, { |
| | | params: { orderId, testPlanId }, |
| | | }) |
| | | } |
| | | |
| | | export function closeTestPlanOrder(orderId: number | string, planId: number | string) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/order/close', null, { |
| | | params: { orderId, planId }, |
| | | }) |
| | | } |
| | | |
| | | export function reassignTestPlanOrder(data: { |
| | | orderId: number | string |
| | | planId: number | string |
| | | vehicleId: number | string |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/order/reassign', data) |
| | | } |
| | | |
| | | export function fetchTestPlanVehiclePageList(data: { |
| | | pageNum: number |
| | | pageSize: number |
| | | plateNumber?: string |
| | | testPlanId: number | string |
| | | vehicleCode?: string |
| | | vehicleStatus?: number |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/detail/pageVehicles', data) |
| | | } |
| | | |
| | | export function pauseTestPlanVehicles(data: { |
| | | testPlanId: number | string |
| | | vehicleIds: Array<number | string> |
| | | paused: 0 | 1 |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/detail/vehicle/pause', data) |
| | | } |
| | | |
| | | export function manageTestPlanVehicles(data: { |
| | | addVehicleIds?: Array<number | string> |
| | | planId: number | string |
| | | removeVehicleIds?: Array<number | string> |
| | | }) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/test-plan-demo/vehicle/manage', data) |
| | | } |
| | |
| | | "vehicleIds": [ 测试车辆ID列表 |
| | | 0 |
| | | ] |
| | | } |
| | | |
| | | 详情接口: |
| | | |
| | | /api/v1/test-plan-demo/detail/monitor/{testPlanId} 下发状态+订单统计+车辆状态统计 get |
| | | 响应:{ |
| | | "code": 0, |
| | | "data": { |
| | | "dispatchStatus": 0, 下发状态 1=下发中 2=已中断 3=已完成 |
| | | "dispatchStatusName": "string", 下发状态文案 |
| | | "interruptAllowed": true, 是否允许中断下发 |
| | | "issuedCount": 0, 已下发数量 |
| | | "testPlanId": 0, |
| | | "totalCount": 0, 订单总数 |
| | | "unissuedCount": 0, 未下发数量 |
| | | "vehicleStatusSummary": { |
| | | "cruising": 0, |
| | | "idle": 0, |
| | | "servicePaused": 0, |
| | | "serving": 0, |
| | | "total": 0 |
| | | } |
| | | }, |
| | | "msg": "string" |
| | | } |
| | | |
| | | 中断下发(冻结所有待下发订单) /api/v1/test-plan-demo/dispatch/interrupt post params |
| | | 请求: planId |
| | | |
| | | |
| | | |
| | | 继续下发(恢复已冻结订单) /api/v1/test-plan-demo/dispatch/resume post params |
| | | 请求: planId |
| | | |
| | | |
| | | /api/v1/test-plan-demo/detail/pageOrders 订单分页 post json |
| | | 请求:{ |
| | | "dispatchEndTime": "string", |
| | | "dispatchStartTime": "string", |
| | | "dispatchStatus": 0, |
| | | "orderNumber": "string", |
| | | "pageNum": 0, |
| | | "pageSize": 0, |
| | | "testPlanId": 0, |
| | | "vehicleId": 0 |
| | | } |
| | | |
| | | 响应:{ |
| | | "code": 0, |
| | | "data": { |
| | | "current": 0, |
| | | "hasNextPage": true, |
| | | "hasPrevPage": true, |
| | | "pages": 0, |
| | | "records": [ |
| | | { |
| | | "actionCodes": [ |
| | | "string" |
| | | ], |
| | | "dispatchStatus": 0, |
| | | "dispatchTime": "string", |
| | | "endAddress": "string", |
| | | "fleetId": 0, |
| | | "fleetName": "string", |
| | | "orderId": 0, |
| | | "orderNumber": "string", |
| | | "orderSource": 0, |
| | | "orderStatus": 0, |
| | | "orderStatusName": "string", |
| | | "plateNumber": "string", |
| | | "startAddress": "string", |
| | | "vehicleId": 0 |
| | | } |
| | | ], |
| | | "size": 0, |
| | | "startIndex": 0, |
| | | "total": 0 |
| | | }, |
| | | "msg": "string" |
| | | } |
| | | |
| | | 导出订单列表 /api/v1/test-plan-demo/detail/exportOrders post json |
| | | 请求: |
| | | |
| | | |
| | | 取消订单 /api/v1/test-plan-demo/detail/order/cancel post params |
| | | 请求: orderId testPlanId |
| | | |
| | | 关闭订单(仅服务中/待客下车的已下发订单可操作) /api/v1/test-plan-demo/order/close post params |
| | | 请求: orderId planId |
| | | |
| | | |
| | | |
| | | 改派/分派订单 /api/v1/test-plan-demo/order/reassign post json |
| | | 请求: { |
| | | "orderId": 0, 要改派的订单ID |
| | | "planId": 0, 测试计划ID |
| | | "vehicleId": 0 改派后的车辆ID |
| | | } |
| | | |
| | | 删除订单 /api/v1/test-plan-demo/order/delete post json |
| | | 请求:{ |
| | | "orderIds": [ |
| | | 0 |
| | | ], |
| | | "planId": 0 |
| | | } |
| | | |
| | | |
| | | 车辆状态分页 /api/v1/test-plan-demo/detail/pageVehicles post json |
| | | 请求: |
| | | { |
| | | "pageNum": 0, |
| | | "pageSize": 0, |
| | | "plateNumber": "string", |
| | | "testPlanId": 0, |
| | | "vehicleCode": "string", |
| | | "vehicleStatus": 0 车辆状态 0=空闲 1=服务中 2=巡游中 3=暂停服务,不传查全部 |
| | | } |
| | | 响应:{ |
| | | "code": 0, |
| | | "data": { |
| | | "current": 0, |
| | | "hasNextPage": true, |
| | | "hasPrevPage": true, |
| | | "pages": 0, |
| | | "records": [ |
| | | { |
| | | "fleetId": 0, |
| | | "fleetName": "string", |
| | | "paused": 0, |
| | | "plateNumber": "string", |
| | | "vehicleCode": "string", |
| | | "vehicleId": 0, |
| | | "vehicleModel": "string", |
| | | "vehicleStatus": 0, |
| | | "vehicleStatusName": "string" |
| | | } |
| | | ], |
| | | "size": 0, |
| | | "startIndex": 0, |
| | | "total": 0 |
| | | }, |
| | | "msg": "string" |
| | | } |
| | | |
| | | |
| | | 暂停/恢复车辆(paused=1暂停,0恢复) /api/v1/test-plan-demo/detail/vehicle/pause post json |
| | | 请求:{ |
| | | testPlanId:‘, 测试计划ID |
| | | vehicleIds:[], 车辆ID列表 |
| | | paused:0 paused=1暂停,0恢复 |
| | | } |
| | | |
| | | 管理测试车辆(增删,越权校验:只能操作属于该计划的车辆) /api/v1/test-plan-demo/vehicle/manage post json |
| | | 请求: { |
| | | "addVehicleIds": [ |
| | | 0 |
| | | ], |
| | | "planId": 0, |
| | | "removeVehicleIds": [ |
| | | 0 |
| | | ] |
| | | } |
| | |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { computed, ref, watch } from 'vue' |
| | | import { NModal, NCard, NInput, NButton, NDataTable, NSelect, NTag } from 'naive-ui' |
| | | import { NButton, NCard, NDataTable, NInput, NModal, NSelect, NTag, useMessage } from 'naive-ui' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import { fetchTestPlanVehiclePageList, reassignTestPlanOrder } from '@/service/api/testPlanManagement' |
| | | |
| | | interface VehicleRow { |
| | | id: number |
| | | id: number | string |
| | | vehicleCode: string |
| | | plateNo: string |
| | | model: string |
| | | procurementSource: string |
| | | fleet: string |
| | | status: number |
| | | statusName: string |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<{ |
| | | const props = defineProps<{ |
| | | visible: boolean |
| | | }>(), {}) |
| | | planId?: number | string |
| | | orderId?: number | string | null |
| | | }>() |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: 'update:visible', val: boolean): void |
| | | (e: 'confirm', data: VehicleRow[]): void |
| | | (e: 'success'): void |
| | | }>() |
| | | |
| | | const message = useMessage() |
| | | |
| | | const show = computed({ |
| | | get: () => props.visible, |
| | | set: (val) => emit('update:visible', val) |
| | | set: val => emit('update:visible', val), |
| | | }) |
| | | |
| | | const model = ref({ |
| | | plateNo: '', |
| | | vehicleCode: '', |
| | | status: null |
| | | status: null as number | null, |
| | | }) |
| | | |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const listData = ref<VehicleRow[]>([]) |
| | | const checkedRowKeys = ref<number[]>([]) |
| | | const checkedRowKeys = ref<Array<number | string>>([]) |
| | | const count = ref(0) |
| | | const loading = ref(false) |
| | | const allLoadedData = ref<Map<number, VehicleRow>>(new Map()) |
| | | |
| | | const submitting = ref(false) |
| | | const isStatusSelectOpen = ref(false) |
| | | |
| | | const statusOptions = [ |
| | | { label: '行程中', value: 1 }, |
| | | { label: '空闲', value: 2 }, |
| | | { label: '空闲', value: 0 }, |
| | | { label: '服务中', value: 1 }, |
| | | { label: '巡游中', value: 2 }, |
| | | { label: '暂停服务', value: 3 }, |
| | | ] |
| | | |
| | | function getStatusColor(status: number) { |
| | | switch (status) { |
| | | case 1: // 行程中 |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | case 2: // 空闲 |
| | | case 0: |
| | | return { textColor: '#10B981', color: '#F6FFED' } |
| | | case 3: // 暂停服务 |
| | | case 1: |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | case 2: |
| | | return { textColor: '#7C3AED', color: '#F3E8FF' } |
| | | case 3: |
| | | return { textColor: '#F59E0B', color: '#FFF7E6' } |
| | | default: |
| | | return { textColor: '#333333', color: '#F5F5F5' } |
| | | } |
| | | } |
| | | |
| | | function getStatusLabel(status: number) { |
| | | function getStatusLabel(status: number, statusName?: string) { |
| | | if (statusName) |
| | | return statusName |
| | | return statusOptions.find(item => item.value === status)?.label || '-' |
| | | } |
| | | |
| | | function getMockList() { |
| | | return [ |
| | | { id: 1, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 1 }, |
| | | { id: 2, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 2 }, |
| | | { id: 3, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 2 }, |
| | | { id: 4, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 3 }, |
| | | { id: 5, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 3 }, |
| | | ] |
| | | } |
| | | async function getList() { |
| | | if (!props.planId) |
| | | return |
| | | |
| | | function getList() { |
| | | loading.value = true |
| | | const sourceList = getMockList() |
| | | const plateNo = model.value.plateNo.trim() |
| | | const vehicleCode = model.value.vehicleCode.trim() |
| | | const status = model.value.status |
| | | const filteredList = sourceList.filter((item) => { |
| | | const matchPlateNo = !plateNo || item.plateNo.includes(plateNo) |
| | | const matchVehicleCode = !vehicleCode || item.vehicleCode.includes(vehicleCode) |
| | | const matchStatus = !status || item.status === status |
| | | return matchPlateNo && matchVehicleCode && matchStatus |
| | | }) |
| | | |
| | | count.value = 1000 // mock pagination total |
| | | |
| | | const start = (pageParams.value.pageNum - 1) * pageParams.value.pageSize |
| | | const end = start + pageParams.value.pageSize |
| | | // mock real slice if we had real data |
| | | listData.value = filteredList |
| | | listData.value.forEach((item) => { |
| | | allLoadedData.value.set(item.id, item) |
| | | }) |
| | | loading.value = false |
| | | try { |
| | | const res = await fetchTestPlanVehiclePageList({ |
| | | pageNum: pageParams.value.pageNum, |
| | | pageSize: pageParams.value.pageSize, |
| | | plateNumber: model.value.plateNo.trim() || undefined, |
| | | testPlanId: props.planId, |
| | | vehicleCode: model.value.vehicleCode.trim() || undefined, |
| | | vehicleStatus: model.value.status ?? undefined, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取车辆列表失败') |
| | | } |
| | | const pageData = res.data || {} |
| | | const records = Array.isArray(pageData.records) ? pageData.records : [] |
| | | listData.value = records.map((item: any) => ({ |
| | | id: item.vehicleId ?? item.id, |
| | | vehicleCode: item.vehicleCode || '-', |
| | | plateNo: item.plateNumber || '-', |
| | | model: item.vehicleModel || '-', |
| | | fleet: item.fleetName || '-', |
| | | status: Number(item.vehicleStatus ?? 0), |
| | | statusName: item.vehicleStatusName || '', |
| | | })) |
| | | count.value = Number(pageData.total || 0) |
| | | } |
| | | catch (error: any) { |
| | | listData.value = [] |
| | | count.value = 0 |
| | | message.error(error?.message || '获取车辆列表失败') |
| | | } |
| | | finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.visible, |
| | | (val) => { |
| | | val => { |
| | | if (val) { |
| | | model.value = { plateNo: '', vehicleCode: '', status: null } |
| | | pageParams.value = { pageNum: 1, pageSize: 10 } |
| | | allLoadedData.value.clear() |
| | | checkedRowKeys.value = [] |
| | | getList() |
| | | } |
| | | else { |
| | | checkedRowKeys.value = [] |
| | | allLoadedData.value.clear() |
| | | } |
| | | } |
| | | }, |
| | | ) |
| | | |
| | | function handleSearch() { |
| | |
| | | function handleReset() { |
| | | model.value = { plateNo: '', vehicleCode: '', status: null } |
| | | pageParams.value.pageNum = 1 |
| | | pageParams.value.pageSize = 10 |
| | | getList() |
| | | } |
| | | |
| | |
| | | show.value = false |
| | | } |
| | | |
| | | function handleConfirm() { |
| | | const selectedRows = checkedRowKeys.value |
| | | .map(id => allLoadedData.value.get(id)) |
| | | .filter((item): item is VehicleRow => Boolean(item)) |
| | | emit('confirm', selectedRows) |
| | | show.value = false |
| | | async function handleConfirm() { |
| | | if (!props.planId || !props.orderId) { |
| | | message.warning('订单信息缺失') |
| | | return |
| | | } |
| | | if (checkedRowKeys.value.length !== 1) { |
| | | message.warning('请选择一辆车辆进行分派') |
| | | return |
| | | } |
| | | |
| | | submitting.value = true |
| | | try { |
| | | const res = await reassignTestPlanOrder({ |
| | | orderId: props.orderId, |
| | | planId: props.planId, |
| | | vehicleId: checkedRowKeys.value[0], |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '分派车辆失败') |
| | | } |
| | | message.success('分派车辆成功') |
| | | emit('success') |
| | | show.value = false |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '分派车辆失败') |
| | | } |
| | | finally { |
| | | submitting.value = false |
| | | } |
| | | } |
| | | |
| | | const columns = computed<DataTableColumns<VehicleRow>>(() => [ |
| | |
| | | title: '序号', |
| | | key: 'index', |
| | | width: 80, |
| | | render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 |
| | | render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1, |
| | | }, |
| | | { title: '车辆编号', key: 'vehicleCode', width: 160 }, |
| | | { title: '车辆编号', key: 'vehicleCode', width: 180 }, |
| | | { title: '车牌号', key: 'plateNo', width: 160 }, |
| | | { title: '型号', key: 'model', width: 160 }, |
| | | { title: '采购来源', key: 'procurementSource', width: 160 }, |
| | | { title: '当前车队', key: 'fleet', flex: 1, ellipsis: true }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | width: 120, |
| | | render: (row) => { |
| | | return ( |
| | | <NTag |
| | | color={getStatusColor(row.status)} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {getStatusLabel(row.status)} |
| | | </NTag> |
| | | ) |
| | | } |
| | | } |
| | | render: row => ( |
| | | <NTag |
| | | color={getStatusColor(row.status)} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {getStatusLabel(row.status, row.statusName)} |
| | | </NTag> |
| | | ), |
| | | }, |
| | | ]) |
| | | </script> |
| | | |
| | |
| | | <template #footer> |
| | | <div class="flex justify-end gap-3" style="gap: 12px;"> |
| | | <n-button class="footer-btn" @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" class="footer-btn" @click="handleConfirm">确认下发</n-button> |
| | | <n-button type="primary" class="footer-btn" :loading="submitting" @click="handleConfirm">确认下发</n-button> |
| | | </div> |
| | | </template> |
| | | </n-card> |
| | |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { computed, ref, watch } from 'vue' |
| | | import { NModal, NCard, NInput, NButton, NDataTable, NSelect, NTag, NSpace, useMessage, useDialog } from 'naive-ui' |
| | | import { NButton, NCard, NDataTable, NInput, NModal, NSelect, NSpace, NTag, useDialog, useMessage } from 'naive-ui' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import { fetchTestPlanVehiclePageList, manageTestPlanVehicles, pauseTestPlanVehicles } from '@/service/api/testPlanManagement' |
| | | import VehicleSelectModal from './VehicleSelectModal.vue' |
| | | |
| | | interface VehicleRow { |
| | | id: number |
| | | id: number | string |
| | | vehicleCode: string |
| | | plateNo: string |
| | | model: string |
| | | status: number |
| | | statusName: string |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<{ |
| | | interface VehicleSelectRow { |
| | | id: number | string |
| | | } |
| | | |
| | | const props = defineProps<{ |
| | | visible: boolean |
| | | }>(), {}) |
| | | planId?: number | string |
| | | }>() |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: 'update:visible', val: boolean): void |
| | | (e: 'add-vehicle'): void |
| | | (e: 'success'): void |
| | | }>() |
| | | |
| | | const message = useMessage() |
| | |
| | | |
| | | const show = computed({ |
| | | get: () => props.visible, |
| | | set: (val) => emit('update:visible', val) |
| | | set: val => emit('update:visible', val), |
| | | }) |
| | | |
| | | const searchModel = ref({ |
| | | plateNo: '', |
| | | vehicleCode: '', |
| | | status: null |
| | | status: null as number | null, |
| | | }) |
| | | |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const listData = ref<VehicleRow[]>([]) |
| | | const checkedRowKeys = ref<number[]>([]) |
| | | const checkedRowKeys = ref<Array<number | string>>([]) |
| | | const count = ref(0) |
| | | const loading = ref(false) |
| | | |
| | | const actionLoading = ref(false) |
| | | const isStatusSelectOpen = ref(false) |
| | | const vehicleSelectVisible = ref(false) |
| | | |
| | | const statusOptions = [ |
| | | { label: '行程中', value: 1 }, |
| | | { label: '空闲', value: 2 }, |
| | | { label: '空闲', value: 0 }, |
| | | { label: '服务中', value: 1 }, |
| | | { label: '巡游中', value: 2 }, |
| | | { label: '暂停服务', value: 3 }, |
| | | ] |
| | | |
| | | function getStatusColor(status: number) { |
| | | switch (status) { |
| | | case 1: // 行程中 |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | case 2: // 空闲 |
| | | case 0: |
| | | return { textColor: '#10B981', color: '#F6FFED' } |
| | | case 3: // 暂停服务 |
| | | case 1: |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | case 2: |
| | | return { textColor: '#7C3AED', color: '#F3E8FF' } |
| | | case 3: |
| | | return { textColor: '#F59E0B', color: '#FFF7E6' } |
| | | default: |
| | | return { textColor: '#333333', color: '#F5F5F5' } |
| | | } |
| | | } |
| | | |
| | | function getStatusLabel(status: number) { |
| | | function getStatusLabel(status: number, statusName?: string) { |
| | | if (statusName) |
| | | return statusName |
| | | return statusOptions.find(item => item.value === status)?.label || '-' |
| | | } |
| | | |
| | | function getMockList() { |
| | | return [ |
| | | { id: 1, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', status: 1 }, |
| | | { id: 2, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', status: 2 }, |
| | | { id: 3, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', status: 2 }, |
| | | { id: 4, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', status: 3 }, |
| | | { id: 5, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', status: 3 }, |
| | | ] |
| | | } |
| | | async function getList() { |
| | | if (!props.planId) |
| | | return |
| | | |
| | | function getList() { |
| | | loading.value = true |
| | | const sourceList = getMockList() |
| | | const plateNo = searchModel.value.plateNo.trim() |
| | | const vehicleCode = searchModel.value.vehicleCode.trim() |
| | | const status = searchModel.value.status |
| | | const filteredList = sourceList.filter((item) => { |
| | | const matchPlateNo = !plateNo || item.plateNo.includes(plateNo) |
| | | const matchVehicleCode = !vehicleCode || item.vehicleCode.includes(vehicleCode) |
| | | const matchStatus = !status || item.status === status |
| | | return matchPlateNo && matchVehicleCode && matchStatus |
| | | }) |
| | | |
| | | count.value = 1000 // mock pagination total |
| | | |
| | | const start = (pageParams.value.pageNum - 1) * pageParams.value.pageSize |
| | | const end = start + pageParams.value.pageSize |
| | | listData.value = filteredList |
| | | loading.value = false |
| | | try { |
| | | const res = await fetchTestPlanVehiclePageList({ |
| | | pageNum: pageParams.value.pageNum, |
| | | pageSize: pageParams.value.pageSize, |
| | | plateNumber: searchModel.value.plateNo.trim() || undefined, |
| | | testPlanId: props.planId, |
| | | vehicleCode: searchModel.value.vehicleCode.trim() || undefined, |
| | | vehicleStatus: searchModel.value.status ?? undefined, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取测试车辆失败') |
| | | } |
| | | const pageData = res.data || {} |
| | | const records = Array.isArray(pageData.records) ? pageData.records : [] |
| | | listData.value = records.map((item: any) => ({ |
| | | id: item.vehicleId ?? item.id, |
| | | vehicleCode: item.vehicleCode || '-', |
| | | plateNo: item.plateNumber || '-', |
| | | model: item.vehicleModel || '-', |
| | | status: Number(item.vehicleStatus ?? 0), |
| | | statusName: item.vehicleStatusName || '', |
| | | })) |
| | | count.value = Number(pageData.total || 0) |
| | | } |
| | | catch (error: any) { |
| | | listData.value = [] |
| | | count.value = 0 |
| | | message.error(error?.message || '获取测试车辆失败') |
| | | } |
| | | finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.visible, |
| | | (val) => { |
| | | val => { |
| | | if (val) { |
| | | searchModel.value = { plateNo: '', vehicleCode: '', status: null } |
| | | pageParams.value = { pageNum: 1, pageSize: 10 } |
| | | checkedRowKeys.value = [] |
| | | getList() |
| | | } |
| | | } |
| | | else { |
| | | checkedRowKeys.value = [] |
| | | } |
| | | }, |
| | | ) |
| | | |
| | | function handleSearch() { |
| | |
| | | function handleReset() { |
| | | searchModel.value = { plateNo: '', vehicleCode: '', status: null } |
| | | pageParams.value.pageNum = 1 |
| | | pageParams.value.pageSize = 10 |
| | | getList() |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function handleSave() { |
| | | message.success('保存成功') |
| | | show.value = false |
| | | } |
| | | |
| | | // 批量操作 |
| | | async function runVehicleManage(params: { addVehicleIds?: Array<number | string>, removeVehicleIds?: Array<number | string> }, successText: string) { |
| | | if (!props.planId) |
| | | return |
| | | |
| | | actionLoading.value = true |
| | | try { |
| | | const res = await manageTestPlanVehicles({ |
| | | planId: props.planId, |
| | | addVehicleIds: params.addVehicleIds || [], |
| | | removeVehicleIds: params.removeVehicleIds || [], |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || successText) |
| | | } |
| | | message.success(successText) |
| | | checkedRowKeys.value = [] |
| | | await getList() |
| | | emit('success') |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || successText) |
| | | } |
| | | finally { |
| | | actionLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function runVehiclePause(vehicleIds: Array<number | string>, paused: 0 | 1, successText: string) { |
| | | if (!props.planId) |
| | | return |
| | | |
| | | actionLoading.value = true |
| | | try { |
| | | const res = await pauseTestPlanVehicles({ |
| | | testPlanId: props.planId, |
| | | vehicleIds, |
| | | paused, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || successText) |
| | | } |
| | | message.success(successText) |
| | | checkedRowKeys.value = [] |
| | | await getList() |
| | | emit('success') |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || successText) |
| | | } |
| | | finally { |
| | | actionLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleBatchRemove() { |
| | | if (!checkedRowKeys.value.length) { |
| | | message.warning('请先选择车辆') |
| | |
| | | content: '移除后将从测试计划中删除', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | message.success('批量移除成功') |
| | | checkedRowKeys.value = [] |
| | | getList() |
| | | } |
| | | onPositiveClick: async () => { |
| | | await runVehicleManage({ removeVehicleIds: checkedRowKeys.value }, '批量移除成功') |
| | | }, |
| | | }) |
| | | } |
| | | |
| | |
| | | message.warning('请先选择车辆') |
| | | return |
| | | } |
| | | message.success('批量暂停成功') |
| | | checkedRowKeys.value = [] |
| | | getList() |
| | | runVehiclePause(checkedRowKeys.value, 1, '批量暂停成功') |
| | | } |
| | | |
| | | function handleBatchRestart() { |
| | |
| | | message.warning('请先选择车辆') |
| | | return |
| | | } |
| | | message.success('批量重启成功') |
| | | checkedRowKeys.value = [] |
| | | getList() |
| | | runVehiclePause(checkedRowKeys.value, 0, '批量重启成功') |
| | | } |
| | | |
| | | function handleAddVehicle() { |
| | | emit('add-vehicle') |
| | | vehicleSelectVisible.value = true |
| | | } |
| | | |
| | | // 单个操作 |
| | | function handleRemove(row: VehicleRow) { |
| | | dialog.warning({ |
| | | title: '确认移除该车辆吗?', |
| | | content: '移除后将从测试计划中删除', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | message.success('移除成功') |
| | | getList() |
| | | } |
| | | onPositiveClick: async () => { |
| | | await runVehicleManage({ removeVehicleIds: [row.id] }, '移除成功') |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | function handleToggleStatus(row: VehicleRow) { |
| | | if (row.status === 3) { |
| | | message.success('重启服务成功') |
| | | } else { |
| | | message.success('暂停服务成功') |
| | | } |
| | | getList() |
| | | const paused: 0 | 1 = row.status === 3 ? 0 : 1 |
| | | const successText = row.status === 3 ? '重启服务成功' : '暂停服务成功' |
| | | runVehiclePause([row.id], paused, successText) |
| | | } |
| | | |
| | | async function handleVehicleSelectConfirm(rows: VehicleSelectRow[]) { |
| | | if (!rows.length) { |
| | | message.warning('请选择至少一辆测试车辆') |
| | | return |
| | | } |
| | | await runVehicleManage({ addVehicleIds: rows.map(item => item.id) }, '添加测试车辆成功') |
| | | vehicleSelectVisible.value = false |
| | | } |
| | | |
| | | const existingVehicles = computed(() => |
| | | listData.value.map(item => ({ |
| | | id: item.id, |
| | | vehicleCode: item.vehicleCode, |
| | | plateNo: item.plateNo, |
| | | model: item.model, |
| | | procurementSource: '-', |
| | | fleet: '-', |
| | | })), |
| | | ) |
| | | |
| | | const columns = computed<DataTableColumns<VehicleRow>>(() => [ |
| | | { type: 'selection', width: 60, align: 'center' }, |
| | |
| | | title: '序号', |
| | | key: 'index', |
| | | width: 80, |
| | | render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 |
| | | render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1, |
| | | }, |
| | | { title: '车辆编号', key: 'vehicleCode', width: 160 }, |
| | | { title: '车辆编号', key: 'vehicleCode', width: 180 }, |
| | | { title: '车牌号', key: 'plateNo', width: 160 }, |
| | | { title: '型号', key: 'model', width: 160 }, |
| | | { |
| | | title: '当前状态', |
| | | key: 'status', |
| | | width: 120, |
| | | render: (row) => { |
| | | return ( |
| | | <NTag |
| | | color={getStatusColor(row.status)} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {getStatusLabel(row.status)} |
| | | </NTag> |
| | | ) |
| | | } |
| | | render: row => ( |
| | | <NTag |
| | | color={getStatusColor(row.status)} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {getStatusLabel(row.status, row.statusName)} |
| | | </NTag> |
| | | ), |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 180, |
| | | render: (row) => { |
| | | return ( |
| | | <NSpace size={16}> |
| | | <span |
| | | class="cursor-pointer-text" |
| | | onClick={() => handleRemove(row)} |
| | | > |
| | | 移除 |
| | | </span> |
| | | <span |
| | | class="cursor-pointer-text" |
| | | onClick={() => handleToggleStatus(row)} |
| | | > |
| | | {row.status === 3 ? '重启服务' : '暂停服务'} |
| | | </span> |
| | | </NSpace> |
| | | ) |
| | | } |
| | | } |
| | | render: row => ( |
| | | <NSpace size={16}> |
| | | <span |
| | | class="cursor-pointer-text" |
| | | onClick={() => handleRemove(row)} |
| | | > |
| | | 移除 |
| | | </span> |
| | | <span |
| | | class="cursor-pointer-text" |
| | | onClick={() => handleToggleStatus(row)} |
| | | > |
| | | {row.status === 3 ? '重启服务' : '暂停服务'} |
| | | </span> |
| | | </NSpace> |
| | | ), |
| | | }, |
| | | ]) |
| | | </script> |
| | | |
| | |
| | | <template #footer> |
| | | <div class="flex justify-end gap-3" style="gap: 12px;"> |
| | | <n-button class="footer-btn" @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" class="footer-btn" @click="handleSave">保存</n-button> |
| | | <n-button type="primary" class="footer-btn" :loading="actionLoading" @click="handleSave">保存</n-button> |
| | | </div> |
| | | </template> |
| | | </n-card> |
| | | </n-modal> |
| | | |
| | | <VehicleSelectModal |
| | | v-model:visible="vehicleSelectVisible" |
| | | :existing-vehicles="existingVehicles" |
| | | @confirm="handleVehicleSelectConfirm" |
| | | /> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | <script setup lang="tsx"> |
| | | import { ref, computed } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { NButton, NSpace, NTag, NForm, NFormItem, NInput, NSelect, NDataTable } from 'naive-ui' |
| | | 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 type { DataTableColumns } from 'naive-ui' |
| | | import { useMessage } from 'naive-ui' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import { downloadFile } from '@/utils/common' |
| | | import { |
| | | cancelTestPlanOrder, |
| | | closeTestPlanOrder, |
| | | deleteTestPlanOrders, |
| | | exportTestPlanDetailOrders, |
| | | fetchTestPlanDetail, |
| | | fetchTestPlanDetailOrderPageList, |
| | | fetchTestPlanMonitorDetail, |
| | | fetchTestPlanVehiclePageList, |
| | | interruptTestPlanDispatch, |
| | | resumeTestPlanDispatch, |
| | | } from '@/service/api/testPlanManagement' |
| | | import AssignVehicleModal from './components/AssignVehicleModal.vue' |
| | | import ManageTestVehicleModal from './components/ManageTestVehicleModal.vue' |
| | | import VehicleSelectModal from './components/VehicleSelectModal.vue' |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const dialog = useDialog() |
| | | const message = useMessage() |
| | | |
| | | // 顶部数据 |
| | | const planName = ref('2026年05月广州白云区巡游计划') |
| | | const planStatus = ref('下发中') |
| | | const totalCount = ref(100) |
| | | const issuedCount = ref(88) |
| | | const unissuedCount = ref(12) |
| | | const currentPlanId = computed(() => String(route.query.id || '')) |
| | | |
| | | // ================= 左侧订单列表数据与逻辑 ================= |
| | | const planName = ref('-') |
| | | const planStatus = ref('-') |
| | | const planStatusColor = ref('#0068FF') |
| | | const planStatusBg = ref('#E6F4FF') |
| | | const dispatchStatus = ref<number>(0) |
| | | const interruptAllowed = ref(false) |
| | | const totalCount = ref(0) |
| | | const issuedCount = ref(0) |
| | | const unissuedCount = ref(0) |
| | | |
| | | const detailLoading = ref(false) |
| | | const monitorLoading = ref(false) |
| | | const orderLoading = ref(false) |
| | | const vehicleLoading = ref(false) |
| | | const exportLoading = ref(false) |
| | | |
| | | const searchModel = ref({ |
| | | orderNo: '', |
| | | user: '', |
| | | vehicle: null, |
| | | fleet: null, |
| | | channel: null |
| | | vehicleId: null as number | string | null, |
| | | dispatchTimeRange: null as [number, number] | null, |
| | | }) |
| | | |
| | | const isVehicleSelectOpen = ref(false) |
| | | const isFleetSelectOpen = ref(false) |
| | | const isChannelSelectOpen = 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, |
| | | }) |
| | | |
| | | interface OrderRow { |
| | | orderId: number | string |
| | | orderNo: string |
| | | issueTime: string |
| | | startPoint: string |
| | | endPoint: string |
| | | vehicle: string |
| | | vehicleId?: number | string |
| | | status: string |
| | | statusColor: string |
| | | statusBg: string |
| | | actions: string[] |
| | | } |
| | | |
| | | interface VehicleRow { |
| | | id: number | string |
| | | plateNo: string |
| | | vehicleCode: string |
| | | fleetName: string |
| | | status: number |
| | | statusName: string |
| | | statusColor: string |
| | | } |
| | | |
| | | const orderData = ref<OrderRow[]>([]) |
| | | const vehicleData = ref<VehicleRow[]>([]) |
| | | |
| | | const isAssignVehicleModalVisible = ref(false) |
| | | const isManageTestVehicleModalVisible = ref(false) |
| | | const currentAssignOrderId = ref<number | string | null>(null) |
| | | |
| | | const PLAN_STATUS_STYLE_MAP: Record<number, { textColor: string, color: string }> = { |
| | | 1: { textColor: '#0068FF', color: '#E6F4FF' }, |
| | | 2: { textColor: '#F59E0B', color: '#FFF7E6' }, |
| | | 3: { textColor: '#10B981', color: '#F6FFED' }, |
| | | } |
| | | |
| | | const VEHICLE_STATUS_STYLE_MAP: Record<number, string> = { |
| | | 0: '#10B981', |
| | | 1: '#0068FF', |
| | | 2: '#7C3AED', |
| | | 3: '#F59E0B', |
| | | } |
| | | |
| | | const ACTION_LABEL_MAP: Record<string, string> = { |
| | | cancel: '取消订单', |
| | | cancel_order: '取消订单', |
| | | cancelOrder: '取消订单', |
| | | close: '关闭订单', |
| | | close_order: '关闭订单', |
| | | closeOrder: '关闭订单', |
| | | delete: '删除', |
| | | delete_order: '删除', |
| | | deleteOrder: '删除', |
| | | detail: '详情', |
| | | view_detail: '详情', |
| | | reassign: '改派', |
| | | re_assign: '改派', |
| | | assign: '分派车辆', |
| | | allocate: '分派车辆', |
| | | dispatch: '分派车辆', |
| | | } |
| | | |
| | | const interruptButtonVisible = computed(() => dispatchStatus.value === 1 || dispatchStatus.value === 2) |
| | | const interruptButtonText = computed(() => (dispatchStatus.value === 2 ? '继续下发' : '中断下发')) |
| | | |
| | | const vehicleOptions = computed(() => |
| | | vehicleData.value.map(item => ({ |
| | | label: item.plateNo, |
| | | value: item.id, |
| | | })), |
| | | ) |
| | | |
| | | const filterItems = computed(() => [ |
| | | { |
| | | label: '订单号', |
| | | path: 'orderNo', |
| | | render: () => <n-input v-model:value={searchModel.value.orderNo} placeholder="请输入" clearable /> |
| | | }, |
| | | { |
| | | label: '用户', |
| | | path: 'user', |
| | | render: () => <n-input v-model:value={searchModel.value.user} placeholder="请输入" clearable /> |
| | | render: () => <n-input v-model:value={searchModel.value.orderNo} placeholder="请输入" clearable />, |
| | | }, |
| | | { |
| | | label: '车辆', |
| | | path: 'vehicle', |
| | | path: 'vehicleId', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.vehicle} |
| | | options={[]} |
| | | v-model:value={searchModel.value.vehicleId} |
| | | options={vehicleOptions.value} |
| | | placeholder="请选择" |
| | | clearable |
| | | onUpdate:show={(val: boolean) => isVehicleSelectOpen.value = val} |
| | | onUpdate:show={(val: boolean) => (isVehicleSelectOpen.value = val)} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | |
| | | alt="no-data" |
| | | /> |
| | | </div> |
| | | ) |
| | | ), |
| | | }} |
| | | </n-select> |
| | | ) |
| | | ), |
| | | }, |
| | | { |
| | | label: '车队', |
| | | path: 'fleet', |
| | | label: '下发时间', |
| | | path: 'dispatchTimeRange', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.fleet} |
| | | options={[]} |
| | | placeholder="请选择" |
| | | <n-date-picker |
| | | v-model:value={searchModel.value.dispatchTimeRange} |
| | | type="datetimerange" |
| | | format="yyyy/MM/dd HH:mm:ss" |
| | | clearable |
| | | onUpdate:show={(val: boolean) => isFleetSelectOpen.value = val} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isFleetSelectOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | alt="no-data" |
| | | /> |
| | | </div> |
| | | ) |
| | | }} |
| | | </n-select> |
| | | ) |
| | | class="w-full" |
| | | /> |
| | | ), |
| | | }, |
| | | { |
| | | label: '渠道', |
| | | path: 'channel', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.channel} |
| | | options={[]} |
| | | placeholder="请选择" |
| | | clearable |
| | | onUpdate:show={(val: boolean) => isChannelSelectOpen.value = val} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isChannelSelectOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | alt="no-data" |
| | | /> |
| | | </div> |
| | | ) |
| | | }} |
| | | </n-select> |
| | | ) |
| | | } |
| | | ]) |
| | | |
| | | interface OrderRow { |
| | | id: number |
| | | orderNo: string |
| | | issueTime: string |
| | | startPoint: string |
| | | endPoint: string |
| | | vehicle: string |
| | | status: string |
| | | statusColor: string |
| | | statusBg: string |
| | | actions: string[] |
| | | function getOrderStatusStyle(statusName: string) { |
| | | if (!statusName) |
| | | 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('关闭')) |
| | | return { textColor: '#8C9299', color: '#F5F5F5' } |
| | | if (statusName.includes('服务') || statusName.includes('待出发') || statusName.includes('待接单') || statusName.includes('待乘客')) |
| | | return { textColor: '#0068FF', color: '#E6F4FF' } |
| | | return { textColor: '#10B981', color: '#F6FFED' } |
| | | } |
| | | |
| | | const orderCheckedRowKeys = ref<number[]>([]) |
| | | function normalizeActionLabel(actionCode: string) { |
| | | if (!actionCode) |
| | | return '' |
| | | if (actionCode.includes('取消订单') || actionCode.includes('关闭订单') || actionCode.includes('删除') || actionCode.includes('详情') || actionCode.includes('改派') || actionCode.includes('分派车辆')) |
| | | return actionCode |
| | | const normalizedKey = actionCode.replace(/-/g, '_') |
| | | return ACTION_LABEL_MAP[actionCode] || ACTION_LABEL_MAP[normalizedKey] || ACTION_LABEL_MAP[actionCode.toLowerCase()] || ACTION_LABEL_MAP[normalizedKey.toLowerCase()] || '' |
| | | } |
| | | |
| | | const orderData = ref<OrderRow[]>([ |
| | | { id: 1, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '待接单', statusColor: '#0068FF', statusBg: '#E6F4FF', actions: ['取消订单', '详情', '删除'] }, |
| | | { id: 2, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '待出发', statusColor: '#10B981', statusBg: '#F6FFED', actions: ['取消订单', '改派', '删除'] }, |
| | | { id: 3, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '待到达预约地', statusColor: '#F59E0B', statusBg: '#FFF7E6', actions: ['取消订单', '改派', '删除'] }, |
| | | { id: 4, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '待乘客上车', statusColor: '#0068FF', statusBg: '#E6F4FF', actions: ['取消订单', '改派', '删除'] }, |
| | | { id: 5, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '服务中', statusColor: '#10B981', statusBg: '#F6FFED', actions: ['关闭订单', '详情', '删除'] }, |
| | | { id: 6, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '已完成', statusColor: '#8C9299', statusBg: '#F5F5F5', actions: ['详情', '删除'] }, |
| | | { id: 7, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '川A999999', status: '待下发', statusColor: '#A034FF', statusBg: '#F4E6FF', actions: ['分派车辆', '删除'] }, |
| | | { id: 8, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '-', status: '待下发', statusColor: '#A034FF', statusBg: '#F4E6FF', actions: ['分派车辆', '删除'] }, |
| | | { id: 9, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '-', status: '待下发', statusColor: '#A034FF', statusBg: '#F4E6FF', actions: ['分派车辆', '删除'] }, |
| | | { id: 10, orderNo: '121314', issueTime: '2022/07/05 12:09', startPoint: '成都天府新谷', endPoint: '天府广场', vehicle: '-', status: '待下发', statusColor: '#A034FF', statusBg: '#F4E6FF', actions: ['分派车辆', '删除'] }, |
| | | ]) |
| | | function resolveOrderActions(actionCodes: unknown) { |
| | | if (!Array.isArray(actionCodes)) |
| | | return [] |
| | | return [...new Set(actionCodes.map(item => normalizeActionLabel(String(item))).filter(Boolean))] |
| | | } |
| | | |
| | | const isAssignVehicleModalVisible = ref(false) |
| | | const isManageTestVehicleModalVisible = ref(false) |
| | | const isVehicleSelectModalVisible = ref(false) |
| | | 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, |
| | | testPlanId: currentPlanId.value, |
| | | } |
| | | } |
| | | |
| | | async function fetchBaseDetail() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | |
| | | detailLoading.value = true |
| | | try { |
| | | const res = await fetchTestPlanDetail(currentPlanId.value) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取测试计划详情失败') |
| | | } |
| | | planName.value = res.data?.planName || '-' |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '获取测试计划详情失败') |
| | | planName.value = '-' |
| | | } |
| | | finally { |
| | | detailLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function fetchMonitorInfo() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | |
| | | monitorLoading.value = true |
| | | try { |
| | | const res = await fetchTestPlanMonitorDetail(currentPlanId.value) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取监控信息失败') |
| | | } |
| | | const monitor = res.data || {} |
| | | dispatchStatus.value = Number(monitor.dispatchStatus || 0) |
| | | planStatus.value = monitor.dispatchStatusName || '-' |
| | | interruptAllowed.value = Boolean(monitor.interruptAllowed) |
| | | totalCount.value = Number(monitor.totalCount || 0) |
| | | issuedCount.value = Number(monitor.issuedCount || 0) |
| | | unissuedCount.value = Number(monitor.unissuedCount || 0) |
| | | const statusStyle = PLAN_STATUS_STYLE_MAP[dispatchStatus.value] || { textColor: '#8C9299', color: '#F5F5F5' } |
| | | planStatusColor.value = statusStyle.textColor |
| | | planStatusBg.value = statusStyle.color |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '获取监控信息失败') |
| | | } |
| | | finally { |
| | | monitorLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function fetchOrderList() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | |
| | | orderLoading.value = true |
| | | try { |
| | | const res = await fetchTestPlanDetailOrderPageList({ |
| | | ...buildOrderQueryParams(), |
| | | pageNum: orderPageParams.value.pageNum, |
| | | pageSize: orderPageParams.value.pageSize, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '获取订单列表失败') |
| | | } |
| | | 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) |
| | | return { |
| | | orderId: item.orderId ?? item.id, |
| | | orderNo: item.orderNumber || '-', |
| | | issueTime: item.dispatchTime || '-', |
| | | startPoint: item.startAddress || '-', |
| | | endPoint: item.endAddress || '-', |
| | | vehicle: item.plateNumber || '-', |
| | | vehicleId: item.vehicleId, |
| | | status: statusName, |
| | | statusColor: statusStyle.textColor, |
| | | statusBg: statusStyle.color, |
| | | actions: resolveOrderActions(item.actionCodes), |
| | | } |
| | | }) |
| | | orderCount.value = Number(pageData.total || 0) |
| | | orderCheckedRowKeys.value = [] |
| | | } |
| | | catch (error: any) { |
| | | orderData.value = [] |
| | | orderCount.value = 0 |
| | | message.error(error?.message || '获取订单列表失败') |
| | | } |
| | | finally { |
| | | orderLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function normalizeVehicleStatusName(status: number, rawName: string) { |
| | | if (rawName) |
| | | return rawName |
| | | switch (status) { |
| | | case 0: |
| | | return '空闲' |
| | | case 1: |
| | | return '服务中' |
| | | case 2: |
| | | return '巡游中' |
| | | case 3: |
| | | return '暂停服务' |
| | | default: |
| | | return '-' |
| | | } |
| | | } |
| | | |
| | | async function fetchVehicleList() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | |
| | | vehicleLoading.value = true |
| | | try { |
| | | const res = await fetchTestPlanVehiclePageList({ |
| | | pageNum: vehiclePageParams.value.pageNum, |
| | | pageSize: vehiclePageParams.value.pageSize, |
| | | 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 : [] |
| | | vehicleData.value = records.map((item: any) => { |
| | | const status = Number(item.vehicleStatus ?? item.paused ?? 0) |
| | | return { |
| | | id: item.vehicleId ?? item.id, |
| | | plateNo: item.plateNumber || '-', |
| | | vehicleCode: item.vehicleCode || '-', |
| | | fleetName: item.fleetName || '-', |
| | | status, |
| | | statusName: normalizeVehicleStatusName(status, item.vehicleStatusName || ''), |
| | | 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 { |
| | | vehicleLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadPageData() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | await Promise.all([ |
| | | fetchBaseDetail(), |
| | | fetchMonitorInfo(), |
| | | fetchOrderList(), |
| | | fetchVehicleList(), |
| | | ]) |
| | | } |
| | | |
| | | const orderColumns = computed<DataTableColumns<OrderRow>>(() => [ |
| | | { type: 'selection', width: 50, align: 'center' }, |
| | | { title: '订单编号', key: 'orderNo', width: 100 }, |
| | | { title: '下发时间', key: 'issueTime', width: 150 }, |
| | | { title: '起点', key: 'startPoint', width: 140 }, |
| | | { title: '终点', key: 'endPoint', width: 140 }, |
| | | { title: '接单车辆', key: 'vehicle', width: 120 }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | { title: '订单编号', key: 'orderNo', width: 160 }, |
| | | { title: '下发时间', key: 'issueTime', width: 180 }, |
| | | { title: '起点', key: 'startPoint', width: 180, ellipsis: { tooltip: true } }, |
| | | { title: '终点', key: 'endPoint', width: 180, ellipsis: { tooltip: true } }, |
| | | { title: '接单车辆', key: 'vehicle', width: 140 }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | width: 120, |
| | | render: (row) => { |
| | | return ( |
| | | <NTag |
| | | color={{ textColor: row.statusColor, color: row.statusBg }} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {row.status} |
| | | </NTag> |
| | | ) |
| | | } |
| | | render: row => ( |
| | | <NTag |
| | | color={{ textColor: row.statusColor, color: row.statusBg }} |
| | | bordered={false} |
| | | size="small" |
| | | style="border-radius: 4px;" |
| | | > |
| | | {row.status} |
| | | </NTag> |
| | | ), |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 180, |
| | | render: (row) => { |
| | | return ( |
| | | <NSpace size={12}> |
| | | {row.actions.map(action => ( |
| | | <span |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 220, |
| | | render: row => ( |
| | | <NSpace size={12}> |
| | | {row.actions.map(action => ( |
| | | <span |
| | | key={`${row.orderId}-${action}`} |
| | | class="cursor-pointer-text" |
| | | onClick={() => handleOrderAction(action, row)} |
| | | > |
| | | {action} |
| | | </span> |
| | | ))} |
| | | </NSpace> |
| | | ) |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | function handleOrderAction(action: string, row: OrderRow) { |
| | | if (action === '分派车辆') { |
| | | isAssignVehicleModalVisible.value = true |
| | | } else { |
| | | message.success(`点击了 ${action}`) |
| | | } |
| | | } |
| | | |
| | | function handleAssignVehicleConfirm(data: any[]) { |
| | | message.success('分派车辆成功') |
| | | } |
| | | |
| | | function handleSearch() { |
| | | message.success('查询') |
| | | } |
| | | |
| | | function handleReset() { |
| | | searchModel.value = { |
| | | orderNo: '', |
| | | user: '', |
| | | vehicle: null, |
| | | fleet: null, |
| | | channel: null |
| | | } |
| | | } |
| | | |
| | | // ================= 右侧测试车辆数据与逻辑 ================= |
| | | interface VehicleRow { |
| | | id: number |
| | | plateNo: string |
| | | status: string |
| | | statusColor: string |
| | | } |
| | | |
| | | const vehicleData = ref<VehicleRow[]>([ |
| | | { id: 1, plateNo: '粤PXV7029', status: '服务中', statusColor: '#0068FF' }, |
| | | { id: 2, plateNo: '粤PXV7029', status: '空闲', statusColor: '#10B981' }, |
| | | { id: 3, plateNo: '粤PXV7029', status: '空闲', statusColor: '#10B981' }, |
| | | { id: 4, plateNo: '粤PXV7029', status: '暂停服务', statusColor: '#F59E0B' }, |
| | | { id: 5, plateNo: '粤PXV7029', status: '服务中', statusColor: '#0068FF' }, |
| | | { id: 6, plateNo: '粤PXV7029', status: '空闲', statusColor: '#10B981' }, |
| | | { id: 7, plateNo: '粤PXV7029', status: '空闲', statusColor: '#10B981' }, |
| | | { id: 8, plateNo: '粤PXV7029', status: '空闲', statusColor: '#10B981' }, |
| | | { id: 9, plateNo: '粤PXV7029', status: '暂停服务', statusColor: '#F59E0B' }, |
| | | onClick={() => handleOrderAction(action, row)} |
| | | > |
| | | {action} |
| | | </span> |
| | | ))} |
| | | </NSpace> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | const vehicleColumns = computed<DataTableColumns<VehicleRow>>(() => [ |
| | | { title: '车牌号', key: 'plateNo' }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | { title: '车牌号', key: 'plateNo', width: 120 }, |
| | | { |
| | | title: '状态', |
| | | key: 'statusName', |
| | | align: 'left', |
| | | render: (row) => { |
| | | return <span style={{ color: row.statusColor }}>{row.status}</span> |
| | | } |
| | | } |
| | | render: row => <span style={{ color: row.statusColor }}>{row.statusName}</span>, |
| | | }, |
| | | ]) |
| | | |
| | | function handleManageVehicle() { |
| | | isManageTestVehicleModalVisible.value = true |
| | | } |
| | | |
| | | function handleAddVehicleFromManage() { |
| | | isVehicleSelectModalVisible.value = true |
| | | function handleSearch() { |
| | | orderPageParams.value.pageNum = 1 |
| | | fetchOrderList() |
| | | } |
| | | |
| | | function handleVehicleSelectConfirm(data: any[]) { |
| | | message.success('添加测试车辆成功') |
| | | function handleReset() { |
| | | searchModel.value = { |
| | | orderNo: '', |
| | | vehicleId: null, |
| | | dispatchTimeRange: null, |
| | | } |
| | | orderPageParams.value.pageNum = 1 |
| | | fetchOrderList() |
| | | } |
| | | |
| | | function handleOrderPageChange(page: number, size: number) { |
| | | 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() { |
| | | if (!currentPlanId.value) |
| | | return |
| | | |
| | | exportLoading.value = true |
| | | try { |
| | | const res = await exportTestPlanDetailOrders({ |
| | | ...buildOrderQueryParams(), |
| | | pageNum: orderPageParams.value.pageNum, |
| | | pageSize: orderPageParams.value.pageSize, |
| | | }) |
| | | if (res instanceof Blob) { |
| | | downloadFile(res, `测试计划订单列表_${Date.now()}.xlsx`) |
| | | message.success('导出成功') |
| | | } |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '导出订单失败') |
| | | } |
| | | finally { |
| | | exportLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleOperationLog() { |
| | | message.info('操作日志待接入') |
| | | } |
| | | |
| | | async function handleDeleteOrders(orderIds: Array<number | string>) { |
| | | if (!currentPlanId.value) { |
| | | message.warning('测试计划ID缺失') |
| | | return |
| | | } |
| | | if (!orderIds.length) { |
| | | message.warning('请选择需要删除的订单') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const res = await deleteTestPlanOrders({ |
| | | orderIds, |
| | | planId: currentPlanId.value, |
| | | }) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '删除订单失败') |
| | | } |
| | | message.success('删除成功') |
| | | if (orderIds.length >= orderData.value.length && orderPageParams.value.pageNum > 1) { |
| | | orderPageParams.value.pageNum-- |
| | | } |
| | | await Promise.all([fetchOrderList(), fetchMonitorInfo()]) |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '删除订单失败') |
| | | } |
| | | } |
| | | |
| | | function confirmDeleteOrders(orderIds: Array<number | string>) { |
| | | dialog.warning({ |
| | | title: '确认删除订单吗?', |
| | | content: '删除后无法恢复', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: async () => { |
| | | await handleDeleteOrders(orderIds) |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | function handleOrderAction(action: string, row: OrderRow) { |
| | | if (!currentPlanId.value) { |
| | | message.warning('测试计划ID缺失') |
| | | return |
| | | } |
| | | |
| | | if (action === '分派车辆' || action === '改派') { |
| | | currentAssignOrderId.value = row.orderId |
| | | isAssignVehicleModalVisible.value = true |
| | | return |
| | | } |
| | | if (action === '删除') { |
| | | confirmDeleteOrders([row.orderId]) |
| | | return |
| | | } |
| | | if (action === '详情') { |
| | | message.info('订单详情待接入') |
| | | return |
| | | } |
| | | if (action === '取消订单') { |
| | | dialog.warning({ |
| | | title: '确认取消订单吗?', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const res = await cancelTestPlanOrder(row.orderId, currentPlanId.value) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '取消订单失败') |
| | | } |
| | | message.success('取消订单成功') |
| | | await Promise.all([fetchOrderList(), fetchMonitorInfo()]) |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '取消订单失败') |
| | | } |
| | | }, |
| | | }) |
| | | return |
| | | } |
| | | if (action === '关闭订单') { |
| | | dialog.warning({ |
| | | title: '确认关闭订单吗?', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const res = await closeTestPlanOrder(row.orderId, currentPlanId.value) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || '关闭订单失败') |
| | | } |
| | | message.success('关闭订单成功') |
| | | await Promise.all([fetchOrderList(), fetchMonitorInfo()]) |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || '关闭订单失败') |
| | | } |
| | | }, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | async function handleAssignVehicleSuccess() { |
| | | isAssignVehicleModalVisible.value = false |
| | | currentAssignOrderId.value = null |
| | | await fetchOrderList() |
| | | } |
| | | |
| | | async function handleManageVehicleSuccess() { |
| | | await Promise.all([fetchVehicleList(), fetchMonitorInfo(), fetchOrderList()]) |
| | | } |
| | | |
| | | function handleInterrupt() { |
| | | message.warning('中断下发') |
| | | if (!currentPlanId.value) |
| | | return |
| | | if (dispatchStatus.value === 1 && !interruptAllowed.value) { |
| | | message.warning('当前状态不允许中断下发') |
| | | return |
| | | } |
| | | |
| | | const isResume = dispatchStatus.value === 2 |
| | | dialog.warning({ |
| | | title: isResume ? '确认继续下发吗?' : '确认中断下发吗?', |
| | | content: isResume ? '继续后将恢复已冻结订单的下发' : '中断后将冻结所有待下发订单', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: async () => { |
| | | try { |
| | | const res = isResume |
| | | ? await resumeTestPlanDispatch(currentPlanId.value) |
| | | : await interruptTestPlanDispatch(currentPlanId.value) |
| | | if (res.code !== 200 && res.code !== 0) { |
| | | throw new Error(res.msg || (isResume ? '继续下发失败' : '中断下发失败')) |
| | | } |
| | | message.success(isResume ? '继续下发成功' : '中断下发成功') |
| | | await Promise.all([fetchMonitorInfo(), fetchOrderList()]) |
| | | } |
| | | catch (error: any) { |
| | | message.error(error?.message || (isResume ? '继续下发失败' : '中断下发失败')) |
| | | } |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | watch( |
| | | () => route.query.id, |
| | | async id => { |
| | | if (!id) { |
| | | planName.value = '-' |
| | | orderData.value = [] |
| | | vehicleData.value = [] |
| | | orderCount.value = 0 |
| | | vehicleCount.value = 0 |
| | | return |
| | | } |
| | | await loadPageData() |
| | | }, |
| | | { immediate: true }, |
| | | ) |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | planName |
| | | }}</span> |
| | | <n-tag |
| | | type="info" |
| | | size="small" |
| | | :bordered="false" |
| | | style=" |
| | | color: #0068ff; |
| | | background-color: #e6f4ff; |
| | | border-radius: 4px; |
| | | " |
| | | :style="{ |
| | | color: planStatusColor, |
| | | backgroundColor: planStatusBg, |
| | | borderRadius: '4px' |
| | | }" |
| | | > |
| | | {{ planStatus }} |
| | | </n-tag> |
| | |
| | | > |
| | | </div> |
| | | <n-button |
| | | v-if="interruptButtonVisible" |
| | | type="error" |
| | | ghost |
| | | style="border-radius: 4px; height: 30px" |
| | | @click="handleInterrupt" |
| | | >中断下发</n-button |
| | | > |
| | | {{ interruptButtonText }} |
| | | </n-button> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | |
| | | <!-- 操作按钮区 --> |
| | | <div class="flex gap-8px mb-12px shrink-0"> |
| | | <n-button>导出订单</n-button> |
| | | <n-button>操作日志</n-button> |
| | | <n-button :loading="exportLoading" @click="handleExportOrders">导出订单</n-button> |
| | | <n-button @click="handleOperationLog">操作日志</n-button> |
| | | <n-button @click="confirmDeleteOrders(orderCheckedRowKeys)">批量删除</n-button> |
| | | </div> |
| | | |
| | | <!-- 表格 --> |
| | |
| | | class="flex-1" |
| | | :columns="orderColumns" |
| | | :data="orderData" |
| | | :row-key="(row) => row.id" |
| | | :loading="orderLoading" |
| | | :row-key="(row) => row.orderId" |
| | | v-model:checked-row-keys="orderCheckedRowKeys" |
| | | flex-height |
| | | :bordered="false" |
| | | :scroll-x="1100" |
| | | :scroll-x="1280" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | |
| | | </template> |
| | | </n-data-table> |
| | | <Pagination |
| | | :count="1000" |
| | | :page="1" |
| | | :page-size="10" |
| | | :count="orderCount" |
| | | :page="orderPageParams.pageNum" |
| | | :page-size="orderPageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px shrink-0" |
| | | @change="handleOrderPageChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | |
| | | class="h-full" |
| | | :columns="vehicleColumns" |
| | | :data="vehicleData" |
| | | :loading="vehicleLoading" |
| | | :row-key="(row) => row.id" |
| | | flex-height |
| | | :bordered="false" |
| | |
| | | </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> |
| | |
| | | |
| | | <AssignVehicleModal |
| | | v-model:visible="isAssignVehicleModalVisible" |
| | | @confirm="handleAssignVehicleConfirm" |
| | | :plan-id="currentPlanId" |
| | | :order-id="currentAssignOrderId" |
| | | @success="handleAssignVehicleSuccess" |
| | | /> |
| | | |
| | | <ManageTestVehicleModal |
| | | v-model:visible="isManageTestVehicleModalVisible" |
| | | @add-vehicle="handleAddVehicleFromManage" |
| | | /> |
| | | |
| | | <VehicleSelectModal |
| | | v-model:visible="isVehicleSelectModalVisible" |
| | | @confirm="handleVehicleSelectConfirm" |
| | | :plan-id="currentPlanId" |
| | | @success="handleManageVehicleSuccess" |
| | | /> |
| | | </div> |
| | | </template> |
| | |
| | | .shadow-sm { |
| | | box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.02); |
| | | } |
| | | :deep(.arrow-icon) { |
| | | transition: transform 0.3s; |
| | | } |
| | | :deep(.arrow-icon--open) { |
| | | transform: rotate(180deg); |
| | | } |
| | | :deep(.cursor-pointer-text) { |
| | | cursor: pointer; |
| | | font-size: 13px; |