【蓝桥杯Web】第十四届蓝桥杯Web模拟赛 3 期 | 精品题解(下)

news2024/11/19 3:48:17

在这里插入图片描述


🧑‍💼 个人简介:一个不甘平庸的平凡人🍬
🖥️ 蓝桥杯专栏:蓝桥杯题解/感悟
🖥️ TS知识总结:十万字TS知识点总结
👉 你的一键三连是我更新的最大动力❤️!
📢 欢迎私信博主加入前端交流群🌹


📑 目录

  • 🔽 前言
  • 9️⃣ 趣购
  • 🔟 分页组件
  • 🔷 虚拟滚动列表(职业院校组)
  • 🔼 结语


🔽 前言

昨天更新了第十四届蓝桥杯Web模拟赛 3 期的一些基础题的解析,今天抽时间把剩余的压轴题的解析肝出来了,本科组最后的两个题加上职业院校组中与本科组不同的一个题,总共三题,这三题还是有一定难度的,各位小伙伴们加油!

9️⃣ 趣购

这一题挺有趣的,考的拖放Api在平时开发中不是很常见,但考的Vue计算属性还是挺有用的,我们先从事件下手:

<div class="good-list">
  <div v-for="good in goods" 
  :key="good.name" 
  class="good" 
  draggable="true" 
  @dragstart="dragstart($event,good)">
    <img :src="good.cover" />
    <span>{{ good.name }}</span>
    <span>¥{{ good.price }}</span>
  </div>
</div>

上面先为每个商品绑定draggable="true"使其变成可拖放元素,再为其绑定dragstart事件,其对应的dragstart事件处理程序如下:

dragstart(ev,good){
  	// 向dataTransfer属性中添加拖拽数据
    ev.dataTransfer.setData("name", good.name);
    ev.dataTransfer.setData("price", good.price);
}

根据题目信息,我们很容易知道可以在dataTransfer 属性中保存事件的数据。

draggable:这个属性是枚举类型 (en-US),而不是布尔类型。这意味着必须显式指定值为 true 或者 false,像 <img draggable> 这样的简写是不允许的。正确的用法是 <img draggable="false">
dragstart事件:当用户开始拖拽一个元素或选中的文本时触发

之后需要为购物车图标绑定放置事件drop

<div id="trolley" class="trolley" @dragover.prevent @drop="drop" >
  <span id="bought" class="bought" v-if="bought.length !== 0">{{
    bought.length
  }}</span>
  <img src="./images/trolley.jpeg" />
</div>

根据题目信息可以得知,可以通过drop事件来获取可拖放元素的数据,而要想触发drop事件需要先清除dragover事件的默认行为,在Vue中可以通过.prevent修饰符来清除事件的默认行为,所以在购物车图标上还需要绑定一个@dragover.preventdrop事件对应的事件处理程序如下:

drop(ev){
  // 先获取dataTransfer上保存的可拖放元素的数据
  const name = ev.dataTransfer.getData("name");
  const price = ev.dataTransfer.getData("price");
  // 向bought数组中添加该商品的信息(向购物车中添加商品)
  this.bought.push({name,price:Number(price)}) // 这里将price转换成number类型,方便之后的计算
},

观察题目代码很容易推断出data中的bought是用来存放购物车的数据的

通过上面的步骤后题目的要求我们已经实现了一半了,下面需要解决的问题就是将购物车(bought)内的数据渲染到页面上,观察发现页面中使用到了两个计算属性来显示购物车(bought)数据:

<div class="result">
  <div>
    购物车商品:<span id="goods">{{ goodsDetail }}</span>
  </div>
  <div>
    购物车商品总计:<span id="total">{{ totalPrice }}</span>
  </div>
</div>

所以接下来我们只需要补全goodsDetailtotalPrice这两个计算属性就ok了:

totalPrice() {
  // 通过数组的reduce求和函数来获取购物车商品总计
  return this.bought.reduce((a, b) => {
    return a + b.price
  }, 0);
},
goodsDetail() {
  /**
   * 这里用了两次reduce
   * 第一次是为了将bought中相同的商品合并为同一个对象,并为其添加一个amount字段表示其数量
   * 第二次是为了将数据转换成符合题目要求的字符串格式
   */
  return this.bought.reduce((a, b) => {
    const good = a.find(item => item.name === b.name) // 先查询a中与b相同的商品
    if (good) {
      // 如果a中有与b相同的商品,则将其amount加1即可
      good.amount++
    } else {
      // 如果a中没有与b相同的商品,则向其push b商品的信息并初始化一个amount字段
      a.push({ name: b.name, price: Number(b.price), amount: 1 })
    }
    return a
  }, []).reduce((a, b) => {
    return a + b.name + '*' + b.amount + ' '
  }, '');
},

