13届蓝桥杯Web大学组题解

667 阅读14分钟

蓝桥杯题解

题目以及参考答案

github.com/gaoxiaoxin/…

一些想法及考试的注意事项

其实蓝桥杯 web 的这次试题让我感觉是偏中等的前端的一些应用, 关键是不能看一些 api 文档, 这就比较致命(对于基础不扎实的我, 感觉就很灾难), 其实日常开发一些东西都是面向百度和 Google 进行开发, 对 api 的这些东西记忆不是很上心, 只是有一个印象。 当我们适应了使用框架去进行开发, 就疏忽了对 js 操作 DOM 的一些关注, 当考出来的时候, 就有些傻眼。

自我总结: 看到考点有 jQuery, 就突击看了一遍教程文档, 过了一遍, 以至于今天看到要操作 DOM 的题目(题 5), 就下意识的使用了 jq; 并且使用了错误的 api...(就裂开来), 加上最近熬夜不喝水导致患上了三叉神经炎带的有的头疼, 在思路不清晰的时候胡乱的尝试了一些写法;导致在第五题前前后后花费一个半小时...

组委会给的题目知识点参考范围 看上去知识点很多, 这就很看重平时的使用。jQuery...

开始解题

配合 GitHub 地址中的题目食用

第一题 水果拼盘

这个题就是考验你的 flex 的基本使用,首先运行代码会得到一个 这个看上去的话就有点很怪, 读题后发现题目是禁止我们去修改圆盘的位置和图片的大小的, 然后把对应的水果归位 那么首先调出控制台, 并且观察代码, 然后有思路后再去进行操作 可以看到这个是简单的 flex 排列,那么对应于 flex 的话, 其实浏览器有个很好用的功能(平时的小积累) 然后第一题基本就是签到题, 不会太难,基本就是改几个参数的事情, 那么我们就可以在刚才的参数盘上进行调试 这里可以看到, 我们在调整了flex-directionflex-wrap 之后就和我们题目的要求一模一样了 然后就去代码中进行更改就行了

总结: 需要熟悉 flex 的属性的使用以及一些小工具技巧的使用

第二题 展开你的扇子

这个题就是考验你的 CSS3 的transform属性的使用 以及 伪元素选择器的使用 首先读题,看题目代码, 题目要求把 12 个方块分为两组, 一组顺时针, 一组逆时针;鼠标放上去进行旋转; 并且组内元素之间相隔 10deg 提取关键词 两组, 旋转, hover伪元素, 旋转单位 10deg; 那么首先, 将 12 个元素的 css 选择器分为两组, 然后从中间分割的地方向上, 对组一的元素进行旋转的大小设置,(这样比较节约时间) 依次加减 10 就行, 组一逆时针就-10deg, 组二顺时针就 +10deg;然后就解决了

#box:hover #item1 {
  transform: rotate(-60deg);
}
#box:hover #item2 {
  transform: rotate(-50deg);
}
#box:hover #item3 {
  transform: rotate(-40deg);
}
#box:hover #item4 {
  transform: rotate(-30deg);
}
#box:hover #item5 {
  transform: rotate(-20deg);
}
#box:hover #item6 {
  transform: rotate(-10deg);
}
/* 顺时针 */
#box:hover #item7 {
  transform: rotate(10deg);
}
#box:hover #item8 {
  transform: rotate(20deg);
}
#box:hover #item9 {
  transform: rotate(30deg);
}
#box:hover #item10 {
  transform: rotate(40deg);
}
#box:hover #item11 {
  transform: rotate(50deg);
}
#box:hover #item12 {
  transform: rotate(60deg);
}

总结: 思路清晰是关键, 需要了解 Csstransform的属性的使用(知道有就行, 毕竟属性不多,可以到时候调试排除)

第三题 和手机相处的时光

这个题就是考验你对echarts有没有一些基础的了解, 为什么说基础, 因为真的很基础 有兴趣的可以看我 GitHub 上 使用 Echarts 写的一个项目。

