@@ -5,8 +5,8 @@
|
||||
|
||||
const https = require('https')
|
||||
|
||||
// 腾讯地图 WebService API Key(需要在腾讯位置服务申请)
|
||||
const API_KEY = 'EJPBZ-DQEEQ-PDN5U-4ZDVX-F4I3F-6MBJC'
|
||||
// 由部署环境注入,避免把地图服务凭据写入仓库。
|
||||
const API_KEY = process.env.TENCENT_MAP_WEBSERVICE_KEY
|
||||
|
||||
// 深圳自然博物馆坐标
|
||||
const MUSEUM_LAT = 22.692763
|
||||
@@ -109,6 +109,12 @@ function calculateRectangle(centerLat, centerLng, widthMeters, heightMeters) {
|
||||
return polygon
|
||||
}
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error('缺少 TENCENT_MAP_WEBSERVICE_KEY;请通过环境变量注入受域名/IP 限制的腾讯地图 WebService Key。')
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
console.log('正在获取深圳自然博物馆建筑围栏信息...\n')
|
||||
|
||||
// 执行查询
|
||||
|
||||
@@ -586,10 +586,13 @@ const markFloorSwitchFailedIfUnrendered = (floorId: string, requestSeq: number)
|
||||
const handleFloorChange = (floor: { id: string; label: string }) => {
|
||||
const floorId = floor.id
|
||||
if (!floorId || loadingFloorId.value) return
|
||||
if (floorId === renderedFloorId.value && activeFloorId.value === floorId && props.layerMode !== 'multi') {
|
||||
if (
|
||||
floorId === renderedFloorId.value
|
||||
&& activeFloorId.value === floorId
|
||||
&& props.indoorView === 'floor'
|
||||
&& props.layerMode !== 'multi'
|
||||
) {
|
||||
emit('floorChange', floorId)
|
||||
emit('indoorViewChange', 'floor')
|
||||
emit('layerModeChange', 'single')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -604,9 +607,6 @@ const handleFloorChange = (floor: { id: string; label: string }) => {
|
||||
floorId,
|
||||
floorLabel: floor.label
|
||||
})
|
||||
emit('indoorViewChange', 'floor')
|
||||
emit('layerModeChange', 'single')
|
||||
|
||||
Promise.resolve(indoorRendererRef.value?.switchFloor?.(floorId))
|
||||
.then(() => {
|
||||
markFloorSwitchFailedIfUnrendered(floorId, requestSeq)
|
||||
@@ -667,10 +667,12 @@ const handleLayerModeChange = (mode: LayerDisplayMode) => {
|
||||
})
|
||||
console.error('楼层切换失败:', error)
|
||||
})
|
||||
emit('indoorViewChange', 'floor')
|
||||
// floor-change 才代表 ThreeMap 已提交单层模型。
|
||||
}
|
||||
|
||||
emit('layerModeChange', mode)
|
||||
if (mode === 'multi') {
|
||||
emit('layerModeChange', mode)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLayerModeToggle = () => {
|
||||
@@ -786,6 +788,8 @@ defineExpose({
|
||||
clearRoute: () => {
|
||||
indoorRendererRef.value?.clearRoute?.()
|
||||
},
|
||||
// 仅发起切换;父级必须以 floor-change 作为已提交的唯一依据。
|
||||
switchFloor: (floorId: string) => indoorRendererRef.value?.switchFloor?.(floorId),
|
||||
showOverview: handleShowOverview,
|
||||
resetToViewBaseline: (options: {
|
||||
view: 'overview' | 'floor'
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
class="guide-top-tabs"
|
||||
:class="`variant-${variant}`"
|
||||
:style="{ top }"
|
||||
role="tablist"
|
||||
aria-label="主功能"
|
||||
>
|
||||
<view
|
||||
v-for="tab in GUIDE_TOP_TABS"
|
||||
:key="tab.id"
|
||||
class="guide-top-tab"
|
||||
:class="{ active: activeTab === tab.id }"
|
||||
role="tab"
|
||||
:tabindex="activeTab === tab.id ? 0 : -1"
|
||||
:aria-selected="activeTab === tab.id"
|
||||
:aria-label="`切换至${tab.label}`"
|
||||
@tap="handleTabTap(tab.id)"
|
||||
@keydown.enter.prevent="handleTabTap(tab.id)"
|
||||
@keydown.space.prevent="handleTabTap(tab.id)"
|
||||
>
|
||||
<view
|
||||
v-if="activeTab === tab.id && variant === 'underline'"
|
||||
|
||||
@@ -226,6 +226,9 @@ import {
|
||||
import {
|
||||
guideUseCase
|
||||
} from '@/usecases/guideUseCase'
|
||||
import {
|
||||
createVisitorPoiPresentations
|
||||
} from '@/view-models/visitorPoiPresentation'
|
||||
import {
|
||||
HOME_POI_CATEGORIES,
|
||||
POI_CATEGORIES,
|
||||
@@ -356,13 +359,18 @@ const isPoiLocatable = (poi: MuseumPoi) => !getPoiDataIssues(poi).some((issue) =
|
||||
|
||||
const unavailablePoiCount = computed(() => pois.value.filter((poi) => !isPoiLocatable(poi)).length)
|
||||
|
||||
// 数据质量统计保留给诊断日志,普通游客只看到可恢复的加载状态。
|
||||
const dataWarning = computed(() => {
|
||||
const warnings: string[] = []
|
||||
if (excludedPoiCount.value) warnings.push(`${excludedPoiCount.value} 个点位分类或楼层信息不完整`)
|
||||
if (duplicatePoiCount.value) warnings.push(`${duplicatePoiCount.value} 个重复点位已合并`)
|
||||
if (unavailablePoiCount.value) warnings.push(`${unavailablePoiCount.value} 个点位暂无地图坐标`)
|
||||
if (floorLoadError.value) warnings.push('楼层信息读取失败')
|
||||
return warnings.join(',')
|
||||
const diagnostic = {
|
||||
excluded: excludedPoiCount.value,
|
||||
duplicates: duplicatePoiCount.value,
|
||||
unavailable: unavailablePoiCount.value,
|
||||
floorLoadError: floorLoadError.value
|
||||
}
|
||||
if (import.meta.env.DEV && Object.values(diagnostic).some(Boolean)) {
|
||||
console.debug('[guide-poi-diagnostics]', diagnostic)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const dedupePois = (items: MuseumPoi[]) => {
|
||||
@@ -860,7 +868,10 @@ const restoreResultListScroll = async () => {
|
||||
if (resultListElement) resultListElement.scrollTop = scrollTop
|
||||
}
|
||||
|
||||
const poiDisplayName = (poi: MuseumPoi) => poi.name?.trim() || '未命名点位'
|
||||
const poiPresentations = computed(() => createVisitorPoiPresentations(pois.value))
|
||||
const poiDisplayName = (poi: MuseumPoi) => (
|
||||
poiPresentations.value.get(poi.id)?.displayName || '未命名点位'
|
||||
)
|
||||
|
||||
const poiResultMeta = (poi: MuseumPoi) => {
|
||||
const categoryLabel = resolvePoiCategory(poi)?.label || poi.primaryCategory?.label || '其他'
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<button
|
||||
v-if="!shouldUseHostNavigation"
|
||||
class="hero-back"
|
||||
aria-label="返回讲解详情"
|
||||
aria-label="返回讲解对象列表"
|
||||
@tap.stop="handleBack"
|
||||
>
|
||||
<text class="hero-back-icon">‹</text>
|
||||
|
||||
@@ -424,6 +424,7 @@ const returningToOverview = ref(false)
|
||||
// GuideMapShell 组件引用,用于显式调用路线清除方法
|
||||
const guideMapShellRef = ref<{
|
||||
clearRoute?: () => void
|
||||
switchFloor?: (floorId: string) => Promise<void> | void
|
||||
showOverview?: () => Promise<void> | void
|
||||
resetToViewBaseline?: (options: {
|
||||
view: 'overview' | 'floor'
|
||||
@@ -1482,12 +1483,24 @@ const handleMoreRouteGuide = async () => {
|
||||
closeArrivalPanel()
|
||||
showRoutePlanner.value = false
|
||||
is3DMode.value = true
|
||||
indoorView.value = 'floor'
|
||||
guideOutdoorState.value = 'home'
|
||||
selectedGuidePoi.value = null
|
||||
isPoiCardCollapsed.value = false
|
||||
isSimulatingRoute.value = false
|
||||
showIndoorHint(`当前楼层:${getGuideFloorLabel(renderedFloorId.value || activeGuideFloor.value) || '馆内单层'}`, 3200)
|
||||
const floorId = activeGuideFloor.value
|
||||
const floorLabel = getGuideFloorLabel(floorId) || '默认楼层'
|
||||
if (!floorId) {
|
||||
showIndoorHint('暂未获取到可进入的楼层,请稍后重试', 3200)
|
||||
return
|
||||
}
|
||||
|
||||
requestedFloorId.value = floorId
|
||||
requestedFloorLabel.value = floorLabel
|
||||
loadingFloorId.value = floorId
|
||||
failedFloorId.value = ''
|
||||
// 渲染器提交 floor-change 前仍保持建筑外观,避免楼层控件显示假状态。
|
||||
showIndoorHint(`正在进入 ${floorLabel}…`, 3200)
|
||||
await guideMapShellRef.value?.switchFloor?.(floorId)
|
||||
}
|
||||
|
||||
const selectFirstArrivalTarget = () => {
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
<view v-if="routePlanError" class="indoor-guide-error">
|
||||
<text class="indoor-guide-error-text">{{ routePlanError }}</text>
|
||||
</view>
|
||||
<view class="start-point-select" @tap="openStartPointPicker">
|
||||
<text class="start-point-select-label">起点</text>
|
||||
<text class="start-point-select-value" :class="{ placeholder: !selectedStartPoint }">
|
||||
{{ selectedStartPoint ? `${selectedStartPoint.name} · ${selectedStartPoint.floorLabel}` : '请选择起点' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="indoor-guide-actions">
|
||||
<view
|
||||
class="indoor-guide-btn secondary"
|
||||
@@ -159,6 +165,19 @@
|
||||
<view class="indoor-guide-peek-handle"></view>
|
||||
<text class="indoor-guide-peek-text">点位预览</text>
|
||||
</view>
|
||||
<RoutePointPicker
|
||||
:visible="startPointPickerVisible"
|
||||
title="选择起点"
|
||||
placeholder="搜索起点"
|
||||
:options="routePointOptions"
|
||||
:selected-poi-id="selectedStartPoint?.poiId || ''"
|
||||
:loading="startPointPickerLoading"
|
||||
:error="startPointPickerError"
|
||||
empty-text="暂无可作为起点的位置"
|
||||
close-text="返回"
|
||||
@close="startPointPickerVisible = false"
|
||||
@select="handleStartPointSelect"
|
||||
/>
|
||||
</GuideMapShell>
|
||||
</GuidePageFrame>
|
||||
</template>
|
||||
@@ -168,6 +187,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
|
||||
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
|
||||
import RoutePointPicker, { type RoutePointOption } from '@/components/navigation/RoutePointPicker.vue'
|
||||
import {
|
||||
guideUseCase
|
||||
} from '@/usecases/guideUseCase'
|
||||
@@ -226,6 +246,11 @@ const routeReadinessMessage = ref('')
|
||||
const routePlanning = ref(false)
|
||||
const routePlanError = ref('')
|
||||
const activeRoutePreview = ref<GuideRouteResult | null>(null)
|
||||
const selectedStartPoint = ref<RoutePointOption | null>(null)
|
||||
const routePointOptions = ref<RoutePointOption[]>([])
|
||||
const startPointPickerVisible = ref(false)
|
||||
const startPointPickerLoading = ref(false)
|
||||
const startPointPickerError = ref('')
|
||||
const isIndoorGuidePanelHidden = ref(false)
|
||||
const indoorGuideTouchStartY = ref(0)
|
||||
const indoorGuideTouchCurrentY = ref(0)
|
||||
@@ -724,9 +749,41 @@ const handleConfirmLocation = async () => {
|
||||
activeFloor.value = startLocation.floor || activeFloor.value
|
||||
isManualLocationPanelOpen.value = false
|
||||
activeRoutePreview.value = null
|
||||
selectedStartPoint.value = null
|
||||
routeViewState.value = 'preview'
|
||||
requestTargetFocus(route.value.targetLocation)
|
||||
saveCurrentLocationPreviewUrl()
|
||||
routePlanError.value = '请选择起点'
|
||||
}
|
||||
|
||||
const openStartPointPicker = async () => {
|
||||
startPointPickerVisible.value = true
|
||||
startPointPickerError.value = ''
|
||||
if (routePointOptions.value.length || startPointPickerLoading.value) return
|
||||
|
||||
startPointPickerLoading.value = true
|
||||
try {
|
||||
routePointOptions.value = (await guideRouteUseCase.listTargets())
|
||||
.filter((target) => Boolean(target.poiId && target.routeNodeId))
|
||||
.map((target) => ({
|
||||
poiId: target.poiId,
|
||||
name: target.name,
|
||||
floorId: target.floorId,
|
||||
floorLabel: target.floorLabel,
|
||||
categoryLabel: target.categoryLabel
|
||||
}))
|
||||
} catch (error) {
|
||||
startPointPickerError.value = error instanceof Error ? error.message : '起点列表加载失败,请重试'
|
||||
} finally {
|
||||
startPointPickerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartPointSelect = (point: RoutePointOption) => {
|
||||
selectedStartPoint.value = point
|
||||
startPointPickerVisible.value = false
|
||||
routePlanError.value = ''
|
||||
activeRoutePreview.value = null
|
||||
}
|
||||
|
||||
const handleStartIndoorGuide = async () => {
|
||||
@@ -734,37 +791,31 @@ const handleStartIndoorGuide = async () => {
|
||||
|
||||
await refreshRouteReadinessState()
|
||||
if (!routeReady.value) return
|
||||
if (!selectedStartPoint.value?.poiId) {
|
||||
routePlanError.value = '请选择起点'
|
||||
return
|
||||
}
|
||||
|
||||
routePlanning.value = true
|
||||
routePlanError.value = ''
|
||||
|
||||
try {
|
||||
const targets = await guideRouteUseCase.listTargets()
|
||||
const startName = route.value.startLocation?.label || ''
|
||||
const startFloor = route.value.startLocation?.floor || ''
|
||||
const targetLocation = route.value.targetLocation
|
||||
const fallbackStart = targets.find((target) => (
|
||||
startName
|
||||
? target.name.includes(startName) || startName.includes(target.name)
|
||||
: false
|
||||
)) || targets.find((target) => (
|
||||
startFloor
|
||||
? target.floorLabel === startFloor || target.floorId === normalizeGuideFloorId(startFloor)
|
||||
: false
|
||||
)) || targets.find((target) => target.poiId !== route.value.facilityId)
|
||||
const selectedStart = targets.find((target) => target.poiId === selectedStartPoint.value?.poiId)
|
||||
|
||||
const endTarget = targets.find((target) => target.poiId === route.value.facilityId)
|
||||
|| (targetLocation
|
||||
? targets.find((target) => target.floorId === targetLocation.floorId && target.name === targetLocation.name)
|
||||
: null)
|
||||
|
||||
if (!fallbackStart || !endTarget) {
|
||||
routePlanError.value = '未找到可用于位置关系预览的起点或终点'
|
||||
if (!selectedStart || !selectedStart.routeNodeId || !endTarget) {
|
||||
routePlanError.value = selectedStart ? '未找到可用于位置关系预览的终点' : '请选择起点'
|
||||
return
|
||||
}
|
||||
|
||||
const result = await guideRouteUseCase.planRoute({
|
||||
startPoiId: fallbackStart.poiId,
|
||||
startPoiId: selectedStart.poiId,
|
||||
endPoiId: endTarget.poiId
|
||||
})
|
||||
|
||||
@@ -952,6 +1003,41 @@ const handlePageBack = () => {
|
||||
color: #b44b42;
|
||||
}
|
||||
|
||||
.start-point-select {
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
background: #f5f7f2;
|
||||
border: 1px solid #e4e5df;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.start-point-select-label {
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #545861;
|
||||
}
|
||||
|
||||
.start-point-select-value {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
font-weight: 500;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.start-point-select-value.placeholder {
|
||||
color: #7a7e76;
|
||||
}
|
||||
|
||||
.indoor-guide-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
52
src/view-models/visitorPoiPresentation.ts
Normal file
52
src/view-models/visitorPoiPresentation.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { MuseumPoi } from '@/domain/museum'
|
||||
|
||||
export type VisitorPoiPresentation = Readonly<{
|
||||
displayName: string
|
||||
floorLabel: string
|
||||
locationHint: string
|
||||
}>
|
||||
|
||||
const normalizeDisplayName = (name: string) => {
|
||||
const normalized = name.trim().replace(/\s+/g, ' ')
|
||||
const hallMatch = normalized.match(/^展厅\s*\d+\s*(.+厅)$/)
|
||||
if (hallMatch?.[1]) return hallMatch[1]
|
||||
return normalized.replace(/(卫生间|洗手间)(\d+)$/, '$1 $2')
|
||||
}
|
||||
|
||||
type VisitorPoiSource = Pick<MuseumPoi, 'id' | 'name' | 'floorId' | 'floorLabel' | 'hallName'> & {
|
||||
primaryCategory?: { label?: string }
|
||||
}
|
||||
|
||||
const locationHintFor = (poi: VisitorPoiSource) => [
|
||||
poi.floorLabel?.trim(),
|
||||
poi.hallName?.trim(),
|
||||
poi.primaryCategory?.label?.trim()
|
||||
].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index).join(' · ')
|
||||
|
||||
export const createVisitorPoiPresentations = <TPoi extends VisitorPoiSource>(pois: TPoi[]) => {
|
||||
const baseNames = new Map<string, number>()
|
||||
pois.forEach((poi) => {
|
||||
const name = normalizeDisplayName(poi.name || '') || '未命名点位'
|
||||
baseNames.set(name, (baseNames.get(name) || 0) + 1)
|
||||
})
|
||||
|
||||
const duplicateIndexes = new Map<string, number>()
|
||||
return new Map(pois.map((poi) => {
|
||||
const baseName = normalizeDisplayName(poi.name || '') || '未命名点位'
|
||||
const duplicateCount = baseNames.get(baseName) || 0
|
||||
const nextIndex = (duplicateIndexes.get(baseName) || 0) + 1
|
||||
duplicateIndexes.set(baseName, nextIndex)
|
||||
const locationHint = locationHintFor(poi)
|
||||
const disambiguation = duplicateCount > 1 ? (locationHint || `设施 ${nextIndex}`) : locationHint
|
||||
|
||||
return [poi.id, {
|
||||
displayName: duplicateCount > 1 ? `${baseName} · ${disambiguation}` : baseName,
|
||||
floorLabel: poi.floorLabel || poi.floorId,
|
||||
locationHint: disambiguation
|
||||
} satisfies VisitorPoiPresentation]
|
||||
}))
|
||||
}
|
||||
|
||||
export const presentVisitorPoi = <TPoi extends VisitorPoiSource>(poi: TPoi): VisitorPoiPresentation => (
|
||||
createVisitorPoiPresentations([poi]).get(poi.id)!
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { defineComponent } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
|
||||
|
||||
const ThreeMapStub = defineComponent({
|
||||
@@ -26,6 +26,10 @@ const ThreeMapStub = defineComponent({
|
||||
}
|
||||
},
|
||||
emits: ['floor-change', 'target-focus'],
|
||||
setup(_, { expose }) {
|
||||
expose({ switchFloor: vi.fn() })
|
||||
return {}
|
||||
},
|
||||
template: '<div data-testid="three-map"></div>'
|
||||
})
|
||||
|
||||
@@ -104,4 +108,27 @@ describe('GuideMapShell 点位过滤契约', () => {
|
||||
expect(renderer.props('initialFloorId')).toBe('L2')
|
||||
expect(renderer.props('initialView')).toBe('overview')
|
||||
})
|
||||
|
||||
it('外观状态点击当前业务楼层仍请求渲染器,且等待 floor-change 才提交业务视图', async () => {
|
||||
const wrapper = mount(GuideMapShell, {
|
||||
props: {
|
||||
mapType: 'indoor',
|
||||
indoorModelSource: modelSource,
|
||||
floors: [{ id: 'L2', label: '2F' }],
|
||||
activeFloor: 'L2',
|
||||
indoorView: 'overview',
|
||||
indoorInitialView: 'overview'
|
||||
},
|
||||
global: { stubs: { ThreeMap: ThreeMapStub } }
|
||||
})
|
||||
const renderer = wrapper.getComponent(ThreeMapStub)
|
||||
|
||||
await (wrapper.vm as any).switchFloor('L2')
|
||||
expect((renderer.vm as any).switchFloor).toHaveBeenCalledWith('L2')
|
||||
expect(wrapper.emitted('indoorViewChange')).toBeUndefined()
|
||||
|
||||
renderer.vm.$emit('floor-change', 'L2')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.emitted('indoorViewChange')).toEqual([['floor']])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -334,6 +334,9 @@ describe('首页搜索与地图闭环', () => {
|
||||
|
||||
await guideAction.trigger('tap')
|
||||
await flushPromises()
|
||||
expect(map.props('indoorView')).toBe('overview')
|
||||
map.vm.$emit('floor-change', 'L1')
|
||||
await flushPromises()
|
||||
expect(map.props('indoorView')).toBe('floor')
|
||||
expect(wrapper.findAll('.guide-quick-action')[0]?.text()).toBe('导览')
|
||||
|
||||
|
||||
35
tests/unit/visitorPoiPresentation.spec.ts
Normal file
35
tests/unit/visitorPoiPresentation.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { MuseumPoi } from '@/domain/museum'
|
||||
import { createVisitorPoiPresentations } from '@/view-models/visitorPoiPresentation'
|
||||
|
||||
const poi = (id: string, name: string, floorLabel = '1F', hallName?: string): MuseumPoi => ({
|
||||
id,
|
||||
name,
|
||||
floorId: floorLabel === '1F' ? 'L1' : 'L2',
|
||||
floorLabel,
|
||||
hallName,
|
||||
accessible: false,
|
||||
primaryCategory: { id: 'facility', label: '服务设施' },
|
||||
categories: []
|
||||
})
|
||||
|
||||
describe('VisitorPoiPresentation', () => {
|
||||
it.each([
|
||||
['展厅 7生态厅', '生态厅'],
|
||||
['男卫生间00', '男卫生间 00'],
|
||||
['贵宾卫生间', '贵宾卫生间']
|
||||
])('规范游客展示名 %s', (rawName, displayName) => {
|
||||
const result = createVisitorPoiPresentations([poi('p1', rawName)])
|
||||
expect(result.get('p1')?.displayName).toBe(displayName)
|
||||
})
|
||||
|
||||
it('用真实楼层或展厅区分重复设施', () => {
|
||||
const result = createVisitorPoiPresentations([
|
||||
poi('p1', '无障碍卫生间', '1F', '恐龙厅'),
|
||||
poi('p2', '无障碍卫生间', '2F', '生态厅')
|
||||
])
|
||||
|
||||
expect(result.get('p1')?.displayName).toBe('无障碍卫生间 · 1F · 恐龙厅 · 服务设施')
|
||||
expect(result.get('p2')?.displayName).toBe('无障碍卫生间 · 2F · 生态厅 · 服务设施')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user