上面代码中使用了数组的reduce方法,对该方法不熟悉的小伙伴可查阅:MDN reduce

🔟 分页组件

到了本科组压轴的题了,这一题的要求还是挺多的,任务量比较大,但好在题中是根据任务数来给分的,所以遇到这种题不要慌,一步一步的向下走就好。

目标一:

/**
 * @description ajax 请求,通过传递的 currentPage, pageSize 获取到当前页和总页数的数据
 * @param {string} url 请求地址,必填
 * @param {string} method 请求方式,可选参数,默认为 get
 * @param {string} data 请求体数据,可选参数
 * @param {number} currentPage 当前页数,必填
 * @param {number} pageSize 每页显示条目个数,必填
 * @return {object} {data,total} data为data.json中data数组的部分数据,total为data.json中total的值
 * */
async function ajax({
  url,
  method = "get",
  data,
  query: { currentPage, pageSize },
}) {
  // TODO:根据函数参数 `query` 对象  `currentPage, pageSize` 获得当前页的数据
  let result = {
    data: [],
    total: 0,
  };

  let res = await axios[method](url, data); // 获取请求结果
  let resData = res.data.data;
  result.total = resData.length;
  result.data = resData.splice((currentPage - 1) * pageSize, pageSize); // 通过splice方法将当前页的数据截取出来

  return result;
}

目标二:

/**
 * @description 事件绑定,改变 this.currentPage 的值,值在 1 到 this.totalPages 之间
 **/
initEvents() {
  this.root.querySelector("#btn-prev").addEventListener("click", () => {
    // TODO:"<" 按钮的点击事件, 点击时 this.currentPage - 1
    if (this.currentPage > 1) {
      this.currentPage--;
      this.initPagination();
    }
  });
  this.root.querySelector("#btn-next").addEventListener("click", () => {
    // TODO:">" 按钮的点击事件, 点击时 this.currentPage + 1
    if (this.currentPage < this.totalPages) {
      this.currentPage++;
      this.initPagination();
    }
  });
  this.root.querySelector(".pager").addEventListener("click", (e) => {
    if (e.target.nodeName.toLowerCase() === "li") {
      if (this.currentPage === e.target.innerText) return;
      if (e.target.classList.contains("more")) return;
      this.currentPage = Number(e.target.innerText);
    }
    this.initPagination();
  });
}

补全initEvents函数并不能,根据事件控制currentPage的值即可,需要注意的就是每当currentPage的值改变都要调用一下initPagination事件(这一点题目代码中initPagination事件上的注释里明确说到了)。

initEvents函数补全之后提交测试我们还不能通过目标二,这是因为负责渲染分页按钮的renderPagination函数还没有补全(目标四的要求),导致页面上还不能正确显示分页组件,所以系统才会不让通过,目标三提交不通过也是这个原因。

也就是说不先通过目标四,目标二和目标三完成了也不会通过,那这题分目标给分的意义在哪呢🙄)

目标三:

个人认为目标三是最为复杂的一个任务,主要是你要能想到这种逻辑,下面代码中我分了两大类情况:

  1. totalPages<=pagerCount时直接遍历totalPages向数组中添加页码就行,这没什么好说的。
  2. totalPages>pagerCount时就比较复杂了,需要再考虑三种情况,也就是题目中给的例子[1,2,3,4,10],[1,3,4,5,10],[1,7,8,9,10]这三种情况,观察这三个数组很容易发现:
    • [1,2,3,4,10]是靠左显示,向右扩散的;也就说从左向右读是连续的,从右向左读会出现断层(4和10)
    • [1,3,4,5,10]是中间显示,向两边扩散;也就说不管是从左向右读还是从右向左读都会出现断层(1和3,5和10)
    • [1,7,8,9,10]是靠右显示,向左扩散的;也就说从右向左读是连续的,从左向右读会出现断层(1和7)

上面关于第二类的三种情况的说明可能不是很准确,大家明白这个意思就行。

了解了情况后,直接看代码:

/**
 * @description 得到分页数组 indexArr,如[1,2,3,4,10],[1,3,4,5,10],[1,7,8,9,10]
 * @param {number} currentPage 当前页数,默认为第一页
 * @param {number} totalPages 总的页码数
 * @param {number} pagerCount 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
 * @return {Array} 分页数组 indexArr
 */