从题目上基本就可以看出这个折线图的 X 轴和 Y 轴是反了, 那么我们将他们调整过来就可以了。 这个题由于我之前也是 面向文档开发,导致我有一点懵, 然后我想起来之前写柱状图的时候要显示汉字是要把坐标轴的属性设置为 category 那么 那么就去尝试设置 X 轴的type, 设置过后和我想的一模一样, 这样题目就出来了。 这对于当时写题来说, 得出结果就是唯一, 但是现在写题解, 就要知其所以然; 面向文档学习: echarts.apache.org/zh/option.h…

首先查看: 坐标轴的 type 代表什么: :::info 代表坐标轴要显示的数据类型

  • value: 数值轴
  • category: 类目轴,为该类型时类目数据可自动从 series.data 或 dataset.source 中取,或者可通过 xAxis.data 设置类目数据。
  • time : 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,在刻度计算上也有所不同,例如会根据跨度的范围来 决定使用月,星期,日还是小时范围的刻度。
  • log : 对数轴。适用于对数数据。

然后去看 data, 其实 data 是简化了数据的映射, 并且文档中有说 那么其实就不难理解, 如果我们没有更换坐标轴的类型, 那么其实 data 中的一维数组是映射到了 X 轴的坐标 :::

后面考虑写一下 echarts 的使用。

总结: 胆大心细, 工具类库的使用

第四题 灯的颜色变化

这个题就是简单的考验你定时器的使用 仔细的读题后, 我们提取关键字 颜色变化, 3s, 操纵display; 然后我们就可以想到定时器, display, 去看代码是有调用trafficlights这个函数; 那么我们就 先想对应的步骤逻辑

  • 先在trafficlights函数中延迟 3s 调用 red 函数, 使灯变红;
  • 在 red 函数中延迟调用 green 函数使灯变绿;

那么其实我们再去考虑一些细节:

  1. 在变色前隐藏前一个颜色的灯的图片 : 选取对应元素设置 display = "none";
  2. 让自己显示 : 选取对应元素设置 display = "block" ps: 这里吃了一个不细心的亏(其实和惯性思维有关), 直接使用了 block 属性; 然后发现图片有了偏移, 最后看人家的元素属性设置, 人家是设置的 'inline-block'
  3. 然后在 red, green 函数中分别实习上面的部分

总结: 看你细心与否, 基础题

第五题 冬奥大抽奖

这个其实就是考你的 Dom 操作, 以及你的思路(有一些小坑), 基本就是考验你的临场发挥和随机应变了;

首先看题以及代码, 我们发现其实, 就是在激活的那个元素上设置 active class , 然后把最终选中的元素的名称显示 在上面, 其实官方已经降低了难度, 没有让我们从 0 实习一个抽奖程序(当时时间是个问题); 那么现在来整理我们的思路

  1. 在看了代码以及题目要求后, 我们发现清除定时器、转动次数、总转动次数和时间间隔已经给出, 并且递归代码也 已经书写完成, 我们只要写逻辑
  2. 那么, 我们就可以先有一个基本思路:
    • 首先获取那 8 个元素是必要的;
    • 然后又发现 times 是在 20~30 之间进行随机, 那么必然会转超过一圈, 那么我们就需要以 8 为一个分界线 我们可以对 time % 8 进行处理;
    • 并且我们在设置 active 后, 轮到下一个元素的时候,上一个的还会在, 那么我们就需要清除之前的样式;
    • 在最后将选中的元素信息显示到 #award 中;

那么我们的初步计划就是上面那样, 那么具体实施呢? 首先我们要创建一个数组liNodes用来收集那 8 个 li 元素,然后循环通过 class 获取对应的 8 个元素; 然后下一步按道理是要对属性进行设置 active, 但是这样的话就会导致我们在前一个元素的样式没有消失的时候 下一个元素就被激活... 所以,在激活下一个元素前, 必须重新设置所有元素的样式, 直接一把梭,循环清除全部样式 然后下一步去设置激活的元素样式(其实这里有个小问题): 现在要使用的元素都在 liNodes 中, 那么其实我们就是不断地去从数组中提取我们需要的元素, 那么怎么提取和设置 class 都是一个问题; 这里我的解决方法比较取巧, 我使用了 time % 8, 因为在liNides中元素是按 class li1~8 排列的, 那么我这样 做正好能获取对应的元素,并且实现抽奖的循环, 这里因为 time 在开始的时候先进行了 ++ ; 本着不动题目提供代码 的原则, 我做了一些调整 : liNodes[(time - 1) % 8], 然后我写出了下面的一行设置代码

