首页 > 其他分享 >比赛抽签分组系统

比赛抽签分组系统

时间:2023-09-20 16:13:48浏览次数:36  
标签:const 比赛 addTeam 抽签 滚动 universitiesToTeams tableBox 分组 team

比赛抽签分组系统(项目回顾总结)

1.滚动列表部分

前言


是什么让我选择了搞纯粹的前端?

最主要的原因是,业务需求简单!当时知道的是,做双栏滚动列表。就一个页面而已。

最重要的原因是,关于分组数据的算法,我发现确实可以通过前端实现,就是只需要 map 存起来就行了。

  • 我学到的教训
    • 在没有和主办方对接之前,不要太相信前面说的很简单的话。
    • 需要做好长远的打算,让软件变得可扩展性强,方便后期提需求。
    • 没有必要提早去动手,需求确认才是真。

对于数据,在滚动列表当中,我用了 js 当中的数据,其中有一个点我不会,就是我并不会拿一个 js 专门存数据,然后...

总之最后滚动列表的数据不可行,原因是因为前端的数据并不能在浏览器中直接修改,也就是本机上还是不变的,要想改原始数据,需要持久层,并且我需要数据能够共享。不像 js 那样,全都放在一个文件里,会特别的乱。(而且因为几个网页都是可以复用的,对于 div 的名称什么都,如果都放在一个 js 里,会很恐怖)

这些让我决定最后采用数据库结合后端。

问题与解决

说实话,对于滚动列表,有几个难点,滚动列表是我第一次在项目中搞纯粹的前端。

如何存数据?

/*
这是一个JavaScript函数,它接受三个参数:一个Map对象,一个键k和一个值v。
如果Map对象已经包含键k,则将值v添加到与该键关联的值列表中。
否则,创建一个新的值列表,并将值v添加到该列表中,然后将该列表与键k一起添加到Map对象中。
 */
function addTeam(map, k, v) {
  if (map.has(k)) {
    map.get(k).push(v);
  } else {
    var lst = [v];
    map.set(k, lst);
  }
}
  • 解释 addTeam

这段代码是一个 JavaScript 函数。

函数名:addTeam

输入参数:

  • map:一个 Map 对象,在 JavaScript 中是一个键值对的集合。
  • k:一个键值,用来标识集合中的一个键。
  • v:一个值,表示要添加到指定集合中的值。

函数作用:将值 v 添加到 map 中指定键 k 对应的值中。如果键 k 已经存在于 map 中,则将值 v 添加到 k 对应的值列表中,否则新建一个列表,并将键 k 和列表作为一个键值对添加到 map 中。

函数逻辑说明:

  • 如果 map 对象中已经存在指定的键 k,则获取该键的值列表 map.get(k),然后将值 v 添加到该列表中,使用 map.get(k).push(v) 的方法实现这个操作。
  • 如果 map 对象中不存在指定的键 k,则创建一个数组 lst,将值 v 添加到该数组中,并将该数组与键 k 一起添加到 map 对象中,使用 map.set(k, lst) 的方法实现这个操作。

这个函数的主要目的是方便地将多个值按照指定键合并到一个 Map 对象中。

  • 存数据
var universitiesToTeams = new Map();
addTeam(universitiesToTeams, '浙江大学', '浙大-team1');
addTeam(universitiesToTeams, '浙江大学', '浙大-team2');
addTeam(universitiesToTeams, '浙江大学', '浙大-team3');
addTeam(universitiesToTeams, '中国美术学院', '中美-team1');
addTeam(universitiesToTeams, '中国美术学院', '中美-team2');

像是这样就可以实现把东西都存在 map 列表里边。

然后对于数据的操作,可以在当前的 js 文件当中实现。


如何做滚动列表?

https://www.jb51.net/article/255363.htm

在这次我学会了,搜索关键词的使用,关于滚动列表,我搜索了半天都没有成功。

因为我是直接搜索到滚动列表四个字。就是什么也没有。

后来我问了吴育锋老师,他一下子就搜索出来了,因为他搜索到是“vue 循环滚动列表”

比起我多了“循环”多了“vue”

所以呀,可以问比较厉害的人帮我搜索,看看它们的关键词。

还可以用 AI,询问选择的问题。就是,选择的方向,比随意一个方向然后努力更重要。

  • 实现的关键

其中关键的滚动在于

这两个函数是使用 JavaScript 实现表格滚动功能的代码段中的两个关键函数。其中:

  1. scrolls() 函数实现了滚动表格的功能,并启动了一个定时器,周期性地调用 stepScroll() 函数,来让表格向下滚动一定距离;

  2. stepScroll() 函数则是实现滚动动画效果的函数。函数内部使用 requestAnimationFrame 来执行一个滚动动画,每次滚动一定的距离,直到达到指定的滚动距离为止。

这两个函数的区别是:

  • scrolls() 函数是定时器的启动函数,其主要作用是启动定时器,并周期性地执行 stepScroll() 函数,从而完成表格的滚动操作。

  • stepScroll() 函数是实现滚动动画效果的关键函数,其内部使用 requestAnimationFrame 来实现滚动效果。

总体来说,二者分别实现了不同的功能,但是二者却需要协作来实现表格的连续滚动效果。

  • 具体实现

实现关键帧滚动一段距离

stepScroll() {
            const that = this;
            const step = 50; // 每次滚动的距离
            let num = 0; // 每次已滚动的距离
            const tableBox = this.$refs.tableBox; // 表格容器

            // 改为全局定时器,且在调用前先进行清空
            clearInterval(this.stepTime);

            function animateScroll() {
                // 每次滚动4px
                tableBox.scrollTop += 4;

                if (num < step) {
                    num += 4;
                    // 不断地执行 animateScroll() 函数,直到滚动距离达到指定值
                    requestAnimationFrame(animateScroll);
                } else {
                    num = 0;
                    that.stopSign = true; // 标识定时器已停止
                }
            }

            // 使用 requestAnimationFrame 执行滚动动画
            requestAnimationFrame(animateScroll);
        },

使用定时器实现执行 stepScroll(),实现滚动

scrolls() {
            const that = this;
            const tableBox = this.$refs.tableBox; // 表格容器
            const tableInner = this.$refs.tableInner; // 表格内容区域
            clearInterval(this.timer); // 在启动定时器前先清空之前的计时器
            this.timer = setInterval(function () {
                // 修改定时器结束判断条件:当滚动距离大于等于内容区域高度时,重新滚动到顶部
                if (tableBox.scrollTop + tableBox.clientHeight >= tableInner.scrollHeight) {
                    tableBox.scrollTop = 0;
                }
                that.stepScroll(); // 执行一次 stepScroll(),即向下滚动一段距离
            }, 50); // 定时器循环周期为 50ms
        },
  • 实现滚动列表(循环式)
new Vue({
  el: '#app', // Vue 实例挂载的元素 ID
  data: {
    tableHei: 'auto', // 表格容器高度
    timer: null, // 定时器句柄
    size: 0, // 计算表格行数
    stopSign: true, // 判断定时器是否停止标识
    stepTime: null, // 改为全局定时器
    universities: universities, // 数据源:大学列表
    teams: teams, // 数据源:参赛队伍列表
  },
  mounted() {
    const that = this;
    const tableBox = this.$refs.tableBox;
    const tableBoxRight = this.$refs.tableBoxRight;
    // 同时给左右两个列表容器元素添加scroll事件监听器,并将当前滚动位置同步到另一侧列表容器
    tableBox.addEventListener('scroll', function (e) {
      tableBoxRight.scrollTop = this.scrollTop;
    });
    tableBoxRight.addEventListener('scroll', function (e) {
      tableBox.scrollTop = this.scrollTop;
    });

    this.getTable(); // 获取表格行数并设置表格容器高度,启动循环滚动

    // 获取按钮元素
    const groupButton = document.getElementById('group-button');
    // 给按钮添加点击事件
    groupButton.addEventListener('click', () => {
      this.stopScroll(); // 调用 stopScroll() 方法
    });
  },
  methods: {
    getTable() {
      const outHei = this.$refs.tableoOut.clientHeight - 60; // 获取表格容器高度
      this.size = Math.floor(outHei / 58); // 计算表格行数
      this.tableHei = this.size * 58 + 'px'; // 设置表格容器高度
      this.scrolls(); // 启动循环滚动
    },
    stepScroll() {
      const that = this;
      const step = 50; // 每次滚动的距离
      let num = 0; // 每次已滚动的距离
      const tableBox = this.$refs.tableBox; // 表格容器

      // 改为全局定时器,且在调用前先进行清空
      clearInterval(this.stepTime);

      function animateScroll() {
        // 每次滚动4px
        tableBox.scrollTop += 4;

        if (num < step) {
          num += 4;
          // 不断地执行 animateScroll() 函数,直到滚动距离达到指定值
          requestAnimationFrame(animateScroll);
        } else {
          num = 0;
          that.stopSign = true; // 标识定时器已停止
        }
      }

      // 使用 requestAnimationFrame 执行滚动动画
      requestAnimationFrame(animateScroll);
    },

    scrolls() {
      const that = this;
      const tableBox = this.$refs.tableBox; // 表格容器
      const tableInner = this.$refs.tableInner; // 表格内容区域
      clearInterval(this.timer); // 在启动定时器前先清空之前的计时器
      this.timer = setInterval(function () {
        // 修改定时器结束判断条件:当滚动距离大于等于内容区域高度时,重新滚动到顶部
        if (
          tableBox.scrollTop + tableBox.clientHeight >=
          tableInner.scrollHeight
        ) {
          tableBox.scrollTop = 0;
        }
        that.stepScroll(); // 执行一次 stepScroll(),即向下滚动一段距离
      }, 50); // 定时器循环周期为 50ms
    },

    //停止滚动并且回到第一条数据!
    stopScroll() {
      const tableBox = this.$refs.tableBox;
      tableBox.scrollTop = 0;
      clearInterval(this.timer); // 清空循环定时器,停止滚动
      clearInterval(this.stepTime); // 清空单次滚动的定时器
      this.stopSign = true; // 标识定时器已经停止
      const timerId = setInterval(() => {
        if (tableBox.scrollTop === 0) {
          clearInterval(timerId);
          this.stopSign = true;
        } else {
          setTimeout(() => {
            this.stopSign = false;
            this.stopScroll();
          }, 100);
        }
      }, 100);
    },
  },
});

