Files
taimed-international-app/uni_modules/wot-design-uni/components/wd-floating-panel/wd-floating-panel.vue
2025-11-04 12:37:04 +08:00

141 lines
3.5 KiB
Vue

<template>
<view
:class="`wd-floating-panel ${customClass} ${safeAreaInsetBottom ? 'is-safe' : ''}`"
:style="rootStyle"
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="handleTouchEnd"
@touchcancel="handleTouchEnd"
>
<view :class="`wd-floating-panel__header`">
<view :class="`wd-floating-panel__header-bar`"></view>
</view>
<scroll-view
:class="`wd-floating-panel__content`"
data-id="content"
:show-scrollbar="showScrollbar"
scroll-y
@touchmove.stop.prevent="handleTouchMove"
>
<slot />
</scroll-view>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-floating-panel',
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, onBeforeMount, ref, watch, type CSSProperties } from 'vue'
import { floatingPanelProps } from './type'
import { addUnit, closest, objToStyle } from '../common/util'
import { useTouch } from '../composables/useTouch'
const touch = useTouch()
const props = defineProps(floatingPanelProps)
const emit = defineEmits(['update:height', 'height-change'])
const heightValue = ref<number>(props.height)
const DAMP = 0.2 // 阻尼系数
let startY: number // 起始位置
const windowHeight = ref<number>(0)
const dragging = ref<boolean>(false) // 是否正在拖拽
const boundary = computed(() => ({
min: props.anchors[0] ? props.anchors[0] : 100,
max: props.anchors[props.anchors.length - 1] ? props.anchors[props.anchors.length - 1] : Math.round(windowHeight.value * 0.6)
}))
const anchors = computed(() => (props.anchors.length >= 2 ? props.anchors : [boundary.value.min, boundary.value.max]))
const rootStyle = computed(() => {
const style: CSSProperties = {
height: addUnit(boundary.value.max),
transform: `translateY(calc(100% + ${addUnit(-heightValue.value)}))`,
transition: !dragging.value ? `transform ${props.duration}ms cubic-bezier(0.18, 0.89, 0.32, 1.28)` : 'none'
}
return `${objToStyle(style)}${props.customStyle}`
})
const updateHeight = (value: number) => {
heightValue.value = value
emit('update:height', value)
}
const handleTouchStart = (event: TouchEvent) => {
touch.touchStart(event)
dragging.value = true
startY = -heightValue.value
}
const handleTouchMove = (event: TouchEvent) => {
const target = event.currentTarget as any
if (target.dataset.id == 'content') {
if (!props.contentDraggable) return
}
touch.touchMove(event)
const moveY = touch.deltaY.value + startY
updateHeight(-ease(moveY))
}
const handleTouchEnd = () => {
dragging.value = false
updateHeight(closest(anchors.value, heightValue.value))
if (heightValue.value !== -startY) {
emit('height-change', { height: heightValue.value })
}
}
const ease = (y: number) => {
const absDistance = Math.abs(y)
const { min, max } = boundary.value
if (absDistance > max) {
return -(max + (absDistance - max) * DAMP)
}
if (absDistance < min) {
return -(min - (min - absDistance) * DAMP)
}
return y
}
watch(
() => props.height,
(value) => {
heightValue.value = value
}
)
watch(
boundary,
() => {
updateHeight(closest(anchors.value, heightValue.value))
},
{ immediate: true }
)
onBeforeMount(() => {
const { windowHeight: _windowHeight } = uni.getSystemInfoSync()
windowHeight.value = _windowHeight
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>