<script setup lang="tsx">
|
import type { DataTableColumns } from 'naive-ui'
|
import { computed, ref, watch } from 'vue'
|
import { NModal, NCard, NInput, NButton, NDataTable, NSelect, NTag } from 'naive-ui'
|
import Pagination from '@/components/common/Pagination.vue'
|
|
interface VehicleRow {
|
id: number
|
vehicleCode: string
|
plateNo: string
|
model: string
|
procurementSource: string
|
fleet: string
|
status: number
|
}
|
|
const props = withDefaults(defineProps<{
|
visible: boolean
|
}>(), {})
|
|
const emit = defineEmits<{
|
(e: 'update:visible', val: boolean): void
|
(e: 'confirm', data: VehicleRow[]): void
|
}>()
|
|
const show = computed({
|
get: () => props.visible,
|
set: (val) => emit('update:visible', val)
|
})
|
|
const model = ref({
|
plateNo: '',
|
vehicleCode: '',
|
status: null
|
})
|
|
const pageParams = ref({
|
pageNum: 1,
|
pageSize: 10
|
})
|
|
const listData = ref<VehicleRow[]>([])
|
const checkedRowKeys = ref<number[]>([])
|
const count = ref(0)
|
const loading = ref(false)
|
const allLoadedData = ref<Map<number, VehicleRow>>(new Map())
|
|
const isStatusSelectOpen = ref(false)
|
const statusOptions = [
|
{ label: '行程中', value: 1 },
|
{ label: '空闲', value: 2 },
|
{ label: '暂停服务', value: 3 },
|
]
|
|
function getStatusColor(status: number) {
|
switch (status) {
|
case 1: // 行程中
|
return { textColor: '#0068FF', color: '#E6F4FF' }
|
case 2: // 空闲
|
return { textColor: '#10B981', color: '#F6FFED' }
|
case 3: // 暂停服务
|
return { textColor: '#F59E0B', color: '#FFF7E6' }
|
default:
|
return { textColor: '#333333', color: '#F5F5F5' }
|
}
|
}
|
|
function getStatusLabel(status: number) {
|
return statusOptions.find(item => item.value === status)?.label || '-'
|
}
|
|
function getMockList() {
|
return [
|
{ id: 1, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 1 },
|
{ id: 2, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 2 },
|
{ id: 3, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 2 },
|
{ id: 4, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 3 },
|
{ id: 5, vehicleCode: '073 D 204 77', plateNo: '粤PXV7029', model: 'Robotaxi', procurementSource: '自产', fleet: '车队名称车队名称车队名称', status: 3 },
|
]
|
}
|
|
function getList() {
|
loading.value = true
|
const sourceList = getMockList()
|
const plateNo = model.value.plateNo.trim()
|
const vehicleCode = model.value.vehicleCode.trim()
|
const status = model.value.status
|
const filteredList = sourceList.filter((item) => {
|
const matchPlateNo = !plateNo || item.plateNo.includes(plateNo)
|
const matchVehicleCode = !vehicleCode || item.vehicleCode.includes(vehicleCode)
|
const matchStatus = !status || item.status === status
|
return matchPlateNo && matchVehicleCode && matchStatus
|
})
|
|
count.value = 1000 // mock pagination total
|
|
const start = (pageParams.value.pageNum - 1) * pageParams.value.pageSize
|
const end = start + pageParams.value.pageSize
|
// mock real slice if we had real data
|
listData.value = filteredList
|
listData.value.forEach((item) => {
|
allLoadedData.value.set(item.id, item)
|
})
|
loading.value = false
|
}
|
|
watch(
|
() => props.visible,
|
(val) => {
|
if (val) {
|
model.value = { plateNo: '', vehicleCode: '', status: null }
|
pageParams.value = { pageNum: 1, pageSize: 10 }
|
allLoadedData.value.clear()
|
checkedRowKeys.value = []
|
getList()
|
}
|
else {
|
checkedRowKeys.value = []
|
allLoadedData.value.clear()
|
}
|
}
|
)
|
|
function handleSearch() {
|
pageParams.value.pageNum = 1
|
getList()
|
}
|
|
function handleReset() {
|
model.value = { plateNo: '', vehicleCode: '', status: null }
|
pageParams.value.pageNum = 1
|
getList()
|
}
|
|
function changePage(page: number, size: number) {
|
pageParams.value.pageNum = size !== pageParams.value.pageSize ? 1 : page
|
pageParams.value.pageSize = size
|
getList()
|
}
|
|
function handleCancel() {
|
show.value = false
|
}
|
|
function handleConfirm() {
|
const selectedRows = checkedRowKeys.value
|
.map(id => allLoadedData.value.get(id))
|
.filter((item): item is VehicleRow => Boolean(item))
|
emit('confirm', selectedRows)
|
show.value = false
|
}
|
|
const columns = computed<DataTableColumns<VehicleRow>>(() => [
|
{ type: 'selection', width: 60, align: 'center' },
|
{
|
title: '序号',
|
key: 'index',
|
width: 80,
|
render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1
|
},
|
{ title: '车辆编号', key: 'vehicleCode', width: 160 },
|
{ title: '车牌号', key: 'plateNo', width: 160 },
|
{ title: '型号', key: 'model', width: 160 },
|
{ title: '采购来源', key: 'procurementSource', width: 160 },
|
{ title: '当前车队', key: 'fleet', flex: 1, ellipsis: true },
|
{
|
title: '状态',
|
key: 'status',
|
width: 120,
|
render: (row) => {
|
return (
|
<NTag
|
color={getStatusColor(row.status)}
|
bordered={false}
|
size="small"
|
style="border-radius: 4px;"
|
>
|
{getStatusLabel(row.status)}
|
</NTag>
|
)
|
}
|
}
|
])
|
</script>
|
|
<template>
|
<n-modal v-model:show="show">
|
<n-card
|
style="
|
width: 1000px;
|
height: 80vh;
|
max-height: 800px;
|
border-radius: 8px;
|
display: flex;
|
flex-direction: column;
|
"
|
:bordered="false"
|
:segmented="{ content: true }"
|
content-style="padding: 16px 24px; flex: 1; display: flex; flex-direction: column; overflow: hidden;"
|
footer-style="padding: 16px 24px; border-top: 1px solid rgba(46, 61, 77, 0.1); flex-shrink: 0;"
|
>
|
<template #header>
|
<span style="font-size: 16px; font-weight: bold">选择分派车辆</span>
|
</template>
|
<template #header-extra>
|
<div class="cursor-pointer" text @click="handleCancel">
|
<span style="font-size: 18px; width: 10px; height: 10px">×</span>
|
</div>
|
</template>
|
|
<div class="flex flex-col h-full overflow-hidden">
|
<div class="mb-3 flex flex-wrap items-end gap-x-24px gap-y-16px" style="flex-shrink: 0">
|
<div class="w-300px" style="width: 300px;">
|
<div class="mb-2 text-sm text-[#333]">
|
车牌号
|
</div>
|
<n-input
|
v-model:value="model.plateNo"
|
placeholder="请输入"
|
clearable
|
/>
|
</div>
|
<div class="w-300px" style="width: 300px;">
|
<div class="mb-2 text-sm text-[#333]">
|
车辆编号
|
</div>
|
<n-input
|
v-model:value="model.vehicleCode"
|
placeholder="请输入"
|
clearable
|
/>
|
</div>
|
<div class="flex ml-auto gap-8px h-32px items-center" style="gap: 8px; margin-left: auto">
|
<n-button style="width: 84px; height: 32px" @click="handleReset">
|
重置
|
</n-button>
|
<n-button
|
type="primary"
|
style="width: 84px; height: 32px"
|
@click="handleSearch"
|
>
|
查询
|
</n-button>
|
</div>
|
<div class="w-300px" style="width: 300px;">
|
<div class="mb-2 text-sm text-[#333]">
|
车辆状态
|
</div>
|
<n-select
|
v-model:value="model.status"
|
:options="statusOptions"
|
placeholder="请选择"
|
clearable
|
@update:show="(val: boolean) => isStatusSelectOpen = val"
|
>
|
<template #arrow>
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
:class="['arrow-icon', isStatusSelectOpen ? 'arrow-icon--open' : '']"
|
style="width: 16px; height: 16px"
|
alt="arrow"
|
/>
|
</template>
|
<template #empty>
|
<div class="flex-center">
|
<img
|
src="/src/assets/element-img/no-data.png"
|
style="width: 50%"
|
alt="no-data"
|
/>
|
</div>
|
</template>
|
</n-select>
|
</div>
|
</div>
|
|
<div class="flex-1 overflow-hidden pt-12px" style="min-height: 0">
|
<n-data-table
|
v-model:checked-row-keys="checkedRowKeys"
|
style="height: 100%"
|
flex-height
|
:columns="columns"
|
:data="listData"
|
:loading="loading"
|
:bordered="false"
|
:row-key="(row) => row.id"
|
:pagination="false"
|
>
|
<template #empty>
|
<div class="flex-center">
|
<img
|
src="/src/assets/element-img/no-data.png"
|
style="width: 123px; height: 125px"
|
alt="no-data"
|
>
|
</div>
|
</template>
|
</n-data-table>
|
</div>
|
|
<Pagination
|
:count="count"
|
:page="pageParams.pageNum"
|
:page-size="pageParams.pageSize"
|
style="margin-top: 12px; flex-shrink: 0"
|
@change="changePage"
|
/>
|
</div>
|
|
<template #footer>
|
<div class="flex justify-end gap-3" style="gap: 12px;">
|
<n-button class="footer-btn" @click="handleCancel">取消</n-button>
|
<n-button type="primary" class="footer-btn" @click="handleConfirm">确认下发</n-button>
|
</div>
|
</template>
|
</n-card>
|
</n-modal>
|
</template>
|
|
<style scoped lang="scss">
|
.mb-3 {
|
margin-bottom: 0.75rem;
|
}
|
|
.mb-2 {
|
margin-bottom: 0.5rem;
|
}
|
|
.flex {
|
display: flex;
|
}
|
|
.flex-wrap {
|
flex-wrap: wrap;
|
}
|
|
.items-end {
|
align-items: flex-end;
|
}
|
|
.items-center {
|
align-items: center;
|
}
|
|
.gap-24px {
|
gap: 24px;
|
}
|
|
.gap-x-24px {
|
column-gap: 24px;
|
}
|
|
.gap-y-16px {
|
row-gap: 16px;
|
}
|
|
.flex-1 {
|
flex: 1 1 0%;
|
}
|
|
.justify-end {
|
justify-content: flex-end;
|
}
|
|
.text-sm {
|
font-size: 0.875rem;
|
line-height: 1.25rem;
|
}
|
|
.footer-btn {
|
padding: 7px 29px;
|
height: auto;
|
font-size: 13px;
|
line-height: 18px;
|
}
|
|
.arrow-icon {
|
transition: transform 0.3s;
|
}
|
.arrow-icon--open {
|
transform: rotate(180deg);
|
}
|
|
.cursor-pointer {
|
cursor: pointer;
|
}
|
</style>
|