验证码code组件

<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>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容