likes
comments
collection
share

验证码组件开发之路(2)

作者站长头像
站长
· 阅读数 74

自上次完成验证码组件的两种类型之后,这段时间又添加了两种类型上去,因此写个文章记录一下这个过程。很多功能完成可能有更好的方法,欢迎各位大佬指点

附上一次文章链接

一、按规定顺序点击相应字符的验证码

首先来看一下效果

验证码组件开发之路(2)

这个的思路就是将传入的字段作为验证码的答案,将每个字符打乱渲染到容器上,每次点击字符再按点击顺序拼接起来和原字符作比较,判断验证是否成功

需要传入的props字段:

interface PropType{
    width?:number, //验证码容器的宽度
    bgImgList?:Array<string>, //背景图,每次刷新会从数组中随机挑选一张作为背景
    verifyText?:string, //验证码字符串
    checkOkColor?:string, //验证成功的提示颜色
    checkFailColor?:string, //验证失败的提示颜色
    autoRefresh?:boolean, //是否开启自动刷新,开启后在验证失败后会自动刷新
    resSize?:number //验证结果的字符大小
}

1、HTML

<div ref="container" class="click-verify_box" 
    :data-result="result"
    :style="{
        width:width + 'px',
        height:width / 2 + 'px',
        backgroundImage:`url(${bgImg})`,
        '--result-opacity':resOpacity,
        '--res-zindex':res_z_index,
        '--res-size':resSize + 'px'
    }"
>
</div>

html部分只需要一个容器就行,验证结果的提示我是用伪元素来显示,所以data-result就作为伪元素的content值,resOpacityres_z_index控制伪元素的出现和隐藏

less部分没什么难度就直接附代码了

<style lang="less" scoped>
    .click-verify_box{
        background-repeat: no-repeat;
        background-size: 100% 100%;
        position: relative;
        user-select: none;
        overflow: hidden;
        &::before{
            content: attr(data-result);
            display: flex;
            justify-content: center;
            align-items: center;
            color: var(--res-color);
            position: relative;
            z-index: var(--res-zindex);
            background-color: rgba(255,255,255,.3);
            backdrop-filter: blur(2px);
            -webkit-backdrop-filter: blur(2px);
            font-size: var(--res-size);
            font-family:'Times New Roman', Times, serif;
            font-weight: 500;
            width: 100%;
            height: 100%;
            opacity: var(--result-opacity);
            transition: opacity .3s linear;
        }
    }
</style>
<style>
    .zyhui-verify-code-order-num{
        position: absolute;
        display: flex;
        justify-content: center;
        align-items: center;
        border: 2px solid #fff;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        color: #fff;
        font-size: 16px;
        font-weight: 600;
        transform: translate(-50%,-50%);
    }
</style>

2、JS

首先,从props里的bgImgList中随机选择一张作为背景

const bgImg = ref<any>()
const getBgImg = ()=> {
    const img = bgImgList[Math.floor(Math.random()*bgImgList.length)]
    bgImg.value = new URL(img,import.meta.url).href
}

接着就是初始化的函数。一开始我想用canvas作为容器,将验证码字符画到画布上,但是这样在点击的时候很难判断每个字符的点击位置。之后还是选择了为每个字符创建一个span放到容器中。所以初始化和刷新的时候先判断容易是否有子元素并移除

const container = ref<HTMLDivElement>()
const init = ()=> {
    removeChild()
    function removeChild() {
        const children = (container.value as HTMLDivElement).childNodes
        while(children.length){
            container.value?.removeChild(children[0])
        }
    }
}

接着就是将props中的verifyText转成数组,循环这个数组,为每个字符生成不同的样式和指定范围的随机位置渲染到容器上,完成后从数组中移除这个元素

