| | |
| | | <script setup lang="tsx"> |
| | | import { ref, computed } from 'vue' |
| | | import { ref, computed, h } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { NButton, NSpace, NTag, NTooltip, NIcon } from 'naive-ui' |
| | | import { NButton, NSpace, NTag, NTooltip, NIcon, NForm, NFormItem, NFormItemGi, NRadioGroup, NRadioButton, NInput, NSwitch, NDataTable, NGrid, useDialog } from 'naive-ui' |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { useMessage } from 'naive-ui' |
| | | import waitIcon from '@/assets/pricingManagement/wait.png' |
| | | import finishIcon from '@/assets/pricingManagement/finish.png' |
| | | import activeIcon from '@/assets/pricingManagement/active.png' |
| | | import noticeSvg from '@/assets/svg-icons/notice.svg' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import { useMessage, useDialog } from 'naive-ui' |
| | | import IconInfo from '~icons/ri/information-line' |
| | | import PointSelectModal from './components/PointSelectModal.vue' |
| | | import OrderGenModeDescModal from './components/OrderGenModeDescModal.vue' |
| | | |
| | | const router = useRouter() |
| | | const message = useMessage() |
| | | const dialog = useDialog() |
| | | const message = useMessage() |
| | | |
| | | const currentStep = ref<number>(1) |
| | | |
| | | // ========== Modals ========== |
| | | const pointSelectModalVisible = ref(false) |
| | | const orderGenModeDescModalVisible = ref(false) |
| | | |
| | | function handlePointSelect(selectedRows: any[]) { |
| | | const existingIds = new Set(step1Data.value.map(item => item.id)) |
| | | const nextRows = selectedRows.filter(item => !existingIds.has(item.id)) |
| | | step1Data.value = [...step1Data.value, ...nextRows] |
| | | } |
| | | |
| | | const stepItems = ['测试点位选择', '订单生成结果预览', '测试车辆选择'] |
| | | |
| | | // ========== Common Actions ========== |
| | | function handleClearPoints() { |
| | | if (step1Data.value.length === 0) |
| | | return |
| | | dialog.warning({ |
| | | title: '是否确认一键清空?', |
| | | content: '清空后,需重新选择测试点位', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | step1Data.value = [] |
| | | step1CheckedRowKeys.value = [] |
| | | message.success('已清空测试点位') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleClearVehicles() { |
| | | if (step3Data.value.length === 0) |
| | | return |
| | | dialog.warning({ |
| | | title: '是否确认一键清空?', |
| | | content: '清空后,需重新选择测试车辆', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | step3Data.value = [] |
| | | step3CheckedRowKeys.value = [] |
| | | message.success('已清空测试车辆') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleBatchRemovePoints() { |
| | | if (step1CheckedRowKeys.value.length === 0) { |
| | | message.warning('请先勾选需要移除的点位') |
| | | return |
| | | } |
| | | dialog.warning({ |
| | | title: '是否确认批量移除?', |
| | | content: '移除后,需重新选择测试点位', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | step1Data.value = step1Data.value.filter(item => !step1CheckedRowKeys.value.includes(item.id)) |
| | | step1CheckedRowKeys.value = [] |
| | | message.success('已移除选中的测试点位') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleBatchRemoveVehicles() { |
| | | if (step3CheckedRowKeys.value.length === 0) { |
| | | message.warning('请先勾选需要移除的车辆') |
| | | return |
| | | } |
| | | dialog.warning({ |
| | | title: '是否确认批量移除?', |
| | | content: '移除后,需重新选择测试车辆', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | step3Data.value = step3Data.value.filter(item => !step3CheckedRowKeys.value.includes(item.id)) |
| | | step3CheckedRowKeys.value = [] |
| | | message.success('已移除选中的测试车辆') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // ================= Step 1: 测试点位选择 ================= |
| | | const planName = ref('') |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="roboTaxi flex flex-col h-full bg-[#F3F4F7] p-16px box-border"> |
| | | <div class="bg-white rounded-8px flex-1 flex flex-col min-h-0 overflow-hidden relative pb-64px"> |
| | | <div class="w-full h-full relative content flex flex-col bg-[#F3F4F7]"> |
| | | <div class="p-12px main flex flex-col flex-1 overflow-hidden"> |
| | | <!-- 顶部步骤条 --> |
| | | <div class="pt-24px pb-24px px-60px border-b border-[#F0F2F5] flex justify-center"> |
| | | <div class="w-[600px]"> |
| | | <n-steps :current="currentStep" status="process"> |
| | | <n-step title="测试点位选择" /> |
| | | <n-step title="订单生成结果预览" /> |
| | | <n-step title="测试车辆选择" /> |
| | | </n-steps> |
| | | <div class="mb-24px shrink-0 flex justify-center mt-12px"> |
| | | <div class="custom-steps"> |
| | | <div |
| | | v-for="(stepTitle, index) in stepItems" |
| | | :key="stepTitle" |
| | | class="step-item" |
| | | > |
| | | <div class="step-node-wrap"> |
| | | <img |
| | | :src="currentStep > index + 1 ? finishIcon : currentStep === index + 1 ? waitIcon : activeIcon" |
| | | class="step-icon" |
| | | alt="step-icon" |
| | | > |
| | | <span |
| | | class="step-title" |
| | | :class="{ 'step-title--active': currentStep === index + 1 }" |
| | | > |
| | | {{ stepTitle }} |
| | | </span> |
| | | </div> |
| | | <div |
| | | v-if="index < stepItems.length - 1" |
| | | class="step-line" |
| | | :class="{ 'step-line--active': currentStep > index + 1 }" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 内容区: 滚动 --> |
| | | <div class="flex-1 overflow-auto p-24px flex flex-col min-h-0"> |
| | | <div class="box flex-1 overflow-auto p-24px flex flex-col min-h-0 bg-white rounded-8px"> |
| | | |
| | | <!-- ================= Step 1 ================= --> |
| | | <div v-show="currentStep === 1" class="flex flex-col h-full"> |
| | | <div v-show="currentStep === 1" class="flex-1 flex flex-col min-h-0"> |
| | | <!-- 表单 --> |
| | | <div class="flex items-center space-x-40px mb-24px bg-[#F8FAFC] p-16px rounded-8px"> |
| | | <div class="flex items-center"> |
| | | <span class="text-[#FF4D4F] mr-4px">*</span> |
| | | <span class="text-14px text-[#333] mr-12px whitespace-nowrap">计划名称</span> |
| | | <n-input v-model:value="planName" placeholder="请输入" class="w-[300px]" /> |
| | | </div> |
| | | <div class="flex items-center"> |
| | | <span class="text-[#FF4D4F] mr-4px">*</span> |
| | | <span class="text-14px text-[#333] mr-12px whitespace-nowrap">订单生成方式</span> |
| | | <n-radio-group v-model:value="orderGenMode" name="orderGenMode"> |
| | | <n-radio-button :value="1">全排列模式</n-radio-button> |
| | | <n-radio-button :value="2">串烧模式</n-radio-button> |
| | | </n-radio-group> |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <n-icon size="16" class="text-[#999] ml-8px cursor-pointer"><IconInfo /></n-icon> |
| | | </template> |
| | | 订单生成方式说明 |
| | | </n-tooltip> |
| | | </div> |
| | | </div> |
| | | <n-form label-placement="left" require-mark-placement="left" class="mb-12px shrink-0"> |
| | | <n-grid :cols="24" :x-gap="24"> |
| | | <n-form-item-gi :span="12" label="计划名称" required> |
| | | <n-input v-model:value="planName" placeholder="请输入" class="w-full" /> |
| | | </n-form-item-gi> |
| | | <n-form-item-gi :span="12" label="订单生成方式" required> |
| | | <div class="flex items-center"> |
| | | <n-radio-group v-model:value="orderGenMode" name="orderGenMode"> |
| | | <n-radio-button :value="1">全排列模式</n-radio-button> |
| | | <n-radio-button :value="2">串烧模式</n-radio-button> |
| | | </n-radio-group> |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <img |
| | | :src="noticeSvg" |
| | | class="ml-8px cursor-pointer" |
| | | style="width: 16px; height: 16px;" |
| | | alt="notice" |
| | | @click="orderGenModeDescModalVisible = true" |
| | | /> |
| | | </template> |
| | | 订单生成方式说明 |
| | | </n-tooltip> |
| | | </div> |
| | | </n-form-item-gi> |
| | | </n-grid> |
| | | </n-form> |
| | | |
| | | <!-- 列表操作 --> |
| | | <div class="flex justify-between items-center mb-16px"> |
| | | <n-space> |
| | | <n-button>一键清空</n-button> |
| | | <n-button>批量移除</n-button> |
| | | </n-space> |
| | | <n-button type="primary" secondary style="background-color: #E6F4FF; border-color: #7ABDFF; color: #0068FF;">选择点位</n-button> |
| | | <!-- 第一步表格操作区 --> |
| | | <div class="mb-12px shrink-0 flex gap-3 justify-between items-center"> |
| | | <div class="flex gap-2"> |
| | | <n-button @click="handleClearPoints">一键清空</n-button> |
| | | <n-button @click="handleBatchRemovePoints">批量移除</n-button> |
| | | </div> |
| | | <n-button type="primary" secondary @click="pointSelectModalVisible = true">选择点位</n-button> |
| | | </div> |
| | | |
| | | <!-- 表格 --> |
| | |
| | | v-model:checked-row-keys="step1CheckedRowKeys" |
| | | flex-height |
| | | :bordered="false" |
| | | :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="1000" |
| | | :page="1" |
| | | :page-size="10" |
| | | class="robotaxi-pagination mt-12px" |
| | | class="robotaxi-pagination mt-12px shrink-0" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ================= Step 2 ================= --> |
| | | <div v-show="currentStep === 2" class="flex flex-col h-full"> |
| | | <div v-show="currentStep === 2" class="flex-1 flex flex-col min-h-0"> |
| | | <!-- 列表操作 --> |
| | | <div class="flex justify-between items-center mb-16px"> |
| | | <n-space> |
| | | <div class="mb-12px shrink-0 flex gap-3 justify-between items-center"> |
| | | <div class="flex gap-2"> |
| | | <n-button>批量移除</n-button> |
| | | <n-button>一键删除失败订单</n-button> |
| | | </n-space> |
| | | </div> |
| | | <div class="flex items-center"> |
| | | <span class="text-14px text-[#333] mr-12px">车辆到达下车点是否停靠</span> |
| | | <n-switch v-model:value="isStopAtDropoff" /> |
| | |
| | | flex-height |
| | | :bordered="false" |
| | | :scroll-x="1200" |
| | | :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="1000" |
| | | :page="1" |
| | | :page-size="10" |
| | | class="robotaxi-pagination mt-12px" |
| | | class="robotaxi-pagination mt-12px shrink-0" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ================= Step 3 ================= --> |
| | | <div v-show="currentStep === 3" class="flex flex-col h-full"> |
| | | <div v-show="currentStep === 3" class="flex-1 flex flex-col min-h-0"> |
| | | <!-- 顶部分类 --> |
| | | <div class="mb-16px"> |
| | | <n-radio-group v-model:value="issueMode" name="issueMode" size="large"> |
| | | <div class="mb-16px shrink-0"> |
| | | <n-radio-group v-model:value="issueMode" name="issueMode" class="custom-radio-tabs"> |
| | | <n-radio-button :value="1"> |
| | | 按订单顺序下发 |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <n-icon size="14" class="text-[#999] ml-4px align-middle"><IconInfo /></n-icon> |
| | | </template> |
| | | 按订单顺序说明 |
| | | </n-tooltip> |
| | | <span class="flex items-center"> |
| | | 按订单顺序下发 |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <img :src="noticeSvg" class="ml-4px" style="width: 14.6px; height: 14.6px;" alt="notice" /> |
| | | </template> |
| | | 按订单顺序说明 |
| | | </n-tooltip> |
| | | </span> |
| | | </n-radio-button> |
| | | <n-radio-button :value="2"> |
| | | 指定车辆下发 |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <n-icon size="14" class="text-[#999] ml-4px align-middle"><IconInfo /></n-icon> |
| | | </template> |
| | | 指定车辆说明 |
| | | </n-tooltip> |
| | | <span class="flex items-center"> |
| | | 指定车辆下发 |
| | | <n-tooltip trigger="hover"> |
| | | <template #trigger> |
| | | <img :src="noticeSvg" class="ml-4px" style="width: 14.6px; height: 14.6px;" alt="notice" /> |
| | | </template> |
| | | 指定车辆说明 |
| | | </n-tooltip> |
| | | </span> |
| | | </n-radio-button> |
| | | </n-radio-group> |
| | | </div> |
| | | |
| | | <!-- 列表操作 --> |
| | | <div class="flex justify-between items-center mb-16px"> |
| | | <n-space> |
| | | <n-button>一键清空车辆</n-button> |
| | | <n-button>批量移除</n-button> |
| | | </n-space> |
| | | <n-button type="primary" secondary style="background-color: #E6F4FF; border-color: #7ABDFF; color: #0068FF;">选择测试车辆</n-button> |
| | | <!-- 第三步表格操作区 --> |
| | | <div class="mb-12px shrink-0 flex gap-3 justify-between items-center"> |
| | | <div class="flex gap-2"> |
| | | <n-button class="n-button--secondary" @click="handleClearVehicles">一键清空车辆</n-button> |
| | | <n-button class="n-button--secondary" @click="handleBatchRemoveVehicles">批量移除</n-button> |
| | | </div> |
| | | <n-button type="primary" secondary>选择测试车辆</n-button> |
| | | </div> |
| | | |
| | | <!-- 表格 --> |
| | |
| | | v-model:checked-row-keys="step3CheckedRowKeys" |
| | | flex-height |
| | | :bordered="false" |
| | | :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="1000" |
| | | :page="1" |
| | | :page-size="10" |
| | | class="robotaxi-pagination mt-12px" |
| | | class="robotaxi-pagination mt-12px shrink-0" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | <!-- 底部操作按钮 --> |
| | | <div class="absolute bottom-0 left-0 right-0 h-64px bg-white border-t border-[#F0F2F5] flex items-center justify-end px-24px z-10 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]"> |
| | | <n-space> |
| | | <n-button v-if="currentStep === 1" @click="handleCancel">取消</n-button> |
| | | <n-button v-if="currentStep > 1" @click="handlePrev">上一步</n-button> |
| | | <n-button v-if="currentStep < 3" type="primary" @click="handleNext">下一步</n-button> |
| | | <n-button v-if="currentStep === 3" type="primary" @click="handleSubmit">开始下发</n-button> |
| | | </n-space> |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | <!-- 底部操作栏 --> |
| | | <div class="footer flex justify-end items-center shrink-0"> |
| | | <NSpace> |
| | | <NButton v-if="currentStep === 1" class="w-84px" @click="handleCancel"> |
| | | 取消 |
| | | </NButton> |
| | | <NButton v-if="currentStep > 1" class="w-84px" @click="handlePrev"> |
| | | 上一步 |
| | | </NButton> |
| | | <NButton v-if="currentStep < 3" type="primary" class="w-84px" @click="handleNext"> |
| | | 下一步 |
| | | </NButton> |
| | | <NButton v-if="currentStep === 3" type="primary" class="w-84px" @click="handleSubmit"> |
| | | 开始下发 |
| | | </NButton> |
| | | </NSpace> |
| | | </div> |
| | | |
| | | <PointSelectModal |
| | | v-model:visible="pointSelectModalVisible" |
| | | @confirm="handlePointSelect" |
| | | /> |
| | | |
| | | <OrderGenModeDescModal |
| | | v-model:visible="orderGenModeDescModalVisible" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | :deep(.n-steps) { |
| | | --n-indicator-icon-size: 24px; |
| | | <style scoped lang="scss"> |
| | | .custom-steps { |
| | | width: 100%; |
| | | margin: 0 auto; |
| | | padding-top: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | :deep(.n-step-indicator) { |
| | | width: 28px !important; |
| | | height: 28px !important; |
| | | font-size: 14px !important; |
| | | |
| | | .step-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | </style> |
| | | |
| | | .step-node-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .step-icon { |
| | | width: 20px; |
| | | height: 20px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 14px; |
| | | line-height: 22px; |
| | | font-weight: 500; |
| | | color: rgba(23, 31, 38, 0.8); |
| | | } |
| | | |
| | | .step-title--active { |
| | | color: #0068FF; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 160px; |
| | | height: 2px; |
| | | margin: 0 8px; |
| | | background-color: rgba(23, 31, 38, 0.1); |
| | | } |
| | | |
| | | .step-line--active { |
| | | background-color: #0068FF; |
| | | } |
| | | |
| | | .footer { |
| | | position: sticky; |
| | | bottom: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 64px; |
| | | background-color: #fff; |
| | | box-shadow: 0px 0px 5px 0px rgba(23, 31, 38, 0.05); |
| | | padding: 16px 24px; |
| | | } |
| | | |
| | | .box { |
| | | padding: 24px; |
| | | background-color: #fff; |
| | | border-radius: 8px; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | --n-label-text-color: rgba(23, 31, 38, 0.8); |
| | | } |
| | | |
| | | .n-button--secondary { |
| | | box-sizing: border-box; |
| | | color: #0068FF; |
| | | border: 1px solid #7ABDFF; |
| | | background-color: #E6F4FF; |
| | | } |
| | | |
| | | :deep(.custom-radio-tabs) { |
| | | |
| | | .n-radio-button { |
| | | height: 32px !important; |
| | | line-height: 30px !important; |
| | | font-size: 13px !important; |
| | | padding: 0 16px !important; |
| | | } |
| | | |
| | | .n-radio-button__state-border { |
| | | border-radius: 4px; |
| | | } |
| | | .n-radio-button.n-radio-button--checked { |
| | | color: #0068FF !important; |
| | | } |
| | | .n-radio-button.n-radio-button--checked .n-radio-button__state-border { |
| | | border-color: #0068FF !important; |
| | | } |
| | | .n-radio-button .n-radio-button__radio-label { |
| | | padding: 7px 16px !important; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | </style> |