Compare commits
2 Commits
653ec67b14
...
2a21108157
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a21108157 | |||
| 8758fe66c6 |
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="explain-hall-select">
|
<view class="explain-hall-select" :class="{ 'host-navigation': shouldUseHostNavigation }">
|
||||||
<view class="explain-page-header">
|
<view v-if="!shouldUseHostNavigation" class="explain-page-header">
|
||||||
<view class="header-back" @tap="handleBack">
|
<view class="header-back" @tap="handleBack">
|
||||||
<text class="header-back-icon">‹</text>
|
<text class="header-back-icon">‹</text>
|
||||||
<text class="header-back-text">返回</text>
|
<text class="header-back-text">返回</text>
|
||||||
@@ -153,6 +153,7 @@ import { computed, ref } from 'vue'
|
|||||||
import {
|
import {
|
||||||
EXPLAIN_UNIT_PREVIEW_FALLBACK
|
EXPLAIN_UNIT_PREVIEW_FALLBACK
|
||||||
} from '@/utils/explainUnitPreviews'
|
} from '@/utils/explainUnitPreviews'
|
||||||
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
export interface ExplainHallSelectItem {
|
export interface ExplainHallSelectItem {
|
||||||
id: string
|
id: string
|
||||||
@@ -239,6 +240,7 @@ const hallPreviewMap: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filteredHalls = computed(() => props.halls)
|
const filteredHalls = computed(() => props.halls)
|
||||||
|
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
const headerTitle = computed(() => {
|
const headerTitle = computed(() => {
|
||||||
if (props.stage === 'unit') return props.selectedHallName || '选择业务单元'
|
if (props.stage === 'unit') return props.selectedHallName || '选择业务单元'
|
||||||
if (props.stage === 'stop') return props.selectedBusinessUnitName || '选择讲解点'
|
if (props.stage === 'stop') return props.selectedBusinessUnitName || '选择讲解点'
|
||||||
@@ -356,6 +358,14 @@ const handleBack = () => {
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.explain-hall-select.host-navigation .explain-scroll {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explain-hall-select.host-navigation .explain-scroll.hall-stage {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.state-title,
|
.state-title,
|
||||||
.state-desc,
|
.state-desc,
|
||||||
.hall-name,
|
.hall-name,
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="guide-page-frame"
|
class="guide-page-frame"
|
||||||
:class="[`variant-${variant}`, { 'no-top-tabs': !showTopTabs }]"
|
:class="[
|
||||||
|
`variant-${variant}`,
|
||||||
|
{
|
||||||
|
'no-top-tabs': !effectiveShowTopTabs,
|
||||||
|
'no-frame-header': !hasFrameHeader
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:style="frameStyle"
|
||||||
>
|
>
|
||||||
<GuideTopTabs
|
<GuideTopTabs
|
||||||
v-if="showTopTabs"
|
v-if="effectiveShowTopTabs"
|
||||||
:active-tab="activeTab"
|
:active-tab="activeTab"
|
||||||
:variant="topTabsVariant"
|
:variant="topTabsVariant"
|
||||||
:top="topTabsTop"
|
:top="topTabsTop"
|
||||||
@tab-change="handleTabChange"
|
@tab-change="handleTabChange"
|
||||||
/>
|
/>
|
||||||
<view
|
<view
|
||||||
v-if="showBack || showCancel"
|
v-if="effectiveShowBack || effectiveShowCancel"
|
||||||
class="guide-page-frame-actions"
|
class="guide-page-frame-actions"
|
||||||
>
|
>
|
||||||
<view
|
<view
|
||||||
v-if="showBack"
|
v-if="effectiveShowBack"
|
||||||
class="frame-action frame-action-back"
|
class="frame-action frame-action-back"
|
||||||
@tap.stop="handleBackTap"
|
@tap.stop="handleBackTap"
|
||||||
>
|
>
|
||||||
<text class="frame-action-text">{{ backLabel }}</text>
|
<text class="frame-action-text">{{ backLabel }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
v-if="showCancel"
|
v-if="effectiveShowCancel"
|
||||||
class="frame-action frame-action-cancel"
|
class="frame-action frame-action-cancel"
|
||||||
@tap.stop="handleCancelTap"
|
@tap.stop="handleCancelTap"
|
||||||
>
|
>
|
||||||
@@ -43,12 +50,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import GuideTopTabs from '@/components/navigation/GuideTopTabs.vue'
|
import GuideTopTabs from '@/components/navigation/GuideTopTabs.vue'
|
||||||
import GlobalAudioPlayerHost from '@/components/audio/GlobalAudioPlayerHost.vue'
|
import GlobalAudioPlayerHost from '@/components/audio/GlobalAudioPlayerHost.vue'
|
||||||
import type { AudioDisplayMode } from '@/components/audio/AudioPlayer.vue'
|
import type { AudioDisplayMode } from '@/components/audio/AudioPlayer.vue'
|
||||||
import type { GuideTopTab } from '@/utils/guideTopTabs'
|
import type { GuideTopTab } from '@/utils/guideTopTabs'
|
||||||
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
activeTab?: GuideTopTab
|
activeTab?: GuideTopTab
|
||||||
variant?: 'overlay' | 'static'
|
variant?: 'overlay' | 'static'
|
||||||
topTabsVariant?: 'underline' | 'segmented'
|
topTabsVariant?: 'underline' | 'segmented'
|
||||||
@@ -86,6 +95,18 @@ const emit = defineEmits<{
|
|||||||
cancel: []
|
cancel: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
|
const effectiveShowTopTabs = computed(() => props.showTopTabs && !shouldUseHostNavigation.value)
|
||||||
|
const effectiveShowBack = computed(() => props.showBack && !shouldUseHostNavigation.value)
|
||||||
|
const effectiveShowCancel = computed(() => props.showCancel && !shouldUseHostNavigation.value)
|
||||||
|
const hasFrameHeader = computed(() => (
|
||||||
|
effectiveShowTopTabs.value || effectiveShowBack.value || effectiveShowCancel.value
|
||||||
|
))
|
||||||
|
const frameStyle = computed(() => ({
|
||||||
|
'--guide-page-top-offset': shouldUseHostNavigation.value ? '0px' : '44px',
|
||||||
|
'--guide-page-detail-top-padding': shouldUseHostNavigation.value ? '24px' : '84px'
|
||||||
|
}))
|
||||||
|
|
||||||
const handleTabChange = (tab: GuideTopTab) => {
|
const handleTabChange = (tab: GuideTopTab) => {
|
||||||
emit('tabChange', tab)
|
emit('tabChange', tab)
|
||||||
}
|
}
|
||||||
@@ -123,11 +144,11 @@ const handleCancelTap = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.variant-static .guide-page-frame-body {
|
.variant-static .guide-page-frame-body {
|
||||||
top: 44px;
|
top: var(--guide-page-top-offset, 44px);
|
||||||
background: var(--museum-bg-light);
|
background: var(--museum-bg-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.variant-static.no-top-tabs .guide-page-frame-body {
|
.variant-static.no-frame-header .guide-page-frame-body {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,12 @@
|
|||||||
:active-tab="activeTopTab"
|
:active-tab="activeTopTab"
|
||||||
variant="static"
|
variant="static"
|
||||||
:show-top-tabs="false"
|
:show-top-tabs="false"
|
||||||
:audio-player-avoid-bottom-nav="false"
|
|
||||||
:audio-player-closable="false"
|
|
||||||
:audio-player-expandable="false"
|
|
||||||
:show-audio-player-host="true"
|
|
||||||
audio-player-mode="mini"
|
|
||||||
@back="handleBack"
|
@back="handleBack"
|
||||||
>
|
>
|
||||||
<view class="explain-detail-page" :class="{ 'with-audio-player': showAudioPlayer }">
|
<view class="explain-detail-page">
|
||||||
<scroll-view
|
<scroll-view
|
||||||
class="content"
|
class="content"
|
||||||
:class="{ 'with-fixed-player': isDetailAudioDockVisible || showAudioPlayer }"
|
:class="{ 'with-fixed-player': isDetailAudioDockVisible }"
|
||||||
scroll-y
|
scroll-y
|
||||||
:show-scrollbar="false"
|
:show-scrollbar="false"
|
||||||
>
|
>
|
||||||
@@ -25,7 +20,7 @@
|
|||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
<view class="hero-image-shade"></view>
|
<view class="hero-image-shade"></view>
|
||||||
<view class="hero-back" @tap.stop="handleBack">
|
<view v-if="!shouldUseHostNavigation" class="hero-back" @tap.stop="handleBack">
|
||||||
<text class="hero-back-icon">‹</text>
|
<text class="hero-back-icon">‹</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="hero-copy">
|
<view class="hero-copy">
|
||||||
@@ -67,7 +62,11 @@
|
|||||||
class="detail-audio-dock"
|
class="detail-audio-dock"
|
||||||
>
|
>
|
||||||
<view class="detail-audio-play" @tap="handlePlayAudio">
|
<view class="detail-audio-play" @tap="handlePlayAudio">
|
||||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none">
|
<svg v-if="detailAudioPlaying" width="26" height="26" viewBox="0 0 24 24" fill="none">
|
||||||
|
<rect x="6" y="5" width="4" height="14" rx="1" fill="currentColor"/>
|
||||||
|
<rect x="14" y="5" width="4" height="14" rx="1" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<svg v-else width="26" height="26" viewBox="0 0 24 24" fill="none">
|
||||||
<path d="M8 5V19L19 12L8 5Z" fill="currentColor"/>
|
<path d="M8 5V19L19 12L8 5Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
</view>
|
</view>
|
||||||
@@ -75,7 +74,10 @@
|
|||||||
<text class="detail-audio-title">{{ audioDockTitle }}</text>
|
<text class="detail-audio-title">{{ audioDockTitle }}</text>
|
||||||
<text class="detail-audio-subtitle">{{ audioDockSubtitle }}</text>
|
<text class="detail-audio-subtitle">{{ audioDockSubtitle }}</text>
|
||||||
<view class="detail-audio-progress">
|
<view class="detail-audio-progress">
|
||||||
<view class="detail-audio-progress-fill"></view>
|
<view
|
||||||
|
class="detail-audio-progress-fill"
|
||||||
|
:style="{ width: `${detailAudioProgressPercent}%` }"
|
||||||
|
></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -126,6 +128,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
EXPLAIN_DETAIL_PLACEHOLDER_IMAGE
|
EXPLAIN_DETAIL_PLACEHOLDER_IMAGE
|
||||||
} from '@/utils/placeholders'
|
} from '@/utils/placeholders'
|
||||||
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
const defaultDetail: ExplainDetailPageViewModel = {
|
const defaultDetail: ExplainDetailPageViewModel = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -168,6 +171,7 @@ const detailTextLoadedByLanguage = ref<Record<AudioLanguage, boolean>>({
|
|||||||
})
|
})
|
||||||
// 讲解详情页位置入口暂未开放,避免误导用户进入位置预览流程。
|
// 讲解详情页位置入口暂未开放,避免误导用户进入位置预览流程。
|
||||||
const isLocationActionVisible = false
|
const isLocationActionVisible = false
|
||||||
|
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
|
|
||||||
const contentLanguageOptions: Array<{
|
const contentLanguageOptions: Array<{
|
||||||
value: AudioLanguage
|
value: AudioLanguage
|
||||||
@@ -206,9 +210,6 @@ const currentAudioTarget = computed(() => ({
|
|||||||
lang: selectedAudioLanguage.value
|
lang: selectedAudioLanguage.value
|
||||||
}))
|
}))
|
||||||
const isCurrentDetailAudio = computed(() => globalAudioPlayer.isCurrentSource(currentAudioTarget.value))
|
const isCurrentDetailAudio = computed(() => globalAudioPlayer.isCurrentSource(currentAudioTarget.value))
|
||||||
const showAudioPlayer = computed(() => (
|
|
||||||
globalAudioPlayer.visible.value && globalAudioPlayer.hasAudio.value
|
|
||||||
))
|
|
||||||
const isCurrentDetailAudioTarget = computed(() => {
|
const isCurrentDetailAudioTarget = computed(() => {
|
||||||
const source = globalAudioPlayer.currentSource.value
|
const source = globalAudioPlayer.currentSource.value
|
||||||
const targetType = exhibit.value.audio.playTargetType || 'ITEM'
|
const targetType = exhibit.value.audio.playTargetType || 'ITEM'
|
||||||
@@ -222,13 +223,24 @@ const isCurrentDetailAudioTarget = computed(() => {
|
|||||||
})
|
})
|
||||||
const isDetailAudioDockVisible = computed(() => (
|
const isDetailAudioDockVisible = computed(() => (
|
||||||
exhibit.value.audio.status === 'playable'
|
exhibit.value.audio.status === 'playable'
|
||||||
&& !isCurrentDetailAudioTarget.value
|
|
||||||
))
|
))
|
||||||
const audioDockTitle = computed(() => exhibit.value.title || '讲解内容')
|
const audioDockTitle = computed(() => exhibit.value.title || '讲解内容')
|
||||||
const audioDockSubtitle = computed(() => [
|
const audioDockSubtitle = computed(() => [
|
||||||
selectedAudioLanguage.value === 'en-US' ? 'English' : '中文',
|
selectedAudioLanguage.value === 'en-US' ? 'English' : '中文',
|
||||||
exhibit.value.audio.durationLabel
|
exhibit.value.audio.durationLabel
|
||||||
].filter(Boolean).join(' · ') || '讲解')
|
].filter(Boolean).join(' · ') || '讲解')
|
||||||
|
const detailAudioPlaying = computed(() => (
|
||||||
|
isCurrentDetailAudio.value
|
||||||
|
&& globalAudioPlayer.playing.value
|
||||||
|
))
|
||||||
|
const detailAudioProgressPercent = computed(() => {
|
||||||
|
if (!isCurrentDetailAudioTarget.value) return 0
|
||||||
|
|
||||||
|
const total = globalAudioPlayer.duration.value || globalAudioPlayer.currentAudio.value?.duration || 0
|
||||||
|
if (!total) return 0
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(100, (globalAudioPlayer.currentTime.value / total) * 100))
|
||||||
|
})
|
||||||
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
||||||
const resolveHallGuidePoi = async () => {
|
const resolveHallGuidePoi = async () => {
|
||||||
return guideUseCase.resolveContentLocationPreviewTarget({
|
return guideUseCase.resolveContentLocationPreviewTarget({
|
||||||
@@ -688,7 +700,7 @@ const handleBack = () => {
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 84px 20px calc(104px + env(safe-area-inset-bottom));
|
padding: var(--guide-page-detail-top-padding, 84px) 20px calc(104px + env(safe-area-inset-bottom));
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,10 +968,6 @@ const handleBack = () => {
|
|||||||
background: rgba(249, 250, 251, 0.96);
|
background: rgba(249, 250, 251, 0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.explain-detail-page.with-audio-player .action-bar {
|
|
||||||
bottom: 88px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-btn {
|
.location-btn {
|
||||||
height: 52px;
|
height: 52px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1308,7 +1316,7 @@ const handleBack = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-audio-progress-fill {
|
.detail-audio-progress-fill {
|
||||||
width: 22%;
|
width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #e7e900;
|
background: #e7e900;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ const handleBack = () => {
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 84px 20px calc(28px + env(safe-area-inset-bottom));
|
padding: var(--guide-page-detail-top-padding, 84px) 20px calc(28px + env(safe-area-inset-bottom));
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="poi-search-page">
|
<view class="poi-search-page" :class="{ 'host-navigation': shouldUseHostNavigation }">
|
||||||
<view class="search-header">
|
<view v-if="!shouldUseHostNavigation" class="search-header">
|
||||||
<view class="search-header-copy">
|
<view class="search-header-copy">
|
||||||
<text class="page-title">点位搜索</text>
|
<text class="page-title">点位搜索</text>
|
||||||
<text class="page-subtitle">Search Museum Locations</text>
|
<text class="page-subtitle">Search Museum Locations</text>
|
||||||
@@ -21,11 +21,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import PoiSearchPanel from '@/components/search/PoiSearchPanel.vue'
|
import PoiSearchPanel from '@/components/search/PoiSearchPanel.vue'
|
||||||
|
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||||
|
|
||||||
const initialKeyword = ref('')
|
const initialKeyword = ref('')
|
||||||
|
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
|
|
||||||
onLoad((options: any) => {
|
onLoad((options: any) => {
|
||||||
const keyword = typeof options.keyword === 'string'
|
const keyword = typeof options.keyword === 'string'
|
||||||
@@ -119,6 +121,14 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
/* #endif */
|
/* #endif */
|
||||||
|
|
||||||
|
.poi-search-page.host-navigation {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-search-page.host-navigation::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.search-header {
|
.search-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
34
src/utils/hostEnvironment.ts
Normal file
34
src/utils/hostEnvironment.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const getCurrentUrlSearchParams = () => {
|
||||||
|
if (typeof window === 'undefined') return new URLSearchParams()
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const hash = window.location.hash || ''
|
||||||
|
const hashQueryStart = hash.indexOf('?')
|
||||||
|
|
||||||
|
if (hashQueryStart >= 0) {
|
||||||
|
const hashParams = new URLSearchParams(hash.slice(hashQueryStart + 1))
|
||||||
|
hashParams.forEach((value, key) => {
|
||||||
|
if (!params.has(key)) params.set(key, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEmbeddedInWechatMiniProgram = () => {
|
||||||
|
if (typeof window === 'undefined') return false
|
||||||
|
|
||||||
|
const params = getCurrentUrlSearchParams()
|
||||||
|
const embedded = params.get('embedded') || params.get('embed') || params.get('host')
|
||||||
|
if (embedded === 'wechat-mini-program' || embedded === 'weapp' || embedded === 'mini-program') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const wxEnvironment = (window as Window & {
|
||||||
|
__wxjs_environment?: string
|
||||||
|
}).__wxjs_environment
|
||||||
|
|
||||||
|
if (wxEnvironment === 'miniprogram') return true
|
||||||
|
|
||||||
|
return /miniProgram/i.test(window.navigator?.userAgent || '')
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user