const verifyTextArr = verifyText.split('')
const letterWidth = width / verifyTextArr.length
const fragment = document.createDocumentFragment()
while(verifyTextArr.length){
    const index = Math.floor(Math.random() * verifyTextArr.length)
    const text = verifyTextArr[index]
    verifyTextArr.splice(index,1)
    const fontSize = Math.floor(Math.random() * width / 10 + width / 7.5)
    const span = document.createElement('span')
    span.textContent = text
    span.style.position = 'absolute'
    span.style.transformOrigin = 'center center'
    span.style.lineHeight = '1em'
    span.style.color = getColor()
    span.style.fontSize = fontSize  + 'px'
    span.style.top = Math.random() * (width / 2 - fontSize) + 'px'
    span.style.left = Math.random() * (letterWidth - fontSize) + (verifyText.length - verifyTextArr.length - 1) * letterWidth + 'px'
    span.style.transform = `rotate(${Math.floor(Math.random() * 61 - 30)}deg)`
    fragment.appendChild(span)
}
(container.value as HTMLDivElement).appendChild(fragment)
function getColor():string {
    return `rgb(${Math.floor(Math.random()*256)},${Math.floor(Math.random()*256)},${Math.floor(Math.random()*256)})`
}

还得为每个span添加点击事件,点击之后将span的字符添加到结果中,并在字符上层渲染出一个点击顺序的符号。先写出创建这个符号的方法

const orderNum = ref<number>(1)
function createClickOrder(x:number,y:number) {
    const clickOrderEl = document.createElement('div');
    (container.value as HTMLDivElement).appendChild(clickOrderEl)
    clickOrderEl.classList.add('zyhui-verify-code-order-num')
    clickOrderEl.textContent = orderNum.value.toString()
    clickOrderEl.style.top = y + 'px'
    clickOrderEl.style.left = x + 'px'
    orderNum.value += 1
}

x、y就是点击时候计算出来的所需渲染符号的坐标

在为span添加点击事件之前,还要考虑到的是,当点击最后一个字符,会自动进行验证,所以可以使用vuewatch函数监听结果字符串和原始字符串的长度相等时进行验证,也可以直接将span的点击事件中添加的创建字符替换为验证函数,在验证函数中创建点击字符,并判断结果字符串和原始字符串相等时进行验证。这里我选择了后者

const emits = defineEmits(['ok','fail','reFresh'])
const result = ref<string>('')
const resOpacity = ref<number>(0)
const res_z_index = ref<number>(-10)
function checkVerifyCode(x:number,y:number,text:string) {
    if(result.value.length !== verifyText.length - 1){
        result.value += text
        createClickOrder(x,y)
    }else{
        result.value += text
        createClickOrder(x,y)
        if(result.value === verifyText){
            res_z_index.value = 999
            result.value = '🗹'
            container.value?.style.setProperty('--res-color',checkOkColor)
            resOpacity.value = 1
            emits('ok')
        }else{
            res_z_index.value = 999
            result.value = '⛌'
            container.value?.style.setProperty('--res-color',checkFailColor)
            resOpacity.value = 1
            emits('fail')
            if(autoRefresh){
                setTimeout(() => {
                    reFresh()
                }, 1000)
            }
        }
    }
}

每次验证都会传递相应的事件出去

然后将checkVerifyCode事件添加到span的点击事件中就可以了

while(verifyTextArr.length){
    //......
    span.addEventListener('click',(e)=>{
        const x = span.offsetLeft + e.offsetX
        const y = span.offsetTop + e.offsetY
        checkVerifyCode(x,y,text)
    })
}

再补充一个reFresh的刷新函数

const reFresh = ()=> {
    result.value = ''
    orderNum.value = 1
    res_z_index.value = -10
    resOpacity.value = 0
    init()
}

最后,将initgetBgImg函数在组件挂载的时候执行一下就可以了

onMounted(() => {
    getBgImg()
    init()
})

二、按文字提示选择相应图片的验证码

老规矩,先看效果:

验证码组件开发之路(2)

介绍一下思路。首先这个验证码得从外部接收图片进来,接收的图片分为rightImgs(代表需要用户选择的图片)和wrongImgs(代表不需要用户选择的图片),其中rightImgs中可以传多组图片,每组图片都会有一个type用来告诉用户需要选择是哪些图片。当初始化时会从rightImgs中随机挑选一组作为resultImgList(最终渲染的图片数组),当图片数量大于9张时,则随机挑选9张加入resultImgList,小于9张时则从wrongImgs中随机挑选补齐9张图片,以下为示例:

const imgList = ref({
    rightImgs:[
        {
            type:'月亮',
            imgs:[
                '/hah.png',
                '/hah.png',
                '/hah.png'
            ]
        }
    ],
    wrongImgs:[
        'https://t7.baidu.com/it/u=1956604245,3662848045&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=1063451194,1129125124&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=2374506090,1216769752&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=4158958181,280757487&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=1423490396,3473826719&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=3796392429,3515260353&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=3980489931,4090080080&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=1463594131,3731527799&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=3265501662,2644840891&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=3638940699,1955702687&fm=193&f=GIF',
        'https://t7.baidu.com/it/u=30263305,3485941528&fm=193&f=GIF'
    ]
})

需要传入的props字段:

interface RightImgsType{
    type:string,
    imgs:Array<string>
}
interface PropType {
    ftp_text_style?:{
        text:string, //按钮文字
        success_text:string, //验证成功按钮文字
        color:string, //按钮颜色
        success_color:string, // 验证成功按钮颜色
        fontSize:string, //按钮字体大小
        clickImgColor:string //点击图片时显示的符号颜色
    }
    imgList:{
        rightImgs:Array<RightImgsType>,
        wrongImgs:Array<string>,
    }
}
const {
    ftp_text_style:{ text,color,fontSize,clickImgColor,success_text,success_color },
    imgList:{rightImgs,wrongImgs}
} = withDefaults(defineProps<PropType>(),{
    ftp_text_style:()=>({
        text:'点击验证',
        success_text:'验证成功',
        color:'rgb(35, 95, 225)',
        success_color:'rgb(30, 167, 55)'fontSize:'16px',
        clickImgColor:'#fff'
    })
})

1、创建容器和按钮

首先创建一个容器,绑定上一些动态属性,为了防止点击按钮时下方弹出的图片选择器影响页面布局,所以容器使用定位并在之后给图片选择器加上绝对定位和zindex使选择器固定在按钮下方并在所有图层之上

<script lang="ts" setup>
const CHECK_SUCCESS = 1
const CHECK_FAIL = 2
const CHECK_ORIGINAL = 0
const checkCodeStatus = ref<number>(CHECK_ORIGINAL) // 验证码的三种状态
const isCodeOpen = ref<boolean>(false) //判断是否打开了图片选择器
const showTip = ref<boolean>(false) //图片选择器打开时显示提示文字
const codeType = ref<string>('') //提示用户所需选择的图片
const showCheckSuccess = computed(() => checkCodeStatus.value === CHECK_SUCCESS)//验证成功后控制显示按钮的成功样式
const openCode = ()=> {
    //....
}
</script>

<template>
    <div style="position: relative;display: inline-block;" 
        :style="{'--color':color,'--fontSize':fontSize,'--success-color':success_color}"
    >
        <span
            class="verify_btn"
            :class="showCheckSuccess?'check-success':'no-check'"
            :style="isCodeOpen?`border-color:${color};color:${color}`:''"
            @click="openCode"
        >
            {{ showCheckSuccess?success_text:text }}
        </span>
        <span class="tip" v-show="showTip">请选择下方所有的{{ codeType }}</span>
    </div>
</template>

相应的less代码:

*{
    box-sizing: border-box;
    padding: 0;
    margin: 0;
    user-select: none;
}
.verify_btn{
    display: inline-block;
    padding: calc(var(--fontSize) * 2 / 3);
    border: 1px solid;
    border-radius: 5px;
    font-size: var(--fontSize);
    margin-bottom: 5px;
}
.no-check{
    border-color: #ccc;
    color: rgb(143, 143, 143);
    &:hover{
        cursor: pointer;
        border-color: var(--color);
        color: var(--color);
        opacity: .8;
    }
    &:active{
        opacity: 1;
    }
}
.check-success{
    border-color: var(--success-color);
    color: var(--success-color);
    &::before{
        content: '√';
        display: inline;
        margin-right: 3px;
        animation: check-success .4s forwards;
    }
}
.tip{
    top: 10px;
    margin-left: 5px;
    font-size: 14px;
    color: rgb(112, 111, 111);
    white-space: nowrap;
}
@keyframes check-success{
    0%{
        opacity: 0;
    }
    100%{
        opacity: 1;
    }
}