liNodes[(time - 1) % 8].setAttribute("class", `li${time % 8} active`);

其实,现在去看,可以发现后面的 class 设置是有问题的;当我运行的时候, 在第一圈结束之后就开始报错, 出现了 之前不存在的 class li0!!!, 然后就进行了一系列操作(其实也不多,比赛后就能很快确定错误)失败后调试代码为

liNodes[(time - 1) % 8].setAttribute(
  "class",
  `li${time % 8 === 0 ? 8 : time % 8} active`
);

最后显示信息;

最终代码(原生版本)为:

let rollTime; // 定义定时器变量用来清除定时器
let time = 0; // 转动次数
let speed = 300; // 转动时间间隔
let times; // 总转动次数

// 开始按钮点击事件后开始抽奖
$("#start").on("click", function () {
  $("#award").text(""); //清空中奖信息
  // times = 1
  times = parseInt(Math.random() * (20 - 30 + 1) + 20, 10); // 定义总转动次数,随机20-30次
  rolling();
});

/* 
1. 获取li1- li8
2. 添加激活类
3. 在激活下一个之前清除之前的样式
4. 8 个 一循环
*/

// TODO:请完善此函数
function rolling() {
  time++; // 转动次数加1
  clearTimeout(rollTime);
  rollTime = setTimeout(() => {
    window.requestAnimationFrame(rolling); // 进行递归动画
  }, speed);
  let liNodes = [];
  for (let i = 1; i < 9; i++) {
    liNodes.push(document.querySelector(`.li${i}`));
  }
  for (let i = 0; i < liNodes.length; i++) {
    liNodes[i].setAttribute("class", `li${i + 1}`);
  }
  liNodes[(time - 1) % 8].setAttribute(
    "class",
    `li${time % 8 === 0 ? 8 : time % 8} active`
  );
  // time > times 转动停止
  if (time > times) {
    clearInterval(rollTime);
    $("#award").text(`恭喜您抽中了${liNodes[(time - 1) % 8].innerText}!!!`);
    time = 0;
    // 将获奖信息显示在p标签上
    return;
  }
}

失败总结:

  1. 选择了错误的方针, 看到有 jq.js 就选择了 jq,导致吃了不熟悉 api 的亏(vscode 上也没安对应的插件...)
  2. 没有细心的进行排错, 前期乱写一通(当时有点小乱), 建议大家比赛找一个僻静的地方.
  3. 等后面写完其他, 再回来写的时候虽然用回了原生, 但是已经各种负面效果添加了
    • 熬夜引起的三叉神经发炎使得头疼,眼睛疼,连一刻清醒也无, 思路特别乱;
    • 考试时间不足
    • 到吃饭的点, 家里特别吵, 各种干扰...

那么我们虽然在考试的时候被 jq 坑了一手, 但是我们也得克服它, 具体思路还是和上面一样, 直接上代码

function rolling() {
  time++; // 转动次数加1
  clearTimeout(rollTime);
  rollTime = setTimeout(() => {
    window.requestAnimationFrame(rolling); // 进行递归动画
  }, speed);
  let ulNode = $(".ul");
  // 在第一次点击之后的点击时,清除之前的样式
  if (time === 1) {
    let frontNode = ulNode.find(".active");
    if (frontNode.length !== 0) {
      let NodeClass = frontNode.attr("class").split(" ")[0];
      frontNode.attr("class", NodeClass);
    }
  }
  // 选中前一个元素
  let prevNode = ulNode.find(`.li${time % 8 === 1 ? 8 : (time - 1) % 8}`);
  // 清除前一个元素的激活样式
  prevNode.attr("class", `li${time % 8 === 1 ? 8 : (time - 1) % 8}`);
  // 选中当前元素
  let indexNode = ulNode.find(`.li${time % 8 === 0 ? 8 : time % 8}`);
  // 设置激活样式
  indexNode.attr("class", `li${time % 8 === 0 ? 8 : time % 8} active`);
  // time > times 转动停止
  if (time > times) {
    clearInterval(rollTime);
    $("#award").text(`恭喜您抽中了${indexNode.text()}!!!`);
    time = 0;
    // 将获奖信息显示在p标签上
    return;
  }
}