如何让两侧的滚动同时进行?

mounted() {
    const that = this;
    const tableBox = this.$refs.tableBox;
    const tableBoxRight = this.$refs.tableBoxRight;
    // 同时给左右两个列表容器元素添加scroll事件监听器,并将当前滚动位置同步到另一侧列表容器
    tableBox.addEventListener('scroll', function(e) {
        tableBoxRight.scrollTop = this.scrollTop;
    });
    tableBoxRight.addEventListener('scroll', function(e) {
        tableBox.scrollTop = this.scrollTop;
    });

    this.getTable(); // 获取表格行数并设置表格容器高度,启动循环滚动

    // 获取按钮元素
    const groupButton = document.getElementById("group-button");
    // 给按钮添加点击事件
    groupButton.addEventListener('click', () => {
        this.stopScroll(); // 调用 stopScroll() 方法
    });
},

其中的 getTable()

getTable() {
    const outHei = this.$refs.tableoOut.clientHeight - 60; // 获取表格容器高度
    this.size = Math.floor(outHei / 58); // 计算表格行数
    this.tableHei = this.size * 58 + 'px'; // 设置表格容器高度
    this.scrolls(); // 启动循环滚动
},

如何让界面设计的好看?(配色和 ui)

收集 22 种开源 Vue 模板和主题框架「干货」 - 知乎 (zhihu.com)

还有 chatGPT 设计一些基础页面,就能省去大改。

前端开发必备的宝藏网站推荐

我发现的纯粹的看配色网站其实根本就不行!

根本行不通。所以最好的还是在前两个链接的网站里找灵感。

滚动列表如何让,它单体循环滚动,而不是两个 div 分别滚动?

好的,你想要实现的效果是当滚动到最后一项时,自动滚回到第一项继续循环滚动。这里可以通过修改定时器结束判断条件来实现。

scrolls() {
  const that = this;
  const tableBox = this.$refs.tableBox; // 表格容器
  const tableInner = this.$refs.tableInner; // 表格内容区域
  clearInterval(this.timer); // 在启动定时器前先清空之前的计时器
  this.timer = setInterval(function () {
    // 修改定时器结束判断条件:当滚动距离大于等于内容区域高度时,重新滚动到顶部
    if (tableBox.scrollTop + tableBox.clientHeight >= tableInner.scrollHeight) {
      tableBox.scrollTop = 0;
    }
    that.stepScroll(); // 执行一次 stepScroll(),即向下滚动一段距离
  }, 50); // 定时器循环周期为 50ms
}


stepScroll() {
            const that = this;
            const step = 50; // 每次滚动的距离
            let num = 0; // 每次已滚动的距离
            const tableBox = this.$refs.tableBox; // 表格容器

            // 改为全局定时器,且在调用前先进行清空
            clearInterval(this.stepTime);

            function animateScroll() {
                // 每次滚动4px
                tableBox.scrollTop += 4;

                if (num < step) {
                    num += 4;
                    // 不断地执行 animateScroll() 函数,直到滚动距离达到指定值
                    requestAnimationFrame(animateScroll);
                } else {
                    num = 0;
                    that.stopSign = true; // 标识定时器已停止
                }
            }

            // 使用 requestAnimationFrame 执行滚动动画
            requestAnimationFrame(animateScroll);
        },

这里修改了判断条件,改成了当滚动距离加上表格容器可视高度大于等于内容区域高度时,自动滚回到顶部。这样即可实现循环滚动的效果,当滚动到最后一项时会自动滚回到第一项继续往下滚。

如何停止滚动?

因为 srcolls()函数的定时器实现了滚动,如果停止,清空定时器当然就可以了。

//停止滚动并且回到第一条数据!
stopScroll() {
    const tableBox = this.$refs.tableBox;
    tableBox.scrollTop = 0;
        clearInterval(this.timer); // 清空循环定时器,停止滚动
        clearInterval(this.stepTime); // 清空单次滚动的定时器
        this.stopSign = true; // 标识定时器已经停止
    const timerId = setInterval(() => {
        if (tableBox.scrollTop === 0) {
            clearInterval(timerId);
            this.stopSign = true;
        } else {
            setTimeout(() => {
                this.stopSign = false;
                this.stopScroll();
            }, 100);
        }
    }, 100);
},

如何把分好组的数据显示在页面上?

  • 前端的展示
<div id="left">
  <!--整个表tableOut-->
  <div class="tableoOut leftTableOut" ref="tableoOut">
    <!--表头-->
    <div class="tableTit leftTableTit" ref="tableTit">
      <div class="no">序号</div>
      <div class="schName">学校名称</div>
      <div class="teamName">队伍名称</div>
    </div>
    <!--tableBox表身-->
    <div
      class="tableBox leftTableBox"
      id="box11"
      tabindex="0"
      ref="tableBox"
      :style="{height: tableHei}"
    >
      <!--滚动的内容!(多列)-->
      <div class="tableInner" ref="tableInner" id="leftTableInner">
        <div
          class="box"
          v-for="(team, teamIndex) in teamsFlat"
          :key="teamIndex"
        >
          <div class="no">{{teamIndex + 1}}</div>
          <div class="schName">{{findUniversityByTeam(team)}}</div>
          <div class="teamName">{{team}}</div>
        </div>
      </div>
    </div>
  </div>
</div>
  • js 获取数据
addTeam(universitiesToTeams, '丽水学院', '丽院-team2');
addTeam(universitiesToTeams, '丽水学院', '丽院-team3');
// 从Map对象获取所有学校和队伍的数组
var universities = Array.from(universitiesToTeams.keys());
var teams = Array.from(universitiesToTeams.values());
var universitiesAndTeams = [];
universitiesToTeams.forEach(function (value, key) {
  universitiesAndTeams.push({ university: key, teams: value });
});

// 将映射对象展平为二维数组,方便在模板中循环
let teamsFlat = [];
for (let [university, teams] of universitiesToTeams) {
  teamsFlat.push(...teams);
}
  • 解释

这段代码的功能是将一个 Map 对象universitiesToTeams中的学校和队伍信息提取出来,并将其展平为一个二维数组teamsFlat和一个对象数组universitiesAndTeams,便于在模板中进行循环展示。

具体实现步骤如下:

  1. 通过addTeam函数向universitiesToTeams对象中添加学校和队伍信息,其中学校和队伍信息被存储为键值对的形式;

  2. 使用Array.from方法分别获取universitiesToTeams中的学校和队伍信息,并分别存储到universitiesteams变量中;

  3. 声明一个空数组universitiesAndTeams,通过forEach函数遍历universitiesToTeams对象,将学校和队伍信息存储为对象并添加到universitiesAndTeams数组中;

  4. 声明空数组teamsFlat,使用for...of循环遍历universitiesToTeams对象,将其中每一个学校对应的队伍信息展开为一维数组并添加到teamsFlat中;

通过以上步骤,代码可以将universitiesToTeams中的信息提取并处理,以便在其他部分中使用。


universitiesAndTeamsteamsFlat都是将universitiesToTeams中的学校和队伍信息提取并转化成的数组,但是它们的格式是不同的。

universitiesAndTeams是一个对象数组,其中每一个元素都表示一个学校及该学校对应的队伍信息,格式如下:

