滑动验证(login.vue 和 hd.vue 相关)

login.vue

<template>
  <body id="poster">
  <el-form class="login-container" label-position="left"
           label-width="0px" :rules="loginFormRules" :model="loginForm">
    <h3 class="login_title">系统登录</h3>

    <el-form-item prop="username" >
      <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" ></el-input>
    </el-form-item>

    <el-form-item prop="password">
      <el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码" ></el-input>
    </el-form-item>

    <el-form-item prop="phone">
      <el-input style="width:70% " placeholder="手机号码" v-model="loginForm.phone" ></el-input>
<!--    dialog = true, bbc = true-->
      <el-button class="send" @click="checkV">发送短信</el-button>
<!--      <el-button class="el-icon-refresh-right" @click="refreshPhone"></el-button>-->
    </el-form-item>


    <el-form-item style="width: 100%">
      <el-button type="primary" style="width: 100%;background: #505458;border: none" v-on:click="login">登录</el-button>
    </el-form-item>
  </el-form>


  <el-dialog  v-if="bbc" title="请完成安全验证" :visible.sync="dialog" width="350px" append-to-body center destroy-on-close>
    <Hd v-on:judgment="updateBool"></Hd>
  </el-dialog>


  </body>


</template>


<script>
  import Hd from './Hd'
  export default {
    components:{ Hd },
    name: 'Login',
    data () {
      return {
        loginFormRules: {
          username: [{ required: true, message: '请输入用户名', trigger: 'blur'}],
            password: [{ required: true, message: '请输入密码', trigger: 'blur'}],
            phone: [{
              pattern: /^(13[0-9]|14[01456879]|15[0-3|5-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
              required: true,
              message: '请输入正确的手机号',
              trigger: 'blur'
            }]
        },
        loginForm: {
          username: '',
          password: '',
          phone: ''
        },
        responseResult: [],
        dialog:false,
        bbc:true,
        // this.state.isVerified
        // isVerified:this.$store.state.isVerified,
      }
    },
    methods: {
      checkV(){
        var check = /^(13[0-9]|14[01456879]|15[0-3|5-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
        if(!check.test(this.loginForm.phone)){
          this.$message.error('手机号码格式不正确, 请重新输入');
        }else {
          this.dialog = true
          this.bbc = true
        }
      },
      updateBool(val){
        this.bbc = val
      },
      refreshPhone(){
        this.bbc = true
      },
      login () {
        this.$axios
          .post('/login', {
            username: this.loginForm.username,
            password: this.loginForm.password
          })
          .then(successResponse => {
            if (successResponse.data.code === 200) {
              this.$router.replace({path: '/index'})
            }
          })
          .catch(failResponse => {
          })
      },
    }
  }
</script>


<style scoped>

  /*.send{*/
  /*  width:;*/
  /*}*/

  .login-container {
    border-radius: 15px;
    background-clip: padding-box;
    margin: 90px auto;
    width: 350px;
    padding: 35px 35px 15px 35px;
    background: #fff;
    border: 1px solid #eaeaea;
    box-shadow: 0 0 25px #cac6c6;
  }

  .login_title {
    margin: 0px auto 40px auto;
    text-align: center;
    color: #505458;
  }
  #poster {
    background:url("../assets/Lighthouse.jpg") no-repeat;
    /*background-position: center;*/
    height: 100%;
    width: 100%;
    background-size: cover;
    position: fixed;
    top:0px;
    left:0px;
  }
  html,body{
    margin: 0px;
    padding: 0px;
  }


</style>

hd.vue

<template>
  <div class="verification" ref="verification">
    <!-- 画布部分 -->
    <canvas ref="slideVerify" class="slide-img"></canvas>
    <div style="display:none">
      <img ref="imgs" :src="imgList[imgIndex]"/>
    </div>
    <!-- 下面滑块部分 -->
    <div class="slide-wrapper bg-start">
      <!-- 滑块 -->
      <div class="btn" ref="btn" @mousedown="mouseDown" @mouseup="mouseUp"></div>
      <p class="text" ref="text">{{content}}</p>
      <div class="bg" ref="bg"></div>
    </div>
    <!-- 刷新按钮 -->
<!--    <button class="refresh" @click="refresh"></button>-->



  </div>
</template>

<script>
  export default {
    name:'Hd',
    data () {
      return {
        imgIndex: 0,
        blockCanvas: null,
        imgList: [require('../assets/1.jpg'),
          require('../assets/2.jpg'),
          require('../assets/3.jpg'),
          require('../assets/4.jpg') ],
        content: '滑动滑块',
        isDown: false, // 鼠标是否按下
        btnX: 0,
        imgX: 0,
        // dialogNotVisible: false
      }
    },
    mounted () {
      this.imgIndex = this.randomNumber(0, 4)
      document.addEventListener('mousemove', this.mouseMove)
      this.imageCanvas()
    },
    methods: {

      // 生成随机数字
      randomNumber (min, max) {
        return Math.floor(Math.random() * (max - min) + min)
      },
      // 鼠标按下时
      mouseDown (e) {
        this.isDown = true
        this.btnX = e.clientX - this.$refs.btn.offsetLeft
      },
      // 鼠标滑动时
      mouseMove (e) {
        // 滑块左端向右边移动的距离
        let moveX = e.clientX - this.btnX
        if (this.isDown) {
          // 滑块滑动时不能超过的距离
          if (this.$refs.btn.offsetLeft <= 259 && this.$refs.btn.offsetLeft >= 0) {
            this.$refs.btn.style.left = `${moveX}px`
            this.blockCanvas.style.left = `${moveX - this.imgX}px`
            this.$refs.bg.style.width = `${moveX}px`
          }
        }
      },
      // 滑动中松开
      mouseUp () {
        let leftX = this.$refs.btn.offsetLeft
        // 方块的位置和缺失的位置重合允许左右2px的误差
        if (this.imgX >= leftX - 1 && this.imgX <= leftX + 1) {
          // 滑动成功时的逻辑
          this.$refs.btn.classList.add('btnsuccess')
          this.isDown = false
          this.$message({
            message: '验证成功',
            type: 'success'
          })
          this.$message({
            message: '验证码已发送',
            type: 'success',
            offset:60
          });
          // this.$store.state.isVerified = false
          this.$emit('judgment',false)

          //成功向后端拿验证码
          // this.axios.get('./verifications').then(resp =>{
          //   this.$store.state.verifications = resp.data
          // })

        }
        // 如果滑动失败,滑块自动回到左边
        if (this.isDown) {
          this.$refs.btn.style.left = 0
          this.blockCanvas.style.left = `-${this.imgX}px`
          this.$refs.bg.style.width = 0
        }
        // 开关原则
        this.isDown = false
      },
      // 画图
      imageCanvas () {
        this.blockCanvas = this.blockCanvas ? this.blockCanvas.remove() : null
        this.blockCanvas = this.createCanvas(300, 150)
        this.$refs.verification.insertBefore(this.blockCanvas, this.$refs.slideVerify)
        let x = this.randomNumber(60, 200)
        let y = 40
        this.imgX = x
        let c = this.$refs.slideVerify
        let bg = c.getContext('2d')
        let img = this.$refs.imgs
        let bk = this.blockCanvas.getContext('2d')
        // 在两块画布上都放上相同的图片
        img.onload = () => {
          bg.drawImage(img, 0, 0)
          bk.drawImage(img, 0, 0)
        }
        this.drawBlock(bg, x, y, 'fill')
        this.drawBlock(bk, x, y, 'clip')
      },
      // 画抠出来的方块
      drawBlock (ctx, x, y, type) {
        ctx.beginPath()
        ctx.moveTo(x, y)
        ctx.arc(x + 42 / 2, y - 9 + 2, 9, 0.72 * Math.PI, 2.26 * Math.PI)
        ctx.lineTo(x + 42, y)
        ctx.arc(x + 42 + 9 - 2, y + 42 / 2, 9, 1.21 * Math.PI, 2.78 * Math.PI)
        ctx.lineTo(x + 42, y + 42)
        ctx.lineTo(x, y + 42)
        ctx.arc(x + 9 - 2, y + 42 / 2, 9 + 0.4, 2.76 * Math.PI, 1.24 * Math.PI, true)
        ctx.lineTo(x, y)
        ctx.lineWidth = 2
        ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'
        ctx.stroke()
        ctx[type]()
        ctx.globalCompositeOperation = 'destination-over'
        // 解决进入页面时不自动扣拼图样式的麻烦(有时需要鼠标点击后才会出现裁剪后的拼图)
        this.blockCanvas.style.left = `-${x}px`
      },
      // 刷新
      refresh () {
        // 有时会出现点击刷新,randomNumber返回的数字和上次存储的一样,画布清空后但是填充时没有改变;所以当一样时,不会执行刷新操作
        if (this.imgIndex == this.randomNumber(0, 4)) {
          return false
        }
        this.clean()
        this.$refs.btn.style.left = 0
        this.$refs.bg.style.width = 0
        this.$refs.btn.classList.remove('btnsuccess')
        this.isDown = false // 鼠标是否按下
        this.btnX = 0 // 鼠标点击的水平位置与滑块移动水平位置的差
        this.imgX = 0
        this.imageCanvas('restore')
        this.imgIndex = this.randomNumber(0, 4)
      },
      // 清空canvas
      clean () {
        let cxt2 = this.$refs.slideVerify.getContext('2d')
        cxt2.clearRect(0, 0, 300, 150)
      },
      // 新建canvas
      createCanvas (width, height) {
        const canvas = document.createElement('canvas')
        canvas.width = width
        canvas.height = height
        canvas.style.position = 'absolute'
        return canvas
      }
    }
  }
</script>

<style scoped>
  .verification {
    position: relative;
    width: 300px;
    margin: 0 auto;
  }
  .slide-wrapper {
    position: relative;
    width: 300px;
    height: 40px;
  }
  .bg-start {
    background: cadetblue;
  }
  .bg {
    position: absolute;
    height: 40px;
    background: #ccc;
  }
  .text {
    position: absolute;
    width: 100%;
    height: 40px;
    text-align: center;
    line-height: 40px;
    margin: 0;
    /* z-index: 1; */
  }
  .text-success {
    color: white;
    z-index: 2;
  }
  .btn {
    position: absolute;
    width: 40px;
    height: 40px;
    z-index: 1;
    border-radius: 5px;
    background: rgb(143, 145, 148);
    text-align: center;
    font-size: 24px;
    color: white;
    box-shadow: 0 0 1px 1px #fff;
    background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==");
  }
  .btnsuccess {
    background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==");
  }
  .refresh {
    cursor: pointer;
    width: 20px;
    height: 20px;
    position: absolute;
    z-index: 1;
    top: 0;
    right: 10px;
    opacity: .6;
    background: url('../assets/Desert.jpg') no-repeat;
    background-size: cover;
  }
</style>


验证码验证(login2.vue 和 SIdentify2.vue相关)

login2.vue

<template>
  <body id="poster">
  <el-form class="login-container" label-position="left"
           label-width="0px" :rules="loginFormRules" :model="loginForm">
    <h3 class="login_title">系统登录</h3>

    <el-form-item prop="username" >
      <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" ></el-input>
    </el-form-item>

    <el-form-item prop="password">
      <el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码" ></el-input>
    </el-form-item>

    <el-form-item prop="inputCode">
      <el-input type="text" v-model="inputCode" auto-complete="off" placeholder="验证码" style="width: 55%;margin-left: -158px"></el-input>
      <SIdentify2 v-bind:identifyCode="identifyCode" @click.native="refreshCode" style="background-color: #ebffdd;margin: -40px 0 0 200px"></SIdentify2>
      <el-row style="width:100px;margin-top: -40px;margin-left: 85px;height: 40px">{{tip}}</el-row>
    </el-form-item>

    <el-form-item prop="phone">
      <el-input style="width:70%;margin-left: -105px" placeholder="手机号码" v-model="loginForm.phone" ></el-input>
      <el-tooltip  effect="dark" content="请先完成上方图形验证" placement="top-start" :disabled="IsVer">
        <el-row style="width: 40px;margin-top: -40px;margin-left: 250px">
          <el-button  @click="checkV2" :disabled="!IsVer">发送短信</el-button>
        </el-row>
      </el-tooltip>
    </el-form-item>

    <el-form-item style="width: 100%">
      <el-button type="primary" style="width: 100%;background: #505458;border: none" v-on:click="login">登录</el-button>
    </el-form-item>
  </el-form>
  </body>


</template>


<script>
  import SIdentify2 from './SIdentify2'
  export default {
    components: {SIdentify2},
    name: 'Login2',
    data () {
      return {
        identifyCode:this.refreshCode(),
        inputCode: '',
        loginFormRules: {
          username: [{required: true, message: '请输入用户名', trigger: 'blur'}],
          password: [{required: true, message: '请输入密码', trigger: 'blur'}],
        },
        loginForm: {
          username: '',
          password: '',
        },
        responseResult: [],
        dialog: false,
        bbc: true,
        IsVer: false,
        tip: '',
      }
    },
    watch: {
      inputCode: function (val) {
        this.checkVer(val)
        this.debouncedCheckVer()
      }
    },
    created: function () {
      this.debouncedCheckVer = _.debounce(this.checkVer, 500)
    },

    //预加载
    mounted:function(){
      this.refreshCode()
    },
    methods: {
      refreshCode() {
        var code = 'abcdefghijklm0123456789nopqrstuvwxyz'
        var num =  Math.round(Math.random()*18)
        var num1 = Math.round(Math.random()*24)
        var num2 = Math.round(Math.random()*30)
        var num3 = Math.round(Math.random()*36)
        this.identifyCode = (code.charAt(num)+code.charAt(num1)+code.charAt(num2)+code.charAt(num3))
        this.IsVer = false
        this.tip = ''
      },


      checkV2 () {
        var check = /^(13[0-9]|14[01456879]|15[0-3|5-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
        if (!check.test(this.loginForm.phone)) {
          this.$message.error('手机号码格式不正确, 请重新输入');
        } else {
          this.$message({
            message: '验证码已发送',
            type: 'success'
          })
        }
      },
      //验证码校验
      checkVer: function () {
        if (this.inputCode === this.identifyCode) {
          this.tip = '验证码正确'
          return this.IsVer = (this.inputCode === this.identifyCode)
        } else {
          this.tip = '验证码输入错误'
        }
      },
      updateBool (val) {
        this.bbc = val
      },
      login () {
        this.$axios
          .post('/login', {
            username: this.loginForm.username,
            password: this.loginForm.password
          })
          .then(successResponse => {
            if (successResponse.data.code === 200) {
              this.$router.replace({path: '/index'})
            }
          })
          .catch(failResponse => {
          })
      },
    }
  }
</script>


<style scoped>

  /*.send{*/
  /*  width:;*/
  /*}*/

  .login-container {
    border-radius: 15px;
    background-clip: padding-box;
    margin: 90px auto;
    width: 350px;
    padding: 35px 35px 15px 35px;
    background: #fff;
    border: 1px solid #eaeaea;
    box-shadow: 0 0 25px #cac6c6;
  }

  .login_title {
    margin: 0px auto 40px auto;
    text-align: center;
    color: #505458;
  }
  #poster {
    background:url("../assets/Lighthouse.jpg") no-repeat;
    /*background-position: center;*/
    height: 100%;
    width: 100%;
    background-size: cover;
    position: fixed;
    top:0px;
    left:0px;
  }
  html,body{
    margin: 0px;
    padding: 0px;
  }


</style>


SIdentify2.vue

<template>
  <div class="s-canvas">
    <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
  </div>
</template>
<script>
  export default {
    name: 'SIdentify2',
    props: {
      identifyCode: {
        type: String,
        default: '45df'
      },
      fontSizeMin: {
        type: Number,
        default: 25
      },
      fontSizeMax: {
        type: Number,
        default: 30
      },
      backgroundColorMin: {
        type: Number,
        default: 255
      },
      backgroundColorMax: {
        type: Number,
        default: 255
      },
      colorMin: {
        type: Number,
        default: 0
      },
      colorMax: {
        type: Number,
        default: 160
      },
      lineColorMin: {
        type: Number,
        default: 100
      },
      lineColorMax: {
        type: Number,
        default: 255
      },
      dotColorMin: {
        type: Number,
        default: 0
      },
      dotColorMax: {
        type: Number,
        default: 255
      },
      contentWidth: {
        type: Number,
        default: 132
      },
      contentHeight: {
        type: Number,
        default: 31
      }
    },
    methods: {
      // 生成一个随机数
      randomNum(min, max) {
        return Math.floor(Math.random() * (max - min) + min)
      },
      // 生成一个随机的颜色
      randomColor(min, max) {
        let r = this.randomNum(min, max)
        let g = this.randomNum(min, max)
        let b = this.randomNum(min, max)
        return 'rgb(' + r + ',' + g + ',' + b + ')'
      },
      drawPic() {
        let canvas = document.getElementById('s-canvas')
        let ctx = canvas.getContext('2d')
        ctx.textBaseline = 'bottom'
        // 绘制背景
        ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
        ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
        // 绘制文字
        for (let i = 0; i < this.identifyCode.length; i++) {
          this.drawText(ctx, this.identifyCode[i], i)
        }
        this.drawLine(ctx)
        this.drawDot(ctx)
      },
      drawText(ctx, txt, i) {
        ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
        ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
        let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
        let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
        var deg = this.randomNum(-45, 45)
        // 修改坐标原点和旋转角度
        ctx.translate(x, y)
        ctx.rotate(deg * Math.PI / 180)
        ctx.fillText(txt, 0, 0)
        // 恢复坐标原点和旋转角度
        ctx.rotate(-deg * Math.PI / 180)
        ctx.translate(-x, -y)
      },
      drawLine(ctx) {
        // 绘制干扰线
        for (let i = 0; i < 5; i++) {
          ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
          ctx.beginPath()
          ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
          ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
          ctx.stroke()
        }
      },
      drawDot(ctx) {
        // 绘制干扰点
        for (let i = 0; i < 80; i++) {
          ctx.fillStyle = this.randomColor(0, 255)
          ctx.beginPath()
          ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
          ctx.fill()
        }
      }
    },
    watch: {
      identifyCode() {
        this.drawPic()
      }
    },
    mounted() {
      this.drawPic()
    }
  }
</script>
<style scoped>
  .s-canvas {
    height: 38px;

  }
  .s-canvas canvas{
    margin-top: 1px;
    margin-left: 8px;
  }
</style>

尾部

Logo

前往低代码交流专区

更多推荐