更新:登录功能

This commit is contained in:
2025-11-04 12:37:04 +08:00
commit a21fb92916
897 changed files with 51500 additions and 0 deletions

View File

@@ -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%;
}

View File

@@ -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>

View File

@@ -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) {
// offset0 ——> offset0
return swipeMove(0)
} else if (reason === 'swipe' && originOffset.value > 0) {
// offset > 0 ——> offset0
position = 'left'
} else if (reason === 'swipe' && originOffset.value < 0) {
// offset < 0 ——> offset0
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>