【蓝桥杯】第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
 18- function 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
 14- function 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
 14- function 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
 15- function 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
 12- function 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
 12- function 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
 5- function collectPuzzle(...puzzles) { 
 // TODO: 在这里写入具体的实现逻辑
 const result = [...new Set(puzzles.flat())];
 return result;
 }
这道题主要考察了以下知识点:
- flat()- 是 JavaScript 数组的一个方法,用于将多维数组扁平化为一维数组。 
- 该方法可以接受一个整数参数,表示要扁平化的嵌套层数。例如,如果传递参数2,则会将二维数组扁平化为一维数组,但不会将三维或以上的数组扁平化。 
- 如果不传递参数,则默认只扁平化一层。若数组中有空位(即未定义的元素),则 - flat()方法默认会将其删除,返回一个新的不含空位的数组。
- 以下是 - flat()方法的示例用法:- 1 
 2
 3
 4
 5
 6
 7
 8- const 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
 30- function 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
 14- document.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
 16- methods: { 
 // 国家列表下拉框改变事件
 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
 25- async 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
 54- function 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
 67- function 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
 3- function 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
 23- function 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
 14- function 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 选项指定组件的名称,这样才能在组件的模板中嵌套自身组件。
 






