uniappSMS verification code input box

The requirement is that the SMS verification code requires a grid input box, as shown in the picture

I found a case online and changed it. Just upload the code.

Structure

<template>
<view class="verify-code">
<!-- Input box -->
<input id="input" :value="code" class="input" :focus="isFocus" :type="inputType" :maxlength="itemSize"
@input="onInput" @focus="inputFocus" @blur="inputBlur" />

<!-- Cursor -->
<view id="cursor" v-if="cursorVisible & amp; & amp; type !== 'middle'" class="cursor"
:style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }">
</view>

<!-- Input box - group -->
<view id="input-ground" class="input-ground">
<view v-for="(item, index) in itemSize" :key="index"
:style="{ borderColor: code.length === index & amp; & amp; cursorVisible ? boxActiveColor : boxNormalColor }"
:class="['box', `box-${type + ''}`, `box::after`]">
<view :style="{ borderColor: boxActiveColor }" class="middle-line"
v-if="type === 'middle' & amp; & amp; !code[index]"></view>

<text class="code-text">{<!-- -->{<!-- --> code[index] | codeFormat(isPassword) }}</text>
</view>
</view>
</view>
</template>

<script>
/**
* @description Enter verification code component
* @property {string} type = [box|middle|bottom] - Display type Default: box -eg:bottom
* @property {string} inputType = [text|number] - Input box type Default: number -eg:number
* @property {number} size - Number of verification code input boxes Default: 6 -eg:6
* @property {boolean} isFocus - whether to focus immediately. Default: true
* @property {boolean} isPassword - whether to display it in password form. Default false -eg: false
* @property {string} cursorColor - Cursor color Default: #cccccc
* @property {string} boxNormalColor - the color of the box where the cursor is not focused Default: #cccccc
* @property {string} boxActiveColor - the color of the box where the cursor is focused Default: #000000
* @event {Function(data)} confirm - input completion callback function
*/
import {<!-- -->
getElementRect
} from './util.js';
export default {<!-- -->
name: '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: '', // Entered verification code
codeCursorLeft: [], // Array of distance moved to the left,
itemSize: 6,
getElement: getElementRect(this),
isPatch: false
};
},
created() {<!-- -->
this.cursorVisible = this.isFocus;
this.validatorSize();
},
mounted() {<!-- -->
this.init();
},
methods: {<!-- -->
/**
* Set the number of verification code boxes
*/
validatorSize() {<!-- -->
if (this.size > 0) {<!-- -->
this.itemSize = Math.floor(this.size);
} else {<!-- -->
throw "methods of 'size' is integer";
}
},
/**
* @description initialization
*/
init() {<!-- -->
this.getCodeCursorLeft();
this.setCursorHeight();
},
/**
* @description Calculate the height of the cursor
*/
setCursorHeight() {<!-- -->
this.getElement('.box', 'single', boxElm => {<!-- -->
this.cursorHeight = boxElm.height * 0.6;
});
},
/**
* @description Get the left position of the cursor in each box
*/
getCodeCursorLeft() {<!-- -->
// Get the position information of the parent box
this.getElement('#input-ground', 'single', parentElm => {<!-- -->
const parentLeft = parentElm.left;
// Get information about each box
this.getElement('.box', 'array', elms => {<!-- -->
this.codeCursorLeft = [];
elms.forEach(elm => {<!-- -->
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
});
});
});
},

//Callback for input changes in the input box
onInput(e) {<!-- -->
let {<!-- -->
value,
keyCode
} = e.detail;
this.cursorVisible = value.length <this.itemSize;
this.code = value;
this.$emit('input', value);
this.inputSuccess(value);
},

// Input completion callback
inputSuccess(value) {<!-- -->
if (value.length === this.itemSize & amp; & amp; !this.isPatch) {<!-- -->
this.$emit('confirm', value);
} else {<!-- -->
this.isPatch = false;
}
},
//Input focus
inputFocus() {<!-- -->
this.cursorVisible = this.code.length <this.itemSize;
},
//Input loses focus
inputBlur() {<!-- -->
this.cursorVisible = false;
}
},
watch: {<!-- -->
value(val) {<!-- -->
if (val !== this.code) {<!-- -->
this.code = val;
}
}
},
filters: {<!-- -->
codeFormat(val, isPassword) {<!-- -->
return val ? (isPassword ? '*' : val) : '';
}
}
};
</script>
<style scoped>
.verify-code {<!-- -->
position: relative;
width: 100%;
box-sizing: border-box;
}

.verify-code .input {<!-- -->
height: 100%;
width: 200%;
position: absolute;
left: -100%;
z-index: 1;
color: transparent;
caret-color: transparent;
background-color: rgba(0, 0, 0, 0);
}

.verify-code .cursor {<!-- -->
position: absolute;
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 2px;
animation-name: cursor;
animation-duration: 0.8s;
animation-iteration-count: infinite;
}

.verify-code .input-ground {<!-- -->
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
box-sizing: border-box;
}

.verify-code .input-ground .box {<!-- -->
position: relative;
display: inline-block;
width: 100rpx;
height: 140rpx;
}

.verify-code .input-ground .box-bottom {<!-- -->
border-bottom-width: 2px;
border-bottom-style: solid;
}

.verify-code .input-ground .box-box {<!-- -->
border-width: 2px;
border-style: solid;
}

.verify-code .input-ground .box-middle {<!-- -->
border: none;
}

.input-ground .box .middle-line {<!-- -->
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform: translate(-50%, -50%);
border-bottom-width: 2px;
border-bottom-style: solid;
}

.input-ground .box .code-text {<!-- -->
position: absolute;
top: 50%;
left: 50%;
font-size: 80rpx;
transform: translate(-50%, -50%);
}

@keyframes cursor {<!-- -->
0% {<!-- -->
opacity: 1;
}

100% {<!-- -->
opacity: 0;
}
}
</style>

util.js

/**
 * @description Get element node - size and other information
 * @param {string} elm - the id and class of the node are equivalent to the parameters of document.querySelect -eg: #id
 * @param {string} type = [single|array] - A single element gets multiple elements. The default is a single element.
 * @param {Function} callback - callback function
 * @param {object} that - context vue2: this, vue3: getCurrentInstance();
 */
export const getElementRect = (that) => (elm, type = 'single', callback) => {<!-- -->
// #ifndef H5
uni
.createSelectorQuery()
.in(that)[type === 'array' ? 'selectAll' : 'select'](elm)
.boundingClientRect()
.exec(data => {<!-- -->
callback(data[0]);
});
// #endif

// #ifdef H5
let elmArr = [];
const result = [];
if (type === 'array') {<!-- -->
elmArr = document.querySelectorAll(elm);
} else {<!-- -->
elmArr.push(document.querySelector(elm));
}

for (let elm of elmArr) {<!-- -->
result.push(elm.getBoundingClientRect());
}
console.log('result', result)
callback(type === 'array' ? result : result[0]);
// #endif
}