手写一个图形验证码

news2024/11/16 13:43:40

文章目录

    • 需求
    • 分析

需求

使用 JS 写一个验证码,并在前端进行校验
在这里插入图片描述

分析

新建文件 VueImageVerify.vue

<template>
  <div class="img-verify">
    <canvas ref="verify" :width="state.width" :height="state.height" @click="handleDraw"></canvas>
  </div>
</template>

<script setup>
import { reactive, onMounted, ref } from 'vue'
const verify = ref(null)
const state = reactive({
  pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', // 字符串
  width: 120,
  height: 40,
  imgCode: ''
})
defineExpose({ state })
onMounted(() => {
  // 初始化绘制图片验证码
  state.imgCode = draw()
})

// 点击图片重新绘制
const handleDraw = () => {
  state.imgCode = draw()
}

// 随机数
const randomNum = (min, max) => {
  return parseInt(Math.random() * (max - min) + min)
}
// 随机颜色
const randomColor = (min, max) => {
  const r = randomNum(min, max)
  const g = randomNum(min, max)
  const b = randomNum(min, max)
  return `rgb(${r},${g},${b})`
}

// 绘制图片
const draw = () => {
  // 3.填充背景颜色,背景颜色要浅一点
  const ctx = verify.value.getContext('2d')
  // 填充颜色
  ctx.fillStyle = randomColor(180, 230)
  // 填充的位置
  ctx.fillRect(0, 0, state.width, state.height)
  // 定义paramText
  let imgCode = ''
  // 4.随机产生字符串,并且随机旋转
  for (let i = 0; i < 4; i++) {
    // 随机的四个字
    const text = state.pool[randomNum(0, state.pool.length)]
    imgCode += text
    // 随机的字体大小
    const fontSize = randomNum(18, 40)
    // 字体随机的旋转角度
    const deg = randomNum(-30, 30)
    /*
      * 绘制文字并让四个文字在不同的位置显示的思路 :
      * 1、定义字体
      * 2、定义对齐方式
      * 3、填充不同的颜色
      * 4、保存当前的状态(以防止以上的状态受影响)
      * 5、平移translate()
      * 6、旋转 rotate()
      * 7、填充文字
      * 8、restore出栈
      * */
    ctx.font = fontSize + 'px Simhei'
    ctx.textBaseline = 'top'
    ctx.fillStyle = randomColor(80, 150)
    /*
      * save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。
      * 这就允许您临时地改变图像状态,
      * 然后,通过调用 restore() 来恢复以前的值。
      * save是入栈,restore是出栈。
      * 用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
      *
      * */
    ctx.save()
    ctx.translate(30 * i + 15, 15)
    ctx.rotate((deg * Math.PI) / 180)
    // fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。
    // 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。
    // context.fillText(text,x,y,maxWidth);
    ctx.fillText(text, -15 + 5, -15)
    ctx.restore()
  }
  // 5.随机产生5条干扰线,干扰线的颜色要浅一点
  for (let i = 0; i < 5; i++) {
    ctx.beginPath()
    ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height))
    ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height))
    ctx.strokeStyle = randomColor(180, 230)
    ctx.closePath()
    ctx.stroke()
  }
  // 6.随机产生40个干扰的小点
  for (let i = 0; i < 40; i++) {
    ctx.beginPath()
    ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.fillStyle = randomColor(150, 200)
    ctx.fill()
  }
  return imgCode
}
</script>
<style>
.img-verify canvas {
  cursor: pointer;
}
</style>
  • 文件 Login.vue 中进行引入
