【蓝桥杯】第14届 Web 应用开发省赛真题解析
第十四届蓝桥杯备赛直播回放:点击观看
4月8日赛后解析:单击观看 (蓝桥杯参赛选手增值服务请见
32:10
或1:12:44
)
01 电影院排座位(5 分)
- 考察:
flex/grid
、nth-child/nth-of-type
首先可以看到,荧幕和座位区域的Dom结构已给出,只需要通过布局实现题目要求的 2-4-2
列的布局效果即可,基本思路是使用 Flex 或者 Grid 布局来实现:
1 | <div class="container"> |
如果使用 grid 布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/* grid 布局答案 */
.seat-area {
margin-top: 50px;
display: grid; /* 将元素设置为网格布局 */
grid-template-columns: repeat(8, auto); /* 设置网格列数为8,每列宽度自适应 */
grid-gapgap: 10px; /* 设置网格间距为10px */
}
/* 设置第2个和第6个座位的右侧边距为20px */
.seat:nth-of-type(8n+2) {
margin-right: 20px;
}
.seat:nth-of-type(8n+6) {
margin-right: 20px;
}如果使用 flex 布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/* flex布局答案 */
.seat-area {
margin-top: 50px;
display: flex;
flex-wrap: wrap;
}
/* 设置座位的右边距和底边距 */
.seat {
margin-right: 10px;
margin-bottom: 10px;
}
/* 将第8列座位的右边距置零 */
.seat:nth-child(8n) {
margin-right: 0px;
}
/* 将第2列座位的右边距置30px */
.seat:nth-child(8n + 2) {
margin-right: 30px;
}
/* 将第6列座位的右边距置30px */
.seat:nth-child(8n + 6) {
margin-right: 30px;
}
这道题主要考察了以下知识点:
- grid layout
grid-template-columns
:用于定义网格列的大小和数量。grid-gap
:用于定义网格单元格之间的间距(是 row-gap 和 column-gap 的简写形式)。
nth-of-type
- 是一个CSS伪类选择器,用于选取一组相同类型的元素中的第 n 个元素。
- 具体而言:
:nth-of-type(n)
匹配其父元素下第 n 个同类型的元素,该选择器接受一个参数,可以是一个具体的数字、关键字odd/even
,或者公式an+b
。 - 以下是一些示例,常用于选择列表、网格和其他具有相同类型元素的结构:
:nth-of-type(2)
选择其父元素下的第二个同类型元素。:nth-of-type(3n + 1)
选择其父元素下每隔三个同类型元素的第一个。:nth-of-type(odd)
选择其父元素下所有的奇数同类型元素。:nth-of-type(even)
选择其父元素下所有的偶数同类型元素。
nth-child
- 是一个CSS伪类选择器,用于选择指定元素的子元素,它的语法:
:nth-child(an+b)
- 其中,a 和 b 是两个可选参数,n 表示整数(0, 1, 2,…),表示元素在其父元素中的位置。
an+b
则表示一系列满足这个元素的公示,其中 a 和 b 为数字,表示这些元素的位置。比如2n+1
表示所有奇数位置。 - 下面是一些常用示例:
:nth-child(n)
匹配父元素的所有子元素。:nth-child(even)
匹配父元素的偶数子元素。:nth-child(odd)
匹配父元素的奇数子元素。:nth-child(3)
匹配父元素的第三个子元素。:nth-child(3n)
匹配父元素的第3、6、9、12…个子元素。:nth-child(3n + 1)
匹配父元素的第1、4、7、10…个子元素。:nth-child(-n + 4)
匹配父元素的前4个子元素。
- 是一个CSS伪类选择器,用于选择指定元素的子元素,它的语法:
02 图片水印生成(5 分)
考察:Dom操作,CSS3常见属性
答案1:通过 for 循环生成指定数量的 span 元素,并将它们添加到容器中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function createWatermark(text, color, deg, opacity, count) {
// 创建水印容器
const container = document.createElement("div");
container.className = "watermark";
// TODO: 根据输入参数创建文字水印
// 通过 for 循环生成指定数量的 span 元素,并将它们添加到容器中
for (let i = 0; i < count; i++) {
const span = document.createElement("span");
span.innerText = text;
span.style.color = color;
span.style.transform = `rotate(${deg}deg)`;
span.style.opacity = opacity;
container.appendChild(span);
}
// 返回水印容器
return container;
}或者通过模板字符串的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14function createWatermark(text, color, deg, opacity, count) {
// 创建水印容器
const container = document.createElement("div");
container.className = "watermark";
// TODO: 根据输入参数创建文字水印
// 通过 for 循环生成指定数量的 span 元素,并将它们添加到容器中
for (let i = 0; i < count; i++) {
const span = document.createElement("span");
container.innerHTML += `<span style="color: ${color}; transform: rotate(${deg}deg); opacity: ${opacity}">${text}</span>`;
}
// 返回水印容器
return container;
}答案2:使用 repeat 方法创建一个包含指定 span 元素的字符串,并为每个 span 元素设置内容和样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14function createWatermark(text, color, deg, opacity, count) {
// 创建水印容器
const container = document.createElement("div");
container.className = "watermark";
// TODO: 根据输入参数创建文字水印
// 使用 repeat 方法创建一个包含指定 span 元素的字符串,并为每个 span 元素设置内容和样式
const spans = `<span style="color: ${color}; opacity: ${opacity}; transform: rotate(${deg}deg);">${text}</span>`.repeat(count);
// 将包含所有 span 元素的字符串添加到水印容器中
container.innerHTML = spans;
// 返回水印容器
return container;
}
这道题主要考察了以下知识点:
transform: rotate(10deg)
这是一个 CSS 变换属性,它通过指定的度数值(
${deg}deg
)将元素旋转。${deg}
变量可以替换为任何数字,用于表示旋转的度数。repeat()
方法是 ES6 标准中引入的,语法如下:1
2
3
4
5
6// 语法
str.repeat(count);
// 示例
str = "a";
const newStr = str.repeat(3);
console.log(newStr); // 输出 'aaa'
03 收集帛书碎片(10 分)
考察:数组拍平、数组去重
解题思路:入参是一个二维数组,转换为一维数组,然后再去重
答案1:使用
concat()
方法拼接二维数组的子项,就相当于将这个二维数组拍平了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function collectPuzzle(...puzzles) {
// TODO: 在这里写入具体的实现逻辑
// console.log(puzzles);
// 1.拼接(相当于对这个二维数组进行了扁平化)
let result = [];
for (let i = 0; i < puzzles.length; i++) {
result = result.concat(puzzles[i]);
}
// console.log(result);
// 2.去重
result = [...new Set(result)];
return result;
}答案2:循环遍历二维数组,手动 push 每个展开后的子项,也能达到拍平效果:
1
2
3
4
5
6
7
8
9
10
11
12function collectPuzzle(...puzzles) {
// TODO: 在这里写入具体的实现逻辑
let result = [];
// 循环遍历二维数组,将每一个子项展开并 push 到 result 中
puzzles.forEach((item) => {
result.push(...item);
});
// 去重
result = [...new Set(result)];
return result;
}或者使用普通 for 循环:
1
2
3
4
5
6
7
8
9
10
11
12function collectPuzzle(...puzzles) {
// TODO: 在这里写入具体的实现逻辑
let result = [];
// 循环遍历二维数组,将每一个子项展开并 push 到 result 中
for (let i = 0; i < puzzles.length; i++) {
result.push(...puzzles[i]);
}
// 去重
result = [...new Set(result)];
return result;
}答案3:或者使用
flat()
方法拍平数组(该方法可以拍平任意维度的数组):1
2
3
4
5function collectPuzzle(...puzzles) {
// TODO: 在这里写入具体的实现逻辑
const result = [...new Set(puzzles.flat())];
return result;
}
这道题主要考察了以下知识点:
flat()
是 JavaScript 数组的一个方法,用于将多维数组扁平化为一维数组。
该方法可以接受一个整数参数,表示要扁平化的嵌套层数。例如,如果传递参数2,则会将二维数组扁平化为一维数组,但不会将三维或以上的数组扁平化。
如果不传递参数,则默认只扁平化一层。若数组中有空位(即未定义的元素),则
flat()
方法默认会将其删除,返回一个新的不含空位的数组。以下是
flat()
方法的示例用法:1
2
3
4
5
6
7
8const arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6]]];
arr1.flat(); // [1, 2, 3, 4, [5, 6]]
const arr3 = [1, 2, [3, 4, [5, 6]]];
arr1.flat(2); // [1, 2, 3, 4, 5, 6]在上方的示例中,arr1 和 arr2 数组中的嵌套数组都被扁平化为了一维数组。在 arr3 中,
flat(2)
方法将嵌套数组扁平化了两层,生成了一个包含所有元素的一维数组。
Set
是 JavaScript 的一种数据结构,它类似于数组,但是它的值是唯一的,不会有重复的值。通常用于数组去重。
04 自适应页面(10 分)
考察:CSS、
媒体查询 @media
答案:
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@media (max-width: 800px) {
/* 隐藏菜单按钮和折叠面板 */
input.menu-btn,
input[type="checkbox"].menu-btn ~ .collapse {
display: none;
}
/* 当菜单按钮被选中时显示折叠面板 */
input[type="checkbox"].menu-btn:checked ~ .collapse {
display: block;
}
/* 定义菜单按钮的样式 */
label.menu-btn {
color: #959595;
cursor: pointer;
display: block;
padding: 16px 32px;
}
/* 当鼠标悬停在菜单按钮时更改颜色 */
label.menu-btn:hover {
color: #fff;
}
/* 将菜单列表中的每个项目设置为块级元素 */
.menu li {
display: block;
}
/* 折叠面板样式 */
.collapse {
position: absolute;
background: #252525;
width: 100%;
}
/* 下拉菜单样式 */
.dropdown ul {
position: relative;
}
/* 将行元素设置为块级元素 */
.row {
display: block;
}
/* 设置图片的宽度为 100% 并去除边距 */
#tutorials img {
width: 100%;
margin: 0;
}
}
这道题主要考察了以下知识点:
@media
规则可以包含一个或多个条件,如媒体查询、宽度和高度、分辨率等,它们由关键字and
连接。其中,最常用的条件是max-width
和min-width
,它们可以根据浏览器窗口的宽度来应用不同的样式。1
2
3
4
5
6
7/* 大于 320 小于 600 */
@media screen and (max-width: 600px) and (min-width: 320px) {
.box {
width: 100%;
height: 300px;
}
}
05 外卖给好评(15 分)
考察点:
vue2
、element-ui
、父子组件传值目标1:
my-rate.vue
组件能够对不同的维度进⾏评分。补全v-model
和show-score
属性即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14<ul class="rate-list">
<li>
<!-- TODO: 补全 el-rate 属性 -->
送餐速度:<el-rate v-model="speed" show-score></el-rate>
</li>
<li>
<!-- TODO: 补全 el-rate 属性 -->
外卖口味:<el-rate v-model="flavour" show-score></el-rate>
</li>
<li>
<!-- TODO: 补全 el-rate 属性 -->
外卖包装:<el-rate v-model="pack" show-score></el-rate>
</li>
</ul>目标2:
my-rate.vue
组件对外抛出change
事件,在三项评分均完成后,触发 change 事件, change 事件包含⼀个参数,用于传递改变后的分数值。答案不唯一,比如我考试时是这么写的: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
59
60<template>
<div class="block">
<span class="demonstration">请为外卖评分: </span>
<ul class="rate-list">
<li>
<!-- TODO: 补全 el-rate 属性 -->
送餐速度:<el-rate v-model="speed" show-score @change="checkRate"></el-rate>
</li>
<li>
<!-- TODO: 补全 el-rate 属性 -->
外卖口味:<el-rate v-model="flavour" show-score @change="checkRate"></el-rate>
</li>
<li>
<!-- TODO: 补全 el-rate 属性 -->
外卖包装:<el-rate v-model="pack" show-score @change="checkRate"></el-rate>
</li>
</ul>
</div>
</template>
<style>
.block {
border: 1px solid #c7c5c5;
padding: 10px;
}
.rate-list {
list-style: none;
padding-inline-start: 20px;
margin-block-start: 10px;
margin-block-end: 10px;
}
.el-rate {
display: inline-block;
}
</style>
<script>
module.exports = {
data() {
return {
speed: 0, // 送餐速度
flavour: 0, // 外卖口味
pack: 0, // 外卖包装
};
},
/* TODO:待补充代码 */
methods: {
// 检查三个维度是否都已经评分
checkRate() {
if (this.speed && this.flavour && this.pack) {
// 向父组件传值
this.$emit('change', {
speed: this.speed,
flavour: this.flavour,
pack: this.pack,
});
}
},
},
};
</script>
06 视频弹幕(15 分)
考点:Dom操作、JS基础
目标1:补全
renderBullet
函数中的代码,控制弹幕的显示颜⾊和移动。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
30function renderBullet(bulletConfig, videoEle, isCreate = false) {
const spanEle = document.createElement("SPAN");
spanEle.classList.add(`bullet${index}`);
if (isCreate) {
spanEle.classList.add("create-bullet");
}
// TODO:控制弹幕的显示颜色和移动,每隔 bulletConfig.time 时间,弹幕移动的距离 bulletConfig.speed
// console.log(bulletConfig);
// 将弹幕内容添加到 span 元素中
spanEle.innerHTML = `${bulletConfig.value}`;
// 显示弹幕
spanEle.style.display = "block";
// 获取视频元素的宽高
let { width: videoWidth, height: videoHeight } = getEleStyle(videoEle);
// 设置弹幕初始位置在视频最右侧,并在垂直方向上随机分布
spanEle.style.left = `${videoWidth}px`;
spanEle.style.top = `${getRandomNum(videoHeight)}px`;
// 将弹幕添加到视频元素中
videoEle.appendChild(spanEle);
// 设置弹幕定时器,每隔一段时间将弹幕左移,当弹幕移出屏幕后,清除弹幕元素和定时器
let timer = setInterval(() => {
spanEle.style.left = parseInt(spanEle.style.left) - bulletConfig.speed + "px";
if (parseInt(spanEle.style.left) <= -64) {
if (spanEle) {
videoEle.removeChild(spanEle);
}
clearInterval(timer);
}
}, bulletConfig.time);
}目标2:补全
#sendBulletBtn
元素的绑定事件,点击发送按钮,输⼊框中的文字出现在弹幕中,样式不同于普通弹幕(样式红色字体红色框已设置,类名为create-bullet
)。通过调用renderBullet
方法和正确的传参实现功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14document.querySelector("#sendBulletBtn").addEventListener("click", () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
// 从输入框中获取弹幕内容
let val = document.querySelector("#bulletContent").value;
// 使用 renderBullet 函数渲染弹幕
renderBullet(
{
...bulletConfig, // 使用扩展运算符合并弹幕配置
value: val, // 设置弹幕内容
},
videoEle, // 视频元素
true // 是否创建新的弹幕
);
});
本题完整代码如下:
1 | const bullets = [ |
这道题怎么说呢,赛后直播中给的参考答案也没有达到题目的要求,或者说不完美。比如弹幕的随机颜色、发送弹幕时判定空值、发送弹幕后清空输入框、暂停视频时停止弹幕滚动,而且给弹幕元素加个动画过渡的话观感应该会更好……但怎么说呢,只是一个简陋的小 demo 吧,理解出题者的思路,按要求操纵 dom 元素就行了。
第二个函数要是优化一下的话可以这样:
1 | document.querySelector("#sendBulletBtn").addEventListener("click", () => { |
07 年度明星项目(20 分)
考点:
ajax
、js/jQuery 基础
目标1:在⻚⾯初始化时使⽤ AJAX 请求地址为
./js/all-data.json
以及./js/translation.json
⽂件中的数据,并将后者中的数据保存⾄translation
变量中。其中all-data.json
⽂件中以数组的形式存储了明星项⽬的数据,translation.json
文件中包含了网站中英文转换所需的数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// TODO: 请在此补充代码实现项目数据文件和翻译数据文件的请求功能
// 存放当前展示数据
const data = [];
// 请求项目数据
$.ajax({
url: "./js/all-data.json",
type: "get",
success: (result) => {
console.log(result);
data.push(...result);
},
});
// 请求翻译数据
$.ajax({
url: "./js/translation.json",
type: "get",
success: (result) => {
console.log(result);
translation = result;
},
});
// TODO-END目标2:页面初始化时利⽤
createProjectItem
函数创建前 15 个项⽬数据(即all-data.json
数组中的前 15 项)的列表元素并加载到⻚⾯中。当用户点击 加载更多 按钮时,则按顺序再显示 15 个项目数据。直到所有项目数据都展示完毕(共 60 个)。所有项⽬展示完毕后需要隐藏 加载更多 按钮。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// TODO: 请在此补充代码实现项目数据文件和翻译数据文件的请求功能
// 存放项目数据的数组
const data = [];
// 记录当前展示的项目数量
let cursor = 15;
// 请求项目数据
$.ajax({
url: "./js/all-data.json",
type: "get",
success: (result) => {
// console.log(result);
data.push(...result);
// 初始化时展示前15条数据
data.slice(0, 15).forEach((item) => {
$(".list > ul").append(
createProjectItem({ ...item, description: item.descriptionCN })
);
});
},
});
// 请求翻译数据
$.ajax({
url: "./js/translation.json",
type: "get",
success: (result) => {
// console.log(result);
translation = result;
},
});
// TODO-END
// TODO: 请修改以下代码实现项目数据展示的功能
// 用户点击加载更多时
$(".load-more").click(() => {
console.log(data.length); // 在控制台输出数据数组的长度
// 如果还有更多元素未显示,则继续显示下一批15条数据
if(cursor < data.length){
data.slice(cursor, (cursor + 15)).forEach((item) => {
$(".list > ul").append(
createProjectItem({ ...item, description: item.descriptionCN })
);
});
cursor += 15;
}
// console.log('当前已展示的条数:' + cursor); // 在控制台输出当前已经显示的项目数量
// 如果所有的元素都已经加载完毕,隐藏加载更多按钮
if(cursor === data.length){
$(".load-more").hide();
}
});
// TODO-END目标3:当⽤户点击页面右上⽅的中英文切换按钮时,根据用户的选择改变项目描述使⽤的语言(不改变原有项目展示数量)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// TODO: 请在此补充代码实现项目描述的语言切换
// 将页面中项目列表清空
$(".list > ul").empty();
// 遍历数据数组中从 0 到 cursor 索引位置之前的元素
data.slice(0, cursor).forEach((item) => {
// 将每个元素插入到列表中
$(".list > ul").append(
createProjectItem({
...item, // 展开 item 对象的所有属性
// 根据当前语言,选择要显示的项目描述信息
description: currLang === "zh-cn" ? item.descriptionCN : item.descriptionEN,
})
);
});别忘了前面加载更多按钮绑定的函数中,
createProjectItem
语言部分也要修改。
本题完整代码如下:
1 | // 保存翻译文件数据的变量 |
08 全球新冠疫情数据统计(20 分)
考察:
Vue2
、Echarts
、axios
目标1:在组件加载时利⽤ axios 请求地址为
./js/covid-data.json
⽂件中的数据。并将所有国家名称在 select 标签下的 option 元素进⾏渲染 (保留默认选项 “Select Country”):1
2
3
4
5
6
7<select>
<option value="">Select Country</option>
<!-- 请在此渲染所有国家选项 -->
<option v-for="country in countrys" :value="country" :key="country">
{{ country }}
</option>
</select>1
2
3
4
5
6
7
8
9
10
11// TODO: 请在此添加代码实现组件加载时数据请求的功能
mounted() {
// 请求数据和初始化图标
axios.get("./js/covid-data.json").then((res) => {
// console.log(res.data)
this.countrys = res.data.map((item) => item.Country); // 国家列表数据
// console.log(this.countrys);
this.allData = res.data; // 所有国家数据
this.initChart(); // 初始化图标
});
},目标2:当⽤户改变 select 筛选器的选择时,根据⽤户的选择改变⻚⾯中展示的国家名以及确诊和死亡⼈ 数数据。如果⽤户没有选择任何国家,则展示默认值 0 和默认标题 “请选择国家”。
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<!-- TODO: 请修改以下代码实现不同国家的数据展示功能 -->
<div class="title">
<h2>{{ currentCountry ? currentCountry : '请选择国家' }}</h2>
</div>
<div class="boxes">
<div class="box1">
<h3>确诊</h3>
<div class="number">
<span class="font-bold">新增:</span>
{{ currentData ? currentData.NewConfirmed : 0 }}
</div>
<div class="number">
<span class="font-bold">总计:</span>
{{ currentData ? currentData.TotalConfirmed : 0 }}
</div>
</div>
<div class="box2">
<h3>死亡</h3>
<div class="number">
<span class="font-bold">新增:</span>
{{ currentData ? currentData.NewDeaths : 0 }}
</div>
<div class="number">
<span class="font-bold">总计:</span>
{{ currentData ? currentData.TotalDeaths : 0 }}
</div>
</div>
</div>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16methods: {
// 国家列表下拉框改变事件
selectChange() {
// console.log(this.currentCountry)
if (this.currentCountry) {
// 如果国家值不为空,则根据国家值获取对应的数据
this.currentData = this.allData.find(
(item) => item.Country === this.currentCountry
)
} else {
// 如果国家值为空,则清空当前国家数据
this.currentData = null;
}
// console.log(this.currentData)
},
},目标3:⻚⾯底部的 ECharts 图表希望显示各个国家的累计确诊⼈数,请修改 initChart 函数的内容, 使得图表 x 轴数据为国家简称,y 轴数据为累计确诊⼈数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 设置x轴数据
xAxis: {
// 这里需要显示国家名称缩写,因为有些国家的全称太长,会导致界面不美观
// data: ["US", "IN", "FR", "DE", "BR", "JP", "KR"],
data: this.allData.map((item) => item.CountryCode),
// ...
},
// 设置y轴数据
series: [
{
// data: [
// 100856162, 44680355, 39560482, 37446795, 36362366, 29489769,
// 29299166,
// ],
data: this.allData.map((item) => item.TotalConfirmed),
// ...
}
],
最终本题代码为:
1 |
|
09 Markdown 文档解析(25 分)
考察点:
Nodejs
、简单正则
、replace 方法替换字符串
目标1:对分隔符进⾏解析
1
2// TODO: 补充分割符正则
this.hr = /^(\-{3,})/;1
2
3
4
5
6
7// 1. 对分隔符进行解析
isHr() {
return this.hr.test(this.lineText);
}
parseHr() {
return "<hr/>";
}目标2:对引⽤区块进⾏解析
1
2
3
4
5
6
7
8// 2. 对引用区块进行解析
isBlockQuote() {
return this.blockQuote.test(this.lineText);
}
parseBlockQuote() {
const tempStr = this.lineText.replace(this.blockQuote, "");
return "<p>" + tempStr + "</p>";
}目标3:对⽆序列表进⾏解析
1
2
3
4
5
6
7
8// 3. 对无序列表进行解析
isUnorderedList() {
return this.unorderedList.test(this.lineText);
}
parseUnorderedList() {
const tempStr = this.lineText.replace(this.unorderedList, "");
return "<li>" + tempStr + "</li>";
}目标4:对图⽚进⾏解析
1
2
3
4
5
6
7
8
9// 4. 对图片进行解析
isImage() {
return this.image.test(this.lineText);
}
parseImage(str) {
return str.replace(this.image, (result, str1, str2) => {
return `<img src="${str2}" alt="${str1}"/>`;
});
}目标5:对⽂字效果进⾏解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 5. 对文字效果进行解析
// 5.1 粗体效果
isStrongText() {
return this.strongText.test(this.lineText);
}
parseStrongText(str) {
return str.replace(this.strongText, (result, str1) => {
return `<b>${str1}</b>`;
});
}
// 5.2 行内代码块
isCodeLine() {
return this.codeLine.test(this.lineText);
}
parseCodeLine(str) {
return str.replace(this.codeLine, (result, str1) => {
return `<code>${str1}</code>`;
});
}
知识点解析:
/^(\-{3,})/
是一个正则表达式,用于匹配以三个或更多连字符(“-”)开头的字符串str.replace()
第一个参数可以是一个正则表达式或者一个字符串,表示要替换的子字符串;第二个参数可以是一个字符串或者一个函数,表示用于替换的新字符串或者替换逻辑。
10 组课神器(25 分)
考察点:
ajax
、axios
、数据结构的处理
目标1:补全
js/index.js
⽂件中 ajax 函数,功能为根据请求方式 method 不同,拿到树型组件的数据并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25async function ajax({ url, method = "get", data }) {
let result;
// TODO:根据请求方式 method 不同,拿到树型组件的数据
// 当method === "get" 时,localStorage 存在数据从 localStorage 中获取,不存在则从 /js/data.json 中获取
// 当method === "post" 时,将数据保存到localStorage 中,key 命名为 data
console.log(url, method, data)
if (method === "get") {
let res;
if (localStorage.getItem("data")) {
result = JSON.parse(localStorage.getItem("data"));
} else {
data = await axios({ url, method, data });
// console.log(data)
res = data.data;
// console.log(res)
result = res.data;
// console.log(result)
}
}
if (method === "post") {
localStorage.setItem("data", JSON.stringify(data));
}
return result;
}知识点:
localStorage
由于本地存储只能存储字符串,因此在将数据存储到本地存储时,需要将数据转换成 JSON 字符串,例如使用JSON.stringify()
方法。目标2:. 补全
js/index.js
⽂件中的treeMenusRender
函数,使⽤所传参数 data ⽣成指定 DOM 结构 的模板字符串。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
54function treeMenusRender(data, grade = 0) {
let treeTemplate = "";
// TODO:根据传入的 treeData 的数据生成树型组件的模板字符串
grade++;
return data.reduce((pre, cur) => {
let isContantChild = !!cur.children;
return (treeTemplate += `
<div class="tree-node" data-grade=${grade} data-index="${cur.id}">
<div class="tree-node-content" style="margin-left:${
(grade - 1) * 15
}px">
<div class="tree-node-content-left">
<img class="point-svg" src="./images/dragger.svg" alt="点击拖动" />
${cur.tag ? `<span class="tree-node-tag">${cur.tag}</span>` : ""}
<span class="tree-node-label">${cur.label}</span>
${
isContantChild
? `<img class="config-svg" src="./images/config.svg" alt="设置" />`
: ""
}
</div>
${
!isContantChild
? `<div class="tree-node-content-right">
<div class="students-count">
<span class="number"> 0人完成</span>
<span class="line">|</span>
<span class="number">0人提交报告</span>
</div>
<div class="config">
<img
class="config-svg"
src="./images/config.svg"
alt=""
/>
<button class="doc-link">编辑文档</button>
</div>
</div>`
: ""
}
</div>
${
isContantChild
? `<div class="tree-node-children">
${isContantChild && treeMenusRender(cur.children, grade)}
</div>`
: ""
}
</div>
`);
}, "");
// return treeTemplate;
}解题思路:写一个递归函数,用于根据传入的数据 data 生成一个树形结构组件的模板字符串 treeTemplate,函数会递归处理 data 的每个节点,根据每个节点的属性值生成一个节点的 HTML 结构,最终拼接成一个完整的树形结构组件的 HTML 模板,函数的主要步骤如下:
- 初始化
treeTemplate
为空字符串。 - 对当前节点所在的级别加 1,即 grade 加 1。
- 使用
Array.prototype.reduce()
遍历 data 数组的每一个节点,对每一个节点生成一个节点的 HTML 结构,将每个节点的 HTML 结构拼接到treeTemplate
字符串上。 - 如果当前节点包含子节点,则递归调用
treeMenusRender()
函数,处理子节点,将子节点的 HTML 结构拼接到当前节点的 HMTL 结构中。 - 返回最后拼接好的
treeTemplate
字符串。
- 初始化
目标3: 补全
js/index.js
文件中的treeDataRefresh
函数,功能为:根据参数列表{ dragGrade, dragElementId }, { dropGrade, dropElementId }
重新⽣成拖拽后的树型组件数据treeData
。(treeData
为全局变量,直接访问并根据参数处理后重新赋值即可)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
59
60
61
62
63
64
65
66
67function treeDataRefresh(
{ dragGrade, dragElementId },
{ dropGrade, dropElementId }
) {
if (dragElementId === dropElementId) return;
// TODO:根据 `dragElementId, dropElementId` 重新生成拖拽完成后的树型组件的数据 `treeData`
// 此处需要实现根据拖拽的元素 id(dragElementId)和放置的目标元素 id(dropElementId)重新生成树型组件的数据 treeData
// 从树形数据中获取并删除被拖拽的元素
const getAndDeleteDragLabelObj = (dragElement, data) => {
let result;
// 拖拽的元素的层级与放置的目标元素的层级差值应该是 1
if (dragGrade - dropGrade > 1 || dragGrade - dropGrade < 0) return result;
const innerFn = (dragElementId, data) => {
data.forEach((treeObj, index, array) => {
if (treeObj.id === Number(dragElementId)) {
array.splice(index, 1);
result = treeObj;
} else {
// 如果当前节点有子节点,则继续递归查找拖拽的元素
treeObj.children && innerFn(dragElementId, treeObj.children);
}
});
};
innerFn(dragElementId, data);
return result;
};
// 将被拖拽的元素插入到放置的目标元素下面
const setDragLabelObjToTreeData = (dragLabelObj, dropElementId, data) => {
for (let i = 0; i < data.length; i++) {
const treeObj = data[i];
if (treeObj.id === Number(dropElementId)) {
if (dragGrade - dropGrade === 1) {
// 如果拖拽的元素的层级与放置的目标元素的层级差值是 1,则将拖拽的元素设置为放置元素的子元素
treeObj.children
? treeObj.children.unshift(dragLabelObj)
: (treeObj["children"] = [dragLabelObj]);
} else if (dragGrade - dropGrade === 0) {
// 如果拖拽的元素的层级与放置的目标元素的层级相同,则将拖拽的元素设置为放置元素的后继元素
data.splice(i + 1, 0, dragLabelObj);
break;
}
} else {
// 如果当前节点有子节点,则继续递归查找拖拽的元素应该被设置的位置
treeObj.children &&
setDragLabelObjToTreeData(
dragLabelObj,
dropElementId,
treeObj.children
);
}
}
};
// 获取被拖拽的元素
let dragLabelObj = getAndDeleteDragLabelObj(dragElementId, treeData);
if (dragElementId) {
// 如果存在 dragElementId,则将被拖拽的元素插入到相应位置
dragLabelObj &&
setDragLabelObjToTreeData(dragLabelObj, dropElementId, treeData);
} else {
// 否则将被拖拽的元素插入到树的顶层
treeData.unshift(dragLabelObj);
}
}解题思路:主要思想是递归
- 获取拖拽的元素
- 获取放置的元素和被拖拽元素需要放置的位置
11 ISBN 转换与生成
这是专科组的题,考点是
字符串
和正则表达式
目标1:补充
getNumbers
函数,剔除输入参数str
中除了数字和大写X
之外的其他字符,将其转换为只有纯数字和大写X
字母的字符串。可以循环去实现,也可以正则去替换:1
2
3function getNumbers(str) {
return str.replace(/[^0-9xX]/g, "").toUpperCase();
}目标2:补充
validISBN10
函数,判断输入参数isbn
是否是一个有效的ISBN-10
字符串,并将判断结果(true 或 false)返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function validISBN10(isbn) {
if (isbn.length !== 10) {
// 检查 ISBN 是否为 10 位
return false;
}
let pre = isbn.slice(0, 9); // 获取 ISBN 的前 9 位
let last = 0;
for (let i = 0; i < pre.length; i++) {
// 计算 ISBN 的校验码
last += (i + 1) * parseInt(pre[i]);
}
last % = 11;
if (last === 10) {
// 如果校验码为 10,将其表示为字母 X
last = "X";
}
if (isbn.slice(-1) == last) {
// 比较 ISBN 的最后一位和计算得到的校验码
return true;
} else {
return false;
}
}目标3:补充
ISBN10To13
函数,将输入参数isbn
(一个有效的ISBN-10
字符串)转化为对应的ISBN-13
字符串,并将转化后的字符串返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14function ISBN10To13(isbn) {
let pre = "978" + isbn.slice(0, 9); // 将 ISBN-10 编码转化为 ISBN-13 编码时,将前三位设置为 978,再加上原来的前 9 位
let last = 0;
for (let i = 0; i < pre.length; i++) {
// 计算 ISBN-13 的校验码
if (i % 2 === 0) {
last += parseInt(pre[i]); // 如果是偶数位,将其乘以 1
} else {
last += parseInt(pre[i]) * 3; // 如果是奇数位,将其乘以 3
}
}
pre += last % 10 !== 0 ? 10 - (last % 10) : 0; // 计算并添加 ISBN-13 的校验码
return pre;
}
12 骨架屏
本题为职业院校组题目,考察点:
vue基础
、props
、递归组件
解题思路:矩形和圆不需要递归,根据条件判断渲染即可,row 和 col 会嵌套,循环
rows/cols
进行递归渲染。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/*
* 骨架屏渲染组件
*/
let ItemTemplate = ``;
// TODO: 请补充完整Template,完成组件代码编写
ItemTemplate = `<div>
<div v-if="paragraph.type === 'row'" class="ske-row-container">
<div class="ske ske-row" :style="row.rowStyle" v-for="row in paragraph.rows">
<!-- 递归调用 item 组件来渲染子节点,传递 row 和 active 属性 -->
<item :paragraph="row" :active="active"></item>
</div>
</div>
<div v-else-if="paragraph.type === 'col'" class="ske-col-container">
<div class="ske ske-col" :style="col.colStyle" v-for="col in paragraph.cols">
<!-- 递归调用 item 组件来渲染子节点,传递 col 和 active 属性 -->
<item :paragraph="col" :active="active"></item>
</div>
</div>
<div v-else-if="paragraph.type === 'rect'" class="ske-rect-container">
<!-- 以矩形形式渲染当前节点 -->
<div class="ske ske-rect" :class="{'ske-ani': active}" :style="paragraph.style"></div>
</div>
<div v-else-if="paragraph.type === 'circle'" class="ske-circle-container">
<!-- 以圆形形式渲染当前节点 -->
<div class="ske ske-circle" :class="{'ske-ani': active}" :style="paragraph.style"></div>
</div>
</div>`;知识点解析:
- 在 Vue 中,递归组件是一种特殊的组件,它可以在其模板中调用自身来生成嵌套的组件结构。这种组件可以用于展示树形结构、菜单、导航、评论等需要递归生成的场景。
- 在递归组件中,我们需要使用组件的 name 选项指定组件的名称,这样才能在组件的模板中嵌套自身组件。