[
    { university: "学校名1", teams: ["队伍1", "队伍2"] },
    { university: "学校名2", teams: ["队伍3", "队伍4", "队伍5"] },
    ...
]

其中,university表示学校名,teams存储该学校名下的队伍列表。

teamsFlat则是一个一维数组,其中依次存储了universitiesToTeams中的每一个队伍信息,格式如下:

["队伍1", "队伍2", "队伍3", "队伍4", "队伍5", ...]

可以看到,teamsFlat中并没有学校名的信息,只是把每一个学校中所有的队伍都放到了同一个数组中。

因此,当需要同时展示学校名和队伍列表时,可以使用universitiesAndTeams;当只需要展示队伍列表时,可以使用teamsFlat

如何把多次分组的数据显示在页面上的同时,让其不发生有部分数据没有渲染成功的问题?

首先需要渲染合适的 div 数量

  • 方法 1,根据 group1 和 group2 的数据,来判断需要把原来 70 多个数据删除一部分,在重复点击多次 group 数据的时候,需要按照数据给增加/减少
//渲染合适的div数量!
function checkAndRenderGroup(group, teamNameElements, noElement, tableInnerID) {
  // 判断数组长度是否小于原来渲染的数量
  if (group.length < teamNameElements.length) {
    // 将多余的 div 删除
    for (let j = group.length; j < teamNameElements.length; j++) {
      teamNameElements[j].parentNode.remove();
    }
  } else if (group.length > teamNameElements.length) {
    // 向 box 元素中添加缺少的 div
    for (let j = teamNameElements.length; j < group.length; j++) {
      // const div = document.createElement('div');
      // div.classList.add('box');
      // div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
      // noElement[noElement.length - 1].parentNode.appendChild(div);

      const container = document.getElementById(tableInnerID);
      const div = document.createElement('div');
      div.classList.add('box');
      div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
      container.appendChild(div);
    }
  }
}
  • 方法 2,每次都全部删除,然后全部按照数据新建。
function checkAndRenderGroup(group, teamNameElements, noElement, tableInnerID) {
  // 删除 tableInnerID 下所有的 div
  const container = document.getElementById(tableInnerID);
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }

  // 向 box 元素中添加缺少的 div
  for (let j = 0; j < group.length; j++) {
    const div = document.createElement('div');
    div.classList.add('box');
    div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
    container.appendChild(div);
  }
  console.log('这个时候的teamNameElements:' + teamNameElements);
  console.log('这个时候的noElement:' + noElement);

  // 刷新 teamNameElements 和 noElement 的值
  teamNameElements = document.querySelectorAll(`#${tableInnerID} .teamName`);
  noElement = document.querySelectorAll(`#${tableInnerID} .no`);
}
  • 在填充数据的时候做对应的操作(就是需要刷新!!如果没有这个就会乱!因为新弄的 div 需要刷新生效。)
//填充数据2
function fillDataToElements(
  group,
  noElement,
  teamNameElements,
  schNameElement,
  tableInnerID
) {
  // 刷新 teamNameElements 和 noElement 的值
  teamNameElements = document.querySelectorAll(`#${tableInnerID} .teamName`);
  noElement = document.querySelectorAll(`#${tableInnerID} .no`);
  schNameElement = document.querySelectorAll(`#${tableInnerID} .schName`);
  for (let i = 0; i < group.length; i++) {
    if (i < noElement.length) {
      // 判断是否越界
      noElement[i].innerHTML = `${i + 1}`;
    }
    const item = group[i];
    displayTeamNameAndUniversity(item, i, teamNameElements, schNameElement);
  }
}

如何给表格+滚动条,并且设定滚动条样式?


.tableBox {
    /*background-color: rgba(222, 247, 231, 0.5);*/
    width: 100%;
    overflow: hidden;
    border-radius: 0 0 10px 10px;
    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
    /*background-color: #f7f7f7;*/
    padding-bottom: 20px;
    margin-bottom: 5px;
    overflow-y: auto;/*滚动条*/
}

/* 隐藏滚动条 */
.tableBox::-webkit-scrollbar {
    display: none;
}

/* 自定义滚动条样式 */
.tableBox::-webkit-scrollbar-track {
    /*background-color: transparent;*/
    /*background-color: rgba(255, 255, 255, 0.5);*/
    background-color: rgba(222, 247, 231, 0.5);

}
.leftTableBox::-webkit-scrollbar-track{
    background-color: #137798;
}

.tableBox::-webkit-scrollbar-thumb {
    background-color: #ccc;
    border-radius: 6px;
}
.table-wrap.custom-scrollbar::-webkit-scrollbar-thumb {
    min-width: 2px;
}



/* 鼠标悬停时显示滚动条 */
.tableBox:hover::-webkit-scrollbar {
    display: block;
}

/* 鼠标悬停时显示滚动条的滑块 */
.tableBox:hover::-webkit-scrollbar-thumb {
    background-color: #404140;
    min-width: 2px;
}

如何解决停止滚动的时候,回到顶部之后,还滚动了一段距离,而不是从序号 1 开始。

主要是用了判断,在滚了之后,停了之后再检测一下是不是到顶部了,不是再考虑再滚一段

//停止滚动并且回到第一条数据!
stopScroll() {
    const tableBox = this.$refs.tableBox;
    tableBox.scrollTop = 0;
        clearInterval(this.timer); // 清空循环定时器,停止滚动
        clearInterval(this.stepTime); // 清空单次滚动的定时器
        this.stopSign = true; // 标识定时器已经停止
    const timerId = setInterval(() => {
        if (tableBox.scrollTop === 0) {
            clearInterval(timerId);
            this.stopSign = true;
        } else {
            setTimeout(() => {
                this.stopSign = false;
                this.stopScroll();
            }, 100);
        }
    }, 100);
},

完整代码

gundong1.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
    <link rel="stylesheet" type="text/css" href="gundong.css" />
  </head>
  <body>
    <div id="app">
      <div id="left">
        <!--整个表tableOut-->
        <div class="tableoOut leftTableOut" ref="tableoOut">
          <!--表头-->
          <div class="tableTit leftTableTit" ref="tableTit">
            <div class="no">序号</div>
            <div class="schName">学校名称</div>
            <div class="teamName">队伍名称</div>
          </div>
          <!--tableBox表身-->
          <div
            class="tableBox leftTableBox"
            id="box11"
            tabindex="0"
            ref="tableBox"
            :style="{height: tableHei}"
          >
            <!--滚动的内容!(多列)-->
            <div class="tableInner" ref="tableInner" id="leftTableInner">
              <div
                class="box"
                v-for="(team, teamIndex) in teamsFlat"
                :key="teamIndex"
              >
                <div class="no">{{teamIndex + 1}}</div>
                <div class="schName">{{findUniversityByTeam(team)}}</div>
                <div class="teamName">{{team}}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div id="right">
        <!--整个表tableOut-->
        <div class="tableoOut rightTableOut" ref="tableoOut">
          <!--表头-->
          <div class="tableTit rightTableTit" ref="tableTit">
            <div class="no">序号</div>
            <div class="schName">学校名称</div>
            <div class="teamName">队伍名称</div>
          </div>
          <!--tableBox表身-->
          <div
            class="tableBox rightTableBox"
            id="box22"
            tabindex="0"
            ref="tableBoxRight"
            :style="{height: tableHei}"
          >
            <!--滚动的内容!(多列)-->
            <div class="tableInner" ref="tableInner" id="rightTableInner">
              <div
                class="box"
                v-for="(team, teamIndex) in teamsFlat"
                :key="teamIndex"
              >
                <div class="no">{{teamIndex + 1}}</div>
                <div class="schName">{{findUniversityByTeam(team)}}</div>
                <div class="teamName">{{team}}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div id="team-container"></div>
    <button id="group-button">Group</button>
  </body>
  <script src="gundong.js"></script>
</html>

gundong.css

/*美化的分割*/
#left {
  float: left;
  width: 48%;
  height: 100vh;
  overflow: hidden;
  margin-left: 2%;
  position: absolute;
  top: 15%;
  bottom: 15%;
}

#right {
  float: right;
  width: 48%;
  height: 100vh;
  overflow: hidden;
  margin-right: 2%;
  position: absolute;
  right: 0;
  top: 15%;
  bottom: 15%;
}

/*整体美化*/
body {
  /*background: #def7e7 url("../img/background-img.jpg") no-repeat fixed center;*/
  background-size: cover;
  background: linear-gradient(to right, #f97574, #f97574, #1b8ba5, #1b8ba5);
  justify-content: center;
  align-items: center;
  height: 75vh;
  /*height: 100%;*/
  /*margin:0;*/
}
/*按钮美化*/
#group-button {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 20px;
  border-radius: 5px;
  /*background-color: rgba(255, 255, 255, 0.5);*/
  background-color: #f97574;
  /*opacity: 0.5;*/
  font-size: 16px;
  color: #fffcfc;
  border: none;
  cursor: pointer;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
}

