2022 年第十三届蓝桥杯 Web 国赛真题解析
- 2022 年 9 月 17 日 河南
本文字数:13616 字
阅读完需:约 45 分钟
前言
省赛真题解析见:
之前写省赛解析时篇幅过长,写的花里胡哨的,导致文章阅读体验不好,这次就不整那些了,直接贴代码,解析都写在代码注释里了,相信各位老大稍微思考一下就能够理解了,同样的,相应的真题代码也会分享给大家:
「蓝桥杯」https://www.aliyundrive.com/s/7fsobhSy8dZ 提取码: 34pi
如果在阅读文章时大佬有好的见解,或者发现了问题,还请多多留言,互相探讨
1、分一分(5 分)
/**
* @param {Object} oldArr
* @param {Object} num
* */
const splitArray = (oldArr, num) => {
// TODO:请补充代码实现功能
let newArr = [];
// 升序排序后解构赋值深拷贝给oldArr2
let oldArr2 = [...oldArr.sort((a, b) => a - b)];
const len = oldArr2.length;
for (let i = 0, j = 0; i < len; i += num, j++) {
// splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
newArr[j] = oldArr2.splice(0, num);
}
return newArr;
};
module.exports = splitArray; // 检测需要,请勿删除
2、新鲜的蔬菜(5 分)
/* TODO:待补充代码 */
#box1 {
display: flex;
justify-content: center;
align-items: center;
}
#box2,
#box3 {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#box2 span:nth-child(2),
#box3 span:nth-child(3) {
align-self: flex-end;
}
#box3 span:nth-child(2) {
align-self: center;
}
3、水果消消乐(10 分)
// TODO:请补充代码
function startGame() {
$("img").show(500);
$("#start").hide();
$("img").hide(500);
// 已经点击的数量
let i = 0;
// 存放已经点击的元素
let clickImg = [];
[...$(".img-box")].forEach((item) => {
item.onclick = function () {
i++;
if (i <= 2) {
$(item.children).show();
clickImg.push(item);
if (i === 2) {
setTimeout(() => {
let score = $("#score")[0];
if (
clickImg[0].children[0].alt ===
clickImg[1].children[0].alt
) {
score.innerHTML = Number(score.innerHTML) + 2;
$(clickImg[0]).css({
// 隐藏元素且保留元素所占位置
visibility: "hidden",
});
$(clickImg[1]).css({
visibility: "hidden",
});
} else {
score.innerHTML = Number(score.innerHTML) - 2;
$(clickImg[0].children[0]).hide();
$(clickImg[1].children[0]).hide();
}
clickImg = [];
i = 0;
}, 400);
}
}
};
});
}
4、用什么来做计算 A (10 分)
// TODO:请补充代码
const btn = document.getElementsByClassName("calc-button");
const formula = document.getElementById("formula");
const result = document.getElementById("result");
// 计算机表达式展示
let showText = "";
// 计算结果
let num = "";
[...btn].forEach((item) => {
item.onclick = function () {
switch (this.id) {
// 点击 =
case "equal":
// eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。
num = eval(showText.replace("x", "*").replace("÷", "/"));
result.value = num;
return;
// 点击 √
case "sqrt":
num = eval(showText.replace("x", "*").replace("÷", "/"));
num = Math.sqrt(num);
result.value = num;
return;
// 点击 AC
case "reset":
showText = "";
num = "";
formula.value = showText;
result.value = num;
return;
default:
showText += this.innerHTML;
formula.value = showText;
return;
}
};
});
5、开学礼物大放送(15 分)
考察的只是简答的页面实现,每个人的实现方式不同,代码差异也很大,这里就不放代码了
6、权限管理(15 分)
$(function () {
// 使用 ajax 获取 userList.json 数据并渲染到页面
getData();
// 为按钮添加事件
$("#add").click(function () {
// TODO:补充代码,实现功能
// 获取选中的option
let option = $("#leftSelect option:selected");
// jQ方法:each() 遍历jQ获取的节点
option.each((index, item) => {
// 删除左侧对应的option
$(`#leftSelect option[value=${item.value}]`).remove();
// 向右侧添加option
$("#rightSelect")[0].add(new Option(item.value, item.value));
});
changeAccess("管理员", option);
});
$("#addAll").click(function () {
// TODO:补充代码,实现功能
let option = $("#leftSelect option");
option.each((index, item) => {
$(`#leftSelect option[value=${item.value}]`).remove();
$("#rightSelect")[0].add(new Option(item.value, item.value));
});
changeAccess("管理员", option);
});
$("#remove").click(function () {
// TODO:补充代码,实现功能
let option = $("#rightSelect option:selected");
option.each((index, item) => {
$(`#rightSelect option[value=${item.value}]`).remove();
$("#leftSelect")[0].add(new Option(item.value, item.value));
});
changeAccess("普通用户", option);
});
$("#removeAll").click(function () {
// TODO:补充代码,实现功能
let option = $("#rightSelect option");
option.each((index, item) => {
$(`#rightSelect option[value=${item.value}]`).remove();
$("#leftSelect")[0].add(new Option(item.value, item.value));
});
changeAccess("普通用户", option);
});
});
/**
* 修改权限
* @param {Object} right 要修改的权限
* @param {Object} changeList 要修改权限的用户列表
*/
function changeAccess(right, changeList) {
// TODO:补充代码,实现功能
changeList.each((index, item) => {
// 将option.value与tr.name对应,找到对应的td并修改其内容
// jQ方法::last 获取最后个元素
$(`#userList tr[name=${item.value}] td:last`).html(right);
});
}
// 异步获取数据
function getData() {
// TODO:补充代码,实现功能
$.ajax("./js/userList.json").then((res) => {
res.forEach((item) => {
// jQ方法:html() 设置html内容
$("#userList tbody").html(
$("#userList tbody").html() +
` <tr name=${item.name}>
<td>${item.name}</td>
<td>${item.right ? "管理员" : "普通用户"}</td>
</tr>`
);
});
});
}
7、一起会议吧(20 分)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>一起会议吧</title>
<link rel="stylesheet" type="text/css" href="./css/index.css" />
<link rel="stylesheet" href="./css/iconfont/iconfont.css" />
</head>
<body>
<div id="app">
<!-- TODO:请在下面实现需求 -->
<!-- 登录/注销窗口 -->
<div class="login">
<div class="left-tools">
<a class="close-btn"></a>
<a class="shrink-btn"></a>
</div>
<h3>{{isLogin?'注销':'登录'}}</h3>
<p v-if="!isLogin">
选择用户:<select id="selectUser" @change="changeOption($event)">
<option :value="item.id" v-for="item in list" :key="item.id">{{item.name}}</option>
</select>
</p>
<p v-else>当前用户为:{{loginUser.name}}</p>
<a class="login-btn" @click="btn">{{isLogin?'注销':'登录'}}</a>
</div>
<!-- 右侧显示用户列表窗口按钮 -->
<button id="show" class="right-btn" v-if="!showUser&&isLogin" @click="showUser=true">
<span class="iconfont icon-left-arrow"></span>
</button>
<!-- 用户列表窗口 -->
<div class="user-dialog" v-if="isLogin&&showUser">
<!-- 用户列表窗口上侧工具栏 -->
<ul class="tools">
<li class="tools-left">
<button :class="{'active':isButton<0}" @click="isButton=-1">
<span class="iconfont icon-close"></span>
</button>
<button :class="{'active':isButton=='0'}" @click="isButton=0">
<span class="iconfont icon-dialog"></span>
</button>
<button :class="{'active':isButton>0}" @click="isButton=1">
<span class="iconfont icon-list"></span>
</button>
</li>
<li class="tools-right">
<button class="show-list" @click="showUser=false">
<span class="iconfont icon-retract"></span>
</button>
</li>
</ul>
<!-- 用户列表 -->
<ul class="say-list">
<li>
<span class="iconfont icon-microphone"></span>
</li>
<li class="line"></li>
<li>正在讲话:{{list.find(item=>item.isHost).name}};</li>
</ul>
<ul class="user-list">
<li v-show="isButton>=0">
<img class="header" :src="loginUser.imgPath" />
<div class="user-name">
<span class="iconfont icon-user header-icon" v-if="loginUser.isHost"></span>
<span class="iconfont icon-microphone"></span> {{loginUser.name}}
</div>
</li>
<li v-for="item in list" :key="item.id" v-if="item.id!==loginUser.id" v-show="isButton>0">
<img class="header" :src="item.imgPath" />
<div class="user-name">
<span class="iconfont icon-user header-icon" v-if="item.isHost"></span>
<span class="iconfont icon-microphone"></span> {{item.name}}
</div>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="./js/vue.js"></script>
<script type="text/javascript" src="./js/axios.min.js"></script>
<script type="text/javascript">
// TODO:请在下面实现需求
new Vue({
el: "#app",
data: {
// 用户列表
list: [],
// 选择用户id
value: '1',
// 是否登录
isLogin: false,
// 是否显示用户参会窗口
showUser: true,
// 登录的用户信息
loginUser: {},
// 用户列表显示效果切换的状态
isButton: 0
},
created() {
axios.get('./js/userList.json').then(res => {
this.list = res.data
})
},
methods: {
btn() {
this.isLogin = !this.isLogin
if (this.isLogin) {
this.loginUser = this.list.find(item => item.id == this.value)
} else {
this.loginUser = {}
this.value = '1'
this.isButton = 0
this.showUser = true
}
},
// 选择下拉框用户时
changeOption(e) {
this.value = e.target.value
}
}
});
</script>
</body>
</html>
8、天气趋势 A (20 分)
<!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" />
<script src="./js/axios.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>
</head>
<body>
<div id="app">
<div class="top-bar">2022年 Y 城全年温度统计图</div>
<!-- 主体 -->
<div class="container">
<!-- 月份 -->
<div class="month">
<ul>
<!-- TODO:待补充代码 在下面的 li 标签中完成 12个月份 (即 monthList) 的渲染 -->
<!-- 选择月份的英文名称等于数据中的key,代表这条数据选中 -->
<li v-for="(item,key,index) in monthList" :class="[monthName===key?'active':'']" @click="changeMonth(key,index)">
{{item}}</li>
</ul>
</div>
<div class="chart">
<!-- TODO:待补充代码 -->
<!-- currentMonth 未来七天和本月 tab 切换,只有当前月才显示 -->
<!-- 选择月份的序号(对应月份-1)等于当前月份的标志(当前月份-1)时显示 -->
<div id="currentMonth" v-if="monthNumber===nowMonth">
<div class="title">
<h3>{{typeTitle}}</h3>
<div class="type" @click="clickType($event)">
<span id="seven" :class="{'active':!type}">未来7天</span>
<span id="current" :class="{'active':type}">本月</span>
</div>
</div>
</div>
<div id="chart"></div>
</div>
</div>
</div>
</body>
</html>
<script>
// TODO:待补充代码
var vm = new Vue({
el: "#app",
data: {
chart: null, // 图表
chartOptions: null, // 图表配置项
typeTitle: "本月天气",
monthList: {
January: "1月",
February: "2月",
March: "3月",
April: "4月",
May: "5月",
June: "6月",
July: "7月",
August: "8月",
September: "9月",
October: "10月",
November: "11月",
December: "12月",
},
// 请求到的全部数据
data: [],
// 选择月份的英文名称
monthName: 'January',
// 选择月份的序号(对应月份-1)
monthNumber: 0,
// 当前月份的标志(当前月份-1)
nowMonth: new Date().getMonth(),
// x轴,y轴数据
yData: [],
xData: [],
// 未来7天和本月数据的状态
type: 1
},
mounted: async function() {
const res = await axios.get('./js/weather.json');
this.data = res.data;
// 初始化x轴和y轴数据
this.yData = res.data[0]['January'];
// 根据y轴温度数据条数的多少判断x轴有多少天
this.xData = [...this.yData.map((e, i) => i + 1)];
// 初始化 echarts
this.$nextTick(() => {
this.initChart();
});
},
methods: {
initChart() {
// 初始化图表
this.chart = echarts.init(document.getElementById("chart"));
// 配置项
this.chartOptions = {
grid: {
top: 35,
bottom: 5,
left: 10,
right: 10,
containLabel: true,
},
tooltip: {
trigger: "axis",
axisPointer: {
lineStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: "rgba(255,255,255,0)",
}, {
offset: 0.5,
color: "rgba(255,255,255,1)",
}, {
offset: 1,
color: "rgba(255,255,255,0)",
}, ],
global: false,
},
},
},
},
xAxis: [{
type: "category",
boundaryGap: false,
axisLabel: {
formatter: "{value}",
fontSize: 12,
margin: 20,
textStyle: {
color: "#bfbfbf",
},
},
axisLine: {
lineStyle: {
color: "#e9e9e9",
},
},
splitLine: {
show: true,
lineStyle: {
color: "#f7f7f7",
},
},
axisTick: {
show: false,
},
// x 轴显示的数据,日期
data: this.xData,
}, ],
yAxis: [{
boundaryGap: false,
type: "value",
axisLabel: {
textStyle: {
color: "#bfbfbf",
},
formatter: `{value}\u2103`,
},
nameTextStyle: {
color: "#fff",
fontSize: 12,
lineHeight: 40,
},
splitLine: {
lineStyle: {
color: "#f7f7f7",
},
},
axisLine: {
show: true,
lineStyle: {
color: "#e9e9e9",
},
},
axisTick: {
show: false,
},
}, ],
series: [{
name: "天气",
type: "line",
smooth: false,
showSymbol: false,
symbolSize: 0,
zlevel: 3,
itemStyle: {
color: "#ff6600",
borderColor: "#a3c8d8",
},
lineStyle: {
normal: {
width: 3,
color: "#ff6600",
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1, [{
offset: 0,
color: "#ff6600",
}, {
offset: 0.8,
color: "#ff9900",
}, ],
false
),
},
},
// Y 轴显示的数据,即温度数据
data: this.yData,
}, ],
};
// 调用此方法设置 echarts 数据
this.chart.setOption(this.chartOptions);
},
// 切换月份
changeMonth(month, index) {
this.monthName = month;
this.monthNumber = index;
this.yData = this.data[index][month];
this.xData = [...this.yData.map((e, i) => i + 1)];
// 如果切换到了当前月份,将type状态重置以下
if (index === this.nowMonth) {
this.type = 1
}
this.initChart()
},
// 切换未来7天和本月
clickType(e) {
switch (e.target.id) {
case "seven":
this.type = 0;
// 解构赋值更新x,y坐标轴的数据
[this.xData, this.yData] = this.getSevenData();
break;
case "current":
this.type = 1
this.yData = this.data[this.monthNumber][this.monthName]
this.xData = [...this.yData.map((e, i) => i + 1)]
break;
}
this.initChart();
},
// 获取未来七天数据
getSevenData() {
let newXdata = [],
newYdata = [];
// 利用循环一天一天的获取
for (let i = 0; i < 7; i++) {
// 当前时间
let now = new Date();
// now.getTime()为当前时间戳 time为需要获取的那天的时间戳
let time = now.getTime() + 1000 * 60 * 60 * 24 * i;
// 用setTime转换,将now转换为需要获取的那天的时间
// now.setTime(time)既是设置now的日期为:从1970.1.1开始走过time毫秒后的日期
now.setTime(time);
newXdata.push(`${now.getMonth() + 1}/${now.getDate()}`)
if (this.monthNumber === now.getMonth()) {
// 如果当前选择的月份与获取那天的时间所处月份一样,则直接取当前y轴里的数据
newYdata.push(this.yData[now.getDate() - 1])
} else {
// 如果当前选择的月份与获取那天的时间所处月份不同,说明进入到了下一月份,取下一月份的数据
let nextMonth = this.data[now.getMonth()];
// 因为不确定月份数据里的key是啥,不能直接取,所以采用for in遍历对象间接获取数据
for (const key in nextMonth) {
newYdata.push(nextMonth[key][now.getDate() - 1])
}
}
}
return [newXdata, newYdata]
}
},
});
</script>
9、JSON 生成器(25 分)
这个我是真的不会。。。在线求大佬解答
10、商城管理系统(25 分)
// menuList 仅为示例数据,非实际使用数据,实际使用数据层级不确定(可能是四级五级六级等),数据结构与 menuList 一致
// 1. `parentId` 如果为 `-1`,则表示此条数据为顶级数据。
// 2. `parentId` 为该条数据的父级数据的 `id`。
let menuList = [
{ parentId: -1, name: "添加管理员", id: 10, auth: "admin" },
{ parentId: 10, name: "管理员权限分配", id: 11, auth: "admin-auth" },
{ parentId: -1, name: "商品管理", id: 1, auth: "product" },
// { parentId: 6, name: "赤门网络暗杀速度还等哈说", id: 12, auth: "product" },
{ parentId: 1, name: "商品列表", id: 4, auth: "productList" },
{ parentId: 4, name: "商品分类", id: 5, auth: "category" },
{ parentId: 5, name: "添加分类", id: 8, auth: "addClassification" },
{ parentId: 4, name: "商品上架", id: 6, auth: "product" },
{ parentId: -1, name: "评论管理", id: 2, auth: "comments" },
{ parentId: -1, name: "个人中心", id: 3, auth: "profile" },
];
/**
* @param {*} menuList 传入的数据
* @return {*} menus 转化后的树形结构数据,auths 转化后的权限列表数组
*/
const getMenuListAndAuth = (menuList) => {
// TODO:待补充代码
let menus = [],
auths = [],
// emenus存放那些还未找到上级(未被push的项)
emenus = [],
// 项是否被落下(是否未被push)
isDiscard = true;
// 创建一个能够递归查找push的函数
function addChildren(fat, chil) {
fat.forEach((fatItem) => {
if (fatItem.id === chil.parentId) {
fatItem.children.push(chil);
// 只要项被push了,说明它没有被落下,立即修改isDiscard
isDiscard = false;
return;
} else {
// 递归
addChildren(fatItem.children, chil);
}
});
}
// 遍历原数据
menuList.forEach((item) => {
// 每一次遍历数据时都提前默认isDiscard为true,因为此时数据还未被push
isDiscard = true;
item.children = [];
auths.push(item.auth);
if (item.parentId === -1) {
menus.push(item);
isDiscard = false;
} else {
addChildren(menus, item);
// 一次遍历结束了,如果isDiscard还为true,即还未被push
// 说明这一项的父级还没有出现,为了防止这一项丢失,将它暂存到emenus中
if (isDiscard) {
emenus.push(item);
}
}
});
// 转换的过程结束了,如果emenus不为空,说明有项被丢弃落下了
// 项被落下的原因是:它的父级比它出现的晚,在顺序遍历到它时,因前面没有它的父级,导致它无法被接收
if (emenus.length !== 0) {
// 这时其它项已经全部加入menus,此时再次将emenus中被落下的项向menus中添加肯定能加上
emenus.forEach((item) => {
addChildren(menus, item);
});
}
return { menus, auths }; // menus 转化后的树形结构数据,auths 转化后的权限列表数组
};
// 请勿删除和修改以下代码
try {
module.exports = { getMenuListAndAuth };
} catch (e) {}
总结心得
如果说之前省赛是一场大水,这次国赛就是实实在在的旱田了,不含一点水分(当时考的时候是这样感觉的),还记得当时刚开考看到第一题先要排序,我立马就想到了数组原型上有自带的排序方法,但讽刺的是我忘记了它是哪个,log
打印了Array.prototype
,然后看着哪个像我就试哪一个,但最终一看时间已经过去半小时了,我急了,不得已采用冒泡排序这种笨但很简单的方法,就这样我在想api
上浪费了太多时间,究其原因还是我的基础不够牢固。
因为省赛太过水了,考前一直感觉可能国赛也就那样吧,考试的时候我也是不慌不忙,慢慢的做,遇到有好法子能实现但是一时半刻想不起 api 的我也不跳过,就一直在那想,可时间就这样拖着拖着就没了,当我真正意识到时间不够了,不能再拖的时候,已经晚了。
哎,现在回过头重新做了一遍题,发现我写最后一题的时间还没有考试时在第一题上浪费的时间一半多,考试时因为最后时间不够,最后一题压根就没看,真是讽刺啊。
感谢这次蓝桥杯的经历,让我真正认识到了自己的不足和努力的方向。
谨以此篇记录我这一段失败的经历
版权声明: 本文为 InfoQ 作者【海底烧烤店ai】的原创文章。
原文链接:【http://xie.infoq.cn/article/b77143764c2e1b770401ae7f0】。文章转载请联系作者。
海底烧烤店ai
前端之行,任重道远! 2022.08.25 加入
本科大三学生、CSDN前端领域新星创作者、华为云享专家、第十三届蓝桥杯国赛三等奖获得者
评论