惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
D
Docker
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客
美团技术团队
V
V2EX
Last Week in AI
Last Week in AI
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Microsoft Security Blog
Microsoft Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
P
Proofpoint News Feed
B
Blog RSS Feed
博客园_首页
B
Blog
博客园 - 叶小钗
I
InfoQ
WordPress大学
WordPress大学
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
Latest news
Latest news
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
Cyberwarzone
Cyberwarzone
Scott Helme
Scott Helme
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
C
CXSECURITY Database RSS Feed - CXSecurity.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
N
News and Events Feed by Topic
S
Secure Thoughts
The Hacker News
The Hacker News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News

百里飞洋

【Art Design】一款精美的前端后台模板,出色的 Vite + TypeScript + Element Plus 的 Vue3 后台管理解决方案 坏消息,被DDoS攻击了;好消息,只被打了2分钟。 【开箱验机】只花 6277 拿下 i7-14650HX/RTX4060 笔记本?机械革命 耀世16Pro 安全下车! 我也中招了!大量简中博客站点被镜像,并翻译成了繁体中文 【GitHub】基于Actions和Pages实现项目的自动构建与部署 【Hexo博客】配置RSS插件,生成 Atom1.0 或 RSS2.0 摘要文件 【业务实践】探索 Excel 表数据的导入和导出功能的完整实现方案 【狄仁杰探案】修复Node.js后端图片上传接口漏洞 【浅谈】关于B站博主“食贫道”发布充电视频《迷失东京》后所引发的热议 【Hexo博客】添加友链朋友圈,纵览好友最新文章 【webpack】如何解决Vue打包项目在浏览器开发者工具中显示源代码 【阿里云】对象存储 OSS 产品评测 【前端】常用加解密技术与使用方法 【蓝桥杯】第13届 Web 应用开发省赛真题解析
【蓝桥杯】第14届 Web 应用开发省赛真题解析
百里飞洋 Barry-Flynn · 2023-04-10 · via 百里飞洋

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

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

    .seat-area {
    margin-top: 50px;
    display: grid;
    grid-template-columns: repeat(8, auto);
    grid-gapgap: 10px;
    }


    .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

    .seat-area {
    margin-top: 50px;
    display: flex;
    flex-wrap: wrap;
    }


    .seat {
    margin-right: 10px;
    margin-bottom: 10px;
    }

    .seat:nth-child(8n) {
    margin-right: 0px;
    }

    .seat:nth-child(8n + 2) {
    margin-right: 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";



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



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



    const spans = `<span style="color: ${color}; opacity: ${opacity}; transform: rotate(${deg}deg);">${text}</span>`.repeat(count);

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

03 收集帛书碎片(10 分)

  • 考察:数组拍平、数组去重

  • 解题思路:入参是一个二维数组,转换为一维数组,然后再去重

  • 答案1:使用 concat() 方法拼接二维数组的子项,就相当于将这个二维数组拍平了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function collectPuzzle(...puzzles) {




    let result = [];
    for (let i = 0; i < puzzles.length; i++) {
    result = result.concat(puzzles[i]);
    }



    result = [...new Set(result)];
    return result;
    }
  • 答案2:循环遍历二维数组,手动 push 每个展开后的子项,也能达到拍平效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function collectPuzzle(...puzzles) {

    let 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) {

    let 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) {

    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();

      const arr2 = [1, 2, [3, 4, [5, 6]]];
      arr1.flat();

      const arr3 = [1, 2, [3, 4, [5, 6]]];
      arr1.flat(2);

      在上方的示例中,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;
    }


    #tutorials img {
    width: 100%;
    margin: 0;
    }
    }

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

  • @media 规则可以包含一个或多个条件,如媒体查询、宽度和高度、分辨率等,它们由关键字 and 连接。其中,最常用的条件是 max-widthmin-width,它们可以根据浏览器窗口的宽度来应用不同的样式。

    1
    2
    3
    4
    5
    6
    7

    @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>

    送餐速度:<el-rate v-model="speed" show-score></el-rate>
    </li>
    <li>

    外卖口味:<el-rate v-model="flavour" show-score></el-rate>
    </li>
    <li>

    外卖包装:<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>

    送餐速度:<el-rate v-model="speed" show-score @change="checkRate"></el-rate>
    </li>
    <li>

    外卖口味:<el-rate v-model="flavour" show-score @change="checkRate"></el-rate>
    </li>
    <li>

    外卖包装:<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,
    };
    },

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



    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", () => {


    let val = document.querySelector("#bulletContent").value;

    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",