/*表格美化*/
.tableoOut {
  margin: 20px auto;
  width: 600px;
  height: 500px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  /*border-radius: 10px;*/
  /*box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);*/
  /*background-color: rgba(222, 247, 231, 0);*/
  /*background-color: #eaeff0;*/
}

.tableBox {
  /*background-color: rgba(222, 247, 231, 0.5);*/
  width: 100%;
  overflow: hidden;
  border-radius: 0 0 10px 10px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
  /*background-color: #f7f7f7;*/
  padding-bottom: 20px;
  margin-bottom: 5px;
  overflow-y: auto; /*滚动条*/
}

.tableTit {
  /*background: #ffffff;*/
  background-color: rgba(222, 247, 231, 0.5);

  width: 100%;
  height: 40px;
  color: #858a84;
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 10px 10px 0 0;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
}

.tableInner {
  height: auto;
}

.box {
  /*background-color: rgba(222, 247, 231, 0.5);*/

  width: 100%;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #333333;
  /*background-color: #ffffff;*/
  /*border-radius: 0 0 10px 10px;*/
}

.box .teamName {
  color: #858a84;
}

.tableoOut .schName,
.tableoOut .teamName,
.tableoOut .no {
  box-sizing: border-box;
  padding: 0 5px;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.tableoOut {
  width: calc(100% - 10px);
  flex-shrink: 0;
}

.schName {
  width: 40%;
  flex-shrink: 0;
}
.no {
  width: 15%;
  flex-shrink: 0;
}
.teamName {
  width: 45%;
}
/* 隐藏滚动条 */
.tableBox::-webkit-scrollbar {
  display: none;
}

/* 自定义滚动条样式 */
.tableBox::-webkit-scrollbar-track {
  /*background-color: transparent;*/
  /*background-color: rgba(255, 255, 255, 0.5);*/
  background-color: rgba(222, 247, 231, 0.5);
}
.leftTableBox::-webkit-scrollbar-track {
  background-color: #137798;
}

.tableBox::-webkit-scrollbar-thumb {
  background-color: #ccc;
  border-radius: 6px;
}
.table-wrap.custom-scrollbar::-webkit-scrollbar-thumb {
  min-width: 2px;
}

/* 鼠标悬停时显示滚动条 */
.tableBox:hover::-webkit-scrollbar {
  display: block;
}

/* 鼠标悬停时显示滚动条的滑块 */
.tableBox:hover::-webkit-scrollbar-thumb {
  background-color: #404140;
  min-width: 2px;
}

/*颜色*/
.leftTableOut {
  /*background-color: #147a94;*/
  /*background-image: linear-gradient(to bottom right, #147a94, #d5edff);*/
  /*background-image: linear-gradient(to left, #147a94, transparent 50%, #d5edff);*/
  /*background-image: linear-gradient(to left, #147a94, #d5edff, #147a94);*/
  /*background-image: linear-gradient(to left, #d5edff, #147a94, #d5edff);*/

  height: 70%;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background-color: transparent;
}
.rightTableOut {
  height: 70%;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background-color: transparent;
}
.leftTableTit {
  background-color: #043c50;
  color: #fafeff;
}
.rightTableTit {
  /*background-color: #e7c76b;*/
  background-color: #f97574;
  /*border: 1px solid red;*/
  /*background-color: #79b9be;*/
  color: #fafeff;
}
.leftTableBox {
  background-image: linear-gradient(to bottom right, #147a94, #116cae);
}
.rightTableBox {
  /*background-image: linear-gradient(to bottom right, #147a94, #027ee6);*/
  background-color: #ffffff;
}

#box11 .box {
  color: #ffffff;
}
#box11 .teamName {
  color: #ffffff;
}
#box22 .box {
  color: #828282;
  /*border: 1px red solid;*/
}
#box22 .teamName {
  color: #828282;
}

gundong.js

var universitiesToTeams = new Map();
addTeam(universitiesToTeams, '浙江大学', '浙大-team1');
addTeam(universitiesToTeams, '浙江大学', '浙大-team2');
addTeam(universitiesToTeams, '浙江大学', '浙大-team3');
addTeam(universitiesToTeams, '中国美术学院', '中美-team1');
addTeam(universitiesToTeams, '中国美术学院', '中美-team2');
addTeam(universitiesToTeams, '浙江工业大学', '浙工大-team1');
addTeam(universitiesToTeams, '浙江工业大学', '浙工大-team2');
addTeam(universitiesToTeams, '浙江师范大学', '浙师大-team1');
addTeam(universitiesToTeams, '浙江师范大学', '浙师大-team2');
addTeam(universitiesToTeams, '宁波大学', '宁波大-team1');
addTeam(universitiesToTeams, '宁波大学', '宁波大-team2');
addTeam(universitiesToTeams, '杭州电子科技大学', '杭电-team1');
addTeam(universitiesToTeams, '杭州电子科技大学', '杭电-team2');
addTeam(universitiesToTeams, '浙江工商大学', '浙商大-team1');
addTeam(universitiesToTeams, '浙江工商大学', '浙商大-team2');
addTeam(universitiesToTeams, '浙江理工大学', '浙理工-team1');
addTeam(universitiesToTeams, '浙江理工大学', '浙理工-team2');
addTeam(universitiesToTeams, '温州医科大学', '温医-team1');
addTeam(universitiesToTeams, '温州医科大学', '温医-team2');
addTeam(universitiesToTeams, '温州医科大学', '温医-team3');
addTeam(universitiesToTeams, '浙江海洋大学', '海洋大-team1');
addTeam(universitiesToTeams, '浙江海洋大学', '海洋大-team2');
addTeam(universitiesToTeams, '浙江农林大学', '农林大-team1');
addTeam(universitiesToTeams, '浙江农林大学', '农林大-team2');
addTeam(universitiesToTeams, '浙江中医药大学', '中药大-team1');
addTeam(universitiesToTeams, '浙江中医药大学', '中药大-team2');
addTeam(universitiesToTeams, '中国计量大学', '计量大-team1');
addTeam(universitiesToTeams, '中国计量大学', '计量大-team2');
addTeam(universitiesToTeams, '中国计量大学', '计量大-team3');
addTeam(universitiesToTeams, '浙江万里学院', '万里院-team1');
addTeam(universitiesToTeams, '浙江万里学院', '万里院-team2');
addTeam(universitiesToTeams, '浙江科技学院', '浙科院-team1');
addTeam(universitiesToTeams, '浙江科技学院', '浙科院-team2');
addTeam(universitiesToTeams, '浙江财经大学', '浙财大-team1');
addTeam(universitiesToTeams, '浙江财经大学', '浙财大-team2');
addTeam(universitiesToTeams, '嘉兴学院', '嘉院-team1');
addTeam(universitiesToTeams, '嘉兴学院', '嘉院-team2');
addTeam(universitiesToTeams, '浙大城市学院', '城市院-team1');
addTeam(universitiesToTeams, '浙大城市学院', '城市院-team2');
addTeam(universitiesToTeams, '浙大宁波理工学院', '宁波理工-team1');
addTeam(universitiesToTeams, '浙大宁波理工学院', '宁波理工-team2');
addTeam(universitiesToTeams, '杭州师范大学', '杭师大-team1');
addTeam(universitiesToTeams, '杭州师范大学', '杭师大-team2');
addTeam(universitiesToTeams, '湖州师范学院', '湖师院-team1');
addTeam(universitiesToTeams, '湖州师范学院', '湖师院-team2');
addTeam(universitiesToTeams, '绍兴文理学院', '绍文理-team1');
addTeam(universitiesToTeams, '绍兴文理学院', '绍文理-team2');
addTeam(universitiesToTeams, '台州学院', '台院-team1');
addTeam(universitiesToTeams, '台州学院', '台院-team2');
addTeam(universitiesToTeams, '温州大学', '温大-team1');
addTeam(universitiesToTeams, '温州大学', '温大-team2');
addTeam(universitiesToTeams, '温州大学', '温大-team3');
addTeam(universitiesToTeams, '浙江外国语学院', '外国语-team1');
addTeam(universitiesToTeams, '浙江外国语学院', '外国语-team2');
addTeam(universitiesToTeams, '浙江传媒学院', '传媒院-team1');
addTeam(universitiesToTeams, '浙江传媒学院', '传媒院-team2');
addTeam(universitiesToTeams, '浙江传媒学院', '传媒院-team3');
addTeam(universitiesToTeams, '宁波工程学院', '宁波工-team1');
addTeam(universitiesToTeams, '宁波工程学院', '宁波工-team2');
addTeam(universitiesToTeams, '宁波工程学院', '宁波工-team3');
addTeam(universitiesToTeams, '衢州学院', '衢院-team1');
addTeam(universitiesToTeams, '衢州学院', '衢院-team2');
addTeam(universitiesToTeams, '浙江水利水电学院', '浙水利-team1');
addTeam(universitiesToTeams, '浙江水利水电学院', '浙水利-team2');
addTeam(universitiesToTeams, '浙江树人学院', '浙树院-team1');
addTeam(universitiesToTeams, '浙江树人学院', '浙树院-team2');
addTeam(universitiesToTeams, '杭州医学院', '杭医-team1');
addTeam(universitiesToTeams, '杭州医学院', '杭医-team2');
addTeam(universitiesToTeams, '丽水学院', '丽院-team1');
addTeam(universitiesToTeams, '丽水学院', '丽院-team2');
addTeam(universitiesToTeams, '丽水学院', '丽院-team3');
// 从Map对象获取所有学校和队伍的数组
var universities = Array.from(universitiesToTeams.keys());
var teams = Array.from(universitiesToTeams.values());
var universitiesAndTeams = [];
universitiesToTeams.forEach(function (value, key) {
  universitiesAndTeams.push({ university: key, teams: value });
});
console.log(universitiesAndTeams);

