董国庆
2026-03-27 381bb74adc2273b75b14eed8b315817e0e49a997
测试demo
15个文件已修改
1624 ■■■■■ 已修改文件
src/router/routes.static.ts 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/copyText.ts 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/transaction.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/robotaxi.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/components/FleetFormModal.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/detail.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/inventoryVehicle/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/specialPointManagement/components/SelectPointModal.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/add.vue 1018 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/api.txt 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/index.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/components/TableModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/detail.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/banner/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts
@@ -12,103 +12,103 @@
    keepAlive: true,
    pid: null,
  },
  // {
  //   name: 'testDemo',
  //   path: '/testDemo',
  //   title: '测试跑单demo',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:analysis',
  //   menuType: 'dir',
  //   componentPath: null,
  //   id: 9,
  //   pid: null,
  // },
  // {
  //   name: 'testPointManagement',
  //   path: '/testDemo/testPointManagement',
  //   title: '测试点位管理',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/testPointManagement/index.vue',
  //   id: 901,
  //   pid: 9,
  // },
  // {
  //   name: 'testPointManagementList',
  //   path: '/testDemo/testPointManagement/list',
  //   title: '测试点位列表',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/testPointManagement/list.vue',
  //   id: 9011,
  //   pid: 9,
  //   hide: true,
  //   fullScreen: true,
  //   activeMenu: '/testDemo/testPointManagement',
  // },
  // {
  //   name: 'specialPointManagement',
  //   path: '/testDemo/specialPointManagement',
  //   title: '特殊点位管理',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/specialPointManagement/index.vue',
  //   id: 902,
  //   pid: 9,
  // },
  // {
  //   name: 'testPlanList',
  //   path: '/testDemo/testPlanList',
  //   title: '测试计划列表',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/testPlanList/index.vue',
  //   id: 903,
  //   pid: 9,
  // },
  // {
  //   name: 'testPlanAdd',
  //   path: '/testDemo/testPlanList/add',
  //   title: '新增测试计划',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/testPlanList/add.vue',
  //   id: 9031,
  //   pid: 9,
  //   hide: true,
  //   activeMenu: '/testDemo/testPlanList',
  // },
  // {
  //   name: 'testPlanDetail',
  //   path: '/testDemo/testPlanList/detail/:id',
  //   title: '测试计划详情',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:list-view',
  //   menuType: 'page',
  //   componentPath: '/testDemo/testPlanList/detail.vue',
  //   id: 9032,
  //   pid: 9,
  //   hide: true,
  //   activeMenu: '/testDemo/testPlanList',
  // },
  // {
  //   name: 'testDemoOrderDetail',
  //   path: '/testDemo/testPlanList/orderDetail',
  //   title: '订单详情',
  //   requiresAuth: false,
  //   icon: 'icon-park-outline:alarm',
  //   menuType: 'page',
  //   componentPath: '/transaction/order/orderManagement/detail.vue',
  //   id: 9033,
  //   pid: 9,
  //   hide: true,
  //   activeMenu: '/testDemo/testPlanList',
  // },
  {
    name: 'testDemo',
    path: '/testDemo',
    title: '测试跑单demo',
    requiresAuth: false,
    icon: 'icon-park-outline:analysis',
    menuType: 'dir',
    componentPath: null,
    id: 9,
    pid: null,
  },
  {
    name: 'testPointManagement',
    path: '/testDemo/testPointManagement',
    title: '测试点位管理',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/index.vue',
    id: 901,
    pid: 9,
  },
  {
    name: 'testPointManagementList',
    path: '/testDemo/testPointManagement/list',
    title: '测试点位列表',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/list.vue',
    id: 9011,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/testPointManagement',
  },
  {
    name: 'specialPointManagement',
    path: '/testDemo/specialPointManagement',
    title: '特殊点位管理',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/specialPointManagement/index.vue',
    id: 902,
    pid: 9,
  },
  {
    name: 'testPlanList',
    path: '/testDemo/testPlanList',
    title: '测试计划列表',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/index.vue',
    id: 903,
    pid: 9,
  },
  {
    name: 'testPlanAdd',
    path: '/testDemo/testPlanList/add',
    title: '新增测试计划',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/add.vue',
    id: 9031,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testPlanDetail',
    path: '/testDemo/testPlanList/detail/:id',
    title: '测试计划详情',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/detail.vue',
    id: 9032,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testDemoOrderDetail',
    path: '/testDemo/testPlanList/orderDetail',
    title: '订单详情',
    requiresAuth: false,
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/order/orderManagement/detail.vue',
    id: 9033,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'system',
    path: '/system',
src/service/api/copyText.ts
@@ -104,3 +104,76 @@
export function fetchCruiseOrderDetail(id: number | string) {
  return request.Get<Service.ResponseResult<any>>(`/order/api/v1/cruise-order/getDetailById/${id}`)
}
export function fetchTestPlanPageList(params: { pageNum: number, pageSize: number, planName?: string }) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/pageList', params)
}
export function addTestPlan(data?: {
  planName: string
  pointList: { startTestPointId: number; endTestPointId: number }[]
  vehicleIdList: number[]
}) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/add', data || {})
}
export function editTestPlan(data: {
  id: number | string
  planName: string
  pointList: { startTestPointId: number; endTestPointId: number; stopFlag: 0 | 1; testOrderType: 1 | 2 }[]
  vehicleIdList: number[]
}) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/edit', data)
}
export function deleteTestPlanById(id: number | string) {
  return request.Delete<Service.ResponseResult<any>>(`/operation/api/v1/xp-test-plan/deleteById/${id}`)
}
export function fetchTestPlanDetail(id: number | string) {
  return request.Get<Service.ResponseResult<any>>('/operation/api/v1/xp-test-plan/detail', { params: { id } })
}
export function fetchTestOrderPageList(data: {
  endTime?: string
  orderNumber?: string
  pageNum: number
  pageSize: number
  plateNumber?: string
  startTime?: string
  status?: number
  testPlanId?: number | string
}) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/testOrder/pageList', data)
}
export function addTestOrder(data: {
  endTestPointId: string
  orderType: number
  startTestPointId: string
  stopFlag: number
  testPlanId: number | string
  vehicleIds: number[]
}) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/testOrder/addTestOrder', data)
}
export function cancelAndCloseTestOrder(data: { orderId: number; type: number }) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/testOrder/cancelAndCloseById', data)
}
export function changeTestOrderAmount(params: { id: number | string; amount: number | string }) {
  return request.Post<Service.ResponseResult<any>>(`/order/api/v1/testOrder/changeAmount?id=${params.id}&amount=${params.amount}`)
}
export function deleteTestOrderById(id: number | string) {
  return request.Delete<Service.ResponseResult<any>>(`/order/api/v1/testOrder/deleteById?id=${id}`)
}
export function reassignTestOrderById(data: { orderId: number; reassignReason: string; vehicleId: number }) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/testOrder/reassignById', data)
}
export function fetchTestOrderDetail(id: number | string) {
  return request.Get<Service.ResponseResult<any>>(`/order/api/v1/testOrder/queryById?id=${id}`)
}
src/service/api/transaction.ts
@@ -46,8 +46,8 @@
}
// 获取可用车辆列表
export function getAvailableVehicleList(orderId: any) {
  return request.Get<Service.ResponseResult<any>>(`/vehicleConvoy/api/v1/vehicle/availableList?orderId=${orderId}`)
export function getAvailableVehicleList(params: any) {
  return request.Get<Service.ResponseResult<any>>(`/vehicleConvoy/api/v1/vehicle/availableList?orderId=${params.orderId}&orderType=${params.orderType}`)
}
/** 订单评价 */
src/styles/robotaxi.scss
@@ -360,4 +360,14 @@
.n-menu .n-menu-item-content.n-menu-item-content--selected::before {
  border-left: 2px solid #fff;
}
}
.n-base-select-menu .n-base-select-option.n-base-select-option--pending::before{
  background-color: unset;
}
.n-base-select-option--selected{
  .n-base-select-option__content{
    color: #0068FF;
  }
}
src/views/assetManagement/fleetManagement/components/FleetFormModal.vue
@@ -460,10 +460,10 @@
            <span class="required-mark">*</span>关联人员
          </div>
          <NSpace>
            <NButton size="small" @click="selectedStaffList = []">
            <NButton size="small" @click="selectedStaffList = []" style="font-size: 12px;">
              重置
            </NButton>
            <NButton type="primary" style="background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF;" secondary size="small" @click="handleAddStaff">
            <NButton type="primary" style="background-color: #E6F4FF; font-size: 12px;color: #0068FF; border: 1px solid #7ABDFF;" secondary size="small" @click="handleAddStaff">
              添加
            </NButton>
          </NSpace>
@@ -492,10 +492,10 @@
            <span class="required-mark">*</span>关联车辆
          </div>
          <NSpace>
            <NButton size="small" @click="selectedVehicleList = []">
            <NButton size="small" @click="selectedVehicleList = []" style="font-size: 12px;">
              重置
            </NButton>
            <NButton type="primary" style="background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF;" secondary size="small" @click="handleAddVehicle">
            <NButton type="primary" style="background-color: #E6F4FF; font-size: 12px;color: #0068FF; border: 1px solid #7ABDFF;" secondary size="small" @click="handleAddVehicle">
              添加
            </NButton>
          </NSpace>
