<script setup lang="tsx">
|
import noDataImg from '@/assets/element-img/no-data.png'
|
import type { DataTableColumns, DataTableRowKey } from 'naive-ui'
|
import { getPointStatusText, getStatusClass } from '@/views/testDemo/operationWorkbench/status'
|
import { computed, ref, watch } from 'vue'
|
import { NButton, NDataTable, NInput, NModal, NSelect } from 'naive-ui'
|
import { fetchPointPageList } from '@/service/api/testOperationWorkbench'
|
import { getMultiSelectRowClassName, shouldIgnoreTableRowClick, toggleMultiSelectRowKey } from '@/utils/table'
|
|
const props = withDefaults(defineProps<{
|
visible: boolean
|
existingPoints?: PointRow[]
|
}>(), {
|
existingPoints: () => [],
|
})
|
|
const emit = defineEmits<{
|
(e: 'update:visible', val: boolean): void
|
(e: 'confirm', data: any[]): void
|
}>()
|
|
const show = computed({
|
get: () => props.visible,
|
set: val => emit('update:visible', val),
|
})
|
|
interface PointRow {
|
id: string
|
pointName: string
|
address: string
|
pointType: number // 1: 运营, 2: 测试
|
status: string
|
}
|
|
const model = ref({
|
pointName: '',
|
pointType: null as number | null,
|
})
|
|
const pageParams = ref({
|
pageNum: 1,
|
pageSize: 10,
|
})
|
|
const pointTypeOptions = [
|
{ label: '运营', value: 1 },
|
{ label: '测试', value: 2 },
|
]
|
|
const listData = ref<PointRow[]>([])
|
const checkedRowKeys = ref<DataTableRowKey[]>([])
|
const count = ref(0)
|
const loading = ref(false)
|
const allLoadedData = ref<Map<DataTableRowKey, PointRow>>(new Map())
|
const saveDisabled = computed(() => checkedRowKeys.value.length === 0)
|
|
function isPointDisabled(row?: Partial<PointRow> | null) {
|
return String(row?.status || '').trim() === '已停用'
|
}
|
|
async function getList() {
|
loading.value = true
|
try {
|
const res = await fetchPointPageList({
|
pointName: model.value.pointName.trim() || undefined,
|
pointType: model.value.pointType || undefined,
|
pageNum: pageParams.value.pageNum,
|
pageSize: pageParams.value.pageSize,
|
})
|
|
if (res.code !== 200 && res.code !== 0) {
|
throw new Error(res.msg || '获取点位列表失败')
|
}
|
|
const records = Array.isArray(res.data?.records) ? res.data.records : []
|
listData.value = records.map((item: any) => ({
|
id: String(item.id),
|
pointName: item.pointName || '-',
|
address: item.pointAddress || '-',
|
pointType: Number(item.pointType ?? 0),
|
status: getPointStatusText(item.operationStatus ?? item.operationStatus ?? 0, item.verificationStatus),
|
}))
|
const disabledPointIdSet = new Set(
|
listData.value.filter(item => isPointDisabled(item)).map(item => item.id),
|
)
|
checkedRowKeys.value = checkedRowKeys.value.filter(id => !disabledPointIdSet.has(id))
|
listData.value.forEach((item) => {
|
allLoadedData.value.set(item.id, item)
|
})
|
count.value = Number(res.data?.total ?? records.length ?? 0)
|
}
|
catch (error: any) {
|
console.error(error)
|
window.$message?.error('点位列表加载失败,请刷新页面')
|
listData.value = []
|
count.value = 0
|
}
|
finally {
|
loading.value = false
|
}
|
}
|
|
watch(
|
() => props.visible,
|
(val) => {
|
if (val) {
|
checkedRowKeys.value = props.existingPoints.map(item => item.id)
|
model.value = { pointName: '', pointType: null }
|
pageParams.value = { pageNum: 1, pageSize: 10 }
|
allLoadedData.value.clear()
|
props.existingPoints.forEach((item) => {
|
allLoadedData.value.set(item.id, item)
|
})
|
getList()
|
}
|
else {
|
checkedRowKeys.value = []
|
allLoadedData.value.clear()
|
}
|
},
|
)
|
|
function handleSearch() {
|
pageParams.value.pageNum = 1
|
getList()
|
}
|
|
function handleReset() {
|
model.value = { pointName: '', pointType: null }
|
pageParams.value.pageNum = 1
|
getList()
|
}
|
|
function changePage(page: number, size: number) {
|
pageParams.value.pageNum = page
|
pageParams.value.pageSize = size
|
getList()
|
}
|
|
function handleCancel() {
|
show.value = false
|
}
|
|
function handleConfirm() {
|
if (saveDisabled.value) {
|
window.$message?.warning('请选择至少一个测试点位')
|
return
|
}
|
const selectedRows = checkedRowKeys.value
|
.map(id => allLoadedData.value.get(id))
|
.filter((item): item is PointRow => Boolean(item))
|
emit('confirm', selectedRows)
|
show.value = false
|
}
|
|
function getRowClassName(row: PointRow) {
|
return [
|
getMultiSelectRowClassName(row.id, checkedRowKeys.value),
|
isPointDisabled(row) ? 'point-row--disabled' : '',
|
].filter(Boolean).join(' ')
|
}
|
|
function handleRowClick(row: PointRow) {
|
if (isPointDisabled(row))
|
return
|
|
checkedRowKeys.value = toggleMultiSelectRowKey(checkedRowKeys.value, row.id)
|
}
|
|
function getRowProps(row: PointRow) {
|
return {
|
style: isPointDisabled(row) ? '' : 'cursor: pointer;',
|
onClick: (event: MouseEvent) => {
|
if (shouldIgnoreTableRowClick(event.target))
|
return
|
handleRowClick(row)
|
},
|
}
|
}
|
|
const columns = computed<DataTableColumns<PointRow>>(() => [
|
{
|
type: 'selection',
|
width: 60,
|
align: 'center',
|
disabled: row => isPointDisabled(row),
|
},
|
{ title: '序号', key: 'index', width: 80, render: (_, index) => (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 },
|
{ title: '点位名称', key: 'pointName', width: 250 },
|
{ title: '详细地址', key: 'address', flex: 1 },
|
{ title: '点位类型', key: 'pointType', width: 120, render: row => row.pointType === 2 ? '测试' : '运营' },
|
{
|
title: '状态',
|
key: 'status',
|
width: 100,
|
render: (row) => {
|
return (
|
<div class={`point-status-tag ${getStatusClass(row.status)}`}>
|
{row.status}
|
</div>
|
)
|
},
|
},
|
])
|
</script>
|
|
<template>
|
<NModal 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="modal-close-trigger" text @click="handleCancel">
|
<span class="modal-close-text">×</span>
|
</div>
|
</template>
|
|
<div class="flex flex-col h-full overflow-hidden">
|
<!-- 搜索区域 -->
|
<div class="mb-3 flex items-end gap-24px" style="flex-shrink: 0">
|
<div class="w-300px" style="width: 300px;">
|
<div class="mb-2 text-sm text-[#333]">
|
点位名称
|
</div>
|
<NInput
|
v-model:value="model.pointName"
|
placeholder="请输入"
|
clearable
|
/>
|
</div>
|
<div class="w-300px" style="width: 300px;">
|
<div class="mb-2 text-sm text-[#333]">
|
点位类型
|
</div>
|
<NSelect
|
v-model:value="model.pointType"
|
:options="pointTypeOptions"
|
placeholder="请选择"
|
clearable
|
>
|
<template #empty>
|
<div class="flex-center">
|
<img :src="noDataImg" style="width: 50%" alt="no-data">
|
</div>
|
</template>
|
</NSelect>
|
</div>
|
<div class="flex ml-auto gap-8px" style="gap: 8px;">
|
<NButton style="width: 84px; height: 32px" @click="handleReset">
|
重置
|
</NButton>
|
<NButton
|
type="primary"
|
style="width: 84px; height: 32px"
|
@click="handleSearch"
|
>
|
查询
|
</NButton>
|
</div>
|
</div>
|
|
<!-- 表格区域 -->
|
<div class="flex-1 overflow-hidden pt-12px" style="min-height: 0">
|
<NDataTable
|
v-model:checked-row-keys="checkedRowKeys"
|
style="height: 100%"
|
flex-height
|
:columns="columns"
|
:data="listData"
|
:loading="loading"
|
:bordered="false"
|
:row-class-name="getRowClassName"
|
:row-key="(row) => row.id"
|
:row-props="getRowProps"
|
:pagination="false"
|
>
|
<template #empty>
|
<div class="flex-center">
|
<img
|
:src="noDataImg"
|
style="width: 123px; height: 125px"
|
alt="no-data"
|
>
|
</div>
|
</template>
|
</NDataTable>
|
</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;">
|
<NButton class="footer-btn" @click="handleCancel">
|
取消
|
</NButton>
|
<NButton type="primary" class="footer-btn" :disabled="saveDisabled" @click="handleConfirm">
|
保存
|
</NButton>
|
</div>
|
</template>
|
</n-card>
|
</NModal>
|
</template>
|
|
<style scoped lang="scss">
|
.mb-3 {
|
margin-bottom: 0.75rem;
|
}
|
.mb-2 {
|
margin-bottom: 0.5rem;
|
}
|
.flex {
|
display: flex;
|
}
|
.items-end {
|
align-items: flex-end;
|
}
|
.gap-24px {
|
gap: 24px;
|
}
|
.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;
|
}
|
|
:deep(.robotaxi-table-row--selected td) {
|
background-color: rgba(24, 160, 88, 0.06);
|
}
|
|
:deep(.point-row--disabled td) {
|
color: rgba(23, 31, 38, 0.4);
|
}
|
|
:deep(.point-status-tag) {
|
display: inline-block;
|
padding: 2px 8px;
|
border-radius: 4px;
|
font-size: 12px;
|
line-height: 16px;
|
text-align: center;
|
|
&.wait-test, &.test-take-effect {
|
background: #FFF6EA;
|
color: #FFAA2A;
|
}
|
&.testing, &.test-pass {
|
background: #E6F4FF;
|
color: #0068FF;
|
}
|
&.test-starting {
|
background: #E9F9ED;
|
color: #19B144;
|
}
|
&.test-fail {
|
background: #FFF3F1;
|
color: #E02716;
|
}
|
&.test-stop, &.test-invalid {
|
background: #E8E9E9;
|
color: #171F26;
|
}
|
}
|
</style>
|