这一版本优化了很多
This commit is contained in:
244
node_modules/wot-design-uni/components/wd-slide-verify/wd-slide-verify.vue
generated
vendored
Normal file
244
node_modules/wot-design-uni/components/wd-slide-verify/wd-slide-verify.vue
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<view :class="rootClass" :style="rootStyle">
|
||||
<!-- 背景提示文字 -->
|
||||
<view class="wd-slide-verify__text">
|
||||
<slot name="text">
|
||||
<text class="wd-slide-verify__text-inner">
|
||||
{{ slideVerifyText }}
|
||||
</text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 滑过区域 -->
|
||||
<view class="wd-slide-verify__track" :style="trackStyle">
|
||||
<view class="wd-slide-verify__track-text">
|
||||
<slot name="success-text">
|
||||
<text class="wd-slide-verify__track-text--success">
|
||||
{{ slideVerifySuccessText }}
|
||||
</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 滑块 -->
|
||||
<view
|
||||
class="wd-slide-verify__button"
|
||||
@touchstart.prevent="onTouchStart"
|
||||
@touchmove.prevent="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
:style="buttonStyle"
|
||||
>
|
||||
<slot v-if="isPass" name="success-icon">
|
||||
<view
|
||||
class="wd-slide-verify__button-icon--success"
|
||||
:style="{
|
||||
backgroundColor: activeBackgroundColor
|
||||
}"
|
||||
>
|
||||
<wd-icon :name="successIcon" :size="successIconSize" color="#fff" />
|
||||
</view>
|
||||
</slot>
|
||||
|
||||
<slot v-else name="icon">
|
||||
<view class="wd-slide-verify__button-icon">
|
||||
<wd-icon :name="icon" :size="iconSize" />
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-slide-verify',
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
virtualHost: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onBeforeUnmount, type CSSProperties } from 'vue'
|
||||
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||
import { slideVerifyProps, type SlideVerifyExpose } from './types'
|
||||
import { useTouch } from '../composables/useTouch'
|
||||
import { useTranslate } from '../composables/useTranslate'
|
||||
import { objToStyle, addUnit, isDef } from '../common/util'
|
||||
|
||||
const props = defineProps(slideVerifyProps)
|
||||
const emit = defineEmits(['success', 'fail'])
|
||||
|
||||
const touch = useTouch()
|
||||
const { translate } = useTranslate('slideVerify')
|
||||
|
||||
const slideVerifyText = computed(() => {
|
||||
return isDef(props.text) && props.text !== '' ? props.text : translate('text')
|
||||
})
|
||||
|
||||
const slideVerifySuccessText = computed(() => {
|
||||
return isDef(props.successText) && props.successText !== '' ? props.successText : translate('successText')
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return [
|
||||
'wd-slide-verify',
|
||||
{
|
||||
'is-disabled': props.disabled,
|
||||
'is-success': isPass.value,
|
||||
'is-dragging': isDragging.value
|
||||
},
|
||||
props.customClass
|
||||
]
|
||||
})
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const style: CSSProperties = {
|
||||
width: addUnit(props.width),
|
||||
height: addUnit(props.height),
|
||||
backgroundColor: props.backgroundColor
|
||||
}
|
||||
|
||||
return `${objToStyle(style)}${props.customStyle}`
|
||||
})
|
||||
|
||||
const buttonStyle = computed(() => {
|
||||
const size = props.height
|
||||
const style: CSSProperties = {
|
||||
width: addUnit(size),
|
||||
height: addUnit(size),
|
||||
transform: `translate(${currentPosition.value}px, 0)`,
|
||||
transition: isResetting.value ? 'all 0.3s ease' : 'none',
|
||||
'--wd-slide-verify-button-size': addUnit(size)
|
||||
}
|
||||
return objToStyle(style)
|
||||
})
|
||||
|
||||
const trackStyle = computed(() => {
|
||||
const style: CSSProperties = {
|
||||
width: `${currentPosition.value}px`,
|
||||
background: props.activeBackgroundColor,
|
||||
'--wot-slide-verify-track-width': addUnit(props.width)
|
||||
}
|
||||
return objToStyle(style)
|
||||
})
|
||||
|
||||
/**
|
||||
* 从字符串或数字中解析出有效的数字。
|
||||
*
|
||||
* - 对于 number 类型,直接返回原值(可能包括 Infinity、-Infinity)。
|
||||
* - 对于 string 类型,使用 `parseFloat` 解析前缀中的数字。
|
||||
* - 当无法解析出有效数字(结果为 NaN)时,返回 0 作为安全默认值。
|
||||
*
|
||||
* 注意:后续使用该函数的逻辑(如 `maxPosition` 计算)会额外通过 `isFinite`
|
||||
* 等判断过滤掉 Infinity / 非法值,因此这里不会主动抛错,而是保证返回一个 number。
|
||||
*/
|
||||
const parseNumber = (value: string | number): number => {
|
||||
if (typeof value === 'number') return value
|
||||
const num = parseFloat(String(value))
|
||||
return isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
// 最大位置,避免超出了范围导致展示异常
|
||||
const maxPosition = computed(() => {
|
||||
const width = parseNumber(props.width)
|
||||
const height = parseNumber(props.height)
|
||||
if (!isFinite(width) || !isFinite(height) || width <= 0 || height <= 0) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(0, width - height)
|
||||
})
|
||||
|
||||
// 完成状态判断
|
||||
const isComplete = computed(() => {
|
||||
const distance = Math.abs(maxPosition.value - currentPosition.value)
|
||||
return distance <= parseNumber(props.tolerance) // 容差范围内完成
|
||||
})
|
||||
|
||||
// 位置状态
|
||||
const currentPosition = ref<number>(0)
|
||||
const startPosition = ref<number>(0)
|
||||
// 成功状态
|
||||
const isPass = ref<boolean>(false)
|
||||
// 拖动状态
|
||||
const isDragging = ref<boolean>(false)
|
||||
// 回弹
|
||||
const isResetting = ref(false)
|
||||
|
||||
const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max))
|
||||
const updatePosition = (position: number) => {
|
||||
// 限制位置在允许范围内
|
||||
currentPosition.value = clamp(position, 0, maxPosition.value)
|
||||
}
|
||||
|
||||
const isDisabled = computed(() => props.disabled || isPass.value)
|
||||
const onTouchStart = (event: TouchEvent): void => {
|
||||
if (isDisabled.value || isDragging.value) return
|
||||
|
||||
touch.touchStart(event)
|
||||
startPosition.value = currentPosition.value
|
||||
isDragging.value = true
|
||||
}
|
||||
|
||||
const onTouchMove = (event: TouchEvent): void => {
|
||||
if (isDisabled.value || !isDragging.value) return
|
||||
|
||||
touch.touchMove(event)
|
||||
updatePosition(startPosition.value + touch.deltaX.value)
|
||||
}
|
||||
// 控制回弹
|
||||
const timer = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const onTouchEnd = (): void => {
|
||||
if (isDisabled.value || !isDragging.value) return
|
||||
isDragging.value = false
|
||||
|
||||
if (isComplete.value) {
|
||||
// 完成
|
||||
updatePosition(maxPosition.value)
|
||||
isPass.value = true
|
||||
emit('success')
|
||||
} else {
|
||||
isResetting.value = true
|
||||
// 失败回到起点
|
||||
updatePosition(0)
|
||||
emit('fail')
|
||||
|
||||
timer.value = setTimeout(() => {
|
||||
isResetting.value = false
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer.value !== null) {
|
||||
clearTimeout(timer.value)
|
||||
timer.value = null
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 重置验证组件到初始状态
|
||||
*/
|
||||
const reset = () => {
|
||||
if (timer.value !== null) {
|
||||
clearTimeout(timer.value)
|
||||
timer.value = null
|
||||
}
|
||||
isResetting.value = true
|
||||
currentPosition.value = 0
|
||||
startPosition.value = 0
|
||||
isPass.value = false
|
||||
isDragging.value = false
|
||||
timer.value = setTimeout(() => {
|
||||
isResetting.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
defineExpose<SlideVerifyExpose>({ reset })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
Reference in New Issue
Block a user