<template>
  <div class="login">
    <s-header :name="type == 'login' ? '登录' : '注册'" :back="'/home'"></s-header>
    <img class="logo" src="https://s.yezgea02.com/1604045825972/newbee-mall-vue3-app-logo.png" alt="">
    <div v-if="state.type == 'login'" class="login-body login">
      <van-form @submit="onSubmit">
        <van-field
          v-model="state.username"
          name="username"
          label="用户名"
          placeholder="用户名"
          :rules="[{ required: true, message: '请填写用户名' }]"
        />
        <van-field
          v-model="state.password"
          type="password"
          name="password"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />
        <van-field
          center
          clearable
          label="验证码"
          placeholder="输入验证码"
          v-model="state.verify"
        >
          <template #button>
            <vue-img-verify ref="verifyRef" />
          </template>
        </van-field>
        <div style="margin: 16px;">
          <div class="link-register" @click="toggle('register')">立即注册</div>
          <van-button round block color="#1baeae" native-type="submit">登录</van-button>
        </div>
      </van-form>
    </div>
    <div v-else class="login-body register">
      <van-form @submit="onSubmit">
        <van-field
          v-model="state.username1"
          name="username1"
          label="用户名"
          placeholder="用户名"
          :rules="[{ required: true, message: '请填写用户名' }]"
        />
        <van-field
          v-model="state.password1"
          type="password"
          name="password1"
          label="密码"
          placeholder="密码"
          :rules="[{ required: true, message: '请填写密码' }]"
        />
        <van-field
          center
          clearable
          label="验证码"
          placeholder="输入验证码"
          v-model="state.verify"
        >
          <template #button>
            <vue-img-verify ref="verifyRef" />
          </template>
        </van-field>
        <div style="margin: 16px;">
          <div class="link-login" @click="toggle('login')">已有登录账号</div>
          <van-button round block color="#1baeae" native-type="submit">注册</van-button>
        </div>
      </van-form>
    </div>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue'
import sHeader from '@/components/SimpleHeader.vue'
import vueImgVerify from '@/components/VueImageVerify.vue'
import { login, register } from '@/service/user'
import { setLocal } from '@/common/js/utils'
import md5 from 'js-md5'
import { showSuccessToast, showFailToast } from 'vant'
const verifyRef = ref(null)
const state = reactive({
  username: '',
  password: '',
  username1: '',
  password1: '',
  type: 'login',
  imgCode: '',
  verify: ''
})

// 切换登录和注册两种模式
const toggle = (v) => {
  state.type = v
  state.verify = ''
}

// 提交登录或注册表单
const onSubmit = async (values) => {
  state.imgCode = verifyRef.value.state.imgCode || ''
  if (state.verify.toLowerCase() != state.imgCode.toLowerCase()) {
    showFailToast('验证码有误')
    return
  }
  if (state.type == 'login') {
    const { data } = await login({
      "loginName": values.username,
      "passwordMd5": md5(values.password)
    })
    setLocal('token', data)
    // 需要刷新页面,否则 axios.js 文件里的 token 不会被重置
    window.location.href = '/'
  } else {
    await register({
      "loginName": values.username1,
      "password": values.password1
    })
    showSuccessToast('注册成功')
    state.type = 'login'
    state.verify = ''
  }
}
</script>

<style lang="less">
  .login {
    .logo {
      width: 120px;
      height: 120px;
      display: block;
      margin: 80px auto 20px;
    }
    .login-body {
      padding: 0 20px;
    }
    .login {
      .link-register {
        font-size: 14px;
        margin-bottom: 20px;
        color: #1989fa;
        display: inline-block;
      }
    }
    .register {
      .link-login {
        font-size: 14px;
        margin-bottom: 20px;
        color: #1989fa;
        display: inline-block;
      }
    }
    .verify-bar-area {
      margin-top: 24px;
      .verify-left-bar {
        border-color: #1baeae;
      }
      .verify-move-block {
        background-color: #1baeae;
        color: #fff;
      }
    }
    .verify {
      >div {
        width: 100%;
      }
      display: flex;
      justify-content: center;
      .cerify-code-panel {
        margin-top: 16px;
      }
      .verify-code {
        width: 40%!important;
        float: left!important;
      }
      .verify-code-area {
        float: left!important;
        width: 54%!important;
        margin-left: 14px!important;
        .varify-input-code {
          width: 90px;
          height: 38px!important;
          border: 1px solid #e9e9e9;
          padding-left: 10px;
          font-size: 16px;
        }
        .verify-change-area {
          line-height: 44px;
        }
      }
    }
  }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“研学测”好帮手,三步带你安装体验TDH社区开发版

