【蓝桥杯】第13届 Web 应用开发省赛真题解析
视频讲解:点击观看
01 水果拼盘(5分)
- 在线环境 - 蓝桥杯题库2448
- 考点:CSS3 中 Flex 弹性布局
查看样式可以看到,所有水果都放在了 #pond
元素中,由于只设置了 display: flex;
,导致所有水果都处于一行。
因此,我们只需设置主轴方向为纵向 、并允许换行即可:
1 | /* TODO:待补充代码 */ |
02 展开你的扇子(5分)
- 在线环境 - 蓝桥杯题库2449
- 考点:CSS3 实现展开动画
查看源码可以看到,页面有 12 个相同大小的 div
元素,要求前 6 个顺时针转动,后 6 个逆时针转动。
转动动画用到了 transform: rotate(10deg);
这个属性:
1 | /*TODO:请补充 CSS 代码*/ |
03 和手机相处的时光(10 分)
- 在线环境 - 蓝桥杯题库2450
- 考点:ECharts 折线图的使用
这是道代码修复题:由说明可知,文件里 var option = {}
中的内容是 ECharts 的配置项,该配置中存在 Bug,导致坐标轴显示不正确。
与 Echarts 官方示例 中的代码作比较,我们可以发现是由于 xAxis
和 yAxis
配置项中的 type 值反了,导致两个轴的显示不正常。X 轴应该是 type: "category",
,Y 轴应该是 type: "value",
:
1 | <script> |
04 灯的颜色变化(10 分)
- 在线环境 - 蓝桥杯题库2451
- 考点:JS 定时器、Promise
通过查看源码我们可知,三个颜色的灯泡是已经在页面中给出了的,只是红灯和绿灯被 display: none;
掉了。
因此我们可以通过定时器来实现描述效果,一个等待 3 秒,一个等待 6 秒:
1 | // TODO:完善此函数 显示红色颜色的灯 |
或者如果想用 Promise 实现异步任务,但注意此时都是等待 3 秒。因为当 red()
执行成功后,才会继续执行 green()
:
1 | // TODO:完善此函数 显示红色颜色的灯 |
05 东奥大抽奖(15 分)
- 在线环境 - 蓝桥杯题库2452
- 考点:jQuery 添加和移除类名
阅读源码我们可知,每次点击按钮后会生成随机20-30次的转动次数,然后会调用 rolling()
函数转起来。
根据要求,我们要补全的代码需要实现:点击开始后,以 class
为 li1
的元素为起点,黄色背景(.active
类)在奖项上顺时针转动。当转动停止后,将获奖提示显示到页面的 id
为 award
元素中。
我们定义一个变量
num
,用来表示高亮索引,也就是控制li1
~li8
哪个奖品高亮。在递归函数中,随着转动次数的增加,索引值也是增加的。但是当索引值大小超过 8 的时候,我们要将其重置为 1:1
2
3
4
5
6
7time++; // 转动次数加1
num++; // 高亮索引加1
if(num > 8) {
num = 1
}
// console.log(time, num)然后用 jQuery 在递归函数中,选中类名为
li+num
的元素,给当前索引的奖品添加active
类名:1
$('.li' + num).addClass('active')
同时移除它的同胞元素的
active
类名:1
$('.li' + num).addClass('active').siblings().removeClass('active');
其中
siblings()
是 jQuery 中的方法,用于查找当前元素的所有同胞元素,也就是有相同类名的同胞元素。注意
addClass()
和removeClass
都是 jQuery 中的方法,用于添加和移除 Dom 元素的指定类名。如果要用原生 JS 语法实现,可以使用classList.add()
和classList.remove()
方法。例如,下面的代码可以实现上方 jQuery 代码相同的效果:1
2
3
4
5
6document.querySelectorAll('.li' + num).forEach(function(el) {
el.classList.add('active');
el.parentNode.querySelectorAll(':scope > li:not(.li' + num + ')').forEach(function(sibling) {
sibling.classList.remove('active');
});
});这里使用了
querySelectorAll()
方法来选中类名为li+num
的元素,得到只有当前元素的 NodeList 节点数组,然后使用forEach()
方法遍历该数组。在遍历过程中,使用classList.add()
方法为当前元素添加类名,使用parentNode
属性和querySelectorAll()
方法查找当前元素的同胞元素,并使用classList.remove()
方法移除它们的类名。显然,使用 jQuery 可以大大减少代码量。
接下来要做的就是在转动停止后,将抽奖结果放到
id
为award
元素中。1
$('#award').text(`恭喜您抽中了${$('.li' + num).text()}!!!`);
jQuery 的
.text()
方法相当于原生 JS 中的.textText
属性,而不是.innerHTML
属性。因为.text()
方法和.textText
属性只会显示文本内容,不会解析 HTML 标记。最后别忘了重置高亮索引,以便于下次抽奖时还是从第一个商品开始转动。
1
2
3
4
5
6
7
8// time > times 转动停止
if (time > times) {
clearInterval(rollTime);
$('#award').text(`恭喜您抽中了${$('.li' + num).text()}!!!`);
time = 0;
num = 0; // 重置高亮索引
return;
}
至此,本题完成,完整代码如下:
1 | let rollTime; // 定义定时器变量用来清除定时器 |
06 蓝桥知识网(15 分)
- 在线环境 - 蓝桥杯题库2453
- 考点:HTML + CSS
这是道页面布局题,注意页面版心宽度为 1024px
,要保证版心居中。
首先清除一下默认样式,然后设置一个版心类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
li {
list-style: none;
}
/* 版心 */
.wrap {
width: 1024px;
margin: 0 auto;
}布局 header 部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!-- 头部 start -->
<header>
<div class="wrap">
<div class="nav">
<h1>蓝桥知识网</h1>
<ul>
<li>首页</li>
<li>热门技术</li>
<li>使用手册</li>
<li>知识库</li>
<li>练习题</li>
<li>联系我们</li>
<li>更多</li>
</ul>
</div>
<div class="container">
<div class="title">蓝桥云课</div>
<div class="text">随时随地丰富你的技术栈!</div>
<div class="join">加入我们</div>
</div>
</div>
</header>
<!-- 头部 end -->1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58/* header */
header {
padding-top: 13px;
background-color: #a6b1e1;
}
header .nav {
height: 46px;
display: flex;
justify-content: space-between;
}
header .nav h1 {
font-size: 18px;
color: white;
margin-right: 365px;
}
header .nav ul {
display: flex;
}
header .nav ul li {
margin-right: 16px;
font-size: 16px;
color: white;
}
header .container {
height: 427px;
text-align: center;
}
header .container .title {
margin-top: 30px;
font-size: 45px;
color: black;
}
header .container .text {
margin-top: 62px;
color: white;
font-size: 21px;
font-weight: 200;
}
header .container .join {
margin: 36px auto 0;
color: #efbfbf;
border-radius: 2px;
font-size: 18px;
box-shadow: inset 0 0 0 2px #efbfbf;
/* 以下为目测 */
width: 120px;
height: 50px;
line-height: 50px;
}布局 main 部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<!-- 主体 start -->
<main>
<div class="wrap">
<ul>
<li>
<div class="info-title">人工智能</div>
<div class="info-content">人工智能亦称智械、机器智能,指由人制造出来的机器所表现出来的智能。通常人工智能是指通过普通计算机程序来呈现人类智能的技术。</div>
</li>
<li>
<div class="info-title">前端开发</div>
<div class="info-content">前端开发是创建 WEB 页面或 APP 等前端界面呈现给用户的过程,通过 HTML,CSS 及 JavaScript 以及衍生出来的各种技术、框架、解决方案,来实现互联网产品的用户界面交互。</div>
</li>
<li>
<div class="info-title">后端开发</div>
<div class="info-content">后端开发是实现页面的交互逻辑,通过使用后端语言来实现页面的操作功能,例如登录、注册等。</div>
</li>
<li>
<div class="info-title">信息安全</div>
<div class="info-content">ISO(国际标准化组织)的定义为:为数据处理系统建立和采用的技术、管理上的安全保护,为的是保护计算机硬件、软件、数据不因偶然和恶意的原因而遭到破坏、更改和泄露。</div>
</li>
</ul>
</div>
</main>
<!-- 主体 end -->1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/* main */
main {
margin-top: 74px;
height: 302px;
}
main .wrap ul {
display: flex;
flex-wrap: wrap;
}
main .wrap ul li {
width: 50%;
height: 144px;
padding-right: 20px;
}
main .wrap ul li .info-title {
font-size: 30px;
font-weight: 200;
columns: black;
}
main .wrap ul li .info-content {
font-size: 18px;
color: #aaa;
line-height: 1.4em;
/* 以下为目测 */
margin-top: 20px;
margin-bottom: 20px;
}布局 footer 部分:
1
2
3
4
5
6
7
8
9<!-- 页尾 start -->
<hr />
<footer>
<div class="wrap">
<div class="footer-title">© 蓝桥云课 2022</div>
<div class="footer-info">京公网安备 11010102005690 号 | 京 ICP 备 2021029920 号</div>
</div>
</footer>
<!-- 页尾 end -->1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/* footer */
footer {
height: 80px;
text-align: center;
font-size: 14px;
color: #aaa;
}
footer .wrap .footer-title {
margin-top: 30px;
}
footer .wrap .footer-info {
margin-top: 10px;
}
07 布局切换(20 分)
考点:
Vue 2.x
、axios
我们先根据要求实现按钮颜色的动态切换,即通过
active
类名的添加和移除来实现:1
2
3
4
5<!-- TODO:请在下面实现需求 -->
<div class="bar">
<a class="grid-icon" :class="{'active': index === 0}" @click="index = 0"></a>
<a class="list-icon" :class="{'active': index === 1}" @click="index = 1"></a>
</div>1
2
3
4
5
6
7
8
9
10
11
12<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
goodsList: [],
index: 0,
},
mounted() {
// TODO:补全代码实现需求
},
});
</script>首先完成数据请求(数据来源
goodsList.json
)1
2
3
4
5axios.get('./goodsList.json')
.then(res => {
// console.log(res)
this.goodsList = res.data
})通过阅读源码我们可知
<ul class="grid">
和<ul class="list">
是互斥事件,因此可以用v-if
来动态渲染。在其中分别使用v-for
渲染出请求回来的数据项。1
2
3
4
5
6
7
8
9
10
11<ul class="grid" v-if="index === 0">
<li v-for="(item, index) in goodsList" :key="index">
<a :href="item.url" target="_blank"> <img :src="item.image.large" /></a>
</li>
</ul>
<ul class="list" v-if="index === 1">
<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>
检测通过,本题完成,完整代码如下:
1 |
|
08 购物车(20 分)
考点:
Vue 2.x
、JS 数组 push/splice 方法
可以看到,当前代码中
addToCart
和removeGood
方法是不完美的。1
2
3
4
5
6
7addToCart(goods) {
// TODO:修改当前函数,实现购物车加入商品需求
goods.num = 1;
this.cartList.push(goods);
this.cartList = JSON.parse(JSON.stringify(this.cartList));
},在
addToCart
中,我们需要先判断购物车列表中有没有该商品,才能选择是 添加该商品 还是 在购物车该商品原数量上加1:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17addToCart(goods) {
// TODO:修改当前函数,实现购物车加入商品需求
let hasGoods = false;
// 遍历查找购物车列表中是否存在该商品
this.cartList.forEach(item => {
if(item.id === goods.id) {
item.num++
hasGoods = true;
}
});
if (!hasGoods) {
goods.num = 1;
this.cartList.push(goods);
}
this.cartList = JSON.parse(JSON.stringify(this.cartList));
},至此,“加入购物车”以及加号按钮功能正常。
但是对于移除购物车函数
removeGoods
来说,就不用判断购物车列表是否有该商品了,因为既然你都可以点击减号按钮了,说明该商品已经在购物车了呀!1
2
3
4
5
6
7
8
9removeGoods(goods) {
// TODO:补全代码实现需求
this.cartList.forEach(item => {
// 遍历查找购物车列表,直到找到该商品
if(item.id === goods.id) {
item.num--
}
})
}现在就实现了点击减号按钮,减少该商品在购物车中的数量了。
但别忘了当数量减少到 0 的时候从购物车移除该商品:
1
2
3
4
5
6
7
8
9
10
11
12
13
14removeGoods(goods) {
// TODO:补全代码实现需求
this.cartList.forEach((item, index) => {
// 遍历查找购物车列表,直到找到该商品
if(item.id === goods.id) {
item.num--
if(item.num <= 0) {
this.cartList.splice(index, 1)
}
}
})
this.cartList = JSON.parse(JSON.stringify(this.cartList));
}其中最后一句
this.cartList = JSON.parse(JSON.stringify(this.cartList));
是对 cartList 进行一次深拷贝,防止干扰之后的一些操作。说实话我没太理解这一步操作,我感觉不进行这次拷贝也能正常工作,感觉这行代码是句废话。问了一下 NewBing 是这样回答我的:这行代码的意义是将
this.cartList
对象进行深拷贝,然后将拷贝后的对象重新赋值给this.cartList
对象。这样做的目的是为了避免直接修改this.cartList
对象时,对其他引用该对象的变量造成影响。如果不进行深拷贝,直接将this.cartList
赋值给其他变量,那么修改其中一个变量的值会影响到其他变量的值。所以这行代码并不是多此一举,而是为了保证程序的正确性。但是…似乎…这道题中不拷贝也不会有啥影响吧……
完整的代码如下:
1 |
|
09 寻找小狼人(25 分)
- 在线环境 - 蓝桥杯题库2456
- 考点:手写数组
filter
方法
根据题目要求,我们需要实现类似数组的 filter
方法, 返回一个都是狼人的新数组:
1 | cardList.filter((item) => item.category == "werewolf") |
阅读源码我们可知要手写的这个 myarray
方法的调用方式是:
1 | let newcardList = cardList.myarray( |
因此该方法需要挂载到数组的原型上,文件中已经给我们提供好了,只需完善即可:
1 | // 返回条件为真的新数组 |
我们需要知道的是,传入的 cb
回调函数的返回值是布尔值,我们需要使用这个函数来过滤数组。此外,还需要知道,在我们要手写的 myarray
这个函数中,this 的值就是调用此函数的那个数组。
本题完整代码如下:
1 | // 返回条件为真的新数组 |
10 课程列表(25 分)
考点:手写分页组件、
axios
、.slice(0, 5)
、.map().join()
、.toFixed(2)
我们首先实现数据请求,通过总数据计算出总页数,并把总页数和当前页数在前端页面显示出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let pageNum = 1; // 当前页码,默认页码1
let maxPage; // 最大页数
// TODO:待补充代码
// 显示当前页码
function showPagination(maxPage, pageNum) {
document.querySelector('#pagination').innerHTML = `共${maxPage}页,当前${pageNum}页`
}
// 数据请求
let data; // 总数据
let pageData; // 每页数据
axios.get('./js/carlist.json')
.then(res => {
// console.log(res)
data = res.data
maxPage = Math.ceil(data.length / 5)
// 显示当前页码
showPagination(maxPage, pageNum);
})根据当前页数从总数据中获取当页数据,并渲染在页面上 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47let pageNum = 1; // 当前页码,默认页码1
let maxPage; // 最大页数
// TODO:待补充代码
// 显示当前页码
function showPagination(maxPage, pageNum) {
document.querySelector('#pagination').innerHTML = `共${maxPage}页,当前${pageNum}页`
}
// 获得每页数据
function getPageData(data) {
return data.slice((pageNum - 1) * 5, pageNum * 5)
}
// 渲染每页数据
function renderHTML(pageData) {
document.querySelector('#list').innerHTML = pageData.map(item => `
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">${item.name}</h5>
<small>${(item.price / 100).toFixed(2)}元</small>
</div>
<p class="mb-1">
${item.description}
</p>
</a>
</div>
`).join();
}
// 数据请求
let data; // 总数据
let pageData; // 每页数据
axios.get('./js/carlist.json')
.then(res => {
// console.log(res)
data = res.data
maxPage = Math.ceil(data.length / 5)
// 显示当前页码
showPagination(maxPage, pageNum);
// 获得每页数据
pageData = getPageData(data);
// 渲染每页数据
renderHTML(pageData);
})然后是实现翻页按钮功能,注意要做边界控制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// 点击上一页
let prev = document.getElementById("prev");
prev.onclick = function () {
// TODO:待补充代码
next.classList.remove('disabled')
pageNum--
console.log(pageNum)
if (pageNum <= 1) {
this.classList.add('disabled')
pageNum = 1
}
// 显示当前页码
showPagination(maxPage, pageNum);
// 获得每页数据
pageData = getPageData(data);
// 渲染每页数据
renderHTML(pageData);
};
// 点击下一页
let next = document.getElementById("next");
next.onclick = function () {
// TODO:待补充代码
prev.classList.remove('disabled')
pageNum++
console.log(pageNum)
if (pageNum >= maxPage) {
this.classList.add('disabled')
pageNum = maxPage
}
// 显示当前页码
showPagination(maxPage, pageNum);
// 获得每页数据
pageData = getPageData(data);
// 渲染每页数据
renderHTML(pageData);
};