Skip to content

图片验证码

作者:江月迟迟
发表于: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.jsvue 文件。
  • js/axios.jsaxios 文件。
  • 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 文件中补全代码,具体需求如下:

  1. 修改 HTML 模版和 script 脚本内容,实现当用户点击某张图片时给图片容器(即 .image-container 元素)添加 selected 类,如果再次点击则去掉该类。当用户没有选择任何图片时,右下方的按钮(即 #check-btn 元素)显示跳过,反之显示提交

  2. 当用户点击右下方按钮(即 #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>