比赛抽签分组系统

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

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/vue@2.6.14/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 的强悍之处。

posted @ 2023-09-20 16:00  Lovi*  阅读(139)  评论(0编辑  收藏  举报