<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 短信验证码
@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 {
// 是否是数字键
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]; // 当前单元格
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); // 下个单元格光标后置
return;
}
if (!/^\d*$/g.test(currentCellText) && index !== this.length) {
// 单元格内容非数字,则清空
targetEl.innerText = '';
currentCellText = '';
}
if (index === this.length && this.type === 'cert') { // 身份证类型最后一位可以为X or x or 数字
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 数字
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; // 非数字类型的按键 直接return
if (currentCellText.length === 0) return; // 如果单元格内容为空不执行下个单元格的focus
this.cellText[index - 1] = currentCellText.slice(0, 1);
// 单元格内容超过一个字符时,才执行下行逻辑
currentCellText.length > 1 &&
(targetEl.innerText = currentCellText.slice(0, 1));
this.$emit('change', this.cellText.join(''));
if (index === this.length) {
targetEl.blur();
return; // 最后一个单元格不执行下个单元格的focus
}
let nextEls: any = this.$refs[`codeItem${index + 1}`];
let nextTargetEl = nextEls[0]; // 下一个单元格
nextTargetEl.focus();
this.cursorBackEnd(nextTargetEl); // 下个单元格光标后置
}
private cursorBackEnd(targetEl: any) {
// 单元格内光标后置
let range: any;
if (window.getSelection) {
// ie11 10 9 ff safari
range = window.getSelection(); // 创建range
range.selectAllChildren(targetEl); // range 选择obj下所有子内容
range.collapseToEnd(); // 光标移至最后
} else if ((document as any).selection) {
// ie10 9 8 7 6 5
range = (document as any).selection.createRange(); // 创建选择对象
range.moveToElementText(targetEl); // range定位到obj
range.collapse(false); // 光标移至最后
range.select();
}
}
created() {
this.cellText = this.value.split('');
}
mounted() {
// 初始化光标在第一个位置
let index = 1;
let curEls: any = this.$refs.codeItem1;
let targetEl = curEls[0]; // 当前单元格
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>
调用
<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">我们已经向<span class="phone"><strong>{{phone}}</strong> </span>发送验证码<br></p>
<p class="tips">输入验证码即代表您同意签署;</p>
// 调用组件
<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}}秒之后重新获取验证码</span>
</p>
<div class="btn-wrap">
<Button :disabled="validateCode.length<6" type="primary" size="large" @click="confirmSign">确认签署</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; // 当前步骤数
@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 = ''; // 创建签章账号ID
private btnText: string = '获取验证码';
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>