吃大亏。。。

第六题 蓝桥知识网

这个题就是简单的切图, 我感觉跟着画就行 根据页面划分结构 然后画图就行... 这里疏忽了, 没看见 footer 的上边框... 有点不细心了

第七题 布局切换

这个题就是单纯的考察你对数据请求知识点的掌握。首先读题并且查看提供的代码和数据后, 我们发现在 goodsList.json

[
  {
    "title": "从零吃透 Vue.js 框架",
    "url": "#/3814",
    "image": {
      "large": "./images/1.png",
      "small": "./images/1.png"
    }
  },
  {
    "title": "初中级前端工程师面试宝典",
    "url": "#/4452",
    "image": {
      "large": "./images/2.png",
      "small": "./images/2.png"
    }
  }
]

直接就把大图和小图的数据全部提供了, 那么我们就只需请求一次,

// 这里使用了 async 和 await; 不懂的也可以使用 promise
async getListData() {
  const { data: res } = await axios.get("./goodsList.json");
  this.goodsList = res;
},

剩下的就是数据的绑定和切换了, 如果原生 js, 我们可能就又要去操作对应的一堆元素, 但是 vue 的话, 其实 我们就可以使用数据响应式来简化操作。大图和小图, 激活和不激活正好就是相对应的两种状态, 那么我们就可以设置 一个变量 isChange; 使用它的变化来切换状态; 上代码, 一看就明白了

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>布局切换</title>
    <script type="text/javascript" src="./js/vue.js"></script>
    <link rel="stylesheet" type="text/css" href="./css/index.css" />
    <script
      src="./js/axios.min.js"
      type="text/javascript"
      charset="utf-8"
    ></script>
  </head>
  <body>
    <div id="app" v-cloak>
      <!-- TODO:请在下面实现需求 -->
      <div class="bar">
        <a
          :class="['grid-icon', isChange ? '': 'active']"
          @click="ChangeList(0)"
        ></a>
        <a
          :class="['list-icon', isChange ? 'active' : '']"
          @click="ChangeList(1)"
        ></a>
      </div>
      <!--grid 示例代码,动态渲染时可删除-->
      <ul class="list" v-if="isChange">
        <li v-for="(item, index) in goodsList" :key="index">
          <a :href="item.url" target="_blank">
            <img :src="item.image.small"
          /></a>
          <p>{{item.title}}</p>
        </li>
      </ul>
      <ul class="grid" v-else>
        <li v-for="(item, index) in goodsList" :key="index">
          <a :href="item.url" target="_blank">
            <img :src="item.image.large"
          /></a>
        </li>
      </ul>
    </div>
  </body>
</html>
<script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      goodsList: [],
      isChange: false,
    },
    methods: {
      ChangeList(num) {
        if (num == 0) {
          this.isChange = false;
        } else {
          this.isChange = true;
        }
      },
      async getListData() {
        const { data: res } = await axios.get("./goodsList.json");
        this.goodsList = res;
      },
    },
    mounted() {
      // TODO:补全代码实现需求
      this.getListData();
    },
  });
</script>

总结: vue 的基础题

第八题 购物车

首先看题目, 我们就可以发现, template模板其实是一开始也在的,只不过是购物车数组中没有数据,被隐藏了 然后去看题目的要求, 我们可以简单的把题目的要求划分为几个功能

  1. addToCart 函数
    • 如果购物车数组中没有这个数据, 就添加属性 num = 1, push 到购物车数组中
    • 如果有就找到已存在的这个数据的 num ++
  2. removeGoods 函数
    • 不用判断是否存在(也可以, 为了小心)
    • 判断对应元素的 num 属性是否为 1, 如果为 1 就删除这个元素
    • 不为 1 就 num --

那么剩下的就是去实现对应的功能 比赛时候写的代码:

addToCart(goods){
      // TODO:修改当前函数,实现购物车加入商品需求
      let index = -1;
      if(this.cartList.length == 0 ){
        goods.num = 1;
        this.cartList.push(goods)
      } else {
        // 比赛时候的代码
        for(let i = 0; i < this.cartList.length ; i ++ ) {
          if(this.cartList[i].id == goods.id) {
            index = i
          }
        }
        if(index == -1) {
          goods.num = 1;
          this.cartList.push(goods)
        }else {
          this.cartList[index].num ++
        }
      }
      this.cartList = JSON.parse(JSON.stringify(this.cartList));
  },
  removeGoods(goods){
      // TODO:补全代码实现需求
      if(this.cartList.length === 0) {
        return
      }else {
        let index = -1;
        for(let i = 0; i < this.cartList.length ; i ++ ) {
          if(this.cartList[i].id == goods.id) {
            index = i
          }
        }
        if(this.cartList[index].num === 1) {
          this.cartList.splice(index, 1);
        }else {
          this.cartList[index].num --
        }
      }
  }

比赛后查文档写的, 就优雅

addToCart(goods){
     // TODO:修改当前函数,实现购物车加入商品需求
     let index = -1;
     if(this.cartList.length == 0 ){
       goods.num = 1;
       this.cartList.push(goods)
     } else {
       // 比赛后查文档写的, 对文档还是不熟练
       index = this.cartList.findIndex((item) => {
         return item.id == goods.id
       })
       if(index == -1) {
         goods.num = 1;
         this.cartList.push(goods)
       }else {
         this.cartList[index].num ++
       }
     }
     this.cartList = JSON.parse(JSON.stringify(this.cartList));
 },
 removeGoods(goods){
     // TODO:补全代码实现需求
     if(this.cartList.length === 0) {
       return
     }else {
       let index = -1;
       index = this.cartList.findIndex((item) => {
         return item.id == goods.id
       })
       if(this.cartList[index].num === 1) {
         this.cartList.splice(index, 1);
       }else {
         this.cartList[index].num --
       }
     }
}

总结: 比赛不比平时开发, api 记不住也不允许查, 但是~~~, 我在比赛后想到了个好办法!!!┭┮﹏┭┮;

如果我们想不起来对应的 api, 我们可以先声明一个对应的数据类型的变量(例: let array = []),然后 使用 array. ┭┮﹏┭┮ 这么一个妙计,马后炮, 一定是我当时状态不对(自我安慰)

在日常开发中也要尽量使用更加优雅的代码实现, 积累 api;

第九题 寻找小狼人

这个题说白了就是让我们手写 filter 方法, 题目那些要求都是 **

这个题其实我也懵了一下, 因为我之前也没有写过这个; 于是去看已有的代码的使用

// 方法调用
let newcardList = cardList.myarray((item) => item.category == "werewolf");

一开始有点懵,但是当我看到这个, 我就想到这是不是算是一种隐式绑定, 于是在myarray中打印this, 发现 和我想的一样 this 就是 cardList; 然后参数 cb 是一个函数, 然后我就又不会了, 就去写 10 题了; 写完 10 题后上了个厕所, 又想到传入的参数是用户的自定义, 相对于 filter 的配件, 那么我是不是可以直接调用 ?于是赶快去试一试, 发现可以, 但是我没有传参, 然后想想它的参数不就是 this 的子代元素吗。那么直接 循环判断, 满足条件就 push 到一个新数组中, 最后返回这个数组就行。

// 返回条件为真的新数组
Array.prototype.myarray = function (cb) {
  // TODO:待补充代码
  let innerArray = [];
  if (typeof cb !== "function") {
    return;
  }
  // 其实这里还可以用forEach, 无所谓
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i])) {
      innerArray.push(this[i]);
    }
  }
  return innerArray;
};

总结: js 的一些深入的基本理论要掌握. 随机应变收集有利数据。

第 10 题 课程列表

我一看题目要求分页, 我以为要发送分页请求, 但是一看是请求本地文件, 那么就不可能分配请求数据过来, 因为分页是后端控制的(因为我写过后端), 那么就其实要前端进行分页, 并且发现是数组对象, 那么自然而然 就想到 slice这个方法, 拷贝选中的数组中的数据返回新数组。