// 将映射对象展平为二维数组,方便在模板中循环
let teamsFlat = [];
for (let [university, teams] of universitiesToTeams) {
  teamsFlat.push(...teams);
}

var groupButton = document.getElementById('group-button');
//给group-button设定了单击事件
groupButton.addEventListener('click', function () {
  var group1 = [];
  var group2 = [];

  universitiesToTeams.forEach(function (teamList) {
    shuffle(teamList); //打乱teamlist,也就是打乱高校内部的队伍名。
    var size = teamList.length;
    // var mid = Math.floor(size / 2);
    /*如果队伍是奇数,那么就把前面的分了,最后一个随机分配到1,2两个队伍里*/
    if (size % 2 != 0) {
      // var rand = Math.floor(Math.random() * 2);
      // if (rand == 0) {
      //     mid += 1;
      // }
      group1.push(teamList[0]);
      group2.push(teamList[1]);
      //奇数的时候,把第三个给少的队伍!
      if (group1.length < group2.length) {
        group1.push(teamList[2]);
      } else {
        group2.push(teamList[2]);
      }
    } else {
      //偶数的时候,一个队伍一个
      group1.push(teamList[0]);
      group2.push(teamList[1]);
    }

    // var size = teamList.length;
    // var mid = Math.ceil(size / 2);
    // if (size % 2 != 0) {
    //     if (group1.length < group2.length) {
    //         group1.push(teamList[size-1]);
    //     } else {
    //         group2.push(teamList[size-1]);
    //     }
    //     teamList.pop();
    // }
    // group1.push.apply(group1, teamList.slice(0, mid));
    // group2.push.apply(group2, teamList.slice(mid));
  });

  console.log('group1:' + group1);
  console.log('group2:' + group2);

  //打乱所有队伍!
  shuffle(group1);
  shuffle(group2);

  // 获取所有的box下的子元素
  const teamNameElements = document.querySelectorAll(
    '#box11 #leftTableInner .box .teamName'
  );
  const schNameElement = document.querySelectorAll(
    '#box11 #leftTableInner .box .schName'
  );
  const noElement = document.querySelectorAll(
    '#box11 #leftTableInner .box .no'
  );
  const teamNameElements2 = document.querySelectorAll(
    '#box22 #rightTableInner .box .teamName'
  );
  const schNameElement2 = document.querySelectorAll(
    '#box22 #rightTableInner .box .schName'
  );
  const noElement2 = document.querySelectorAll(
    '#box22 #rightTableInner .box .no'
  );
  // 检查和渲染左侧队伍
  checkAndRenderGroup(group1, teamNameElements, noElement, 'leftTableInner');
  // 检查和渲染右侧右侧
  checkAndRenderGroup(group2, teamNameElements2, noElement2, 'rightTableInner');

  //填充数据
  fillDataToElements(
    group1,
    noElement,
    teamNameElements,
    schNameElement,
    'leftTableInner'
  );
  fillDataToElements(
    group2,
    noElement2,
    teamNameElements2,
    schNameElement2,
    'rightTableInner'
  );
});

/*
这是一个JavaScript函数,它接受三个参数:一个Map对象,一个键k和一个值v。
如果Map对象已经包含键k,则将值v添加到与该键关联的值列表中。
否则,创建一个新的值列表,并将值v添加到该列表中,然后将该列表与键k一起添加到Map对象中。
 */
function addTeam(map, k, v) {
  if (map.has(k)) {
    map.get(k).push(v);
  } else {
    var lst = [v];
    map.set(k, lst);
  }
}
//加入一个方法
// 查找给定队伍对应的学校
function findUniversityByTeam(team) {
  for (let [university, teams] of universitiesToTeams) {
    if (teams.indexOf(team) !== -1) {
      return university;
    }
  }
  return '';
}
/*
打乱数组,这个方法用来打乱相同高校的队伍。
 */
function shuffle(array) {
  for (var i = array.length - 1; i > 0; i--) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
}

function displayTeamNameAndUniversity(item, i, teamNameElems, schNameElems) {
  if (i >= teamNameElems.length || i >= schNameElems.length) {
    return; // 如果超出了范围,则直接返回
  }
  teamNameElems[i].innerHTML = item;
  schNameElems[i].innerHTML = findUniversityByTeam(item);
}
//渲染合适的div数量!
function checkAndRenderGroup(group, teamNameElements, noElement, tableInnerID) {
  // 判断数组长度是否小于原来渲染的数量
  if (group.length < teamNameElements.length) {
    // 将多余的 div 删除
    for (let j = group.length; j < teamNameElements.length; j++) {
      teamNameElements[j].parentNode.remove();
    }
  } else if (group.length > teamNameElements.length) {
    // 向 box 元素中添加缺少的 div
    for (let j = teamNameElements.length; j < group.length; j++) {
      // const div = document.createElement('div');
      // div.classList.add('box');
      // div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
      // noElement[noElement.length - 1].parentNode.appendChild(div);

      const container = document.getElementById(tableInnerID);
      const div = document.createElement('div');
      div.classList.add('box');
      div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
      container.appendChild(div);
    }
  }
}

// function checkAndRenderGroup(group, teamNameElements, noElement, tableInnerID) {
//     // 删除 tableInnerID 下所有的 div
//     const container = document.getElementById(tableInnerID);
//     while (container.firstChild) {
//         container.removeChild(container.firstChild);
//     }
//
//     // 向 box 元素中添加缺少的 div
//     for (let j = 0; j < group.length; j++) {
//         const div = document.createElement('div');
//         div.classList.add('box');
//         div.innerHTML = `<div class="no"></div><div class="schName"></div><div class="teamName"></div>`;
//         container.appendChild(div);
//     }
//     console.log("这个时候的teamNameElements:"+teamNameElements);
//     console.log("这个时候的noElement:"+noElement);
//
//     // 刷新 teamNameElements 和 noElement 的值
//     teamNameElements = document.querySelectorAll(`#${tableInnerID} .teamName`);
//     noElement = document.querySelectorAll(`#${tableInnerID} .no`);
// }

//填充数据2
function fillDataToElements(
  group,
  noElement,
  teamNameElements,
  schNameElement,
  tableInnerID
) {
  // 刷新 teamNameElements 和 noElement 的值
  teamNameElements = document.querySelectorAll(`#${tableInnerID} .teamName`);
  noElement = document.querySelectorAll(`#${tableInnerID} .no`);
  schNameElement = document.querySelectorAll(`#${tableInnerID} .schName`);
  for (let i = 0; i < group.length; i++) {
    if (i < noElement.length) {
      // 判断是否越界
      noElement[i].innerHTML = `${i + 1}`;
    }
    const item = group[i];
    displayTeamNameAndUniversity(item, i, teamNameElements, schNameElement);
  }
}

