<template>
<div>
<div class="code-number-wrap">
<div :ref="`codeItem${item}`" v-text="cellText[index]" @keyup="keyupHandler(item,$event)" v-for="(item, index) in len" :key="index" :tabindex="item" class="code-item" contenteditable="true" ></div>
</div>
</div>
</template>
<script lang='ts'>
import { Component, Prop, Vue, Watch, Model } from 'vue-property-decorator';
@Component
export default class CodeNumber extends Vue {
@Prop({ type: Number, default: 0 }) length!: number;
@Prop({ type: String, default: 'cert' }) type!: string; // cert 身份證 validate 短信驗(yàn)證碼
@Model('change', { type: String }) value!: string;
private len: number[] = [];
private cellText: string[] = ['', '', '', ''];
@Watch('length', { immediate: true, deep: true })
private lengthWatcher(val: number) {
if (typeof val === 'number') {
this.len = [];
for (let i = 0; i < val; i++) {
this.len.push(i + 1);
}
}
}
@Watch('value', { immediate: true, deep: true })
private valueChangeHandle(val:string) {
this.cellText = val.split('');
}
private isNumberKey(code:string):boolean {
// 是否是數(shù)字鍵
return /\d+/.test(code);
// return !(code.indexOf('Digit') === -1 && code.indexOf('Numpad') === -1);
}
private keyupHandler(index: number, $event: any) {
let curEls: any = this.$refs[`codeItem${index}`];
let targetEl = curEls[0]; // 當(dāng)前單元格
let currentCellText = targetEl.innerText;
if ($event.keyCode === 8 || $event.keyCode === 46) {
// 刪除邏輯
this.cellText[index - 1] = '';
this.$emit('change', this.cellText.join(''));
if (index === 1) return;
let preEl = (this.$refs[`codeItem${index - 1}`] as any)[0];
preEl.focus();
this.cursorBackEnd(preEl); // 下個(gè)單元格光標(biāo)后置
return;
}
if (!/^\d*$/g.test(currentCellText) && index !== this.length) {
// 單元格內(nèi)容非數(shù)字,則清空
targetEl.innerText = '';
currentCellText = '';
}
if (index === this.length && this.type === 'cert') { // 身份證類型最后一位可以為X or x or 數(shù)字
if (currentCellText.toLowerCase() !== 'x' && !/^\d*$/g.test(currentCellText)) {
targetEl.innerText = '';
currentCellText = '';
}
}
if (index === this.length && this.type === 'validate' && !/^\d*$/g.test(currentCellText)) { // 身份證類型最后一位可以為X or x or 數(shù)字
targetEl.innerText = '';
currentCellText = '';
}
// let code:string = ($event.code || $event.keyCode).toString();
if ((!this.isNumberKey(currentCellText) && index !== this.length) || (!this.isNumberKey(currentCellText) && index === this.length && this.type === 'validateCode')) return; // 非數(shù)字類型的按鍵 直接return
if (currentCellText.length === 0) return; // 如果單元格內(nèi)容為空不執(zhí)行下個(gè)單元格的focus
this.cellText[index - 1] = currentCellText.slice(0, 1);
// 單元格內(nèi)容超過(guò)一個(gè)字符時(shí)源祈,才執(zhí)行下行邏輯
currentCellText.length > 1 &&
(targetEl.innerText = currentCellText.slice(0, 1));
this.$emit('change', this.cellText.join(''));
if (index === this.length) {
targetEl.blur();
return; // 最后一個(gè)單元格不執(zhí)行下個(gè)單元格的focus
}
let nextEls: any = this.$refs[`codeItem${index + 1}`];
let nextTargetEl = nextEls[0]; // 下一個(gè)單元格
nextTargetEl.focus();
this.cursorBackEnd(nextTargetEl); // 下個(gè)單元格光標(biāo)后置
}
private cursorBackEnd(targetEl: any) {
// 單元格內(nèi)光標(biāo)后置
let range: any;
if (window.getSelection) {
// ie11 10 9 ff safari
range = window.getSelection(); // 創(chuàng)建range
range.selectAllChildren(targetEl); // range 選擇obj下所有子內(nèi)容
range.collapseToEnd(); // 光標(biāo)移至最后
} else if ((document as any).selection) {
// ie10 9 8 7 6 5
range = (document as any).selection.createRange(); // 創(chuàng)建選擇對(duì)象
range.moveToElementText(targetEl); // range定位到obj
range.collapse(false); // 光標(biāo)移至最后
range.select();
}
}
created() {
this.cellText = this.value.split('');
}
mounted() {
// 初始化光標(biāo)在第一個(gè)位置
let index = 1;
let curEls: any = this.$refs.codeItem1;
let targetEl = curEls[0]; // 當(dāng)前單元格
targetEl.focus();
}
}
</script>
<style lang="scss" scoped>
$cellWidth: 40px;
.code-number-wrap {
color: $c-7f8389;
display: flex;
justify-content: space-between;
align-items: center;
.code-item {
display: inline-block;
width: $cellWidth;
height: $cellWidth;
line-height: $cellWidth;
text-align: center;
border: 1px solid $c-d4d5d9;
-webkit-user-select: text;
}
}
</style>
<style lang="scss">
.pix-form-item-error .code-number-wrap .code-item {
border: 1px solid #ff6167;
}
</style>
調(diào)用
<template>
<div>
<h1 class="error-tips">{{titles.name}}</h1>
<div class="titleColor">
<Steps :current="current" :above="true" v-if="titles.type">
<Step v-for="(item, index) in stepNames" :key="index" :title="item"></Step>
</Steps>
</div>
<div class="confirm-sign-wrap">
<p class="send-result-tips">我們已經(jīng)向<span class="phone"><strong>{{phone}}</strong> </span>發(fā)送驗(yàn)證碼<br></p>
<p class="tips">輸入驗(yàn)證碼即代表您同意簽署;</p>
// 調(diào)用組件
<code-number :length="6" v-model="validateCode"></code-number>
<p class="tip">
<span v-if="showBtn" @click="fetchValidateCode" class="fr colors">{{btnText}}</span>
<span v-if="!showBtn" class="fr time-count-tips">{{timeCount}}秒之后重新獲取驗(yàn)證碼</span>
</p>
<div class="btn-wrap">
<Button :disabled="validateCode.length<6" type="primary" size="large" @click="confirmSign">確認(rèn)簽署</Button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { State, Getter, Action, Mutation, namespace } from 'vuex-class';
import CodeNumber from '@/components/bussCom/codeNumber/codeNumber.vue';
@Component({
components: {
CodeNumber
}
})
export default class Crumb extends Vue {
// @Prop(Number) current!: number; // 當(dāng)前步驟數(shù)
@Prop({ type: Function }) responseProcess!: Function;
@Mutation setIsShow: any;
@Mutation setComponentName: any;
@State userInfo!: any;
@State titles!: any;
@State stepNames!: any;
@State current!: any;
private source: any; // 緩存
private phone: string = '';
private sendphone: string = '';
private showBtn: boolean = true;
private validateCode: string = '';
private timeCount: number = 60;
private timer: number = -1;
private accountUid: string = ''; // 創(chuàng)建簽章賬號(hào)ID
private btnText: string = '獲取驗(yàn)證碼';
private show: boolean = false;
}
</script>
<style lang="scss" scoped>
.error-tips {
text-align: center;
margin: 10px 0;
}
.titleColor {
background: #faf9f9;
margin: 10px 0;
}
.confirm-sign-wrap {
color: $c-7f8389;
width: 320px;
margin: 0 auto;
font-size: $fontSize;
margin-bottom: 260px;
.phone {
color: $text-active;
padding: 0 10px;
}
.send-result-tips {
margin: 40px 0 32px 0;
text-align: center;
}
.pix-spin {
color: black !important;
}
.tips {
margin-bottom: 16px;
// color: $text-active;
// line-height: 32px;
text-align: center;
}
.tip {
margin-top: 10px;
cursor: default;
.colors {
color: #1d78f4;
cursor: pointer;
}
}
}
.btn-wrap {
text-align: center;
margin-top: 24px;
button {
width: 100%;
margin-top: 10px;
}
}
.pix-steps-item {
margin-top: 10px;
}
div >>> .pix-steps .pix-steps-head {
background: #faf9f9 !important;
}
div >>> .pix-steps .pix-steps-title {
background: #faf9f9 !important;
}
.textColor {
color: black;
z-index: 1000000;
}
</style>