This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user