const createPaginationIndexArr = (currentPage, totalPages, pagerCount) => {
  let indexArr = [];
  // TODO:根据传参生成分页数组 indexArr
  indexArr[0] = 1; // 第一项肯定是1
  
  if (!currentPage) {
    // currentPage不存在时默认为1
    currentPage = 1;
  }
  if (!totalPages) {
    // totalPages不存在时默认为currentPage
    totalPages = currentPage;
  }
  if (!pagerCount) {
    // pagerCount不存在时默认为5
    pagerCount = 5;
  }
  // 上面三个判断可以不要

  let medial = Math.floor(pagerCount / 2); // 中间位置

  if (totalPages <= pagerCount) {
    for (let i = 1; i < totalPages; i++) {
      indexArr[i] = i + 1;
    }
  } else {
    indexArr[pagerCount - 1] = totalPages; // 最后一项为totalPages
    // 当前页数靠左边,则从左向右扩散添加
    // 例如当前页数是2,3,4,总页数为10,页码按钮数是5时:[1,2,3,4,10]
    if (currentPage <= medial && totalPages - currentPage > medial) {
      for (let i = 1; i < pagerCount - 1; i++) {
        indexArr[i] = i + 1;
      }
    }
    // 当前页数在中间,则从中间向两边扩散
    // 例如当前页数是4,总页数为10,页面按钮数是5时:[1,3,4,5,10]
    if (currentPage > medial && totalPages - currentPage > medial) {
      indexArr[medial] = currentPage; // 中间位置设置为当夜页数
      for (let i = medial - 1, c = 1; i > 0; i--, c++) {
        indexArr[i] = currentPage - c; // 向左扩散添加
        if (medial + c < pagerCount - 1) {
          indexArr[medial + c] = currentPage + c; // 向右扩散添加
        }
      }
    }
    // 当前页数在右边,则从右向左扩散添加
    // 例如当前页数是7,8,9,总页数为10,页面按钮数是5时:[1,7,8,9,10]
    if (currentPage > medial && totalPages - currentPage <= medial) {
      for (let i = pagerCount - 2, c = 1; i > 0; i--, c++) {
        indexArr[i] = totalPages - c;
      }
    }
  }

  return indexArr;
};

module.exports = {
  createPaginationIndexArr,
};

在第二类情况下indexArr数组的第一项一定是1,最后一项一定是totalPages,所以我们只需要再根据那三种情况填充indexArr中剩余位置的空间即可。

目标四:

/**
 * @description 根据序号数组生成分页组件的字符串模板通过 innerHTML 挂载在 root 元素内
 * @param {Array} indexArr 分页数组 indexArr
 * @return {String} 分页组件的字符串模板
 */
renderPagination(indexArr) {
  let template = "";
  // TODO:根据 indexArr 数组生成分页组件的字符串模板 template
  template = indexArr
    .map((item, index) => {
      let more = `<li class="number more">...</li>`;
      let str = `<li class="number ${item === this.currentPage ? "active" : ""}">${item}</li>`;

      if (index > 0 && item - indexArr[index - 1] > 1) {
        // 如果当前item与上一个item(index[index-1])的差值大于1,则需要在当前分页按钮的前面添加...
        return more + str;
      }
      return str;
    })
    .join("");

  this.root.innerHTML = `
      <div class="pagination">
          <div class="btn btn-left" id="btn-prev">&lt;</div>
          <ul class="pager">${template} </ul>
          <div class="btn btn-right" id="btn-next">&gt;</div>
      </div>`;
}

🔷 虚拟滚动列表(职业院校组)

这一题考察日常开发中常见的列表优化方式:虚拟滚动列表。

先通过axios获得全部的数据:

mounted() {
  // TODO: 完成数据请求
  axios.get("./data.json").then((res) => {
    this.list = res.data; // 存放总数据
    this.totalHeight = res.data.length * this.itemHeight; // 总高度
  });
},

然后对容器绑定滚动事件scroll

  <div id="virtual-list" class="virtual-list" @scroll="scroll">
methods: {
  // TODO: 完成事件处理
  scroll(e) {
    this.start = Math.floor(e.target.scrollTop / this.itemHeight);
  },
},

scroll事件中,我们通过滚动条已经滚动的高度/每一项的高度来获取可视区域内的第一项的下标(this.start)。

例如:已经滚动300,每一项高度为100,则已经滚动了三项了,目前可视区域内的第一项应为第四项,其对应的下标就为3

计算出需要渲染到页面上的列表:

computed: {
  showingList() {
    let sliceStart = this.start > this.buffer ? this.start - this.buffer : 0;
    let sliceEnd = this.start + this.length + this.buffer + 1;
    return this.list.slice(sliceStart, sliceEnd);
  },
},

