第十三届-第十五届 蓝桥杯Web组 历年真题(省赛/国赛)

目录

2024年十五届国赛大学组真题(共10道题)

第一题 真人鉴定器(5分)

第二题 俄罗斯方块(5分)

第三题 个人消息同步(10分)

第四题 工作协调(10分)

第五题 新手引导(15分)

第六题 简易webpack(15分)

第七题 会议日程(20分)

第八题 代码量统计(20分)

第九题 国际化适配(25分)

第十题 GitHub Desktop(25)

2024年十五届省赛大学组真题(共10道题)

第一题 智能停车系统(5分)

第二题 布局切换(5分)

第三题 产品360度展示(10分)

第四题 多表单校验(10分)

第五题 找回连接的奇幻之旅(15分)

第六题 tree 命令助手(15分)

第七题 Github 明星项目统计(20分)

第八题 小蓝驿站(20分)

第九题 商品浏览足迹(25分)

第十题 NPM Download Simulator(25分)

2023年十四届省赛大学组真题(共10道题) 

第一题 电影院排座位(5分)

第二题 图片水印生成(5分)

第三题 收集帛书碎片(10分)

第四题 自适应页面(10分)

第五题 全球新冠疫情数据统计(15分)

第六题 年度明星项目(15分)

第七题 视频弹幕(20分)

第八题 外卖给好评(20分)

第九题 Markdown文档解析(25分)

第十题 组课神器(25分)

备赛链接:备赛蓝桥杯 - 蓝桥云课

2024年十五届国赛大学组真题(共10道题)

第一题 真人鉴定器(5分)

