图片验证码
介绍
在我们访问一些网站的时候,常常会遇到让我们选择图片来验证是否是人类的验证码,利用这种方式网站可以过滤掉来自机器的恶意访问。
本题请实现一个带图片验证码功能的登录页面。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
1 2 3 4 5 6 7 8 9 10
| ├── 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 是页面的初始效果图。
注意:打开环境后发现缺少项目代码,请复制下述命令至命令行进行下载:
1 2 3
| 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
题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| <!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>
|