2、创建图片选择器

加入图片选择器,并给它设置一个出场动画:

<script lang="ts" setup>
interface ResultImg{
    isRight:boolean, //当前图片是否为需要用户点击的图片
    imgUrl:string, //图片链接
    key:number, //图片的唯一标识
    content:string, //点击图片时显示的提示文案
    display:string //控制图片点击文案的显示与隐藏
}

const showCode = ref<boolean>(false) //控制图片选择器的显示与隐藏
const resultImgList = ref<Array<ResultImg>>([])
const imgRefs = ref()

const clickImg = ()=> {
    //...
}
const showImgs = ()=> {
    //...
}
const checkCode = ()=> {
    //...
}
</script>

//图片选择器和按钮处于同一级下
<transition name="code">
    <div class="verify-show" v-if="showCode">
        <div class="img_box" 
            :style="{'--display':item.display,'--clickImgColor':clickImgColor}"
            v-for="(item) in resultImgList" :key="item.key" 
            :data-order="item.content" 
            @click="clickImg(item.key)"
        >
            <img :src="item.imgUrl" alt="" ref="imgRefs" >
        </div>
        <div class="btn_box">
            <span class="refresh_btn" @click="showImgs">找不到?刷新</span>
            <span style="float: right;">
                <span class="btn" @click="closeCode">取消</span>
                <span class="btn" style="margin-left: 5px;" @click="checkCode">确认</span>
            </span>
        </div>
    </div>
</transition>

这里使用img显示图片而不是直接给div设置背景图的原因后面会介绍。此外,点击图片显示的选中样式是通过img_box这个divbefore伪元素来渲染的,所以resultImgList中的图片属性需要contentdisplay来控制伪元素的显示隐藏与显示的内容

相应的less代码;

.verify-show{
    position: absolute;
    z-index: 999;
    width: 234px;
    border: 1px solid #ccc;
    background-color: #fff;
    border-radius: 2px;
    padding: 5px;
    .img_box{
        display: inline-block;
        position: relative;
        margin: 0 2px;
        &:hover{
            cursor: pointer;
        }
        &::before{
            content: attr(data-order);
            position: absolute;
            display: var(--display);
            width: 18px;
            height: 18px;
            border-radius: 50%;
            border: 2px solid var(--clickImgColor);
            text-align: center;
            line-height: 18px;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
            color: var(--clickImgColor);
            padding: 5px;
            font-size: 16px;
            font-weight: 600;
            z-index: 999;
        }
        img{
            width: 70px;
            height: 70px;
            position: relative;
        }
    }
    .btn_box{
        margin-top: 5px;
        .refresh_btn{
            font-size: 14px;
            color: #999;
            &:hover{
                cursor: pointer;
                color: var(--color);
            }
        }
        .btn{
            padding: 2px 3px;
            font-size: 14px;
            border-radius: 2px;
            color: #fff;
            &:first-child{
                background-color: rgba(248, 10, 30, 0.7);
            }
            &:last-child{
                background-color: rgba(16, 89, 245, 0.7);
            }
            &:hover{
                cursor: pointer;
            }
            &:active{
                box-shadow: inset 0 0 3px #fff;
            }
        }
    }
}
.code-enter-active,
.code-leave-active{
    transition: all .5s;
}
.code-enter-from,
.code-leave-to{
    opacity: 0;
    transform: scale(0);
    transform-origin: left top;
}

3、为图片选择器创建loadingerror

按目前的思路,点击按钮调用按钮的showImgs事件,在事件中将图片选择器显示出来并构建图片属性对象放入resultImgList,就能正常渲染了。不过当网络较慢的时候,图片加载也会变慢,如下面的示例一样:

验证码组件开发之路(2)

为了解决这个问题,我选择在图片的上面盖一层loading,当所有图片加载完成后再去掉loading,所以要用到imgonload方法,如果直接设置背景图片的话,我目前还不知道有什么方法可以监听背景图的加载,要是有大佬知道可以指点一下

验证码组件开发之路(2)

既然有了loading,那么图片要是加载失败就在上面盖一层error提示图片加载失败,完成后的效果如下:

验证码组件开发之路(2) 验证码组件开发之路(2)