function changeBanner(isPre) {
    // TODO:待补充代码
    if (isPre) {
      if (thisIndex == 0) {
        thisIndex = imgListLi.length - 1;
      } else {
        thisIndex--;
      }
    } else {
      if (thisIndex == imgListLi.length - 1) {
        thisIndex = 0;
      } else {
        thisIndex++;
      }
    }
    // TODO: END

第二题 俄罗斯方块(5分)

.z-shape {
  display: grid;
  /* TODO:待补充代码 */
  /* 2行 */
  grid-template-rows: repeat(2, 30px);
  /* 4列 */
  grid-template-columns: repeat(4, 30px);
}

.l-shape {
  display: grid;
  /* TODO:待补充代码 */
  /* 2行 */
  grid-template-rows: repeat(2,30px);
  /* 3列 */
  grid-template-columns: repeat(3,30px);
}

第三题 个人消息同步(10分)

目标一:
import { defineStore } from "pinia";
import { ref } from "vue";

export const useMessageStore = defineStore("message", () => {
  const messageState = ref([]);
  // 定义请求地址 MockUrl
  const MockUrl = "./data.json";
  let getUserMessage = async () => {
    // TODO:待补充代码
    await axios.get(MockUrl).then((res) => {
        messageState.value = res.data.data
    });
    // TODO:END
  };

  return {
    messageState,
    getUserMessage,
  };
});

目标二:
 setup() {
    const messageStore = useMessageStore();
    const headerData = reactive({
      headerArr: ["学习", "蓝桥杯", "考证", "讨论区", "校企版"],
    });
    const isShowPop = ref(true);

    // TODO:待补充代码 目标 2
    const messageStateLen = computed(() => {
      return messageStore.messageState.length;
    });
    return {
      headerData,
      messageStore,
      isShowPop,
      messageStateLen,
    };
  },

......

  <! --TODO:待补充代码 目标 2-- >
  <div class="tip">{{messageStateLen}}</div>


目标三:
<!-- TODO: 待修改代码 目标 3 -->
<!-- 单条消息模板 start-->
<div v-for="item in messageStore.messageState" :key="item.msg_id"  class="message-content-wrapper mt-20px" >
<div class="message-list">
    <div class="message-item">
        <div class="message-content">
            <div class="message-cate">
                <span class="label label-success font-14">{{item.label}}</span>
            </div>
            <div class="message-main-content">
            {{item.content}}
            </div>
        </div>
        <div class="msg-create-time text-right">
            <small> {{item.send_at}}</small>
        </div>
    </div>
</div>
</div>
<!-- 单条消息模板 end-->

第四题 工作协调(10分)

static difference(a, b) {
        // TODO:待补充代码 目标 1
        let dSet = new XSet()
        for (let itemA of a) {
            // // 判断集合b里是否不包含a,如果集合b里不包含a,则符合条件
            if (Array.from(b).every(e => itemA !== e)) {
                dSet.add(itemA)
            }
        }
        return dSet // 修改此处为函数的正确返回值 
    }


static intersection(a, ...bSets) {
        // TODO:待补充代码 目标 2
        let iSet = new XSet()
      for (let itema of a) {
        if (bSets.every(bSet => bSet.has(itema))) {
            iSet.add(itema)
        }
      }
        return iSet// 修改此处为函数的正确返回值 
    }


static union(a, ...bSets) {
       // TODO:待补充代码 目标 3
       let uSet = new XSet()
       a.size ? uSet.add(...Array.from(a)): null
       for (let itemb of Array.from(bSets)) {
        for (let element of itemb) {
            uSet.add(element)
        }
        
       }
       return uSet // 修改此处为函数的正确返回值 
    }

 总结:对集合,数组,对象方法的用法

第五题 新手引导(15分)

// TODO:待补充代码 目标 1
  introduce.style.top = y + "px";
  if (isLeft) {
    introduce.style.left = right + distance + "px";
  } else {
    // clientWidth
    introduce.style.left =
      left - getDomWholeRect(introduce).width - distance + "px";
  }
  console.log(target);
  // TODO:END


function copyTarget() {
  //  TODO:待补充代码
  // 克隆 DOM 节点
  clone = target.cloneNode(true);
  // 当前target的位置大小
  const { top, right, bottom, left, x, y, width, height } = boundingClientRect;
  console.log(target);
  // 设置clone的位置和样式属性
  clone.style.zIndex = 9999;
  clone.style.top = y + "px";
  clone.style.left = x + "px";
  clone.style.position = "absolute";
  clone.style.width = width + "px";
  clone.style.height = height + "px";
  // 添加在target上
  target.parentNode.appendChild(clone);
  //  TODO:END
}


// 移除元素
  function removeTarget() {
    clone?.remove()
  }

第六题 简易webpack(15分)

const buildFn = () => {
  const path = require("path");
  const fs = require("fs");
  const webpack = require("./webpack.config"); // webpack 配置对象
  // 以上代码请勿修改
  // TODO:待补充代码,所有代码请写在 `buildFn` 函数内
 
  //目标一
  //解构赋值-拿到webpack对象中的全部数据
  const { entry, output } = webpack;
  const { path: _path, filename, publicPath, resolve } = output;
  const alias = resolve.alias;
  let files = fs.readFileSync(entry, "utf-8"); //用于同步读取文件内容的方法调用(内容)
  for (const key in alias) {
    //替换 alias  的路径,在 entry中
    files = files.replace(key, alias[key]);
  }
  let outPath = path.resolve(_path, filename);
  fs.writeFileSync(outPath, files); //将替换后的文件 files 写入到 outPath路径中
 
  //目标二
  let htmlFiles = fs.readFileSync(
    path.resolve(__dirname, "index.html"),
    "utf-8"
  ); //用于同步读取文件内容的方法调用(内容)
  // 在 HTML 文件中的 src=" 或 href=" 前插入 publicPath
  let newHtmlFiles = htmlFiles.replace(
    //综合起来,(?<=(src|href)=") 这个后行断言的作用是:检查当前位置的前面是否是 src=" 或者 href="。如果满足这个条件,就认为找到了一个匹配项。
    /(?<=(src|href)=")/g,
    output.publicPath
  );
  fs.writeFileSync(path.resolve(_path, "index.html"), newHtmlFiles); //将替换后的文件 htmlFiles 写入到 outPath路径中
  //目标三
  dist_path = path.join(_path, 'static')
  fs.mkdirSync(dist_path)
  function dfs(path1, dis_path) {
    let fileList = fs.readdirSync(path1)
    for (let f of fileList) {
      const fileStats = fs.statSync(path.join(path1, f));
      let sourceFilePath = path.join(path1, f)
      let distFilePath = path.join(dis_path, f)
      if (fileStats.isDirectory()) {
        fs.mkdirSync(distFilePath)
        dfs(sourceFilePath, distFilePath)
      }
      if (fileStats.isFile()) {
        fs.copyFileSync(sourceFilePath, distFilePath);
      }
    }
    return;
  }
  dfs(path.resolve(__dirname, 'static'), dist_path)
 
};
module.exports = buildFn;


第七题 会议日程(20分)

// 创建日程
          const handleSave = async (formEl) => {
            if (!formEl) return;
            await formEl.validate(async (valid, fields) => {
              if (valid) {
                const params = editState.form;
                if (!params.id) {
                  // TODO:待补充代码  目标 1
                  await axios.post("/api/meetings", params);
                  // TODO:END
                  await fetchData();
                  showEdit.value = false;
                } else {
                  // 编辑日程
                  await axios.put("/api/modify", params);
                  await fetchData();
                  showEdit.value = false;
                }
              } else {
                console.log("error submit!", fields);
              }
            });
          };

          // 删除单个会议日程
          const handleDeleteOne = async (record) => {
            // TODO:待补充代码  目标 2
            await axios.delete(`/api/delmeeting/${record.id}`);
            // TODO:END
            fetchData();
          };

          // 点击单个会议日程
          const handleSelect = (record) => {
            // TODO:待补充代码  目标 3 和 4
            for (const day of list.value) {
              for (const item of day.meetings) {
                if (item.id === record.id) {
                  item.checked = !item.checked;
                  day.checked = day.meetings.every((item) => item.checked);
                  allCheckStatus.value = list.value.every((day) => day.checked);
                  return;
                }
              }
            }
            // TODO:END
          };

          // 点击日期多选框
          const handleSelectDate = (item) => {
            // TODO:待补充代码  目标 3 和 4
            for (const day of list.value) {
              if (day.id === item.id) {
                day.checked = !day.checked;
                day.meetings.forEach((item) => (item.checked = day.checked));
                allCheckStatus.value = list.value.every((day) => day.checked);
                return;
              }
            }
            // TODO:END
          };

          // 点击全选选择框
          const handleSelectAll = () => {
            // TODO:待补充代码  目标 4
            allCheckStatus.value = !allCheckStatus.value;
            list.value.forEach((day) => {
              day.checked = allCheckStatus.value;
              day.meetings.forEach((item) => {
                item.checked = allCheckStatus.value;
              });
            });
            // TODO:END
          };


第八题 代码量统计(20分)

const MockUrl = "./data.json"; // 定义请求地址
/**
 * @return {Array}  请求到的数组
 */
async function fetchCodeData() {
  // TODO:待补充代码 目标 1
  let response = await fetch(MockUrl);
  return await response.json();
}

// 根据数据绘制图表
function setChart(chart, data) {
  chart.setOption({
    title: {
      text: "代码量统计",
      left: "center",
      top: 20,
    },

    series: [
      {
        name: "Code Counter",
        type: "treemap",
        data,
        itemStyle: {
          gapWidth: 5,
        },
        // TODO:待补充代码 目标 3
        visibleMin: 6400,
        label: {
          // TODO:待补充代码 目标 3
          formatter: "{b}\n{c}行",
        },
        levels: [
          {
            color: ["#FCB944", "#80B7C2", "#C48483", "#F0663B", "#75D180"],
          },
          {
            colorSaturation: [0.35, 0.5],
          },
        ],
      },
    ],
  });
}

window.onload = async () => {
  const dom = document.querySelector("#canvasContainer");
  const chart = echarts.init(dom);

  // 获取原始数据
  const rawData = await fetchCodeData();

  // 处理后的数据
  const processedData = [];
  // TODO:待补充代码 目标 2

  rawData.forEach(([pathName, codeVal], idx) => {
    let splitArr = pathName.split("/");
    loadData(processedData, splitArr, codeVal);
  });
  function loadData(pathArr, splitArr, codeVal) {
    if (splitArr.length == 1) {
      pathArr.push({
        name: splitArr[0],
        value: codeVal,
        children: [],
      });
    } else {
      for (const child of pathArr) {
        if (child.name === splitArr[0]) {
          splitArr.shift();
          loadData(child.children, splitArr, codeVal);
        }
      }
    }
  }
  // TODO:END
  // 绘制图表
  setChart(chart, processedData);
};


第九题 国际化适配(25分)

 const t = (langKey, option) => {
    // TODO:待补充代码 目标 1
    let langs = { zh_CN, en_US, ja_JP };
    let langKeys = langKey.split(".");
    let result = langKeys[0] == "nav" ? langs[lang].nav[langKeys[1]] : langs[lang][langKey]
    return result.replace(/\$\$(.*?)\$\$/g, (match,variableName)=>{
      return option[variableName] || match;
    // TODO:END
    })
  };



const resutl = {};
const urls = url.slice(url.indexOf('?')+1).split('&');
urls.forEach(e => {
let temp = e.split("=");
resutl[temp[0]] = temp[1];
});
return resutl;



watch(selectLang,(newVal,oldVal) =>{
let arr = selectLang.value.split("__");
history.replaceState({},null, `?lang=${arr[0]}&theme=${arr[1]}`);
ctx.emit('url-change')
})


第十题 GitHub Desktop(25)


// 回滚到此版本 
function onRollBackClick(commitId) {
    // TODO:待补充代码  目标 1
    const c = this.currentBranchCommits
    c.splice(0, c.findIndex(x => x.commitId == commitId))
}

// 新建分支 
function onNewBranchClick() {
    // TODO:待补充代码  目标 2
    const i = inputNewBranch, v = i.value
    with (gitState.value)
    if (v && !branchList.includes(v)) {
        branchCommitsObject[v] = [...branchCommitsObject[currentBranch]]
        branchList.push(currentBranch = v)
        i.value = ''
    }
}

// 删除分支
function onBranchDelete(branchName) {
    // TODO:待补充代码  目标 3
    if (branchName != 'master')
        with (this.gitState) {
            branchList.splice(branchList.indexOf(branchName), 1)
            delete branchCommitsObject[branchName]
            if (currentBranch == branchName)
                currentBranch = 'master'
        }
}




-------------

/**
 * 辅助函数,用于深拷贝对象
 * @param {Object} obj
 * @returns
 */
function deepClone(obj) {
  if (Array.isArray(obj)) {
    return obj.map((it) => deepClone(it));
  } else if (typeof obj === "object") {
    const result = {};
    for (let key in obj) {
      result[key] = deepClone(obj[key]);
    }
    return result;
  } else {
    return obj;
  }
}

/**
 * TODO: 待补充代码 目标 4
 * @param {Ref} someRef Vue的某个ref对象,需要对传入的ref对象的历史状态做记录
 * @returns 返回一个对象,其中包含函数 undo 和 redo; undo 表示撤销,比如给 someRef 设置一个新状态后,调用 undo 可以将 someRef 还原为上一个旧状态;同理,在旧状态调用 redo 可以将 someRef 恢复为新状态
 */
function useRefHistory(someRef) {
  let stack = [],
    flag = true,
    n = (maxn = -1);
  Vue.watch(
    someRef,
    (x) => (flag ? (stack[(maxn = ++n)] = JSON.stringify(x)) : (flag = true)),
    { deep: true, immediate: true }
  );
  return {
    undo() {
      if (n) {
        flag = false;
        someRef.value = JSON.parse(stack[--n]);
      }
    },
    redo() {
      if (n != maxn) {
        flag = false;
        someRef.value = JSON.parse(stack[++n]);
      }
    },
  };
}

2024年十五届省赛大学组真题(共10道题)

第一题 智能停车系统(5分)

.cars {
  position: absolute;
  z-index: 2;
  width: 600px;
  height: 600px;
  display: flex;
  flex-direction: column;
  /* 排成列*/
  /* TODO: 请为下方属性填空,不要更改其他选择器里的代码 */
  flex-wrap: wrap;
  align-content: space-between;
  justify-content: space-between;
}

第二题 布局切换(5分)

// 获取元素
const layoutContainer = document.getElementById("layoutContainer"); // 布局元素
const layoutOptions = document.querySelectorAll(".layout-option"); // 三个模式元素
const switching = document.getElementById("switching"); // 模式按钮

// 显示模式
switching.addEventListener("click", function () {
    mode.style.display = "flex"; // 设置显示为flex布局
});

// 遍历选项
layoutOptions.forEach(function (option) {
    // 经典模式,浏览模式,工具模式点击事件
    option.addEventListener("click", function () {
        // TODO:待补充代码
        let imgList = document.querySelectorAll(".layout-option");
        const _this = this
        imgList.forEach((item) => {
            if (item.id == _this.id) {
                item.classList.add('active')
            }
            else {
                item.classList.remove('active')
            }
        });


        // TODO:END
        //  以下代码无需修改
        // 根据不同选项进行布局处理
        if (this === layoutOptions[0]) {
            // Classic mode
            tool.style.display = "none"; // 隐藏工具
            layoutContainer.classList.add("two-column-layout"); // 添加两列布局类
            layoutContainer.classList.remove("three-column-layout"); // 移除三列布局类
        } else if (this === layoutOptions[1]) {
            // Browse mode
            tool.style.display = "none"; // 隐藏工具
            layoutContainer.classList.add("three-column-layout"); // 添加三列布局类
            layoutContainer.classList.remove("two-column-layout"); // 移除两列布局类
        } else if (this === layoutOptions[2]) {
            // Tool mode
            tool.style.display = "flex"; // 显示工具
        }
        mode.style.display = "none"; // 隐藏布局容器
    });
});

第三题 产品360度展示(10分)

/**
 * @param {*} initialValue 初始值
 * @param {Array} sequence 由普通函数或 Promise 函数组成的数组
 * @return {Promise}
 */

const pipeline = (initialValue, sequence) => {
    // TODO: 待补充代码
    // console.log(sequence[0](initialValue));
    return new Promise(async (resolve, reject) => {
        for (let index = 0; index < sequence.length; index++) {
            initialValue = await sequence[index](initialValue);
        }
        resolve(initialValue);
    });
};

// 检测需要,请勿删除
try {
    module.exports = { pipeline };
} catch { }

第四题 多表单校验(10分)

// TODO:待补充代码
          const reg = /[^\u4e00-\u9fa5]/g;
          const rules = reactive({
            name: {
              required: true,
              validator: (rule, value, callback) => {
                if (value === "") {
                  callback(new Error("请输入姓名,只能输入汉字"));
                } else if (reg.test(value)) {
                  callback(new Error("只能输入汉字"));
                } else {
                  callback();
                }
              },
            },
            sex: { required: true, message: "请选择性别" },
            age: { required: true, message: "请选择性别" },
            isCompetition: { required: true, message: "请输入年龄" },
            isEntrepreneurship: {
              required: true,
              message: "请选择是否参加过编程比赛",
            },
            footnote: { required: true, message: "请选择是否有过创业经历" },
          });

第五题 找回连接的奇幻之旅(15分)

function resetableOnce(fn) {
  // TODO: 待补充代码
  let result;
  let done = true;
  function runOnce() {
    if (done) {
      result = fn(...arguments);
      console.log(...arguments);
      done = false;
    }
    return result;
  }
  function reset() {
    done = true;
  }
  // TODO: END
  return { runOnce, reset };
}

第六题 tree 命令助手(15分)

// 生成文件树
function generateTree(dirPath) {
  // TODO:待补充代码
  let pathArr = fs.readdirSync(dirPath);
  let result = [];
  for (const path_name of pathArr) {
    file_path = path.resolve(dirPath, path_name);
    // file_path = path.join(dirPath, path_name);
    if (fs.statSync(file_path).isDirectory()) {
      result.push({
        name: path_name,
        children: generateTree(file_path),
      });
    } else {
      result.push({
        name: path_name,
      });
    }
  }
  return result
}

第七题 Github 明星项目统计(20分)

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>Github 明星项目统计</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="./lib/axios.js"></script>
  <script src="lib/vue.global.js"></script>
  <script src="lib/echarts.min.js"></script>

<body>
  <div id="app">
    <!-- 图表容器 -->
    <div id="chart" style="width: 100%; height: 500px;"></div>
    <div class="filters">
      <div>
        筛选语言
        <!-- TODO: 待补充代码 -->
        <select name="language" id="language" @change="changeHandle" v-model="language">
          <option v-for="item in languages" :value="item">{{item}}</option>
        </select>
      </div>
      <div>展示第<input id="first" type="text" v-model="pageStart" @input="changeHandle">到第<input id="second" type="text"
          v-model="pageEnd" @input="changeHandle">位的项目</div>
    </div>
  </div>
</body>

<script>
  var xData;
  var yData;
  const app = Vue.createApp({
    setup() {
      // 定义响应式数据
      const chart = Vue.ref(null);
      const chartOptions = Vue.ref(null);
      const chartData = Vue.ref(null);
      xData = Vue.ref(null);
      yData = Vue.ref(null);
      const languages = Vue.ref(['All', 'JavaScript', 'TypeScript', 'Python', 'Shell', 'C++', 'C#', 'Go', 'Rust', 'Java']);
      const language = Vue.ref('All');
      const pageStart = Vue.ref(1);
      const pageEnd = Vue.ref(100);
      // 语言筛选改变时或页面数字输入框数字改变时的处理函数
      const changeHandle = () => {
        // TODO:待补充代码
        let result = Vue.ref([])
        if(language.value == 'All') {
          xData.value = chartData.value.map(item => item.name);
          yData.value = chartData.value.map(item => item.stars);
        }else {
          result.value =  chartData.value.filter(item => item.language == language.value)
          result.value =  result.value.slice(pageStart.value - 1, pageEnd.value);
          xData.value = result.value.map(item => item.name);
          yData.value = result.value.map(item => item.stars);
        }
        initChart();
      };

      // 初始化图表
      const initChart = () => {
        chart.value = echarts.init(document.getElementById('chart'));
        chartOptions.value = {
          title: {
            text: 'Github 明星项目统计',
            x: 'center'
          },
          tooltip: {
            trigger: 'axis',
            axisPointer: {
              type: 'shadow',
              label: {
                show: true
              }
            }
          },
          xAxis: {
            data: xData.value,
          },
          yAxis: {
            type: 'value',
            label: 'star数量'
          },
          series: [
            {
              data: yData.value,
              type: 'bar',
            }
          ],
        };
        chart.value.setOption(chartOptions.value);
      };

      // 组件挂载时获取数据
      Vue.onMounted(() => {
        axios.get('./js/data.json').then(res => {
          chartData.value = res.data;
          let newData = chartData.value.slice(pageStart.value - 1, pageEnd.value);
          xData.value = chartData.value.map(item => item.name);
          yData.value = chartData.value.map(item => item.stars);
          initChart();
        });
      });

      return {
        chart,
        chartOptions,
        chartData,
        xData,
        yData,
        languages,
        language,
        pageStart,
        pageEnd,
        initChart,
        changeHandle,
      };
    },
  });

  var vm = app.mount('#app');
</script>

</html>

第八题 小蓝驿站(20分)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>小蓝驿站</title>
    <!-- 链接外部样式表 -->
    <link rel="stylesheet" href="css/style.css">
    <!-- 引入 Vue.js 框架 -->
    <script src="lib/vue.global.js"></script>
</head>

<body>
    <div id="app">
        <!-- 邮箱顶部标题和操作按钮 -->
        <div class="email-header">
            <!-- 邮箱标题 -->
            <div class="email-header-logo">
                <h1 class="email-header-title">小蓝驿站</h1>
            </div>
            <!-- 邮箱操作按钮,包括撰写邮件、设置和退出 -->
            <div class="email-header-actions">
                <button class="email-header-button">撰写邮件</button>
                <button class="email-header-button">设置</button>
                <button class="email-header-button">退出</button>
            </div>
        </div>

        <div class="container">
            <!-- 邮箱侧边栏 -->
            <div class="email-sidebar">
                <!-- 邮箱文件夹列表 -->
                <ul class="folder-list">
                    <li class="folder-list-item">收件箱</li>
                    <li class="folder-list-item">草稿箱</li>
                    <li class="folder-list-item">已发送</li>
                    <li class="folder-list-item">已删除</li>
                    <li class="folder-list-item">垃圾邮件</li>
                    <li class="folder-list-item">广告邮件</li>
                </ul>
            </div>
            <div>
                <!-- 通讯录标题 -->
                <div class="contacts-title">通讯录</div>
                <!-- 添加联系人区块 -->
                <div class="add-contacts">
                    <!-- 添加联系人标题 -->
                    <div class="contacts-group-title">添加联系人</div>
                    <!-- 添加联系人表单 -->
                    <div class="add-contact">
                        <!-- 联系人输入框,使用 Vue 的 v-model 进行双向数据绑定 -->
                        <input class="add-contact-input" v-model="newContact" placeholder="新联系人名字">
                        <!-- 添加按钮,点击后触发 addContact 方法 -->
                        <button class="add-contact-button" @click="addContact">添加</button>
                    </div>
                </div>
                <!-- 联系人列表 -->
                <ul class="contacts-list">
                    <!-- TODO:待补充代码 目标 1  -->
                    <!-- 以 A 为例 start -->
                    <li class="contacts-group" v-for="item in contacts "> <!-- 字母分组渲染 DOM 结构-->
                        <div class="contacts-group-title">{{item.letter}}</div> <!-- 分组的 字母名称 -->
                        <ul>
                            <li class="contact-item" v-for="el in item.contacts"> <!-- contact-item 人名渲染 dom 结构-->
                                <span class="contact-name">{{el.name}}</span><button
                                    class="del-contact-button">删除</button>
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </div>

    <script>
        // 从 Vue 对象中解构出 reactive、computed 和 ref 等方法
        const { reactive, computed, ref, onMounted } = Vue;
        // 定义 Vue 应用
        const app = {
            setup() {
                // 定义并初始化联系人列表
                let contacts = ref([]);
                onMounted(async () => {
                    const res = await fetch('./data.json').then(res => res.json()).then(data => data);
                    contacts.value = res;;
                });

                // 定义并初始化新联系人名字
                const newContact = ref("");

                // 定义计算属性,对联系人列表进行排序
                const sortedContacts = computed(() => {
                    return contacts.value.sort((a, b) => a.letter.localeCompare(b.letter));
                });
                // 定义添加联系人的方法
                const addContact = () => {
                    // TODO:待补充代码 目标 2 
                    let newVal = newContact.value
                    if (newVal != '') {
                        let firstLetter = newVal.charAt(0).toLocaleUpperCase();
                        let 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 = "";
                };

                // 返回应用所需的数据和方法
                return {
                    contacts,
                    newContact,
                    sortedContacts,
                    addContact
                };
            }
        };

        // 创建并挂载 Vue 应用
        Vue.createApp(app).mount("#app");
    </script>
</body>

</html>

第九题 商品浏览足迹(25分)

window.onload = async ()=> {
  const MockUrl =`./js/data.json`;// 请求地址
  let data=[];// 存储请求后的数据
  // TODO:待补充代码,目标 1
  let res =await axios.get(MockUrl)
  // await等待异步操作完成,返回res,相当于then中的res
  console.log(res)
  data = res.data
  // TODO:END
  // 请求完成后调用下面的代码
  const newData = getData(data);
  showData(newData);
};

/**
* 将同一天浏览的相同商品去重并作为数组返回
* @param {Array} defaultData json 文件中读取到的原始数据
* @returns 去重后的数据,数据结构与 defaultData 相同
*/
const removeDuplicates = defaultData => {
console.log(defaultData)
let newData = [];
// TODO:待补充代码
const map = new Map();
defaultData.forEach(item=>{
  if(!map.has(item.id)){
    newData.push(item)
    map.set(item.id,1)
  }
});
console.log(newData)
return newData;
}

/**
* 将去重后的数据根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
* @param {*} defaultData 去重后的数据
* @returns 根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
*/
const sortByDate = defaultData => {
let newData = [];
// TODO:待补充代码
newData.push(...defaultData)
      newData.sort((a, b) => {
        return (new Date(b.viewed_on)) - (new Date(a.viewed_on))
      })
      
return newData;
}
/**
* 将去重排序后的所有商品数据,作为一个对象存储并返回,
* 该对象的所有 `key` 为浏览商品的当天日期(即,字段 viewed_on 格式化为 YYYY-MM-DD),
* `value` 为当天浏览的所有商品(以数组形式表现)。
* @param {Array} defaultData 重后的所有商品数据
* @returns 
*/
const transformStructure = defaultData => {
  let newData = {};
  // TODO:待补充代码
  const hashMap = new Map()
  defaultData.forEach(item => {
    const date = new Date(item.viewed_on)
    const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
    let arr = hashMap.get(dateStr)
    if (!arr) {
      arr = []
      hashMap.set(dateStr, arr)
    }
    arr.push(item)
  })
  hashMap.entries().forEach(entry => {
    const [date, data] = entry
    newData[date] = data
  })
  return newData;
}

第十题 NPM Download Simulator(25分)

......

2023年十四届省赛大学组真题(共10道题) 

第一题 电影院排座位(5分)

/* TODO:待补充代码 */
.seat-area {
  display: grid;
  margin-top: 50px;
  grid-template-rows: repeat(6, 1fr);
  grid-template-columns: repeat(8, 1fr);
  gap: 10px;
}
.seat-area .seat:nth-child(8n + 2) {
  margin-right: 20px;
}
.seat-area .seat:nth-child(8n + 7) {
  margin-left: 20px;
}

第二题 图片水印生成(5分)

/**
 * 创建一个文字水印的div
 * @param  {string} text - 水印文字
 * @param  {string} color - 水印颜色
 * @param  {number} deg - 水印旋转角度
 * @param  {number} opacity - 水印透明度
 * @param  {number} count - 水印数量
 */
function createWatermark(text, color, deg, opacity, count) {
  // 创建水印容器
  const container = document.createElement("div");
  container.className = "watermark";
  console.log(text, color, deg, opacity, count);
  // TODO: 根据输入参数创建文字水印

  let template = `<span style="color:${color};transform: rotate(${deg}deg); opacity:${opacity}">${text}</span>`;
  for (let i = 0; i < count; i++) {
    container.innerHTML += template;
  }
  return container;
}

// 以下代码不需要修改
// 调用createWatermark方法,创建图片水印
const watermark = createWatermark("WaterMark", "white", 45, 0.5, 11);
// 将水印挂载到图片容器上
const container = document.querySelector(".container");
container.appendChild(watermark);

// 提供图片保存功能
const button = document.querySelector("button");
button.addEventListener("click", () => {
  domtoimage.toJpeg(document.querySelector(".container")).then((dataUrl) => {
    const link = document.createElement("a");
    link.download = "image.jpeg";
    link.href = dataUrl;
    link.click();
  });
});

第三题 收集帛书碎片(10分)

function collectPuzzle(...puzzles) {
  // TODO:在这里写入具体的实现逻辑
  // 对所有的拼图进行收集,获取不同拼图类型的结果,并返回
  let result = puzzles.flat(Infinity);
  return [...new Set(result)];
}

// 检测需要,请勿删除
module.exports = collectPuzzle;

第四题 自适应页面(10分)

@media (max-width: 800px) {
  #tutorials .row {
    grid-template-columns: 1fr;
  }
  #tutorials img {
    margin: 0;
  }
  #tutorials .text .box {
    margin-bottom: 15px;
    margin-top: 20px;
  }
  label.icon-menu {
    display: block;
    color: white;
    padding: 0 20px;

    line-height: 54px;
  }
  .menu {
    height: 54px;
    margin-bottom: 25px;
  }
  .menu li {
    display: flex;
    flex-direction: column;
    background-color: #252525;
  }
  .collapse {
    display: none;
  }
  .menu:hover .collapse {
    display: flex;
    flex-direction: column;
  }
  .dropdown:hover ul {
    display: contents;
  }
  .dropdown:hover ul li {
    background-color: white;
  }
}