new Vue({
  el: '#app', // Vue 实例挂载的元素 ID
  data: {
    tableHei: 'auto', // 表格容器高度
    timer: null, // 定时器句柄
    size: 0, // 计算表格行数
    stopSign: true, // 判断定时器是否停止标识
    stepTime: null, // 改为全局定时器
    universities: universities, // 数据源:大学列表
    teams: teams, // 数据源:参赛队伍列表
  },
  mounted() {
    const that = this;
    const tableBox = this.$refs.tableBox;
    const tableBoxRight = this.$refs.tableBoxRight;
    // 同时给左右两个列表容器元素添加scroll事件监听器,并将当前滚动位置同步到另一侧列表容器
    tableBox.addEventListener('scroll', function (e) {
      tableBoxRight.scrollTop = this.scrollTop;
    });
    tableBoxRight.addEventListener('scroll', function (e) {
      tableBox.scrollTop = this.scrollTop;
    });

    this.getTable(); // 获取表格行数并设置表格容器高度,启动循环滚动

    // 获取按钮元素
    const groupButton = document.getElementById('group-button');
    // 给按钮添加点击事件
    groupButton.addEventListener('click', () => {
      this.stopScroll(); // 调用 stopScroll() 方法
    });
  },
  methods: {
    getTable() {
      const outHei = this.$refs.tableoOut.clientHeight - 60; // 获取表格容器高度
      this.size = Math.floor(outHei / 58); // 计算表格行数
      this.tableHei = this.size * 58 + 'px'; // 设置表格容器高度
      this.scrolls(); // 启动循环滚动
    },
    stepScroll() {
      const that = this;
      const step = 50; // 每次滚动的距离
      let num = 0; // 每次已滚动的距离
      const tableBox = this.$refs.tableBox; // 表格容器

      // 改为全局定时器,且在调用前先进行清空
      clearInterval(this.stepTime);

      function animateScroll() {
        // 每次滚动4px
        tableBox.scrollTop += 4;

        if (num < step) {
          num += 4;
          // 不断地执行 animateScroll() 函数,直到滚动距离达到指定值
          requestAnimationFrame(animateScroll);
        } else {
          num = 0;
          that.stopSign = true; // 标识定时器已停止
        }
      }

      // 使用 requestAnimationFrame 执行滚动动画
      requestAnimationFrame(animateScroll);
    },

    scrolls() {
      const that = this;
      const tableBox = this.$refs.tableBox; // 表格容器
      const tableInner = this.$refs.tableInner; // 表格内容区域
      clearInterval(this.timer); // 在启动定时器前先清空之前的计时器
      this.timer = setInterval(function () {
        // 修改定时器结束判断条件:当滚动距离大于等于内容区域高度时,重新滚动到顶部
        if (
          tableBox.scrollTop + tableBox.clientHeight >=
          tableInner.scrollHeight
        ) {
          tableBox.scrollTop = 0;
        }
        that.stepScroll(); // 执行一次 stepScroll(),即向下滚动一段距离
      }, 50); // 定时器循环周期为 50ms
    },

    //停止滚动并且回到第一条数据!
    stopScroll() {
      const tableBox = this.$refs.tableBox;
      tableBox.scrollTop = 0;
      clearInterval(this.timer); // 清空循环定时器,停止滚动
      clearInterval(this.stepTime); // 清空单次滚动的定时器
      this.stopSign = true; // 标识定时器已经停止
      const timerId = setInterval(() => {
        if (tableBox.scrollTop === 0) {
          clearInterval(timerId);
          this.stopSign = true;
        } else {
          setTimeout(() => {
            this.stopSign = false;
            this.stopScroll();
          }, 100);
        }
      }, 100);
    },
  },
});

页面展示

  • 正在滚动的页面

image-20230509175411419

  • 分组之后(当我鼠标放上去才会有滚动条出现)

image-20230509175452149

image-20230721213248880

2.最终成品部分

问题与解决

前端的设计,好几个页面

首先在 vscode 用 html 的形式设定好几个静态页面。比较满意的,并且用 js 生成模拟数据。

确定好 html 的形式满意之后,在改成对应的 jsp 页面

然后用 idea 打开,运行看看(由于设定了视图解析器,并且把 jsp 那些页面设定了后端才能访问,所以其实有点麻烦,)不过可以慢慢配,先看首页什么的。

首页如何弄好看?

不能瞎弄,而是去参考别人设定好的类似的模板。

接着参考着选合适的就行。

controller 和 jsp 之间的数据传递(让前端能够拿到数据库里的数据并且显示出来,不显示也行)

因为用的是 ssm 框架。

举个最简单的数据传递的例子

  • 后段设置属性
@Autowired
@Qualifier("TeamServiceImpl")
private TeamService teamService;

@RequestMapping("/allTeam")
public String list(Model model) {
    List<Team> list = teamService.queryAllTeam();
    model.addAttribute("list1", list);
    System.out.println("测试看看" + list);
    return "jn-number";
}

这里通过 model.addAttribute("list1", list);,把数据库查询到的 list 集合数据设置成属性。

  • 前端接收
<tbody>
        <c:forEach var="team" items="${requestScope.get('list1')}" begin="0" end="46">
            <tr>
                <td>${team.getSerialNumber()}</td>
                <td>${team.getSchoolName()}</td>
                <td>${team.getTeamName()}</td>
                <td class="team-number">NO.${team.getTeamNumber()}</td>
            </tr>
        </c:forEach>
        </tbody>

通过 c:forEach,循环遍历后端传过来的 list 集合。并且把 list 集合里边的每一个 team 对象设置成 team。

通过 team 对象来获取对应的数据库的字段值。就可以显示了。

对于 begin 和 end 对应的是 list 集合当中的第 0 条到第 46 条数据。(一共 47 条数据)

ajax 请求的使用(ajax 参数类型对应后端的参数类型)

首先导入两个包

    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.12.4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/json2/20160511/json2.min.js"></script>

接着演示 ajax 请求的几种类型。

  • 传递数组
$('#update-button').click(function () {
  // var randomArray = ["apple", "pear", "banana"];
  var randomArray = generateRandomArray();
  $.ajax({
    url: '/ssm/team/updateTeamNum',
    type: 'POST',
    data: { 'randomArray[]': randomArray },
    success: function (response) {
      if (response != null) {
        console.log(response);
        //刷新当前页
        location.reload();
      } else {
        console.log('失败了');
      }
    },
  });
});

接收方法

@RequestMapping(value = "/updateTeamNum", method = RequestMethod.POST)
@ResponseBody
public String updateTeam(@RequestParam("randomArray[]") int[] randomArray){}
  • 传递数字
$.ajax({
  url: '/ssm/team/updateTeamTestLock',
  type: 'POST',
  data: { locked: 1 },
  success: function (data) {
    // 更新按钮文本和样式
    var btn = $('.lock1');
    var locked = data;
    if (locked == '2' || locked == '3') {
      btn.text('已锁定');
      btn.css('background-color', '#ccc');
      const numberButton = document.getElementById('update-button');
      numberButton.disabled = true; /*已锁定则禁用!*/
    } else {
      btn.text('锁定');
      const numberButton = document.getElementById('update-button');
      numberButton.disabled = false;
    }
  },
});
@RequestMapping(value = "/updateTeamTestLock", method = RequestMethod.POST)
    @ResponseBody
    public String updateTeam1(@RequestParam("locked") int locked) {}
  • 传递字符串
<form action="/ssm/team/process-form" method="POST">
  <label for="password"><b>密码:</b></label>
  <input type="password" placeholder="请输入密码" name="password" required />
  <button type="submit" class="submit-button">确认</button>
</form>
@PostMapping("/process-form")
public String processForm(@RequestParam("password") String password,Model model) {}
  • 传递多个字符串
<form action="/ssm/team/process-form1" method="POST">
  <label for="password">
    <b>密码:</b>
  </label>
  <input type="password" placeholder="请输入密码" name="password" required />
  <!-- <label for="team"><b>晋级的队伍编号:</b></label>
            <input type="text" placeholder="请输入晋级的队伍编号" name="team" required> -->
  <label for="teams"><b>晋级的队伍编号(多个编号以逗号分隔):</b></label>

  <textarea
    placeholder="请输入晋级的队伍编号,以逗号分隔"
    id="teams"
    name="teams"
    required
    style="padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-top: 5px;
margin-bottom: 10px;
resize: vertical;
width: 100%;
height: 120px;
font-size: 16px;
line-height: 24px;
color: black;
outline: none;"
  >
  </textarea>

  <button type="submit" class="submit-button">确认</button>
</form>
@PostMapping("/process-form1")
public String processForm(@RequestParam("password") String password,
                          @RequestParam("teams") String teams,Model model) {}

对应的 ajax 应该是这样的

$.ajax({
  type: 'POST',
  url: '/process-form1', // 接口地址
  data: {
    password: '123456', // 表单数据
    teams: 'Team A,Team B,Team C',
  },
  success: function (data) {
    console.log('请求成功:', data);
  },
  error: function (xhr, error) {
    console.log('请求失败:', error);
  },
});

打印功能,如何对表格实行分页以及,表头和表身打印需要的部分?

前端设置这个

<button class="print" onclick="window.print()">打印</button>

css 中设置

@media print {
  /*设定不需要打印的部分*/
  #update-button,
  .lock1,
  .print {
    display: none;
  }
  /*设定表格数据分页的时候要一行满了再分页*/
  table tr {
    page-break-inside: avoid;
  }
}

给每个板块设定密码框,要输入正确的密码才能进去。

