更新:登录功能
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
@import '../common/abstracts/variable';
|
||||
@import '../common/abstracts/mixin';
|
||||
|
||||
@include b(swipe-action) {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@include e(left) {
|
||||
left: 0;
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
@include e(right) {
|
||||
right: 0;
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.wd-swipe-action__left,
|
||||
.wd-swipe-action__right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { ExtractPropTypes, PropType } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeStringProp } from '../common/props'
|
||||
|
||||
export type SwipeActionStatus = 'left' | 'close' | 'right'
|
||||
|
||||
// 点击关闭按钮、滑动关闭按钮、通过控制value关闭按钮
|
||||
export type SwipeActionReason = 'click' | 'swipe' | 'value'
|
||||
|
||||
export type SwipeActionPosition = SwipeActionStatus | 'inside'
|
||||
|
||||
export type SwipeActionBeforeClose = (reason: SwipeActionReason, position: SwipeActionPosition) => void
|
||||
|
||||
export const swipeActionProps = {
|
||||
...baseProps,
|
||||
|
||||
/**
|
||||
* 滑动按钮的状态,使用v-model进行双向绑定。
|
||||
* 可选值为:'left'(左滑)、'close'(关闭状态)、'right'(右滑)。
|
||||
* 类型:string
|
||||
* 默认值:'close'
|
||||
*/
|
||||
modelValue: makeStringProp<SwipeActionStatus>('close'),
|
||||
|
||||
/**
|
||||
* 是否禁用滑动操作。
|
||||
* 类型:boolean
|
||||
* 默认值:false
|
||||
*/
|
||||
disabled: makeBooleanProp(false),
|
||||
|
||||
/**
|
||||
* 在关闭滑动按钮前调用的钩子函数。
|
||||
* 可以在此函数中执行一些关闭前的操作,如确认提示等。
|
||||
* 类型:function
|
||||
* 默认值:无
|
||||
*/
|
||||
beforeClose: Function as PropType<SwipeActionBeforeClose>
|
||||
}
|
||||
|
||||
export type SwipeActionProps = ExtractPropTypes<typeof swipeActionProps>
|
||||
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<!--注意阻止横向滑动的穿透:横向移动时阻止冒泡-->
|
||||
<view
|
||||
:class="`wd-swipe-action ${customClass}`"
|
||||
:style="customStyle"
|
||||
@click.stop="onClick()"
|
||||
@touchstart="startDrag"
|
||||
@touchmove="onDrag"
|
||||
@touchend="endDrag"
|
||||
@touchcancel="endDrag"
|
||||
>
|
||||
<!--容器-->
|
||||
<view class="wd-swipe-action__wrapper" :style="wrapperStyle">
|
||||
<!--左侧操作-->
|
||||
<view class="wd-swipe-action__left" @click="onClick('left')">
|
||||
<slot name="left" />
|
||||
</view>
|
||||
<!--内容-->
|
||||
<slot />
|
||||
<!--右侧操作-->
|
||||
<view class="wd-swipe-action__right" @click="onClick('right')">
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-swipe-action',
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
virtualHost: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { getCurrentInstance, inject, onBeforeMount, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { closeOther, pushToQueue, removeFromQueue } from '../common/clickoutside'
|
||||
import { type Queue, queueKey } from '../composables/useQueue'
|
||||
import { useTouch } from '../composables/useTouch'
|
||||
import { getRect } from '../common/util'
|
||||
import { swipeActionProps, type SwipeActionPosition, type SwipeActionReason, type SwipeActionStatus } from './types'
|
||||
|
||||
const props = defineProps(swipeActionProps)
|
||||
const emit = defineEmits(['click', 'update:modelValue'])
|
||||
|
||||
const queue = inject<Queue | null>(queueKey, null)
|
||||
|
||||
const wrapperStyle = ref<string>('')
|
||||
|
||||
// 滑动开始时,wrapper的偏移量
|
||||
const originOffset = ref<number>(0)
|
||||
// wrapper现在的偏移量
|
||||
const wrapperOffset = ref<number>(0)
|
||||
// 是否处于滑动状态
|
||||
const touching = ref<boolean>(false)
|
||||
|
||||
const touch = useTouch()
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value, old) => {
|
||||
changeState(value, old)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (queue && queue.pushToQueue) {
|
||||
queue.pushToQueue(proxy)
|
||||
} else {
|
||||
pushToQueue(proxy)
|
||||
}
|
||||
// 滑动开始时,wrapper的偏移量
|
||||
originOffset.value = 0
|
||||
// wrapper现在的偏移量
|
||||
wrapperOffset.value = 0
|
||||
// 是否处于滑动状态
|
||||
touching.value = false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
touching.value = true
|
||||
changeState(props.modelValue)
|
||||
touching.value = false
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (queue && queue.removeFromQueue) {
|
||||
queue.removeFromQueue(proxy)
|
||||
} else {
|
||||
removeFromQueue(proxy)
|
||||
}
|
||||
})
|
||||
|
||||
function changeState(value: SwipeActionStatus, old?: SwipeActionStatus) {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
getWidths().then(([leftWidth, rightWidth]) => {
|
||||
switch (value) {
|
||||
case 'close':
|
||||
// 调用此函数时,偏移量本就是0
|
||||
if (wrapperOffset.value === 0) return
|
||||
close('value', old)
|
||||
break
|
||||
case 'left':
|
||||
swipeMove(leftWidth)
|
||||
break
|
||||
case 'right':
|
||||
swipeMove(-rightWidth)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取左/右操作按钮的宽度
|
||||
* @return {Promise<[Number, Number]>} 左宽度、右宽度
|
||||
*/
|
||||
function getWidths() {
|
||||
return Promise.all([
|
||||
getRect('.wd-swipe-action__left', false, proxy).then((rect) => {
|
||||
return rect.width ? rect.width : 0
|
||||
}),
|
||||
getRect('.wd-swipe-action__right', false, proxy).then((rect) => {
|
||||
return rect.width ? rect.width : 0
|
||||
})
|
||||
])
|
||||
}
|
||||
/**
|
||||
* @description wrapper滑动函数
|
||||
* @param {Number} offset 滑动漂移量
|
||||
*/
|
||||
function swipeMove(offset = 0) {
|
||||
// this.offset = offset
|
||||
const transform = `translate3d(${offset}px, 0, 0)`
|
||||
// 跟随手指滑动,不需要动画
|
||||
const transition = touching.value ? 'none' : '.6s cubic-bezier(0.18, 0.89, 0.32, 1)'
|
||||
wrapperStyle.value = `
|
||||
-webkit-transform: ${transform};
|
||||
-webkit-transition: ${transition};
|
||||
transform: ${transform};
|
||||
transition: ${transition};
|
||||
`
|
||||
// 记录容器当前偏移的量
|
||||
wrapperOffset.value = offset
|
||||
}
|
||||
/**
|
||||
* @description click的handler
|
||||
* @param event
|
||||
*/
|
||||
function onClick(position?: SwipeActionPosition) {
|
||||
if (props.disabled || wrapperOffset.value === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
position = position || 'inside'
|
||||
close('click', position)
|
||||
emit('click', {
|
||||
value: position
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 开始滑动
|
||||
*/
|
||||
function startDrag(event: TouchEvent) {
|
||||
if (props.disabled) return
|
||||
|
||||
originOffset.value = wrapperOffset.value
|
||||
touch.touchStart(event)
|
||||
if (queue && queue.closeOther) {
|
||||
queue.closeOther(proxy)
|
||||
} else {
|
||||
closeOther(proxy)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description 滑动时,逐渐展示按钮
|
||||
* @param event
|
||||
*/
|
||||
function onDrag(event: TouchEvent) {
|
||||
if (props.disabled) return
|
||||
|
||||
touch.touchMove(event)
|
||||
if (touch.direction.value === 'vertical') {
|
||||
return
|
||||
} else {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
touching.value = true
|
||||
|
||||
// 本次滑动,wrapper应该设置的偏移量
|
||||
const offset = originOffset.value + touch.deltaX.value
|
||||
getWidths().then(([leftWidth, rightWidth]) => {
|
||||
// 如果需要想滑出来的按钮不存在,对应的按钮肯定滑不出来,容器处于初始状态。此时需要模拟一下位于此处的start事件。
|
||||
if ((leftWidth === 0 && offset > 0) || (rightWidth === 0 && offset < 0)) {
|
||||
swipeMove(0)
|
||||
return startDrag(event)
|
||||
}
|
||||
// 按钮已经展示完了,再滑动没有任何意义,相当于滑动结束。此时需要模拟一下位于此处的start事件。
|
||||
if (leftWidth !== 0 && offset >= leftWidth) {
|
||||
swipeMove(leftWidth)
|
||||
return startDrag(event)
|
||||
} else if (rightWidth !== 0 && -offset >= rightWidth) {
|
||||
swipeMove(-rightWidth)
|
||||
return startDrag(event)
|
||||
}
|
||||
swipeMove(offset)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 滑动结束,自动修正位置
|
||||
*/
|
||||
function endDrag() {
|
||||
if (props.disabled) return
|
||||
// 滑出"操作按钮"的阈值
|
||||
const THRESHOLD = 0.3
|
||||
touching.value = false
|
||||
|
||||
getWidths().then(([leftWidth, rightWidth]) => {
|
||||
if (
|
||||
originOffset.value < 0 && // 之前展示的是右按钮
|
||||
wrapperOffset.value < 0 && // 目前仍然是右按钮
|
||||
wrapperOffset.value - originOffset.value < rightWidth * THRESHOLD // 并且滑动的范围不超过右边框阀值
|
||||
) {
|
||||
swipeMove(-rightWidth) // 回归右按钮
|
||||
emit('update:modelValue', 'right')
|
||||
} else if (
|
||||
originOffset.value > 0 && // 之前展示的是左按钮
|
||||
wrapperOffset.value > 0 && // 现在仍然是左按钮
|
||||
originOffset.value - wrapperOffset.value < leftWidth * THRESHOLD // 并且滑动的范围不超过左按钮阀值
|
||||
) {
|
||||
swipeMove(leftWidth) // 回归左按钮
|
||||
emit('update:modelValue', 'left')
|
||||
} else if (
|
||||
rightWidth > 0 &&
|
||||
originOffset.value >= 0 && // 之前是初始状态或者展示左按钮显
|
||||
wrapperOffset.value < 0 && // 现在展示右按钮
|
||||
Math.abs(wrapperOffset.value) > rightWidth * THRESHOLD // 视图中已经展示的右按钮长度超过阀值
|
||||
) {
|
||||
swipeMove(-rightWidth)
|
||||
emit('update:modelValue', 'right')
|
||||
} else if (
|
||||
leftWidth > 0 &&
|
||||
originOffset.value <= 0 && // 之前初始状态或者右按钮显示
|
||||
wrapperOffset.value > 0 && // 现在左按钮
|
||||
Math.abs(wrapperOffset.value) > leftWidth * THRESHOLD // 视图中已经展示的左按钮长度超过阀值
|
||||
) {
|
||||
swipeMove(leftWidth)
|
||||
emit('update:modelValue', 'left')
|
||||
} else {
|
||||
// 回归初始状态
|
||||
close('swipe')
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 关闭操过按钮,并在合适的时候调用 beforeClose
|
||||
*/
|
||||
function close(reason: SwipeActionReason, position?: SwipeActionPosition) {
|
||||
if (reason === 'swipe' && originOffset.value === 0) {
|
||||
// offset:0 ——> offset:0
|
||||
return swipeMove(0)
|
||||
} else if (reason === 'swipe' && originOffset.value > 0) {
|
||||
// offset > 0 ——> offset:0
|
||||
position = 'left'
|
||||
} else if (reason === 'swipe' && originOffset.value < 0) {
|
||||
// offset < 0 ——> offset:0
|
||||
position = 'right'
|
||||
}
|
||||
|
||||
if (reason && position) {
|
||||
props.beforeClose && props.beforeClose(reason, position)
|
||||
}
|
||||
|
||||
swipeMove(0)
|
||||
if (props.modelValue !== 'close') {
|
||||
emit('update:modelValue', 'close')
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ close })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
Reference in New Issue
Block a user