当前位置: 首页 > news >正文

第十六届 -- 蓝桥杯Web开发大学组省赛个人复盘

文章目录

    • 1. 结论
    • 2. 总体分析
    • 3. 题目解析
      • 3.1 精英云课堂
      • 3.2 沉浸阅读
      • 3.3 二维码生成器
      • 3.4 图形设计工具
      • 3.5 欧洲杯顶级球员数据分析
      • 3.6 内存优化之一键清理垃圾文件
      • 3.7 新闻中心
    • 4. 总结

1. 结论

先说结论,web组难度分布一般为前6~7题为简单,基础扎实就没啥问题。最后三道题个人觉得也没啥难度,但是实现过程相对复杂(我还是太拉了,比赛的时候居然没来得及写完)。

因为是所有本科大学一起比,作为双非文科院校的渣滓,出成绩前有点担心新疆其他两所211高校可能存在高手抢夺省一道名额。不过看来我的担心还是多余了,拿了省一(顺手摘了第一☝️),下面来复盘一下这次的省赛。

2. 总体分析

试题序号、试题名称及基础源代码文件夹名称对应如下:

  1. 精英云课堂(5 分)
  2. 沉浸阅读(5 分)
  3. 二维码生成(10 分)
  4. 图形设计工具(10 分)
  5. 欧洲杯顶级球员数据分析(15 分)
  6. 内存优化之一键清理垃圾文件(15 分)
  7. 新闻中心(20 分)
  8. Github 身份验证(20 分)
  9. 大事件活动日历(25 分)
  10. 批量导入(25 分)

从难题的分布可以看出,从第5~6道Node的题目开始,就进入了中等题的难度范围。

比较麻烦的几道分别是:

  • 内存优化之一键清理垃圾文件
  • 新闻中心
  • Github 身份验证
  • 大事件活动日历
  • 批量导入

先来大致讲讲这几道题目考了什么吧。

  1. 内存优化这道题我大致记得主要考察的是Node.js的文件操作fs模块,而且和之前差不多,考察了文件夹的递归。

我记得当时看到这道题时我的嘴角不自觉的上扬了,因为这道题在近三年的国赛还是省赛题目中考过基本一样思路的。

  1. 新闻中心这道题目考察了Ajax请求 和数据的处理。数据处理实际上只要JS基础扎实就没啥问题。相对来说比较常规。
  2. Github身份验证这道题目我记得就比较清楚了,第一问考察了随机数的生成也就是Math.random()函数的使用;第二问考的是Pinia传值;第三问没什么难度。
  3. 大事件活动日历这道题目,考察了ElementPlus框架的使用,并且涉及了Vue具名插槽的使用方法。第三问则考了计算组件Computed
  4. 批量导入是最后一道题目,也是最不好写的一道。考察的也很纯粹,就是Js的综合考察。要求也很明显,想要快速做出这道题必须有较强的用Js 处理数组、对象等大量数据的能力。还要能够快速理解题目的要求。

根据这些题目可以整理出本次难题的标签列表:Echartfs模块Ajax请求Js数据处理ElementPlusVue3PiniaJs.Math类

3. 题目解析

下面贴出来的代码不一定正确,是我比赛的时候自己写的,仅供参考。

3.1 精英云课堂

TODO代码:

/* TODO:待修改代码 START*/
.container {flex-grow: 1; margin: 20px;border-radius: 8px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);display: flex;
}
main {flex: 1;padding: 20px;background-color: #ffffff;border-radius: 8px;margin: 10px;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}.left-sidebar, .right-sidebar {background-color: #ffffff;padding: 20px;width: 220px;box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);border-radius: 8px;margin: 10px;
}
/* TODO:待修改代码 END */

很简单,会flex布局 就行了。

3.2 沉浸阅读

js/index.js中的代码:

function HideDom() {// TODO:待补充代码const buttons = document.querySelectorAll('.panel-btn')const recommend = document.querySelector('#recommend')const authorblock = document.querySelector('#authorblock')let flag = (buttons[0].style.display == '' || buttons[0].style.display == 'block');if (flag) {for (let i = 0; i < 4; i++) {let btn = buttons[i];btn.style.display = 'none'recommend.style.display = 'none'authorblock.style.display = 'none'}} else {for (let i = 0; i < 4; i++) {let btn = buttons[i];btn.style.display = 'block'recommend.style.display = 'block'authorblock.style.display = 'block'}}
}

比较简单,会写Javascript 就行。

3.3 二维码生成器

js/index.js 中的代码:

