This commit is contained in:
@@ -9,13 +9,26 @@
|
||||
</view>
|
||||
|
||||
<scroll-view class="explain-scroll" scroll-y :show-scrollbar="false" @scroll="handleScroll" @scrolltolower="requestMore">
|
||||
<view v-if="loading" class="state-block"><text class="state-title">正在加载讲解对象</text><text class="state-desc">稍后将展示该展厅的讲解对象。</text></view>
|
||||
<GuideLoadingState v-if="loading" title="正在加载讲解对象" description="请稍候" />
|
||||
<view v-else-if="error" class="state-block error"><text class="state-title">讲解对象加载失败</text><text class="state-desc">{{ error }}</text><button class="state-retry" @tap="emit('retry')">重新加载</button></view>
|
||||
<template v-else>
|
||||
<view v-if="guideStops.length" class="stop-list">
|
||||
<view v-for="stop in guideStops" :key="stop.id" class="stop-card" @tap="emit('guideStopClick', stop)">
|
||||
<image v-if="stop.coverImageUrl" class="stop-cover" :src="stop.coverImageUrl" mode="aspectFit" />
|
||||
<view v-else class="stop-cover placeholder" />
|
||||
<image
|
||||
v-if="shouldLoadCover(stop)"
|
||||
class="stop-cover"
|
||||
:src="stop.coverImageUrl"
|
||||
mode="aspectFit"
|
||||
@error="handleImageError(stop.id)"
|
||||
/>
|
||||
<view
|
||||
v-else-if="stop.coverImageUrl && !failedImageIds.has(stop.id)"
|
||||
:ref="(element) => registerCoverSlot(stop.id, element)"
|
||||
class="stop-cover loading"
|
||||
/>
|
||||
<view v-else class="stop-cover placeholder">
|
||||
<text class="stop-cover-fallback">{{ stop.name.slice(0, 1) }}</text>
|
||||
</view>
|
||||
<view class="stop-copy">
|
||||
<text class="stop-name">{{ stop.name }}</text>
|
||||
</view>
|
||||
@@ -34,8 +47,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||
import GuideLoadingState from '@/components/navigation/GuideLoadingState.vue'
|
||||
|
||||
export interface ExplainGuideStopCatalogItem {
|
||||
id: string
|
||||
@@ -67,9 +81,70 @@ const props = withDefaults(defineProps<{
|
||||
const emit = defineEmits<{ guideStopClick: [stop: ExplainGuideStopCatalogItem], back: [], retry: [], retryMore: [] }>()
|
||||
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||
const showInternalHeader = computed(() => !shouldUseHostNavigation.value)
|
||||
const failedImageIds = ref(new Set<string>())
|
||||
const visibleCoverIds = ref(new Set<string>())
|
||||
const coverSlots = new Map<string, Element>()
|
||||
const supportsIntersectionObserver = typeof IntersectionObserver !== 'undefined'
|
||||
let coverObserver: IntersectionObserver | null = null
|
||||
|
||||
const shouldLoadCover = (stop: ExplainGuideStopCatalogItem) => Boolean(
|
||||
stop.coverImageUrl
|
||||
&& !failedImageIds.value.has(stop.id)
|
||||
&& (!supportsIntersectionObserver || visibleCoverIds.value.has(stop.id))
|
||||
)
|
||||
|
||||
const resolveDomElement = (element: unknown): Element | null => {
|
||||
if (element instanceof Element) return element
|
||||
if (element && typeof element === 'object' && '$el' in element) {
|
||||
const componentElement = (element as { $el?: unknown }).$el
|
||||
if (componentElement instanceof Element) return componentElement
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const registerCoverSlot = (stopId: string, instance: unknown) => {
|
||||
const element = resolveDomElement(instance)
|
||||
if (!element) {
|
||||
coverSlots.delete(stopId)
|
||||
return
|
||||
}
|
||||
|
||||
coverSlots.set(stopId, element)
|
||||
coverObserver?.observe(element)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!supportsIntersectionObserver) return
|
||||
|
||||
coverObserver = new IntersectionObserver((entries) => {
|
||||
const nextVisibleIds = new Set(visibleCoverIds.value)
|
||||
let changed = false
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return
|
||||
const stopId = [...coverSlots.entries()].find(([, element]) => element === entry.target)?.[0]
|
||||
if (!stopId || nextVisibleIds.has(stopId)) return
|
||||
nextVisibleIds.add(stopId)
|
||||
coverObserver?.unobserve(entry.target)
|
||||
changed = true
|
||||
})
|
||||
if (changed) visibleCoverIds.value = nextVisibleIds
|
||||
}, { rootMargin: '160px 0px' })
|
||||
|
||||
coverSlots.forEach((element) => coverObserver?.observe(element))
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
coverObserver?.disconnect()
|
||||
coverObserver = null
|
||||
coverSlots.clear()
|
||||
})
|
||||
|
||||
const handleImageError = (stopId: string) => {
|
||||
failedImageIds.value = new Set([...failedImageIds.value, stopId])
|
||||
}
|
||||
|
||||
const availabilityLabel = (stop: ExplainGuideStopCatalogItem) => (
|
||||
stop.audioStatus === 'READY' || stop.hasAudio ? '音频' : stop.hasTextRecord ? '图文' : '暂无内容'
|
||||
stop.audioStatus === 'READY' || stop.hasAudio ? '讲解' : stop.hasTextRecord ? '图文' : '暂无内容'
|
||||
)
|
||||
const requestMore = () => { if (props.hasMore && !props.loading && !props.loadingMore && !props.loadMoreError) emit('retryMore') }
|
||||
const handleScroll = (event: Event) => { const target = event.target as HTMLElement | null; if (target && target.scrollTop + target.clientHeight >= target.scrollHeight - 8) requestMore() }
|
||||
@@ -81,6 +156,6 @@ const handleScroll = (event: Event) => { const target = event.target as HTMLElem
|
||||
.header-back { position: absolute; left: 12px; top: 10px; width: 44px; height: 44px; display: flex; align-items: center; }.header-back-icon { width: 20px; height: 20px; position: relative; } .header-back-icon::before { content: ''; position: absolute; left: 3px; top: 4px; width: 10px; height: 10px; border-left: 1.5px solid #262421; border-bottom: 1.5px solid #262421; transform: rotate(45deg); }
|
||||
.header-back-text { display: none; }.header-title { max-width: calc(100% - 152px); padding: 0 8px; font-size: 18px; line-height: 26px; font-weight: 700; color: #262421; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.explain-scroll { height: 100%; padding: 80px 16px calc(16px + env(safe-area-inset-bottom)); box-sizing: border-box; background: #f7f9f2; }.host-navigation .explain-scroll { padding-top: 16px; }
|
||||
.stop-list { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 4px; }.stop-card { width: 100%; min-width: 0; height: 80px; padding: 8px 0; display: flex; align-items: center; gap: 12px; box-sizing: border-box; overflow: hidden; border: 0; border-bottom: 1px solid #dbded4; border-radius: 0; background: transparent; }.stop-card:active { background: rgba(227, 235, 201, 0.4); }.stop-cover { flex: 0 0 64px; width: 64px; height: 64px; overflow: hidden; border-radius: 6px; background: #f5f5ed; }.stop-copy { min-width: 0; display: flex; flex: 1; overflow: hidden; }.stop-name { display: -webkit-box; max-height: 40px; font-size: 14px; line-height: 20px; font-weight: 700; color: #262421; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }.stop-status { flex: 0 0 44px; width: 44px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: #e3ebc9; }.stop-status-text { font-size: 11px; line-height: 16px; color: #262421; white-space: nowrap; }
|
||||
.stop-list { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 4px; }.stop-card { width: 100%; min-width: 0; height: 80px; padding: 8px 0; display: flex; align-items: center; gap: 12px; box-sizing: border-box; overflow: hidden; border: 0; border-bottom: 1px solid #dbded4; border-radius: 0; background: transparent; }.stop-card:active { background: rgba(227, 235, 201, 0.4); }.stop-cover { flex: 0 0 64px; width: 64px; height: 64px; overflow: hidden; border-radius: 6px; background: #f5f5ed; }.stop-cover-fallback { font-size: 22px; line-height: 28px; color: #a9b29b; }.stop-copy { min-width: 0; display: flex; flex: 1; overflow: hidden; }.stop-name { display: -webkit-box; max-height: 40px; font-size: 14px; line-height: 20px; font-weight: 700; color: #262421; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }.stop-status { flex: 0 0 44px; width: 44px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: #e3ebc9; }.stop-status-text { font-size: 11px; line-height: 16px; color: #262421; white-space: nowrap; }
|
||||
.state-block { margin: 34px 0; padding: 22px 16px; box-sizing: border-box; text-align: center; border: 1px solid #e4e6df; border-radius: 8px; background: #fff; }.state-block.error { border-color: #e5c2c2; background: #fff7f7; }.state-title,.state-desc { display: block; }.state-title { font-size: 15px; line-height: 21px; font-weight: 700; color: #141412; }.state-desc { margin-top: 6px; font-size: 12px; line-height: 18px; color: #68725d; }.state-retry { margin-top: 12px; padding: 0 14px; font-size: 13px; line-height: 32px; color: #141412; background: #e0df00; border: 0; border-radius: 6px; }.load-more-state { min-height: 52px; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); text-align: center; }
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
class="explain-scroll"
|
||||
:class="{
|
||||
'hall-stage': stage === 'hall',
|
||||
'unit-stage': stage === 'unit',
|
||||
'stop-stage': stage === 'stop'
|
||||
}"
|
||||
scroll-y
|
||||
@@ -19,10 +20,11 @@
|
||||
@scrolltolower="handleScrollToLower"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view v-if="loading" class="state-block">
|
||||
<text class="state-title">{{ loadingTitle }}</text>
|
||||
<text class="state-desc">{{ loadingDescription }}</text>
|
||||
</view>
|
||||
<GuideLoadingState
|
||||
v-if="loading"
|
||||
:title="loadingTitle"
|
||||
:description="loadingDescription"
|
||||
/>
|
||||
|
||||
<view v-else-if="error" class="state-block error">
|
||||
<text class="state-title">讲解对象加载失败</text>
|
||||
@@ -39,35 +41,57 @@
|
||||
:style="{ '--hall-card-color': hallCardTheme(hall).color }"
|
||||
@tap="handleHallClick(hall.id)"
|
||||
>
|
||||
<view
|
||||
v-if="hallPreviewUrl(hall)"
|
||||
class="hall-card-art hall-thumb-shell"
|
||||
:class="{
|
||||
'is-image-loaded': isHallPreviewLoaded(hall.id),
|
||||
'is-image-error': isHallPreviewError(hall.id)
|
||||
}"
|
||||
>
|
||||
<view class="hall-thumb-fallback">
|
||||
<text class="hall-thumb-text">{{ hallIconText(hall.name) }}</text>
|
||||
<view
|
||||
v-if="isCustomHallCard(hall)"
|
||||
class="hall-full-card-art"
|
||||
:class="{
|
||||
'is-image-loaded': isHallPreviewLoaded(hall.id),
|
||||
'is-image-error': isHallPreviewError(hall.id)
|
||||
}"
|
||||
>
|
||||
<image
|
||||
v-if="!isHallPreviewError(hall.id)"
|
||||
class="hall-full-card-image"
|
||||
:src="hallPreviewUrl(hall)"
|
||||
mode="aspectFill"
|
||||
:lazy-load="isHallPreviewLazy(index)"
|
||||
@load="handleHallPreviewLoad(hall.id)"
|
||||
@error="handleHallPreviewError(hall.id)"
|
||||
/>
|
||||
<view v-else class="hall-full-card-fallback">
|
||||
<text class="hall-name">{{ hall.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<image
|
||||
v-if="!isHallPreviewError(hall.id)"
|
||||
class="hall-card-art-image"
|
||||
:src="hallPreviewUrl(hall)"
|
||||
mode="aspectFit"
|
||||
:lazy-load="isHallPreviewLazy(index)"
|
||||
@load="handleHallPreviewLoad(hall.id)"
|
||||
@error="handleHallPreviewError(hall.id)"
|
||||
/>
|
||||
<template v-else>
|
||||
<view
|
||||
v-if="hallPreviewUrl(hall)"
|
||||
class="hall-card-art hall-thumb-shell"
|
||||
:class="{
|
||||
'is-image-loaded': isHallPreviewLoaded(hall.id),
|
||||
'is-image-error': isHallPreviewError(hall.id)
|
||||
}"
|
||||
>
|
||||
<view class="hall-thumb-fallback">
|
||||
<text class="hall-thumb-text">{{ hallIconText(hall.name) }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="!isHallPreviewError(hall.id)"
|
||||
class="hall-card-art-image"
|
||||
:src="hallPreviewUrl(hall)"
|
||||
mode="aspectFit"
|
||||
:lazy-load="isHallPreviewLazy(index)"
|
||||
@load="handleHallPreviewLoad(hall.id)"
|
||||
@error="handleHallPreviewError(hall.id)"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="hall-card-art placeholder">
|
||||
<text class="hall-thumb-text">{{ hallIconText(hall.name) }}</text>
|
||||
</view>
|
||||
<view class="hall-overview-copy">
|
||||
<text class="hall-name">{{ hall.name }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view v-else class="hall-card-art placeholder">
|
||||
<text class="hall-thumb-text">{{ hallIconText(hall.name) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="hall-overview-copy">
|
||||
<text class="hall-name">{{ hall.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="stage === 'stop' && guideStops.length" class="hall-list">
|
||||
@@ -106,6 +130,34 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="stage === 'unit' && businessUnits.length" class="hall-list">
|
||||
<view
|
||||
v-for="unit in businessUnits"
|
||||
:key="unit.id"
|
||||
class="hall-card business-unit-card"
|
||||
@tap="handleBusinessUnitClick(unit.id)"
|
||||
>
|
||||
<image
|
||||
v-if="unit.previewImageUrl"
|
||||
class="hall-thumb"
|
||||
:src="unit.previewImageUrl"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-else class="hall-thumb placeholder">
|
||||
<text class="hall-thumb-text">{{ hallIconText(unit.name) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="hall-main">
|
||||
<text class="hall-name">{{ unit.name }}</text>
|
||||
<view class="hall-meta-row">
|
||||
<text class="hall-meta-text">{{ unit.guideStopCount }} 个讲解点</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="hall-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="state-block empty">
|
||||
<text class="state-title">{{ emptyTitle }}</text>
|
||||
<text class="state-desc">{{ emptyDescription }}</text>
|
||||
@@ -126,6 +178,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||
import GuideLoadingState from '@/components/navigation/GuideLoadingState.vue'
|
||||
|
||||
export interface ExplainHallSelectItem {
|
||||
id: string
|
||||
@@ -155,10 +208,19 @@ export interface ExplainGuideStopSelectItem {
|
||||
playTargetId?: string
|
||||
}
|
||||
|
||||
export type ExplainSelectStage = 'hall' | 'stop'
|
||||
export interface ExplainBusinessUnitSelectItem {
|
||||
id: string
|
||||
name: string
|
||||
hallId?: string
|
||||
previewImageUrl?: string
|
||||
guideStopCount: number
|
||||
}
|
||||
|
||||
export type ExplainSelectStage = 'hall' | 'unit' | 'stop'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
halls?: ExplainHallSelectItem[]
|
||||
businessUnits?: ExplainBusinessUnitSelectItem[]
|
||||
guideStops?: ExplainGuideStopSelectItem[]
|
||||
stage?: ExplainSelectStage
|
||||
selectedHallName?: string
|
||||
@@ -170,6 +232,7 @@ const props = withDefaults(defineProps<{
|
||||
forceInternalHeader?: boolean
|
||||
}>(), {
|
||||
halls: () => [],
|
||||
businessUnits: () => [],
|
||||
guideStops: () => [],
|
||||
stage: 'hall',
|
||||
selectedHallName: '',
|
||||
@@ -183,27 +246,28 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
hallClick: [hallId: string]
|
||||
businessUnitClick: [unitId: string]
|
||||
guideStopClick: [stop: ExplainGuideStopSelectItem]
|
||||
back: []
|
||||
retry: []
|
||||
retryMore: []
|
||||
}>()
|
||||
|
||||
const HALL_PREVIEW_BASE = '/static/icons/halls'
|
||||
const HALL_PREVIEW_BASE = '/static/icons/halls/custom-cards'
|
||||
const HALL_PREVIEW_EAGER_COUNT = 4
|
||||
const loadedHallPreviewIds = ref(new Set<string>())
|
||||
const failedHallPreviewIds = ref(new Set<string>())
|
||||
|
||||
const hallPreviewMap: Record<string, string> = {
|
||||
宇宙厅: `${HALL_PREVIEW_BASE}/normalized/universe.png`,
|
||||
地球厅: `${HALL_PREVIEW_BASE}/normalized/earth.png`,
|
||||
演化厅: `${HALL_PREVIEW_BASE}/normalized/evolution.png`,
|
||||
恐龙厅: `${HALL_PREVIEW_BASE}/normalized/dinosaur.png`,
|
||||
人类厅: `${HALL_PREVIEW_BASE}/normalized/human.png`,
|
||||
动物厅: `${HALL_PREVIEW_BASE}/normalized/biology.png`,
|
||||
生物厅: `${HALL_PREVIEW_BASE}/normalized/biology.png`,
|
||||
生态厅: `${HALL_PREVIEW_BASE}/normalized/ecology.png`,
|
||||
家园厅: `${HALL_PREVIEW_BASE}/normalized/homeland.png`
|
||||
宇宙厅: `${HALL_PREVIEW_BASE}/universe.png`,
|
||||
地球厅: `${HALL_PREVIEW_BASE}/earth.png`,
|
||||
演化厅: `${HALL_PREVIEW_BASE}/evolution.png`,
|
||||
恐龙厅: `${HALL_PREVIEW_BASE}/dinosaur.png`,
|
||||
人类厅: `${HALL_PREVIEW_BASE}/human.png`,
|
||||
动物厅: `${HALL_PREVIEW_BASE}/biology.png`,
|
||||
生物厅: `${HALL_PREVIEW_BASE}/biology.png`,
|
||||
生态厅: `${HALL_PREVIEW_BASE}/ecology.png`,
|
||||
家园厅: `${HALL_PREVIEW_BASE}/homeland.png`
|
||||
}
|
||||
|
||||
const hallCardThemeMap: Record<string, { color: string; englishName: string }> = {
|
||||
@@ -223,29 +287,36 @@ const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||
const showInternalHeader = computed(() => props.forceInternalHeader || !shouldUseHostNavigation.value)
|
||||
const headerTitle = computed(() => {
|
||||
if (props.stage === 'stop') return props.selectedHallName || '讲解对象'
|
||||
if (props.stage === 'unit') return props.selectedHallName || '讲解单元'
|
||||
return '免费讲解'
|
||||
})
|
||||
const loadingTitle = computed(() => {
|
||||
if (props.stage === 'stop') return '正在加载讲解对象'
|
||||
return '正在加载展厅讲解'
|
||||
if (props.stage === 'unit') return '正在加载讲解单元'
|
||||
return '正在加载免费讲解'
|
||||
})
|
||||
const loadingDescription = computed(() => {
|
||||
if (props.stage === 'stop') return '稍后将展示该展厅的讲解对象。'
|
||||
if (props.stage === 'unit') return '稍后将展示该展厅的讲解单元。'
|
||||
return '稍后将展示可选择的展厅。'
|
||||
})
|
||||
const emptyTitle = computed(() => {
|
||||
if (props.stage === 'stop') return '该展厅暂无讲解对象'
|
||||
if (props.stage === 'unit') return '该展厅暂无讲解单元'
|
||||
return '未找到相关展厅'
|
||||
})
|
||||
const emptyDescription = computed(() => {
|
||||
if (props.stage === 'stop') return '可返回选择其他展厅。'
|
||||
return '暂无可选择的展厅讲解。'
|
||||
if (props.stage === 'unit') return '可返回选择其他展厅。'
|
||||
return '暂无可选择的免费讲解。'
|
||||
})
|
||||
|
||||
const hallPreviewUrl = (hall: ExplainHallSelectItem) => {
|
||||
return hallPreviewMap[hall.name.trim()] || hall.image || ''
|
||||
}
|
||||
|
||||
const isCustomHallCard = (hall: ExplainHallSelectItem) => Boolean(hallPreviewMap[hall.name.trim()])
|
||||
|
||||
const hallIconText = (name: string) => name.trim().slice(0, 1) || '讲'
|
||||
|
||||
const isHallPreviewLoaded = (hallId: string) => loadedHallPreviewIds.value.has(hallId)
|
||||
@@ -280,6 +351,10 @@ const handleHallClick = (hallId: string) => {
|
||||
emit('hallClick', hallId)
|
||||
}
|
||||
|
||||
const handleBusinessUnitClick = (unitId: string) => {
|
||||
emit('businessUnitClick', unitId)
|
||||
}
|
||||
|
||||
const handleGuideStopClick = (stopId: string) => {
|
||||
const stop = props.guideStops.find((item) => item.id === stopId)
|
||||
if (stop) {
|
||||
@@ -831,11 +906,13 @@ const handleBack = () => {
|
||||
}
|
||||
|
||||
.hall-overview-list {
|
||||
grid-auto-rows: 138px;
|
||||
// Keep the eight-hall overview balanced on tall phones while preserving a
|
||||
// fixed minimum row height for compact screens.
|
||||
grid-auto-rows: max(138px, calc((100vh - 148px) / 4));
|
||||
}
|
||||
|
||||
.hall-overview-card {
|
||||
min-height: 138px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.hall-overview-card .hall-card-art {
|
||||
@@ -850,6 +927,28 @@ const handleBack = () => {
|
||||
filter: grayscale(1) brightness(0.72) contrast(1.65);
|
||||
}
|
||||
|
||||
.hall-full-card-art {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: var(--hall-card-color);
|
||||
}
|
||||
|
||||
.hall-full-card-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hall-full-card-fallback {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hall-overview-copy {
|
||||
top: 24px;
|
||||
}
|
||||
|
||||
@@ -49,10 +49,11 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="state-block">
|
||||
<text class="state-title">正在加载讲解内容</text>
|
||||
<text class="state-desc">稍后将展示当前楼层的场景空间与展品讲解。</text>
|
||||
</view>
|
||||
<GuideLoadingState
|
||||
v-if="loading"
|
||||
title="正在加载讲解内容"
|
||||
description="请稍候"
|
||||
/>
|
||||
|
||||
<view v-else-if="error" class="state-block error">
|
||||
<text class="state-title">讲解内容加载失败</text>
|
||||
@@ -232,6 +233,7 @@ import { computed, ref } from 'vue'
|
||||
import type {
|
||||
ExplainCardViewModel
|
||||
} from '@/view-models/explainViewModels'
|
||||
import GuideLoadingState from '@/components/navigation/GuideLoadingState.vue'
|
||||
|
||||
export interface ExplainFilterOption {
|
||||
id: 'all' | 'hall' | 'theme' | 'nearby' | 'playable'
|
||||
|
||||
Reference in New Issue
Block a user