测试计划选择点位弹窗优化//去掉测试运营工作台的状态枚举-待生效,可运营//加围栏的交互-hover显示围栏类型,加层级判断:测试>运营 类型相同 更新时间最近优先
9个文件已修改
543 ■■■■■ 已修改文件
src/views/businessOperations/operationWorkbench/list.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.scss 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/workbench.constants.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/list.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.scss 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/workbench.constants.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/components/PointSelectModal.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/list.vue
@@ -67,8 +67,8 @@
]
const fenceStatusOptions = [
  { label: '草稿', value: 0 },
  { label: '待生效', value: 1 },
  // { label: '草稿', value: 0 },
  // { label: '待生效', value: 1 },
  { label: '启用中', value: 2 },
  { label: '已停用', value: 3 },
]
src/views/businessOperations/operationWorkbench/map.scss
@@ -709,6 +709,52 @@
  color: #e02716;
}
.fence-hover-tooltip {
  position: absolute;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 88px;
  max-width: 160px;
  padding: 7px 10px;
  border-radius: 6px;
  color: #fff;
  font-size: 13px;
  line-height: 18px;
  white-space: nowrap;
  pointer-events: none;
  transform: translate(-50%, -100%);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
  &.is-enabled {
    background: #19b144;
  }
  &.is-disabled {
    background: #8f959e;
  }
  &.is-testing {
    background: #171f26;
  }
}
.fence-hover-tooltip__text {
  overflow: hidden;
  text-overflow: ellipsis;
}
.fence-hover-tooltip__arrow {
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 10px;
  height: 10px;
  background: inherit;
  transform: translateX(-50%) rotate(45deg);
}
.map-tips {
  position: absolute;
  top: 46px;
src/views/businessOperations/operationWorkbench/map.vue
@@ -1412,7 +1412,17 @@
const stationPolygons = shallowRef<any[]>([])
const fencePolygons = shallowRef<any[]>([])
const currentCityBoundaryPaths = ref<LngLatTuple[][]>([])
const fenceHoverTooltipVisible = ref(false)
const fenceHoverTooltipText = ref('')
const fenceHoverTooltipType = ref<'enabled' | 'disabled' | 'testing'>('testing')
const fenceCreateOutsideCurrentCity = ref(false)
const fenceHoverTooltipPosition = reactive({
  x: 0,
  y: 0,
})
let fenceHoverTooltipTimer: ReturnType<typeof setTimeout> | null = null
let pendingFenceHoverKey = ''
let pendingFenceHoverPixel: { x: number, y: number } | null = null
// 围栏/场站点选后会有“立即聚焦 + 异步详情回来的二次聚焦”两条链路。
// 这里记录同一轮选中的自动聚焦请求,避免重复 fitView 抢走用户正在手动调整的视野。
const lastMapWheelViewportInteractionAt = ref(0)
@@ -1993,9 +2003,105 @@
function handleCityBoundaryMouseOut() {
  fenceCreateOutsideCurrentCity.value = false
  hideFenceHoverTooltip()
  resetFenceEditGuideHoverState()
  resetRestrictedEditGuideHoverState()
  resetStationEditGuideHoverState()
}
function clearFenceHoverTooltipTimer() {
  if (fenceHoverTooltipTimer) {
    clearTimeout(fenceHoverTooltipTimer)
    fenceHoverTooltipTimer = null
  }
}
function hideFenceHoverTooltip() {
  clearFenceHoverTooltipTimer()
  pendingFenceHoverKey = ''
  pendingFenceHoverPixel = null
  fenceHoverTooltipVisible.value = false
  fenceHoverTooltipText.value = ''
  fenceHoverTooltipType.value = 'testing'
}
function getFenceHoverKey(item: any) {
  return `${String(item?.electronicId || '')}-${String(item?.id || '')}`
}
function resolveFenceOverlayType(item: any) {
  if (Number(item?.areaStatus || 1) === 2 || Number(item?.status) === 3)
    return 'disabled' as const
  if (Number(item?.status) === 2 || enabledFenceIdSet.value.has(String(item?.electronicId || '')))
    return 'enabled' as const
  return 'testing' as const
}
function getFenceHoverTooltipText(type: 'enabled' | 'disabled' | 'testing') {
  if (type === 'enabled')
    return '启用中围栏'
  if (type === 'disabled')
    return '已停用围栏'
  return '测试围栏'
}
function parseFencePriorityTime(value: unknown) {
  const timestamp = dayjs(String(value || '')).valueOf()
  return Number.isFinite(timestamp) ? timestamp : 0
}
function getFenceOverlayPriority(item: any) {
  const type = resolveFenceOverlayType(item)
  return {
    type,
    typePriority: type === 'testing' ? 2 : type === 'enabled' ? 1 : 0,
    updateTimePriority: parseFencePriorityTime(item?.updateTime),
    fallbackPriority: Number(item?.electronicId ?? item?.id ?? 0) || 0,
  }
}
function updateFenceHoverTooltipPositionByPixel(pixel?: { x: number, y: number } | null) {
  if (!map.value || !pixel)
    return false
  const container = map.value.getContainer?.()
  if (!container)
    return false
  const offsetX = 12
  const offsetY = 18
  const rawX = Number(pixel.x) + offsetX
  const rawY = Number(pixel.y) - offsetY
  const clampedX = Math.max(12, Math.min(rawX, container.clientWidth - 12))
  const clampedY = Math.max(12, Math.min(rawY, container.clientHeight - 12))
  fenceHoverTooltipPosition.x = clampedX
  fenceHoverTooltipPosition.y = clampedY
  return true
}
function scheduleFenceHoverTooltip(item: any, pixel?: { x: number, y: number } | null) {
  if (Number(item?.areaType) !== 1 || isFenceFormMode.value || isStationFormMode.value)
    return
  const hoverKey = getFenceHoverKey(item)
  pendingFenceHoverKey = hoverKey
  pendingFenceHoverPixel = pixel
  updateFenceHoverTooltipPositionByPixel(pixel)
  clearFenceHoverTooltipTimer()
  fenceHoverTooltipTimer = window.setTimeout(() => {
    if (pendingFenceHoverKey !== hoverKey)
      return
    const nextType = resolveFenceOverlayType(item)
    if (!updateFenceHoverTooltipPositionByPixel(pendingFenceHoverPixel))
      return
    fenceHoverTooltipType.value = nextType
    fenceHoverTooltipText.value = getFenceHoverTooltipText(nextType)
    fenceHoverTooltipVisible.value = true
    fenceHoverTooltipTimer = null
  }, 300)
}
function handleNativeMouseMove(event: MouseEvent) {
@@ -8839,6 +8945,7 @@
function clearMapEntities() {
  if (!map.value)
    return
  hideFenceHoverTooltip()
  if (pointMarkers.value.length)
    map.value.remove(pointMarkers.value)
  if (cityBoundaryPolygons.value.length)
@@ -8943,6 +9050,22 @@
        }
        return false
      })
      .sort((prev: any, next: any) => {
        const prevPriority = getFenceOverlayPriority(prev)
        const nextPriority = getFenceOverlayPriority(next)
        if (prevPriority.typePriority !== nextPriority.typePriority)
          return prevPriority.typePriority - nextPriority.typePriority
        if (
          prevPriority.type === 'testing'
          && nextPriority.type === 'testing'
          && prevPriority.updateTimePriority !== nextPriority.updateTimePriority
        ) {
          return prevPriority.updateTimePriority - nextPriority.updateTimePriority
        }
        if (prevPriority.updateTimePriority !== nextPriority.updateTimePriority)
          return prevPriority.updateTimePriority - nextPriority.updateTimePriority
        return prevPriority.fallbackPriority - nextPriority.fallbackPriority
      })
      .map((item) => {
        const areaType = Number(item.areaType)
        const isDisabledFenceArea = Number(item.areaStatus || 1) === 2
@@ -8980,6 +9103,7 @@
        // 这里仅保留“停用禁行区不可点”,不再沿用旧逻辑把停用运营区一并禁点。
        const shouldBlockAreaClick
          = areaType === 2 && isDisabledFenceArea
        const fencePriority = getFenceOverlayPriority(item)
        const polygon = new (window as any).AMap.Polygon({
          path: points,
          fillColor:
@@ -9043,9 +9167,12 @@
            && !isPointMapPickActive.value
            && !isFenceFormMode.value
            && !isStationFormMode.value,
          zIndex: isReadonlyRestrictedActive ? 230 : isActive ? 220 : 205,
          zIndex: areaType === 2
            ? (isReadonlyRestrictedActive ? 230 : 205)
            : 240 + fencePriority.typePriority * 40 + (isActive ? 8 : 0),
        })
        polygon.on('click', () => {
          hideFenceHoverTooltip()
          if (
            shouldBlockAreaClick
            || isPointMapPickActive.value
@@ -9083,6 +9210,18 @@
            return
          }
          handleMapEntityClick('fence', currentFenceId)
        })
        polygon.on('mouseover', (event: any) => {
          scheduleFenceHoverTooltip(item, event?.pixel || null)
        })
        polygon.on('mousemove', (event: any) => {
          pendingFenceHoverPixel = event?.pixel || null
          updateFenceHoverTooltipPositionByPixel(pendingFenceHoverPixel)
        })
        polygon.on('mouseout', () => {
          if (pendingFenceHoverKey === getFenceHoverKey(item))
            pendingFenceHoverKey = ''
          hideFenceHoverTooltip()
        })
        polygon.setExtData({
          id: String(item.electronicId),
@@ -9654,6 +9793,18 @@
          class="map-content"
          :class="{ 'map-content--warning-cursor': fenceCreateOutsideCurrentCity }"
        />
        <div
          v-if="fenceHoverTooltipVisible"
          class="fence-hover-tooltip"
          :class="`is-${fenceHoverTooltipType}`"
          :style="{
            left: `${fenceHoverTooltipPosition.x}px`,
            top: `${fenceHoverTooltipPosition.y}px`,
          }"
        >
          <span class="fence-hover-tooltip__text">{{ fenceHoverTooltipText }}</span>
          <span class="fence-hover-tooltip__arrow" />
        </div>
        <MapInfoPopupCard
          v-if="showFenceEditMapInfoPopup"
          :style="mapInfoPopupStyle"
src/views/businessOperations/operationWorkbench/workbench.constants.ts
@@ -74,8 +74,8 @@
// 三类对象在左侧筛选区共用的状态筛选项。
export const statusOptionsMap: Record<WorkbenchTab, OptionItem[]> = {
  fence: [
    { label: "草稿", value: "draft" },
    { label: "待生效", value: "pending" },
    // { label: "草稿", value: "draft" },
    // { label: "待生效", value: "pending" },
    { label: "启用中", value: "enabled" },
    { label: "已停用", value: "disabled" },
  ],
src/views/testDemo/operationWorkbench/list.vue
@@ -82,7 +82,7 @@
const fenceStatusOptions = [
  { label: '草稿', value: 0 },
  { label: '待生效', value: 1 },
  // { label: '待生效', value: 1 },
  { label: '启用中', value: 2 },
  { label: '已停用', value: 3 },
  { label: '待测试', value: 4 },
src/views/testDemo/operationWorkbench/map.scss
@@ -739,6 +739,52 @@
  }
}
.fence-hover-tooltip {
  position: absolute;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 88px;
  max-width: 160px;
  padding: 7px 10px;
  border-radius: 6px;
  color: #fff;
  font-size: 13px;
  line-height: 18px;
  white-space: nowrap;
  pointer-events: none;
  transform: translate(-50%, -100%);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
  &.is-enabled {
    background: #19b144;
  }
  &.is-disabled {
    background: #8f959e;
  }
  &.is-testing {
    background: #171f26;
  }
}
.fence-hover-tooltip__text {
  overflow: hidden;
  text-overflow: ellipsis;
}
.fence-hover-tooltip__arrow {
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 10px;
  height: 10px;
  background: inherit;
  transform: translateX(-50%) rotate(45deg);
}
.map-tips {
  position: absolute;
  top: 46px;
src/views/testDemo/operationWorkbench/map.vue
@@ -1082,13 +1082,23 @@
const cityBoundaryHoverVisible = ref(false)
const cityBoundaryHoverText = ref('')
const cityBoundaryHoverOutsideSelected = ref(false)
const fenceHoverTooltipVisible = ref(false)
const fenceHoverTooltipText = ref('')
const fenceHoverTooltipType = ref<'enabled' | 'disabled' | 'testing'>('testing')
const fenceCreateOutsideCurrentCity = ref(false)
const cityBoundaryHoverPosition = reactive({
  x: 0,
  y: 0,
})
const fenceHoverTooltipPosition = reactive({
  x: 0,
  y: 0,
})
let cityBoundaryHoverQueryTimer: ReturnType<typeof setTimeout> | null = null
let cityBoundaryHoverQuerySeq = 0
let fenceHoverTooltipTimer: ReturnType<typeof setTimeout> | null = null
let pendingFenceHoverKey = ''
let pendingFenceHoverPixel: { x: number, y: number } | null = null
const cityBoundaryHoverTextCache = new Map<string, string>()
let skipFenceCreateCityChangeConfirm = false
let fenceCreateBoundaryModalOpening = false
@@ -1509,8 +1519,105 @@
  }
}
function clearFenceHoverTooltipTimer() {
  if (fenceHoverTooltipTimer) {
    clearTimeout(fenceHoverTooltipTimer)
    fenceHoverTooltipTimer = null
  }
}
function hideFenceHoverTooltip() {
  clearFenceHoverTooltipTimer()
  pendingFenceHoverKey = ''
  pendingFenceHoverPixel = null
  fenceHoverTooltipVisible.value = false
  fenceHoverTooltipText.value = ''
  fenceHoverTooltipType.value = 'testing'
}
function getFenceHoverKey(item: any) {
  return `${String(item?.electronicId || '')}-${String(item?.id || '')}`
}
function resolveFenceOverlayType(item: any) {
  if (Number(item?.flowStatus) === 5 && Number(item?.status) === 2)
    return 'enabled' as const
  if (Number(item?.flowStatus) === 5 && Number(item?.status) === 3)
    return 'disabled' as const
  return 'testing' as const
}
function getFenceHoverTooltipText(type: 'enabled' | 'disabled' | 'testing') {
  if (type === 'enabled')
    return '启用中围栏'
  if (type === 'disabled')
    return '已停用围栏'
  return '测试围栏'
}
function parseFencePriorityTime(value: unknown) {
  const timestamp = dayjs(String(value || '')).valueOf()
  return Number.isFinite(timestamp) ? timestamp : 0
}
function getFenceOverlayPriority(item: any) {
  const type = resolveFenceOverlayType(item)
  return {
    type,
    typePriority: type === 'testing' ? 2 : type === 'enabled' ? 1 : 0,
    updateTimePriority: parseFencePriorityTime(item?.updateTime),
    fallbackPriority: Number(item?.electronicId ?? item?.id ?? 0) || 0,
  }
}
function updateFenceHoverTooltipPositionByPixel(pixel?: { x: number, y: number } | null) {
  if (!map.value || !pixel)
    return false
  const container = map.value.getContainer?.()
  if (!container)
    return false
  const tooltipWidth = 88
  const offsetX = 12
  const offsetY = 18
  const rawX = Number(pixel.x) + offsetX
  const rawY = Number(pixel.y) - offsetY
  const clampedX = Math.max(12, Math.min(rawX, container.clientWidth - 12))
  const clampedY = Math.max(12, Math.min(rawY, container.clientHeight - 12))
  fenceHoverTooltipPosition.x = clampedX
  fenceHoverTooltipPosition.y = clampedY
  return true
}
function scheduleFenceHoverTooltip(item: any, pixel?: { x: number, y: number } | null) {
  if (Number(item?.areaType) !== 1 || isFenceFormMode.value || isStationFormMode.value)
    return
  const hoverKey = getFenceHoverKey(item)
  pendingFenceHoverKey = hoverKey
  pendingFenceHoverPixel = pixel
  updateFenceHoverTooltipPositionByPixel(pixel)
  clearFenceHoverTooltipTimer()
  fenceHoverTooltipTimer = window.setTimeout(() => {
    if (pendingFenceHoverKey !== hoverKey)
      return
    const nextType = resolveFenceOverlayType(item)
    if (!updateFenceHoverTooltipPositionByPixel(pendingFenceHoverPixel))
      return
    fenceHoverTooltipType.value = nextType
    fenceHoverTooltipText.value = getFenceHoverTooltipText(nextType)
    fenceHoverTooltipVisible.value = true
    fenceHoverTooltipTimer = null
  }, 300)
}
function handleCityBoundaryMouseOut() {
  hideCityBoundaryHoverTooltip()
  hideFenceHoverTooltip()
  resetFenceEditGuideHoverState()
  resetRestrictedEditGuideHoverState()
}
@@ -8526,6 +8633,7 @@
function clearMapEntities() {
  if (!map.value)
    return
  hideFenceHoverTooltip()
  if (pointMarkers.value.length)
    map.value.remove(pointMarkers.value)
  if (cityBoundaryPolygons.value.length)
@@ -8622,6 +8730,22 @@
        }
        return false
      })
      .sort((prev: any, next: any) => {
        const prevPriority = getFenceOverlayPriority(prev)
        const nextPriority = getFenceOverlayPriority(next)
        if (prevPriority.typePriority !== nextPriority.typePriority)
          return prevPriority.typePriority - nextPriority.typePriority
        if (
          prevPriority.type === 'testing'
          && nextPriority.type === 'testing'
          && prevPriority.updateTimePriority !== nextPriority.updateTimePriority
        ) {
          return prevPriority.updateTimePriority - nextPriority.updateTimePriority
        }
        if (prevPriority.updateTimePriority !== nextPriority.updateTimePriority)
          return prevPriority.updateTimePriority - nextPriority.updateTimePriority
        return prevPriority.fallbackPriority - nextPriority.fallbackPriority
      })
      .map((item) => {
        const areaType = Number(item.areaType)
        const points = String(item.fencePoints)
@@ -8663,6 +8787,7 @@
        const isFenceEnabled
          = areaType === 1
            && enabledFenceIdSet.value.has(String(item.electronicId))
        const fencePriority = getFenceOverlayPriority(item)
        // 非启用中的选中围栏使用蓝色选中态;启用中的围栏即使被选中也保持绿色。
        const isFenceSelectedBlue = isFenceDetailActive && !isFenceEnabled
        const isFenceHighlighted = isFenceDetailActive || isFenceEnabled
@@ -8741,9 +8866,12 @@
            && !isFenceCreateInteractionLocked.value
            && !isFenceFormMode.value
            && !isStationFormMode.value,
          zIndex: isReadonlyRestrictedActive ? 230 : isActive ? 220 : 205,
          zIndex: areaType === 2
            ? (isReadonlyRestrictedActive ? 230 : 205)
            : 240 + fencePriority.typePriority * 40 + (isActive ? 8 : 0),
        })
        polygon.on('click', () => {
          hideFenceHoverTooltip()
          if (
            isPointMapPickActive.value
            || isFenceCreateInteractionLocked.value
@@ -8781,6 +8909,18 @@
          if (readonlyRestrictedAreaId.value === '')
            renderMapEntities()
          handleMapEntityClick('fence', String(item.electronicId))
        })
        polygon.on('mouseover', (event: any) => {
          scheduleFenceHoverTooltip(item, event?.pixel || null)
        })
        polygon.on('mousemove', (event: any) => {
          pendingFenceHoverPixel = event?.pixel || null
          updateFenceHoverTooltipPositionByPixel(pendingFenceHoverPixel)
        })
        polygon.on('mouseout', () => {
          if (pendingFenceHoverKey === getFenceHoverKey(item))
            pendingFenceHoverKey = ''
          hideFenceHoverTooltip()
        })
        polygon.setExtData({
          id: String(item.electronicId),
@@ -9370,6 +9510,18 @@
          >
          {{ cityBoundaryHoverText }}
        </div>
        <div
          v-if="fenceHoverTooltipVisible"
          class="fence-hover-tooltip"
          :class="`is-${fenceHoverTooltipType}`"
          :style="{
            left: `${fenceHoverTooltipPosition.x}px`,
            top: `${fenceHoverTooltipPosition.y}px`,
          }"
        >
          <span class="fence-hover-tooltip__text">{{ fenceHoverTooltipText }}</span>
          <span class="fence-hover-tooltip__arrow" />
        </div>
        <MapInfoPopupCard
          v-if="showFenceEditMapInfoPopup"
          :style="mapInfoPopupStyle"
src/views/testDemo/operationWorkbench/workbench.constants.ts
@@ -77,9 +77,9 @@
    { label: "待测试", value: "wait-test" },
    { label: "测试中", value: "testing" },
    { label: "测试不通过", value: "test-fail" },
    { label: "可运营", value: "operable" },
    // { label: "可运营", value: "operable" },
    { label: "草稿", value: "draft" },
    { label: "待生效", value: "pending" },
    // { label: "待生效", value: "pending" },
    { label: "启用中", value: "enabled" },
    { label: "已停用", value: "disabled" },
  ],
src/views/testDemo/testPlanManagement/components/PointSelectModal.vue
@@ -6,7 +6,7 @@
import { NButton, NDataTable, NForm, NFormItem, NGi, NInput, NModal, NSelect } from 'naive-ui'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import Pagination from '@/components/common/Pagination.vue'
import { fetchPointPageList } from '@/service/api/testOperationWorkbench'
import { fetchFenceAndStationAreaList, fetchPointPageList } from '@/service/api/testOperationWorkbench'
import { getMultiSelectRowClassName, shouldIgnoreTableRowClick, toggleMultiSelectRowKey } from '@/utils/table'
const props = withDefaults(defineProps<{
@@ -30,13 +30,24 @@
  id: string
  pointName: string
  address: string
  pointArea: string
  pointType: number // 1: 运营, 2: 测试
  status: string
}
interface PointAreaOption {
  label: string
  value: string
  type: number
  name: string
  ids: number | string
}
const model = ref({
  pointName: '',
  pointType: null as number | null,
  operationStatusList: null as number[] | null,
  fenceAndStationList: [] as string[],
})
const pageParams = ref({
@@ -49,15 +60,79 @@
  { label: '测试', value: 2 },
]
const pointStatusOptions = [
  { label: '待测试', value: 1 },
  { label: '测试中', value: 2 },
  { label: '测试不通过', value: 3 },
  { label: '可运营', value: 4 },
  { label: '待生效', value: 5 },
  { label: '启用中', value: 6 },
  { label: '已停用', value: 7 },
  { label: '已失效', value: 8 },
]
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 pointAreaOptions = ref<PointAreaOption[]>([])
const saveDisabled = computed(() => checkedRowKeys.value.length === 0)
const tableScrollX = 900
function buildPointAreaOptionValue(item: { type: number, ids: number | string }) {
  return `${Number(item.type)}_${String(item.ids)}`
}
function getSelectedPointAreaList() {
  const selectedValueSet = new Set(model.value.fenceAndStationList || [])
  return pointAreaOptions.value
    .filter(item => selectedValueSet.has(item.value))
    .map(item => ({
      type: item.type,
      name: item.name,
      ids: item.ids,
    }))
}
function getPointAreaText(row?: any) {
  const electronicName = String(row?.electronicName || '').trim()
  const stationName = String(row?.stationName || '').trim()
  if (electronicName && stationName)
    return `[区]${electronicName} , [场]${stationName}`
  if (electronicName)
    return `[区]${electronicName}`
  if (stationName)
    return `[场]${stationName}`
  return '-'
}
function isPointDisabled(row?: Partial<PointRow> | null) {
  return String(row?.status || '').trim() === '已停用'
}
async function loadPointAreaOptions() {
  try {
    const res = await fetchFenceAndStationAreaList()
    const list = Array.isArray(res.data) ? res.data : []
    pointAreaOptions.value = list
      .filter((item: any) => item && item.type && item.name !== undefined && item.ids !== undefined)
      .map((item: any) => ({
        label: `${Number(item.type) === 1 ? '围栏' : '场站'}-${String(item.name || '-')}`,
        value: buildPointAreaOptionValue(item),
        type: Number(item.type),
        name: String(item.name || ''),
        ids: item.ids,
      }))
  }
  catch (error) {
    console.error('加载所属区域失败:', error)
    pointAreaOptions.value = []
  }
  const optionValueSet = new Set(pointAreaOptions.value.map(item => item.value))
  model.value.fenceAndStationList = (model.value.fenceAndStationList || []).filter(value => optionValueSet.has(value))
}
async function getList() {
@@ -66,6 +141,8 @@
    const res = await fetchPointPageList({
      pointName: model.value.pointName.trim() || undefined,
      pointType: model.value.pointType || undefined,
      operationStatusList: model.value.operationStatusList?.length ? model.value.operationStatusList : undefined,
      fenceAndStationList: getSelectedPointAreaList(),
      pageNum: pageParams.value.pageNum,
      pageSize: pageParams.value.pageSize,
    })
@@ -79,6 +156,7 @@
      id: String(item.id),
      pointName: item.pointName || '-',
      address: item.pointAddress || '-',
      pointArea: getPointAreaText(item),
      pointType: Number(item.pointType ?? 0),
      status: getPointStatusText(item.operationStatus ?? item.operationStatus ?? 0, item.verificationStatus),
    }))
@@ -107,12 +185,13 @@
  (val) => {
    if (val) {
      checkedRowKeys.value = props.existingPoints.map(item => item.id)
      model.value = { pointName: '', pointType: null }
      model.value = { pointName: '', pointType: null, operationStatusList: null, fenceAndStationList: [] }
      pageParams.value = { pageNum: 1, pageSize: 10 }
      allLoadedData.value.clear()
      props.existingPoints.forEach((item) => {
        allLoadedData.value.set(item.id, item)
      })
      loadPointAreaOptions()
      getList()
    }
    else {
@@ -128,7 +207,7 @@
}
function handleReset() {
  model.value = { pointName: '', pointType: null }
  model.value = { pointName: '', pointType: null, operationStatusList: null, fenceAndStationList: [] }
  pageParams.value.pageNum = 1
  getList()
}
@@ -188,11 +267,12 @@
    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: 'pointName', width: 160,ellipsis:{tooltip:true} },
  { title: '所属区域', key: 'pointArea', width: 160,ellipsis:{tooptip:true} },
  { title: '详细地址', key: 'address', width: 220 ,ellipsis:{tooltip:true}},
  { title: '点位类型', key: 'pointType', width: 120, render: row => row.pointType === 2 ? '测试' : '运营' },
  {
    title: '状态',
    title: '点位状态',
    key: 'status',
    width: 100,
    render: (row) => {
@@ -230,6 +310,25 @@
                />
              </NFormItem>
            </NGi>
            <NGi>
              <NFormItem label="所属区域" path="fenceAndStationList">
                <NSelect
                  v-model:value="model.fenceAndStationList"
                  :options="pointAreaOptions"
                  placeholder="请选择"
                  clearable
                  multiple
                  max-tag-count="responsive"
                >
                  <template #empty>
                    <div class="flex-center">
                      <img :src="noDataImg" style="width: 50%" alt="no-data">
                    </div>
                  </template>
                </NSelect>
              </NFormItem>
            </NGi>
            <NGi>
              <NFormItem label="点位类型" path="pointType">
                <NSelect
@@ -237,6 +336,24 @@
                  :options="pointTypeOptions"
                  placeholder="请选择"
                  clearable
                >
                  <template #empty>
                    <div class="flex-center">
                      <img :src="noDataImg" style="width: 50%" alt="no-data">
                    </div>
                  </template>
                </NSelect>
              </NFormItem>
            </NGi>
            <NGi>
            <NFormItem label="点位状态" path="operationStatusList">
                <NSelect
                  v-model:value="model.operationStatusList"
                  :options="pointStatusOptions"
                  placeholder="请选择"
                  clearable
                  multiple
                  max-tag-count="responsive"
                >
                  <template #empty>
                    <div class="flex-center">
@@ -268,6 +385,7 @@
          v-model:checked-row-keys="checkedRowKeys"
          style="height: 100%"
          flex-height
          :scroll-x="tableScrollX"
          :columns="columns"
          :data="listData"
          :loading="loading"