图片验证码
发表于:2024-12-10
字数统计:1627 字
预计阅读6分钟
介绍
在我们访问一些网站的时候,常常会遇到让我们选择图片来验证是否是人类的验证码,利用这种方式网站可以过滤掉来自机器的恶意访问。
本题请实现一个带图片验证码功能的登录页面。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
txt
├── css
│ └── style.css
├── effect.gif
├── images
├── index.html
├── initial.gif
└── js
├── axios.js
├── data.json
└── vue.js其中:
css/style.css是样式文件。index.html是主页面。images文件夹内包含了页面使用的图片文件。js/vue.js是vue文件。js/axios.js是axios文件。js/data.json是图片数据文件。effect.gif是页面最终的效果图。initial.gif是页面的初始效果图。
注意:打开环境后发现缺少项目代码,请复制下述命令至命令行进行下载:
bash
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/18421/pic.zip
unzip pic.zip && rm pic.zip在浏览器中预览 index.html 页面,初始交互效果可参考文件夹下面的 gif 图,图片名称为 initial.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。

目标
请在 index.html 文件中补全代码,具体需求如下:
修改
HTML模版和script脚本内容,实现当用户点击某张图片时给图片容器(即.image-container元素)添加selected类,如果再次点击则去掉该类。当用户没有选择任何图片时,右下方的按钮(即#check-btn元素)显示跳过,反之显示提交。当用户点击右下方按钮(即
#check-btn元素)时,对用户选择的正确性进行判断。规则如下:- 用户必须选中所有正确的图片,并且不能有错误的图片被选(图片的正误可以根据
data.json中图片的type属性判断)。
data.json参数说明参数 说明 类型 path图片路径 string type图片是否是正确答案 boolean category图片对应的问题类别 number - 如果用户选择错误,则展示验证失败,请重试的提示,并让用户继续选择;反之展示验证成功的提示,并将弹窗关闭(即
.modal元素),同时隐藏验证码开启按钮(即.box元素),并显示登录(即#login元素)按钮。
- 用户必须选中所有正确的图片,并且不能有错误的图片被选(图片的正误可以根据
最终效果可参考文件夹下面的 gif 图,图片名称为 effect.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。

规定
- 请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成无法判题通过。
- 满足需求后,保持 Web 服务处于可以正常访问状态,点击「提交检测」系统会自动检测。
判分标准
- 完成目标 1,得 5 分。
- 完成目标 2,得 15 分。
总通过次数: 17 | 总提交次数: 13 | 通过率: 130.8%
难度: 中等 标签: 蓝桥杯, 2023, 国赛, Web 前端, Vue.js
题解
vue
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>图片验证码</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script src="js/axios.js"></script>
</head>
<body>
<div id="app">
<header>
<h1>图片验证码</h1>
</header>
<div class="container">
<form action="" @submit.prevent="">
<label for="id">账号:</label>
<input type="text" name="id">
<label for="password">密码:</label>
<input type="password" name="password">
<div class="box" v-if="showBox">
<div class="checkbox" @click="startCheck"></div>
<span>我是人类</span>
</div>
<button id="login" type="submit" v-if="showLogin">登录</button>
</form>
</div>
<div class="modal" v-if="showModal">
<div class="head">
<h2>请点击所有包含{{ hint }}的图片</h2>
<p>如果没有,请点击"跳过"</p>
</div>
<div class="images">
<div class="image-container" v-for="(image, index) in images" @click="addSelected(index, $event)">
<img :src="image.path">
</div>
</div>
<div class="footer">
<img src="images/redo.svg" @click="startCheck">
<button id="check-btn" @click="check">{{ selectedIndexCounts ? '提交' : '跳过' }}</button>
</div>
</div>
<div class="toast" v-if="showToast">
{{ toastContent }}
</div>
</div>
</body>
<script>
var vm = new Vue({
el: "#app",
// TODO: 你可以根据需要添加新的data或computed属性
data: {
category: '',
toastContent: '',
allImages: [],
images: [],
showToast: false,
showModal: false,
showLogin: false,
showBox: true,
selectedIndexCounts: 0,
selectedIndexMap: {}
},
created() {
axios.get("./js/data.json").then(res => {
this.allImages = res.data;
})
},
computed: {
hint() {
return this.category === 1 ? '果汁' : '猫咪'
},
},
methods: {
// 展示验证图像模态框,随机选择一个问题类型和九张图片
startCheck() {
this.showModal = true;
this.category = Math.random() > 0.5 ? 1 : 2;
this.images = pick(this.allImages.filter(i => i.category === this.category), 9)
this.selectedIndexCounts = 0
this.selectedIndexMap = {}
document.querySelectorAll('.image-container').forEach(item => {
item.classList.contains('selected') ? item.classList.remove('selected') : ''
})
},
// TODO: 请修改以下代码实现用户选择正确与否的判断逻辑,可以添加新的方法
check() {
let result;
// 根据用户选择的正确与否展示相应的toast
result = this.images.every((item, index) => (item.type === false && !this.selectedIndexMap.hasOwnProperty(index) || (item.type === true) && this.selectedIndexMap.hasOwnProperty(index)));
if (result) {
this.toastContent = '验证成功'
this.showModal = false
this.showLogin = true
this.showBox = false
} else {
this.toastContent = '验证失败,请重试'
}
// 展示提示用户验证情况的Toast
this.showToast = true;
setTimeout(() => {
this.showToast = false;
}, 1500)
},
addSelected(index, event) {
if (event.currentTarget.classList.contains('selected')) {
event.currentTarget.classList.remove('selected')
this.selectedIndexCounts--
delete this.selectedIndexMap[index]
} else {
event.currentTarget.classList.add('selected')
this.selectedIndexCounts++
this.selectedIndexMap[index] = true
}
}
},
});
// 从数组中随机选择指定数量的元素
function pick(array, number) {
const s = new Set();
for (let i = 0; i < number; i++) {
const num = Math.floor(Math.random() * 20);
if (s.has(num)) {
i--;
} else {
s.add(num)
}
}
return Array.from(s).map(n => array[n]);
}
</script>
</html>