题目中给了buffer这个字段,其目的是为了防止出现白屏,在我们每次计算需要渲染到页面上的列表时向前面多计算buffer个,向后面也多计算buffer个。
在这里插入图片描述

showingList方法中我们主要通过数组的slice方法来截取到需要渲染到页面上的部分,其中:sliceStart表示开始截取的下标,sliceEnd表示结束截取的下标(因为是截取不到sliceEnd位置的,所以需要提前+1)。

this.start改变时,showingList计算属性会重新执行,于是就能获取到每次滚动时需要渲染到页面中的数据。

只是获取到数据还不行,当我们进行滚动时需要将列表项容器也进行位移,这样才能保证数据一直在可视区域内:

<ul
  id="list"
  class="list"
  :style="{
    transform:
      'translateY(' +
      (start > buffer ? (start - buffer) * itemHeight : 0) +
      'px)',
  }">

start小于buffer时,由于列表可视区域下方还有buffer个元素,当我们滚动时列表项能自然进行滚动,所以不需要设置translateY,只有当start>buffer时才需要设置translateY

实现虚拟列表的代码量并不多,主要还是在于逻辑。

🔼 结语

距离第十四届蓝桥杯的正式比赛还有不到一个月的时间,好好复习,祝大家都能在正式比赛中取得满意的成绩!

如果本篇文章对你有所帮助,还请客官一件四连!❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/409317.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML插入图片

图片介绍一、src属性二、其余属性三、alt用法四、title用法五、属性特点六、src路径的讲解一、src属性 符号&#xff1a;img 特点&#xff1a;单标签 代码&#xff1a; <!doctype html> <html><head><meta charset"utf-8"><title>HT…

尚品汇后台管理项目(Vue)

简介 1:什么是后台管理系统项目? 注意&#xff1a;前端领域当中&#xff0c;开发后台管理系统项目&#xff0c;并非是java、php等后台语言项目。 在前面课程当中&#xff0c;我们已经开发了一个项目【尚品汇电商平台项目】&#xff0c;这个项目主要针对的是用户&#xff08;游…

vue表单验证rules以及validator验证器的使用

为防止用户犯错&#xff0c;尽可能更早地发现并纠正错误。 Element中Form &#xff08;表单&#xff09;组件提供了表单验证的功能&#xff0c;只需要通过 rules 属性传入约定的验证规则&#xff0c;并将 Form-Item 的 prop 属性设置为需校验的字段名即可。 注意&#xff1a;…

案例说明:vue中Element UI下拉列表el-option中的key、value、label含义各是什么

可以简单理解为&#xff1a;label 是给用户展示的东西&#xff0c;value是前端往后端传递的真实值 <template><div><el-page-header back"goBack" content"注册"></el-page-header><el-divider></el-divider><el-…

flex布局优化(两端对齐,从左至右)

文章目录前言方式一 nth-child方式二 gap属性方式三 设置margin左右两边为负值总结前言 flex布局是前端常用的布局方式之一&#xff0c;但在使用过程中&#xff0c;我们总是感觉不太方便&#xff0c;因为日常开发中&#xff0c;大多数时候&#xff0c;我们想要的效果是这样的 …

2023年网络安全比赛--CMS网站渗透中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.使用渗透机对服务器信息收集,并将服务器中网站服务端口号作为flag提交; 2.使用渗透机对服务器信息收集,将网站的名称作为flag提交; 3.使用渗透机对服务器渗透,将可渗透页面的名称作为flag提交; 4.使用渗透机对服务器渗透,…

全网最新的vue.js下载和安装的3种方法(2023年)

文章目录1. 文章引言2. 环境搭建3. 安装vue.js3.1 方法一&#xff1a;官网下载vue.js源代码3.2 方法二&#xff1a;使用npm install创建3.3 方法三&#xff1a;使用bower下载4. 总结1. 文章引言 我主要从事java后端开发&#xff0c;但对前端也非常感兴趣&#xff0c;立志成为全…

【vite·5】vite中环境变量的使用与配置(全网最全)

什么是环境变量 根据当前的代码环境变化的变量就叫做环境变量。比如&#xff0c;在生产环境和开发环境将BASE_URL设置成不同的值&#xff0c;用来请求不同的环境的接口。 环境变量一般可以在全局访问到。在webapck中&#xff0c;我们也许看到过这样的代码 // webpack.config.…

JavaWeb:实现购物商城(课程设计完整版)

