第十四届蓝桥杯备赛直播回放:点击观看

4月8日赛后解析:单击观看 (蓝桥杯参赛选手增值服务请见 32:101:12:44)

01 电影院排座位(5 分)

  • 考察:flex/gridnth-child/nth-of-type

首先可以看到,荧幕和座位区域的Dom结构已给出,只需要通过布局实现题目要求的 2-4-2 列的布局效果即可,基本思路是使用 Flex 或者 Grid 布局来实现:

1
2
3
4
5
6
7
8
9
<div class="container">
<!-- 荧幕区域 -->
<div class="screen">阿凡达2</div>
<!-- 座位区域 -->
<div class="seat-area">
<div class="seat"></div>
...
</div>
</div>
  • 如果使用 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;
    }

这道题主要考察了以下知识点:

  1. grid layout
    • grid-template-columns:用于定义网格列的大小和数量。
    • grid-gap:用于定义网格单元格之间的间距(是 row-gap 和 column-gap 的简写形式)。
  2. 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) 选择其父元素下所有的偶数同类型元素。
  3. 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个子元素。

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;
    }

这道题主要考察了以下知识点:

  1. 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) 方法将嵌套数组扁平化了两层,生成了一个包含所有元素的一维数组。

  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-widthmin-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 分)

  • 考察点:vue2element-ui、父子组件传值

  • 目标1:my-rate.vue 组件能够对不同的维度进⾏评分。补全 v-modelshow-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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
const bullets = [
"前方高能",
"原来如此",
"这么简单",
"学到了",
"学费了",
"666666",
"111111",
"workerman",
"学习了",
"别走,奋斗到天明",
];

/**
* @description 根据 bulletConfig 配置在 videoEle 元素最右边生成弹幕,并移动到最左边,弹幕最后消失
* @param {Object} bulletConfig 弹幕配置
* @param {Element} videoEle 视频元素
* @param {boolean} isCreate 是否为新增发送的弹幕,为 true 表示为新增的弹幕
*
*/
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);
}

document.querySelector("#sendBulletBtn").addEventListener("click", () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
// 从输入框中获取弹幕内容
let val = document.querySelector("#bulletContent").value;
// 如果输入框中没有内容,则不发送弹幕
if (!val) {
return;
}
// 使用 renderBullet 函数渲染弹幕
renderBullet(
{
...bulletConfig, // 使用扩展运算符合并弹幕配置
value: val, // 设置弹幕内容
},
videoEle, // 视频元素
true // 是否创建新的弹幕
);
// 清空输入框
document.querySelector("#bulletContent").value = "";
});

function getEleStyle(ele) {
// 获得元素的width,height,left,right,top,bottom
return ele.getBoundingClientRect();
}

function getRandomNum(end, start = 0) {
// 获得随机数,范围是 从start到 end
return Math.floor(start + Math.random() * (end - start + 1));
}

// 设置 index 是为了弹幕数组循环滚动
let index = 0;
const videoEle = document.querySelector("#video");
// 弹幕配置
const bulletConfig = {
isHide: false, // 是否隐藏
speed: 5, // 弹幕的移动距离
time: 50, // 弹幕每隔多少ms移动一次
value: "", // 弹幕的内容
};
let isPlay = false;
let timer; // 保存定时器
document.querySelector("#vd").addEventListener("play", () => {
// 监听视频播放事件,当视频播放时,每隔 1000s 加载一条弹幕
isPlay = true;
bulletConfig.value = bullets[index++];
renderBullet(bulletConfig, videoEle);
timer = setInterval(() => {
bulletConfig.value = bullets[index++];
renderBullet(bulletConfig, videoEle);
if (index >= bullets.length) {
index = 0;
}
}, 1000);
});

document.querySelector("#vd").addEventListener("pause", () => {
isPlay = false;
clearInterval(timer);
});

document.querySelector("#switchButton").addEventListener("change", (e) => {
if (e.target.checked) {
bulletConfig.isHide = false;
} else {
bulletConfig.isHide = true;
}
});

这道题怎么说呢,赛后直播中给的参考答案也没有达到题目的要求,或者说不完美。比如弹幕的随机颜色、发送弹幕时判定空值、发送弹幕后清空输入框、暂停视频时停止弹幕滚动,而且给弹幕元素加个动画过渡的话观感应该会更好……但怎么说呢,只是一个简陋的小 demo 吧,理解出题者的思路,按要求操纵 dom 元素就行了。

第二个函数要是优化一下的话可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
document.querySelector("#sendBulletBtn").addEventListener("click", () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
// 从输入框中获取弹幕内容
let val = document.querySelector("#bulletContent").value;
// 如果输入框中没有内容,则不发送弹幕
if (!val) {
return;
}
// 使用 renderBullet 函数渲染弹幕
renderBullet(
{
...bulletConfig, // 使用扩展运算符合并弹幕配置
value: val, // 设置弹幕内容
},
videoEle, // 视频元素
true // 是否创建新的弹幕
);
// 清空输入框
document.querySelector("#bulletContent").value = "";
});

07 年度明星项目(20 分)

  • 考点:ajaxjs/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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// 保存翻译文件数据的变量
let translation = {};
// 记录当前语言
let currLang = "zh-cn";

// 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: 请修改以下代码实现项目数据展示的功能

// // 以下代码(13-23行)为 createProjectItem 函数使用示例
// // Mock一个项目的数据
// const item = {
// icon: "bun.svg",
// description:
// "Incredibly fast JavaScript runtime, bundler, transpiler and package manager...",
// name: "Bun",
// stars: 37087,
// tags: ["runtime", "bun"],
// };
// // 添加至页面的项目列表中,查看页面可以看到有一行 bun 的项目数据
// $(".list > ul").append(createProjectItem(item));