星环科技TDH社区开发版&#xff0c;作为一款单机可部署、开箱即用的大数据基础平台产品&#xff0c;大幅降低了用户的资源成本和使用门槛。与此同时&#xff0c;TDH社区开发版兼顾此前TDH社区版&#xff08;分布式&#xff09;组件成熟、简单易用、易运维等特点&#xff0c;可以…

《剑指 Offer》专项突破版 - 面试题 28 : 展平多级双向链表(C++ 实现)

题目连接&#xff1a;LCR 028. 扁平化多级双向链表 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 在一个多级双向链表中&#xff0c;节点除了有两个指针分别指向前后两个节点&#xff0c;还有一个指针指向它的子链表&#xff0c;并且子链表也是一个双向链表&…

深入理解MySQL InnoDB线程模型

当我们谈论数据库性能时&#xff0c;存储引擎的线程模型是一个不可忽视的方面。MySQL的InnoDB存储引擎&#xff0c;作为目前最受欢迎的存储引擎之一&#xff0c;其线程模型的设计对于实现高并发、高性能的数据操作至关重要。在本文中&#xff0c;我们将深入探讨MySQL InnoDB线程…

day31_CSS

今日内容 CSS概述引入方式 (where)选择器(how)属性(how) 1 CSS介绍 层叠样式表&#xff08;cascading style sheet&#xff09; CSS 用来美化HTML页面,可以让页面更好看,还可以布局页面. 好处 美化页面,布局页面使用外部css文件,可以实现样式文件和html文件分离,便于维护使用外…

JS进阶-内置构造函数(二)

小提示&#xff1a;这些内置函数在开发使用的频率非常的频繁&#xff0c;建议认真看一下&#xff0c;并背一下 目录 知识回顾&#xff1a; • Object 三个常用静态方法&#xff08;静态方法就是只有构造函数Object可以调用的&#xff09; Object.keys Object.values Obj…

【2024】下载安装Cisco Packet Tracer 8.2.1

一、注册账号 进入www.cisco.com 点击右上角的Log in 点击注册 之后输入邮箱和其他相关信息&#xff0c;正常注册即可 唯一注意的点&#xff1a;国家或地区 选项中别选China&#xff0c;否则之后登录软件时会有问题 二、下载安装包 进入packet-tracer下载&#xff0c;下…

【Tailwind】各种样式的进度条

基本样式进度条&#xff1a; <div class"mb-5 h-2 rounded-full bg-gray-200"><div class"h-2 rounded-full bg-orange-500" style"width: 50%"></div> </div>带文字的进度条&#xff1a; <div class"relativ…

品优购项目规划

1&#xff0c;网站favicon图标 favicon.ico 一般用于作为缩略的网站标注&#xff0c;它显示在浏览器的地址栏或者标签上 1&#xff0c;制作favicon图标 ①把品优购图标切成png图片 ②把png图片转换为ico图标&#xff0c;这需要借助于第三方转换网站&#xff0c;比如 比特虫…

基于移动边缘计算 (MEC) 的资源调度分配优化研究(提供MATLAB代码)

一、优化模型简介 边缘计算资源调度优化模型是为了解决边缘计算场景下的资源分配和任务调度问题而提出的一种数学模型。该模型旨在通过优化算法来实现资源的有效利用和任务的高效执行&#xff0c;以提高边缘计算系统的性能和用户的服务体验。 在边缘计算资源调度优化模型中&a…

基于vue实现计数器案例

