From 81ae74dfaa7a41003f2303323b099273cdaaa6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B5=A9=E6=96=8C?= Date: Wed, 11 Dec 2024 12:51:51 +0800 Subject: [PATCH] Add files via upload --- trunk/web/template/syzoj/js/scrollboard.js | 719 +++++++++++++++++++++ 1 file changed, 719 insertions(+) create mode 100644 trunk/web/template/syzoj/js/scrollboard.js diff --git a/trunk/web/template/syzoj/js/scrollboard.js b/trunk/web/template/syzoj/js/scrollboard.js new file mode 100644 index 00000000000..4ee2a9442e9 --- /dev/null +++ b/trunk/web/template/syzoj/js/scrollboard.js @@ -0,0 +1,719 @@ +/** + * 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对象数组 + */ +/*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对象数组 + */ +/*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 比赛中的题目ID:A,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; //比赛中的题目ID:A,B,C,D... + //this.subTime = new Date(subTime); + this.subTime = StringToDate(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} 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 = + "
\ +
\ + \ + \ + \ + \ + \ + "; + var footHTML = + "\ +
RankTeamSolvedPenalty
\ +
"; + $('body').append(headHTML + footHTML); + + //题目列 + for (var i = 0; i < this.problemList.length; i++) { + var alphabetId = this.problemList[i]; + var bodyHTML = "" + alphabetId + ""; + $('.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 = + "
\ + \ + "; + var rankHTML = ""; + var teamHTML = ""; + var solvedHTML = ""; + var penaltyHTML = ""; + var problemHTML = ""; + for (var key in this.problemList) { + problemHTML += ""; + else { + if (tProblem.isAccepted) { + problemHTML += "" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + ""; + } else { + problemHTML += "" + tProblem.submitCount + ""; + } + } + } + } + var footHTML = + " \ +
" + rank + "" + team.teamName + /*"
" + team.teamMember +*/ "
" + team.solved + "" + parseInt(team.penalty / 1000.0 / 60.0) + ""; + var tProblem = team.submitProblemList[this.problemList[key]]; + if (tProblem) { + if (tProblem.isUnkonwn) + problemHTML += "" + tProblem.submitCount + "
\ +
"; + + 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 = + "
\ + \ + "; + var rankHTML = ""; + var teamHTML = ""; + var solvedHTML = ""; + var penaltyHTML = ""; + var problemHTML = ""; + for (var key in this.problemList) { + problemHTML += ""; + } + var footHTML = + " \ +
\ +
"; + + 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 = "" + tProblem.submitCount + ""; + else { + if (tProblem.isAccepted) { + problemHTML = "" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + ""; + } else { + problemHTML = "" + tProblem.submitCount + ""; + } + } + + + 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); + +} +function showTeam(teamId){ + console.log("show:"+teamId); + var clientHeight = document.documentElement.clientHeight || document.body.clientHeight || 0; + var teamTopHeight = $("div[team-id=\"" + teamId + "\"]").offset().top - clientHeight + 100; + //移动视点 + $('body,html').stop().animate({ + scrollTop: teamTopHeight + }, + 500); +} + +/** + * 更新队伍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"); + } + + window.setTimeout("showTeam('"+team.teamId+"')",800); + } +}