第十五届蓝桥杯Web开发应用赛道省/国赛部分题解
题目解析与代码实现
第一题:跨屏变形记(CSS 响应式布局)
题目要求:实现页面在不同屏幕尺寸下的布局切换,当屏幕宽度≤768px 时,容器改为垂直排列,各模块宽度自适应。
/* 响应式布局核心代码 */
@media (max-width: 768px) {#container {display: flex; /* 弹性布局 */flex-direction: column; /* 垂直排列 */gap: 45px; /* 模块间距 */}#banner, #news {width: 100%; /* 宽度占满容器 */}
}
解析:利用 CSS 媒体查询(@media)检测屏幕宽度,通过 Flex 布局实现响应式排列。flex-direction: column
使子元素垂直排列,gap
属性简化 margin 间距设置。
第二题:垃圾分类(JavaScript 数据分类)
题目要求:根据垃圾类型(厨余/可回收/其他/有害)将数据分类到不同数组。
// 方法一:forEach + switch 分支
const garbagesorting = () => {const wasteData = waste._rawValue;wasteData.forEach(item => {switch (item.type) {case "厨余垃圾": food_waste.value.push(item.txt); break;case "可回收垃圾": recyclable_waste.value.push(item.txt); break;case "其他垃圾": other_waste.value.push(item.txt); break;case "有害垃圾": harmful_waste.value.push(item.txt); break;}});
};// 方法二:map 简洁写法
waste.value.map(item => {const { type, txt } = item;type === '厨余垃圾' && food_waste.value.push(txt);type === '可回收垃圾' && recyclable_waste.value.push(txt);type === '其他垃圾' && other_waste.value.push(txt);type === '有害垃圾' && harmful_waste.value.push(txt);
});
解析:两种方法均通过遍历数据实现分类。方法一逻辑清晰,适合复杂条件;方法二利用短路运算符简化代码,适合快速开发。注意数组引用类型特性,避免直接修改原数据。
第三题:神奇的滤镜(DOM 事件与样式控制)
题目要求:点击滤镜按钮时显示/隐藏对应滤镜层,并确保新显示的滤镜层在最上方。
filterTrigger.forEach(trigger => {trigger.addEventListener("click", (event) => {const target = event.target;const filterName = target.dataset.filterName; // 获取 data-filter-name 属性值const filterElement = document.querySelector(`.Filter[data-filter-name="${filterName}"]`);if (target.checked) {filterElement.style.display = "block"; // 显示滤镜层updateZIndex(filterElement); // 自定义函数:更新 Z 轴层级} else {filterElement.style.display = "none"; // 隐藏滤镜层}});
});// 辅助函数:确保当前滤镜层在最上层
function updateZIndex(element) {element.style.zIndex = Date.now(); // 使用时间戳生成唯一层级值
}
解析:通过 dataset 属性获取自定义数据,避免硬编码。updateZIndex
函数利用时间戳保证层级唯一性,确保新激活的滤镜层覆盖旧层。
第四题:解密工具(递归算法与回溯)
题目要求:生成指定长度的不重复数字组合(如 max=5,count=3 时生成 [1,2,3], [1,2,4] 等)。
function getPossiblePasswords(max, count) {const result = [];// 递归函数:参数为当前数组和下一个数字起始值function get_arr_pwd(arr, start) {if (arr.length === count) { // 终止条件:数组长度达标result.push([...arr]); // 深拷贝避免引用污染return;}for (let i = start; i <= max; i++) {arr.push(i); // 选择当前数字get_arr_pwd(arr, i + 1); // 递归下一层,起始值+1保证不重复arr.pop(); // 回溯:撤销当前选择}}get_arr_pwd([], 1); // 从数字 1 开始生成return result;
}
解析:典型的组合生成问题,使用回溯算法实现。start
参数确保数字递增不重复,arr.pop()
实现回溯,避免重复计算。
第五题:项目语言占比分析器(Node.js 文件系统操作)
题目要求:遍历指定目录,统计不同扩展名文件的大小占比(区分指定类型与其他类型)。
const fs = require('fs');
const path = require('path');function scanFiles(directoryPath, fileExtensionArr) {if (!fs.existsSync(directoryPath)) return `目录路径 ${directoryPath} 不存在`;if (!Array.isArray(fileExtensionArr)) return 'fileExtensionArr 必须是数组类型';const resultMap = {};let totalSize = 0;function deep(url) {const files = fs.readdirSync(url);files.forEach(item => {const fullPath = path.resolve(url, item);const stats = fs.statSync(fullPath);if (stats.isDirectory()) {deep(fullPath); // 递归处理子目录} else {const ext = path.extname(item).substring(1); // 获取扩展名(如 .js 转为 js)const category = fileExtensionArr.includes(ext) ? ext : 'other'; // 分类为指定类型或其他const size = stats.size;totalSize += size;// 累加文件大小resultMap[category] = resultMap[category] ? { filename: category, percentage: resultMap[category].percentage + size } : { filename: category, percentage: size };}});}deep(directoryPath);// 计算百分比并格式化结果return Object.values(resultMap).map(item => ({filename: item.filename,percentage: ((item.percentage / totalSize) * 100).toFixed(2)}));
}
解析:通过递归遍历目录(fs.readdirSync
+ 递归函数 deep
),利用 path.extname
解析文件扩展名,按指定类型分类统计。注意处理 totalSize
避免浮点数精度问题。
第六题:学生探览(Vue 3 + ECharts 图表)
题目要求:基于 Vue 实现学生信息搜索与雷达图对比(个人成绩 vs 班级平均分)。
// Vue 组件核心逻辑(setup 语法糖)
import { ref, computed, onMounted } from "vue";
import axios from "axios";
import echarts from "echarts";export default {setup() {const historyPrompts = [/* 搜索提示词数组 */];const searchText = ref("");const itemIndex = ref(-1);const items = ref([]);const chartDiv = ref(null);// 计算属性:学生姓名列表const names = computed(() => items.value.map(it => it.name));// 计算属性:当前学生信息const item = computed(() => itemIndex.value >= 0 ? items.value[itemIndex.value] : undefined);// 计算属性:班级平均分(三门科目)const classAverageScores = computed(() => {if (items.value.length === 0) return [0, 0, 0];return items.value.reduce((acc, cur) => {acc[0] += cur.scores[0]; // 技术能力得分acc[1] += cur.scores[1]; // 硬实力得分acc[2] += cur.scores[2]; // 软技能得分return acc;}, [0, 0, 0]).map(v => Math.round(v / items.value.length));});// 生命周期钩子:初始化数据onMounted(async () => {const res = await axios.get("./mock/data.json");items.value = res.data;});function onSearch() {itemIndex.value = items.value.findIndex(it => it.name === searchText.value);if (!item.value) return;// 延迟渲染图表,确保 DOM 已更新setTimeout(() => {const chart = echarts.init(chartDiv.value);chart.setOption({tooltip: {},legend: { data: ["我的表现", "班级平均表现"] },radar: {shape: "circle",indicator: [ // 雷达图指标{ name: "技术能力得分", max: 100 },{ name: "硬实力得分", max: 100 },{ name: "软技能得分", max: 100 },],},series: [{type: "radar",data: [{ value: item.value.scores, name: "我的表现" },{ value: classAverageScores.value, name: "班级平均表现" },],},],});}, 0);}return { /* 暴露模板所需变量与方法 */ };},
};
解析:利用 Vue 计算属性实现响应式数据联动,ECharts 雷达图对比个人与班级平均分。注意 setTimeout
处理异步渲染,避免图表初始化时 DOM 未加载完成。
第七题:购物狂欢节(Pinia 状态管理)
题目要求:使用 Pinia 实现购物车功能,包括添加商品(重复则数量+1)、删除商品、清空购物车及总价计算。
// store/products.js
import { defineStore } from "pinia";export const useProductsStore = defineStore("products", {state: () => ({ products: [] }),actions: {async fetchProducts(category) {const res = await fetch(`/api/products/${category}.json`);this.products = await res.json(); // 更新商品列表},},
});// store/cart.js
export const useCartStore = defineStore("cart", {state: () => ({ products: [] }),actions: {addProduct(product) {const existing = this.products.find(p => p.id === product.id);existing ? existing.quantity++ : this.products.push({ ...product, quantity: 1 });},removeProduct(productId) {this.products = this.products.filter(p => p.id !== productId); // 过滤删除},clearCart() {this.products = []; // 清空数组},},getters: {totalPrice() {return this.products.reduce((total, p) => total + p.price * p.quantity, 0); // 计算总价},},
});
解析:Pinia 的 state 存储响应式数据,actions 处理业务逻辑,getters 实现计算属性。添加商品时通过 find
检测重复项,总价计算使用数组 reduce
方法。
第八题:个性化桌面(本地存储与拖拽功能)
题目要求:实现背景图切换、新建文件、文件排序及拖拽排序功能。
// 背景图切换与本地存储
const changeBgc = (url) => {localStorage.setItem("bgcUrl", url); // 保存背景图 URLcurrentUrl.value = localStorage.getItem("bgcUrl"); // 更新当前显示
};// 生成随机文件名(3位随机字符 + .txt)
function generateRandomString(length = 3) {const chars = "abcdefghijklmnopqrstuvwxyz";return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
}// 新增文件并保存到本地存储
const addFile = () => {const randomName = `${generateRandomString()}.txt`;const newFile = {name: randomName,content: "",createdDate: new Date().toISOString().slice(0, 19).replace("T", " "), // 格式化时间};fileList.value.push(newFile);localStorage.setItem("fileList", JSON.stringify(fileList.value)); // 序列化存储
};// 拖拽功能实现
let dragStartFile = null;const handleDragStart = (event, item, index) => {dragStartFile = { item, index };event.dataTransfer.setData("text/plain", index); // 存储拖拽索引
};const handleDrop = (event) => {event.preventDefault();const targetIndex = parseInt(event.dataTransfer.getData("text/plain"));const { item, index } = dragStartFile;// 数组移动元素fileList.value.splice(index, 1);fileList.value.splice(targetIndex, 0, item);localStorage.setItem("fileList", JSON.stringify(fileList.value)); // 更新存储dragStartFile = null; // 重置拖拽状态
};
解析:利用 localStorage
实现数据持久化,拖拽功能通过 dragstart
和 drop
事件结合数组 splice
方法实现元素位置交换。注意处理数据类型转换(如索引转为字符串存储)。