refactor: split explain hall entry from home tab

This commit is contained in:
lyf
2026-07-10 14:01:37 +08:00
parent ac81574207
commit 857fb296c6
2 changed files with 16 additions and 321 deletions

View File

@@ -11,40 +11,28 @@
<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"
stage="hall"
:loading="explainLoading"
:error="explainError"
@hall-click="handleExplainHallClick"
@business-unit-click="handleExplainBusinessUnitClick"
@guide-stop-click="handleExplainGuideStopClick"
@back="handlePanelBack"
@back="handleBack"
/>
</view>
</GuidePageFrame>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { computed, ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import ExplainHallSelect, {
type ExplainBusinessUnitSelectItem,
type ExplainGuideStopSelectItem,
type ExplainHallSelectItem,
type ExplainSelectStage
type ExplainHallSelectItem
} from '@/components/explain/ExplainHallSelect.vue'
import {
explainUseCase
} from '@/usecases/explainUseCase'
import {
normalizeExplainDetailTargetFromGuideStop
} from '@/domain/explainDetailTarget'
import type {
ExplainBusinessUnit,
MuseumHall
} from '@/domain/museum'
import {
@@ -54,12 +42,6 @@ import {
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
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
@@ -87,83 +69,21 @@ const buildExplainHallItems = (
}))
)
const selectedExplainBusinessUnit = computed(() => (
explainBusinessUnits.value.find((unit) => unit.id === selectedExplainBusinessUnitId.value) || null
))
const explainPageTitle = computed(() => {
if (explainStage.value === 'unit') return selectedExplainHallName.value || '选择业务单元'
if (explainStage.value === 'stop') return selectedExplainBusinessUnitName.value || '选择讲解点'
return '讲解'
})
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
const syncHostNavigationTitle = () => {
const title = explainPageTitle.value
if (typeof uni !== 'undefined') {
uni.setNavigationBarTitle({ title })
}
if (typeof document !== 'undefined') {
document.title = title
}
}
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
})) || []
))
type ExplainHistoryState = {
museumExplainList?: boolean
stage?: ExplainSelectStage
hallId?: string
hallName?: string
unitId?: string
unitName?: string
}
let syncingExplainHistory = false
let applyingExplainHistory = false
const stageRank: Record<ExplainSelectStage, number> = {
hall: 0,
unit: 1,
stop: 2
}
const currentExplainHistoryState = (): ExplainHistoryState => ({
museumExplainList: true,
stage: explainStage.value,
hallId: selectedExplainHallId.value,
hallName: selectedExplainHallName.value,
unitId: selectedExplainBusinessUnitId.value,
unitName: selectedExplainBusinessUnitName.value
museumExplainList: true
})
const writeExplainHistoryState = (replace = false) => {
@@ -188,48 +108,6 @@ const applyExplainHistoryState = async (state: ExplainHistoryState | null) => {
if (!state?.museumExplainList || applyingExplainHistory) return
applyingExplainHistory = true
const targetStage = state.stage || 'hall'
if (targetStage === 'hall') {
explainStage.value = 'hall'
selectedExplainHallId.value = ''
selectedExplainHallName.value = ''
selectedExplainBusinessUnitId.value = ''
selectedExplainBusinessUnitName.value = ''
explainBusinessUnits.value = []
explainError.value = ''
applyingExplainHistory = false
return
}
if (state.hallId) {
selectedExplainHallId.value = state.hallId
selectedExplainHallName.value = state.hallName || '业务单元'
selectedExplainBusinessUnitId.value = ''
selectedExplainBusinessUnitName.value = ''
explainStage.value = 'unit'
if (!explainBusinessUnits.value.length || explainBusinessUnits.value[0]?.hallId !== state.hallId) {
explainLoading.value = true
explainError.value = ''
try {
explainBusinessUnits.value = await explainUseCase.loadTemporaryBusinessUnitsByHall(state.hallId)
} catch (error) {
console.error('恢复展厅讲解点失败:', error)
explainError.value = '展厅讲解点加载失败,请稍后重试'
explainBusinessUnits.value = []
} finally {
explainLoading.value = false
}
}
}
if (targetStage === 'stop' && state.unitId) {
selectedExplainBusinessUnitId.value = state.unitId
selectedExplainBusinessUnitName.value = state.unitName || '讲解点'
explainStage.value = 'stop'
}
applyingExplainHistory = false
}
@@ -238,26 +116,10 @@ const handleExplainHistoryPopState = (event: PopStateEvent) => {
void applyExplainHistoryState((event.state || null) as ExplainHistoryState | null)
}
const pushExplainStageHistory = (previousStage: ExplainSelectStage) => {
if (!shouldUseHostNavigation.value || applyingExplainHistory) return
if (stageRank[explainStage.value] <= stageRank[previousStage]) return
writeExplainHistoryState()
}
const replaceExplainStageHistory = () => {
writeExplainHistoryState(true)
}
watch(explainPageTitle, syncHostNavigationTitle)
watch(shouldUseHostNavigation, (value) => {
if (value) {
syncHostNavigationTitle()
replaceExplainStageHistory()
}
})
const loadExplainHalls = async () => {
if (explainHallItems.value.length) return
if (explainLoadPromise) return explainLoadPromise
@@ -284,7 +146,6 @@ const loadExplainHalls = async () => {
}
onLoad(() => {
syncHostNavigationTitle()
replaceExplainStageHistory()
void loadExplainHalls()
@@ -319,82 +180,18 @@ const handleTopTabChange = (tab: GuideTopTab) => {
navigateToGuideTopTab(tab)
}
const handleExplainHallClick = async (hallId: string) => {
const previousStage = explainStage.value
const handleExplainHallClick = (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'
pushExplainStageHistory(previousStage)
} catch (error) {
console.error('加载展厅讲解点失败:', error)
explainError.value = '展厅讲解点加载失败,请稍后重试'
explainStage.value = 'unit'
pushExplainStageHistory(previousStage)
} finally {
explainLoading.value = false
}
}
const handleExplainBusinessUnitClick = (unitId: string) => {
const previousStage = explainStage.value
const unit = explainBusinessUnits.value.find((item) => item.id === unitId)
selectedExplainBusinessUnitId.value = unitId
selectedExplainBusinessUnitName.value = unit?.name || '讲解点'
explainStage.value = 'stop'
pushExplainStageHistory(previousStage)
}
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
hallId,
hallName: hall?.name || '讲解'
})
uni.navigateTo({
url: `/pages/exhibit/detail?${params.toString()}`
url: `/pages/explain/business-unit-list?${params.toString()}`
})
}
const handlePanelBack = () => {
if (explainStage.value === 'stop') {
explainStage.value = 'unit'
selectedExplainBusinessUnitId.value = ''
selectedExplainBusinessUnitName.value = ''
explainError.value = ''
replaceExplainStageHistory()
return
}
if (explainStage.value === 'unit') {
explainStage.value = 'hall'
selectedExplainHallId.value = ''
selectedExplainHallName.value = ''
selectedExplainBusinessUnitId.value = ''
selectedExplainBusinessUnitName.value = ''
explainBusinessUnits.value = []
explainError.value = ''
replaceExplainStageHistory()
return
}
handleBack()
}
</script>
<style scoped lang="scss">

View File

@@ -247,16 +247,10 @@
<view v-else-if="currentTab === 'explain'" class="explain-page">
<ExplainHallSelect
:halls="explainHallItems"
:business-units="explainBusinessUnitItems"
:guide-stops="explainGuideStopItems"
:stage="explainStage"
:selected-hall-name="selectedExplainHallName"
:selected-business-unit-name="selectedExplainBusinessUnitName"
stage="hall"
:loading="explainLoading"
:error="explainError"
@hall-click="handleExplainHallClick"
@business-unit-click="handleExplainBusinessUnitClick"
@guide-stop-click="handleExplainGuideStopClick"
@back="handleExplainBack"
/>
</view>
@@ -317,9 +311,6 @@ import type {
RoutePointOption
} from '@/components/navigation/RoutePointPicker.vue'
import ExplainHallSelect, {
type ExplainBusinessUnitSelectItem,
type ExplainGuideStopSelectItem,
type ExplainSelectStage,
type ExplainHallSelectItem
} from '@/components/explain/ExplainHallSelect.vue'
import {
@@ -350,7 +341,6 @@ import {
import type {
GuideRouteResult,
GuideRouteTarget,
ExplainBusinessUnit,
MuseumFloor,
MuseumHall
} from '@/domain/museum'
@@ -749,48 +739,10 @@ const routeSimulationStepText = computed(() => {
})
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 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
})) || []
))
// Tab 切换处理
const syncTopTabToUrl = (tabId: GuideTopTab) => {
if (typeof window === 'undefined') return
@@ -1456,71 +1408,17 @@ const guideSearchText = computed(() => {
// 讲解页面处理
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 = []
explainError.value = ''
explainLoading.value = true
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
hallId,
hallName: hall?.name || '讲解'
})
uni.navigateTo({
url: `/pages/exhibit/detail?${params.toString()}`
url: `/pages/explain/business-unit-list?${params.toString()}`
})
}
const handleExplainBack = () => {
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
}
currentTab.value = 'guide'
syncTopTabToUrl('guide')
loadTopTabData('guide')