蓝桥杯十五届省赛Web应用开发题目答案解析

> 本文转自 赵阿卷

1.智能停车系统 ⭐️

考点: flex 布局解题答案:

.cars {
  position: absolute;
  z-index: 2;
  display: flex;
  width: 600px;
  height: 600px;
  flex-direction: column;
 /* TODO: 请在下方编写题目代码,不要更改其他选择器里的代码 */
  flex-wrap: wrap;  
  align-content:space-between; 
  justify-content:space-between; 
}

2.分享点滴 ⭐️

考察:对象的遍历,字符串基本操作
答案1:

function appendParamsToURL(url, params) {
  // 创建一个空字符串来存储参数字符串
  let str = "";

  // 遍历参数对象中的每个键
  for (const key in params) {
    // 将键值对追加到字符串中
    str += `${key}=${params[key]}&`;
  }

  // 移除字符串末尾的 "&" 符号
  str = str.slice(0, -1);

  // 判断是否原始 URL 已包含查询字符串(通过是否包含 "?" 符号来判断)
  // 如果已包含查询字符串,则设置 sym 变量为 "&",用于以 "&" 作为分隔符追加参数
  // 否则,设置 sym 变量为 "?",作为查询字符串的起始符号
  let sym = url.includes("?") ? "&" : "?";

  // 连接原始 URL、sym 符号和参数字符串,并返回最终的 URL
  return `${url}${sym}${str}`;
}

答案2:

function appendParamsToURL(url, params) {
  // 使用 Object.entries() 获取参数对象的所有键值对并按照规则拼接
  const paramArray = Object.entries(params).map(
    ([key, value]) => `${key}=${value}`
  );
  // 使用join方法拼接参数字符串
  const paramString = paramArray.join("&");
  // 判断URL中是否已包含参数
  let sym = url.includes("?") ? "&" : "?";
  // 返回拼接后的URL
  return `${url}${sym}${paramString}`;
}

3. 布局切换 ⭐️