"学习了",
"别走,奋斗到天明",
];








function renderBullet(bulletConfig, videoEle, isCreate = false) {
const spanEle = document.createElement("SPAN");
spanEle.classList.add(`bullet${index}`);
if (isCreate) {
spanEle.classList.add("create-bullet");
}

console.log(bulletConfig);

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", () => {


let val = document.querySelector("#bulletContent").value;

if (!val) {
return;
}

renderBullet(
{
...bulletConfig,
value: val,
},
videoEle,
true
);

document.querySelector("#bulletContent").value = "";
});

function getEleStyle(ele) {

return ele.getBoundingClientRect();
}

function getRandomNum(end, start = 0) {

return Math.floor(start + Math.random() * (end - start + 1));
}


let index = 0;
const videoEle = document.querySelector("#video");

const bulletConfig = {
isHide: false,
speed: 5,
time: 50,
value: "",
};
let isPlay = false;
let timer;
document.querySelector("#vd").addEventListener("play", () => {

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", () => {


let val = document.querySelector("#bulletContent").value;

if (!val) {
return;
}

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


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

  • 目标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


    const data = [];

    let cursor = 15;

    $.ajax({
    url: "./js/all-data.json",
    type: "get",
    success: (result) => {

    data.push(...result);

    data.slice(0, 15).forEach((item) => {
    $(".list > ul").append(
    createProjectItem({ ...item, description: item.descriptionCN })
    );
    });
    },
    });


    $.ajax({
    url: "./js/translation.json",
    type: "get",
    success: (result) => {

    translation = result;
    },
    });





    $(".load-more").click(() => {
    console.log(data.length);

    if(cursor < data.length){
    data.slice(cursor, (cursor + 15)).forEach((item) => {
    $(".list > ul").append(
    createProjectItem({ ...item, description: item.descriptionCN })
    );
    });
    cursor += 15;
    }



    if(cursor === data.length){
    $(".load-more").hide();
    }
    });


  • 目标3:当⽤户点击页面右上⽅的中英文切换按钮时,根据用户的选择改变项目描述使⽤的语言(不改变原有项目展示数量)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16



    $(".list > ul").empty();


    data.slice(0, cursor).forEach((item) => {

    $(".list > ul").append(
    createProjectItem({
    ...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";



const data = [];

let cursor = 15;

$.ajax({
url: "./js/all-data.json",
type: "get",
success: (result) => {

data.push(...result);

data.slice(0, 15).forEach((item) => {
$(".list > ul").append(
createProjectItem({ ...item, description: item.descriptionCN })
);
});
},
});


$.ajax({
url: "./js/translation.json",
type: "get",
success: (result) => {

translation = result;
},
});


















$(".load-more").click(() => {
console.log(data.length);

if(cursor < data.length){
data.slice(cursor, (cursor + 15)).forEach((item) => {
$(".list > ul").append(
createProjectItem({
...item,

description: currLang === "zh-cn" ? item.descriptionCN : item.descriptionEN,
})
);
});
cursor += 15;
}



if(cursor === data.length){
$(".load-more").hide();
}
});




$(".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]);
}
});



$(".list > ul").empty();


data.slice(0, cursor).forEach((item) => {

$(".list > ul").append(
createProjectItem({
...item,

description: currLang === "zh-cn" ? item.descriptionCN : item.descriptionEN,
})
);
});
});









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

    mounted() {

    axios.get("./js/covid-data.json").then((res) => {

    this.countrys = res.data.map((item) => item.Country);

    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

    <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() {

    if (this.currentCountry) {

    this.currentData = this.allData.find(
    (item) => item.Country === this.currentCountry
    )
    } else {

    this.currentData = null;
    }

    },
    },
  • 目标3:⻚⾯底部的 ECharts 图表希望显示各个国家的累计确诊⼈数,请修改 initChart 函数的内容, 使得图表 x 轴数据为国家简称,y 轴数据为累计确诊⼈数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    xAxis: {


    data: this.allData.map((item) => item.CountryCode),

    },

    series: [
    {




    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>

<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: {

initChart() {

this.chart = echarts.init(document.getElementById("chart"));
this.chartOptions = {
title: {
text: "全球感染人数前30国家累计确诊人数统计",
x: "center",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
label: {
show: true,
},
},
},

xAxis: {


data: this.allData.map((item) => item.CountryCode),
axisLabel: {
show: true,
interval: 0,
},
},
yAxis: {
type: "value",
name: "确诊数量",
},

series: [
{




data: this.allData.map((item) => item.TotalConfirmed),
type: "bar",
itemStyle: {
normal: {
color: "#a90000",
},
},
},
],
};

this.chart.setOption(this.chartOptions);
},

selectChange() {

if (this.currentCountry) {

this.currentData = this.allData.find(
(item) => item.Country === this.currentCountry
)
} else {

this.currentData = null;
}

},
},

mounted() {

axios.get("./js/covid-data.json").then((res) => {

this.countrys = res.data.map((item) => item.Country);

this.allData = res.data;
this.initChart();
});
},
});
</script>
</html>

09 Markdown 文档解析(25 分)

  • 考察点:Nodejs简单正则replace 方法替换字符串

  • 目标1:对分隔符进⾏解析

    1
    2

    this.hr = /^(\-{3,})/;
    1
    2
    3
    4
    5
    6
    7

    isHr() {
    return this.hr.test(this.lineText);
    }
    parseHr() {
    return "<hr/>";
    }
  • 目标2:对引⽤区块进⾏解析

    1
    2
    3
    4
    5
    6
    7
    8

    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

    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

    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


    isStrongText() {
    return this.strongText.test(this.lineText);
    }
    parseStrongText(str) {
    return str.replace(this.strongText, (result, str1) => {
    return `<b>${str1}</b>`;
    });
    }

    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;



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

    res = data.data;

    result = res.data;

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

    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>
    `);
    }, "");


    }

    解题思路:写一个递归函数,用于根据传入的数据 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;



    const getAndDeleteDragLabelObj = (dragElement, data) => {
    let result;

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

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

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

    return false;
    }
    let pre = isbn.slice(0, 9);
    let last = 0;
    for (let i = 0; i < pre.length; i++) {

    last += (i + 1) * parseInt(pre[i]);
    }
    last % = 11;
    if (last === 10) {

    last = "X";
    }
    if (isbn.slice(-1) == last) {

    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);
    let last = 0;
    for (let i = 0; i < pre.length; i++) {

    if (i % 2 === 0) {
    last += parseInt(pre[i]);
    } else {
    last += parseInt(pre[i]) * 3;
    }
    }
    pre += last % 10 !== 0 ? 10 - (last % 10) : 0;
    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 = ``;

    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 选项指定组件的名称,这样才能在组件的模板中嵌套自身组件。

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 百里飞洋

技术内容: 若存在错误或不当之处,还望兄台不吝赐教,期待与您交流!

打赏

  • WeChat微信打赏

    WeChat微信打赏

  • AliPay支付宝打赏

    AliPay支付宝打赏