@@ -562,8 +562,8 @@
}
.section-title {
  font-size: 14px;
  font-weight: bold;
  font-size: 16px;
  font-weight: 500;
  display: flex;
  align-items: center;
  padding-left: 12px;
src/views/assetManagement/fleetManagement/detail.vue
@@ -672,8 +672,6 @@
                    type="primary"
                    class="edit"
                    secondary
                    size="small"
                    style="width: 58px;border-radius: 4px;"
                    @click="handleAddVehicle"
                  >
                    新增
@@ -764,8 +762,6 @@
                    type="primary"
                    class="edit"
                    secondary
                    size="small"
                    style="padding: 7px 16px;"
                    @click="handleAddStaff"
                  >
                    新增
@@ -831,7 +827,9 @@
  min-height: 100%;
  .edit {
    border: 1px solid #7abdff;
    border: 1px solid #7ABDFF;
    background-color: #E6F4FF;
    color: #0068FF;
  }
  // 车队信息卡片
@@ -888,10 +886,12 @@
          .label {
            color: rgba(23, 31, 38, 0.6);
            font-size: 13px;
            white-space: nowrap;
          }
          .value {
            font-size: 13px;
            color: rgba(23, 31, 38, 1);
            font-weight: 400;
          }
src/views/assetManagement/vehicleManagement/inventoryVehicle/index.vue
@@ -748,21 +748,21 @@
            <span v-if="checkedRowKeys.length > 0" class="text-gray-500">
              {{ $t('accountManagement.messages.selectedRecords', { count: checkedRowKeys.length }) }}
            </span>
              <NButton v-permission="50112" :disabled="checkedRowKeys.length === 0" @click="handleBatchSetArea">
              <NButton v-permission="50112"  style="color: #171F26;" :disabled="checkedRowKeys.length === 0" @click="handleBatchSetArea">
                批量设置运营区域
              </NButton>
              <NButton v-permission="50113" :disabled="checkedRowKeys.length === 0" @click="handleBatchOperation">
              <NButton v-permission="50113" style="color: #171F26;" :disabled="checkedRowKeys.length === 0" @click="handleBatchOperation">
                批量运营
              </NButton>
            </div>
            <NSpace>
              <NButton v-permission="50114" @click="handleDownloadTemplate">
              <NButton v-permission="50114" @click="handleDownloadTemplate" style="color: #171F26;">
                下载模板
              </NButton>
              <NButton v-permission="50114" @click="handleImport">
              <NButton v-permission="50114" @click="handleImport" style="color: #171F26;">
                导入
              </NButton>
              <NButton v-permission="50115" @click="handleExport">
              <NButton v-permission="50115" @click="handleExport" style="color: #171F26;">
                导出
              </NButton>
              <NButton
src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue
@@ -141,7 +141,12 @@
  { title: '充电后剩余电量', key: 'endRemainingBattery', align: 'left' as const, width: 160, render: (row: any) => row.endRemainingBattery ? `${row.endRemainingBattery}%` : '-' },
  { title: '图片', key: 'endChargingPicture', align: 'left' as const, width: 140, render: (row: any) => renderImageCell(row.endChargingPicture) },
  { title: '充电时间', key: 'chargingTime', align: 'left' as const, width: 240, render: (row: any) =>
    `${row.startTime ? dayjs(row.startTime).format('YYYY/MM/DD HH:mm:ss') : '-'} 至 ${row.endTime ? dayjs(row.endTime).format('YYYY/MM/DD HH:mm:ss') : '-'}` },
(
  <span>{row.startTime ? dayjs(row.startTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
<span style="color: rgb(23, 31, 38,.8);"> 至 </span>
    {row.endTime ? dayjs(row.endTime).format('YYYY/MM/DD HH:mm:ss') : '-'}  </span>
)
},
  { title: '操作人', key: 'operator', align: 'left' as const, width: 180, render: (row: any) => [row.operatorStartName, row.operatorEndName].filter(Boolean).join(' / ') || '-' },
  {
    title: '操作',
@@ -183,7 +188,11 @@
const maintenanceColumns = [
  { title: '序号', key: 'index', align: 'left' as const, width: 80, render: (row: any, index: number) => (maintenancePage.value.pageNum - 1) * maintenancePage.value.pageSize + index + 1 },
  { title: '维修原因', key: 'repairReason', align: 'left' as const, width: 180 },
  { title: '停运时间', key: 'repairTime', align: 'left' as const, width: 240, render: (row: any) => `${row.startTime ? dayjs(row.startTime).format('YYYY/MM/DD') : '-'} 至 ${row.endTime ? dayjs(row.endTime).format('YYYY/MM/DD') : '-'}` },
  { title: '停运时间', key: 'repairTime', align: 'left' as const, width: 240, render: (row: any) => (
    <span>{row.startTime ? dayjs(row.startTime).format('YYYY/MM/DD') : '-'}
<span style="color: rgb(23, 31, 38,.8);"> 至 </span>
    {row.endTime ? dayjs(row.endTime).format('YYYY/MM/DD') : '-'}  </span>
  ) },
  { title: '维修总费用', key: 'repairMoney', align: 'left' as const, width: 140, render: (row: any) => (row.repairMoney != null ? `¥${row.repairMoney}` : '-') },
  { title: '送修人', key: 'operatorStartName', align: 'left' as const, width: 120 },
  { title: '送修时间', key: 'sendTime', align: 'left' as const, width: 180, render: (row: any) => row.sendTime ? dayjs(row.sendTime).format('YYYY/MM/DD HH:mm:ss') : '-' },
@@ -221,7 +230,7 @@
        3: { type: 'success', text: '四级' },
      }
      const urgency = urgencyMap[row.taskUrgencyLevel]
      return urgency ? <NTag type={urgency.type} size="small">{urgency.text}</NTag> : '-'
      return urgency ? <NTag type={urgency.type} size="small" bordered={false}>{urgency.text}</NTag> : '-'
    },
  },
  { title: '任务描述', key: 'taskDescription', align: 'left' as const, minWidth: 200 },
@@ -239,7 +248,7 @@
        3: { type: 'default', text: '已取消' },
      }
      const status = statusMap[row.status]
      return status ? <NTag type={status.type} size="small">{status.text}</NTag> : '-'
      return status ? <NTag type={status.type} size="small" bordered={false}>{status.text}</NTag> : '-'
    },
  },
  { title: '处理人', key: 'processor', align: 'left' as const, width: 120, render: (row: any) => row.processor || '-' },
@@ -1315,7 +1324,7 @@
              class="collapse-icon"
              :class="{ 'is-collapsed': !isVehicleInfoExpanded }"
              @click="toggleVehicleInfo"
            >
            />
            <span class="header-title">车辆信息</span>
          </div>
          <div
@@ -1330,7 +1339,7 @@
          >
            编辑
          </div>
          <div
          <!-- <div
            v-else
            type="primary"
            class="edit"
@@ -1340,7 +1349,7 @@
            @click="handleBack"
          >
            返回
          </div>
          </div> -->
        </div>
        <!-- 车辆信息内容 -->
@@ -1352,8 +1361,12 @@
              <img
                :src="vehicleDetail.vehicleImage"
                alt="车辆图片"
                @error="e => { e.target.src = defaultImage }"
              >
                @error="
                  (e) => {
                    e.target.src = defaultImage;
                  }
                "
              />
            </div>
            <!-- 基础信息 -->
@@ -1366,7 +1379,8 @@
                  class="status-chip"
                  :class="vehicleTag.cls"
                  @click="handleStatusClick"
                >{{ vehicleTag.text }}</span>
                  >{{ vehicleTag.text }}</span
                >
                <span class="status-chip" :class="operatingTag.cls">{{
                  operatingTag.text
                }}</span>
@@ -1376,7 +1390,9 @@
                  vehicleDetail.color || "白色"
                }}</span>
                <span class="basic-separator">|</span>
                <span class="basic-value">VIN{{ `   ${vehicleDetail.vinCode}` }}</span>
                <span class="basic-value"
                  >VIN{{ `   ${vehicleDetail.vinCode}` }}</span
                >
              </div>
            </div>
          </div>
@@ -1406,7 +1422,11 @@
              <div style="width: 300px">
                <span class="detail-label">年检到期时间:</span>
                <span class="detail-value">{{
                  vehicleDetail.inspectionExpireDate ? dayjs(vehicleDetail.inspectionExpireDate).format("YYYY/MM/DD") : "-"
                  vehicleDetail.inspectionExpireDate
                    ? dayjs(vehicleDetail.inspectionExpireDate).format(
                        "YYYY/MM/DD"
                      )
                    : "-"
                }}</span>
              </div>
              <span
