蓝桥杯Web第十四届蓝桥杯Web模拟赛 3 期 | 精品题解(下)
Posted 海底烧烤店ai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蓝桥杯Web第十四届蓝桥杯Web模拟赛 3 期 | 精品题解(下)相关的知识,希望对你有一定的参考价值。
🧑💼 个人简介:一个不甘平庸的平凡人🍬
🖥️ 蓝桥杯专栏:蓝桥杯题解/感悟
🖥️ TS知识总结:十万字TS知识点总结
👉 你的一键三连是我更新的最大动力❤️!
📢 欢迎私信博主加入前端交流群🌹
📑 目录
🔽 前言
昨天更新了第十四届蓝桥杯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.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
)数据:
<div class="result">
<div>
购物车商品:<span id="goods"> goodsDetail </span>
</div>
<div>
购物车商品总计:<span id="total"> totalPrice </span>
</div>
</div>
所以接下来我们只需要补全goodsDetail
和totalPrice
这两个计算属性就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
函数还没有补全(目标四的要求),导致页面上还不能正确显示分页组件,所以系统才会不让通过,目标三提交不通过也是这个原因。
也就是说不先通过目标四,目标二和目标三完成了也不会通过,那这题分目标给分的意义在哪呢🙄)
目标三:
个人认为目标三是最为复杂的一个任务,主要是你要能想到这种逻辑,下面代码中我分了两大类情况:
- 当
totalPages<=pagerCount
时直接遍历totalPages
向数组中添加页码就行,这没什么好说的。 - 当
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"><</div>
<ul class="pager">$template </ul>
<div class="btn btn-right" id="btn-next">></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
。
实现虚拟列表的代码量并不多,主要还是在于逻辑。
🔼 结语
距离第十四届蓝桥杯的正式比赛还有不到一个月的时间,好好复习,祝大家都能在正式比赛中取得满意的成绩!
如果本篇文章对你有所帮助,还请客官一件四连!❤️
蓝桥杯Web第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-职业院校组 | 精品题解
🧑💼 个人简介:一个不甘平庸的平凡人🍬
🖥️ Nodejs专栏:Node.js从入门到精通
🖥️ TS知识总结:十万字TS知识点总结
👉 你的一键三连是我更新的最大动力❤️!
📢 欢迎私信博主加入前端交流群🌹
📑 目录
🔽 前言
上篇文章已经对大学组的十道题做了解析,有的小伙伴私信我说能不能出一篇职业院校组的解析,我去看了一下职业院校组模拟赛的题,发现就只有三道题与大学组的不同,于是这里就针对性的讲一下这三题,其它题的解析见大学组题解:Web 应用开发模拟赛 1 期-大学组 | 精品题解
1️⃣ 回文字符串
形如 aba,abba 的字符串都是回文字符串。另外,单字符串、空字符串也是一种特殊的回文字符串哦。
题非常的简单,直接上代码:
function isPalindromeStr(str)
// 在这里写入具体的实现逻辑
// 返回值是 boolean 类型, 如果是回文字符串应该返回 true, 否则返回 false
return typeof str === "string" && (!str || str === [...str].reverse().join(''))
;
module.exports = isPalindromeStr; // 检测需要请勿删除
!str
用来处理空字符串的情况(!""
的结果为true
)。[...str].reverse().join('')
代表将str
转换为数组,然后通过数组的reverse
方法反转,最后再通过join
转回字符串。
如果你感觉上面一行代码的形式不太好理解,可以看下面的解法:
function isPalindromeStr(str)
// 在这里写入具体的实现逻辑
// 返回值是 boolean 类型, 如果是回文字符串应该返回 true, 否则返回 false
if(typeof str !== "string") return false;
if (!str) return true;
for (let i = 0; i < str.length; i++)
if (str[i] !== str[str.length - i - 1])
// 但凡有不相等的就直接return fasle
return false
return true
;
module.exports = isPalindromeStr; // 检测需要请勿删除
简单的使用循环,来从两头向里开始判断是否相等,不过这种写法还可以在性能上再优化一下:
function isPalindromeStr(str)
// 在这里写入具体的实现逻辑
// 返回值是 boolean 类型, 如果是回文字符串应该返回 true, 否则返回 false
if(typeof str !== "string") return false;
if (!str) return true;
let strLength = str.length; // 存储数组长度
let halfLength = Math.ceil(strLength / 2); // 数组的一半长度(向上取整)
for (let i = 0; i < halfLength; i++)
if (str[i]! == str[strLength - i - 1])
return false
return true
;
module.exports = isPalindromeStr; // 检测需要请勿删除
- 因为是从两头向里进行遍历,所以不需要遍历整个字符串,只需遍历一半即可,这样在极端情况(
str
是回文字符串时)下就能节约一半的遍历时间。 - 通过一个
strLength
变量存储字符串的长度,之后在需要使用字符串长度的地方替换使用strLength
,这样能避免频繁访问str
所造成的性能影响,不过这种影响是微乎其微的,这里只是想到了,所以就简单说一下。
2️⃣ 别抖了(防抖函数)
让写一个防抖函数,防抖和节流是必备的基础知识,要注意防抖和节流是两种不同的方式:
- 防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
- 节流:指连续触发事件但是在 n 秒中只执行一次函数。
代码:
function debounce(fn, delay = 0)
// TODO: 在这里写入具体的实现逻辑
// 返回一个新的防抖函数
// 即使函数在 delay 时间段内多次被调用,也只会在最后一次函数被调用的 delay 时间结束后执行
let timer = null;
return function (...args)
if (timer) clearTimeout(timer);
timer = setTimeout(()=>
fn(...args)
,delay)
module.exports = debounce; // 检测需要,请勿删除
这是最简单的防抖函数,在复杂的情况下书写防抖函数时是需要注意到原函数this
指向,是否立即执行,是否能取消执行等多种情况。
3️⃣ 分阵营,比高低
原本的学生数据格式:
[
name: "潇然",
class: 2, // 班级
math: 110, // 数学成绩
language: 92, // 语文成绩
english: 114, // 英语成绩
physics: 56, // 物理成绩
chemistry: 74, // 化学成绩
,
name: "张三",
class: 1,
math: 100,
language: 80,
english: 60,
physics: 80,
chemistry: 60,
,
// ...
];
题目要求我们转换成下面的格式:
// 返回的结果对象:
// key 是班级号,同一个班级中学生成绩降序排列
1: [
name: "潇然",
class: 1,
math: 110,
language: 92,
english: 114,
physics: 56,
chemistry: 74,
,
name: "张三",
class: 1,
math: 10,
language: 8,
english: 60,
physics: 8,
chemistry: 60,
,
// ...
],
2: [
// ...
],
;
代码:
function orderStudentGrade(students)
// TODO: 在这里写入具体的实现逻辑
// 将学生的成绩按班级分组,同一班级中按照总分从高到底排序
let obj = ;
// 将数据分班
students.forEach((item) =>
obj[item.class]
? obj[item.class].push(item)
: (obj[item.class] = [item]);
);
// 排序
for (const key in obj)
obj[key].sort((a,b)=>
let aCount = a.math + a.language + a.english + a.physics + a.chemistry;
let bCount = b.math + b.language + b.english + b.physics + b.chemistry;
return bCount - aCount
)
return obj
module.exports = orderStudentGrade; // 检测需要,请勿删除
🔼 结语
至此,第十四届蓝桥杯Web应用开发模拟赛 1 期的所有题解就全部完成了,如果大家有问题,欢迎评论区留言,也欢迎私信我加入我们的前端技术交流群。
模拟赛 1 期距离结束就剩1天的时间了,没完成的小伙伴要抓紧时间了。
如果本篇文章对你有所帮助,还请客官一件四连!❤️
📢 欢迎私信博主加入前端交流群🌹
以上是关于蓝桥杯Web第十四届蓝桥杯Web模拟赛 3 期 | 精品题解(下)的主要内容,如果未能解决你的问题,请参考以下文章
蓝桥杯Web第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解
蓝桥杯Web第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-职业院校组 | 精品题解