0


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

在这里插入图片描述


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


📑 目录


🔽 前言

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

9️⃣ 趣购

这一题挺有趣的,考的拖放

Api

在平时开发中不是很常见,但考的

Vue

计算属性还是挺有用的,我们先从事件下手:

<divclass="good-list"><divv-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

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

根据题目信息可以得知,可以通过

drop

事件来获取可拖放元素的数据,而要想触发

drop

事件需要先清除

dragover

事件的默认行为,在

Vue

中可以通过

.prevent

修饰符来清除事件的默认行为,所以在购物车图标上还需要绑定一个

@dragover.prevent

drop

事件对应的事件处理程序如下:

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

)数据:

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

所以接下来我们只需要补全

goodsDetail

totalPrice

这两个计算属性就ok了:

totalPrice(){// 通过数组的reduce求和函数来获取购物车商品总计returnthis.bought.reduce((a, b)=>{return a + b.price
  },0);},
goodsDetail(){/**
   * 这里用了两次reduce
   * 第一次是为了将bought中相同的商品合并为同一个对象,并为其添加一个amount字段表示其数量
   * 第二次是为了将数据转换成符合题目要求的字符串格式
   */returnthis.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的值
 * */asyncfunctionajax({
  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 - 1if(this.currentPage >1){this.currentPage--;this.initPagination();}});this.root.querySelector("#btn-next").addEventListener("click",()=>{// TODO:">" 按钮的点击事件, 点击时 this.currentPage + 1if(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
 */constcreatePaginationIndexArr=(currentPage, totalPages, pagerCount)=>{let indexArr =[];// TODO:根据传参生成分页数组 indexArr
  indexArr[0]=1;// 第一项肯定是1if(!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

<divid="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;returnthis.list.slice(sliceStart, sliceEnd);},},

题目中给了

buffer

这个字段,其目的是为了防止出现白屏,在我们每次计算需要渲染到页面上的列表时向前面多计算

buffer

个,向后面也多计算

buffer

个。
在这里插入图片描述

showingList

方法中我们主要通过数组的

slice

方法来截取到需要渲染到页面上的部分,其中:

sliceStart

表示开始截取的下标,

sliceEnd

表示结束截取的下标(因为是截取不到

sliceEnd

位置的,所以需要提前+1)。

this.start

改变时,

showingList

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

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

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

start

小于

buffer

时,由于列表可视区域下方还有

buffer

个元素,当我们滚动时列表项能自然进行滚动,所以不需要设置

translateY

,只有当

start>buffer

时才需要设置

translateY

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

🔼 结语

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

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

在这里插入图片描述


本文转载自: https://blog.csdn.net/m0_51969330/article/details/129438748
版权归原作者 海底烧烤店ai 所有, 如有侵权,请联系我们删除。

“【蓝桥杯Web】第十四届蓝桥杯Web模拟赛 3 期 | 精品题解(下)”的评论:

还没有评论