Some checks failed
CI / verify (push) Has been cancelled
- 接入目录分页接口返回的 externalCode 字段 - 在讲解对象标题下方统一展示编号 - 保留空编号对象的原有布局并补充回归测试 Made-with: Proma
178 lines
6.1 KiB
Vue
178 lines
6.1 KiB
Vue
<template>
|
|
<GuidePageFrame active-tab="explain" variant="static" :show-top-tabs="false" @tab-change="handleTopTabChange" @back="handleBack">
|
|
<view class="explain-guide-stop-page">
|
|
<ExplainGuideStopCatalog
|
|
:guide-stops="explainGuideStopItems"
|
|
:selected-hall-name="selectedExplainHallName"
|
|
:loading="explainLoading"
|
|
:loading-more="loadingMore"
|
|
:has-more="hasMore"
|
|
:error="explainError"
|
|
:load-more-error="loadMoreError"
|
|
@guide-stop-click="handleExplainGuideStopClick"
|
|
@retry="reloadFirstPage"
|
|
@retry-more="loadNextPage"
|
|
@back="handleBack"
|
|
/>
|
|
</view>
|
|
</GuidePageFrame>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onUnmounted, ref } from 'vue'
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
|
|
import ExplainGuideStopCatalog, { type ExplainGuideStopCatalogItem } from '@/components/explain/ExplainGuideStopCatalog.vue'
|
|
import { explainUseCase } from '@/usecases/explainUseCase'
|
|
import type { ExplainGuideStop } from '@/domain/museum'
|
|
import { navigateToGuideTopTab, type GuideTopTab } from '@/utils/guideTopTabs'
|
|
|
|
const PAGE_SIZE = 20
|
|
const selectedExplainHallId = ref('')
|
|
const selectedExplainHallName = ref('')
|
|
const explainGuideStopItems = ref<ExplainGuideStopCatalogItem[]>([])
|
|
const explainLoading = ref(false)
|
|
const loadingMore = ref(false)
|
|
const explainError = ref('')
|
|
const loadMoreError = ref('')
|
|
const hasMore = ref(false)
|
|
const nextPageNo = ref(1)
|
|
let requestVersion = 0
|
|
let disposed = false
|
|
|
|
const toGuideStopItems = (stops: ExplainGuideStop[]): ExplainGuideStopCatalogItem[] => stops.map((stop) => ({
|
|
id: stop.id,
|
|
name: stop.name,
|
|
externalCode: stop.externalCode,
|
|
hallId: stop.hallId,
|
|
hallName: stop.hallName,
|
|
floorId: stop.floorId,
|
|
poiId: stop.poiId,
|
|
mapX: stop.mapX,
|
|
mapY: stop.mapY,
|
|
coverImageUrl: stop.imageStatus === 'MISSING' ? undefined : stop.coverImageUrl,
|
|
description: stop.description,
|
|
hasAudio: stop.hasAudio,
|
|
audioStatus: stop.audioStatus,
|
|
guideLevel: stop.guideLevel
|
|
}))
|
|
|
|
const syncPageTitle = (title: string) => {
|
|
uni.setNavigationBarTitle({ title })
|
|
if (typeof document !== 'undefined') document.title = title
|
|
}
|
|
|
|
const loadPage = async (pageNo: number, replace: boolean) => {
|
|
const hallId = selectedExplainHallId.value
|
|
if (!hallId || disposed || (!replace && (loadingMore.value || !hasMore.value))) return false
|
|
const version = ++requestVersion
|
|
if (replace) {
|
|
explainLoading.value = true
|
|
explainError.value = ''
|
|
loadMoreError.value = ''
|
|
} else {
|
|
loadingMore.value = true
|
|
loadMoreError.value = ''
|
|
}
|
|
try {
|
|
const page = await explainUseCase.listGuideStopsPageByHall(hallId, pageNo, PAGE_SIZE)
|
|
if (disposed || version !== requestVersion || hallId !== selectedExplainHallId.value) return
|
|
const incoming = toGuideStopItems(page.items)
|
|
if (replace) {
|
|
explainGuideStopItems.value = incoming
|
|
} else {
|
|
const existingIds = new Set(explainGuideStopItems.value.map((item) => item.id))
|
|
explainGuideStopItems.value = [...explainGuideStopItems.value, ...incoming.filter((item) => !existingIds.has(item.id))]
|
|
}
|
|
nextPageNo.value = page.pageNo + 1
|
|
hasMore.value = page.hasMore && explainGuideStopItems.value.length < page.total
|
|
return true
|
|
} catch (error) {
|
|
if (disposed || version !== requestVersion) return
|
|
if (replace) explainError.value = '讲解对象加载失败,请稍后重试'
|
|
else loadMoreError.value = '加载更多讲解对象失败,请重试'
|
|
return false
|
|
} finally {
|
|
if (!disposed && version === requestVersion) {
|
|
explainLoading.value = false
|
|
loadingMore.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
const reloadFirstPage = () => {
|
|
if (!selectedExplainHallId.value) return
|
|
nextPageNo.value = 1
|
|
hasMore.value = false
|
|
void loadPage(1, true)
|
|
}
|
|
|
|
const loadNextPage = () => void loadPage(nextPageNo.value, false)
|
|
|
|
onLoad((options: Record<string, string | string[] | undefined> = {}) => {
|
|
const first = (value: string | string[] | undefined) => Array.isArray(value) ? value[0] : value
|
|
selectedExplainHallId.value = first(options.hallId) || ''
|
|
selectedExplainHallName.value = first(options.hallName) || '讲解对象'
|
|
syncPageTitle('讲解对象')
|
|
if (!selectedExplainHallId.value) {
|
|
explainError.value = '缺少展厅参数,请返回展厅列表后重试'
|
|
return
|
|
}
|
|
reloadFirstPage()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
disposed = true
|
|
requestVersion += 1
|
|
})
|
|
|
|
const handleExplainGuideStopClick = (stop: ExplainGuideStopCatalogItem) => {
|
|
const stopId = stop.id
|
|
const params = new URLSearchParams({ id: stopId, stopId, tab: 'explain' })
|
|
// 分页对象可能未回填展厅字段,详情返回必须保留当前列表的展厅上下文。
|
|
const hallId = stop.hallId || selectedExplainHallId.value
|
|
const hallName = stop.hallName || selectedExplainHallName.value
|
|
if (hallId) params.set('hallId', hallId)
|
|
if (hallName) params.set('hallName', hallName)
|
|
if (stop.floorId) params.set('floorId', stop.floorId)
|
|
if (stop.poiId) params.set('poiId', stop.poiId)
|
|
uni.navigateTo({ url: `/pages/exhibit/detail?${params.toString()}` })
|
|
}
|
|
|
|
const fallbackToHallList = () => uni.reLaunch({ url: '/pages/explain/list' })
|
|
|
|
type ExplainPageStackEntry = {
|
|
route?: string
|
|
options?: {
|
|
tab?: string | string[]
|
|
}
|
|
}
|
|
|
|
const returnToHallList = () => {
|
|
const pages = getCurrentPages()
|
|
const previousPage = pages[pages.length - 2] as ExplainPageStackEntry | undefined
|
|
const previousRoute = previousPage?.route
|
|
const previousTab = Array.isArray(previousPage?.options?.tab)
|
|
? previousPage.options.tab[0]
|
|
: previousPage?.options?.tab
|
|
const canNavigateBackToHallList = (
|
|
previousRoute === 'pages/explain/list'
|
|
|| (previousRoute === 'pages/index/index' && previousTab === 'explain')
|
|
)
|
|
|
|
if (canNavigateBackToHallList) {
|
|
uni.navigateBack({ delta: 1, fail: fallbackToHallList })
|
|
return
|
|
}
|
|
|
|
fallbackToHallList()
|
|
}
|
|
|
|
const handleBack = returnToHallList
|
|
const handleTopTabChange = (tab: GuideTopTab) => navigateToGuideTopTab(tab)
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.explain-guide-stop-page { position: absolute; inset: 0; background: #edf0ea; }
|
|
</style>
|