@@ -1419,13 +1439,19 @@
            <div class="detail-item">
              <span class="detail-label">保险到期时间:</span>
              <span class="detail-value">{{
                vehicleDetail.insuranceExpireDate ? dayjs(vehicleDetail.insuranceExpireDate).format("YYYY/MM/DD") : "-"
                vehicleDetail.insuranceExpireDate
                  ? dayjs(vehicleDetail.insuranceExpireDate).format(
                      "YYYY/MM/DD"
                    )
                  : "-"
              }}</span>
            </div>
            <div class="detail-item">
              <span class="detail-label">入库时间:</span>
              <span class="detail-value">{{
                vehicleDetail.entryTime ? dayjs(vehicleDetail.entryTime).format("YYYY/MM/DD HH:mm:ss") : "-"
                vehicleDetail.entryTime
                  ? dayjs(vehicleDetail.entryTime).format("YYYY/MM/DD HH:mm:ss")
                  : "-"
              }}</span>
            </div>
            <div class="detail-item">
@@ -1488,7 +1514,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1522,7 +1550,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1566,7 +1597,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1600,7 +1633,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1643,7 +1679,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1674,7 +1712,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1718,7 +1759,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1749,7 +1792,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1793,7 +1839,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1824,7 +1872,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1877,7 +1928,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 300px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -1911,7 +1964,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -1959,7 +2015,7 @@
                              :class="{ 'arrow-icon--open': isTaskTypeOpen }"
                              style="width: 16px; height: 16px"
                              alt="arrow"
                            >
                            />
                          </template>
                        </NSelect>
                      </NFormItem>
@@ -1976,10 +2032,12 @@
                            <img
                              src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
                              class="arrow-icon"
                              :class="{ 'arrow-icon--open': isTaskGeneralIdOpen }"
                              :class="{
                                'arrow-icon--open': isTaskGeneralIdOpen,
                              }"
                              style="width: 16px; height: 16px"
                              alt="arrow"
                            >
                            />
                          </template>
                        </NSelect>
                      </NFormItem>
@@ -2003,7 +2061,7 @@
                              :class="{ 'arrow-icon--open': isTaskErrorIdOpen }"
                              style="width: 16px; height: 16px"
                              alt="arrow"
                            >
                            />
                          </template>
                        </NCascader>
                      </NFormItem>
@@ -2015,7 +2073,9 @@
                          start-placeholder="开始时间"
                          end-placeholder="结束时间"
                          style="width: 280px"
                          :actions="[]" :update-value-on-close="true" :close-on-select="true"
                          :actions="[]"
                          :update-value-on-close="true"
                          :close-on-select="true"
                          format="yyyy/MM/dd"
                        />
                      </NFormItem>
@@ -2046,7 +2106,10 @@
              >
                <template #empty>
                  <div class="flex-center">
                    <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px">
                    <img
                      src="/src/assets/element-img/no-data.png"
                      style="width: 123px; height: 125px"
                    />
                  </div>
                </template>
              </NDataTable>
@@ -2142,12 +2205,12 @@
      }
      .edit {
        border: 1px solid #7ABDFF;
        color:#0068FF;
        background: #E6F4FF;
        border: 1px solid #7abdff;
        color: #0068ff;
        background: #e6f4ff;
        border-radius: 4px;
        line-height: 13px;
        cursor: pointer;
      }
    }
