chore: freeze guide and explain update
This commit is contained in:
@@ -1,38 +1,30 @@
|
||||
<template>
|
||||
<view class="explain-list">
|
||||
<view class="spatial-stage">
|
||||
<view class="stage-copy">
|
||||
<text class="stage-kicker">SGS 场景讲解</text>
|
||||
<text class="stage-title">{{ activeFloorLabel }} · {{ filteredCards.length }} 个讲解内容</text>
|
||||
<text class="stage-desc">
|
||||
{{ hallSummaries.length }} 个场景空间 · {{ unavailableAudioCount }} 个音频待同步
|
||||
</text>
|
||||
</view>
|
||||
<view class="stage-map">
|
||||
<view class="stage-floor">
|
||||
<text class="stage-floor-text">{{ activeFloorLabel }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="(marker, index) in previewMarkers"
|
||||
:key="marker.id"
|
||||
class="stage-marker"
|
||||
:class="`marker-${index + 1}`"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-panel">
|
||||
<view class="panel-handle"></view>
|
||||
|
||||
<view class="panel-summary">
|
||||
<view class="summary-main">
|
||||
<text class="summary-title">当前楼层 {{ activeFloorLabel }}</text>
|
||||
<text class="summary-subtitle">
|
||||
{{ filteredCards.length }} 个讲解内容 · {{ unavailableAudioCount }} 个音频待同步
|
||||
<scroll-view
|
||||
class="explain-scroll"
|
||||
:scroll-y="true"
|
||||
:show-scrollbar="false"
|
||||
:lower-threshold="120"
|
||||
@scrolltolower="handleScrollToLower"
|
||||
>
|
||||
<view class="explain-hero">
|
||||
<view class="hero-copy">
|
||||
<text class="hero-kicker">{{ isAllListMode ? '全部讲解' : '馆内讲解' }}</text>
|
||||
<text class="hero-title">{{ heroTitle }}</text>
|
||||
<text class="hero-desc">
|
||||
{{ heroDescription }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="summary-chip">
|
||||
<text class="summary-chip-text">位置预览</text>
|
||||
<view class="hero-meta-row">
|
||||
<view v-if="showBrowseAll && !isAllListMode" class="hero-browse-all" @tap="handleBrowseAll">
|
||||
<text class="hero-browse-all-text">全部</text>
|
||||
</view>
|
||||
<view class="hero-chip">
|
||||
<text class="hero-chip-text">位置预览</text>
|
||||
</view>
|
||||
<view class="hero-chip muted">
|
||||
<text class="hero-chip-text">{{ textOnlyCount }} 个图文讲解</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -57,19 +49,18 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="panel-scroll" :scroll-y="true" :show-scrollbar="false">
|
||||
<view v-if="loading" class="state-block">
|
||||
<text class="state-title">正在加载讲解内容</text>
|
||||
<text class="state-desc">稍后将展示当前楼层的场景空间与展品讲解。</text>
|
||||
</view>
|
||||
<view v-if="loading" class="state-block">
|
||||
<text class="state-title">正在加载讲解内容</text>
|
||||
<text class="state-desc">稍后将展示当前楼层的场景空间与展品讲解。</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="error" class="state-block error">
|
||||
<text class="state-title">讲解内容加载失败</text>
|
||||
<text class="state-desc">{{ error }}</text>
|
||||
</view>
|
||||
<view v-else-if="error" class="state-block error">
|
||||
<text class="state-title">讲解内容加载失败</text>
|
||||
<text class="state-desc">{{ error }}</text>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view v-if="featuredCard" class="featured-section">
|
||||
<template v-else>
|
||||
<view v-if="featuredCard && !isAllListMode" class="featured-section">
|
||||
<view class="section-heading">
|
||||
<text class="section-title">今日推荐讲解</text>
|
||||
<text class="section-count">{{ featuredCard.audioStatusText }}</text>
|
||||
@@ -87,7 +78,7 @@
|
||||
<view class="featured-body">
|
||||
<text class="featured-title">{{ featuredCard.title }}</text>
|
||||
<text class="featured-meta">{{ cardMeta(featuredCard) }}</text>
|
||||
<text class="featured-summary">{{ featuredCard.summary || '讲解文案来自 SGS 场景设置数据,正式 CMS 接入后可继续替换。' }}</text>
|
||||
<text class="featured-summary">{{ featuredCard.summary || '该讲解暂无文稿。' }}</text>
|
||||
<view class="featured-actions">
|
||||
<view
|
||||
v-if="featuredCard.audioStatus === 'playable'"
|
||||
@@ -100,11 +91,11 @@
|
||||
<text class="action-text disabled">{{ featuredCard.audioStatusText }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="featuredCard.poiId"
|
||||
v-if="featuredCard.poiId && featuredCard.locationActionText"
|
||||
class="action-btn secondary"
|
||||
@tap.stop="handleLocationClick(featuredCard)"
|
||||
>
|
||||
<text class="action-text">查看位置</text>
|
||||
<text class="action-text">{{ featuredCard.locationActionText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -124,28 +115,38 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="hallSummaries.length" class="hall-section">
|
||||
<view v-if="hallSummaries.length && (!isAllListMode || activeFilter === 'hall')" class="hall-section">
|
||||
<view class="section-heading">
|
||||
<text class="section-title">场景空间</text>
|
||||
<text class="section-count">{{ hallSummaries.length }} 个空间</text>
|
||||
<text class="section-count">{{ hallSummaries.length }} 个展厅</text>
|
||||
</view>
|
||||
<view class="hall-grid">
|
||||
<view
|
||||
v-for="hall in hallSummaries"
|
||||
:key="hall.name"
|
||||
class="hall-chip"
|
||||
:class="{ active: activeHallName === hall.name }"
|
||||
@tap="handleHallFilter(hall.name)"
|
||||
>
|
||||
<text class="hall-name">{{ hall.name }}</text>
|
||||
<text class="hall-count">{{ hall.count }}</text>
|
||||
<view class="hall-chip-main">
|
||||
<text class="hall-name">{{ hall.name }}</text>
|
||||
<text class="hall-count">{{ hall.count }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="hall.id"
|
||||
class="hall-detail-action"
|
||||
@tap.stop="handleHallDetailClick(hall.id)"
|
||||
>
|
||||
<text class="hall-detail-text">查看空间</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list-section">
|
||||
<view class="section-heading">
|
||||
<text class="section-title">讲解列表</text>
|
||||
<text class="section-count">{{ displayedCards.length }} 条</text>
|
||||
<text class="section-title">{{ listTitle }}</text>
|
||||
<text class="section-count">{{ listCountText }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="displayedCards.length" class="card-list">
|
||||
@@ -186,14 +187,34 @@
|
||||
<text class="small-action-text">详情</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="card.poiId"
|
||||
v-if="card.poiId && card.locationActionText"
|
||||
class="small-action"
|
||||
@tap.stop="handleLocationClick(card)"
|
||||
>
|
||||
<text class="small-action-text">位置</text>
|
||||
<text class="small-action-text">{{ card.locationActionText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="hasMoreCards" class="list-more">
|
||||
<view
|
||||
v-if="showBrowseAll"
|
||||
class="more-action primary"
|
||||
@tap="handleBrowseAll"
|
||||
>
|
||||
<text class="more-action-text primary">查看全部 {{ filteredCards.length }} 条</text>
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="more-action"
|
||||
@tap="handleLoadMore"
|
||||
>
|
||||
<text class="more-action-text">加载更多 {{ nextLoadCount }} 条</text>
|
||||
</view>
|
||||
<text class="more-desc">
|
||||
已展示 {{ displayedCards.length }} 条,剩余 {{ remainingCardsCount }} 条
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="state-block empty">
|
||||
@@ -201,9 +222,8 @@
|
||||
<text class="state-desc">试试空间名称、标本名称、楼层或主题关键词。</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -228,13 +248,23 @@ const props = withDefaults(defineProps<{
|
||||
loading?: boolean
|
||||
error?: string
|
||||
hotSearches?: string[]
|
||||
listTitle?: string
|
||||
initialRenderLimit?: number
|
||||
pageSize?: number
|
||||
showBrowseAll?: boolean
|
||||
pageMode?: 'home' | 'all'
|
||||
}>(), {
|
||||
cards: () => [],
|
||||
exhibits: () => [],
|
||||
filters: () => [],
|
||||
loading: false,
|
||||
error: '',
|
||||
hotSearches: () => []
|
||||
hotSearches: () => [],
|
||||
listTitle: '讲解列表',
|
||||
initialRenderLimit: 16,
|
||||
pageSize: 24,
|
||||
showBrowseAll: false,
|
||||
pageMode: 'home'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -242,87 +272,19 @@ const emit = defineEmits<{
|
||||
featuredClick: [card: ExplainCardViewModel]
|
||||
audioClick: [card: ExplainCardViewModel]
|
||||
locationClick: [card: ExplainCardViewModel]
|
||||
hallClick: [hallId: string]
|
||||
filterChange: [filter: ExplainFilterOption['id']]
|
||||
search: [keyword: string]
|
||||
browseAll: []
|
||||
}>()
|
||||
|
||||
const searchInputValue = ref('')
|
||||
const activeFilter = ref<ExplainFilterOption['id']>('all')
|
||||
const activeHallName = ref('')
|
||||
const visibleCardLimit = ref(props.initialRenderLimit)
|
||||
|
||||
const cards = computed(() => props.cards.length ? props.cards : props.exhibits)
|
||||
|
||||
const playableCount = computed(() => cards.value.filter((card) => card.audioStatus === 'playable').length)
|
||||
const unavailableAudioCount = computed(() => cards.value.filter((card) => card.audioStatus === 'unavailable').length)
|
||||
|
||||
const hallSummaries = computed(() => {
|
||||
const result = new Map<string, number>()
|
||||
cards.value.forEach((card) => {
|
||||
const hallName = card.hallName || '未分配展厅'
|
||||
result.set(hallName, (result.get(hallName) || 0) + 1)
|
||||
})
|
||||
return Array.from(result.entries())
|
||||
.map(([name, count]) => ({ name, count }))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
})
|
||||
|
||||
const activeFloorLabel = computed(() => {
|
||||
const counts = new Map<string, number>()
|
||||
cards.value.forEach((card) => {
|
||||
if (card.floorLabel) {
|
||||
counts.set(card.floorLabel, (counts.get(card.floorLabel) || 0) + 1)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] || '全馆'
|
||||
})
|
||||
|
||||
const previewMarkers = computed(() => cards.value.slice(0, 5))
|
||||
|
||||
const resolvedFilters = computed<ExplainFilterOption[]>(() => props.filters.length ? props.filters : [
|
||||
{ id: 'all', label: '全部', count: cards.value.length },
|
||||
{ id: 'hall', label: '展厅', count: hallSummaries.value.length },
|
||||
{ id: 'theme', label: '主题', count: props.hotSearches.length || hallSummaries.value.length },
|
||||
{ id: 'nearby', label: '附近', count: cards.value.filter((card) => card.floorLabel === activeFloorLabel.value).length },
|
||||
{ id: 'playable', label: '可播放', count: playableCount.value }
|
||||
])
|
||||
|
||||
const keywordMatchedCards = computed(() => {
|
||||
const keyword = searchInputValue.value.trim().toLowerCase()
|
||||
if (!keyword) return cards.value
|
||||
|
||||
return cards.value.filter((card) => [
|
||||
card.title,
|
||||
card.subtitle,
|
||||
card.summary,
|
||||
card.hallName,
|
||||
card.floorLabel,
|
||||
card.audioStatusText,
|
||||
contentTypeText(card.contentType),
|
||||
...card.badges
|
||||
].filter(Boolean).join(' ').toLowerCase().includes(keyword))
|
||||
})
|
||||
|
||||
const filteredCards = computed(() => {
|
||||
let result = keywordMatchedCards.value
|
||||
|
||||
if (activeHallName.value) {
|
||||
result = result.filter((card) => card.hallName === activeHallName.value)
|
||||
}
|
||||
|
||||
if (activeFilter.value === 'nearby') {
|
||||
result = result.filter((card) => card.floorLabel === activeFloorLabel.value)
|
||||
}
|
||||
|
||||
if (activeFilter.value === 'playable') {
|
||||
result = result.filter((card) => card.audioStatus === 'playable')
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const displayedCards = computed(() => filteredCards.value)
|
||||
const featuredCard = computed(() => cards.value.find((card) => card.audioStatus === 'playable') || cards.value[0])
|
||||
const isAllListMode = computed(() => props.pageMode === 'all')
|
||||
|
||||
const contentTypeText = (type: ExplainCardViewModel['contentType']) => {
|
||||
const textMap: Record<ExplainCardViewModel['contentType'], string> = {
|
||||
@@ -334,6 +296,133 @@ const contentTypeText = (type: ExplainCardViewModel['contentType']) => {
|
||||
return textMap[type]
|
||||
}
|
||||
|
||||
const searchableCards = computed(() => cards.value.map((card) => ({
|
||||
card,
|
||||
searchText: [
|
||||
card.title,
|
||||
card.subtitle,
|
||||
card.summary,
|
||||
card.hallName,
|
||||
card.floorLabel,
|
||||
card.audioStatusText,
|
||||
contentTypeText(card.contentType),
|
||||
...card.badges
|
||||
].filter(Boolean).join(' ').toLowerCase()
|
||||
})))
|
||||
|
||||
const summarizeHalls = (sourceCards: ExplainCardViewModel[]) => {
|
||||
const result = new Map<string, { id?: string, count: number }>()
|
||||
sourceCards.forEach((card) => {
|
||||
if (!card.hallName) return
|
||||
|
||||
const hallName = card.hallName
|
||||
const existing = result.get(hallName)
|
||||
result.set(hallName, {
|
||||
id: existing?.id || card.hallId,
|
||||
count: (existing?.count || 0) + 1
|
||||
})
|
||||
})
|
||||
return Array.from(result.entries())
|
||||
.map(([name, summary]) => ({ name, id: summary.id, count: summary.count }))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
}
|
||||
|
||||
const getDominantFloorLabel = (sourceCards: ExplainCardViewModel[]) => {
|
||||
const counts = new Map<string, number>()
|
||||
sourceCards.forEach((card) => {
|
||||
if (card.floorLabel) {
|
||||
counts.set(card.floorLabel, (counts.get(card.floorLabel) || 0) + 1)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] || '全馆'
|
||||
}
|
||||
|
||||
const keywordMatchedCards = computed(() => {
|
||||
const keyword = searchInputValue.value.trim().toLowerCase()
|
||||
if (!keyword) return cards.value
|
||||
|
||||
return searchableCards.value
|
||||
.filter((item) => item.searchText.includes(keyword))
|
||||
.map((item) => item.card)
|
||||
})
|
||||
|
||||
const hallSummaries = computed(() => summarizeHalls(keywordMatchedCards.value))
|
||||
const baseFilteredCards = computed(() => {
|
||||
let result = keywordMatchedCards.value
|
||||
|
||||
if (activeHallName.value) {
|
||||
result = result.filter((card) => card.hallName === activeHallName.value)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
const dominantFloorLabel = computed(() => getDominantFloorLabel(baseFilteredCards.value))
|
||||
const trimmedSearchKeyword = computed(() => searchInputValue.value.trim())
|
||||
|
||||
const filteredCards = computed(() => {
|
||||
let result = baseFilteredCards.value
|
||||
|
||||
if (activeFilter.value === 'nearby') {
|
||||
result = result.filter((card) => card.floorLabel === dominantFloorLabel.value)
|
||||
}
|
||||
|
||||
if (activeFilter.value === 'playable') {
|
||||
result = result.filter((card) => card.audioStatus === 'playable')
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const activeFloorLabel = computed(() => getDominantFloorLabel(filteredCards.value))
|
||||
const heroTitle = computed(() => {
|
||||
if (trimmedSearchKeyword.value) {
|
||||
return `搜索“${trimmedSearchKeyword.value}” · ${filteredCards.value.length} 条结果`
|
||||
}
|
||||
if (activeFilter.value === 'playable') {
|
||||
return `可播放讲解 · ${filteredCards.value.length} 条`
|
||||
}
|
||||
if (activeFilter.value === 'hall' && activeHallName.value) {
|
||||
return `${activeHallName.value} · ${filteredCards.value.length} 条`
|
||||
}
|
||||
if (activeFilter.value === 'nearby') {
|
||||
return `${activeFloorLabel.value}附近 · ${filteredCards.value.length} 条`
|
||||
}
|
||||
if (isAllListMode.value) {
|
||||
return `${filteredCards.value.length} 条讲解`
|
||||
}
|
||||
return `馆内讲解 · ${filteredCards.value.length} 条`
|
||||
})
|
||||
const heroDescription = computed(() => (
|
||||
isAllListMode.value
|
||||
? `${playableCount.value} 个可播放音频 · ${textOnlyCount.value} 个图文讲解`
|
||||
: `${hallSummaries.value.length} 个展厅 · ${playableCount.value} 个可播放音频`
|
||||
))
|
||||
const playableCount = computed(() => filteredCards.value.filter((card) => card.audioStatus === 'playable').length)
|
||||
const textOnlyCount = computed(() => filteredCards.value.filter((card) => card.audioStatus !== 'playable').length)
|
||||
const displayedCards = computed(() => filteredCards.value.slice(0, visibleCardLimit.value))
|
||||
const hasMoreCards = computed(() => displayedCards.value.length < filteredCards.value.length)
|
||||
const remainingCardsCount = computed(() => Math.max(filteredCards.value.length - displayedCards.value.length, 0))
|
||||
const nextLoadCount = computed(() => Math.min(props.pageSize, remainingCardsCount.value))
|
||||
const listCountText = computed(() => (
|
||||
hasMoreCards.value
|
||||
? `${displayedCards.value.length}/${filteredCards.value.length} 条`
|
||||
: `${filteredCards.value.length} 条`
|
||||
))
|
||||
const defaultFilters = computed<ExplainFilterOption[]>(() => [
|
||||
{ id: 'all', label: '全部', count: keywordMatchedCards.value.length },
|
||||
{ id: 'hall', label: '展厅', count: hallSummaries.value.length },
|
||||
{ id: 'nearby', label: '附近', count: baseFilteredCards.value.filter((card) => card.floorLabel === dominantFloorLabel.value).length },
|
||||
{ id: 'playable', label: '可播放', count: baseFilteredCards.value.filter((card) => card.audioStatus === 'playable').length }
|
||||
])
|
||||
const resolvedFilters = computed<ExplainFilterOption[]>(() => (
|
||||
props.filters.length ? props.filters : defaultFilters.value
|
||||
).filter((filter) => filter.id !== 'theme'))
|
||||
const featuredCard = computed(() => (
|
||||
filteredCards.value.find((card) => card.audioStatus === 'playable')
|
||||
|| filteredCards.value[0]
|
||||
))
|
||||
|
||||
const cardMeta = (card: ExplainCardViewModel) => [
|
||||
card.hallName,
|
||||
card.floorLabel,
|
||||
@@ -347,34 +436,57 @@ const normalizedBadges = (card: ExplainCardViewModel) => {
|
||||
return badges.slice(0, 3)
|
||||
}
|
||||
|
||||
const resetDisplayedCards = () => {
|
||||
visibleCardLimit.value = props.initialRenderLimit
|
||||
}
|
||||
|
||||
const handleSearchInput = () => {
|
||||
resetDisplayedCards()
|
||||
emit('search', searchInputValue.value)
|
||||
}
|
||||
|
||||
const handleSearchConfirm = () => {
|
||||
resetDisplayedCards()
|
||||
emit('search', searchInputValue.value.trim())
|
||||
}
|
||||
|
||||
const handleClearSearch = () => {
|
||||
searchInputValue.value = ''
|
||||
activeHallName.value = ''
|
||||
resetDisplayedCards()
|
||||
emit('search', '')
|
||||
}
|
||||
|
||||
const handleFilterChange = (filter: ExplainFilterOption['id']) => {
|
||||
if (filter === 'theme') {
|
||||
activeFilter.value = 'all'
|
||||
activeHallName.value = ''
|
||||
resetDisplayedCards()
|
||||
emit('filterChange', filter)
|
||||
return
|
||||
}
|
||||
|
||||
activeFilter.value = filter
|
||||
if (filter !== 'hall') {
|
||||
if (filter === 'hall') {
|
||||
activeHallName.value = activeHallName.value || hallSummaries.value[0]?.name || ''
|
||||
} else {
|
||||
activeHallName.value = ''
|
||||
}
|
||||
resetDisplayedCards()
|
||||
emit('filterChange', filter)
|
||||
}
|
||||
|
||||
const handleHallFilter = (hallName: string) => {
|
||||
activeFilter.value = 'hall'
|
||||
activeHallName.value = activeHallName.value === hallName ? '' : hallName
|
||||
resetDisplayedCards()
|
||||
emit('filterChange', 'hall')
|
||||
}
|
||||
|
||||
const handleHallDetailClick = (hallId: string) => {
|
||||
emit('hallClick', hallId)
|
||||
}
|
||||
|
||||
const handleCardClick = (card: ExplainCardViewModel) => {
|
||||
emit('exhibitClick', card)
|
||||
}
|
||||
@@ -386,6 +498,21 @@ const handleAudioClick = (card: ExplainCardViewModel) => {
|
||||
const handleLocationClick = (card: ExplainCardViewModel) => {
|
||||
emit('locationClick', card)
|
||||
}
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (!hasMoreCards.value) return
|
||||
visibleCardLimit.value += props.pageSize
|
||||
}
|
||||
|
||||
const handleScrollToLower = () => {
|
||||
if (!props.showBrowseAll) {
|
||||
handleLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
const handleBrowseAll = () => {
|
||||
emit('browseAll')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -394,39 +521,37 @@ const handleLocationClick = (card: ExplainCardViewModel) => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #edf0ea;
|
||||
background: #f6f7f4;
|
||||
}
|
||||
|
||||
.spatial-stage {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 298px;
|
||||
padding: 18px 16px 0;
|
||||
.explain-scroll {
|
||||
height: 100%;
|
||||
padding: 0 16px calc(28px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background: #dfe5dc;
|
||||
}
|
||||
|
||||
.stage-copy {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 220px;
|
||||
.explain-hero {
|
||||
padding: 18px 0 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stage-kicker,
|
||||
.stage-title,
|
||||
.stage-desc {
|
||||
.hero-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-kicker,
|
||||
.hero-title,
|
||||
.hero-desc {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stage-kicker {
|
||||
.hero-kicker {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #5e6658;
|
||||
}
|
||||
|
||||
.stage-title {
|
||||
.hero-title {
|
||||
margin-top: 4px;
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
@@ -434,174 +559,67 @@ const handleLocationClick = (card: ExplainCardViewModel) => {
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.stage-desc {
|
||||
.hero-desc {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #5e6658;
|
||||
}
|
||||
|
||||
.stage-map {
|
||||
position: absolute;
|
||||
left: 26px;
|
||||
right: 26px;
|
||||
bottom: 38px;
|
||||
height: 128px;
|
||||
border: 1px solid rgba(88, 95, 78, 0.2);
|
||||
border-radius: 8px;
|
||||
background: #f4f5ef;
|
||||
overflow: hidden;
|
||||
.hero-meta-row {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.stage-map::before,
|
||||
.stage-map::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
right: 32px;
|
||||
height: 1px;
|
||||
background: rgba(88, 95, 78, 0.2);
|
||||
}
|
||||
|
||||
.stage-map::before {
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
.stage-map::after {
|
||||
top: 84px;
|
||||
}
|
||||
|
||||
.stage-floor {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
min-width: 44px;
|
||||
.hero-chip {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
padding: 0 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
background: #151713;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.stage-floor-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.stage-marker {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #2f7d59;
|
||||
border: 3px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.marker-1 {
|
||||
left: 28%;
|
||||
top: 36%;
|
||||
}
|
||||
|
||||
.marker-2 {
|
||||
left: 48%;
|
||||
top: 54%;
|
||||
}
|
||||
|
||||
.marker-3 {
|
||||
left: 62%;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.marker-4 {
|
||||
left: 74%;
|
||||
top: 62%;
|
||||
}
|
||||
|
||||
.marker-5 {
|
||||
left: 38%;
|
||||
top: 68%;
|
||||
}
|
||||
|
||||
.content-panel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 232px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 -8px 28px rgba(25, 28, 21, 0.08);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.panel-handle {
|
||||
align-self: center;
|
||||
width: 64px;
|
||||
height: 4px;
|
||||
margin-top: 8px;
|
||||
border-radius: 2px;
|
||||
background: #d9ddd3;
|
||||
}
|
||||
|
||||
.panel-summary {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 16px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.summary-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.summary-title,
|
||||
.summary-subtitle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
font-size: 18px;
|
||||
line-height: 25px;
|
||||
font-weight: 700;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.summary-subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #697064;
|
||||
}
|
||||
|
||||
.summary-chip {
|
||||
flex-shrink: 0;
|
||||
.hero-browse-all {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
padding: 0 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #eef1e8;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d8dbd1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.summary-chip-text {
|
||||
.hero-browse-all-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.hero-chip.muted {
|
||||
background: #eef1e8;
|
||||
}
|
||||
|
||||
.hero-chip-text {
|
||||
font-size: 12px;
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.hero-chip.muted .hero-chip-text {
|
||||
color: #495143;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex-shrink: 0;
|
||||
margin: 0 16px 12px;
|
||||
margin: 0 0 16px;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -636,13 +654,6 @@ const handleLocationClick = (card: ExplainCardViewModel) => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.panel-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin: 12px 0 10px;
|
||||
display: flex;
|
||||
@@ -830,32 +841,106 @@ const handleLocationClick = (card: ExplainCardViewModel) => {
|
||||
|
||||
.hall-chip {
|
||||
width: calc(50% - 4px);
|
||||
height: 42px;
|
||||
min-height: 54px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
background: #f7f8f3;
|
||||
border: 1px solid #ebece5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hall-chip.active {
|
||||
background: #eef4e8;
|
||||
border-color: rgba(103, 118, 90, 0.28);
|
||||
}
|
||||
|
||||
.hall-chip-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hall-name {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #151713;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hall-count {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 700;
|
||||
color: #68725d;
|
||||
}
|
||||
|
||||
.hall-detail-action {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 8px;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
border: 1px solid #dfe5d5;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.hall-detail-text {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #546548;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
padding-bottom: calc(28px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.list-more {
|
||||
padding: 6px 0 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.more-action {
|
||||
min-width: 156px;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d8dbd1;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.more-action.primary {
|
||||
background: #151713;
|
||||
border-color: #151713;
|
||||
}
|
||||
|
||||
.more-action-text {
|
||||
font-size: 13px;
|
||||
color: #151713;
|
||||
}
|
||||
|
||||
.more-action-text.primary {
|
||||
color: var(--museum-accent);
|
||||
}
|
||||
|
||||
.more-desc {
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
color: #788072;
|
||||
}
|
||||
|
||||
.explain-card {
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
Reference in New Issue
Block a user