这一版本优化了很多
This commit is contained in:
82
node_modules/wot-design-uni/components/wd-slide-verify/index.scss
generated
vendored
Normal file
82
node_modules/wot-design-uni/components/wd-slide-verify/index.scss
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
@import '../common/abstracts/variable';
|
||||
@import '../common/abstracts/mixin';
|
||||
|
||||
@include b(slide-verify) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
@include when(disabled) {
|
||||
cursor: not-allowed;
|
||||
|
||||
& {
|
||||
opacity: 0.8;
|
||||
|
||||
@include e(button) {
|
||||
color: $-slide-verify-disabled-color;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include e(text-inner) {
|
||||
color: $-slide-verify-text-color;
|
||||
font-size: $-slide-verify-text-size;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
@include e(track) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
transition: background 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@include e(track-text) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
min-width: $-slide-verify-track-width;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
@include m(success) {
|
||||
color: $-slide-verify-success-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(button) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $-slide-verify-button-bg;
|
||||
box-shadow: 0 2px 8px $-slide-verify-button-shadow;
|
||||
color: $-slide-verify-button-color;
|
||||
cursor: grab;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
// 按钮图标
|
||||
@include e(button-icon) {
|
||||
@include m(success) {
|
||||
width: calc($-slide-verify-button-size * 0.5);
|
||||
height: calc($-slide-verify-button-size * 0.5);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
node_modules/wot-design-uni/components/wd-slide-verify/types.ts
generated
vendored
Normal file
88
node_modules/wot-design-uni/components/wd-slide-verify/types.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { ComponentPublicInstance, ExtractPropTypes } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumericProp, makeStringProp } from '../common/props'
|
||||
|
||||
export const slideVerifyProps = {
|
||||
...baseProps,
|
||||
/**
|
||||
* 滑动条宽度(单位:px)
|
||||
* @default 300
|
||||
*/
|
||||
width: makeNumericProp(300),
|
||||
|
||||
/**
|
||||
* 滑块高度(单位:px)
|
||||
* @default 40
|
||||
*/
|
||||
height: makeNumericProp(40),
|
||||
|
||||
/**
|
||||
* 容错范围(单位:px),距离终点多少距离内视为验证通过
|
||||
* @default 10
|
||||
*/
|
||||
tolerance: makeNumericProp(10),
|
||||
|
||||
/**
|
||||
* 提示文字
|
||||
* @default '向右滑动验证'
|
||||
*/
|
||||
text: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* 验证成功提示文字
|
||||
* @default '验证通过'
|
||||
*/
|
||||
successText: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* 是否禁用
|
||||
* @default false
|
||||
*/
|
||||
disabled: makeBooleanProp(false),
|
||||
|
||||
/**
|
||||
* 背景颜色
|
||||
* @default '#F5F7FA'
|
||||
*/
|
||||
backgroundColor: makeStringProp('#F5F7FA'),
|
||||
|
||||
/**
|
||||
* 激活时的背景颜色
|
||||
* @default '#49C75F'
|
||||
*/
|
||||
activeBackgroundColor: makeStringProp('#49C75F'),
|
||||
|
||||
/**
|
||||
* 滑块图标名称
|
||||
* @default 'a-chevron-rightdouble'
|
||||
*/
|
||||
icon: makeStringProp('a-chevron-rightdouble'),
|
||||
|
||||
/**
|
||||
* 成功图标名称
|
||||
* @default 'check'
|
||||
*/
|
||||
successIcon: makeStringProp('check'),
|
||||
|
||||
/**
|
||||
* 图标大小(单位:px)
|
||||
* @default 20
|
||||
*/
|
||||
iconSize: makeNumericProp(20),
|
||||
|
||||
/**
|
||||
* 成功图标大小(单位:px)
|
||||
* @default 12
|
||||
*/
|
||||
successIconSize: makeNumericProp(12)
|
||||
}
|
||||
|
||||
export type SlideVerifyProps = ExtractPropTypes<typeof slideVerifyProps>
|
||||
|
||||
export type SlideVerifyExpose = {
|
||||
/**
|
||||
* 重置验证组件到初始状态
|
||||
*/
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export type SlideVerifyInstance = ComponentPublicInstance<SlideVerifyProps, SlideVerifyExpose>
|
||||
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