一、需求 页面显示两个按钮&#xff0c;分别为&#xff1a;增加 和 减少&#xff1b;显示加减后的数字&#xff1b;加到10提示不能再加&#xff0c;减到0提示不能再减&#xff1b; 二、代码演示 1、实现步骤 data中定义数据: 比如 num 值为1methods中添加两个方法: 比如add…

普通人如何打造自己人生的护城河?

哈喽&#xff0c;大家好啊&#xff0c;我是雷工。 今天在看《张一鸣管理日志》时看到这么一句话&#xff1a; 今日头条不断吸引更优秀的工程师&#xff0c;不断更新算法&#xff0c;才有了当前今日头条的算法护城河。 头条的算法有多牛&#xff0c;看你周边就知道了。 越来越多…

《WebKit 技术内幕》学习之十一(2):多媒体

2 视频 2.1 HTML5视频 在HTML5规范定义中&#xff0c;Web开发者可以使用“video”元素来播放视频资源。视频中有个重要的问题就是视频编码格式&#xff0c;对此&#xff0c;目前标准中包含了三种编码格式&#xff0c;它们分别是Ogg、MPEG4和WebM。其中Ogg是由Xiph.org组织开…

鼠标移入/点击子组件,获取选中子组件事件

不管是移入&#xff0c;或者是点击事件 都要知道是触发的哪个组件 这里子组件是个通用小标题title 所以&#xff0c;通过标题内容&#xff0c;获取触发的哪个子组件子组件 <template><div mouseover"tMouseover" mouseleave"tMouseLeave" class&…

如何使用iPhone或iPad上的二维码共享Wi-Fi密码?这里有详细步骤

你有没有想过在不泄露网络密码的情况下与客人共享你的家庭或工作Wi-Fi?你肯定不是第一个这样想的人,我们很高兴地通知你,多亏了以下这个的变通方法,你现在可以使用iPhone或iPad做到这一点。 通常,如果你想让其他人访问网络,你需要共享你的Wi-Fi密码。苹果通过引入与任何…

Redis原理篇(String)

一.编码方式 String 有三种编码方式 1.RAW编码 type是类型&#xff0c;表示该类型是String类型 encoding是编码方式&#xff0c;表示当前是String的RAW编码方式 ptr指针指向一个SDS&#xff08;动态字符串&#xff09;对象 2.EMBSTR编码 当要存的字符串长度小于44个字节时&…

Fluent Bit配置与使用——基于版本V2.2.2

Fluent Bit日志采集终端 文档适用版本&#xff1a;V2.2 1、日志文件处理流程 数据源是一个普通文件&#xff0c;其中包含 JSON 内容&#xff0c;使用tail插件记录日志&#xff0c;通过parsers进行格式化匹配&#xff08;图里没写&#xff09;&#xff0c;通过两个筛选器&…

手把手教你安装Jupyter Notebook(保姆级教程)

Jupyter Notebook介绍 什么是Jupyter Notebook Jupyter Notebook 是一个基于 Web 的交互式计算环境&#xff0c;支持多种编程语言&#xff0c;包括 Python、R、Julia 等。它的主要功能是将代码、文本、数学方程式、可视化和其他相关元素组合在一起&#xff0c;创建一个动态文…

代码随想录算法训练营29期|day29 任务以及具体安排

* 491.递增子序列 class Solution {List<List<Integer>>result new ArrayList<>();LinkedList<Integer>path new LinkedList<>();boolean[] used;public List<List<Integer>> findSubsequences(int[] nums) {//Arrays.sort(nums);…

MySQL 8.3 发布,具体有哪些新增和删减?

MySQL 8.3 主要更新&#xff1a;用于标记事务分组的 GTID、JSON EXPLAIN 格式增强、一些功能删除等。 MySQL 是一款广泛使用的开源的关系型数据库管理系统&#xff0c;已推出其最新版本 MySQL 8.3。它带来了新功能和一些删除&#xff0c;有望简化数据库操作。让我们来看看有哪些…