<template>
|
<view
|
class="panel-container"
|
:style="{
|
transform: `translateY(${translateY}px)`,
|
transition: isDragging ? 'none' : 'transform 0.3s ease',
|
}"
|
@touchstart="onTouchStart"
|
@touchmove="onTouchMove"
|
@touchend="onTouchEnd"
|
>
|
<!-- 顶部拖拽提示条 -->
|
<view class="drag-bar"></view>
|
|
<!-- 搜索框(带毛玻璃) -->
|
<view class="search-box" :style="searchBoxStyle">
|
<slot name="searchBox"></slot>
|
</view>
|
|
<!-- 内容区 -->
|
<scroll-view scroll-y class="panel-content">
|
<view class="dummy-content">
|
<text>这里可以放搜索结果、推荐地点等内容</text>
|
</view>
|
</scroll-view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted } from 'vue'
|
|
// 获取屏幕高度
|
let screenHeight = 0
|
|
// 拖拽状态
|
const startY = ref(0)
|
const currentY = ref(0)
|
const translateY = ref(0)
|
const isDragging = ref(false)
|
|
const initialPosition = ref(0.8)
|
const oneSlidePosition = ref(0.6)
|
const twoSlidePosition = ref(0.1)
|
|
// 毛玻璃效果
|
const searchBoxStyle = computed(() => {
|
return translateY.value <= screenHeight * oneSlidePosition.value
|
? {
|
backdropFilter: 'blur(10px)',
|
background: 'rgba(255, 255, 255, 0.7)',
|
}
|
: {
|
background: '#fff',
|
}
|
})
|
|
// 获取屏幕高度
|
onMounted(() => {
|
const info = uni.getSystemInfoSync()
|
screenHeight = info.windowHeight
|
|
// #ifdef APP-PLUS
|
initialPosition.value = 0.8
|
// #endif
|
|
// #ifdef H5
|
initialPosition.value = 0.95
|
|
if (screenHeight < 500) {
|
initialPosition.value = 0.86
|
}
|
|
if (screenHeight > 575) {
|
initialPosition.value = 0.85
|
}
|
|
if (screenHeight > 642) {
|
initialPosition.value = 0.865
|
}
|
// #endif
|
|
translateY.value = screenHeight * initialPosition.value
|
})
|
|
// 拖拽开始
|
const onTouchStart = (e) => {
|
// 只在拖拽时,阻止页面滚动
|
startY.value = e.touches[0].clientY
|
currentY.value = translateY.value
|
isDragging.value = true
|
}
|
|
// 拖拽移动
|
const onTouchMove = (e) => {
|
// 只在拖拽时,阻止页面滚动
|
if (isDragging.value) {
|
e.preventDefault() // 阻止滚动行为
|
e.stopPropagation() // 阻止事件冒泡
|
}
|
|
const delta = e.touches[0].clientY - startY.value
|
let nextY = currentY.value + delta
|
|
// 限制拖拽范围 [0, 90% 屏幕高度]
|
nextY = Math.min(screenHeight * (1 - twoSlidePosition.value), Math.max(0, nextY))
|
|
translateY.value = nextY
|
}
|
|
// 拖拽结束后自动吸附
|
const onTouchEnd = () => {
|
isDragging.value = false
|
const percentFromTop = 100 - (translateY.value / screenHeight) * 100
|
|
if (percentFromTop < 20) translateY.value = screenHeight * initialPosition.value
|
else if (percentFromTop < 40) translateY.value = screenHeight * oneSlidePosition.value
|
else if (percentFromTop < 75) translateY.value = screenHeight * oneSlidePosition.value
|
else if (percentFromTop < 90) translateY.value = screenHeight * twoSlidePosition.value
|
else translateY.value = screenHeight * twoSlidePosition.value
|
}
|
</script>
|
|
<style scoped>
|
.panel-container {
|
position: fixed;
|
bottom: 0;
|
left: 0;
|
width: 100%;
|
height: 90vh;
|
background: #fff;
|
border-top-left-radius: 20rpx;
|
border-top-right-radius: 20rpx;
|
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.15);
|
display: flex;
|
flex-direction: column;
|
z-index: 997;
|
touch-action: none; /* 禁止默认的滚动行为 */
|
}
|
|
.drag-bar {
|
width: 80rpx;
|
height: 8rpx;
|
background: #ccc;
|
border-radius: 4rpx;
|
margin: 16rpx auto;
|
}
|
|
.search-box {
|
padding: 0 24rpx;
|
margin-bottom: 12rpx;
|
border-radius: 40rpx;
|
transition: backdrop-filter 0.3s ease, background 0.3s ease;
|
}
|
|
.search-input {
|
width: 100%;
|
height: 80rpx;
|
border-radius: 40rpx;
|
background: #f2f2f2;
|
padding: 0 24rpx;
|
font-size: 28rpx;
|
border: none;
|
outline: none;
|
}
|
|
.panel-content {
|
flex: 1;
|
padding: 20rpx;
|
}
|
|
.dummy-content {
|
text-align: center;
|
color: #999;
|
margin-top: 100rpx;
|
}
|
</style>
|