From e92145a4d561e89f0f2a69f39c212a583c8de18c Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期三, 27 五月 2026 17:41:31 +0800
Subject: [PATCH] Merge branch 'feat/deploy-1.0' of http://gitlab.xiaopeng.local:18080/robotaxi1/xp-robotaxi-web into feat/deploy-1.0
---
src/views/testDemo/testPlanList/add.vue | 336 +++++++++++++++++++++++++++++++++++++++++++++++
src/service/api/copyText.ts | 25 +++
src/views/testDemo/testPlanList/detail.vue | 5
3 files changed, 356 insertions(+), 10 deletions(-)
diff --git a/src/service/api/copyText.ts b/src/service/api/copyText.ts
index 59b4fa6..9aa25b7 100644
--- a/src/service/api/copyText.ts
+++ b/src/service/api/copyText.ts
@@ -208,3 +208,28 @@
export function fetchTestOrderDetail(id: number | string) {
return request.Get<Service.ResponseResult<any>>(`/order/api/v1/testOrder/queryById?id=${id}`)
}
+
+/** 批量测试 - 启动 */
+export function batchStartTestPlan(data: { testPlanId: number | string; lapCount?: number }) {
+ return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/batchStart', data)
+}
+
+/** 批量测试 - 查询批次进度 */
+export function fetchBatchProgress(testPlanId: number | string) {
+ return request.Get<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/batchProgress', { params: { testPlanId } })
+}
+
+/** 批量测试 - 查询车辆进度 */
+export function fetchVehicleProgress(batchId: number | string) {
+ return request.Get<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/vehicleProgress', { params: { batchId } })
+}
+
+/** 批量测试 - 重试失败订单 */
+export function batchRetryTestPlan(testPlanId: number | string) {
+ return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/batchRetry', null, { params: { testPlanId } })
+}
+
+/** 批量测试 - 取消批次 */
+export function batchCancelTestPlan(testPlanId: number | string) {
+ return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/batchCancel', null, { params: { testPlanId } })
+}
diff --git a/src/views/testDemo/testPlanList/add.vue b/src/views/testDemo/testPlanList/add.vue
index 99ab97b..bef8926 100644
--- a/src/views/testDemo/testPlanList/add.vue
+++ b/src/views/testDemo/testPlanList/add.vue
@@ -1,5 +1,5 @@
<script setup lang="tsx">
-import { computed, h, onMounted, ref, watch } from 'vue'
+import { computed, h, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import SelectPointModal from '@/views/testDemo/specialPointManagement/components/SelectPointModal.vue'
import VehicleSelectModal from '@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue'
@@ -9,12 +9,17 @@
import {
addTestOrder,
addTestPlan,
+ batchCancelTestPlan,
+ batchRetryTestPlan,
+ batchStartTestPlan,
cancelAndCloseTestOrder,
changeTestOrderAmount,
deleteTestOrderById,
editTestPlan,
+ fetchBatchProgress,
fetchTestOrderPageList,
fetchTestPlanDetail,
+ fetchVehicleProgress,
reassignTestOrderById,
} from '@/service/api/copyText'
import { getAvailableVehicleList } from '@/service/api/transaction'
@@ -847,10 +852,6 @@
message.warning('请至少添加一个测试点位')
return
}
- if (vehicles.value.length === 0) {
- message.warning('请至少选择一辆车辆')
- return
- }
const invalidPointIndex = testPoints.value.findIndex((p) => {
const startId = String(p.startTestPointId || '').trim()
const endId = String(p.endTestPointId || '').trim()
@@ -883,16 +884,241 @@
}
}
+// ==================== 批量测试相关 ====================
+const lapCount = ref(1)
+const batchProgress = ref<any>(null)
+const vehicleProgressList = ref<any[]>([])
+let batchPollTimer: ReturnType<typeof setInterval> | null = null
+
+const batchStatusMap: Record<string, { label: string, type: 'default' | 'success' | 'warning' | 'error' | 'info' }> = {
+ RUNNING: { label: '运行中', type: 'warning' },
+ SUCCESS: { label: '成功', type: 'success' },
+ PARTIAL_SUCCESS: { label: '部分成功', type: 'warning' },
+ FAILED: { label: '失败', type: 'error' },
+}
+
+function getBatchStatusLabel(status: string) {
+ return batchStatusMap[status]?.label || status || '-'
+}
+
+function getBatchStatusType(status: string) {
+ return batchStatusMap[status]?.type || 'default'
+}
+
+function getVehicleProgressStatusLabel(status: string) {
+ const map: Record<string, { label: string, type: 'default' | 'success' | 'warning' | 'error' | 'info' }> = {
+ RUNNING: { label: '运行中', type: 'warning' },
+ COMPLETED: { label: '已完成', type: 'success' },
+ ERROR: { label: '异常', type: 'error' },
+ CANCELLED: { label: '已取消', type: 'default' },
+ PENDING: { label: '待执行', type: 'info' },
+ }
+ return map[status]?.label || status || '-'
+}
+
+function getVehicleProgressStatusType(status: string) {
+ const map: Record<string, 'default' | 'success' | 'warning' | 'error' | 'info'> = {
+ RUNNING: 'warning',
+ COMPLETED: 'success',
+ ERROR: 'error',
+ CANCELLED: 'default',
+ PENDING: 'info',
+ }
+ return map[status] || 'default'
+}
+
+const batchProgressPercent = computed(() => {
+ if (!batchProgress.value || !batchProgress.value.totalCount) return 0
+ return Math.round((batchProgress.value.completedCount / batchProgress.value.totalCount) * 100)
+})
+
+const isBatchRunning = computed(() => batchProgress.value?.status === 'RUNNING')
+
+async function getBatchProgress() {
+ if (!planId.value) return
+ const res = await fetchBatchProgress(planId.value)
+ if (res.code === 200 && res.data) {
+ batchProgress.value = res.data
+ }
+}
+
+async function getVehicleProgressList() {
+ if (!batchProgress.value || !batchProgress.value.batchId) return
+ const res = await fetchVehicleProgress(batchProgress.value.batchId)
+ if (res.code === 200) {
+ vehicleProgressList.value = res.data || []
+ }
+}
+
+async function refreshBatchData() {
+ await getBatchProgress()
+ await getVehicleProgressList()
+}
+
+function startBatchPolling() {
+ stopBatchPolling()
+ batchPollTimer = setInterval(() => {
+ refreshBatchData()
+ }, 5000)
+}
+
+function stopBatchPolling() {
+ if (batchPollTimer) {
+ clearInterval(batchPollTimer)
+ batchPollTimer = null
+ }
+}
+
+async function handleBatchStart() {
+ if (!planId.value) {
+ message.warning('测试计划初始化中,请稍后再试')
+ return
+ }
+ if (vehicles.value.length === 0) {
+ message.warning('请先选择车辆')
+ return
+ }
+ if (testPoints.value.length === 0) {
+ message.warning('请至少添加一个测试点位')
+ return
+ }
+ if (!lapCount.value || lapCount.value < 1) {
+ message.warning('请输入有效的圈数(至少1圈)')
+ return
+ }
+
+ dialog.create({
+ title: '确认启动批量测试?',
+ content: `将为 ${vehicles.value.length} 辆车按 ${testPoints.value.length} 个点位组合生成链式巡游订单,每车跑 ${lapCount.value} 圈`,
+ positiveText: '确定',
+ negativeText: '取消',
+ maskClosable: false,
+ autoFocus: false,
+ onPositiveClick: async () => {
+ const res = await batchStartTestPlan({ testPlanId: planId.value, lapCount: lapCount.value })
+ if (res.code === 200) {
+ message.success('批量测试已启动')
+ await refreshBatchData()
+ startBatchPolling()
+ }
+ else {
+ message.error(res?.message || '启动失败')
+ }
+ },
+ })
+}
+
+async function handleBatchRetry() {
+ if (!planId.value) return
+ const res = await batchRetryTestPlan(planId.value)
+ if (res.code === 200) {
+ message.success('重试已提交')
+ await refreshBatchData()
+ if (isBatchRunning.value) startBatchPolling()
+ }
+ else {
+ message.error(res?.message || '重试失败')
+ }
+}
+
+async function handleBatchCancel() {
+ if (!planId.value) return
+ dialog.warning({
+ title: '确认取消批量测试?',
+ content: '取消后未完成的订单将不再执行',
+ positiveText: '确定',
+ negativeText: '取消',
+ maskClosable: false,
+ autoFocus: false,
+ onPositiveClick: async () => {
+ const res = await batchCancelTestPlan(planId.value)
+ if (res.code === 200) {
+ message.success('已取消')
+ stopBatchPolling()
+ await refreshBatchData()
+ }
+ else {
+ message.error(res?.message || '取消失败')
+ }
+ },
+ })
+}
+
+const vehicleProgressTableScrollX = 960
+const vehicleProgressColumns = computed<DataTableColumns<any>>(() => [
+ {
+ title: '序号',
+ key: 'index',
+ width: 60,
+ align: 'left',
+ render: (_row, index) => index + 1,
+ },
+ {
+ title: '车牌号',
+ key: 'plateNumber',
+ width: 120,
+ align: 'left',
+ },
+ {
+ title: '当前圈数/总圈数',
+ key: 'currentLap',
+ width: 140,
+ align: 'left',
+ render: row => `${row.currentLap || 0}/${row.totalLaps || '-'}`,
+ },
+ {
+ title: '当前段/总段数',
+ key: 'currentSegment',
+ width: 140,
+ align: 'left',
+ render: row => `${row.currentSegment || 0}/${row.totalSegments || '-'}`,
+ },
+ {
+ title: '当前订单ID',
+ key: 'currentOrderId',
+ width: 140,
+ align: 'left',
+ render: row => row.currentOrderId || '-',
+ },
+ {
+ title: '状态',
+ key: 'status',
+ width: 100,
+ align: 'left',
+ render: row => <n-tag type={getVehicleProgressStatusType(row.status)} size="small">{getVehicleProgressStatusLabel(row.status)}</n-tag>,
+ },
+ {
+ title: '错误信息',
+ key: 'errorMsg',
+ width: 200,
+ align: 'left',
+ ellipsis: true,
+ render: row => row.errorMsg || '-',
+ },
+])
+
+// 监听批次状态,自动停止轮询
+watch(() => batchProgress.value?.status, (status) => {
+ if (status && status !== 'RUNNING') {
+ stopBatchPolling()
+ }
+})
+
onMounted(() => {
if (isEdit.value || isDetail.value) {
planId.value = String(route.query.id || '')
getDetail()
getOrderList()
+ refreshBatchData()
}
else {
initCreatePlanId()
setInitialSnapshot()
}
+})
+
+onUnmounted(() => {
+ stopBatchPolling()
})
watch(() => route.query.id, (id) => {
@@ -905,6 +1131,7 @@
if (isEdit.value || isDetail.value) {
getDetail()
getOrderList()
+ refreshBatchData()
}
})
</script>
@@ -1114,6 +1341,105 @@
/>
</n-card>
+ <!-- 批量测试区域:详情/编辑模式下显示 -->
+ <template v-if="isDetail || isEdit">
+ <n-card style="border-radius: 6px" content-style="padding: 24px;">
+ <div class="mb-4 flex items-center justify-between">
+ <div class="flex items-center">
+ <div class="mr-2 bg-blue-500" style="width: 3px; height: 14px" />
+ <span class="font-bold" style="font-size: 16px">批量测试</span>
+ </div>
+ <div class="flex items-center gap-3">
+ <div class="flex items-center gap-2">
+ <span class="text-sm">圈数:</span>
+ <n-input-number
+ v-model:value="lapCount"
+ :min="1"
+ :max="100"
+ style="width: 100px"
+ :disabled="isBatchRunning"
+ />
+ </div>
+ <n-button
+ type="primary"
+ @click="handleBatchStart"
+ :disabled="isBatchRunning"
+ >
+ 启动批量测试
+ </n-button>
+ <n-button
+ @click="handleBatchRetry"
+ :disabled="!batchProgress || batchProgress.errorCount === 0"
+ >
+ 重试失败
+ </n-button>
+ <n-button
+ type="error"
+ @click="handleBatchCancel"
+ :disabled="!isBatchRunning"
+ >
+ 取消
+ </n-button>
+ </div>
+ </div>
+ </n-card>
+
+ <!-- 批次进度卡片 -->
+ <n-card v-if="batchProgress" style="border-radius: 6px" content-style="padding: 24px;">
+ <div class="mb-4 flex items-center">
+ <div class="mr-2 bg-blue-500" style="width: 3px; height: 14px" />
+ <span class="font-bold" style="font-size: 16px">批次进度</span>
+ </div>
+ <div class="flex items-center gap-6 mb-4">
+ <n-tag :type="getBatchStatusType(batchProgress.status)" size="medium">
+ {{ getBatchStatusLabel(batchProgress.status) }}
+ </n-tag>
+ <span class="text-sm text-gray-500" v-if="batchProgress.startTime">
+ 开始:{{ dayjs(batchProgress.startTime).format('YYYY/MM/DD HH:mm:ss') }}
+ </span>
+ <span class="text-sm text-gray-500" v-if="batchProgress.endTime">
+ 结束:{{ dayjs(batchProgress.endTime).format('YYYY/MM/DD HH:mm:ss') }}
+ </span>
+ </div>
+ <n-progress
+ type="line"
+ :percentage="batchProgressPercent"
+ :status="batchProgress.status === 'FAILED' ? 'error' : batchProgress.status === 'SUCCESS' ? 'success' : 'default'"
+ :height="20"
+ :show-indicator="true"
+ class="mb-4"
+ />
+ <div class="flex gap-8 text-sm">
+ <div>总订单数:<span class="font-bold">{{ batchProgress.totalCount || 0 }}</span></div>
+ <div>已创建:<span class="font-bold text-blue-600">{{ batchProgress.createdCount || 0 }}</span></div>
+ <div>已完成:<span class="font-bold text-green-600">{{ batchProgress.completedCount || 0 }}</span></div>
+ <div>异常数:<span class="font-bold" :class="batchProgress.errorCount > 0 ? 'text-red-600' : ''">{{ batchProgress.errorCount || 0 }}</span></div>
+ </div>
+ </n-card>
+
+ <!-- 车辆进度卡片 -->
+ <n-card v-if="batchProgress" style="border-radius: 6px; margin-bottom: 80px" content-style="padding: 24px;">
+ <div class="mb-4 flex items-center">
+ <div class="mr-2 bg-blue-500" style="width: 3px; height: 14px" />
+ <span class="font-bold" style="font-size: 16px">车辆进度</span>
+ </div>
+ <n-data-table
+ :columns="vehicleProgressColumns"
+ :data="vehicleProgressList"
+ :bordered="false"
+ :scroll-x="vehicleProgressTableScrollX"
+ :header-height="18"
+ :row-height="18"
+ >
+ <template #empty>
+ <div class="flex-center">
+ <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
+ </div>
+ </template>
+ </n-data-table>
+ </n-card>
+ </template>
+
<div
class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4"
style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100"
diff --git a/src/views/testDemo/testPlanList/detail.vue b/src/views/testDemo/testPlanList/detail.vue
index 7652134..cfa2c68 100644
--- a/src/views/testDemo/testPlanList/detail.vue
+++ b/src/views/testDemo/testPlanList/detail.vue
@@ -214,11 +214,6 @@
message.warning('请至少选择一个点位')
return
}
- if (vehicles.value.length === 0) {
- message.warning('请至少选择一辆车辆')
- return
- }
-
const res = await editCruisePlan({
id: planId.value,
planName: name,
--
Gitblit v1.7.1