<form action="/ssm/team/process-form1" method="POST">
  <label for="password">
    <b>密码:</b>
  </label>
  <input type="password" placeholder="请输入密码" name="password" required />
  <!-- <label for="team"><b>晋级的队伍编号:</b></label>
            <input type="text" placeholder="请输入晋级的队伍编号" name="team" required> -->
  <label for="teams"><b>晋级的队伍编号(多个编号以逗号分隔):</b></label>

  <textarea
    placeholder="请输入晋级的队伍编号,以逗号分隔"
    id="teams"
    name="teams"
    required
    style="padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-top: 5px;
margin-bottom: 10px;
resize: vertical;
width: 100%;
height: 120px;
font-size: 16px;
line-height: 24px;
color: black;
outline: none;"
  >
  </textarea>

  <button type="submit" class="submit-button">确认</button>
</form>
/*技能赛道决赛密码框*/
@PostMapping("/process-form1")
public String processForm(@RequestParam("password") String password,
                          @RequestParam("teams") String teams,Model model) {
    // 对接收到的表单数据进行处理
    System.out.println("password = " + password);
    System.out.println("teams = " + teams);
    if(password.equals("js")){

        List<Integer> list = new ArrayList<>();
        for (String s : teams.split(",")) {
            list.add(Integer.valueOf(s));
        }
        Collections.shuffle(list);
        int length = list.size();
        model.addAttribute("list4",list);

        //这里需要保证除了我输入之外的队伍编号的final有数据,其他的final都是0。
        //首先我的得遍历list,这里的list是队伍编号,(有可能会和下面的重复)
        //我需要通过队伍编号找到对应的team,然后查找team的final数据。(不在找到的范围里的呢。就把final设定为0即可)
        //不是队伍编号找到对team的final设置成0.(很绕!)
        //简单来说遍历list找到的相反的才设定为0。难道不遍历list吗?
        //不如遍历整个47条数据,查找队伍编号,当队伍编号不属于list集合里的,就设定为0。(成功!差点没把我绕晕!)
        for (int i = 0; i < 47; i++) {
                Team team = teamService.queryTeamById(i+1);
                if(!list.contains(team.getTeamNumber())){//如果list集合当中没有包含的队伍编号。
                    team.setFinalOrder(0);
                    team.setType2(0);
                    teamService.updateTeam(team);//更新!

                }else {
                    System.out.println("==================================");
                    System.out.println("输出满足的队伍编号(不用设置成0的):"+team.getTeamNumber());
                    System.out.println("==================================");

                }
        }
        /*这里才是更改过后的,打印出来不为final不为0的数据!*/

        for (int i = 0; i < 47; i++) {
            Team team = teamService.queryTeamById(i+1);
            if(team.getFinalOrder()!=0){
                System.out.print("hhhh"+team.getTeamNumber()+",");
            }
        }


        //找到现在找type2为1和2的按照决赛顺序的排列显示出来即可。
        teamService.getTeamsByTypeOrderByOrderNum2(1);//找到决赛左边
        teamService.getTeamsByTypeOrderByOrderNum2(2);//找到决赛右边
        List<Integer> teamNumber1 = new ArrayList<>();
        List<Integer> teamNumber2 = new ArrayList<>();

        List<Team> teamList1 = teamService.getTeamsByTypeOrderByOrderNum2(1);
        List<Team> teamList2 = teamService.getTeamsByTypeOrderByOrderNum2(2);
        for (Team team : teamList1){
            teamNumber1.add(team.getTeamNumber());
        }
        for (Team team : teamList2){
            teamNumber2.add(team.getTeamNumber());
        }

        model.addAttribute("group1Number",teamNumber1);
        model.addAttribute("group2Number",teamNumber2);

        return "jn-js";
    }else {
        return "redirect:/index.jsp";
    }
}

实现锁定功能

  • 前端

需要在刚进入页面的时候就从数据库里拿数据(锁定字段),判断当前的锁定状态,然后在前端显示

$(document).ready(function () {
  /*页面刚加载就执行锁定按钮!*/
  $.ajax({
    url: '/ssm/team/updateTeamTestLock',
    type: 'POST',
    data: { locked: 1 },
    success: function (data) {
      // 更新按钮文本和样式
      var btn = $('.lock1');
      var locked = data;
      if (locked == '2' || locked == '3') {
        btn.text('已锁定');
        btn.css('background-color', '#ccc');
        const numberButton = document.getElementById('update-button');
        numberButton.disabled = true; /*已锁定则禁用!*/
      } else {
        btn.text('锁定');
        const numberButton = document.getElementById('update-button');
        numberButton.disabled = false;
      }
    },
  });
});

接着在点击锁定按钮的时候,实现加锁和解锁操作(对应修改数据库的 lock 字段)

$(function () {
  $('.lock1').click(function () {
    // 发送 Ajax 请求
    $.ajax({
      url: '/ssm/team/updateTeamLock',
      type: 'POST',
      data: { locked: 1 }, // 锁定
      success: function (data) {
        // 更新按钮文本和样式
        var btn = $('.lock1');
        var locked = data;
        if (locked == '1' || locked == '2' || locked == '3') {
          btn.text('已锁定');
          btn.css('background-color', '#ccc');
          const numberButton = document.getElementById('update-button');
          numberButton.disabled = true; /*已锁定则禁用!*/
        } else {
          btn.text('锁定');
          const numberButton = document.getElementById('update-button');
          numberButton.disabled = false;
        }
      },
    });
  });
});
  • 后端
@RequestMapping(value = "/updateTeamTestLock", method = RequestMethod.POST)
@ResponseBody
public String updateTeam1(@RequestParam("locked") int locked) {
    System.out.println("前端传过来的lock:"+locked);
    Team team = teamService.queryTeamById(1);
    int locked1 = team.getLockStatus();
    System.out.println("赋值后的locked:"+locked1);
    return Integer.toString(locked1);
}
@RequestMapping(value = "/updateTeamLock", method = RequestMethod.POST)
    @ResponseBody
    public String updateTeam2(@RequestParam("locked") int locked) {
        /*如果原本为1/2/3 ,说明是已锁定,这个时候返回为0,让locked=0*/
        Team team1 = teamService.queryTeamById(1);
        int locked1 = team1.getLockStatus();
        if(locked1==1 || locked1==2 || locked1==3){
            System.out.println("打印一下原来锁定的lock1");
            locked1--;/*原来锁定了,现在要解锁,也就是降级!*/
        }else {
            locked1++;/*原来就是未锁定状态,也就是0,需要++变成1咯*/
        }
        //更新所有锁定状态!
        for (int i = 0; i < 47; i++) {
            int id = i + 1;
            Team team =  teamService.queryTeamById(id);
            team.setId(id);
            team.setLockStatus(locked1);//为1/2/3的时候就是已锁定,为0的时候,就是未锁定!
            teamService.updateTeam(team);
        }
        System.out.println("锁定状态更改成功!");

        return Integer.toString(locked1);
    }

实现随机分组功能,并把分成两组的乱序数据给排列出场顺序。

/**
     * 修改队伍的初赛顺序!
     * @return
     */
    @RequestMapping(value = "/updateTeamCS", method = RequestMethod.POST)
    @ResponseBody
    public String updateTeam3(@RequestParam("randomArray[]") int[] randomArray,Model model) {
        System.out.println("测试进来初赛没");
        /**
         * 拿到乱序的组编号和id!
         */
        System.out.println("打印前端传过来的组"+randomArray);
        Map<String, Map<Integer, Integer>> universitiesToTeams = new HashMap<>();
//            addTeam(universitiesToTeams, "University A", 34, 1);
        /*试试看遍历数据库的1-47套条数据,弄一个for循环把数据填进去!*/
        for (int i = 0; i < 47; i++) {
            int id = i + 1;
            Team team =  teamService.queryTeamById(id);
            addTeam(universitiesToTeams, team.getSchoolName(), team.getTeamNumber(), team.getId());
//                addTeam(universitiesToTeams, team.getSchoolName(), team.getTeamName());
        }
        Map<String, Map<Integer, Integer>> groups = getRandomGroups(universitiesToTeams);

        model.addAttribute("group1Number",groups.get("group1").keySet());
        model.addAttribute("group2Number",groups.get("group2").keySet());
        model.addAttribute("group1ID",groups.get("group1").values());
        model.addAttribute("group2ID",groups.get("group2").values());

        // 获取 group1 中的队伍编号和队伍 ID
        int index = 0;
            Map<Integer, Integer> group1 = groups.get("group1");
            for (Map.Entry<Integer, Integer> entry : group1.entrySet()) {
                Integer number = entry.getKey(); // 队伍编号
                Integer id = entry.getValue(); // 队伍 ID
                Team team = teamService.queryTeamById(id);
                team.setPreOrder(index);//把初赛顺序给放到数据库里
                team.setType(1);
                index++;
                teamService.updateTeam(team);
//                System.out.println("group1: number = " + number + ", id = " + id);
            }

            // 获取 group2 中的队伍编号和队伍 ID
        int index2 = 0;
            Map<Integer, Integer> group2 = groups.get("group2");
            for (Map.Entry<Integer, Integer> entry : group2.entrySet()) {
                Integer number = entry.getKey(); // 队伍编号
                Integer id = entry.getValue(); // 队伍 ID
                Team team = teamService.queryTeamById(id);
                team.setPreOrder(index2);//把初赛顺序给放到数据库里
                team.setType(2);
                index2++;
                teamService.updateTeam(team);
//                System.out.println("group2: number = " + number + ", id = " + id);

            }

        System.out.println("技能初赛顺序搞定!!");

        return "成功";
    }

