| | |
| | | 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', |
| | |
| | | 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}`) |
| | | } |
| | |
| | | } |
| | | |
| | | // 获取可用车辆列表 |
| | | 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}`) |
| | | } |
| | | |
| | | /** 订单评价 */ |
| | |
| | | |
| | | .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; |
| | | } |
| | | } |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | display: flex; |
| | | align-items: center; |
| | | padding-left: 12px; |
| | |
| | | type="primary" |
| | | class="edit" |
| | | secondary |
| | | size="small" |
| | | style="width: 58px;border-radius: 4px;" |
| | | @click="handleAddVehicle" |
| | | > |
| | | 新增 |
| | |
| | | type="primary" |
| | | class="edit" |
| | | secondary |
| | | size="small" |
| | | style="padding: 7px 16px;" |
| | | @click="handleAddStaff" |
| | | > |
| | | 新增 |
| | |
| | | min-height: 100%; |
| | | |
| | | .edit { |
| | | border: 1px solid #7abdff; |
| | | border: 1px solid #7ABDFF; |
| | | background-color: #E6F4FF; |
| | | color: #0068FF; |
| | | } |
| | | |
| | | // 车队信息卡片 |
| | |
| | | |
| | | .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; |
| | | } |
| | |
| | | <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 |
| | |
| | | { 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: '操作', |
| | |
| | | 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') : '-' }, |
| | |
| | | 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 }, |
| | |
| | | 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 || '-' }, |
| | |
| | | class="collapse-icon" |
| | | :class="{ 'is-collapsed': !isVehicleInfoExpanded }" |
| | | @click="toggleVehicleInfo" |
| | | > |
| | | /> |
| | | <span class="header-title">车辆信息</span> |
| | | </div> |
| | | <div |
| | |
| | | > |
| | | 编辑 |
| | | </div> |
| | | <div |
| | | <!-- <div |
| | | v-else |
| | | type="primary" |
| | | class="edit" |
| | |
| | | @click="handleBack" |
| | | > |
| | | 返回 |
| | | </div> |
| | | </div> --> |
| | | </div> |
| | | |
| | | <!-- 车辆信息内容 --> |
| | |
| | | <img |
| | | :src="vehicleDetail.vehicleImage" |
| | | alt="车辆图片" |
| | | @error="e => { e.target.src = defaultImage }" |
| | | > |
| | | @error=" |
| | | (e) => { |
| | | e.target.src = defaultImage; |
| | | } |
| | | " |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 基础信息 --> |
| | |
| | | class="status-chip" |
| | | :class="vehicleTag.cls" |
| | | @click="handleStatusClick" |
| | | >{{ vehicleTag.text }}</span> |
| | | >{{ vehicleTag.text }}</span |
| | | > |
| | | <span class="status-chip" :class="operatingTag.cls">{{ |
| | | operatingTag.text |
| | | }}</span> |
| | |
| | | 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> |
| | |
| | | <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 |
| | |
| | | <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"> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | :class="{ 'arrow-icon--open': isTaskTypeOpen }" |
| | | style="width: 16px; height: 16px" |
| | | alt="arrow" |
| | | > |
| | | /> |
| | | </template> |
| | | </NSelect> |
| | | </NFormItem> |
| | |
| | | <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> |
| | |
| | | :class="{ 'arrow-icon--open': isTaskErrorIdOpen }" |
| | | style="width: 16px; height: 16px" |
| | | alt="arrow" |
| | | > |
| | | /> |
| | | </template> |
| | | </NCascader> |
| | | </NFormItem> |
| | |
| | | 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> |
| | |
| | | > |
| | | <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> |
| | |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | |
| | |
| | | font-size: 14px; |
| | | .plate-number { |
| | | font-size: 20px; |
| | | font-weight: 500; |
| | | color: #171f26; |
| | | } |
| | | .tag-info { |
| | |
| | | } |
| | | } |
| | | .info-list { |
| | | border-radius: 6px !important; |
| | | :deep(.n-card__content) { |
| | | padding: 0 0 12px 0 !important; |
| | | } |
| | |
| | | :deep(.n-tabs) { |
| | | .n-tabs-nav { |
| | | background: #fff; |
| | | border-radius: 6px; |
| | | // padding: 0 16px; |
| | | .n-tabs-tab-wrapper { |
| | | padding-left: 16px; |
| | |
| | | <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 |
| | |
| | | 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({ |
| | |
| | | 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) |
| | |
| | | |
| | | 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) |
| | | } |
| | | }) |
| | | } |
| | |
| | | allSelectedMap.value.clear() |
| | | if (props.existingPoints) { |
| | | props.existingPoints.forEach((p) => { |
| | | allSelectedMap.value.set(p.id, p) |
| | | allSelectedMap.value.set(String(p.id), p) |
| | | }) |
| | | } |
| | | |
| | |
| | | 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 { |
| | |
| | | |
| | | 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) |
| | |
| | | }) |
| | | |
| | | 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('') |
| | | |
| | |
| | | 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), |
| | | }) |
| | |
| | | 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) |
| | | } |
| | |
| | | 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: '', |
| | |
| | | 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: '', |
| | |
| | | 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: '', |
| | |
| | | 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: '订单编号', |
| | |
| | | }, |
| | | { |
| | | 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', |
| | | }, |
| | |
| | | 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> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | |
| | | 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, |
| | |
| | | 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('请输入计划名称') |
| | |
| | | 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() |
| | |
| | | } |
| | | |
| | | 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> |
| | |
| | | <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> |
| | | |
| | |
| | | style="width: 400px" |
| | | maxlength="30" |
| | | show-count |
| | | :disabled="isReadOnly" |
| | | /> |
| | | </div> |
| | | </n-card> |
| | |
| | | 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;"> |
| | |
| | | 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 |
| | |
| | | <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" |
| | |
| | | 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> |
| | |
| | | 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> |
| | | |
| | |
| | | :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 |
| | |
| | | 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" |
| | |
| | | </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" |
| | |
| | | title="生成订单" |
| | | preset="card" |
| | | size="small" |
| | | :bordered="false" |
| | | style="width: 400px" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }" |
| | | :mask-closable="false" |
| | | > |
| | | <div class="p-4"> |
| | |
| | | </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> |
| | | |
| | |
| | | |
| | | 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" |
| | | } |
| | | |
| | | |
| | | 订单相关: |
| | |
| | | }, |
| | | "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 |
| | | |
| | | |
| | | |
| | | |
| | |
| | | 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() |
| | |
| | | |
| | | 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 = { |
| | |
| | | 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 } |
| | |
| | | startLoading() |
| | | try { |
| | | const planName = model.value.planName?.trim() |
| | | const res = await fetchCruisePlanPageList({ |
| | | const res = await fetchTestPlanPageList({ |
| | | ...pageParams.value, |
| | | planName: planName || undefined, |
| | | }) |
| | |
| | | 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: '', |
| | |
| | | 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() |
| | |
| | | }) |
| | | } |
| | | |
| | | const columns = computed<DataTableColumns<CruisePlanRow>>(() => [ |
| | | const columns = computed<DataTableColumns<TestPlanRow>>(() => [ |
| | | { |
| | | title: '计划ID', |
| | | key: 'id', |
| | |
| | | ellipsis: true, |
| | | }, |
| | | { |
| | | title: '关联订单数', |
| | | key: 'orderNum', |
| | | width: 140, |
| | | align: 'left', |
| | | resizable: true, |
| | | ellipsis: true, |
| | | }, |
| | | { |
| | | title: '关联车辆数', |
| | | key: 'vehicleNum', |
| | | width: 140, |
| | |
| | | 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)}> |
| | |
| | | :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" |
| | |
| | | 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, |
| | |
| | | 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`, |
| | |
| | | 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 || {} |
| | |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | align: 'center', |
| | | align: 'left', |
| | | width: 200, |
| | | fixed: 'right', |
| | | render: row => ( |