考察 DOM 操作、class 类名操作

  option.addEventListener('click', function () {
       for (const otherOption of layoutOptions) {
            // 移除其他选项的 active 类
            otherOption.classList.remove('active');
         }
        // TODO:待补充代码
        this.classList.add('active'); // 为当前选项添加active类
  }

4. 产品 360 度展示 ⭐️⭐️

考察:Promise、循环答案1:

const pipeline = async (initialValue, sequence) => {
  let res = initialValue;
  for (const fn of sequence) {
    res = await fn(res);
  }
  return res;
};

答案2:

const pipeline = (initialValue, sequence) => {
  let res = Promise.resolve(initialValue);
  for (const fn of sequence) {
    res = res.then((result) => fn(result));
  }
  return res;
};

5. 多表单验证 ⭐️

考察element-plus组件的使用

const validateName = (rule, value, callback) => {
  if (value === "") {
    callback(new Error("请输入姓名"));
  } else {
    const reg = /[^\u4e00-\u9fa5]/g;
    if (reg.test(value)) {
      callback(new Error("只能输入汉字"));
    }
    callback();
  }
};
// TODO-3:书写表单校验规则,并绑定到对应表单上
const rules = reactive({
  name: [
    { required: true, message: "请输入姓名", trigger: "blur" },
    { validator: validateName, trigger: "blur" },
  ],
  sex: [{ required: true, message: "请选择性别", trigger: "change" }],
  age: [{ required: true, message: "请输入年龄", trigger: "blur" }],
  isCompetition: [
    { required: true, message: "请选择是否参加过编程比赛", trigger: "change" },
  ],
  isEntrepreneurship: [
    { required: true, message: "请选择是否有过创业经历", trigger: "change" },
  ],
});

6. 找回连接的奇幻之旅 ⭐️⭐️⭐️

考察:函数封装、闭包

function resetableOnce(fn) {
  let done = false; // 标记函数是否已执行
  let result; // 保存函数执行的结果

  /**
   * 保证只执行一次的函数
   * @returns {Any} 返回函数fn的执行结果
   */
  function runOnce() {
    if (!done) {
      // 如果之前没有执行过,则执行函数fn,并保存结果
      result = fn.apply(this, arguments);
      done = true; // 标记为已完成,确保之后不再执行
    }
    // 返回之前执行的结果
    return result;
  }

  /**
   * 重置函数的执行状态
   */
  function reset() {
    done = false; // 重置done为false,允许函数再次执行
  }

  // 返回控制对象
  return { runOnce, reset };
}

7. 悠然画境 ⭐️⭐️

考察数组操作、字符串操作

function generateAndDisplayImages(imageCount, selectedText) {
  // TODO:待补充代码 根据数量和用户输入在页面上生成图片并显示到 .right-panel 元素中
  let count = 0;
  artDataArray.forEach((el) => {
    let tags = el.tags.split("、");
    tags.forEach((tag) => {
      if (selectedText.includes(tag)) {
        count++;
      }
    });
    el["count"] = count;
    count = 0;
  });

  artDataArray.sort((a, b) => {
    return b.count - a.count; // 从小到大 -> a.age - b.age
  });
  let b = artDataArray.slice(0, imageCount);
  return b;
}

8. 简易 JSX 解析器 ⭐️⭐️

考察 dom 操作、字符串方法

/**
 * 将驼峰式命名转换为短横线命名
 * @param {string} str - 驼峰式命名的字符串
 * @returns {string} - 转换后的短横线命名字符串
 */
function camelToDash(str) {
  let result = "";
  let hasUppercase = false; // 标记字符串中是否包含大写字母

  for (let i = 0; i < str.length; i++) {
    let char = str.charAt(i);

    // 检查当前字符是否为大写字母
    if (char === char.toUpperCase()) {
      hasUppercase = true;
      if (i > 0) {
        result += "-"; // 在大写字母前添加短横线
      }
      result += char.toLowerCase(); // 将大写字母转换为小写字母并添加到结果中
    } else {
      result += char; // 如果不是大写字母,直接添加到结果中
    }
  }

  // 如果字符串中没有大写字母,则直接返回原字符串
  if (!hasUppercase) {
    return str;
  }

  return result;
}

const jsx = (type, config) => {
  /* TODO: 请完善 jsx 函数的代码实现 */
  const dom = document.createElement(type);
  for (let key in config) {
    const prop = config[key];
    if (key === "style") {
      // 处理样式
      let styleStr = Object.entries(prop)
        .map((item) => {
          return `${camelToDash(item[0])}:${item[1]}`;
        })
        .join(";");
      dom.setAttribute("style", styleStr);
    } else if (typeof prop === "function") {
      dom[key] = prop;
    } else if (key === "children") {
      // 处理 children
      if (typeof prop === "string") {
        dom.appendChild(document.createTextNode(prop));
      } else {
        prop.forEach((child) =>
          typeof child === "string"
            ? dom.appendChild(document.createTextNode(child))
            : dom.appendChild(child)
        );
      }
    } else {
      // 处理其他属性
      dom.setAttribute(key, prop);
    }
  }
  return dom;
};

9. tree 命令助手 ⭐️⭐️⭐️

考察 fs、递归函数

function generateTree(dirPath) {
  // 读取目录下的所有文件和文件夹
  const files = fs.readdirSync(dirPath);
  const tree = [];

  files.forEach(file => {
    const filePath = path.join(dirPath, file);
    const isDirectory = fs.statSync(filePath).isDirectory();

    if (isDirectory) {
      // 如果是目录,则递归生成子目录的文件树
      const subtree = generateTree(filePath);
      tree.push({ name: file, children: subtree });
    } else {
      // 如果是文件,则直接添加到文件树
      tree.push({ name: file });
    }
  });

  return tree;
}

10. Github 明星项目统计 ⭐️⭐️⭐️

考察 vue3 v-for、Echarts 数据处理

<select name="language" id="language" @change="changeHandle" v-model="language">
  <option v-for="language in languages" :value="language">{{language}}</option>
</select>
<script>
  setup() {
       const changeHandle = () => {
          let newData = chartData.value.filter(item => item.language === language.value);
          if (language.value === 'All') {
            newData = chartData.value;
          }
          newData = newData.slice(pageStart.value - 1, pageEnd.value);
          xData.value = newData.map(item => item.name);
          yData.value = newData.map(item => item.stars);
          initChart();
        };
  }
</script>

11. 小蓝驿站 ⭐️⭐️⭐️

考察 vue3 v-for、ref、数据处理

// 目标 1
<ul class="contacts-list">
  <!-- 使用 Vue 的 v-for 指令循环渲染 sortedContacts 中的每个联系人组 -->
  <li
    class="contacts-group"
    v-for="(group, groupIndex) in sortedContacts"
    :key="groupIndex"
  >
    <!-- 如果联系人组中有联系人,则显示该组的标题 -->
    <div v-if="group.contacts.length > 0" class="contacts-group-title">
      {{ group.letter }}
    </div>
    <!-- 使用 v-for 指令循环渲染每个联系人 -->
    <ul>
      <li
        class="contact-item"
        v-for="(contact, contactIndex) in group.contacts"
        :key="contactIndex"
      >
        <!-- 显示联系人名字 -->
        <span class="contact-name">{{ contact.name }}</span>
        <button class="del-contact-button">删除</button>
      </li>
    </ul>
  </li>
</ul>
<script>
    setup() {
        // 目标 2
    const addContact = () => {
      // TODO:待补充代码 目标 2
      if (newContact.value.trim() !== "") {
        // 获取新联系人名字的首字母并转为大写
        const firstLetter = newContact.value[0].toUpperCase();
        // 查找是否有相同首字母的联系人组
        const groupIndex = contacts.value.findIndex(
          (group) => group.letter === firstLetter
        );
        if (groupIndex > -1) {
          // 如果找到了相同首字母的联系人组,就添加到该组
          contacts.value[groupIndex].contacts.push({ name: newContact.value });
        } else {
          // 如果没有找到相同首字母的联系人组,就新建一个组,并添加到联系人列表
          const newGroup = {
            letter: firstLetter,
            contacts: [{ name: newContact.value }],
          };
          contacts.value.push(newGroup);
        }
      }
      // TODO:END
      // 添加完成清空联系人输入框
      newContact.value = "";
    };
  }
</script>

12. 商品浏览足迹 ⭐️⭐️⭐️

考察 axios 请求、数据处理

window.onload = async ()=> {
      const res = await  axios("./js/data.json");
      const newData = getData(res.data);
      showData(newData);
};
/**
 * 将同一天浏览的相同商品去重并作为数组返回
 * @param {Array} defaultData json 文件中读取到的原始数据
 * @returns 去重后的数据,数据结构与 defaultData 相同
 */
const removeDuplicates = defaultData => {
  let newData = [];
  // TODO:在下面补充代码,最终完成该函数封装
  defaultData.forEach((defaultGoods) => {
    const defaultDate = new Date(defaultGoods.viewed_on);
    const dD = `${defaultDate.getFullYear()}-${
      defaultDate.getMonth() + 1
    }-${defaultDate.getDate()}`;
    defaultGoods.date = dD;

    let hadThisGoods = false;
    newData.forEach((tmpGoods) => {
      const tmpDate = new Date(tmpGoods.viewed_on);
      const tD = `${tmpDate.getFullYear()}-${
        tmpDate.getMonth() + 1
      }-${tmpDate.getDate()}`;
      if (tmpGoods.id == defaultGoods.id && tD == dD) {
        hadThisGoods = true;
      }
    });
    if (!hadThisGoods) {
      newData.push(defaultGoods);
    }
  });
  return newData;
}

/**
 * 将去重后的数据根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
 * @param {*} defaultData 去重后的数据
 * @returns 根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
 */
const sortByDate = defaultData => {
  let newData = [];
  // TODO:在下面补充代码,最终完成该函数封装
  function sortByProperty(property, asc) {
    return function (value1, value2) {
      let a = new Date(value1[property]);
      let b = new Date(value2[property]);
      // 默认升序
      if (asc == undefined) {
        return a - b;
      } else {
        return asc ? a - b : b - a;
      }
    };
  }
  newData = defaultData.sort(sortByProperty("date", false));
  return newData;
}
/**
 * 将去重排序后的所有商品数据,作为一个对象存储并返回,
 * 该对象的所有 `key` 为浏览商品的当天日期(即,字段 viewed_on 格式化为 YYYY-MM-DD),
 * `value` 为当天浏览的所有商品(以数组形式表现)。
 * @param {Array} defaultData 重后的所有商品数据
 * @returns 
 */
const transformStructure = defaultData => {
  let newData = {};
  // TODO:在下面补充代码,最终完成该函数封装
  //  月份和日期 不足10的前面补0
  defaultData.forEach(item=>{
    let date=new Date(item.date) ;
    let y = date.getFullYear()
    let m = date.getMonth()+1
    m=m<10?'0'+m:m
    let d = date.getDate();
    d=d<10?'0'+d:d
    item.date=`${y}-${m}-${d}`;   
  })
  newData = defaultData.reduce((aD, item) => {
    console.log(item);
    if (!aD[item.date]) {
      aD[item.date] = [];
      aD[item.date].push(item);
    } else {
      if (!aD[item.date].includes(item.date)) {
        aD[item.date].push(item);
      }
    }
    return aD;
  }, {});
  return newData;
}

13. 扫雷游戏 ⭐️⭐️⭐️⭐️

考察二维数组操作、递归

function mineSweeperAlgorithms(arr, { row, col }) {
  if (flagData[row][col]) return;

  flagData[row][col] = true;

  const { positionWithoutMineArr, count } = getAroundAndCount(arr, {
    row,
    col,
  });

  if (count == 0) {
    // 如果当前格子周围没有地雷,递归执行扫雷算法
    positionWithoutMineArr.forEach((around) => {
      mineSweeperAlgorithms(arr, around);
    });
  }
}

14.NPM Download Simulator ⭐️⭐️⭐️⭐️

考察:获取数据类型、 promise.race

function myRace(iterable) {
  return new Promise((resolve, reject) => {
    if (!(Symbol.iterator in Object(iterable))) {
      reject(
        new TypeError(
          `${Object.prototype.toString
            .call({ iterable })
            .slice(-8, -1)
            .toLocaleLowerCase()} is not iterable`
        )
      );
      return;
    }
    const promises = Array.from(iterable);
    let resolvedOrRejected = false;
    for (const promise of promises) {
      Promise.resolve(promise).then(
        (value) => {
          if (!resolvedOrRejected) {
            resolvedOrRejected = true;
            resolve(value);
          }
        },
        (reason) => {
          if (!resolvedOrRejected) {
            resolvedOrRejected = true;
            reject(reason);
          }
        }
      );
    }
  });
}
打赏
评论区
头像
文章目录