其中的工具类

TeamSplit

package com.lovi.utils;

import java.util.*;

public class TeamSplit {

    public static Map<String, Map<Integer, Integer>> getRandomGroups(Map<String, Map<Integer, Integer>> universitiesToTeams) {

    Map<String, List<Integer>> groups = new HashMap<>();
    List<Integer> group1 = new ArrayList<>();
    List<Integer> group2 = new ArrayList<>();

    // 遍历高校-队伍编号-队伍ID列表,为每个高校的队伍编号-队伍ID列表打乱顺序并分配到不同的列表中
    for (Map.Entry<String, Map<Integer, Integer>> universityToTeamList : universitiesToTeams.entrySet()) {
        Map<Integer, Integer> teamMap = universityToTeamList.getValue();
        List<Integer> teamNumbers = new ArrayList<>(teamMap.keySet());
        Collections.shuffle(teamNumbers);/*把队伍编号打乱了*/

        int size = teamNumbers.size();
        if (size % 2 != 0) {
            group1.add(teamNumbers.get(0));
            group2.add(teamNumbers.get(1));
            if (group1.size() < group2.size()) {
                group1.add(teamNumbers.get(2));
            } else {
                group2.add(teamNumbers.get(2));
            }
        } else {
            group1.add(teamNumbers.get(0));
            group2.add(teamNumbers.get(1));
        }
    }
        Collections.shuffle(group1);
        Collections.shuffle(group2);


        Map<String, List<Integer>> groups1 = new LinkedHashMap<>();
        // 如果组元素不平均,交换 group1 和 group2 的内容
        if (group1.size() < group2.size()) {
            List<Integer> temp = group1;
            group1 = group2;
            group2 = temp;
        }
        groups1.put("group1", group1);
        groups1.put("group2", group2);



// 将每组队伍编号和 ID 按顺序组装成 Map
        Map<String, Map<Integer, Integer>> result = new HashMap<>();
        for (Map.Entry<String, List<Integer>> groupEntry : groups1.entrySet()) {
            String groupId = groupEntry.getKey();
            List<Integer> groupNumbers = groupEntry.getValue();
            Map<Integer, Integer> groupIds = new LinkedHashMap<>();

            for (Integer teamNumber : groupNumbers) {
                for (Map.Entry<String, Map<Integer, Integer>> universityToTeamList : universitiesToTeams.entrySet()) {
                    Map<Integer, Integer> teamMap = universityToTeamList.getValue();
                    if (teamMap.containsKey(teamNumber)) {
                        Integer teamId = teamMap.get(teamNumber);
                        groupIds.put(teamNumber, teamId);
                        break;
                    }
                }
            }

            result.put(groupId, groupIds);
        }

        return result;


    }

    public static void addTeam(Map<String, Map<Integer, Integer>> universitiesToTeams, String university,
                               Integer teamNumber, Integer teamId) {
        Map<Integer, Integer> teams = universitiesToTeams.computeIfAbsent(university, k -> new HashMap<>());
        teams.put(teamNumber, teamId);
    }


    public static void main(String[] args) {
        Integer[] array1 = shuffleArray(1, 16);
        Integer[] array2 = shuffleArray(17, 28);
        System.out.println("Array 1: " + Arrays.toString(array1));
        System.out.println("Array 2: " + Arrays.toString(array2));
    }







    public static Integer[] shuffleArray(int start, int end) {
        List<Integer> list = createList(start, end);
        Collections.shuffle(list);
        return list.toArray(new Integer[list.size()]);
    }

    private static List<Integer> createList(int start, int end) {
        Integer[] array = new Integer[end - start + 1];
        for (int i = 0; i < array.length; i++) {
            array[i] = start + i;
        }
        return Arrays.asList(array);
    }



}

页面展示

image-20230509175548922

image-20230509175600430

image-20230509175621422

image-20230509175638971

image-20230509175650658

3.感悟

我应该把我每一个遇到的困难都给记录下来,以及我是如何解决的,记录下来。

进行页面设计的时候结合 AI 的能力和模板。

写代码之前做好功能设计,思考时间 ↑,代码时间 ↓

这次的经验让我熟练了 ssm 框架的 crud 操作,并且了解了关键词的使用,和许多不错的找灵感的设计网站。

培养了我的思维能力。

更加深入的学习了 ajax,对前后端的数据交互更加得心应手。见识到了 js 的强悍之处。

标签:const,比赛,addTeam,抽签,滚动,universitiesToTeams,tableBox,分组,team
From: https://www.cnblogs.com/Lovi/p/17717501.html

相关文章

  • Sql Server分组排序及生成序列号
    1--1.分组排序2SELECT*,ROW_NUMBER()OVER(PARTITIONBY@fileName,@fileName1ORDERBYIDDESC)ASrowNumFROM@tableName34--2.生成序列号5SELECT*,ROW_NUMBER()over(orderbyidASC)ASrowNumFROM@tableName 翻译搜索复制......
  • Jasper模板使用记录七——Group分组
    Group特点1.通过Group分组可以将集合中的数据进行分组显示2.Group分组有GroupHeader和GroupFooter可以在每个组的前后添加元素3.Group分组的效果是在Detail中显示的注意点Group并不会将乱序的集合数据进行分组和排序,只会按照集合的顺序进行遍历,如果本条数据和上一条......
  • SQL系列2-分组数据
    sql系列2-分组数据✅```sqlSELECTorder_num,COUNT(*)ASitemsFROMOrderItemsGROUPBYorder_numHAVINGCOUNT(*)>=3ORDERBYitems,order_num;```编写SQL语句,返回名为cheapest_item的字段,该字段包含每个供应商成本最低的产品(使用Products表中的prod_price),然后从最......
  • 数据分组
       ......
  • 「比赛游记」初赛简记
    「比赛游记」初赛简记J计算机常识两个,一个操作系统一个忘了,比较好。大家都在讨论哈夫曼编码?手算了一下应该选A啊。wkh说选B,那就选B吧,我不会。早上指导xrlong使用DP数数,虽然他似乎没太听但是压中题了,比较厉害。没有栈。又出锅是吧,我丢的30min这一块谁给我补啊?阅......
  • P8868 [NOIP2022] 比赛
    https://www.luogu.com.cn/problem/P8868我学会了历史和!在一阵扫描线过后,你会发现,\([l,r]\)的所有子区间的答案,就一定是扫到\(i\)的时候,加上\([k,i]\)的答案,\(k\lei,i\in[l,r]\),然后又因为只有当\(i\gel\)的时候,才能对左端点在\([l,r]\)的答案贡献,因此,你会发现这个东......
  • SQL基础总结(五):汇总和分组数据
    本系列blog源自前年写的SQL学习笔记,汇总一下发上来。(1月份发了前三篇笔记,原以为后面的笔记误操作删了,今天在硬盘里又找到了,一起发上来)-------------------------只对单个值进行操作的是标量函数。对一组值进行操作以产生一个汇总值的是SQL聚合函数或集合函数。可以对行的集合进行......
  • MySQL 分组取最新的一条
    1.MySQL分组取最新的一条2.MySQLnotinnotexists CREATETABLE`test_dept`(`deptid`int(11)NOTNULL,`deptname`varchar(255)CHARACTERSETutf8COLLATEutf8_general_ciDEFAULTNULL,PRIMARYKEY(`deptid`)USINGBTREE)ENGINE=InnoDBCHARACTERSET......
  • RocketMQ教程-(4)-领域模型-消费者分组ConsumerGroup
    定义消费者分组是ApacheRocketMQ系统中承载多个消费行为一致的消费者的负载均衡分组。和消费者不同,消费者分组并不是运行实体,而是一个逻辑资源。在ApacheRocketMQ中,通过消费者分组内初始化多个消费者实现消费性能的水平扩展以及高可用容灾。在消费者分组中,统一定义以下消费行......
  • 视频监控平台EasyCVR分组批量绑定/取消通道功能的后端代码设计逻辑介绍
    视频监控平台/视频存储/视频分析平台EasyCVR基于云边端一体化管理,可支持视频实时监控、云端录像、云存储、磁盘阵列存储、回放与检索、智能告警、平台级联等功能。安防监控平台在线下场景中应用广泛,包括智慧工地、智慧工厂、智慧校园、智慧社区等等。在前期的文章中我们介绍了关于......