前言 做一个javaweb可以对前后端基础知识进行巩固。 就比如 前端可以用 htmlcssjsjQuery&#xff1b; 后端Http协议&#xff0c;Servlet基础&#xff0c;JSP技术&#xff0c;Mysql等 该程序做的这个就相对比较基础&#xff0c;适合大学生当课程设计用 在文章结尾附项目源代码和…

Vue生命周期钩子剖析(共12个钩子)

生命周期示意图&#xff1a; 生命周期及其钩子函数理解 生命周期&#xff1a; Vue是一个构造函数&#xff0c;当执行执行这个函数时&#xff0c;相当于初始化vue实例&#xff1b;在创建实例过程中&#xff0c;需要设置数据监听&#xff0c;编译模板&#xff0c;将实例挂载到DO…

Vue warn]: Component is missing template or render function.

警告&#xff1a;Component is missing template or render function. 问题声明&#xff1a; 组件缺少模板或渲染功能。 解决问题 方式一&#xff1a; 在写vue项目时&#xff0c;网页没有加载出来东西一片空白&#xff0c;然后控制台出现黄色的警告&#xff1a; 原因是&…

html--盒子的边框属性(border)

content:内容框&#xff08;我们设置的宽高是内容框的宽高&#xff09; padding:内边距 top right bottom left&#xff08;四边--一般默认指定的方向&#xff09; border:边框线包裹了内边距&#xff08;四边&#xff09; margin:外边距 在边框的外面 元素和其他元素的间…

【手把手带你学JavaSE】String类(下篇)

目录前言一、字符串查找二、字符串转换2.1 数值和字符串转化2.2 大小写转化2.3 字符串和数组的转换2.4 格式化三、字符串替换四、字符串拆分4.1 拆分处理4.2 部分拆分4.3 拆分IP地址五、字符串截取六、其他的方法6.1 String trim()6.2 boolean isEmpty()6.3 int length()6.4 判…

vue3与vue2的区别(你不知道细节全在这)

先来说说当下市场开发使用的问题&#xff0c;目前2021年使用vue3开发的企业还是少&#xff0c;基本上都还是以vue2的形式进行开发&#xff0c;vue3的开发模式跟react很像&#xff0c;这时候有人就会想那我学vue3有用么&#xff0c;淦&#xff0c;他喵的&#xff0c;先别激动&am…

Vue开发项目入门——Vue脚手架

1.什么是Vue脚手架 Vue脚手架是Vue官方提供的标准化开发工具&#xff08;开发平台&#xff09;&#xff0c;它提供命令行和UI界面&#xff0c;方便创建vue工程、配置第三方依赖、编译vue工程。 特别注意&#xff1a;Vue脚手架是用来方便开发的&#xff0c;但vue脚手架不是最终发…

Canvas百战成神-圆(1)

Canvas百战成神-圆 初始化容器 <canvas id"canvas"></canvas>canvas{border: 1px solid black; }让页面占满屏幕 *{margin: 0;padding: 0; } html,body{width: 100%;height: 100%;overflow: hidden; } ::-webkit-scrollbar{display: none; }初始化画笔…

【网络请求之Axios】axios的基础用法

1. axios概述 axios 是一个专注于网络请求的库。axios 在请求到数据之后&#xff0c;在真正的数据之外&#xff0c;套了一层外壳。 2.axios的基本使用 2.1 发送get请求 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta cha…

前端:弹幕标签用法详细介绍(跑马灯)

弹幕标签 1&#xff0c;注意弹幕标签marquee&#xff0c;现在一些浏览器是不支持的 2&#xff0c;弹幕标签也叫跑马灯 marquee格式及其含有的属性 1.基本格式 如下&#xff1a; <marquee></marquee>2.一些属性 1&#xff0c;direction属性&#xff1a;表示的…

39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程

《JavaScript再出发》系列文章阅读《仙宗》发布招仙贴&#xff0c;广招天下道友 文章目录JavaScript中Promise的基本概念、使用方法&#xff0c;以及回调地狱规避一、前言二、Promise基本概念2.1 异步工作的封装2.2 Promise执行结果获取thencatchfinally三、使用Promise解决回调…

qiankun微应用之间、主微应用之间相互跳转方式总结与实践

一、子应用互相访问 1、背景 &#xff08;1&#xff09;未来可能需要做不同子应用菜单的合并&#xff0c;如在bi应用下的侧边栏或者别的地方&#xff0c;需要跳转到数据治理的数仓主题里&#xff0c;或者涉及到子应用值改变&#xff0c;其他应用也需要使用&#xff1b; &…