loading的图片使用svg制作,相关代码如下:

<script lang="ts" setup>
const showError = ref<boolean>(false)
const showLoading = computed(
    //...
)
</script>
// 图片的div在同一层级
<div class="masking" v-if="showLoading">
    <svg class="loading" width="35" height="35">
        <line y2="13.42387" x2="17.5" y1="0.17387" x1="17.5" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#999999" fill="none"/>
        <line y2="34.96296" x2="17.54115" y1="22.23122" x1="17.45885" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#999999" fill="none"/>
        <line transform="rotate(45 25.185184478759773,10.008744239807127)" stroke="#999999" y2="16.63374" x2="25.18518" y1="3.38374" x1="25.18518" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/>
        <line transform="rotate(90 28.148147583007812,17.500000000000004)" stroke="#999999" y2="24.125" x2="28.14815" y1="10.875" x1="28.14815" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/>
        <line transform="rotate(135 25.34979248046875,25.564292907714844)" stroke="#999999" y2="32.18929" x2="25.34979" y1="18.93929" x1="25.34979" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/>
        <line transform="rotate(90 6.748980522155759,17.500000000000004)" stroke="#999999" y2="24.125" x2="6.74898" y1="10.875" x1="6.74898" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/>
        <line transform="rotate(-45 9.711940765380861,9.926440238952637)" y2="16.55144" x2="9.71194" y1="3.30144" x1="9.71194" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#999999" fill="none"/>
        <line transform="rotate(-135 9.87654972076416,25.399682998657227)" stroke="#999999" y2="32.02468" x2="9.87655" y1="18.77468" x1="9.87655" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/>
    </svg>
    <div class="loading-text">loading...</div>
</div>
<div v-if="showError" class="loading-error">加载图片出错...</div>

showLoading的实现后面再进行介绍

4、按钮点击和图片加载

按钮的点击方法还是很简单的,如下所示:

const openCode = ()=> {
    if(showCheckSuccess.value){
        return
    }
    if(isCodeOpen.value) {
        return closeCode()
    }
    isCodeOpen.value = true
    showImgs()
}
const closeCode = ()=> {
    showCode.value = false
    showTip.value = false
    isCodeOpen.value = false
}

在实现showImgs之前,先完成图片的初始化事件, 第一步先清空原来的最终需要渲染的图片数组resultImgList,再从传入的rightImgs随机挑选一组图片作为需要选择的图片:

const initCodeImgs = ()=> {
    resultImgList.value.length = 0
    isloadCount.value = 0 // 已经加载的图片数量,后面会用到
    const randomImgs = rightImgs[Math.floor(Math.random() * rightImgs.length)]
    codeType.value = randomImgs.type
    //...
}

然后根据randomImgswrongImgs生成resultImgList,为了后面的代码清晰,这里使用promise进行控制:

let imgKey:any = 0 //每个图片的唯一标识,因为数组需要打乱,所以使用v-for时不能使用index

//跟在上文的省略号后
return new Promise((resolve,reject)=>{
    if(randomImgs.imgs.length >= 9){
        randomSort(randomImgs.imgs)
        for (let i = 0; i < 9; i++) {
            resultImgList.value.push({
                isRight:true,
                imgUrl:new URL(randomImgs.imgs[i],import.meta.url).href,
                key:imgKey++,
                content:'',
                display:'none'
            })
        }
        resolve([randomImgs.type,resultImgList.value.length])
    }else if(randomImgs.imgs.length > 0 && randomImgs.imgs.length < 9){
        const arr = []
        for (const imgUrl of randomImgs.imgs) {
            arr.push({
                isRight:true,
                imgUrl:new URL(imgUrl,import.meta.url).href,
                key:imgKey++,
                content:'',
                display:'none'
            })
        }
        randomSort(wrongImgs)
        for (let i = 0; i < 9-randomImgs.imgs.length; i++) {
            if(wrongImgs[i]){
                arr.push({
                    isRight:false,
                    imgUrl:new URL(wrongImgs[i],import.meta.url).href,
                    key:imgKey++,
                    content:'',
                    display:'none'
                })
            }
        }
        randomSort(arr)
        resultImgList.value = [...arr]
        resolve([randomImgs.type,arr.length])
    }else{
        reject('error')
    }
})
//打乱一个数组的辅助函数
function randomSort<T extends any>(arr:Array<T>){
    let n = arr.length
    while(n--){
        const index = Math.floor(Math.random() * n);
        [arr[n],arr[index]] = [arr[index],arr[n]]
    }
}

