2025-08-13 17:19:40 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<view :class="['drawer', { 'drawer-open': isVisible }]" :style="drawerStyle">
|
|
|
|
|
|
<view class="drawer-content" @touchstart.passive="onTouchStart" @touchmove.passive="onTouchMove"
|
|
|
|
|
|
@touchend="onTouchEnd" @touchcancel="onTouchEnd">
|
|
|
|
|
|
<!-- 抽屉中间的半圆 -->
|
2025-08-29 17:33:30 +08:00
|
|
|
|
<view class="drawer-content-circle" :class="circletarget?`pulse`:``" :style="isVisible?{}:{background:`linear-gradient(to bottom,#62E8FF,#0097FF)`}" @click="whiteDrawer">
|
2025-08-13 17:19:40 +08:00
|
|
|
|
<image class="drawer-img" :src="isVisible?'/static/index/watch/whitearrow.png':'/static/index/watch/arrow.png' " />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<slot />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-08-29 17:33:30 +08:00
|
|
|
|
<script setup lang="ts">
|
2025-08-13 17:19:40 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ref,
|
|
|
|
|
|
defineProps,
|
|
|
|
|
|
computed,
|
|
|
|
|
|
onMounted
|
|
|
|
|
|
} from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
// 控制抽屉显示隐藏
|
|
|
|
|
|
const isVisible = ref(false)
|
2025-08-29 17:33:30 +08:00
|
|
|
|
// 定义 emit 事件
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(e: 'open'): void
|
|
|
|
|
|
}>()
|
2025-08-13 17:19:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 接收父组件传入的宽度百分比
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
widNumber: {
|
|
|
|
|
|
type: Number,
|
2025-08-21 16:51:53 +08:00
|
|
|
|
default: 25
|
2025-08-29 17:33:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
circletarget:{
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
2025-08-13 17:19:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 获取屏幕宽度,用于拖拽计算
|
|
|
|
|
|
const screenWidth = ref(0)
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
const sys = uni.getSystemInfoSync()
|
|
|
|
|
|
screenWidth.value = sys.screenWidth
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 拖拽状态
|
|
|
|
|
|
const startX = ref(0)
|
|
|
|
|
|
const dragging = ref(false)
|
|
|
|
|
|
const currentOffset = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
// 计算样式:宽度 + 拖拽或开关 transform
|
|
|
|
|
|
const drawerStyle = computed(() => {
|
|
|
|
|
|
const widthPct = `${props.widNumber}%`
|
|
|
|
|
|
if (dragging.value) {
|
|
|
|
|
|
// 拖动时,X 方向正值向右移动抽屉
|
|
|
|
|
|
const offset = currentOffset.value
|
|
|
|
|
|
return {
|
|
|
|
|
|
width: widthPct,
|
|
|
|
|
|
transform: `translateX(${offset}px)`,
|
|
|
|
|
|
transition: 'none'
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 非拖动交由 class 控制开关状态
|
|
|
|
|
|
return {
|
|
|
|
|
|
width: widthPct
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 打开/关闭
|
|
|
|
|
|
function openDrawer() {
|
|
|
|
|
|
isVisible.value = true
|
2025-08-29 17:33:30 +08:00
|
|
|
|
emit('open')
|
2025-08-13 17:19:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeDrawer() {
|
|
|
|
|
|
isVisible.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function whiteDrawer() {
|
|
|
|
|
|
// 点击半圆:切换 & 旋转
|
2025-08-29 17:33:30 +08:00
|
|
|
|
if(isVisible.value){
|
|
|
|
|
|
closeDrawer()
|
|
|
|
|
|
}else{
|
|
|
|
|
|
openDrawer()
|
|
|
|
|
|
}
|
2025-08-13 17:19:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
openDrawer,
|
|
|
|
|
|
closeDrawer
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 拖拽事件
|
|
|
|
|
|
function onTouchStart(e) {
|
|
|
|
|
|
if (!isVisible.value) return
|
|
|
|
|
|
dragging.value = true
|
|
|
|
|
|
currentOffset.value = 0
|
|
|
|
|
|
startX.value = e.touches[0].pageX
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onTouchMove(e) {
|
|
|
|
|
|
if (!dragging.value) return
|
|
|
|
|
|
const delta = e.touches[0].pageX - startX.value
|
|
|
|
|
|
currentOffset.value = delta > 0 ? delta : 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onTouchEnd() {
|
|
|
|
|
|
if (!dragging.value) return
|
|
|
|
|
|
dragging.value = false
|
|
|
|
|
|
const halfPx = screenWidth.value * (props.widNumber / 100) / 2
|
|
|
|
|
|
if (currentOffset.value > halfPx) {
|
|
|
|
|
|
closeDrawer()
|
|
|
|
|
|
}
|
|
|
|
|
|
currentOffset.value = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.drawer {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
height: 85vh;
|
|
|
|
|
|
background: #eff0f4;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
border-top-left-radius: 80rpx;
|
|
|
|
|
|
border-bottom-left-radius: 80rpx;
|
|
|
|
|
|
/* 初始隐藏 */
|
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
|
transition: transform 0.4s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drawer-open {
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drawer-content {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drawer-content-circle {
|
|
|
|
|
|
position: absolute;
|
2025-08-21 16:51:53 +08:00
|
|
|
|
bottom: 240rpx;
|
2025-08-13 17:19:40 +08:00
|
|
|
|
left: -60rpx;
|
|
|
|
|
|
width: 150rpx;
|
|
|
|
|
|
height: 160rpx;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
z-index: -1;
|
|
|
|
|
|
// background: #fff;
|
|
|
|
|
|
background: linear-gradient(to right,
|
|
|
|
|
|
#fff 0rpx,
|
|
|
|
|
|
#eff0f4 60rpx,
|
|
|
|
|
|
#eff0f4 100%);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
clip-path: inset(0 60% 0 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drawer-img {
|
|
|
|
|
|
width: 20rpx;
|
|
|
|
|
|
height: 20rpx;
|
|
|
|
|
|
margin-left: 25rpx;
|
2025-08-21 16:51:53 +08:00
|
|
|
|
// transform: rotate(180deg);
|
2025-08-13 17:19:40 +08:00
|
|
|
|
}
|
2025-08-29 17:33:30 +08:00
|
|
|
|
.target {
|
|
|
|
|
|
--color: #99C9FD;
|
|
|
|
|
|
--thick: 2px;
|
|
|
|
|
|
--radius: 150rpx;
|
|
|
|
|
|
--outline-offset: 5rpx;
|
|
|
|
|
|
/* 外扩多少 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 内层虚线(你现在用的) */
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background-color: #ddf0ff;
|
|
|
|
|
|
/* 内部背景 */
|
|
|
|
|
|
animation: scalePulse 360ms cubic-bezier(.2, .8, .2, 1);
|
|
|
|
|
|
/* 外层虚线:放在 outline(不会影响元素尺寸) */
|
|
|
|
|
|
outline: var(--thick) dashed var(--color);
|
|
|
|
|
|
outline-offset: var(--outline-offset);
|
|
|
|
|
|
|
|
|
|
|
|
/* 保证文本 / 子元素在最上层 */
|
|
|
|
|
|
// position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.pulse{
|
|
|
|
|
|
/* 可调参数 */
|
|
|
|
|
|
--scale: 1.2;
|
|
|
|
|
|
--dur: 0.8s;
|
|
|
|
|
|
|
|
|
|
|
|
animation: pulse var(--dur) ease-in-out infinite;
|
|
|
|
|
|
transform-origin: center center;
|
|
|
|
|
|
will-change: transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 放大到一定值再回到原始(平滑) */
|
|
|
|
|
|
@keyframes pulse{
|
|
|
|
|
|
0% { transform: scale(1); }
|
|
|
|
|
|
50% { transform: scale(var(--scale)); }
|
|
|
|
|
|
100% { transform: scale(1); }
|
|
|
|
|
|
}
|
2025-08-13 17:19:40 +08:00
|
|
|
|
</style>
|