第五题 全球新冠疫情数据统计(15分)

第六题 年度明星项目(15分)

// 保存翻译文件数据的变量
let translation = {};
// 保存所有数据的变量
let data = [];
let page = 0;
// 记录当前语言
let currLang = "zh-cn";

// TODO: 请在此补充代码实现项目数据文件和翻译数据文件的请求功能
window.onload = async () => {
  const res = await fetch("./js/all-data.json");
  data = await res.json();
  const res2 = await fetch("./js/translation.json");
  translation = await res2.json();

  //目标二
  data.slice(0, 15).map((val) => {
    let obj = {
      icon: val.icon,
      description: currLang == "zh-cn" ? val.descriptionCN : val.descriptionEN,
      name: val.name,
      stars: val.stars,
      tags: val.tags,
    };
    return $(".list > ul").append(createProjectItem(obj));
  });
};
// TODO-END

// TODO: 请修改以下代码实现项目数据展示的功能
let loadMore = document.querySelector(".load-more");
loadMore.addEventListener("click", () => {
  page += 1;
  data.slice(15 * page, 15 * (page + 1)).map((val) => {
    let obj = {
      icon: val.icon,
      description: currLang == "zh-cn" ? val.descriptionCN : val.descriptionEN,
      name: val.name,
      stars: val.stars,
      tags: val.tags,
    };
    return $(".list > ul").append(createProjectItem(obj));
  });
  if (page == 3) {
    loadMore.style.display = "none";
  }
});
// TODO-END