// 用户点击加载更多时
$(".load-more").click(() => {
console.log(data.length); // 在控制台输出数据数组的长度
// 如果还有更多元素未显示,则继续显示下一批15条数据
if(cursor < data.length){
data.slice(cursor, (cursor + 15)).forEach((item) => {
$(".list > ul").append(
createProjectItem({
...item, // 展开 item 对象的所有属性
// 根据当前语言,选择要显示的项目描述信息
description: currLang === "zh-cn" ? item.descriptionCN : item.descriptionEN,
})
);
});
cursor += 15;
}

// console.log('当前已展示的条数:' + cursor); // 在控制台输出当前已经显示的项目数量
// 如果所有的元素都已经加载完毕,隐藏加载更多按钮
if(cursor === data.length){
$(".load-more").hide();
}
});

// TODO-END

// 用户点击切换语言的回调
$(".lang").click(() => {
// 切换页面文字的中英文
if (currLang === "en") {
$(".lang").text("English");
currLang = "zh-cn";
} else {
$(".lang").text("中文");
currLang = "en";
}
$("body")
.find("*")
.each(function () {
const text = $(this).text().trim();
if (translation[text]) {
$(this).text(translation[text]);
}
});
// 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,
})
);
});
});

// 生成列表DOM元素的函数,将该元素的返回值append至列表中即可生成一行项目数据
/**
* @param {string} name - 项目名称
* @param {string} description - 项目描述
* @param {string[]} tags - 项目标签
* @param {number} stars - 项目star数量
* @param {string} icon - 项目icon路径
*/
function createProjectItem({ name, description, tags, stars, icon }) {
return `
<li class="item">
<img src="images/${icon}" alt="">
<div class="desc">
<h3>${name}</h3>
<p>${description}</p>
<ul class="labels">
${tags.map((tag) => `<li>${tag}</li>`).join("")}
</ul>
</div>
<div class="stars">
+${stars} 🌟
</div>
</li>
`;
}

08 全球新冠疫情数据统计(20 分)

  • 考察:Vue2Echartsaxios

  • 目标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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>全球新冠疫情数据统计</title>
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<link rel="stylesheet" type="text/css" href="css/style.css" />
</head>

<body>
<div id="app">
<header>
<div>全球新冠疫情数据统计</div>
</header>
<main>
<!-- 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>
<select v-model="currentCountry" @change="selectChange">
<option value="">Select Country</option>
<!-- 请在此渲染所有国家选项 -->
<option v-for="country in countrys" :value="country" :key="country">
{{ country }}
</option>
</select>
<div id="chart" style="width: 100%; height: 50vh"></div>
</main>
</div>
</body>

<script src="js/axios.min.js"></script>
<script src="js/vue.min.js" type="text/javascript" charset="utf-8"></script>
<script
src="js/echarts.min.js"
type="text/javascript"
charset="utf-8"
></script>
<script>
var vm = new Vue({
el: "#app",
data: {
allData: [], // 所有国家数据
currentCountry: "", // 当前选中的国家
currentData: null, // 当前选中国家的数据
countrys: [],
targetCountry: "请选择国家",
sureNum: 0,
Allsure: 0,
newDeath: 0,
AllNewDeath: 0,
},
methods: {
// TODO: 请修改该函数代码实现题目要求
initChart() {
// 初始化图表
this.chart = echarts.init(document.getElementById("chart"));
this.chartOptions = {
title: {
text: "全球感染人数前30国家累计确诊人数统计",
x: "center",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
label: {
show: true,
},
},
},
// 设置x轴数据
xAxis: {
// 这里需要显示国家名称缩写,因为有些国家的全称太长,会导致界面不美观
// data: ["US", "IN", "FR", "DE", "BR", "JP", "KR"],
data: this.allData.map((item) => item.CountryCode),
axisLabel: {
show: true,
interval: 0,
},
},
yAxis: {
type: "value",
name: "确诊数量",
},
// 设置y轴数据
series: [
{
// data: [
// 100856162, 44680355, 39560482, 37446795, 36362366, 29489769,
// 29299166,
// ],
data: this.allData.map((item) => item.TotalConfirmed),
type: "bar",
itemStyle: {
normal: {
color: "#a90000",
},
},
},
],
};
// 调用此方法设置 echarts 数据
this.chart.setOption(this.chartOptions);
},
// 国家列表下拉框改变事件
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)
},
},
// 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(); // 初始化图标
});
},
});
</script>
</html>

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 分)

  • 考察点:ajaxaxios数据结构的处理

  • 目标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 模板,函数的主要步骤如下:

    1. 初始化 treeTemplate 为空字符串。
    2. 对当前节点所在的级别加 1,即 grade 加 1。
    3. 使用 Array.prototype.reduce() 遍历 data 数组的每一个节点,对每一个节点生成一个节点的 HTML 结构,将每个节点的 HTML 结构拼接到 treeTemplate 字符串上。
    4. 如果当前节点包含子节点,则递归调用 treeMenusRender() 函数,处理子节点,将子节点的 HTML 结构拼接到当前节点的 HMTL 结构中。
    5. 返回最后拼接好的 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);
    }
    }

    解题思路:主要思想是递归

    1. 获取拖拽的元素
    2. 获取放置的元素和被拖拽元素需要放置的位置

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>`;

    知识点解析:

    1. 在 Vue 中,递归组件是一种特殊的组件,它可以在其模板中调用自身来生成嵌套的组件结构。这种组件可以用于展示树形结构、菜单、导航、评论等需要递归生成的场景。
    2. 在递归组件中,我们需要使用组件的 name 选项指定组件的名称,这样才能在组件的模板中嵌套自身组件。