Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-input-otp/cl-input-otp.uvue

257 lines
4.3 KiB
Plaintext
Raw Normal View History

2025-07-21 16:47:04 +08:00
<template>
<view
class="cl-input-otp"
:class="[
{
'cl-input-otp--disabled': disabled
},
pt.className
]"
>
2025-07-21 16:47:04 +08:00
<view class="cl-input-otp__inner">
<cl-input
v-model="value"
ref="inputRef"
:type="inputType"
:pt="{
className: '!h-full'
}"
:disabled="disabled"
:autofocus="autofocus"
:maxlength="length"
:hold-keyboard="false"
:clearable="false"
2025-08-20 17:05:17 +08:00
@change="onChange"
2025-07-21 16:47:04 +08:00
></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,
2025-09-04 20:18:18 +08:00
'is-active': value.length >= index && isFocus
2025-07-21 16:47:04 +08:00
},
pt.item?.className
]"
>
<cl-text
2025-09-04 20:18:18 +08:00
:color="value.length >= index && isFocus ? 'primary' : ''"
:pt="{
className: pt.value?.className
}"
>{{ item }}</cl-text
2025-07-21 16:47:04 +08:00
>
<view
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, ref, watch, type PropType, type Ref } from "vue";
import type { ClInputType, PassThroughProps } from "../../types";
import { isDark, 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 value = ref(props.modelValue) as Ref<string>;
/**
* 是否聚焦状态
*/
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;
});
/**
* 监听绑定值变化
* 同步更新内部值
*/
watch(
computed(() => props.modelValue),
(val: string) => {
value.value = val;
}
);
/**
* 输入事件处理
* @param val 输入值
*/
2025-08-20 17:05:17 +08:00
function onChange(val: string) {
2025-07-21 16:47:04 +08:00
// 更新绑定值
emit("update:modelValue", val);
// 输入完成时触发done事件
if (val.length == props.length) {
uni.hideKeyboard();
emit("done", val);
}
}
</script>
<style lang="scss" scoped>
.cl-input-otp {
2025-09-03 10:01:28 +08:00
@apply relative overflow-visible;
2025-07-21 16:47:04 +08:00
&__inner {
@apply absolute top-0 h-full z-10;
opacity: 0;
2025-09-03 10:01:28 +08:00
left: 0;
width: 100%;
2025-07-21 16:47:04 +08:00
}
&__list {
@apply flex flex-row relative;
margin: 0 -10rpx;
}
&__item {
2025-09-04 20:18:18 +08:00
@apply flex flex-row items-center justify-center duration-100;
2025-07-21 16:47:04 +08:00
@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 {
2025-09-04 20:18:18 +08:00
@apply absolute duration-100;
2025-07-21 16:47:04 +08:00
@apply bg-primary-500;
width: 2rpx;
2025-09-04 20:18:18 +08:00
height: 24rpx;
2025-07-21 16:47:04 +08:00
}
// #ifdef H5 || MP
&__cursor {
animation: flash 1s infinite ease;
}
@keyframes flash {
0% {
2025-09-04 20:18:18 +08:00
opacity: 0;
2025-07-21 16:47:04 +08:00
}
50% {
opacity: 1;
}
100% {
2025-09-04 20:18:18 +08:00
opacity: 0;
2025-07-21 16:47:04 +08:00
}
}
// #endif
&--disabled {
@apply opacity-50;
}
2025-07-21 16:47:04 +08:00
}
</style>