function generateQRCode() {// 获取文本输入框的值并去除前后空格const text = document.getElementById('text-input').value.trim();// 获取二维码容器元素const qrcode = document.getElementById('qrcode');let tip = document.querySelector('.tip');// 获取二维码容器的外层容器const qrcodeContainer = document.querySelector('.qrcode-container');// 获取错误信息容器const errorMessageContainer = document.querySelector('.input-container');tip.style.display = 'none';// 检查并移除可能存在的错误信息// TODO:目标 1 let hasErr = errorMessageContainer.lastChild.tagName == 'P';if (text == '' && !hasErr) {const p = document.createElement('p');p.innerText = '请输⼊⽂本内容'p.style.color = '#ff0000';errorMessageContainer.appendChild(p)} else if (text != '' && hasErr) {let errNode = errorMessageContainer.lastChild;errorMessageContainer.removeChild(errNode);}// TODO:目标 1 ENDif (text.length === 0) {return;}// TODO:目标 2const strLength = text.length;let qrsize = 1; // TODO 待修改代码 目标 2 // 设置容器样式if (strLength*20 <= 200) {qrsize = 200} else if (strLength*20 <= 300) {qrsize = (strLength*20)} else {qrsize = 300}qrcodeContainer.style.width = qrsize + 'px'qrcodeContainer.style.height = qrsize + 'px'if (strLength > 5) {qrcodeContainer.style.border = '6px solid #ff69b4'} else {qrcodeContainer.style.border = '6px solid #00bfff'}//  TODO:目标 2 END // 创建 QRious 实例const qr = new QRious({element: qrcode,value: text,size:qrsize,background: "white"});
}

个人感觉相比前两题繁琐度上去一些,但整体还是没啥难度🤔,考的也很简单:Javascript

Dom操作。

3.4 图形设计工具

js/index.js 中的代码:

// 工具栏的所有图形
const graphicalArr = document.querySelectorAll('.graphical');
// 切换颜色
const colorArr = document.querySelectorAll('.color');
// 展示图层的画布
const canvas = document.querySelector('main');
// 图层管理器的dom
const layerManagerBox = document.getElementById('layer-manager');class LayerManager {constructor() {// 图层序列this.layers = [{id: 1,visible: true,name: '图层1',active: true,content: []},{id: 2,visible: true,name: '图层2',active: false,content: []}];// id计数器this.count = 2;}// 修改图层内容createContent(content) {// 获取当前选中的图层const layer = this.layers.find((layer) => layer.active);layer.content.push(content);}// 添加图层addLayer(id) {const newId = ++this.count; // 新图层的 ID// TODO:待补充代码let idx = this.layers.findIndex(item => item.id == id)this.layers.splice(idx, 0, {id: newId,visible: true,name: '图层'+newId,active: false,content: []})// TODO:ENDthis.selectLayer(newId); // 选中图层}// 删除图层removeLayer(id) {// TODO:待补充代码if (this.layers.length == 1) return;let idx = this.layers.findIndex(item => item.id == id)this.layers.splice(idx, 1);}// 向上移动moveLayerUp(id) {// TODO:待补充代码let idx = this.layers.findIndex(item => item.id == id);this.layers.splice(idx-1, 0, this.layers[idx])this.layers.splice(idx+1, 1)}// 向下移动moveLayerDown(id) {// TODO:待补充代码let idx = this.layers.findIndex(item => item.id == id);this.layers.splice(idx+2, 0, this.layers[idx])this.layers.splice(idx, 1)}// 修改可见性toggleLayerVisibility(id) {const layer = this.layers.find((layer) => layer.id === id);if (layer) {layer.visible = !layer.visible;}}// 选中图层selectLayer(id) {this.layers.forEach((layer) => {layer.active = layer.id === id;});}
}// 图层管理器实例
const layerManager = new LayerManager();
// 渲染页面
function renderLayers() {layerManagerBox.innerHTML = '';canvas.innerHTML = '';// 渲染图层管理器layerManager.layers.forEach((layer) => {// 图层卡片const layerDiv = document.createElement('div');layerDiv.classList.add('layer-card');layer.active && layerDiv.classList.add('layer-card_active');layerDiv.addEventListener('click', () => {layerManager.selectLayer(layer.id);renderLayers();});// 修改可见性的眼睛const eyeIcon = document.createElement('span');eyeIcon.classList.add('visible');eyeIcon.innerText = layer.visible ? '👁️' : '🚫';eyeIcon.addEventListener('click', () => {layerManager.toggleLayerVisibility(layer.id);renderLayers();});// 图层名称const layerName = document.createElement('span');layerName.classList.add('layer-name');layerName.innerText = layer.name;// 图层操作区域const operatesBox = document.createElement('div');operatesBox.classList.add('operates');// 向上按钮按钮const upButton = document.createElement('img');upButton.setAttribute('src', './images/arrowToTop.png');upButton.addEventListener('click', (e) => {e.stopPropagation();layerManager.moveLayerUp(layer.id);renderLayers();});// 向下移动按钮const downButton = document.createElement('img');downButton.setAttribute('src', './images/arrowToBottom.png');downButton.addEventListener('click', (e) => {e.stopPropagation();layerManager.moveLayerDown(layer.id);renderLayers();});// 删除按钮const deleteButton = document.createElement('img');deleteButton.setAttribute('src', './images/delete.png');deleteButton.addEventListener('click', (e) => {e.stopPropagation();layerManager.removeLayer(layer.id);renderLayers();});// 新增按钮const addButton = document.createElement('img');addButton.setAttribute('src', './images/add.png');addButton.addEventListener('click', (e) => {e.stopPropagation();layerManager.addLayer(layer.id);renderLayers();});operatesBox.appendChild(upButton);operatesBox.appendChild(downButton);operatesBox.appendChild(deleteButton);operatesBox.appendChild(addButton);layerDiv.appendChild(eyeIcon);layerDiv.appendChild(layerName);layerDiv.appendChild(operatesBox);layerManagerBox.appendChild(layerDiv);});//渲染图层for (let i = layerManager.layers.length - 1; i >= 0; i--) {const layer = layerManager.layers[i];const layerCanvas = document.createElement('div');layerCanvas.classList.add('layer');if (!layer.active) layerCanvas.style.pointerEvents = 'none';if (layer.content && layer.content.length && layer.visible) {for (let j = 0; j < layer.content.length; j++) {const dom = layer.content[j];dom.onmousedown = function () {if (!layer.active) return;moveElement = this;};dom.onmouseup = function () {moveElement = null;};layerCanvas.appendChild(dom);}}canvas.appendChild(layerCanvas);}
}// 初始化渲染
renderLayers();// 当前正在拖拽移动的dom元素
var moveElement;
for (let i = 0; i < graphicalArr.length; i++) {const item = graphicalArr[i];item.ondragstart = function (e) {// console.log(e);// offsetX;e.dataTransfer.setData('offsetX', e.offsetX);e.dataTransfer.setData('offsetY', e.offsetY);moveElement = e.target.cloneNode(true);moveElement.setAttribute('draggable', false);};item.ondragend = () => {};
}
// 拖入画布后实现拖拽移动
document.addEventListener('mousemove', function (e) {setElementPosition(20, 20);
});
canvas.addEventListener('mouseleave', function (e) {moveElement = null;
});// 图形拖入画布后向当前选中的图层添加图形
canvas.ondrop = function (e) {e.preventDefault();if (!moveElement) return;// 获取鼠标相当于正在拖拽的图形元素的偏移位置const offsetX = +(e.dataTransfer.getData('offsetX') || 0);const offsetY = +(e.dataTransfer.getData('offsetY') || 0);// 设置图形相对于画布的位置setElementPosition(offsetX, offsetY);layerManager.createContent(moveElement);renderLayers();moveElement = null;
};// 设置图形相对于画布的位置 offsetX offsetY 鼠标相对于拖拽元素的偏移量
function setElementPosition(offsetX, offsetY) {if (!moveElement) return;// 获取鼠标相对于整个窗口的坐标const mouseX = event.clientX;const mouseY = event.clientY;// 获取画布在屏幕的位置const rect = canvas.getBoundingClientRect();// 计算鼠标相对于指定元素的坐标const relativeX = mouseX - rect.left;const relativeY = mouseY - rect.top;moveElement.style.left = relativeX - offsetX + 'px';moveElement.style.top = relativeY - offsetY + 'px';
}canvas.ondragover = function (e) {e.preventDefault();
};// 选择颜色
for (let i = 0; i < colorArr.length; i++) {const color = colorArr[i];color.onclick = (e) => {const val = color.getAttribute('color');graphicalArr.forEach((element) => {element.style.setProperty('--graphical-color', val);});};
}

比上一题要麻烦一些,但总体来说没有很难,本质上是在考察js数组操作

3.5 欧洲杯顶级球员数据分析

js/index.js 中的代码:

var myChart = echarts.init(document.getElementById('main'));
var dataList = []; // 存储 data.json 中的所有数据
var MockURL = './js/data.json';
/*** 获取指定条件下的图表数据* @param {Array} dataList data.json 中的所有数据* @param {String} year 选择展示数据的年份,例:'2021'*/
function showData(dataList, year) {try {// 设置图表选项var option = {graphic: {type: 'image',id: 'background',left: 0,top: 0,z: -10,bounding: 'raw',origin: [0, 0],style: {image: './images/football.svg',width: 900,height: 600}},xAxis: {type: 'value',min: 0,max: 100,axisTick: {show: false},axisLabel: {show: false},axisLine: {show: false},splitLine: {show: false}},yAxis: {type: 'value',min: 0,max: 100,axisTick: {show: false},axisLabel: {show: false},axisLine: {show: false},splitLine: {show: false}},series: [{name: 'Missed',type: 'scatter',data: [[70, 67],[88.5, 50],[87.9, 50],[74.5, 47.4],[86, 70.5],[88.5, 50]],itemStyle: {color: '#808080',opacity: 0.4},symbolSize: 10},{name: 'Scored',type: 'scatter',data: [[72.5, 44.7],[72.1, 38.4],[91.9, 51.7],[75, 76.9],[76.5, 32.5],[90.9, 66.5]],itemStyle: {color: '#0000ff',opacity: 0.4},symbolSize: 10}]};// TODO:待补充代码 目标 2let newDataList = dataList;if (year == 'all') {newDataList = dataList;} else {newDataList = dataList.filter(item => item.season == year)}option.series[0].data = newDataList.filter(item => item.result != "Goal").map(item => [item.X * 100, item.Y * 100])option.series[1].data = newDataList.filter(item => item.result == "Goal").map(item => [item.X * 100, item.Y * 100])// 使用指定的配置项和数据显示图表myChart.setOption(option);} catch (error) {console.error('Error fetching data:', error);}
}
// 根据选择年份更新图表数据
function updateData() {// TODO:待补充代码 目标 3const year = document.querySelector('#year').value;showData(dataList, year)
}(async () => {// TODO:待补充代码 目标 1const res = await axios.get(MockURL)dataList = res.data;// 将获取到的数据显示在图表中showData(dataList, 'all')
})()
  • 第一问考察Ajax请求 ,本次比赛我全用了axios 但是保险起见最好还是要会fetch请求
  • 第二、三问考察对请求的数据进行处理,要了解数组操作和Echart 的用法。

3.6 内存优化之一键清理垃圾文件

utils.js 中的代码:

const fs = require('fs');
const path = require('path');/*** 找到垃圾文件* @param {string} dirPath 放待清理文件的文件夹*/
function findGarbageFiles(dirPath) {let garbageFiles = [];// TODO:待补充代码const files = fs.readdirSync(dirPath);for (let file of files) {// 文件夹或文件的路径const filePath = path.resolve(dirPath, file)  const fileStats = fs.statSync(filePath);const isFile = fileStats.isFile();const isDirectory = fileStats.isDirectory();const fileSize = fileStats.size;if (isDirectory) {garbageFiles.push(...findGarbageFiles(filePath))} else if (isFile) {if (fileSize <= 1024*1024) {garbageFiles.push({path: filePath,size: fileSize})}}}return garbageFiles;
}/*** 清理垃圾文件* @param {Array} garbageFiles 待清理的文件*/
function cleanGarbageFiles(garbageFiles) {// TODO:待补充代码for (let file of garbageFiles) {const {path:filePath, size} = file;fs.unlinkSync(filePath);}
}module.exports = { findGarbageFiles, cleanGarbageFiles}; // 检测需求,请勿删除

如果你会一点递归算法,这题就不难,本质就是一个暴力递归整个文件夹和下面的所有子文件。考察了fs模块Node.js 中的path模块,最好要会使用path.resolve() 这类方法。

3.7 新闻中心

index.html 中的代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>新闻中心</title><link rel="stylesheet" href="./css/index.css"></head><body><div id="app" v-cloak><header><h1>新闻中心</h1><h2>与未来同行,让我们的生活更美好</h2></header><main><template v-if="isLoading"><div class="skeleton" v-for="skeleton in 3" :key="skeleton"><p class="title"></p><p class="source"></p><p class="content"></p><div class="new-footer"><span></span><span></span></div></div></template><template v-else><div class="new-item" v-for="(item,index) in newsList" :key="index" :title="item.title" :source="item.source":content="item.content" :author="item.author" :date="item.createTime"><p class="title">{{ item.title}}</p><p class="source">来源:{{ item.source }}</p><p class="content">{{ item.content }}</p><div class="new-footer"><span>{{ item.author }}</span><span>{{ item.createTime }}</span></div></div></template></main><transition name="notify"><div class="notify" v-if="retryNewsList.length"><p>获取到了新的新闻,是否载入?</p><button class="confirm" @click="onInsertData">确定</button></div></transition></div><script src="./lib/axios.min.js"></script><script src="./lib/axios-mock-adapter.min.js"></script><script src="./lib/mock.js"></script><script src="./lib/vue@next.js"></script><script>const {ref,onMounted,reactive,toRefs,watch} = Vue;const app = Vue.createApp({setup() {const newsList = ref([])const retryNewsList = ref([])const isLoading = ref(true)const failedSources = [];// TODO:待补充代码async function getNews(requestUrls) {const tem_list = ref([]);const tem_retry = ref([]);async function getUrls(urls, target_list) {for (let url of urls) {try {const res = await axios.get(url);if (res.data.code == 200) {let process_data = res.data.data.map(res_obj => {const title = Object.keys(res_obj).includes('name') ? res_obj.name : res_obj.titleconst content = res_obj.contentlet source = Object.keys(res_obj).includes('from') ? res_obj.from : res_obj.sourcesource = Object.keys(res_obj).includes('origin') ? res_obj.origin : res_obj.sourceconst author = res_obj.authorconst createTime = Object.keys(res_obj).includes('date') ? res_obj.date : res_obj.createTimereturn {title,content,source,author,createTime}})target_list.value.push(...process_data);}} catch (error) {tem_retry.value.push(error.response.request.responseURL)}}newsList.value.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));isLoading.value = false;}await getUrls(requestUrls, newsList)await getUrls(requestUrls, retryNewsList)}// TODO:END// 所有新闻源的请求地址const newsSource = ['news/caijing', 'news/shehui', 'news/keji', 'news/yule', 'news/jiaoyu', 'news/guoji']getNews(newsSource)// 载入加载失败,重试后获取成功的新闻const onInsertData = () => {newsList.value = [...newsList.value, ...retryNewsList.value]retryNewsList.value = []newsList.value.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));}return {isLoading,newsList,retryNewsList,onInsertData}}});let vm = app.mount('#app');</script>
</body></html>

这题很明显,比较综合考察了Vue3 。还考察了Ajax请求 ,反正我是Axios 一把梭。如果我没有记错,这题难点在对多个请求失败的错误处理,当时也浪费了挺久的时间。

后面的几题做的不行,就不贴出来了。

4. 总结

总结,这次还是有点遗憾,没有全做出来。有几个方面的知识掌握的不太好:

  • 多个Promise如何处理
  • Js常见的错误处理方法
  • Vue3框架一些函数和高级语法有所遗忘
  • Pinia操作
  • ElementPlus框架常见的使用情况

相关文章:

  • [FPGA 官方 IP] Binary Counter
  • 编程题python常用技巧-持续
  • 第 11 届蓝桥杯 C++ 青少组中 / 高级组省赛 2020 年真题,选择题详细解释
  • 【笔记】深度学习模型训练的 GPU 内存优化之旅③:内存交换篇
  • 如何降低LabVIEW开发费用
  • 自动剪辑批量混剪视频过原创软件工具视频帧级处理技术实践批量截图解析
  • Leetcode刷题记录25——合并区间
  • 移动光猫 UNG853H 获取超级管理员账号密码
  • 一键解放双手,操作丝滑起飞!
  • Vue3 + OpenLayers 企业级应用进阶
  • 【浅尝Java】Java简介第一个Java程序(含JDK、JRE与JVM关系、javcdoc的使用)
  • matlab 中function的用法
  • 网络分析/
  • 22.2Linux的I2C驱动实验(编程)_csdn
  • 突破zero-RL 困境!LUFFY 如何借离线策略指引提升推理能力?
  • T575729 正经数组
  • mem0 安装与测试:一个强大的对话记忆管理工具
  • 2025五一杯数学建模C题:社交媒体平台用户分析问题,完整第一问模型与求解+代码
  • C语言 指针(2)
  • Winform(7.序列化方式整理)
  • 履新宿州市政府党组书记后,任东暗访五一假期安全生产工作
  • 特朗普宣布提名迈克·沃尔兹为下一任美国驻联合国大使
  • 刘洪洁已任六安市委副书记、市政府党组书记
  • 王毅谈金砖国家开展斡旋调解的经验和独特优势
  • 三家“券商系”公募同日变更掌门人,新董事长均为公司股东方老将
  • 中老铁路跨境国际旅客突破50万人次