245 lines
7.0 KiB
Vue
245 lines
7.0 KiB
Vue
<template>
|
|
<GuidePageFrame
|
|
active-tab="explain"
|
|
variant="static"
|
|
:show-top-tabs="false"
|
|
show-back
|
|
back-label="讲解首页"
|
|
@tab-change="handleTopTabChange"
|
|
@back="handleBack"
|
|
>
|
|
<view class="explain-all-page">
|
|
<ExplainHallSelect
|
|
:halls="explainHallItems"
|
|
:business-units="explainBusinessUnitItems"
|
|
:guide-stops="explainGuideStopItems"
|
|
:stage="explainStage"
|
|
:selected-hall-name="selectedExplainHallName"
|
|
:selected-business-unit-name="selectedExplainBusinessUnitName"
|
|
:loading="explainLoading"
|
|
:error="explainError"
|
|
@hall-click="handleExplainHallClick"
|
|
@business-unit-click="handleExplainBusinessUnitClick"
|
|
@guide-stop-click="handleExplainGuideStopClick"
|
|
@back="handlePanelBack"
|
|
/>
|
|
</view>
|
|
</GuidePageFrame>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
|
|
import ExplainHallSelect, {
|
|
type ExplainBusinessUnitSelectItem,
|
|
type ExplainGuideStopSelectItem,
|
|
type ExplainHallSelectItem,
|
|
type ExplainSelectStage
|
|
} from '@/components/explain/ExplainHallSelect.vue'
|
|
import {
|
|
explainUseCase
|
|
} from '@/usecases/explainUseCase'
|
|
import {
|
|
normalizeExplainDetailTargetFromGuideStop
|
|
} from '@/domain/explainDetailTarget'
|
|
import type {
|
|
ExplainBusinessUnit,
|
|
MuseumHall
|
|
} from '@/domain/museum'
|
|
import {
|
|
navigateToGuideTopTab,
|
|
type GuideTopTab
|
|
} from '@/utils/guideTopTabs'
|
|
|
|
const explainHallItems = ref<ExplainHallSelectItem[]>([])
|
|
const explainBusinessUnits = ref<ExplainBusinessUnit[]>([])
|
|
const explainStage = ref<ExplainSelectStage>('hall')
|
|
const selectedExplainHallId = ref('')
|
|
const selectedExplainHallName = ref('')
|
|
const selectedExplainBusinessUnitId = ref('')
|
|
const selectedExplainBusinessUnitName = ref('')
|
|
const explainLoading = ref(false)
|
|
const explainError = ref('')
|
|
let explainLoadPromise: Promise<void> | null = null
|
|
|
|
const normalizeExplainKeyword = (value?: string) => (value || '').trim().toLowerCase()
|
|
|
|
const buildExplainHallItems = (
|
|
halls: MuseumHall[],
|
|
summaries: Awaited<ReturnType<typeof explainUseCase.loadExplainHallSummaries>> = {}
|
|
): ExplainHallSelectItem[] => (
|
|
halls.map((hall) => ({
|
|
id: hall.id,
|
|
name: hall.name,
|
|
floorLabel: hall.floorLabel,
|
|
image: hall.image,
|
|
explainCount: hall.exhibitCount || 0,
|
|
businessUnitCount: summaries[hall.id]?.businessUnitCount,
|
|
guideStopCount: summaries[hall.id]?.guideStopCount,
|
|
searchText: normalizeExplainKeyword([
|
|
hall.name,
|
|
hall.floorLabel,
|
|
hall.description,
|
|
hall.area
|
|
].filter(Boolean).join(' '))
|
|
}))
|
|
)
|
|
|
|
const selectedExplainBusinessUnit = computed(() => (
|
|
explainBusinessUnits.value.find((unit) => unit.id === selectedExplainBusinessUnitId.value) || null
|
|
))
|
|
|
|
const explainBusinessUnitItems = computed<ExplainBusinessUnitSelectItem[]>(() => (
|
|
explainBusinessUnits.value.map((unit) => ({
|
|
id: unit.id,
|
|
name: unit.name,
|
|
hallId: unit.hallId,
|
|
previewImageUrl: unit.previewImageUrl,
|
|
guideStopCount: unit.guideStopCount
|
|
}))
|
|
))
|
|
|
|
const explainGuideStopItems = computed<ExplainGuideStopSelectItem[]>(() => (
|
|
selectedExplainBusinessUnit.value?.stops.map((stop) => ({
|
|
id: stop.id,
|
|
name: stop.name,
|
|
hallId: stop.hallId,
|
|
hallName: stop.hallName,
|
|
floorId: stop.floorId,
|
|
poiId: stop.poiId,
|
|
mapX: stop.mapX,
|
|
mapY: stop.mapY,
|
|
coverImageUrl: stop.coverImageUrl,
|
|
description: stop.description,
|
|
hasAudio: stop.hasAudio,
|
|
audioStatus: stop.audioStatus,
|
|
guideLevel: stop.guideLevel,
|
|
playTargetType: stop.playTargetType,
|
|
playTargetId: stop.playTargetId
|
|
})) || []
|
|
))
|
|
|
|
const loadExplainHalls = async () => {
|
|
if (explainHallItems.value.length) return
|
|
if (explainLoadPromise) return explainLoadPromise
|
|
|
|
explainLoading.value = true
|
|
explainError.value = ''
|
|
|
|
explainLoadPromise = (async () => {
|
|
try {
|
|
const halls = await explainUseCase.loadExplainHalls()
|
|
const summaries = await explainUseCase.loadExplainHallSummaries(halls.map((hall) => hall.id))
|
|
explainHallItems.value = buildExplainHallItems(halls, summaries)
|
|
} catch (error) {
|
|
console.error('加载讲解展厅失败:', error)
|
|
explainError.value = '讲解展厅加载失败,请稍后重试'
|
|
explainHallItems.value = []
|
|
} finally {
|
|
explainLoading.value = false
|
|
explainLoadPromise = null
|
|
}
|
|
})()
|
|
|
|
return explainLoadPromise
|
|
}
|
|
|
|
onLoad(() => {
|
|
void loadExplainHalls()
|
|
})
|
|
|
|
const handleBack = () => {
|
|
uni.navigateBack({
|
|
delta: 1,
|
|
fail: () => {
|
|
uni.reLaunch({
|
|
url: '/pages/index/index?tab=explain'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleTopTabChange = (tab: GuideTopTab) => {
|
|
navigateToGuideTopTab(tab)
|
|
}
|
|
|
|
const handleExplainHallClick = async (hallId: string) => {
|
|
const hall = explainHallItems.value.find((item) => item.id === hallId)
|
|
selectedExplainHallId.value = hallId
|
|
selectedExplainHallName.value = hall?.name || '业务单元'
|
|
selectedExplainBusinessUnitId.value = ''
|
|
selectedExplainBusinessUnitName.value = ''
|
|
explainBusinessUnits.value = []
|
|
explainLoading.value = true
|
|
explainError.value = ''
|
|
|
|
try {
|
|
explainBusinessUnits.value = await explainUseCase.loadTemporaryBusinessUnitsByHall(hallId)
|
|
explainStage.value = 'unit'
|
|
} catch (error) {
|
|
console.error('加载展厅讲解点失败:', error)
|
|
explainError.value = '展厅讲解点加载失败,请稍后重试'
|
|
explainStage.value = 'unit'
|
|
} finally {
|
|
explainLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleExplainBusinessUnitClick = (unitId: string) => {
|
|
const unit = explainBusinessUnits.value.find((item) => item.id === unitId)
|
|
selectedExplainBusinessUnitId.value = unitId
|
|
selectedExplainBusinessUnitName.value = unit?.name || '讲解点'
|
|
explainStage.value = 'stop'
|
|
}
|
|
|
|
const handleExplainGuideStopClick = (stop: ExplainGuideStopSelectItem) => {
|
|
const target = stop.playTargetType && stop.playTargetId
|
|
? {
|
|
targetType: stop.playTargetType,
|
|
targetId: stop.playTargetId
|
|
}
|
|
: normalizeExplainDetailTargetFromGuideStop({ id: stop.id })
|
|
const params = new URLSearchParams({
|
|
id: stop.id,
|
|
tab: 'explain',
|
|
targetType: target.targetType,
|
|
targetId: target.targetId
|
|
})
|
|
uni.navigateTo({
|
|
url: `/pages/exhibit/detail?${params.toString()}`
|
|
})
|
|
}
|
|
|
|
const handlePanelBack = () => {
|
|
if (explainStage.value === 'stop') {
|
|
explainStage.value = 'unit'
|
|
selectedExplainBusinessUnitId.value = ''
|
|
selectedExplainBusinessUnitName.value = ''
|
|
explainError.value = ''
|
|
return
|
|
}
|
|
|
|
if (explainStage.value === 'unit') {
|
|
explainStage.value = 'hall'
|
|
selectedExplainHallId.value = ''
|
|
selectedExplainHallName.value = ''
|
|
selectedExplainBusinessUnitId.value = ''
|
|
selectedExplainBusinessUnitName.value = ''
|
|
explainBusinessUnits.value = []
|
|
explainError.value = ''
|
|
return
|
|
}
|
|
|
|
handleBack()
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.explain-all-page {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: #edf0ea;
|
|
}
|
|
</style>
|