小程序初始提交
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<view
|
||||
class="cl-input-otp"
|
||||
:class="[
|
||||
{
|
||||
'cl-input-otp--disabled': disabled
|
||||
},
|
||||
pt.className
|
||||
]"
|
||||
>
|
||||
<view class="cl-input-otp__inner" @tap="onCursor()">
|
||||
<cl-input
|
||||
v-model="value"
|
||||
ref="inputRef"
|
||||
:type="inputType"
|
||||
:pt="{
|
||||
className: '!h-full'
|
||||
}"
|
||||
:disabled="disabled"
|
||||
:autofocus="autofocus"
|
||||
:maxlength="length"
|
||||
:hold-keyboard="false"
|
||||
:clearable="false"
|
||||
@change="onChange"
|
||||
></cl-input>
|
||||
</view>
|
||||
|
||||
<view class="cl-input-otp__list" :class="[pt.list?.className]">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="cl-input-otp__item"
|
||||
:class="[
|
||||
{
|
||||
'is-disabled': disabled,
|
||||
'is-dark': isDark,
|
||||
'is-active': value.length >= index && isFocus
|
||||
},
|
||||
pt.item?.className
|
||||
]"
|
||||
>
|
||||
<cl-text
|
||||
:color="value.length >= index && isFocus ? 'primary' : ''"
|
||||
:pt="{
|
||||
className: pt.value?.className
|
||||
}"
|
||||
>{{ item }}</cl-text
|
||||
>
|
||||
<view
|
||||
ref="cursorRef"
|
||||
class="cl-input-otp__cursor"
|
||||
:class="[pt.cursor?.className]"
|
||||
v-if="value.length == index && isFocus && item == ''"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref, watch, type PropType, type Ref } from "vue";
|
||||
import type { ClInputType, PassThroughProps } from "../../types";
|
||||
import { createAnimation, isDark, isEmpty, last, parsePt } from "@/cool";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-input-otp"
|
||||
});
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
// 透传样式
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 绑定值
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 是否自动聚焦
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 验证码位数
|
||||
length: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框类型
|
||||
inputType: {
|
||||
type: String as PropType<ClInputType>,
|
||||
default: "number"
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 事件定义
|
||||
* update:modelValue - 更新绑定值
|
||||
* done - 输入完成
|
||||
*/
|
||||
const emit = defineEmits(["update:modelValue", "done"]);
|
||||
|
||||
/**
|
||||
* 透传样式类型定义
|
||||
*/
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
list?: PassThroughProps;
|
||||
item?: PassThroughProps;
|
||||
cursor?: PassThroughProps;
|
||||
value?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传样式
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 输入框引用
|
||||
const inputRef = ref<ClInputComponentPublicInstance | null>(null);
|
||||
|
||||
// 光标引用
|
||||
const cursorRef = ref<UniElement[]>([]);
|
||||
|
||||
// 输入值
|
||||
const value = ref(props.modelValue);
|
||||
|
||||
/**
|
||||
* 是否聚焦状态
|
||||
*/
|
||||
const isFocus = computed<boolean>(() => {
|
||||
if (props.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputRef.value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (inputRef.value as ClInputComponentPublicInstance).isFocus;
|
||||
});
|
||||
|
||||
/**
|
||||
* 验证码数组
|
||||
* 根据长度生成空数组,每个位置填充对应的输入值
|
||||
*/
|
||||
const list = computed<string[]>(() => {
|
||||
const arr = [] as string[];
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
arr.push(value.value.charAt(i));
|
||||
}
|
||||
|
||||
return arr;
|
||||
});
|
||||
|
||||
/**
|
||||
* 光标动画
|
||||
*/
|
||||
async function onCursor() {
|
||||
await nextTick();
|
||||
|
||||
if (isEmpty(cursorRef.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
// #ifdef APP
|
||||
createAnimation(last(cursorRef.value), {
|
||||
duration: 600,
|
||||
loop: -1,
|
||||
alternate: true
|
||||
})
|
||||
.opacity("0", "1")
|
||||
.play();
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入事件处理
|
||||
* @param val 输入值
|
||||
*/
|
||||
function onChange(val: string) {
|
||||
// 更新绑定值
|
||||
emit("update:modelValue", val);
|
||||
|
||||
// 输入完成时触发done事件
|
||||
if (val.length == props.length) {
|
||||
uni.hideKeyboard();
|
||||
emit("done", val);
|
||||
}
|
||||
|
||||
// 更新光标动画
|
||||
onCursor();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
computed(() => props.modelValue),
|
||||
(val: string) => {
|
||||
value.value = val;
|
||||
onCursor();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-input-otp {
|
||||
@apply relative;
|
||||
|
||||
&__inner {
|
||||
@apply absolute top-0 h-full z-10;
|
||||
opacity: 0;
|
||||
// 小程序隐藏 placeholder
|
||||
left: -100%;
|
||||
width: 200%;
|
||||
}
|
||||
|
||||
&__list {
|
||||
@apply flex flex-row relative;
|
||||
margin: 0 -10rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
@apply flex flex-row items-center justify-center duration-100;
|
||||
@apply border border-solid border-surface-200 rounded-lg bg-white;
|
||||
height: 80rpx;
|
||||
width: 80rpx;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&.is-disabled {
|
||||
@apply bg-surface-100 opacity-70;
|
||||
}
|
||||
|
||||
&.is-dark {
|
||||
@apply bg-surface-800 border-surface-600;
|
||||
|
||||
&.is-disabled {
|
||||
@apply bg-surface-700;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@apply border-primary-500;
|
||||
}
|
||||
}
|
||||
|
||||
&__cursor {
|
||||
@apply absolute bg-primary-500;
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
|
||||
// #ifndef APP
|
||||
animation: blink 1s infinite;
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ClInputType, PassThroughProps } from "../../types";
|
||||
|
||||
export type ClInputOtpPassThrough = {
|
||||
className?: string;
|
||||
list?: PassThroughProps;
|
||||
item?: PassThroughProps;
|
||||
cursor?: PassThroughProps;
|
||||
value?: PassThroughProps;
|
||||
};
|
||||
|
||||
export type ClInputOtpProps = {
|
||||
className?: string;
|
||||
pt?: ClInputOtpPassThrough;
|
||||
modelValue?: string;
|
||||
autofocus?: boolean;
|
||||
length?: number;
|
||||
disabled?: boolean;
|
||||
inputType?: ClInputType;
|
||||
};
|
||||
Reference in New Issue
Block a user