@@ -2194,6 +2257,7 @@
            font-size: 14px;
            .plate-number {
              font-size: 20px;
              font-weight: 500;
              color: #171f26;
            }
            .tag-info {
@@ -2257,6 +2321,7 @@
    }
  }
  .info-list {
    border-radius: 6px !important;
    :deep(.n-card__content) {
      padding: 0 0 12px 0 !important;
    }
@@ -2348,6 +2413,7 @@
  :deep(.n-tabs) {
    .n-tabs-nav {
      background: #fff;
      border-radius: 6px;
      // padding: 0 16px;
      .n-tabs-tab-wrapper {
        padding-left: 16px;
src/views/testDemo/specialPointManagement/components/SelectPointModal.vue
@@ -1,13 +1,13 @@
<script setup lang="ts">
import { h, ref, watch } from 'vue'
import type { DataTableColumns } from 'naive-ui'
import { fetchPointPageList } from '@/service/api/point'
import { fetchPointPageList } from '@/service/api/copyText'
import { useBoolean } from '@/hooks'
type RowKey = string | number
interface Point {
  id: number
  id: number | string
  name?: string
  pointAddress?: string
  address?: string
@@ -32,7 +32,7 @@
  pointName: '',
})
const selectedPoints = ref<RowKey[]>([])
const allSelectedMap = ref<Map<number, Point>>(new Map())
const allSelectedMap = ref<Map<string, Point>>(new Map())
const listData = ref<any[]>([])
const pagination = ref({
@@ -83,9 +83,14 @@
function handleConfirm() {
  const result: Point[] = []
  selectedPoints.value.forEach((id: RowKey) => {
    const numId = Number(id)
    if (allSelectedMap.value.has(numId)) {
      result.push(allSelectedMap.value.get(numId)!)
    const pointId = String(id)
    if (allSelectedMap.value.has(pointId)) {
      result.push(allSelectedMap.value.get(pointId)!)
      return
    }
    const currentPagePoint = listData.value.find(item => String(item.id) === pointId)
    if (currentPagePoint) {
      result.push(currentPagePoint)
    }
  })
  emit('confirm', result)
@@ -94,16 +99,21 @@
function handleUpdateChecked(keys: RowKey[]) {
  selectedPoints.value = keys
  const selectedKeySet = new Set(keys.map(key => String(key)))
  listData.value.forEach((item) => {
    if ((keys as number[]).includes(item.id)) {
      const old = allSelectedMap.value.get(item.id)
    const pointId = String(item.id)
    if (selectedKeySet.has(pointId)) {
      const old = allSelectedMap.value.get(pointId)
      if (old) {
        allSelectedMap.value.set(item.id, { ...old, ...item })
        allSelectedMap.value.set(pointId, { ...old, ...item })
      }
      else {
        allSelectedMap.value.set(item.id, item)
        allSelectedMap.value.set(pointId, item)
      }
    }
    else {
      allSelectedMap.value.delete(pointId)
    }
  })
}
@@ -115,7 +125,7 @@
    allSelectedMap.value.clear()
    if (props.existingPoints) {
      props.existingPoints.forEach((p) => {
        allSelectedMap.value.set(p.id, p)
        allSelectedMap.value.set(String(p.id), p)
      })
    }
src/views/testDemo/testPlanList/add.vue
@@ -3,41 +3,50 @@
import { useRoute, useRouter } from 'vue-router'
import SelectPointModal from '@/views/testDemo/specialPointManagement/components/SelectPointModal.vue'
import VehicleSelectModal from '@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue'
import { addCruisePlan, editCruisePlan, fetchCruisePlanDetail } from '@/service/api/cruise'
import type { DataTableColumns } from 'naive-ui'
import dayjs from 'dayjs'
import { useBoolean } from '@/hooks'
import {
  addTestOrder,
  addTestPlan,
  cancelAndCloseTestOrder,
  changeTestOrderAmount,
  deleteTestOrderById,
  editTestPlan,
  fetchTestOrderPageList,
  fetchTestPlanDetail,
  reassignTestOrderById,
} from '@/service/api/copyText'
import { getAvailableVehicleList } from '@/service/api/transaction'
const router = useRouter()
const route = useRoute()
const message = useMessage()
const dialog = useDialog()
interface Point {
  id: number
  name: string
  address: string
  isCharge: boolean
  isPark: boolean
}
interface TestPoint {
  id: number
  pickupPoint: Point | null
  dropoffPoint: Point | null
  isPark: boolean
  orderType: string
  startTestPointId: number | null
  startTestPointName: string
  startTestPointAddress: string
  endTestPointId: number | null
  endTestPointName: string
  endTestPointAddress: string
  stopFlag: 0 | 1
  testOrderType: 1 | 2 | null
}
interface Order {
interface TestOrderRow {
  id: number
  orderType: string
  orderType: number
  orderNumber: string
  orderTime: string
  orderSource: string
  startPoint: string
  endPoint: string
  acceptVehicle: string
  belongFleet: string
  status: string
  createTime: string
  orderSource: number
  startAddress: string
  endAddress: string
  plateNumber: string
  fleetName: string
  status: number
}
interface Vehicle {
@@ -50,20 +59,25 @@
const planName = ref('')
const testPoints = ref<TestPoint[]>([])
const orders = ref<Order[]>([])
const orders = ref<TestOrderRow[]>([])
const orderCount = ref(0)
const orderPageParams = ref({
  pageNum: 1,
  pageSize: 10,
})
const showSelectModal = ref(false)
const showPointSelectModal = ref(false)
const showVehicleModal = ref(false)
const showPickupSelectModal = ref(false)
const showDropoffSelectModal = ref(false)
const vehicles = ref<Vehicle[]>([])
const orderSearchModel = ref({
  orderNumber: '',
  vehicle: '',
  status: '',
  orderTime: null,
  plateNumber: '',
  status: null as number | null,
  orderTimeRange: null as [number, number] | null,
})
const isOrderStatusOpen = ref(false)
const orderTableScrollX = 1440
const showOrderModal = ref(false)
const currentTestPointIndex = ref(-1)
@@ -73,7 +87,9 @@
})
const isEdit = computed(() => route.query.mode === 'edit')
const planId = computed(() => String(route.query.id || ''))
const isDetail = computed(() => route.query.mode === 'detail')
const isReadOnly = computed(() => isDetail.value)
const planId = ref(String(route.query.id || ''))
const initialSnapshot = ref('')
@@ -81,10 +97,10 @@
  return JSON.stringify({
    planName: planName.value.trim(),
    testPoints: testPoints.value.map(p => ({
      pickupPointId: p.pickupPoint?.id,
      dropoffPointId: p.dropoffPoint?.id,
      isPark: !!p.isPark,
      orderType: p.orderType,
      startTestPointId: p.startTestPointId,
      endTestPointId: p.endTestPointId,
      stopFlag: p.stopFlag,
      testOrderType: p.testOrderType,
    })),
    vehicles: vehicles.value.map(v => v.id),
  })
@@ -94,17 +110,41 @@
  initialSnapshot.value = buildSnapshot()
}
function handleResetPoints() {
  testPoints.value = []
function resolvePlanId(data: any) {
  const id = data?.id ?? data?.planId ?? data?.testPlanId ?? data
  if (id === null || id === undefined || id === '')
    return ''
  return String(id)
}
async function initCreatePlanId() {
  const res = await addTestPlan()
  if (res?.code !== 200) {
    message.error(res?.message || '初始化测试计划失败')
    return
  }
  const nextPlanId = resolvePlanId(res.data)
  if (!nextPlanId) {
    message.error('初始化测试计划失败')
    return
  }
  planId.value = nextPlanId
}
function handleAddPoint() {
  if (isReadOnly.value)
    return
  const newPoint: TestPoint = {
    id: Date.now(),
    pickupPoint: null,
    dropoffPoint: null,
    isPark: true,
    orderType: '',
    startTestPointId: null,
    startTestPointName: '',
    startTestPointAddress: '',
    endTestPointId: null,
    endTestPointName: '',
    endTestPointAddress: '',
    stopFlag: 1,
    testOrderType: null,
  }
  testPoints.value.push(newPoint)
}
@@ -113,112 +153,229 @@
const selectedPointType = ref('')
function handleSelectPickupPoint(index: number) {
  if (isReadOnly.value)
    return
  selectedPointIndex.value = index
  selectedPointType.value = 'pickup'
  showPickupSelectModal.value = true
  showPointSelectModal.value = true
}
function handleSelectDropoffPoint(index: number) {
  if (isReadOnly.value)
    return
  selectedPointIndex.value = index
  selectedPointType.value = 'dropoff'
  showDropoffSelectModal.value = true
  showPointSelectModal.value = true
}
function handleConfirmPickupSelect(points: any[]) {
const existingPointsForPointModal = computed(() => {
  const index = selectedPointIndex.value
  if (index < 0)
    return []
  const row = testPoints.value[index]
  if (!row)
    return []
  if (selectedPointType.value === 'pickup' && row.startTestPointId) {
    return [{
      id: row.startTestPointId,
      pointName: row.startTestPointName,
      pointAddress: row.startTestPointAddress,
    }]
  }
  if (selectedPointType.value === 'dropoff' && row.endTestPointId) {
    return [{
      id: row.endTestPointId,
      pointName: row.endTestPointName,
      pointAddress: row.endTestPointAddress,
    }]
  }
  return []
})
function handleConfirmPointSelect(points: any[]) {
  console.log('points',JSON.parse(JSON.stringify(points)))
  console.log('selectedPointIndex.value',selectedPointIndex.value)
  console.log('testPoints.value',testPoints.value)
  const point = points[0]
  if (point && selectedPointIndex.value !== -1) {
    const currentPoint = testPoints.value[selectedPointIndex.value]
    if (currentPoint) {
      currentPoint.pickupPoint = {
        id: point.id,
        name: point.pointName || point.name,
        address: point.pointAddress || point.address,
        isCharge: point.isCharge !== undefined ? point.isCharge : false,
        isPark: point.isPark !== undefined ? point.isPark : true,
      if (selectedPointType.value === 'pickup' && currentPoint.endTestPointId && String(currentPoint.endTestPointId) === String(point.id)) {
        message.warning('同一行上车点和下车点不能相同')
        return
      }
      if (selectedPointType.value === 'dropoff' && currentPoint.startTestPointId && String(currentPoint.startTestPointId) === String(point.id)) {
        message.warning('同一行上车点和下车点不能相同')
        return
      }
      if (selectedPointType.value === 'pickup') {
        currentPoint.startTestPointId = point.id
        currentPoint.startTestPointName = point.pointName || point.name || ''
        currentPoint.startTestPointAddress = point.pointAddress || point.address || ''
      }
      if (selectedPointType.value === 'dropoff') {
        currentPoint.endTestPointId = point.id
        currentPoint.endTestPointName = point.pointName || point.name || ''
        currentPoint.endTestPointAddress = point.pointAddress || point.address || ''
      }
    }
    testPoints.value = [...testPoints.value]
  }
  selectedPointIndex.value = -1
  selectedPointType.value = ''
}
function handleConfirmDropoffSelect(points: any[]) {
  const point = points[0]
  if (point && selectedPointIndex.value !== -1) {
    const currentPoint = testPoints.value[selectedPointIndex.value]
    if (currentPoint) {
      currentPoint.dropoffPoint = {
        id: point.id,
        name: point.pointName || point.name,
        address: point.pointAddress || point.address,
        isCharge: point.isCharge !== undefined ? point.isCharge : false,
        isPark: point.isPark !== undefined ? point.isPark : true,
      }
    }
  }
  selectedPointIndex.value = -1
  selectedPointType.value = ''
  showPointSelectModal.value = false
}
function handleGenerateOrder(index: number) {
  if (isReadOnly.value)
    return
  if (vehicles.value.length === 0) {
    message.warning('请先选择车辆')
    return
  }
  const testPoint = testPoints.value[index]
  if (!testPoint.pickupPoint || !testPoint.dropoffPoint) {
  if (!testPoint.startTestPointId || !testPoint.endTestPointId) {
    message.warning('请先选择上车点和下车点')
    return
  }
  if (String(testPoint.startTestPointId) === String(testPoint.endTestPointId)) {
    message.warning('同一行上车点和下车点不能相同')
    return
  }
  currentTestPointIndex.value = index
  orderModalData.value = {
    orderType: testPoint.orderType || '测试订单',
    isPark: testPoint.isPark,
    orderType: testPoint.testOrderType === 2 ? '巡游订单' : '测试订单',
    isPark: testPoint.stopFlag === 1,
  }
  showOrderModal.value = true
}
function handleConfirmGenerateOrder() {
async function handleConfirmGenerateOrder() {
  if (currentTestPointIndex.value === -1)
    return
  const testPoint = testPoints.value[currentTestPointIndex.value]
  if (!testPoint)
    return
  testPoint.orderType = orderModalData.value.orderType
  testPoint.isPark = orderModalData.value.isPark
  const newOrder: Order = {
    id: Date.now(),
    orderType: orderModalData.value.orderType,
    orderNumber: `ORD${Date.now()}`,
    orderTime: new Date().toLocaleString(),
    orderSource: '测试计划',
    startPoint: testPoint.pickupPoint?.name || '-',
    endPoint: testPoint.dropoffPoint?.name || '-',
    acceptVehicle: vehicles.value[0]?.plateNumber || '-',
    belongFleet: vehicles.value[0]?.fleetName || '-',
    status: '待接单',
  if (!planId.value) {
    message.warning('测试计划初始化中,请稍后再试')
    return
  }
  orders.value.push(newOrder)
  showOrderModal.value = false
  message.success('订单生成成功')
  const orderTypeValue = orderModalData.value.orderType === '巡游订单' ? 2 : 1
  const stopFlag = orderTypeValue === 1 ? 1 : (orderModalData.value.isPark ? 1 : 0)
  try {
    const res = await addTestOrder({
      endTestPointId: String(testPoint.endTestPointId || ''),
      orderType: orderTypeValue,
      startTestPointId: String(testPoint.startTestPointId || ''),
      stopFlag,
      testPlanId: planId.value || '',
      vehicleIds: vehicles.value.map(v => v.id),
    })
    if (res?.code === 200) {
      testPoint.testOrderType = orderTypeValue
      testPoint.stopFlag = stopFlag as 0 | 1
      showOrderModal.value = false
      message.success('订单生成成功')
      getOrderList()
      return
    }
    message.error(res?.message || '订单生成失败')
  }
  catch {
    message.error('订单生成失败,请稍后重试')
  }
}
function handleRemovePoint(index: number) {
  if (isReadOnly.value)
    return
  testPoints.value.splice(index, 1)
}
function handleResetOrderSearch() {
  orderSearchModel.value = {
    orderNumber: '',
    vehicle: '',
    status: '',
    orderTime: null,
    plateNumber: '',
    status: null,
    orderTimeRange: null,
  }
  orderPageParams.value.pageNum = 1
  getOrderList()
}
function handleSearchOrders() {
  orderPageParams.value.pageNum = 1
  getOrderList()
}
function handleCancelOrder(order: Order) {
const { bool: orderLoading, setTrue: startOrderLoading, setFalse: endOrderLoading } = useBoolean(false)
const statusOptions = [
  { label: '待接单', value: 1, type: 'primary' },
  { label: '待出发', value: 2, type: 'success' },
  { label: '接驾中', value: 3, type: 'warning' },
  { label: '待客上车', value: 4, type: 'primary' },
  { label: '乘客已上车', value: 5, type: 'success' },
  { label: '服务中', value: 6, type: 'success' },
  { label: '待客下车', value: 7, type: 'warning' },
  { label: '待付款', value: 8, type: 'warning' },
  { label: '待评价', value: 9, type: 'default' },
  { label: '已完成', value: 10, type: 'success' },
  { label: '已取消', value: 0, type: 'default' },
]
const orderStatusOptions = statusOptions.map(item => ({ label: item.label, value: item.value }))
function getOrderTypeLabel(type: number) {
  return type === 2 ? '巡游订单' : type === 1 ? '测试订单' : '-'
}
function getOrderSourceLabel(source: number) {
  return source === 1 ? '系统自动创建' : '-'
}
function getOrderStatusLabel(status: number) {
  return statusOptions.find(item => item.value === status)?.label || '-'
}
async function getOrderList() {
  startOrderLoading()
  try {
    const orderNumber = orderSearchModel.value.orderNumber?.trim()
    const plateNumber = orderSearchModel.value.plateNumber?.trim()
    const range = orderSearchModel.value.orderTimeRange
    const startTime = range?.[0] ? dayjs(range[0]).format('YYYY-MM-DD') : undefined
    const endTime = range?.[1] ? dayjs(range[1]).format('YYYY-MM-DD') : undefined
    const res = await fetchTestOrderPageList({
      ...orderPageParams.value,
      orderNumber: orderNumber || undefined,
      plateNumber: plateNumber || undefined,
      status: orderSearchModel.value.status ?? undefined,
      startTime,
      endTime,
      testPlanId: planId.value || undefined,
    })
    console.log('res',res)
    if (res.code === 200) {
      orders.value = res.data?.records || []
      orderCount.value = res.data?.total || 0
      return
    }
    orders.value = []
    orderCount.value = 0
  }
  finally {
    endOrderLoading()
  }
}
function changeOrderPage(page: number, size: number) {
  orderPageParams.value.pageNum = page
  orderPageParams.value.pageSize = size
  getOrderList()
}
function handleCancelOrder(order: TestOrderRow) {
  dialog.warning({
    title: '确认取消订单?',
    content: '',
@@ -226,14 +383,17 @@
    negativeText: '取消',
    maskClosable: false,
    autoFocus: false,
    onPositiveClick: () => {
      orders.value = orders.value.filter(o => o.id !== order.id)
      message.success('取消订单成功')
    onPositiveClick: async () => {
      const res = await cancelAndCloseTestOrder({ orderId: order.id, type: 1 })
      if (res.code === 200) {
        message.success('取消订单成功')
        getOrderList()
      }
    },
  })
}
function handleDeleteOrder(order: Order) {
function handleDeleteOrder(order: TestOrderRow) {
  dialog.warning({
    title: '确认删除订单?',
    content: '',
@@ -241,18 +401,65 @@
    negativeText: '取消',
    maskClosable: false,
    autoFocus: false,
    onPositiveClick: () => {
      orders.value = orders.value.filter(o => o.id !== order.id)
      message.success('删除订单成功')
    onPositiveClick: async () => {
      const res = await deleteTestOrderById(order.id)
      if (res.code === 200) {
        message.success('删除订单成功')
        getOrderList()
      }
    },
  })
}
function handleReassignOrder(order: Order) {
  message.info('改派功能开发中')
const showReassignModal = ref(false)
const isReassignVehicleOpen = ref(false)
const reassignVehicleOptions = ref<{ label: string, value: number }[]>([])
const reassignForm = ref({
  orderId: 0,
  vehicleId: null as number | null,
  reassignReason: '',
})
async function fetchReassignVehicleOptions(orderId: number) {
  const res = await getAvailableVehicleList({orderId,orderType:2})
  if (res.code === 200) {
    reassignVehicleOptions.value = (res.data || []).map((item: any) => ({
      label: item.plateNumber || item.vehicleCode || `车辆${item.id}`,
      value: item.id,
    }))
    return
  }
  reassignVehicleOptions.value = []
}
function handleCloseOrder(order: Order) {
async function handleReassignOrder(order: TestOrderRow) {
  reassignForm.value = {
    orderId: order.id,
    vehicleId: null,
    reassignReason: '',
  }
  await fetchReassignVehicleOptions(order.id)
  showReassignModal.value = true
}
async function handleConfirmReassign() {
  if (!reassignForm.value.vehicleId || !reassignForm.value.reassignReason.trim()) {
    message.warning('请选择车辆并填写改派原因')
    return
  }
  const res = await reassignTestOrderById({
    orderId: reassignForm.value.orderId,
    vehicleId: reassignForm.value.vehicleId,
    reassignReason: reassignForm.value.reassignReason.trim(),
  })
  if (res.code === 200) {
    message.success('改派成功')
    showReassignModal.value = false
    getOrderList()
  }
}
function handleCloseOrder(order: TestOrderRow) {
  dialog.warning({
    title: '确认关闭订单?',
    content: '',
@@ -260,23 +467,55 @@
    negativeText: '取消',
    maskClosable: false,
    autoFocus: false,
    onPositiveClick: () => {
      orders.value = orders.value.filter(o => o.id !== order.id)
      message.success('关闭订单成功')
    onPositiveClick: async () => {
      const res = await cancelAndCloseTestOrder({ orderId: order.id, type: 2 })
      if (res.code === 200) {
        message.success('关闭订单成功')
        getOrderList()
      }
    },
  })
}
function handleOrderDetail(order: Order) {
  message.info('订单详情功能开发中')
function handleOrderDetail(order: TestOrderRow) {
  if (order.orderType === 2) {
    router.push({ name: 'patrolPlanOrderDetail', query: { orderId: String(order.id), type: 'cruise' } })
    return
  }
  router.push({ name: 'orderDetail', query: { orderId: String(order.id), type: 'test' } })
}
const orderColumns = computed<DataTableColumns<Order>>(() => [
const showChangeAmountModal = ref(false)
const changeAmountForm = ref({
  id: 0,
  amount: null as number | null,
})
function handleChangeAmount(order: TestOrderRow) {
  changeAmountForm.value = { id: order.id, amount: null }
  showChangeAmountModal.value = true
}
async function handleConfirmChangeAmount() {
  if (changeAmountForm.value.amount === null) {
    message.warning('请输入订单金额')
    return
  }
  const res = await changeTestOrderAmount({ id: changeAmountForm.value.id, amount: changeAmountForm.value.amount })
  if (res.code === 200) {
    message.success('改价成功')
    showChangeAmountModal.value = false
    getOrderList()
  }
}
const orderColumns = computed<DataTableColumns<TestOrderRow>>(() => [
  {
    title: '订单类型',
    key: 'orderType',
    width: 100,
    align: 'left',
    render: (row) => getOrderTypeLabel(row.orderType),
  },
  {
    title: '订单编号',
@@ -286,37 +525,39 @@
  },
  {
    title: '下单时间',
    key: 'orderTime',
    key: 'createTime',
    width: 160,
    align: 'left',
    render: row => <span>{row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '-'}</span>,
  },
  {
    title: '订单来源',
    key: 'orderSource',
    width: 100,
    align: 'left',
    render: row => <span>{getOrderSourceLabel(row.orderSource)}</span>,
  },
  {
    title: '起点',
    key: 'startPoint',
    minWidth: 150,
    key: 'startAddress',
    width: 160,
    align: 'left',
  },
  {
    title: '终点',
    key: 'endPoint',
    minWidth: 150,
    key: 'endAddress',
    width: 160,
    align: 'left',
  },
  {
    title: '接单车辆',
    key: 'acceptVehicle',
    key: 'plateNumber',
    width: 120,
    align: 'left',
  },
  {
    title: '归属车队',
    key: 'belongFleet',
    key: 'fleetName',
    width: 120,
    align: 'left',
  },
@@ -325,38 +566,189 @@
    key: 'status',
    width: 100,
    align: 'left',
    render: (row: Order) => {
      const statusType = row.status === '已完成' ? 'success' : row.status === '已取消' ? 'default' : 'primary'
      return h('n-tag', { type: statusType, size: 'small' }, () => row.status)
    render: (row: TestOrderRow) => {
      const option = statusOptions.find(item => item.value == row.status)
      console.log('option', option,'row.status',row.status)
      return <n-tag type={option?.type || 'default'} size="small">{option?.label || '-'}</n-tag>
    },
  },
  {
    title: '操作',
    key: 'actions',
    width: 200,
    width: 240,
    align: 'left',
    fixed: 'right' as const,
    render: (row: Order) => {
    render: (row: TestOrderRow) => {
      return (
        <n-space size={2}>
          <n-button text type="primary" size="small" onClick={() => handleCancelOrder(row)}>
            取消订单
          </n-button>
          {(row.status === 1 || row.status === 2 || row.status === 3 || row.status === 4) && (
            <n-button text type="primary" size="small" onClick={() => handleCancelOrder(row)}>
              取消订单
            </n-button>
          )}
          {row.status === 6 && (
            <n-button text type="primary" size="small" onClick={() => handleCloseOrder(row)}>
              关闭订单
            </n-button>
          )}
          {row.status === 8 && (
            <n-button text type="primary" size="small" onClick={() => handleChangeAmount(row)}>
              改价
            </n-button>
          )}
          {(row.status === 2 || row.status === 3 || row.status === 4) && (
            <n-button text type="primary" size="small" onClick={() => handleReassignOrder(row)}>
              改派
            </n-button>
          )}
          <n-button text type="primary" size="small" onClick={() => handleOrderDetail(row)}>
            详情
          </n-button>
          <n-button text type="error" size="small" onClick={() => handleDeleteOrder(row)}>
            删除
          </n-button>
          <n-button text type="primary" size="small" onClick={() => handleReassignOrder(row)}>
            改派
          </n-button>
          <n-button text type="primary" size="small" onClick={() => handleCloseOrder(row)}>
            关闭订单
          </n-button>
          {row.status === 0 && (
            <n-button text type="error" size="small" onClick={() => handleDeleteOrder(row)}>
              删除
            </n-button>
          )}
        </n-space>
      )
    },
  },
])
const vehicleTableScrollX = 680
const vehicleColumns = computed<DataTableColumns<Vehicle>>(() => [
  {
    title: '序号',
    key: 'index',
    width: 60,
    align: 'left',
    render: (_row, index) => index + 1,
  },
  {
    title: '车辆编码',
    key: 'vehicleCode',
    width: 160,
    align: 'left',
  },
  {
    title: '车牌号',
    key: 'plateNumber',
    width: 120,
    align: 'left',
  },
  {
    title: '型号',
    key: 'vehicleModel',
    width: 120,
    align: 'left',
  },
  {
    title: '所属车队',
    key: 'fleetName',
    width: 140,
    align: 'left',
  },
  {
    title: '操作',
    key: 'actions',
    width: 80,
    align: 'left',
    render: (row: Vehicle) => (
      <n-button
        text
        type="primary"
        size="small"
        disabled={isReadOnly.value}
        onClick={() => handleRemoveVehicle(row)}
      >
        移除
      </n-button>
    ),
  },
])
const testPointTableScrollX = 960
const testPointColumns = computed<DataTableColumns<TestPoint>>(() => [
  {
    title: '排序',
    key: 'index',
    width: 60,
    align: 'left',
    render: (_row, index) => index + 1,
  },
  {
    title: '上车点',
    key: 'pickupPoint',
    width: 200,
    align: 'left',
    render: (row: TestPoint) => row.startTestPointName || '-',
  },
  {
    title: '下车点',
    key: 'dropoffPoint',
    width: 200,
    align: 'left',
    render: (row: TestPoint) => row.endTestPointName || '-',
  },
  {
    title: '是否停车',
    key: 'isPark',
    width: 100,
    align: 'left',
    render: (row: TestPoint) => (row.stopFlag === 1 ? '是' : '否'),
  },
  {
    title: '订单类型',
    key: 'orderType',
    width: 120,
    align: 'left',
    render: (row: TestPoint) => row.testOrderType == 2 ? '巡游订单' : row.testOrderType == 1 ? '测试订单' : '-',
  },
  {
    title: '操作',
    key: 'actions',
    width: 280,
    align: 'left',
    render: (_row, index) => (
      <n-space size={2}>
        <n-button
          text
          type="primary"
          size="small"
          disabled={isReadOnly.value}
          onClick={() => handleSelectPickupPoint(index)}
        >
          选择上车点
        </n-button>
        <n-button
          text
          type="primary"
          size="small"
          disabled={isReadOnly.value}
          onClick={() => handleSelectDropoffPoint(index)}
        >
          选择下车点
        </n-button>
        <n-button
          text
          type="primary"
          size="small"
          disabled={isReadOnly.value}
          onClick={() => handleGenerateOrder(index)}
        >
          生成订单
        </n-button>
        <n-button
          text
          type="error"
          size="small"
          disabled={isReadOnly.value}
          onClick={() => handleRemovePoint(index)}
        >
          移除
        </n-button>
      </n-space>
    ),
  },
])
@@ -405,18 +797,20 @@
  if (!planId.value)
    return
  const res = await fetchCruisePlanDetail(planId.value)
  const res = await fetchTestPlanDetail(planId.value)
  if (res.code === 200) {
    planName.value = res.data?.planName || ''
    const pointList = (res.data?.pointList || [])
      .slice()
      .sort((a: any, b: any) => (a.sortOrder || 0) - (b.sortOrder || 0))
    chargePoints.value = pointList.map((p: any) => ({
      id: p.pointId,
      name: p.pointName,
      address: p.pointAddress,
      isCharge: false,
      isPark: p.stopFlag !== undefined ? p.stopFlag === 1 : true,
    const pointList = (res.data?.pointList || []).slice()
    testPoints.value = pointList.map((p: any, index: number) => ({
      id: Date.now() + index,
      startTestPointId: p.startTestPointId,
      startTestPointName: p.startTestPointName || '',
      startTestPointAddress: p.startTestPointAddress || '',
      endTestPointId: p.endTestPointId,
      endTestPointName: p.endTestPointName || '',
      endTestPointAddress: p.endTestPointAddress || '',
      stopFlag: p.stopFlag === 1 ? 1 : 0,
      testOrderType: p.testOrderType || null,
    }))
    vehicles.value = (res.data?.vehicleList || []).map((v: any) => ({
      id: v.vehicleId,
@@ -426,15 +820,24 @@
      fleetName: v.fleetName,
    }))
    setInitialSnapshot()
    getOrderList()
    return
  }
  planName.value = ''
  chargePoints.value = []
  testPoints.value = []
  vehicles.value = []
  setInitialSnapshot()
}
async function handleSave() {
  if (isReadOnly.value) {
    router.back()
    return
  }
  if (!planId.value) {
    message.warning('测试计划初始化中,请稍后再试')
    return
  }
  const name = planName.value.trim()
  if (!name) {
    message.warning('请输入计划名称')
@@ -448,20 +851,32 @@
    message.warning('请至少选择一辆车辆')
    return
  }
  const invalidPointIndex = testPoints.value.findIndex((p) => {
    const startId = String(p.startTestPointId || '').trim()
    const endId = String(p.endTestPointId || '').trim()
    return !startId || !endId
  })
  if (invalidPointIndex > -1) {
    message.warning(`第${invalidPointIndex + 1}行请先选择上车点和下车点`)
    return
  }
  const samePointIndex = testPoints.value.findIndex((p) => String(p.startTestPointId) === String(p.endTestPointId))
  if (samePointIndex > -1) {
    message.warning(`第${samePointIndex + 1}行上车点和下车点不能相同`)
    return
  }
  const payload = {
    id: planId.value,
    planName: name,
    testPointList: testPoints.value.map((p, index) => ({
      pickupPointId: p.pickupPoint?.id,
      dropoffPointId: p.dropoffPoint?.id,
      sortOrder: index + 1,
      stopFlag: (p.isPark ? 1 : 0) as 0 | 1,
      orderType: p.orderType,
    pointList: testPoints.value.map(p => ({
      startTestPointId: p.startTestPointId || 0,
      endTestPointId: p.endTestPointId || 0,
      stopFlag: p.stopFlag,
      testOrderType: p.testOrderType || 1,
    })),
    vehicleIdList: vehicles.value.map(v => v.id),
  }
  const res = isEdit.value
    ? await editCruisePlan({ id: planId.value, ...payload })
    : await addCruisePlan(payload)
  const res = await editTestPlan(payload)
  if (res.code === 200) {
    message.success('保存成功')
    router.back()
@@ -469,17 +884,27 @@
}
onMounted(() => {
  if (isEdit.value) {
  if (isEdit.value || isDetail.value) {
    planId.value = String(route.query.id || '')
    getDetail()
    getOrderList()
  }
  else {
    initCreatePlanId()
    setInitialSnapshot()
  }
})
watch(() => route.query.id, (id) => {
  if (isEdit.value || isDetail.value) {
    planId.value = String(id || '')
  }
})
watch(() => planId.value, () => {
  if (isEdit.value) {
  if (isEdit.value || isDetail.value) {
    getDetail()
    getOrderList()
  }
})
</script>
@@ -488,7 +913,7 @@
  <div class="h-full flex flex-col gap-4">
    <n-card style="border-radius: 6px" content-style="padding: 16px 24px;">
      <div class="text-lg font-bold">
        {{ isEdit ? "编辑测试计划" : "新增测试计划" }}
        {{ isDetail ? "测试计划详情" : isEdit ? "编辑测试计划" : "新增测试计划" }}
      </div>
    </n-card>
@@ -509,6 +934,7 @@
          style="width: 400px"
          maxlength="30"
          show-count
          :disabled="isReadOnly"
        />
      </div>
    </n-card>
@@ -529,55 +955,25 @@
              color: #0068ff;
            "
            @click="handleSelectVehicle"
            :disabled="isReadOnly"
          >
            选择
          </n-button>
          <n-button style="height: 28px" @click="handleResetVehicles">
          <n-button style="height: 28px" @click="handleResetVehicles" :disabled="isReadOnly">
            重置
          </n-button>
        </div>
      </div>
      <div class="mb-2 flex bg-gray-100 px-4 py-3">
        <div class="flex-1 text-sm font-medium text-gray-700">序号</div>
        <div class="flex-1 text-sm font-medium text-gray-700">车辆编码</div>
        <div class="flex-1 text-sm font-medium text-gray-700">车牌号</div>
        <div class="flex-1 text-sm font-medium text-gray-700">型号</div>
        <div class="flex-1 text-sm font-medium text-gray-700">所属车队</div>
        <div class="w-24 text-sm font-medium text-gray-700">操作</div>
      </div>
      <div
        v-for="(vehicle, index) in vehicles"
        :key="vehicle.id"
        class="flex items-center px-4 py-3"
        style="border-bottom: 1px solid rgba(23, 31, 38, 0.1)"
      >
        <div class="flex-1 text-sm">
          {{ index + 1 }}
        </div>
        <div class="flex-1 text-sm pr-4">
          {{ vehicle.vehicleCode }}
        </div>
        <div class="flex-1 text-sm pr-4">
          {{ vehicle.plateNumber }}
        </div>
        <div class="flex-1 text-sm pr-4">
          {{ vehicle.vehicleModel }}
        </div>
        <div class="flex-1 text-sm pr-4">
          {{ vehicle.fleetName }}
        </div>
        <div class="w-24">
          <n-button
            text
            type="primary"
            size="small"
            @click="handleRemoveVehicle(vehicle)"
          >
            移除
          </n-button>
        </div>
      </div>
      <n-data-table
        :columns="vehicleColumns"
        :data="vehicles"
        :bordered="false"
        :scroll-x="vehicleTableScrollX"
        :header-height="18"
        :row-height="18"
        :row-key="row => row.id"
      />
    </n-card>
    <n-card style="border-radius: 6px" content-style="padding: 24px;">
@@ -594,75 +990,21 @@
            color: #0068ff;
          "
          @click="handleAddPoint"
          :disabled="isReadOnly"
        >
          添加
        </n-button>
      </div>
      <div class="mb-2 flex bg-gray-100 px-4 py-3">
        <div class="w-16 text-sm font-medium text-gray-700">排序</div>
        <div class="flex-1 text-sm font-medium text-gray-700">上车点</div>
        <div class="flex-1 text-sm font-medium text-gray-700">下车点</div>
        <div class="w-24 text-sm font-medium text-gray-700">是否停车</div>
        <div class="w-32 text-sm font-medium text-gray-700">订单类型</div>
        <div class="w-64 text-sm font-medium text-gray-700">操作</div>
      </div>
      <div
        v-for="(point, index) in testPoints"
        :key="point.id"
        class="flex items-center px-4 py-3"
        style="border-bottom: 1px solid rgba(23, 31, 38, 0.1)"
      >
        <div class="w-16 flex">
          <span class="text-sm">{{ index + 1 }}</span>
        </div>
        <div class="flex-1 text-sm pr-4">
          {{ point.pickupPoint?.name || "-" }}
        </div>
        <div class="flex-1 text-sm text-gray-600 pr-4">
          {{ point.dropoffPoint?.name || "-" }}
        </div>
        <div class="w-24 text-sm">
          {{ point.isPark ? "是" : "否" }}
        </div>
        <div class="w-32 text-sm">
          {{ point.orderType || "-" }}
        </div>
        <div class="w-64 flex gap-2">
          <n-button
            text
            type="primary"
            size="small"
            @click="handleSelectPickupPoint(index)"
          >
            选择上车点
          </n-button>
          <n-button
            text
            type="primary"
            size="small"
            @click="handleSelectDropoffPoint(index)"
          >
            选择下车点
          </n-button>
          <n-button
            text
            type="primary"
            size="small"
            @click="handleGenerateOrder(index)"
          >
            生成订单
          </n-button>
          <n-button
            text
            type="error"
            size="small"
            @click="handleRemovePoint(index)"
          >
            移除
          </n-button>
        </div>
      </div>
      <n-data-table
        :columns="testPointColumns"
        :data="testPoints"
        :bordered="false"
        :scroll-x="testPointTableScrollX"
        :header-height="18"
        :row-height="18"
        :row-key="row => row.id"
      />
    </n-card>
    <n-card
@@ -687,7 +1029,7 @@
        <div class="flex items-center gap-2">
          <div class="text-sm" style="width: 70px">车辆</div>
          <n-input
            v-model:value="orderSearchModel.vehicle"
            v-model:value="orderSearchModel.plateNumber"
            placeholder="请输入车辆"
            clearable
            style="width: 200px"
@@ -700,21 +1042,16 @@
            placeholder="请选择订单状态"
            clearable
            style="width: 150px"
            :options="[
              { label: '待接单', value: '待接单' },
              { label: '待出发', value: '待出发' },
              { label: '服务中', value: '服务中' },
              { label: '已完成', value: '已完成' },
              { label: '已取消', value: '已取消' },
            ]"
            :options="orderStatusOptions"
            @update:show="(val) => isOrderStatusOpen = val"
          >
            <template #arrow>
              <img
                src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
                class="arrow-icon"
                :class="['arrow-icon', isOrderStatusOpen ? 'arrow-icon--open' : '']"
                style="width: 16px; height: 16px"
                alt="arrow"
              />
              >
            </template>
          </n-select>
        </div>
@@ -727,12 +1064,18 @@
            clearable
            :actions="[]"
            :close-on-select="true"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            style="width: 300px"
          />
        </div>
        <div class="flex gap-2">
          <n-button type="primary" @click="handleSearchOrders"> 查询 </n-button>
          <n-button @click="handleResetOrderSearch"> 重置 </n-button>
          <n-button type="primary" @click="handleSearchOrders">
            查询
          </n-button>
          <n-button @click="handleResetOrderSearch">
            重置
          </n-button>
        </div>
      </div>
@@ -741,12 +1084,25 @@
          :columns="orderColumns"
          :data="orders"
          :bordered="false"
          :scroll-x="1200"
          :scroll-x="orderTableScrollX"
          :header-height="18"
          :row-height="18"
          :flex-height="true"
        />
          :loading="orderLoading"
        >
        <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>
      </div>
      <Pagination
        :count="orderCount"
        :page="orderPageParams.pageNum"
        :page-size="orderPageParams.pageSize"
        class="robotaxi-pagination mt-12px"
        @change="changeOrderPage"
      />
    </n-card>
    <div
@@ -754,9 +1110,10 @@
      style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100"
    >
      <n-button style="width: 84px; height: 32px" @click="handleCancel">
        取消
        {{ isDetail ? "返回" : "取消" }}
      </n-button>
      <n-button
        v-if="!isDetail"
        type="primary"
        style="width: 84px; height: 32px"
        @click="handleSave"
@@ -766,19 +1123,9 @@
    </div>
    <SelectPointModal
      v-model:show="showSelectModal"
      :existing-points="testPoints"
      @confirm="handleConfirmSelect"
    />
    <SelectPointModal
      v-model:show="showPickupSelectModal"
      :existing-points="testPoints"
      @confirm="handleConfirmPickupSelect"
    />
    <SelectPointModal
      v-model:show="showDropoffSelectModal"
      :existing-points="testPoints"
      @confirm="handleConfirmDropoffSelect"
      v-model:show="showPointSelectModal"
      :existing-points="existingPointsForPointModal"
      @confirm="handleConfirmPointSelect"
    />
    <VehicleSelectModal
      v-model:visible="showVehicleModal"
@@ -791,7 +1138,11 @@
      title="生成订单"
      preset="card"
      size="small"
      :bordered="false"
       style="width: 400px"
       :segmented="{
        content: true,
        action: true,
      }"
      :mask-closable="false"
    >
      <div class="p-4">
@@ -828,6 +1179,91 @@
        </div>
      </template>
    </n-modal>
    <n-modal
      v-model:show="showChangeAmountModal"
      title="订单改价"
      preset="card"
      size="small"
      style="width: 400px"
       :segmented="{
        content: true,
        action: true,
      }"
      :bordered="false"
      :mask-closable="false"
    >
      <div class="p-4">
        <div class="text-sm mb-2">订单金额:</div>
        <n-input-number
          v-model:value="changeAmountForm.amount"
          :precision="2"
          min="0"
          placeholder="请输入金额"
          style="width: 100%"
        />
      </div>
      <template #footer>
        <div class="flex justify-end gap-2">
          <n-button @click="showChangeAmountModal = false"> 取消 </n-button>
          <n-button type="primary" @click="handleConfirmChangeAmount">
            确认
          </n-button>
        </div>
      </template>
    </n-modal>
    <n-modal
      v-model:show="showReassignModal"
      title="改派订单"
      preset="card"
      size="small"
       style="width: 400px"
       :segmented="{
        content: true,
        action: true,
      }"
      :bordered="false"
      :mask-closable="false"
    >
      <div class="p-4">
        <div class="text-sm mb-2">改派车辆:</div>
        <n-select
          v-model:value="reassignForm.vehicleId"
          placeholder="请选择改派车辆"
          :options="reassignVehicleOptions"
          clearable
          style="width: 100%"
          @update:show="(val) => isReassignVehicleOpen = val"
        >
          <template #arrow>
            <img
              src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
              :class="['arrow-icon', isReassignVehicleOpen ? 'arrow-icon--open' : '']"
              style="width: 16px; height: 16px"
              alt="arrow"
            >
          </template>
        </n-select>
        <div class="text-sm mb-2 mt-4">改派原因:</div>
        <n-input
          v-model:value="reassignForm.reassignReason"
          type="textarea"
          placeholder="请输入改派原因"
          maxlength="200"
          show-count
        />
      </div>
      <template #footer>
        <div class="flex justify-end gap-2">
          <n-button @click="showReassignModal = false"> 取消 </n-button>
          <n-button type="primary" @click="handleConfirmReassign">
            确认
          </n-button>
        </div>
      </template>
    </n-modal>
  </div>
</template>
src/views/testDemo/testPlanList/api.txt
@@ -74,6 +74,38 @@
​operation​/api​/v1​/xp-test-plan​/detail  测试计划详情  get  params
  请求:id 
  响应:{
        "code": 0,
        "data": {
            "createTime": "2026-03-27T06:50:31.264Z",
            "id": 0,
            "planName": "string", 计划名称
            "pointList": [ 测试点位列表
            {
                "endTestPointAddress": "string", 终点详细地址
                "endTestPointId": 0,
                "endTestPointName": "string", 终点点位名称
                "startTestPointAddress": "string", 起点详细地址
                "startTestPointId": 0,
                "startTestPointName": "string", 起点点位名称
                "stopFlag": 0, 是否停车 0-否 1-是
                "testOrderType": 0 测试订单类型(1=测试单,2=巡游单)
            }
            ],
            "pointNum": 0, 关联点位数
            "vehicleList": [ 测试车辆列表
            {
                "fleetName": "string", 所属车队
                "plateNumber": "string", 车牌号
                "vehicleCode": "string", 车辆编码
                "vehicleId": 0, 车辆ID
                "vehicleModel": "string" 型号
            }
            ],
            "vehicleNum": 0
        },
        "msg": "string"
        }
 订单相关:
@@ -114,14 +146,44 @@
        },
        "msg": "string"
        }
2,  order​/api​/v1​/testOrder​/addTestOrder 生成订单 post json
2,      order​/api​/v1​/testOrder​/addTestOrder 生成订单 post json
   请求:{
        "endTestPointId": 0, 测试终点id
        "orderType": 0, 订单类型(1=测试单,2=巡游单)
        "startTestPointId": 0, 测试起点id
        "stopFlag": 0, 是否停车(0=否,1=是)
        "testPlanId": 0, 测试计划id
        "testPlanPointId": 0 测试计划点位id
        "vehicleIds": [ 车辆id
            0
         ]
        }
    响应:{
        "code": 0,
        "data": {},
        "msg": "string"
        }
3, order/api​/v1​/testOrder​/cancelAndCloseById 取消订单  post  json
   请求:{
        "orderId": 1, 订单ID
        "type": 1 操作类型:1=取消订单,2=关闭订单
        }
4, order/api/v1/testOrder/changeAmount  改价  post  params
  请求:amount 订单金额   id  订单ID
5,  order​/api​/v1​/testOrder​/deleteById 删除订单 delete
  请求:id
6,order/api/v1/testOrder/reassignById  改派  post json
 请求:{
    "orderId": 1,
    "reassignReason": "车辆故障需要更换", 改派原因
    "vehicleId": 100 新车辆ID
    }
7,order​/api​/v1​/testOrder​/queryById  详情  get
 请求:id
src/views/testDemo/testPlanList/index.vue
@@ -5,7 +5,7 @@
import { useRouter } from 'vue-router'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import { useBoolean } from '@/hooks'
import { deleteCruisePlanById, fetchCruisePlanPageList } from '@/service/api/copyText'
import { deleteTestPlanById, fetchTestPlanPageList } from '@/service/api/copyText'
const router = useRouter()
const dialog = useDialog()
@@ -13,11 +13,12 @@
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
interface CruisePlanRow {
interface TestPlanRow {
  id: number
  planName: string
  pointNum: number
  vehicleNum: number
  orderNum: number
}
const initialModel = {
@@ -30,9 +31,9 @@
  pageSize: 10,
})
const count = ref(0)
const listData = ref<CruisePlanRow[]>([])
const listData = ref<TestPlanRow[]>([])
const tableScrollX = 860
const tableScrollX = 1000
function handleResetSearch() {
  model.value = { ...initialModel }
@@ -44,7 +45,7 @@
  startLoading()
  try {
    const planName = model.value.planName?.trim()
    const res = await fetchCruisePlanPageList({
    const res = await fetchTestPlanPageList({
      ...pageParams.value,
      planName: planName || undefined,
    })
@@ -76,15 +77,15 @@
  router.push({ name: 'testPlanAdd' })
}
function handleDetail(row: CruisePlanRow) {
  router.push({ name: 'testPlanDetail', params: { id: String(row.id) } })
function handleDetail(row: TestPlanRow) {
  router.push({ name: 'testPlanAdd', query: { id: String(row.id), mode: 'detail' } })
}
function handleEdit(row: CruisePlanRow) {
function handleEdit(row: TestPlanRow) {
  router.push({ name: 'testPlanAdd', query: { id: String(row.id), mode: 'edit' } })
}
function handleDelete(row: CruisePlanRow) {
function handleDelete(row: TestPlanRow) {
  dialog.create({
    title: '确认删除该测试计划吗?',
    content: '',
@@ -93,7 +94,7 @@
    maskClosable: false,
    autoFocus: false,
    onPositiveClick: async () => {
      const res = await deleteCruisePlanById(row.id)
      const res = await deleteTestPlanById(row.id)
      if (res.code === 200) {
        message.success('删除成功')
        getList()
@@ -102,7 +103,7 @@
  })
}
const columns = computed<DataTableColumns<CruisePlanRow>>(() => [
const columns = computed<DataTableColumns<TestPlanRow>>(() => [
  {
    title: '计划ID',
    key: 'id',
@@ -145,6 +146,14 @@
    ellipsis: true,
  },
  {
    title: '关联订单数',
    key: 'orderNum',
    width: 140,
    align: 'left',
    resizable: true,
    ellipsis: true,
  },
  {
    title: '关联车辆数',
    key: 'vehicleNum',
    width: 140,
@@ -158,7 +167,7 @@
    width: 200,
    align: 'left',
    fixed: 'right',
    render: (row: CruisePlanRow) => {
    render: (row: TestPlanRow) => {
      return (
        <NSpace justify="start">
          <NButton v-permission="4074" text type="primary" onClick={() => handleDetail(row)}>
@@ -234,7 +243,13 @@
          :scroll-x="tableScrollX"
          :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>
        <Pagination
          :count="count"
          :page="pageParams.pageNum"
src/views/transaction/order/orderManagement/components/TableModal.vue
@@ -72,7 +72,7 @@
const options = ref()
async function getList(orderId: number) {
  const res = await getAvailableVehicleList(orderId)
  const res = await getAvailableVehicleList({orderId,orderType:1})
  if (res.code === 200) {
    options.value = res.data.map((item: any) => ({
      label: item.plateNumber,
src/views/transaction/order/orderManagement/detail.vue
@@ -5,6 +5,7 @@
import { useBoolean } from '@/hooks'
import { getOrderDetail } from '@/service/api/transaction'
import { fetchCruiseOrderDetail } from '@/service/api/cruise'
import { fetchTestOrderDetail } from '@/service/api/copyText'
const { load } = useScriptTag(
  `https://webapi.amap.com/maps?v=2.0&key=${import.meta.env.VITE_MAP_KEY}&plugin=AMap.PlaceSearch,AMap.Geocoder`,
@@ -101,8 +102,11 @@
onMounted(() => {
  if (orderId.value) {
    startLoading()
    // const detailApi = getOrderDetail
    const detailApi = type.value === 'cruise' ? fetchCruiseOrderDetail : getOrderDetail
    const detailApi = type.value === 'cruise'
      ? fetchCruiseOrderDetail
      : type.value === 'test'
        ? fetchTestOrderDetail
        : getOrderDetail
    detailApi(orderId.value).then((res) => {
      if (res.code === 200) {
        model.value = res.data || {}
src/views/userContent/banner/index.vue
@@ -200,7 +200,7 @@
  {
    title: '操作',
    key: 'actions',
    align: 'center',
    align: 'left',
    width: 200,
    fixed: 'right',
    render: row => (