// 用户点击切换语言的回调
$(".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]);
      }
    });
  // TODO: 请在此补充代码实现项目描述的语言切换
  const p = document.querySelectorAll("ul li p");
  if (currLang == "en") {
    p.forEach((item, index) => {
      item.innerHTML = data[index].descriptionEN;
    });
  } else if (currLang == "zh-cn") {
    p.forEach((item, index) => {
      item.innerHTML = data[index].descriptionCN;
    });
  }
});

// 生成列表DOM元素的函数,将该元素的返回值append至列表中即可生成一行项目数据
/**
 * @param  {string} name - 项目名称
 * @param  {string} description - 项目描述
 * @param  {string[]} tags - 项目标签
 * @param  {number} stars - 项目star数量
 * @param  {string} icon - 项目icon路径
 */
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>
  `;
}

第七题 视频弹幕(20分)

const bullets = [
  "前方高能",
  "原来如此",
  "这么简单",
  "学到了",
  "学费了",
  "666666",
  "111111",
  "workerman",
  "学习了",
  "别走,奋斗到天明",
];

/**
 * @description 根据 bulletConfig 配置在 videoEle 元素最右边生成弹幕,并移动到最左边,弹幕最后消失
 * @param {Object} bulletConfig 弹幕配置
 * @param {Element} videoEle 视频元素
 * @param {boolean} isCreate 是否为新增发送的弹幕,为 true 表示为新增的弹幕
 *
 */
function renderBullet(bulletConfig, videoEle, isCreate = false) {
  const spanEle = document.createElement("SPAN");
  spanEle.classList.add(`bullet${index}`);
  if (isCreate) {
    spanEle.classList.add("create-bullet");
  }
  // TODO:控制弹幕的显示颜色和移动,每隔 bulletConfig.time 时间,弹幕移动的距离  bulletConfig.speed
  let left = getEleStyle(videoEle).width;
  let top = getRandomNum(getEleStyle(videoEle).height);
  spanEle.innerHTML = bulletConfig.value;
  spanEle.style.left = left + "px";
  spanEle.style.top = top + "px";

  spanEle.style.color = `rgb(${getRandomNum(255)},${getRandomNum(
    255
  )},${getRandomNum(255)})`;

  videoEle.appendChild(spanEle);

  setInterval(() => {
    // 向左移动距离为 bulletConfig.speed(弹幕配置对象)。
    left -= bulletConfig.speed;
    spanEle.style.left = left + "px";

    if (getEleStyle(spanEle).right <= getEleStyle(videoEle).left) {
      videoEle.removeChild(spanEle);
      clearInterval(timer);
    }
  }, bulletConfig.time);
}

document.querySelector("#sendBulletBtn").addEventListener("click", () => {
  // TODO:点击发送按钮,输入框中的文字出现在弹幕中
  let intVal = document.querySelector("#bulletContent").value;
  bulletConfig.value = intVal;
  document.querySelector("#bulletContent").value = "";
  renderBullet(bulletConfig, videoEle, (isCreate = true));
});

function getEleStyle(ele) {
  // 获得元素的width,height,left,right,top,bottom
  return ele.getBoundingClientRect();
}

function getRandomNum(end, start = 0) {
  // 获得随机数,范围是 从start到 end
  return Math.floor(start + Math.random() * (end - start + 1));
}

// 设置 index 是为了弹幕数组循环滚动
let index = 0;
const videoEle = document.querySelector("#video");
// 弹幕配置
const bulletConfig = {
  isHide: false, // 是否隐藏
  speed: 5, // 弹幕的移动距离
  time: 50, // 弹幕每隔多少ms移动一次
  value: "", // 弹幕的内容
};
let isPlay = false;
let timer; // 保存定时器
document.querySelector("#vd").addEventListener("play", () => {
  // 监听视频播放事件,当视频播放时,每隔 1000s 加载一条弹幕
  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;
  }
});

第八题 外卖给好评(20分)

第九题 Markdown文档解析(25分)

第十题 组课神器(25分)

/**
 * @description 模拟 ajax 请求,拿到树型组件的数据 treeData
 * @param {string} url 请求地址
 * @param {string} method 请求方式,必填,默认为 get
 * @param {string} data 请求体数据,可选参数
 * @return {Array}
 * */
async function ajax({ url, method = "get", data }) {
  let result;
  // TODO:根据请求方式 method 不同,拿到树型组件的数据
  // 当method === "get" 时,localStorage 存在数据从 localStorage 中获取,不存在则从 /js/data.json 中获取
  // 当method === "post" 时,将数据保存到localStorage 中,key 命名为 data
  if (method == "get") {
    let dataList = localStorage.getItem("data");
    if (dataList) {
      result = JSON.parse(dataList);
    } else {
      await axios.get(url).then((res) => {
        result = res.data.data;
      });
    }
  } else if (method == "post") {
    let newData = JSON.stringify(data);
    localStorage.setItem("data", newData);
  }
  return result;
}

/**
 * @description 找到元素节点的父亲元素中类选择器中含有 tree-node 的元素节点
 * @param {Element} node 传入的元素节点
 * @return {Element} 得到的元素节点
 */
const getTreeNode = (node) => {
  let curElement = node;
  while (!curElement.classList.contains("tree-node")) {
    if (curElement.classList.contains("tree")) {
      break;
    }
    curElement = curElement.parentNode;
  }
  return curElement;
};

/**
 * @description 根据 dragElementId, dropElementId 重新生成拖拽完成后的树型组件的数据 treeData
 * @param {number} dragGrade 被拖拽的元素的等级,值为 dragElement data-grade属性对应的值
 * @param {number} dragElementId 被拖拽的元素的id,值为当前数据对应在 treeData 中的id
 * @param {number} dropGrade 放入的目标元素的等级,值为 dropElement data-grade属性对应的值
 * @param {number} dropElementId 放入的目标元素的id,值为当前数据对应在 treeData 中的id
 */
function treeDataRefresh(
  { dragGrade, dragElementId },
  { dropGrade, dropElementId }
) {
  // TODO:根据 `dragElementId, dropElementId` 重新生成拖拽完成后的树型组件的数据 `treeData`
  let dragStr = JSON.stringify(getDragElement(treeData, dragElementId));
  let dropStr = JSON.stringify(getDragElement(treeData, dropElementId));
  let treeDataStr = JSON.stringify(treeData);
  if (dragGrade === dropGrade) {
    treeDataStr = treeDataStr.replace(dragStr, "");
    treeDataStr = treeDataStr.replace(dropStr, dropStr + "," + dragStr);
  }
  if (dragGrade - dropGrade == 1) {
    if (dropStr.includes(dragStr)) dropStr = dropStr.replace(dragStr, "");
    const newDragStr = `${dragStr},`;
    const newDropStr = dropStr.replace("[", "[" + newDragStr);
    treeDataStr = treeDataStr.replace(dragStr, "");
    treeDataStr = treeDataStr.replace(dropStr, newDropStr);
  }
  // 处理多余字符
  treeDataStr = treeDataStr
    .replace(",,", ",")
    .replace("[,", "[")
    .replace(",]", "]");
  treeData = JSON.parse(treeDataStr);
}
function getDragElement(data, id) {
  for (const obj of flatObj(data)) {
    if (obj.id == id) return obj;
  }
}
function flatObj(data) {
  return data.reduce((prev, cur) => {
    prev = [...prev, cur];
    if (cur?.children) prev = [...prev, ...flatObj(cur.children)];
    return prev;
  }, []);
}
/**
 * @description 根据 treeData 的数据生成树型组件的模板字符串,在包含 .tree-node 的元素节点需要加上 data-grade=${index}表示菜单的层级 data-index="${id}" 表示菜单的唯一id
 * @param {array} data treeData 数据
 * @param {number} grade 菜单的层级
 * @return 树型组件的模板字符串
 *
 * */
function treeMenusRender(data, grade = 0) {
  let treeTemplate = "";
  // TODO:根据传入的 treeData 的数据生成树型组件的模板字符串
  grade++;
  for (obj of data) {
    treeTemplate +=
      grade === 3
        ? `<div class="tree-node" data-index="${obj.id}" data-grade="${grade}">
              <div class="tree-node-content" style="margin-left: 30px">
                <div class="tree-node-content-left">
                  <img src="./images/dragger.svg" alt="" class="point-svg" />
                  <span class="tree-node-tag">${obj.tag}</span>
                  <span class="tree-node-label">${obj.label}</span>
                </div>
                <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>`
        : `<div class="tree-node" data-index="${obj.id}" data-grade="${grade}">
              <div class="tree-node-content" style="margin-left: ${
                grade === 2 && 15
              }px">
                <div class="tree-node-content-left">
                  <img src="./images/dragger.svg" alt="" class="point-svg" />
                  <span class="tree-node-label">${obj.label}</span>
                  <img class="config-svg" src="./images/config.svg" alt="" />
                </div>
              </div>`;

    if (obj?.children)
      treeTemplate += `<div class="tree-node-children">${treeMenusRender(
        obj.children,
        grade
      )}</div>`;
    treeTemplate += `</div>`;
  }
  return treeTemplate;
}

let treeData; // 树型组件的数据 treeData

// 拖拽到目标元素放下后执行的函数
const dropHandler = (dragElement, dropElement) => {
  let dragElementId = dragElement.dataset.index;
  let dragGrade = dragElement.dataset.grade;
  if (dropElement) {
    let dropElementId = dropElement.dataset.index;
    let dropGrade = dropElement.dataset.grade;

    treeDataRefresh({ dragGrade, dragElementId }, { dropGrade, dropElementId });
    document.querySelector(".tree").innerHTML = treeMenusRender(treeData);
    document.querySelector("#test").innerText = treeData
      ? JSON.stringify(treeData)
      : "";
    ajax({ url: "./js/data.json", method: "post", data: treeData });
  }
};
// 初始化
ajax({ url: "./js/data.json" }).then((res) => {
  treeData = res;
  document.querySelector("#test").innerText = treeData
    ? JSON.stringify(treeData)
    : "";
  let treeEle = document.querySelector(".tree");
  treeEle.dataset.grade = 0;
  let treeTemplate = treeMenusRender(treeData);
  treeTemplate && (treeEle.innerHTML = treeTemplate);
  const mDrag = new MDrag(".tree-node", dropHandler);
  // 事件委托,按下小图标记录得到被拖拽的元素,该元素 class 包含 .tree-node
  document.querySelector(".tree").addEventListener("mousedown", (e) => {
    e.preventDefault();
    if (
      e.target.nodeName.toLowerCase() === "img" &&
      e.target.classList.contains("point-svg")
    ) {
      let dragElement = getTreeNode(e.target);
      // MDrag类的drag方法实现拖拽效果
      mDrag.drag(e, dragElement);
    }
  });
});

/**
 * @description 实现拖拽功能的类,该类的功能为模拟 HTML5 drag 的功能
 *              鼠标按下后,监听 document 的 mousemove 和 mouseup 事件
 *              当开始拖拽一个元素后会在 body 内插入对应的克隆元素,并随着鼠标的移动而移动
 *              鼠标抬起后,移除克隆元素和 mousemove 事件,如果到达目标触发传入的 dropHandler 方法
 */
class MDrag {
  constructor(dropElementSelector, dropHandler) {
    // 目标元素的选择器
    this.dropElementSelector = dropElementSelector;
    // 拖拽到目标元素放下后执行的函数
    this.dropHandler = dropHandler;

    // 保存所有的目标元素
    this.dropBoundingClientRectArr = [];
    // 被拖拽的元素
    this._dragElement = null;
    // 拖拽中移动的元素
    this._dragElementClone = null;
    // 目标元素
    this._dropElement = null;
    // 拖拽移动事件
    this._dragMoveBind = null;
    // 拖拽鼠标抬起事件
    this._dragUpBind = null;

    this.init();
  }
  init() {
    const dropElements = document.querySelectorAll(this.dropElementSelector);
    this.dropBoundingClientRectArr = Array.from(dropElements).map((el) => {
      return { boundingClientRect: el.getBoundingClientRect(), el };
    });
  }
  dragMove(e) {
    const { pageX, pageY } = e;
    this._dragElementClone.style.left = `${e.pageX}px`;
    this._dragElementClone.style.top = `${e.pageY}px`;
    this.setMouseOverElementStyle(pageX, pageY);
  }
  dragend(e) {
    // 移动到目标元素后mouseup事件触发,删除 this._dragElementClone 元素和解除mousemove/mouseup事件
    const { pageX, pageY } = e;
    document.removeEventListener("mousemove", this._dragMoveBind);
    document.removeEventListener("mouseup", this._dragUpBind);
    if (
      Array.from(document.body.children).indexOf(this._dragElementClone) != -1
    ) {
      document.body.removeChild(this._dragElementClone);
    }
    this._dropElement = this.getActualDropElement(pageX, pageY);
    this.drop();
  }
  drag(e, dragElement) {
    this._dragElement = dragElement;
    this._dragElementClone = dragElement.cloneNode(true);
    this._dragElementClone.style.position = "absolute";
    this._dragElementClone.style.left = `${e.pageX - 20}px`;
    this._dragElementClone.style.top = `${e.pageY - 20}px`;
    this._dragElementClone.style.opacity = 0.5;
    this._dragElementClone.style.width = "800px";
    document.body.appendChild(this._dragElementClone);
    // 绑定mousemove和mouseup事件
    this._dragMoveBind = this.dragMove.bind(this);
    this._dragUpBind = this.dragend.bind(this);
    document.addEventListener("mousemove", this._dragMoveBind);
    document.addEventListener("mouseup", this._dragUpBind);
    return this;
  }
  getActualDropElement(pageX, pageY) {
    const dropAttributeArr = this.dropBoundingClientRectArr.filter(
      (obj) =>
        pageY >= obj.boundingClientRect.top &&
        pageY <= obj.boundingClientRect.top + obj.boundingClientRect.height
    );
    if (dropAttributeArr.length == 1) {
      return dropAttributeArr[0].el;
    } else if (dropAttributeArr.length > 1) {
      let temp = dropAttributeArr.reduce((prev, next) => {
        if (
          Math.abs(pageY - prev.boundingClientRect.top) <=
          Math.abs(pageY - next.boundingClientRect.top)
        ) {
          return prev;
        } else {
          return next;
        }
      });
      return temp.el;
    } else {
      return null;
    }
  }
  setMouseOverElementStyle(pageX, pageY) {
    let mousemoveEle = this.getActualDropElement(pageX, pageY);
    if (mousemoveEle) {
      this.dropBoundingClientRectArr.forEach((obj) => {
        obj.el.classList.contains("mouseover-active") &&
          obj.el.classList.remove("mouseover-active");
      });
      mousemoveEle.classList.add("mouseover-active");
    }
  }
  drop() {
    this.dropHandler && this.dropHandler(this._dragElement, this._dropElement);
    this.init();
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值