lilishop-uniapp/components/verify-code/verify-code.vue

279 lines
6.2 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="xt__verify-code">
<!-- 输入框 -->
<input
id="xt__input"
:value="code"
class="xt__input"
:focus="isFocus"
:password="isPassword"
:type="inputType"
:maxlength="size"
@input="input"
@focus="inputFocus"
@blur="inputBlur"
/>
<!-- 光标 -->
<view
id="xt__cursor"
v-if="cursorVisible && type !== 'middle'"
class="xt__cursor"
:style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }"
></view>
<!-- 输入框 - 组 -->
<view id="xt__input-ground" class="xt__input-ground">
<template v-for="(item, index) in size">
<view
:key="index"
:style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
:class="['xt__box', `xt__box-${type + ''}`, `xt__box::after`]"
>
<view :style="{ borderColor: boxActiveColor }" class="xt__middle-line" v-if="type === 'middle' && !code[index]"></view>
<text class="xt__code-text">{{ code[index] | codeFormat(isPassword) }}</text>
</view>
</template>
</view>
</view>
</template>
<script>
/**
* @description 输入验证码组件
* @property {string} type = [box|middle|bottom] - 显示类型 默认box -eg:bottom
* @property {string} inputType = [text|number] - 输入框类型 默认number -eg:number
* @property {number} size = [4|6] - 支持的验证码数量 默认6 -eg:6
* @property {boolean} isFocus - 是否立即聚焦 默认true
* @property {boolean} isPassword - 是否以密码形式显示 默认false -eg:false
* @property {string} cursorColor - 光标颜色 默认:#cccccc
* @property {string} boxNormalColor - 光标未聚焦到的框的颜色 默认:#cccccc
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
* @event {Function(data)} confirm - 输入完成
*/
export default {
name: 'xt-verify-code',
props: {
value: {
type: String,
default: () => ''
},
type: {
type: String,
default: () => 'box'
},
inputType: {
type: String,
default: () => 'number'
},
size: {
type: Number,
default: () => 6
},
isFocus: {
type: Boolean,
default: () => true
},
isPassword: {
type: Boolean,
default: () => false
},
cursorColor: {
type: String,
default: () => '#cccccc'
},
boxNormalColor: {
type: String,
default: () => '#cccccc'
},
boxActiveColor: {
type: String,
default: () => '#000000'
}
},
model: {
prop: 'value',
event: 'input'
},
data() {
return {
cursorVisible: false,
cursorHeight: 35,
code: '', // 输入的验证码
codeCursorLeft: [] // 向左移动的距离数组
};
},
created() {
this.cursorVisible = this.isFocus;
},
mounted() {
this.init();
},
methods: {
/**
* @description 初始化
*/
init() {
this.getCodeCursorLeft();
this.setCursorHeight();
},
/**
* @description 获取元素节点
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
* @param {Function} callback - 回调函数
*/
getElement(elm, type = 'single', callback) {
uni
.createSelectorQuery()
.in(this)
[type === 'array' ? 'selectAll' : 'select'](elm)
.boundingClientRect()
.exec(data => {
callback(data[0]);
});
},
/**
* @description 计算光标的高度
*/
setCursorHeight() {
this.getElement('.xt__box', 'single', boxElm => {
this.cursorHeight = boxElm.height * 0.6;
});
},
/**
* @description 获取光标在每一个box的left位置
*/
getCodeCursorLeft() {
// 获取父级框的位置信息
this.getElement('#xt__input-ground', 'single', parentElm => {
const parentLeft = parentElm.left;
// 获取各个box信息
this.getElement('.xt__box', 'array', elms => {
this.codeCursorLeft = [];
elms.forEach(elm => {
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
});
});
});
},
// 输入框输入变化的回调
input(e) {
const value = e.detail.value;
this.cursorVisible = value.length !== this.size;
this.$emit('input', value);
this.inputSuccess(value);
},
// 输入完成回调
inputSuccess(value) {
if (value.length === this.size) {
this.$emit('confirm', value);
}
},
// 输入聚焦
inputFocus() {
this.cursorVisible = this.code.length !== this.size;
},
// 输入失去焦点
inputBlur() {
this.cursorVisible = false;
}
},
watch: {
value(val) {
this.code = val;
}
},
filters: {
codeFormat(val, isPassword) {
let value = '';
if (val) {
value = isPassword ? '*' : val;
}
return value;
}
}
};
</script>
<style lang="scss" scoped>
.xt__verify-code {
position: relative;
width: 100%;
box-sizing: border-box;
.xt__input {
height: 100%;
width: 200%;
position: absolute;
left: -100%;
z-index: 1;
}
.xt__cursor {
position: absolute;
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 2px;
animation-name: cursor;
animation-duration: 0.8s;
animation-iteration-count: infinite;
}
.xt__input-ground {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
box-sizing: border-box;
.xt__box {
position: relative;
display: inline-block;
width: 76rpx;
height: 112rpx;
&-bottom {
border-bottom-width: 2px;
border-bottom-style: solid;
}
&-box {
border-width: 2px;
border-style: solid;
}
&-middle {
border: none;
}
.xt__middle-line {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform: translate(-50%, -50%);
border-bottom-width: 2px;
border-bottom-style: solid;
}
.xt__code-text {
position: absolute;
top: 50%;
left: 50%;
font-size:52rpx;
transform: translate(-50%, -50%);
}
}
}
}
@keyframes cursor {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>