用canvas实现验证码的绘制
login.vue主文件
1 <template> 2 <div class="login-wrapper"> 3 <img src="../../assets/images/logo.png" class="logo" /> 4 <div class="login-box"> 5 <ul class="login-title-list"> 6 <li class="login-title-item active">账号登录</li> 7 <li class="login-title-item">二维码登录</li> 8 </ul> 9 <div class="login-body"> 10 <el-form 11 autocomplete="off" 12 :model="loginForm" 13 :rules="loginRules" 14 ref="loginForm" 15 label-position="left" 16 label-width="0px" 17 class="card-box login-form" 18 > 19 <el-form-item prop="username"> 20 <svg-icon icon-class="user" class="icon-svg" /> 21 <el-input placeholder="请输入邮箱" type="text" v-model="loginForm.username" /> 22 </el-form-item> 23 <el-form-item prop="password"> 24 <svg-icon icon-class="password" class="icon-svg" /> 25 <el-input placeholder="请输入密码" type="password" v-model="loginForm.password" /> 26 </el-form-item> 27 <el-form-item prop="verifycode" class="form-item-captcha"> 28 <el-input placeholder="请输入验证码" type="captcha" v-model="loginForm.verifycode" /> 29 <span @click="refreshCode" class="yzm"> 30 <s-identify :identifyCode="identifyCode"></s-identify> 31 </span> 32 </el-form-item> 33 <el-form-item class="login-wrap"> 34 <el-button type="primary" :loading="loading" style="width:100%;" @click="handleLogin" 35 >登录</el-button 36 > 37 </el-form-item> 38 </el-form> 39 <div class="login-footer"> 40 <el-checkbox v-model="rememberPassword">记住密码</el-checkbox> 41 <span>忘记密码?</span> 42 </div> 43 </div> 44 </div> 45 </div> 46 </template> 47 48 <script> 49 import { mapState } from 'vuex'; 50 import SIdentify from './identify.vue'; 51 import { encryptDes } from '../../assets/js/utils/des'; 52 53 export default { 54 name: 'Login', 55 components: { 56 SIdentify 57 }, 58 data() { 59 // 验证码自定义验证规则 60 const validateVerifycode = (rule, value, callback) => { 61 let newVal = value.toLowerCase(); 62 let identifyStr = this.identifyCode.toLowerCase(); 63 if (newVal === '') { 64 callback(new Error('请输入验证码')); 65 } else if (newVal !== identifyStr) { 66 callback(new Error('验证码不正确!')); 67 } else { 68 callback(); 69 } 70 }; 71 return { 72 loginForm: { 73 username: '', 74 password: '', 75 verifycode: '' 76 }, 77 identifyCodes: '1234567890ABCDEFGHIGKLMNOPQRSTUVWXYZ', 78 identifyCode: '', 79 rememberPassword: true, 80 loading: false, 81 loginRules: { 82 username: [{ required: true, trigger: 'blur', message: '账号不能为空' }], 83 password: [ 84 { required: true, message: '请输入密码', trigger: 'blur' }, 85 { min: 6, message: '密码长度最少为6位', trigger: 'blur' } 86 ], 87 verifycode: [{ required: true, trigger: 'blur', validator: validateVerifycode }] 88 } 89 }; 90 }, 91 methods: { 92 // 生成随机数 93 randomNum(min, max) { 94 return Math.floor(Math.random() * (max - min) + min); 95 }, 96 // 切换验证码 97 refreshCode() { 98 this.identifyCode = ''; 99 this.makeCode(this.identifyCodes, 4); 100 }, 101 // 生成四位随机验证码 102 makeCode(o, l) { 103 for (let i = 0; i < l; i++) { 104 this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]; 105 } 106 }, 107 handleLogin() { 108 this.$refs.loginForm.validate(valid => { 109 if (valid) { 110 this.loading = true; 111 const loginParams = { 112 loginUserId: this.loginForm.username, 113 password: encryptDes(this.loginForm.password) 114 }; 115 this.$store.dispatch('login', loginParams).then(res => { 116 this.loading = false; 117 if (res.status === 200) { 118 this.$store.commit('SET_USER_INFO', res.data.onlineUserInfo); 119 this.$router.push({ path: '/' }); 120 } 121 }); 122 } else { 123 return false; 124 } 125 }); 126 } 127 }, 128 mounted() { 129 // 验证码初始化 130 this.identifyCode = ''; 131 this.makeCode(this.identifyCodes, 4); 132 } 133 }; 134 </script>
identify.vue文件
1 <template> 2 <div class="s-canvas"> 3 <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas> 4 </div> 5 </template> 6 <script> 7 export default { 8 name: 'SIdentify', 9 props: { 10 identifyCode: { 11 type: String, 12 default: '1234' 13 }, 14 fontSizeMin: { 15 type: Number, 16 default: 28 17 }, 18 fontSizeMax: { 19 type: Number, 20 default: 40 21 }, 22 backgroundColorMin: { 23 type: Number, 24 default: 180 25 }, 26 backgroundColorMax: { 27 type: Number, 28 default: 240 29 }, 30 colorMin: { 31 type: Number, 32 default: 50 33 }, 34 colorMax: { 35 type: Number, 36 default: 160 37 }, 38 lineColorMin: { 39 type: Number, 40 default: 40 41 }, 42 lineColorMax: { 43 type: Number, 44 default: 180 45 }, 46 dotColorMin: { 47 type: Number, 48 default: 0 49 }, 50 dotColorMax: { 51 type: Number, 52 default: 255 53 }, 54 contentWidth: { 55 type: Number, 56 default: 112 57 }, 58 contentHeight: { 59 type: Number, 60 default: 40 61 } 62 }, 63 methods: { 64 // 生成一个随机数 65 randomNum(min, max) { 66 return Math.floor(Math.random() * (max - min) + min); 67 }, 68 // 生成一个随机的颜色 69 randomColor(min, max) { 70 let r = this.randomNum(min, max); 71 let g = this.randomNum(min, max); 72 let b = this.randomNum(min, max); 73 return `rgb(${r},${g},${b})`; 74 }, 75 drawPic() { 76 let canvas = document.getElementById('s-canvas'); 77 let ctx = canvas.getContext('2d'); 78 ctx.textBaseline = 'bottom'; 79 // 绘制背景 80 ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax); 81 ctx.fillRect(0, 0, this.contentWidth, this.contentHeight); 82 // 绘制文字 83 for (let i = 0; i < this.identifyCode.length; i++) { 84 this.drawText(ctx, this.identifyCode[i], i); 85 } 86 this.drawLine(ctx); 87 this.drawDot(ctx); 88 }, 89 drawText(ctx, txt, i) { 90 ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax); 91 ctx.font = `${this.randomNum(this.fontSizeMin, this.fontSizeMax)}px SimHei`; 92 let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1)); 93 let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5); 94 let deg = this.randomNum(-30, 30); 95 // 修改坐标原点和旋转角度 96 ctx.translate(x, y); 97 ctx.rotate((deg * Math.PI) / 270); 98 ctx.fillText(txt, 0, 0); 99 // 恢复坐标原点和旋转角度 100 ctx.rotate((-deg * Math.PI) / 270); 101 ctx.translate(-x, -y); 102 }, 103 drawLine(ctx) { 104 // 绘制干扰线 105 for (let i = 0; i < 2; i++) { 106 ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax); 107 ctx.beginPath(); 108 ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight)); 109 ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight)); 110 ctx.stroke(); 111 } 112 }, 113 drawDot(ctx) { 114 // 绘制干扰点 115 for (let i = 0; i < 20; i++) { 116 ctx.fillStyle = this.randomColor(0, 255); 117 ctx.beginPath(); 118 ctx.arc( 119 this.randomNum(0, this.contentWidth), 120 this.randomNum(0, this.contentHeight), 121 1, 122 0, 123 2 * Math.PI 124 ); 125 ctx.fill(); 126 } 127 } 128 }, 129 watch: { 130 identifyCode() { 131 this.drawPic(); 132 } 133 }, 134 mounted() { 135 this.drawPic(); 136 } 137 }; 138 </script>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!