這里記錄登錄頁(yè)面驗(yàn)證碼的做法,采取的是前后端分離的做法,前端用Vue,后端用Flask
首先是GIF效果圖:
后端返回的數(shù)據(jù)結(jié)構(gòu)(base64字符串,response.data.img):
?
1、Vue前端頁(yè)面基本采用Ruoyi Ui里面的登錄頁(yè)面代碼,里面的一些方法進(jìn)行重寫;
- 首先是單個(gè)vue文件里網(wǎng)頁(yè)內(nèi)容<template></template>部分:
<template>
<div class="login">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">通用后臺(tái)管理系統(tǒng)</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="賬號(hào)"
prefix-icon="el-icon-user"
>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密碼"
@keyup.enter.native="handleLogin"
prefix-icon="el-icon-lock"
show-password
>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="驗(yàn)證碼"
style="width: 63%"
@keyup.enter.native="handleLogin"
prefix-icon="el-icon-key"
>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-checkbox
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>記住密碼</el-checkbox>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width: 100%"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 錄</span>
<span v-else>登 錄 中...</span>
</el-button>
<div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'"
>立即注冊(cè)</router-link
>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright ? 2021-2022 VIP.vip All Rights Reserved.</span>
</div>
</div>
</template>
- 交互方法<Script></Script>部分:
1) Axios請(qǐng)求配置。
function get(url, params, response_type) {
let newAxios = axios.create()
let promise;
// 請(qǐng)求超時(shí)時(shí)間
newAxios.defaults.timeout = 10000;
return new Promise((resolve, reject) => {
promise = newAxios.get(url, {
params: params,
responseType: response_type
});
promise.then((response) => {
resolve(response);
}).catch(error => {
reject(error);
})
})
}
Vue.prototype.get = get
2) toLogin登錄方法。
export function toLogin(data) {
return this.get('/login/toLogin', [data])
}
3) 其他js代碼,其中setToken,setUserInfo,setInstId,removeAll方法不重要,這些方法是登錄后保存一些用戶信息用的,因此讀者可自行忽略掉。
<script>
import { toLogin } from "@/api/login.js";
import {
setToken,
setUserInfo,
setInstId,
removeAll,
} from "../../utils/permission";
export default {
name: "Login",
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "123456",
rememberMe: false,
code: "",
uuid: "",
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "請(qǐng)輸入您的賬號(hào)" },
],
password: [
{ required: true, trigger: "blur", message: "請(qǐng)輸入您的密碼" },
],
code: [{ required: true, trigger: "change", message: "請(qǐng)輸入驗(yàn)證碼" }],
},
loading: false,
// 驗(yàn)證碼開關(guān)
captchaOnOff: true,
// 注冊(cè)開關(guān)
register: false,
redirect: undefined,
};
},
watch: {
$route: {
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true,
},
},
created() {
this.getCode();
},
methods: {
getCode() {
this.get("/getImgCode").then((res) => {
this.codeUrl = res.data.img;
});
},
// 登錄請(qǐng)求
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
let _this = this;
toLogin(_this.loginForm)
.then((res) => {
removeAll(); //清除所有本地緩存
if (res.status === 200) {
this.$store.commit("getUserId", res.data.userInfo.ID);
setToken(res.data.token);
let fixedUserInfo = res.data.userInfo;
this.get(
"/myinfo_img_download",
[fixedUserInfo.img, fixedUserInfo.ID, "login"],
""
).then((res) => {
if (res.data.code != 200) {
_this.$message.error(res.data.msg);
fixedUserInfo.img = res.data.imgs;
setUserInfo(JSON.stringify(fixedUserInfo));
_this.$router.push("/");
} else {
fixedUserInfo.img = res.data.imgs;
setUserInfo(JSON.stringify(fixedUserInfo));
_this.$router.push("/");
}
});
setInstId(res.data.inst_id);
_this.showMsg(res.data.userInfo.nickname);
} else if (res.status === 201) {
_this.$message({
message: "該用戶已被停用!",
type: "warning",
});
} else if (res.status === 304) {
_this.$message({
message: "驗(yàn)證碼錯(cuò)誤!",
type: "error",
});
} else if (res.status === 500) {
_this.$message({
message: "密碼錯(cuò)誤!",
type: "error",
});
} else {
_this.$message({
message: "用戶名不存在!",
type: "error",
});
}
})
.catch((err) => {
console.log(err);
});
}
});
},
// 消息通知
showMsg(realName) {
this.$notify({
title: "提示",
dangerouslyUseHTMLString: true,
message: "尊敬的<strong>" + realName + "</strong>,歡迎回來(lái)。",
type: "success",
duration: 2000,
});
},
},
};
</script>
4) CSS代碼,注意background-image中的圖片要自己準(zhǔn)備。
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../../assets/images/login-background.jpg");
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
font-weight: 600;
font-size: 21px;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 38px;
float: right;
display: flex;
img {
margin-left: 10px;
position: relative;
top: -3px;
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 38px;
}
</style>
login-backg.jpg?
?
2、Flask的代碼則參考另外一篇網(wǎng)上的代碼段。
from flask import Flask, render_template,\
request, jsonify, make_response, Response, send_file,session,send_from_directory
from flask_cors import CORS
import json
import base64
app = Flask(__name__)
''' 解決后端跨域問(wèn)題,不然會(huì)在前端網(wǎng)頁(yè)控制臺(tái)顯示“ccess to XMLHttpRequest at 'http://localhost:8080/api/login' from origin 'null' has been blocked” '''
CORS(app, supports_credentials=True)
# 存儲(chǔ)驗(yàn)證碼
session = {}
# 前端Login界面調(diào)取驗(yàn)證碼圖片,返回JSON字符串,非blob數(shù)據(jù)類型
@app.route('/getImgCode', methods=["GET", "POST"])
def imgCode():
res = imageCode().getImgCode()
return jsonify({"img":res})
# -------------------------------- #
# from io import BytesIO
import random
import string
from PIL import Image, ImageFont, ImageDraw, ImageFilter
# 生成驗(yàn)證碼
class imageCode():
'''驗(yàn)證碼處理'''
def rndColor(self):
'''隨機(jī)顏色'''
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
def geneText(self):
'''生成4位驗(yàn)證碼'''
# ascii_letters是生成所有字母 digits是生成所有數(shù)字0-9
imgCode = ''.join(random.sample(string.ascii_letters + string.digits, 4))
return imgCode
def drawLines(self, draw, num, width, height):
'''劃線'''
for num in range(num):
x1 = random.randint(0, width / 2)
y1 = random.randint(0, height / 2)
x2 = random.randint(0, width)
y2 = random.randint(height / 2, height)
draw.line(((x1, y1), (x2, y2)), fill='black', width=1)
def getVerifyCode(self):
'''生成驗(yàn)證碼圖形'''
code = self.geneText()
# 圖片大小120×50
width, height = 120, 50
# 新圖片對(duì)象
im = Image.new('RGB', (width, height), 'white')
# 字體
font = ImageFont.truetype('app/static/arial.ttf', 40)
# draw對(duì)象
draw = ImageDraw.Draw(im)
# 繪制字符串
for item in range(4):
draw.text((5 + random.randint(-3, 3) + 23 * item, 5 + random.randint(-3, 3)),
text=code[item], fill=self.rndColor(), font=font)
# 劃線,參數(shù)1為畫板,參數(shù)2為線條數(shù)量,參數(shù)3為寬度,參數(shù)4為高度
self.drawLines(draw, 2, width, height)
return im, code
def getImgCode(self):
image, code = self.getVerifyCode()
session['imageCode'] = code
file_path =r"./upload/loginPic.jpg"
image.save(file_path)
# 把驗(yàn)證碼圖片的base64字段作為response返回前端,類型是string
with open(file_path, 'rb') as img_f:
img_stream = img_f.read()
img_stream = base64.b64encode(img_stream)
base64_string = img_stream.decode('utf-8')
base64_string = "data:image/png;base64," + base64_string
return base64_string
if __name__ == '__main__':
# 0.0.0.0 表示同一個(gè)局域網(wǎng)均可訪問(wèn),也可以替換成本機(jī)地址:通過(guò)命令行命令:ipcofig 獲取
app.run(host='0.0.0.0', port='5000', debug=True)
上面geneText是生產(chǎn)隨機(jī)字母和數(shù)字結(jié)合的驗(yàn)證碼內(nèi)容的方法,此步比較關(guān)鍵
然后是PIL(Pillow)庫(kù)畫圖的方法了:
ImageDraw.Draw.text()是在給定位置繪制字符串,生成圖片返回Web端使用。ImageDraw.Draw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align=”left”)
ImageDraw.Draw.line()是在給定xy的數(shù)組,fill的填充顏色,線的寬度情況下劃線
ImageDraw.Draw.line(xy, fill=None, width=0)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-403396.html
最后以返回圖片的base64字符串給前端即可?
3、參考網(wǎng)址:?
flask驗(yàn)證碼https://www.qb5200.com/article/361920.html文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-403396.html
到了這里,關(guān)于菜鳥級(jí):Vue Element-UI 前端 + Flask 后端 的登錄頁(yè)面驗(yàn)證碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!