Files
hustoj/web/template/sidebar/scrollboard.js
2024-10-10 12:56:56 +08:00

707 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* scrollboard.js
* ACM竞赛滚榜展示插件基于JQuery、Bootstrap
*
* Version 1.0.0
* Author: qinshaoxuan qsxuan.com
* Github: https://github.com/qinshaoxuan/ScrollBoard
* Demo: https://qinshaoxuan.github.io/ScrollBoard/
*
*/
/**
* 从服务器获取提交列表可按后台json格式修改
* @return {Array<Submit>} 初始化后的Submit对象数组
*/
/*function getSubmitList() {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "data/submitData.json",
dataType: "json",
data: {},
async: false,
success: function(result) {
for (var key in result.data) {
var sub = result.data[key];
// 提交ID用户ID题号提交时间结果
data.push(new Submit(sub.submitId, sub.userId, sub.alphabetId, sub.subTime, sub.resultId));
}
},
error: function() {
alert("获取Submit数据失败");
}
});
return data;
}*/
function getSubmitList(cid) {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "contestrank3.php?cid="+cid+"&type=json&list=submit",
dataType: "json",
data: {},
async: false,
success: function(result) {
for (var key in result) {
var sub = result[key];
// 提交ID用户ID题号提交时间结果
data.push(new Submit(sub.submitID, sub.userID, sub.alphabetID, sub.subTime, sub.resultID));
}
},
error: function() {
alert("获取Submit数据失败");
}
});
return data;
}
/**
* 从服务器获取队伍列表可按后台json格式修改
* @return {Array<Team>} 初始化后的Team对象数组
*/
/*function getTeamList() {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "data/teamData.json",
dataType: "json",
async: false,
data: {},
success: function(result) {
for (var key in result.data) {
var team = result.data[key];
// 队伍ID 队伍名称真名offical
data[team.teamId] = new Team(team.teamId, team.nickname, team.realname, team.official);
}
},
error: function() {
alert("获取Team数据失败");
}
});
return data;
}*/
function getTeamList(cid) {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "contestrank3.php?cid="+cid+"&type=json&list=team",
dataType: "json",
async: false,
data: {},
success: function(result) {
for (var key in result) {
var team = result[key];
// 队伍ID 队伍名称真名offical
data[team.userID] = new Team(team.userID, team.nickName, team.realName, team.official);
}
},
error: function() {
alert("获取Team数据失败");
}
});
return data;
}
/**
* yyyy-mm-dd hh:mm:ss格式转Date
* @param {Date} s 字符串对应的日期
*/
function StringToDate(s) {
var d = new Date();
d.setYear(parseInt(s.substring(0, 4), 10));
d.setMonth(parseInt(s.substring(5, 7) - 1, 10));
d.setDate(parseInt(s.substring(8, 10), 10));
d.setHours(parseInt(s.substring(11, 13), 10));
d.setMinutes(parseInt(s.substring(14, 16), 10));
d.setSeconds(parseInt(s.substring(17, 19), 10));
return d;
}
/**
* Submit对象
* @param {int} submitId 全局runID
* @param {int} teamId 队伍ID
* @param {String} alphabetId 比赛中的题目IDA,B,C...
* @param {int} subTime 提交时间
* @param {int} resultId 判题结果ID
*/
function Submit(submitId, teamId, alphabetId, subTime, resultId) {
this.submitId = submitId; //全局runID
this.teamId = teamId; //队伍ID
this.alphabetId = alphabetId; //比赛中的题目IDA,B,C,D...
this.subTime = new Date(subTime);
/**
* 判题结果ID
* @type {int}
* @value 0 Accepted
* @value 1 Presentation Error
* @value 2 Time Limit Exceeded
* @value 3 Memory Limit Exceeded
* @value 4 Wrong Answer
* @value 5 Runtime Error
* @value 6 Output Limit Exceeded
* @value 7 Compile Error
* @value 8 System Error
* @value 9 Security Error
* @value -1 Waiting
*/
this.resultId = resultId;
}
/**
* TeamProblem对象用来存放每个队伍的每道题的提交情况
*/
function TeamProblem() {
this.alphabetId = "";
this.isAccepted = false;
this.penalty = 0; //罚时毫秒数
this.acceptedTime = new Date(); //AC时间
this.submitCount = 0; //AC前提交次数如果AC了值加1
this.isUnkonwn = false; //是否为封榜后提交如果封榜前已AC也为false
}
/**
* Team对象
* @param {int} teamId 队伍ID
* @param {String} teamName 队伍名
* @param {String} teamMember 队员
* @param {boolean} official 是否计入排名
*/
function Team(teamId, teamName, teamMember, official) {
this.teamId = teamId; //队伍ID
this.teamName = teamName; //队伍名
this.teamMember = teamMember; //队员
this.official = true; //计入排名
this.solved = 0; //通过数
this.penalty = 0; //罚时,单位为毫秒
this.gender = false; //女队,默认否
this.submitProblemList = []; //提交题目列表
this.unkonwnAlphabetIdMap = new Array(); //未知的题目AlphabetId列表
this.submitList = []; //提交列表
this.lastRank = 0; //最终排名
this.nowRank = 0; //当前排名
}
/**
* Team对象初始化函数更新到封榜时的状态
* @param {Date} startTime 比赛开始时间
* @param {Date} freezeBoardTime 封榜时间
*/
Team.prototype.init = function(startTime, freezeBoardTime) {
//按提交顺序排序
this.submitList.sort(function(a, b) {
return a.submitId - b.submitId;
});
for (var key in this.submitList) {
var sub = this.submitList[key];
//创建对象
var p = this.submitProblemList[sub.alphabetId];
if (!p) p = new TeamProblem();
//设置alphabetId
p.alphabetId = sub.alphabetId;
//已经AC的题目不再计算
if (p.isAccepted) continue;
//封榜后的提交设置isUnkonwn为true
if (sub.subTime > freezeBoardTime) {
p.isUnkonwn = true;
this.unkonwnAlphabetIdMap[p.alphabetId] = true;
}
//增加提交次数
p.submitCount++;
//更新AC状态
p.isAccepted = (sub.resultId == 0);
//如果当前提交AC
if (p.isAccepted) {
//则保存AC时间
p.acceptedTime = sub.subTime.getTime() - startTime.getTime();
//如果为封榜前AC则计算罚时,且队伍通过题数加1
if (p.acceptedTime < freezeBoardTime - startTime) {
p.penalty += p.acceptedTime + (p.submitCount - 1) * 20 * 60 * 1000;
this.solved++;
this.penalty += p.penalty;
}
}
//更新submitProblemList
this.submitProblemList[p.alphabetId] = p;
}
}
/**
* 计算Team中有多少道题状态未知
* @return {int} 未知状态题目的数量
*/
Team.prototype.countUnkonwnProblme = function() {
var count = 0;
for (var key in this.unkonwnAlphabetIdMap) {
count++;
}
return count;
}
/**
* 滚榜时,更新一个队伍的一个题的状态
* @return {boolean} true:当前队伍排名上升,false:排名无变化
*/
Team.prototype.updateOneProblem = function() {
for (var key in this.submitProblemList) {
var subProblem = this.submitProblemList[key];
//如果题目结果未知
if (subProblem.isUnkonwn) {
//更新题目状态
subProblem.isUnkonwn = false;
delete this.unkonwnAlphabetIdMap[subProblem.alphabetId];
//如果AC则更新题目状态
if (subProblem.isAccepted) {
subProblem.penalty += subProblem.acceptedTime + (subProblem.submitCount - 1) * 20 * 60 * 1000;
this.solved++;
this.penalty += subProblem.penalty;
return true;
}
return false;
}
}
}
/**
* 队伍排位函数
* @param {Team} a Team a
* @param {Team} b Team b
* @return {int} 负数a排位高正数b排位高
*/
function TeamCompare(a, b) {
if (a.solved != b.solved) //第一关键字,通过题数高者排位高
return a.solved > b.solved ? -1 : 1;
if (a.penalty != b.penalty) //第二关键字,罚时少者排位高
return a.penalty < b.penalty ? -1 : 1;
//return a.teamId < b.teamId ? -1 : 1; //第三关键字队伍ID小者排位高
return a.teamId.localeCompare(b.teamId);
}
/**
* Board对象
* @param {int} problemCount 题目数量
* @param {Array<int>} medalCounts 奖牌数,无特等奖则为3个数,有特等奖则为4个数,第一个为特等奖
* @param {Date} startTime 比赛开始时间
* @param {Date} freezeBoardTime 封榜时间
*/
function Board(problemCount, medalCounts, startTime, freezeBoardTime, cid) {
this.problemCount = problemCount; //题目数量
this.medalCounts = medalCounts; //奖牌数数组,无特等奖则为3个数,有特等奖则为4个数第一个为特等奖
this.medalRanks = []; //每个奖牌的最后一名的RANK值
this.medalStr = ["gold", "silver", "bronze"];
this.problemList = []; //题目alphabetId编号列表
this.startTime = startTime;
this.freezeBoardTime = freezeBoardTime;
this.teamList = []; //从服务器获取的teamList为teamId与Team对象的映射
this.submitList = []; //从服务器获取的所有的submitList,Submit对象数组
this.teamNowSequence = []; //当前队伍排名存队伍ID
this.teamNextSequence = []; //下一步队伍排名存队伍ID
this.teamCount = 0; //队伍数量
this.displayTeamPos = 0; //当前展示的队伍位置
this.noAnimate = true; //当前无动画进行
//根据题目数量设置alphabetId
var ACode = 65;
for (var i = 0; i < problemCount; i++)
this.problemList.push(String.fromCharCode(ACode + i));
//计算medalRanks
this.medalRanks[0] = medalCounts[0];
for (var i = 1; i < this.medalCounts.length; ++i) {
this.medalRanks[i] = this.medalCounts[i] + this.medalRanks[i - 1];
}
//从服务器得到submitList和teamList
this.submitList = getSubmitList(cid);
this.teamList = getTeamList(cid);
//将submit存到对应的Team对象里
for (var key in this.submitList) {
var sub = this.submitList[key];
this.teamList[sub.teamId].submitList.push(sub);
}
//初始化Team对象同时将队伍ID放入序列
for (var key in this.teamList) {
var team = this.teamList[key];
team.init(this.startTime, this.freezeBoardTime);
this.teamNowSequence.push(team);
this.teamCount++;
}
this.displayTeamPos = this.teamCount - 1;
//队伍排序
this.teamNowSequence.sort(function(a, b) {
return TeamCompare(a, b);
});
this.teamNextSequence = this.teamNowSequence.slice(0);
}
/**
* 更新队伍排序,得到下一个队伍移动后的序列
* @return {int} 排名上升的队伍要插入的位置,如果无变化返回-1
*/
Board.prototype.updateTeamSequence = function() {
var teamSequence = this.teamNextSequence.slice(0); //复制数组js为引用传递
teamSequence.sort(function(a, b) {
return TeamCompare(a, b);
});
//找到第一个改变的位置,即为排名上升的队伍要插入的位置
var toPos = -1;
for (var i = 0; i < this.teamCount; i++) {
if (this.teamNextSequence[i].teamId != teamSequence[i].teamId) {
toPos = i;
break;
}
}
//更新队列
this.teamNowSequence = this.teamNextSequence.slice(0);
this.teamNextSequence = teamSequence.slice(0);
return toPos;
}
/**
* 不断更新最后一个unkonwn队伍的题目状态直到排名发生变化或者无题目可更新
* @return {Team} 返回正在更新的Team对象没有则返回null
*/
Board.prototype.UpdateOneTeam = function() {
//得到需要更新的队伍在当前排名中的的位置
var updateTeamPos = this.teamCount - 1;
while (updateTeamPos >= 0 && this.teamNextSequence[updateTeamPos].countUnkonwnProblme() < 1)
updateTeamPos--;
//如果有队伍可更新
if (updateTeamPos >= 0) {
//不断更新队伍题目状态,直到排名发生变化或者无题目可更新
while (this.teamNextSequence[updateTeamPos].countUnkonwnProblme() > 0) {
//更新一个题目状态
var result = this.teamNextSequence[updateTeamPos].updateOneProblem();
return this.teamNextSequence[updateTeamPos];
}
}
return null;
}
/**
* 显示封榜时的状态
*/
Board.prototype.showInitBoard = function() {
//设置表头宽度百分比
var rankPer = 5; //Rank列宽度百分比
var teamPer = 25; //Team列宽度百分比
var solvedPer = 4; //Solved列宽度百分比
var penaltyPer = 7; //Penalty列宽度百分比
var problemStatusPer = (100.0 - rankPer - teamPer - solvedPer - penaltyPer) / this.problemCount; //Problem列宽度百分比
//表头
var headHTML =
"<div id=\"timer\"></div>\
<div class=\"ranktable-head\">\
<table class=\"table\">\
<tr>\
<th width=\"" + rankPer + "%\">Rank</th>\
<th width=\"" + teamPer + "%\">Team</th>\
<th width=\"" + solvedPer + "%\">Solved</th>\
<th width=\"" + penaltyPer + "%\">Penalty</th>";
var footHTML =
"</tr>\
</table>\
</div>";
$('body').append(headHTML + footHTML);
//题目列
for (var i = 0; i < this.problemList.length; i++) {
var alphabetId = this.problemList[i];
var bodyHTML = "<th width=\"" + problemStatusPer + "%\">" + alphabetId + "</th>";
$('.ranktable-head tr').append(bodyHTML);
}
var maxRank = 0;
//队伍
for (var i = 0; i < this.teamCount; i++) {
var team = this.teamNowSequence[i];
//计算每支队伍的排名和奖牌情况
var rank = 0;
var medal = -1;
if (team.solved != 0) {
rank = i + 1;
maxRank = rank + 1;
for (var j = this.medalRanks.length - 1; j >= 0; j--) {
if (rank <= this.medalRanks[j])
medal = j;
}
} else {
rank = maxRank;
medal = -1;
}
//构造HTML
var headHTML =
"<div id=\"team_" + team.teamId + "\" class=\"team-item\" team-id=\"" + team.teamId + "\"> \
<table class=\"table\"> \
<tr>";
var rankHTML = "<th class=\"rank\" width=\"" + rankPer + "%\">" + rank + "</th>";
var teamHTML = "<td class=\"team-name\" width=\"" + teamPer + "%\"><span>" + team.teamName + /*"<br/>" + team.teamMember +*/ "</span></td>";
var solvedHTML = "<td class=\"solved\" width=\"" + solvedPer + "%\">" + team.solved + "</td>";
var penaltyHTML = "<td class=\"penalty\" width=\"" + penaltyPer + "%\">" + parseInt(team.penalty / 1000.0 / 60.0) + "</td>";
var problemHTML = "";
for (var key in this.problemList) {
problemHTML += "<td class=\"problem-status\" width=\"" + problemStatusPer + "%\" alphabet-id=\"" + this.problemList[key] + "\">";
var tProblem = team.submitProblemList[this.problemList[key]];
if (tProblem) {
if (tProblem.isUnkonwn)
problemHTML += "<span class=\"label label-warning\">" + tProblem.submitCount + "</span></td>";
else {
if (tProblem.isAccepted) {
problemHTML += "<span class=\"label label-success\">" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + "</span></td>";
} else {
problemHTML += "<span class=\"label label-danger\">" + tProblem.submitCount + "</span></td>";
}
}
}
}
var footHTML =
"</tr> \
</table> \
</div>";
var HTML = headHTML + rankHTML + teamHTML + solvedHTML + penaltyHTML + problemHTML + footHTML;
//填充HTML
$('body').append(HTML);
//设置奖牌对应的CSS样式
if (medal != -1)
$("#team_" + team.teamId).addClass(this.medalStr[medal]);
}
//构造一个空的队伍,填充底部
var headHTML =
"<div id=\"team-void\" class=\"team-item\"> \
<table class=\"table\"> \
<tr>";
var rankHTML = "<th class=\"rank\" width=\"" + rankPer + "%\"></th>";
var teamHTML = "<td class=\"team-name\" width=\"" + teamPer + "%\"></td>";
var solvedHTML = "<td class=\"solved\" width=\"" + solvedPer + "%\"></td>";
var penaltyHTML = "<td class=\"penalty\" width=\"" + penaltyPer + "%\"></td>";
var problemHTML = "";
for (var key in this.problemList) {
problemHTML += "<td class=\"problem-status\" width=\"" + problemStatusPer + "%\" alphabet-id=\"" + this.problemList[key] + "\"></td>";
}
var footHTML =
"</tr> \
</table> \
</div>";
var HTML = headHTML + rankHTML + teamHTML + solvedHTML + penaltyHTML + problemHTML + footHTML;
//填充HTML
$('body').append(HTML);
//按排名对队伍的div进行排序
var headerHeight = 44; //表头的高度
var teamHeight = 68; //队伍行的高度
for (var i = 0; i < this.teamCount; ++i) {
//var teamId = this.teamList[this.teamNowSequence[i]].teamId;
var teamId = this.teamNowSequence[i].teamId;
$("div[team-id=\"" + teamId + "\"]").stop().animate({ top: i * teamHeight + headerHeight }, 800);
}
//移到底部
$("#team-void").stop().animate({ top: this.teamCount * teamHeight + headerHeight }, 800);
}
/**
* 更新队伍的表现状态即改变HTML样式
* @param {Team} team 要改变的Team对象
* @return {boolean} 要更新的题目是否AC
*/
Board.prototype.updateTeamStatus = function(team) {
var thisBoard = this;
//更新ProblemStatus
for (var key in team.submitProblemList) {
var tProblem = team.submitProblemList[key];
//构造题目状态HTML
problemHTML = "";
if (tProblem.isUnkonwn)
problemHTML = "<span class=\"label label-warning\">" + tProblem.submitCount + "</td>";
else {
if (tProblem.isAccepted) {
problemHTML = "<span class=\"label label-success\">" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + "</td>";
} else {
problemHTML = "<span class=\"label label-danger\">" + tProblem.submitCount + "</td>";
}
}
var $problemStatus = $("#team_" + team.teamId + " .problem-status[alphabet-id=\"" + key + "\"]");
var $statusSpan = $problemStatus.children('span[class="label label-warning"]');
//让题目状态闪烁,并更新状态
if (tProblem.isUnkonwn == false) {
//加高亮边框前去掉所有高亮边框
$('.team-item.hold').removeClass("hold");
var $team = $("div[team-id=\"" + team.teamId + "\"]");
//加高亮边框
$team.addClass("hold");
//得到TeamDiv距顶部的高度
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight || 0;
var teamTopHeight = $team.offset().top - clientHeight + 100;
//移动视点
$('body,html').stop().animate({
scrollTop: teamTopHeight
},
500);
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard, tProblem, problemHTML) {
//闪烁两次后显示未知题目的结果
var speed = 100; //闪烁速度
$statusSpan.fadeOut(speed).fadeIn(speed, function() {
//更新题目表现状态
$(this).parent().html(problemHTML);
});
})(thisBoard, tProblem, problemHTML);
}
}
//延时更新榜单
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard, team) {
//延时0.2s
$('#timer').animate({ margin: 0 }, 200, function() {
/*
更新Rank
*/
var maxRank = 0;
//移除div中的奖牌样式
for (var i in thisBoard.medalStr) {
$(".team-item").removeClass(thisBoard.medalStr[i]);
}
//对于每个队计算排名和奖牌情况
for (var i = 0; i < thisBoard.teamCount; i++) {
var t = thisBoard.teamNextSequence[i];
var medal = -1;
var rankValue = 0;
if (t.solved != 0) {
rankValue = i + 1;
maxRank = rankValue + 1;
for (var j = thisBoard.medalRanks.length - 1; j >= 0; j--) {
if (rankValue <= thisBoard.medalRanks[j])
medal = j;
}
} else {
rankValue = maxRank;
medal = -1;
}
$team = $("div[team-id=\"" + t.teamId + "\"]");
if (medal != -1)
$team.addClass(thisBoard.medalStr[medal]);
$("#team_" + t.teamId + " .rank").html(rankValue);
}
//更新Solved
$("#team_" + team.teamId + " .solved").html(team.solved);
//更新Penaly
$("#team_" + team.teamId + " .penalty").html(parseInt(team.penalty / 1000.0 / 60.0));
}, false);
})(thisBoard, team);
}
/**
* 更新队伍div的位置
* @param {int} toPos 当前关注队伍在序列中的终点位置,-1为不移动
*/
Board.prototype.moveTeam = function(toPos) {
var thisBoard = this;
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard) {
var headerHeight = 44;
var teamHeight = 68;
for (var i = 0; i < thisBoard.teamCount; ++i) {
var teamId = thisBoard.teamNextSequence[i].teamId;
//延时1.2s更新位置,为了等待题目状态更新完成
if(toPos != -1)
$("div[team-id=\"" + teamId + "\"]").animate({ margin: 0 }, 200).animate({ top: i * teamHeight + headerHeight }, 1000, function() {
thisBoard.noAnimate = true;
});
else
$("div[team-id=\"" + teamId + "\"]").animate({ margin: 0 }, 200 ,function() {
thisBoard.noAnimate = true;
});
}
})(thisBoard);
}
/**
* 按下按键时调用的函数,包括榜更新一步的过程
*/
Board.prototype.keydown = function() {
//等动画结束后再进行下一步
if (this.noAnimate) {
this.noAnimate = false;
//更新一支队伍的状态,没有则team==null
var team = this.UpdateOneTeam();
if (team) {
//根据现在的状态更新序列
var toPos = this.updateTeamSequence();
//更新队伍HTML内容
this.updateTeamStatus(team);
//移动队伍
this.moveTeam(toPos);
} else {
//无队伍可更新时取消高亮边框
$('.team-item.hold').removeClass("hold");
}
}
}