然后分析需求

  1. 请求数据
  2. 数据拆分, 并且在前进和后退的时候重新拆分, 使用 es6 字符串模板进行数据渲染
  3. 判断是否是最前一页和最后一页 去添加样式
  4. 在非最前最后的时候清除disabled
  5. 在 pagination 中显示当前页码和总页码(要求了变量名称)

来实现功能

  1. 获取数据
async function addData() {
  // 比赛的时候写这个没请求到,404, 现在好了不知道为啥...
  const { data: res } = await axios.get("js/carlist.json");
  ListData = res;
  maxPage = Math.floor(ListData.length / 5) + 1;
  // 用 pageNum ==> 当前页数来控制数据拆分
  carlist = ListData.slice((pageNum - 1) * 5, pageNum * 5);
  // 这里因为要反复渲染和添加元素, 就进行了代码的抽离
  addNode(carlist);
}

其实这里的数据请求是有个小坑的(不知道为啥现在没了。。。),之前数据请求的时候, cardList.json是 404, 我看了一下是因为相对路径的锅...(完蛋, 我发现当我套两层文件夹的时候路径有问题, 赌命了现在, 看官方判卷了) 要是不看我代码就完蛋, 寄(因为当时比赛的时候一层文件夹是没问题的)

看代码

let pageNum = 1; // 当前页码,默认页码1
let maxPage; // 最大页数
// TODO:待补充代码
let ListData = [];
let carlist = [];
async function addData() {
  const { data: res } = await axios.get("../../10/js/carlist.json");
  ListData = res;
  maxPage = Math.floor(ListData.length / 5) + 1;
  carlist = ListData.slice((pageNum - 1) * 5, pageNum * 5);
  addNode(carlist);
}
// 添加元素
function addNode(array = carlist) {
  let list = document.querySelector("#list");
  list.innerHTML = "";
  for (let i = 0; i < array.length; i++) {
    let node = document.createElement("div");
    // 设置属性
    node.setAttribute("class", "list-group");
    // 构建模板
    node.innerHTML = `
      <a href="#" class="list-group-item list-group-item-action">
          <div class="d-flex w-100 justify-content-between">
            <h5 class="mb-1">${array[i].name}</h5>
            <small>${array[i].price}</small>
          </div>
          <p class="mb-1">
            ${array[i].description}
          </p>
        </a>
    `;
    list.append(node);
  }
  document.querySelector(
    "#pagination"
  ).innerHTML = `当前页码: ${pageNum}, 总页码:${maxPage}`;
}
// 点击上一页
let prev = document.getElementById("prev");
prev.onclick = function () {
  // TODO:待补充代码
  // 首页不进行操作
  if (pageNum == 1) {
    return;
  } else {
    pageNum--;
    // 重新划分数据
    carlist = ListData.slice((pageNum - 1) * 5, pageNum * 5);
    addNode();
    // 解锁锁定样式
    next.setAttribute("class", "page-item");
    // 进行判断
    if (pageNum == 1) {
      prev.setAttribute("class", "page-item disabled");
    }
  }
};
// 点击下一页
let next = document.getElementById("next");
next.onclick = function () {
  // TODO:待补充代码
  // 尾页不进行操作
  if (pageNum === maxPage) {
    return;
  } else {
    // 逻辑同上
    pageNum++;
    carlist = ListData.slice((pageNum - 1) * 5, pageNum * 5);
    prev.setAttribute("class", "page-item");
    addNode();
    if (pageNum === maxPage) {
      next.setAttribute("class", "page-item disabled");
    }
  }
};

addData();
总结

注意的点:

  1. 调整好自己的状态, 找一个安静的地方(很重要)
  2. 思路清晰是关键
  3. 选择自己熟悉的方法
  4. 在规则内合理使用小技巧(vscode 插件, 浏览器工具插件),毕竟比的是开发。
  5. 第 4 点, 并不是鼓励你去作弊!!!

其实这是蓝桥杯第一次举办 Web 开发比赛, 大家都没有经验, 后面就会有所准备。 个人认为, 这个比赛并不代表开发的实力, 因为现在前端知识的繁杂, 我们更加偏向于对底层理论知识 的掌握与理解。 至于 api,面向文档开发就可,毕竟我们是开发对吧, 用户又不会限制你不使用 Google 与百度。 不能为了比赛而学习, 比赛只是一个学习的附属品。