测试计划选择点位弹窗优化//去掉测试运营工作台的状态枚举-待生效,可运营//加围栏的交互-hover显示围栏类型,加层级判断:测试>运营 类型相同 更新时间最近优先
| | |
| | | ] |
| | | |
| | | const fenceStatusOptions = [ |
| | | { label: '草稿', value: 0 }, |
| | | { label: '待生效', value: 1 }, |
| | | // { label: '草稿', value: 0 }, |
| | | // { label: '待生效', value: 1 }, |
| | | { label: '启用中', value: 2 }, |
| | | { label: '已停用', value: 3 }, |
| | | ] |
| | |
| | | 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; |
| | |
| | | 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) |
| | |
| | | |
| | | 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) { |
| | |
| | | function clearMapEntities() { |
| | | if (!map.value) |
| | | return |
| | | hideFenceHoverTooltip() |
| | | if (pointMarkers.value.length) |
| | | map.value.remove(pointMarkers.value) |
| | | if (cityBoundaryPolygons.value.length) |
| | |
| | | } |
| | | 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 |
| | |
| | | // 这里仅保留“停用禁行区不可点”,不再沿用旧逻辑把停用运营区一并禁点。 |
| | | const shouldBlockAreaClick |
| | | = areaType === 2 && isDisabledFenceArea |
| | | const fencePriority = getFenceOverlayPriority(item) |
| | | const polygon = new (window as any).AMap.Polygon({ |
| | | path: points, |
| | | fillColor: |
| | |
| | | && !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 |
| | |
| | | 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), |
| | |
| | | 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" |
| | |
| | | // 三类对象在左侧筛选区共用的状态筛选项。 |
| | | 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" }, |
| | | ], |
| | |
| | | |
| | | const fenceStatusOptions = [ |
| | | { label: '草稿', value: 0 }, |
| | | { label: '待生效', value: 1 }, |
| | | // { label: '待生效', value: 1 }, |
| | | { label: '启用中', value: 2 }, |
| | | { label: '已停用', value: 3 }, |
| | | { label: '待测试', value: 4 }, |
| | |
| | | } |
| | | } |
| | | |
| | | .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; |
| | |
| | | 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 |
| | |
| | | } |
| | | } |
| | | |
| | | 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() |
| | | } |
| | |
| | | function clearMapEntities() { |
| | | if (!map.value) |
| | | return |
| | | hideFenceHoverTooltip() |
| | | if (pointMarkers.value.length) |
| | | map.value.remove(pointMarkers.value) |
| | | if (cityBoundaryPolygons.value.length) |
| | |
| | | } |
| | | 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) |
| | |
| | | const isFenceEnabled |
| | | = areaType === 1 |
| | | && enabledFenceIdSet.value.has(String(item.electronicId)) |
| | | const fencePriority = getFenceOverlayPriority(item) |
| | | // 非启用中的选中围栏使用蓝色选中态;启用中的围栏即使被选中也保持绿色。 |
| | | const isFenceSelectedBlue = isFenceDetailActive && !isFenceEnabled |
| | | const isFenceHighlighted = isFenceDetailActive || isFenceEnabled |
| | |
| | | && !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 |
| | |
| | | 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), |
| | |
| | | > |
| | | {{ 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" |
| | |
| | | { 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" }, |
| | | ], |
| | |
| | | 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<{ |
| | |
| | | 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({ |
| | |
| | | { 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() { |
| | |
| | | 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, |
| | | }) |
| | |
| | | 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), |
| | | })) |
| | |
| | | (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 { |
| | |
| | | } |
| | | |
| | | function handleReset() { |
| | | model.value = { pointName: '', pointType: null } |
| | | model.value = { pointName: '', pointType: null, operationStatusList: null, fenceAndStationList: [] } |
| | | pageParams.value.pageNum = 1 |
| | | getList() |
| | | } |
| | |
| | | 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) => { |
| | |
| | | /> |
| | | </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 |
| | |
| | | :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"> |
| | |
| | | v-model:checked-row-keys="checkedRowKeys" |
| | | style="height: 100%" |
| | | flex-height |
| | | :scroll-x="tableScrollX" |
| | | :columns="columns" |
| | | :data="listData" |
| | | :loading="loading" |