比赛抽签分组系统(项目回顾总结)
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 实现表格滚动功能的代码段中的两个关键函数。其中:
-
scrolls()
函数实现了滚动表格的功能,并启动了一个定时器,周期性地调用stepScroll()
函数,来让表格向下滚动一定距离; -
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
,便于在模板中进行循环展示。
具体实现步骤如下:
-
通过
addTeam
函数向universitiesToTeams
对象中添加学校和队伍信息,其中学校和队伍信息被存储为键值对的形式; -
使用
Array.from
方法分别获取universitiesToTeams
中的学校和队伍信息,并分别存储到universities
和teams
变量中; -
声明一个空数组
universitiesAndTeams
,通过forEach
函数遍历universitiesToTeams
对象,将学校和队伍信息存储为对象并添加到universitiesAndTeams
数组中; -
声明空数组
teamsFlat
,使用for...of
循环遍历universitiesToTeams
对象,将其中每一个学校对应的队伍信息展开为一维数组并添加到teamsFlat
中;
通过以上步骤,代码可以将universitiesToTeams
中的信息提取并处理,以便在其他部分中使用。
universitiesAndTeams
和teamsFlat
都是将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);
},
},
});
页面展示
- 正在滚动的页面
- 分组之后(当我鼠标放上去才会有滚动条出现)
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);
}
}
页面展示
3.感悟
我应该把我每一个遇到的困难都给记录下来,以及我是如何解决的,记录下来。
进行页面设计的时候结合 AI 的能力和模板。
写代码之前做好功能设计,思考时间 ↑,代码时间 ↓
这次的经验让我熟练了 ssm 框架的 crud 操作,并且了解了关键词的使用,和许多不错的找灵感的设计网站。
培养了我的思维能力。
更加深入的学习了 ajax,对前后端的数据交互更加得心应手。见识到了 js 的强悍之处。
标签:const,比赛,addTeam,抽签,滚动,universitiesToTeams,tableBox,分组,team From: https://www.cnblogs.com/Lovi/p/17717501.html