修复点位搜索H5宽度适配

This commit is contained in:
lyf
2026-07-06 17:17:58 +08:00
parent 19d048dd66
commit e2b6a331ba
4 changed files with 510 additions and 23 deletions

View File

@@ -42,6 +42,8 @@ onLaunch(() => {
--radius-button: 8px;
--radius-small: 6px;
--radius-mini: 5.5px;
--museum-h5-page-max-width: 430px;
--museum-h5-page-width: min(100vw, var(--museum-h5-page-max-width));
/* 间距 */
--space-xs: 4px;

View File

@@ -5,8 +5,20 @@
@touchstart="handlePanelTouchStart"
@touchend="handlePanelTouchEnd"
@touchcancel="resetPanelTouch"
@mousedown="handlePanelMouseStart"
@mousemove="handlePanelMouseMove"
@mouseup="handlePanelMouseEnd"
@mouseleave="resetPanelMouse"
>
<view class="search-box">
<view v-if="variant === 'home' && showSearchContent" class="home-fullscreen-nav">
<view class="home-back-button" @tap.stop="collapseHomePanel">
<text class="home-back-icon"></text>
</view>
<text class="home-fullscreen-title">点位搜索</text>
<view class="home-nav-spacer"></view>
</view>
<view class="search-box" @tap="handleSearchBoxTap" @click="handleSearchBoxTap">
<view class="search-icon"></view>
<input
class="search-input"
@@ -16,6 +28,9 @@
placeholder="请输入地点进行搜索"
placeholder-class="search-placeholder"
:value="searchDraftKeyword"
@mousedown="handleSearchBoxTap"
@click="handleSearchBoxTap"
@tap="handleSearchBoxTap"
@focus="handleSearchFocus"
@input="handleSearchInput"
@confirm="handleSearchConfirm"
@@ -43,7 +58,7 @@
</view>
<view v-if="showSearchContent" class="poi-search-content">
<view v-if="variant === 'home'" class="home-collapse-handle" @tap="collapseHomePanel">
<view v-if="variant === 'home'" class="home-collapse-handle" @tap.stop="collapseHomePanel">
<view class="home-collapse-bar"></view>
</view>
@@ -127,7 +142,7 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import type {
MuseumFloor,
MuseumPoi
@@ -188,6 +203,8 @@ const homeExpanded = ref(false)
const touchStartX = ref(0)
const touchStartY = ref(0)
const isPanelTouching = ref(false)
const isPanelMouseDown = ref(false)
const homeContentTapBlockedUntil = ref(0)
const floors = ref<MuseumFloor[]>([])
const activeFloor = ref('1F')
const pois = ref<MuseumPoi[]>([])
@@ -196,6 +213,7 @@ const initialSpaceLoadError = ref('')
const showSearchContent = computed(() => props.variant === 'page' || homeExpanded.value)
const collapseDragThreshold = 48
const homeExpandTapGuardMs = 360
const displayFloors = computed(() => [...floors.value]
.filter(isIndoorNavigableFloor)
@@ -326,10 +344,67 @@ const focusSearchInput = async () => {
searchInputFocused.value = true
}
const handleSearchFocus = () => {
if (props.variant === 'home') {
const expandHomePanel = () => {
if (props.variant !== 'home') return
if (!homeExpanded.value) {
homeContentTapBlockedUntil.value = Date.now() + homeExpandTapGuardMs
}
homeExpanded.value = true
}
const shouldBlockHomeContentTap = () => (
props.variant === 'home'
&& Date.now() < homeContentTapBlockedUntil.value
)
// #ifdef H5
const homeSearchLockClass = 'guide-home-search-fullscreen-lock'
let removeViewportResizeListener: (() => void) | null = null
const updateHomeSearchViewportHeight = () => {
if (typeof document === 'undefined') return
const viewportHeight = window.visualViewport?.height || window.innerHeight
document.documentElement.style.setProperty('--home-search-viewport-height', `${viewportHeight}px`)
}
const setH5HomeSearchLock = (locked: boolean) => {
if (typeof document === 'undefined') return
document.documentElement.classList.toggle(homeSearchLockClass, locked)
document.body?.classList.toggle(homeSearchLockClass, locked)
if (!locked) {
document.documentElement.style.removeProperty('--home-search-viewport-height')
removeViewportResizeListener?.()
removeViewportResizeListener = null
return
}
updateHomeSearchViewportHeight()
if (!removeViewportResizeListener) {
const target = window.visualViewport || window
target.addEventListener('resize', updateHomeSearchViewportHeight)
removeViewportResizeListener = () => {
target.removeEventListener('resize', updateHomeSearchViewportHeight)
}
}
}
// #endif
const handleSearchFocus = () => {
if (props.variant === 'home') {
expandHomePanel()
}
}
const handleSearchBoxTap = () => {
if (props.variant !== 'home' || homeExpanded.value) return
expandHomePanel()
void focusSearchInput()
}
const collapseHomePanel = () => {
@@ -346,12 +421,27 @@ const resetPanelTouch = () => {
touchStartY.value = 0
}
const getTouchPoint = (event: any) => event.changedTouches?.[0] || event.touches?.[0] || null
const resetPanelMouse = () => {
isPanelMouseDown.value = false
touchStartX.value = 0
touchStartY.value = 0
}
const getGesturePoint = (event: any) => {
const touchPoint = event.changedTouches?.[0] || event.touches?.[0]
if (touchPoint) return touchPoint
if (typeof event.clientX === 'number' && typeof event.clientY === 'number') {
return event
}
return null
}
const handlePanelTouchStart = (event: any) => {
if (props.variant !== 'home' || !homeExpanded.value) return
const point = getTouchPoint(event)
const point = getGesturePoint(event)
if (!point) return
touchStartX.value = point.clientX
@@ -365,7 +455,7 @@ const handlePanelTouchEnd = (event: any) => {
return
}
const point = getTouchPoint(event)
const point = getGesturePoint(event)
if (!point) {
resetPanelTouch()
return
@@ -383,10 +473,61 @@ const handlePanelTouchEnd = (event: any) => {
resetPanelTouch()
}
const handlePanelMouseStart = (event: any) => {
if (props.variant !== 'home' || !homeExpanded.value) return
const point = getGesturePoint(event)
if (!point) return
touchStartX.value = point.clientX
touchStartY.value = point.clientY
isPanelMouseDown.value = true
}
const handlePanelMouseMove = (event: any) => {
if (!isPanelMouseDown.value || props.variant !== 'home' || !homeExpanded.value) return
const point = getGesturePoint(event)
if (!point) return
const deltaX = point.clientX - touchStartX.value
const deltaY = point.clientY - touchStartY.value
const isIntentionalDownDrag = deltaY > collapseDragThreshold
&& deltaY > Math.abs(deltaX) * 1.2
if (isIntentionalDownDrag) {
collapseHomePanel()
resetPanelMouse()
}
}
const handlePanelMouseEnd = (event: any) => {
if (!isPanelMouseDown.value || props.variant !== 'home' || !homeExpanded.value) {
resetPanelMouse()
return
}
const point = getGesturePoint(event)
if (!point) {
resetPanelMouse()
return
}
const deltaX = point.clientX - touchStartX.value
const deltaY = point.clientY - touchStartY.value
const isIntentionalDownDrag = deltaY > collapseDragThreshold
&& deltaY > Math.abs(deltaX) * 1.2
if (isIntentionalDownDrag) {
collapseHomePanel()
}
resetPanelMouse()
}
const handleSearchInput = (event: any) => {
searchDraftKeyword.value = event.detail.value
if (props.variant === 'home') {
homeExpanded.value = true
expandHomePanel()
}
}
@@ -404,7 +545,7 @@ const handleSearchConfirm = async (event?: any) => {
searchKeyword.value = value.trim()
searchDraftKeyword.value = searchKeyword.value
homeExpanded.value = true
expandHomePanel()
const matchedShortcut = findShortcutByKeyword(searchKeyword.value)
if (matchedShortcut) {
@@ -418,7 +559,7 @@ const handleSearchConfirm = async (event?: any) => {
const searchShortcut = async (shortcut: PoiSearchShortcut) => {
searchKeyword.value = shortcut.label
searchDraftKeyword.value = shortcut.label
homeExpanded.value = true
expandHomePanel()
isLoading.value = true
try {
@@ -456,6 +597,8 @@ const createPoiLocationQuery = (poi: MuseumPoi) => (
)
const handleResultTap = (poi: MuseumPoi) => {
if (shouldBlockHomeContentTap()) return
emit('resultTap', poi)
if (poi.primaryCategory.id === 'operation_experience') {
@@ -474,7 +617,13 @@ const applyInitialKeyword = async (keyword: string) => {
const normalizedKeyword = keyword.trim()
searchKeyword.value = normalizedKeyword
searchDraftKeyword.value = normalizedKeyword
homeExpanded.value = props.variant === 'page' || Boolean(keyword)
if (props.variant === 'page') {
homeExpanded.value = true
} else if (normalizedKeyword) {
expandHomePanel()
} else {
homeExpanded.value = false
}
if (normalizedKeyword) {
await loadPois(normalizedKeyword)
@@ -490,6 +639,9 @@ watch(() => props.initialKeyword, (keyword) => {
watch(showSearchContent, (expanded) => {
emit('expanded-change', expanded)
// #ifdef H5
setH5HomeSearchLock(props.variant === 'home' && expanded)
// #endif
}, { immediate: true })
onMounted(async () => {
@@ -500,7 +652,14 @@ onMounted(async () => {
}
})
onUnmounted(() => {
// #ifdef H5
setH5HomeSearchLock(false)
// #endif
})
defineExpose({
expandHomePanel,
collapseHomePanel,
focusSearchInput,
searchShortcut
@@ -508,6 +667,15 @@ defineExpose({
</script>
<style scoped lang="scss">
/* #ifdef H5 */
:global(html.guide-home-search-fullscreen-lock),
:global(body.guide-home-search-fullscreen-lock) {
height: 100%;
overflow: hidden;
overscroll-behavior: none;
}
/* #endif */
.poi-search-panel {
width: 100%;
box-sizing: border-box;
@@ -515,8 +683,70 @@ defineExpose({
font-family: '鸿蒙黑体', 'HarmonyOS Sans SC', 'HarmonyOS Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.variant-home.expanded {
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
overscroll-behavior: contain;
}
.variant-page {
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.home-fullscreen-nav {
height: 38px;
flex: 0 0 38px;
display: grid;
grid-template-columns: 38px minmax(0, 1fr) 38px;
align-items: center;
margin-bottom: 10px;
}
.home-back-button,
.home-nav-spacer {
width: 38px;
height: 38px;
}
.home-back-button {
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: #ffffff;
border: 1px solid #e1e4da;
}
.home-back-icon {
font-size: 30px;
line-height: 30px;
font-weight: 600;
color: #151713;
transform: translateX(-1px);
}
.home-fullscreen-title {
min-width: 0;
font-size: 17px;
line-height: 22px;
font-weight: 800;
color: #151713;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search-box {
height: 48px;
flex: 0 0 48px;
display: flex;
align-items: center;
padding: 0 14px;
@@ -617,12 +847,18 @@ defineExpose({
min-width: 0;
flex: 1;
height: 100%;
font-size: 15px;
line-height: 20px;
font-size: 16px;
line-height: 22px;
font-weight: 500;
color: #262421;
}
.search-input :deep(input),
.search-input :deep(.uni-input-input) {
font-size: 16px;
line-height: 22px;
}
.search-placeholder {
color: #8b8d85;
}
@@ -682,12 +918,22 @@ defineExpose({
margin-top: 12px;
}
.variant-page .poi-search-content {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
padding-bottom: env(safe-area-inset-bottom);
}
.home-collapse-handle {
height: 18px;
margin: -4px 0 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.home-collapse-bar {
@@ -703,6 +949,16 @@ defineExpose({
padding-right: 2px;
}
.variant-home.expanded .poi-search-content {
flex: 1;
min-height: 0;
max-height: none;
display: flex;
flex-direction: column;
overflow: hidden;
padding-right: 0;
}
.category-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -846,6 +1102,12 @@ defineExpose({
gap: 8px;
overflow-x: auto;
padding-bottom: 2px;
scrollbar-width: none;
overscroll-behavior-x: contain;
}
.facility-chip-row::-webkit-scrollbar {
display: none;
}
.facility-chip {
@@ -964,6 +1226,61 @@ defineExpose({
flex: 1;
}
.variant-page .category-grid,
.variant-page .section-title-row,
.variant-page .facility-chip-row {
flex: 0 0 auto;
}
.variant-page .result-card {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.variant-page .result-body {
flex: 1;
min-height: 0;
}
.variant-page .floor-tabs,
.variant-page .result-list {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.variant-page .result-list {
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
box-sizing: border-box;
}
.variant-home.expanded .category-grid,
.variant-home.expanded .section-title-row,
.variant-home.expanded .facility-chip-row {
flex: 0 0 auto;
}
.variant-home.expanded .result-card {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.variant-home.expanded .result-body {
flex: 1;
min-height: 0;
}
.variant-home.expanded .floor-tabs,
.variant-home.expanded .result-list {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.result-row {
min-height: 54px;
display: flex;
@@ -1063,9 +1380,22 @@ defineExpose({
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.variant-page .category-grid,
.variant-home.expanded .category-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.variant-page .category-item,
.variant-home.expanded .category-item {
min-height: 62px;
padding: 7px 4px;
gap: 5px;
}
.floor-tabs {
width: 52px;
flex-basis: 52px;
}
}
</style>

View File

@@ -137,11 +137,26 @@
</view>
</view>
<view
v-if="showGuideHomeDock"
v-show="homeSearchExpanded"
class="guide-home-search-scrim"
@mousedown.stop.prevent="handleHomeSearchOutsideTap"
@click.stop="handleHomeSearchOutsideTap"
@touchstart.stop.prevent="handleHomeSearchOutsideTap"
@tap.stop="handleHomeSearchOutsideTap"
></view>
<view
v-if="showGuideHomeDock"
class="guide-home-dock"
:class="{ expanded: homeSearchExpanded }"
@mousedown.capture="handleHomeDockActivate"
@click.capture="handleHomeDockActivate"
@tap.capture="handleHomeDockActivate"
>
<PoiSearchPanel
ref="homeSearchPanelRef"
variant="home"
@expanded-change="handleHomeSearchExpandedChange"
/>
@@ -404,6 +419,10 @@ const isPoiCardCollapsed = ref(false)
const guideMapShellRef = ref<{
clearRoute?: () => void
} | null>(null)
const homeSearchPanelRef = ref<{
expandHomePanel?: () => void
collapseHomePanel?: () => void
} | null>(null)
const homeSearchExpanded = ref(false)
let launchOverlayFallbackTimer: ReturnType<typeof setTimeout> | null = null
@@ -1303,6 +1322,18 @@ const handleHomeSearchExpandedChange = (expanded: boolean) => {
homeSearchExpanded.value = expanded
}
const handleHomeSearchOutsideTap = () => {
homeSearchPanelRef.value?.collapseHomePanel?.()
homeSearchExpanded.value = false
}
const handleHomeDockActivate = () => {
if (homeSearchExpanded.value) return
homeSearchPanelRef.value?.expandHomePanel?.()
homeSearchExpanded.value = true
}
const handleOutdoorNavClose = () => {
closeOutdoorNavPanel()
}
@@ -1897,8 +1928,10 @@ const handleExplainBack = () => {
.guide-home-dock {
position: absolute;
left: 12px;
right: 12px;
--museum-h5-page-width: min(100vw, 430px);
left: 50%;
right: auto;
width: calc(var(--museum-h5-page-width) - 24px);
bottom: calc(env(safe-area-inset-bottom) + 16px);
padding: 12px;
box-sizing: border-box;
@@ -1906,9 +1939,53 @@ const handleExplainBack = () => {
border: 1px solid rgba(255, 255, 255, 0.96);
border-radius: 16px;
box-shadow: 0 10px 28px rgba(36, 49, 42, 0.16);
transform: translateX(-50%);
z-index: 1003;
}
.guide-home-dock.expanded {
position: fixed;
left: 50%;
right: auto;
top: 0;
bottom: auto;
width: var(--museum-h5-page-width);
height: 100vh;
height: 100dvh;
height: var(--home-search-viewport-height, 100dvh);
max-height: none;
display: flex;
flex-direction: column;
padding: calc(env(safe-area-inset-top) + 12px) 14px calc(env(safe-area-inset-bottom) + 14px);
background: #f7f8f2;
border: 0;
border-radius: 0;
box-shadow: none;
transform: translateX(-50%);
overflow: hidden;
overscroll-behavior: contain;
touch-action: pan-y;
z-index: 1100;
}
/* #ifdef H5 */
.guide-home-dock.expanded {
min-height: 100vh;
min-height: 100dvh;
max-width: var(--museum-h5-page-width);
}
/* #endif */
.guide-home-search-scrim {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: transparent;
z-index: 1002;
}
.guide-quick-actions {
position: absolute;
top: clamp(96px, 14vh, 118px);

View File

@@ -10,16 +10,18 @@
</view>
</view>
<view class="search-panel-shell">
<PoiSearchPanel
:initial-keyword="initialKeyword"
variant="page"
autofocus
/>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import PoiSearchPanel from '@/components/search/PoiSearchPanel.vue'
@@ -43,25 +45,71 @@ const handleBackToMap = () => {
}
})
}
// #ifdef H5
let removeSearchViewportResizeListener: (() => void) | null = null
const updateSearchViewportHeight = () => {
if (typeof document === 'undefined') return
const viewportHeight = window.visualViewport?.height || window.innerHeight
document.documentElement.style.setProperty('--poi-search-page-height', `${viewportHeight}px`)
}
onMounted(() => {
updateSearchViewportHeight()
const target = window.visualViewport || window
target.addEventListener('resize', updateSearchViewportHeight)
removeSearchViewportResizeListener = () => {
target.removeEventListener('resize', updateSearchViewportHeight)
}
})
onUnmounted(() => {
removeSearchViewportResizeListener?.()
removeSearchViewportResizeListener = null
if (typeof document !== 'undefined') {
document.documentElement.style.removeProperty('--poi-search-page-height')
}
})
// #endif
</script>
<style scoped lang="scss">
.poi-search-page {
--museum-h5-page-width: min(100vw, 430px);
width: 100%;
height: 100vh;
height: 100dvh;
min-height: 100vh;
min-height: 100dvh;
padding: calc(env(safe-area-inset-top) + 32px) 14px 24px;
padding: calc(env(safe-area-inset-top) + 28px) 14px calc(env(safe-area-inset-bottom) + 16px);
box-sizing: border-box;
background:
linear-gradient(180deg, rgba(224, 225, 0, 0.22) 0, rgba(245, 245, 237, 0) 146px),
#f5f5ed;
color: #262421;
font-family: '鸿蒙黑体', 'HarmonyOS Sans SC', 'HarmonyOS Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
overflow-y: auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* #ifdef H5 */
.poi-search-page {
height: var(--poi-search-page-height, 100dvh);
min-height: var(--poi-search-page-height, 100dvh);
max-width: var(--museum-h5-page-width);
margin: 0 auto;
overscroll-behavior: none;
}
/* #endif */
.search-header {
min-height: 46px;
flex: 0 0 auto;
display: flex;
align-items: flex-start;
justify-content: space-between;
@@ -69,6 +117,13 @@ const handleBackToMap = () => {
margin: 0 2px 16px;
}
.search-panel-shell {
min-height: 0;
flex: 1;
display: flex;
overflow: hidden;
}
.search-header-copy {
min-width: 0;
display: flex;
@@ -119,4 +174,27 @@ const handleBackToMap = () => {
margin: 0 auto;
}
}
@media (max-width: 360px) {
.poi-search-page {
padding-top: calc(env(safe-area-inset-top) + 22px);
padding-left: 12px;
padding-right: 12px;
}
.search-header {
margin-bottom: 12px;
}
.page-title {
font-size: 23px;
line-height: 28px;
}
.map-link {
min-width: 68px;
height: 32px;
padding: 0 8px;
}
}
</style>