因为最后渲染的图片最多为9张,所以优先考虑rightImgs中的图片,不够9张,就从wrongImgs中随机选取补齐9张,如果最后加起来都不够9张,就全部都放到resultImgList中。这里应该还有更简单的写法,不过我当时没去多想

之后就是showImgs的实现,用三个常量表示当前验证码的三种状态,通过绑定在img标签上的imgRef,调用imgonloadonerror事件,每次加载完成则记录一次已加载的图片数量,当有一张图片加载失败,就显示加载失败,代码如下:

const isloadCount = ref<number>(0) //表示已经加载的图片数量
const needLoadCount = ref<number>(0)//表示需要加载的图片数量
const showLoading = computed(() => !(isloadCount.value === needLoadCount.value && isloadCount.value != 0))
const showImgs = ()=> {
    showCode.value = true
    showError.value = false
    checkCodeStatus.value = CHECK_ORIGINAL
    initCodeImgs()
    .then((array:any)=>{
        const [type,imgCount] = array
        codeType.value = type
        showTip.value = true
        needLoadCount.value = imgCount
        nextTick(() => {
            for (const img of imgRefs.value) {
                (img as HTMLImageElement).onload = ()=> {
                    isloadCount.value++
                }
                (img as HTMLImageElement).onerror = ()=> {
                    needLoadCount.value = isloadCount.value = -1 //关闭loading
                    showError.value = true
                    showTip.value = false
                }
            }
        })
    })
    .catch((err)=>{
        console.log(err);
    })
}

这里用计算属性控制showLoading,当需要加载的图片数量不等于已经加载的图片数量时,显示loading

5、点击图片和验证

这部分就很简单了,点击图片的时候只要设置图片对应的resultImgList中对象的contentdisplay就行,代码如下:

const clickImg = (key:number)=> {
    const imgObj = resultImgList.value.find(item => item.key === key)
    if(imgObj){
        imgObj.content = imgObj.content?'':'√'
        imgObj.display = imgObj.display=='block'?'none':'block'
    }
}

验证函数代码如下:

const checkCode = ()=> {
    const result = resultImgList.value.findIndex(item => (item.isRight && !item.content) || (!item.isRight && item.content))
    if(result === -1){
        checkCodeStatus.value = CHECK_SUCCESS
        emits('ok')
        closeCode()
    }else{
        checkCodeStatus.value = CHECK_FAIL
        emits('fail')
        let timer = setTimeout(() => {
            showImgs()
            clearTimeout(timer)
        }, 600);
    }
}

最后在模版中把验证失败部分加在与图片盒子同级下就行了:

//js
const showCheckFail = computed(() => checkCodeStatus.value === CHECK_FAIL)

//html
<transition name="check-defeat">
    <div v-if="showCheckFail" class="masking-defeat">验证失败</div>
</transition>

//less
.masking-fail{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 99999;
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    display: flex;
    justify-content: center;
    align-items: center;
    color: rgb(235, 54, 54);
    font-size: 20px;
    font-weight: 600;
    letter-spacing: .2em;
}
.check-fail-enter-active,
.check-fail-leave-active{
    transition: .5s;
}
.check-fail-enter-from,
.check-fail-leave-to{
    opacity: 0;
}

三、总结

完成这两验证码到我写这篇文章的时候隔了也有两礼拜了,写的过程中又发现有些地方可以简化一下,不过就不花费时间去改了。如果有什么错误或者可以改进的地方,欢迎大佬们指点。

组件已经发布了,可通过npm i zyhzyh-ui下载,在main.js中使用

import { VerifyCode} from 'zyhzyh-ui' 
import 'zyhzyh-ui/es/style.css' 
const app = createApp(App) 
app.use(VerifyCode)
转载自:https://juejin.cn/post/7187267436214190117
评论
请登录