美化比赛排行榜,增加外榜、打星队伍的支持
This commit is contained in:
parent
e3a7be0382
commit
fe3be8dc1a
|
@ -96,6 +96,7 @@ docker ps # 查看当前运行的容器状态
|
|||
| 2021-09-21 | 增加比赛打印功能、账号限制功能 | Himit_ZH |
|
||||
| 2021-10-05 | 增加站内消息系统——评论、回复、点赞、系统通知的消息,优化前端。 | Himit_ZH |
|
||||
| 2021-10-06 | 美化比赛排行榜,增加对FPS题目导入的支持 | Himit_ZH |
|
||||
| 2021-12-09 | 美化比赛排行榜,增加外榜、打星队伍的支持 | Himit_ZH |
|
||||
|
||||
## 五、部分截图
|
||||
|
||||
|
|
|
@ -32,6 +32,6 @@
|
|||
|
||||
然后压缩测试用例到一个zip中
|
||||
|
||||
注意:不要在这些文件外面套多一层文件夹,请直接压缩!!!
|
||||
**注意:即使没有输入或者没有输出,也请提供对应的空输入(输出)文件,不要在这些文件外面套多一层文件夹,请直接压缩!!!**
|
||||
|
||||
同时建议:尽量合并测试用例到一个文件中,减少测试用例组数,这会一定程度上提高判题性能。
|
|
@ -1,7 +1,10 @@
|
|||
package top.hcode.hoj.controller.admin;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
@ -23,6 +26,7 @@ import top.hcode.hoj.pojo.entity.contest.ContestAnnouncement;
|
|||
import top.hcode.hoj.pojo.entity.contest.ContestProblem;
|
||||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
import top.hcode.hoj.pojo.vo.AdminContestVo;
|
||||
import top.hcode.hoj.pojo.vo.AnnouncementVo;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
import top.hcode.hoj.service.common.impl.AnnouncementServiceImpl;
|
||||
|
@ -37,6 +41,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpSession;
|
||||
import javax.validation.Valid;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -119,8 +124,15 @@ public class AdminContestController {
|
|||
if (!isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
return CommonResult.successResponse(contest, "查询成功!");
|
||||
|
||||
AdminContestVo adminContestVo = BeanUtil.copyProperties(contest, AdminContestVo.class, "starAccount");
|
||||
if (StringUtils.isEmpty(contest.getStarAccount())){
|
||||
adminContestVo.setStarAccount(new ArrayList<>());
|
||||
}else {
|
||||
JSONObject jsonObject = JSONUtil.parseObj(contest.getStarAccount());
|
||||
List<String> starAccount = jsonObject.get("star_account", List.class);
|
||||
adminContestVo.setStarAccount(starAccount);
|
||||
}
|
||||
return CommonResult.successResponse(adminContestVo, "查询成功!");
|
||||
}
|
||||
|
||||
@DeleteMapping("")
|
||||
|
@ -141,7 +153,11 @@ public class AdminContestController {
|
|||
@PostMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult addContest(@RequestBody Contest contest) {
|
||||
public CommonResult addContest(@RequestBody AdminContestVo adminContestVo) {
|
||||
Contest contest = BeanUtil.copyProperties(adminContestVo, Contest.class, "starAccount");
|
||||
JSONObject accountJson = new JSONObject();
|
||||
accountJson.set("star_account", adminContestVo.getStarAccount());
|
||||
contest.setStarAccount(accountJson.toString());
|
||||
boolean result = contestService.save(contest);
|
||||
if (result) { // 添加成功
|
||||
return CommonResult.successResponse(null, "添加成功!");
|
||||
|
@ -153,7 +169,7 @@ public class AdminContestController {
|
|||
@PutMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult updateContest(@RequestBody Contest contest, HttpServletRequest request) {
|
||||
public CommonResult updateContest(@RequestBody AdminContestVo adminContestVo, HttpServletRequest request) {
|
||||
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
|
@ -161,10 +177,13 @@ public class AdminContestController {
|
|||
// 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
// 只有超级管理员和比赛拥有者才能操作
|
||||
if (!isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
if (!isRoot && !userRolesVo.getUid().equals(adminContestVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
Contest contest = BeanUtil.copyProperties(adminContestVo, Contest.class, "starAccount");
|
||||
JSONObject accountJson = new JSONObject();
|
||||
accountJson.set("star_account", adminContestVo.getStarAccount());
|
||||
contest.setStarAccount(accountJson.toString());
|
||||
boolean result = contestService.saveOrUpdate(contest);
|
||||
if (result) {
|
||||
return CommonResult.successResponse(null, "修改成功!");
|
||||
|
|
|
@ -81,6 +81,7 @@ public class ContestFileController {
|
|||
@RequiresAuthentication
|
||||
public void downloadContestRank(@RequestParam("cid") Long cid,
|
||||
@RequestParam("forceRefresh") Boolean forceRefresh,
|
||||
@RequestParam(value = "removeStar", defaultValue = "false") Boolean removeStar,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
// 获取当前登录的用户
|
||||
|
@ -118,21 +119,17 @@ public class ContestFileController {
|
|||
|
||||
if (contest.getType().intValue() == Constants.Contest.TYPE_ACM.getCode()) { // ACM比赛
|
||||
|
||||
List<ContestRecordVo> contestRecordList = contestRecordService.getACMContestRecord(contest.getAuthor(), contest.getId());
|
||||
Assert.notEmpty(contestRecordList, "比赛暂无排行榜记录!");
|
||||
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.calcACMRank(isOpenSealRank, contest, contestRecordList);
|
||||
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.getACMContestScoreboard(isOpenSealRank, removeStar, contest);
|
||||
EasyExcel.write(response.getOutputStream())
|
||||
.head(fileService.getContestRankExcelHead(contestProblemDisplayIDList, true))
|
||||
.sheet("rank")
|
||||
.doWrite(fileService.changeACMContestRankToExcelRowList(acmContestRankVoList, contestProblemDisplayIDList));
|
||||
.doWrite(fileService.changeACMContestRankToExcelRowList(acmContestRankVoList, contestProblemDisplayIDList,contest.getRankShowName()));
|
||||
} else {
|
||||
List<ContestRecordVo> oiContestRecord = contestRecordService.getOIContestRecord(cid, contest.getAuthor(), isOpenSealRank, contest.getSealRankTime(), contest.getStartTime(), contest.getEndTime());
|
||||
Assert.notEmpty(oiContestRecord, "比赛暂无排行榜记录!");
|
||||
List<OIContestRankVo> oiContestRankVoList = contestRecordService.calcOIRank(oiContestRecord);
|
||||
List<OIContestRankVo> oiContestRankVoList = contestRecordService.getOIContestScoreboard(isOpenSealRank, removeStar, contest);
|
||||
EasyExcel.write(response.getOutputStream())
|
||||
.head(fileService.getContestRankExcelHead(contestProblemDisplayIDList, false))
|
||||
.sheet("rank")
|
||||
.doWrite(fileService.changOIContestRankToExcelRowList(oiContestRankVoList, contestProblemDisplayIDList));
|
||||
.doWrite(fileService.changOIContestRankToExcelRowList(oiContestRankVoList, contestProblemDisplayIDList,contest.getRankShowName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,6 @@ public class AccountController {
|
|||
* @Return
|
||||
* @Since 2020/11/5
|
||||
*/
|
||||
|
||||
@RequestMapping(value = "/check-username-or-email", method = RequestMethod.POST)
|
||||
public CommonResult checkUsernameOrEmail(@RequestBody Map<String, Object> data) throws MessagingException {
|
||||
String email = (String) data.get("email");
|
||||
|
|
|
@ -95,7 +95,6 @@ public class ContestController {
|
|||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
// 页数,每页题数若为空,设置默认值
|
||||
if (currentPage == null || currentPage < 1) currentPage = 1;
|
||||
|
||||
if (limit == null || limit < 1) limit = 10;
|
||||
|
||||
Page<ContestVo> contestList = contestService.getContestList(limit, currentPage, type, status, keyword);
|
||||
|
@ -125,7 +124,6 @@ public class ContestController {
|
|||
// 设置当前服务器系统时间
|
||||
contestInfo.setNow(new Date());
|
||||
|
||||
|
||||
return CommonResult.successResponse(contestInfo, "获取成功");
|
||||
}
|
||||
|
||||
|
@ -483,6 +481,7 @@ public class ContestController {
|
|||
@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "forceRefresh") Boolean forceRefresh,
|
||||
@RequestParam(value = "removeStar", defaultValue = "0") Boolean removeStar,
|
||||
HttpServletRequest request) {
|
||||
|
||||
// 获取当前登录的用户
|
||||
|
@ -512,12 +511,11 @@ public class ContestController {
|
|||
if (contest.getType().intValue() == Constants.Contest.TYPE_ACM.getCode()) { // ACM比赛
|
||||
|
||||
// 进行排行榜计算以及排名分页
|
||||
resultList = contestRecordService.getContestACMRank(isOpenSealRank, contest, currentPage, limit);
|
||||
resultList = contestRecordService.getContestACMRank(isOpenSealRank, removeStar, contest, currentPage, limit);
|
||||
|
||||
} else { //OI比赛:以最后一次提交得分作为该题得分
|
||||
|
||||
resultList = contestRecordService.getContestOIRank(cid, contest.getAuthor(), isOpenSealRank, contest.getSealRankTime(), contest.getStartTime(),
|
||||
contest.getEndTime(), currentPage, limit);
|
||||
resultList = contestRecordService.getContestOIRank(isOpenSealRank, removeStar, contest, currentPage, limit);
|
||||
}
|
||||
|
||||
if (resultList == null || resultList.getSize() == 0) {
|
||||
|
@ -649,4 +647,105 @@ public class ContestController {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @MethodName getContestOutsideInfo
|
||||
* @param cid 比赛id
|
||||
* @Description 提供比赛外榜所需的比赛信息和题目信息
|
||||
* @Return
|
||||
* @Since 2021/12/8
|
||||
*/
|
||||
@GetMapping("/get-contest-outsize-info")
|
||||
public CommonResult getContestOutsideInfo(@RequestParam(value = "cid", required = true) Long cid) {
|
||||
ContestVo contestInfo = contestService.getContestInfoById(cid);
|
||||
|
||||
if (contestInfo == null) {
|
||||
return CommonResult.errorResponse("访问错误:该比赛不存在!");
|
||||
}
|
||||
|
||||
if (!contestInfo.getOpenRank()) {
|
||||
return CommonResult.errorResponse("本场比赛未开启外榜,禁止访问外榜!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
// 获取本场比赛的状态
|
||||
if (contestInfo.getStatus().equals(Constants.Contest.STATUS_SCHEDULED.getCode())) {
|
||||
return CommonResult.errorResponse("本场比赛正在筹备中,禁止访问外榜!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
contestInfo.setNow(new Date());
|
||||
ContestOutsideInfo contestOutsideInfo = new ContestOutsideInfo();
|
||||
contestOutsideInfo.setContest(contestInfo);
|
||||
|
||||
QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();
|
||||
contestProblemQueryWrapper.eq("cid", cid);
|
||||
List<ContestProblem> contestProblemList = contestProblemService.list(contestProblemQueryWrapper);
|
||||
contestOutsideInfo.setProblemList(contestProblemList);
|
||||
|
||||
return CommonResult.successResponse(contestOutsideInfo, "success");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @param cid 比赛id
|
||||
* @param removeStar 是否移除打星队伍
|
||||
* @param forceRefresh 是否强制实时榜单
|
||||
* @MethodName getContestScoreBoard
|
||||
* @Description 提供比赛外榜排名数据
|
||||
* @Return
|
||||
* @Since 2021/12/07
|
||||
*/
|
||||
@GetMapping("/get-contest-outside-scoreboard")
|
||||
public CommonResult getContestOutsideScoreboard(@RequestParam(value = "cid", required = true) Long cid,
|
||||
@RequestParam(value = "removeStar", defaultValue = "0") Boolean removeStar,
|
||||
@RequestParam(value = "forceRefresh") Boolean forceRefresh,
|
||||
HttpServletRequest request) {
|
||||
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
|
||||
if (contest == null) {
|
||||
return CommonResult.errorResponse("访问错误:该比赛不存在!");
|
||||
}
|
||||
|
||||
if (!contest.getOpenRank()) {
|
||||
return CommonResult.errorResponse("本场比赛未开启外榜,禁止访问外榜!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
if (contest.getStatus().equals(Constants.Contest.STATUS_SCHEDULED.getCode())) {
|
||||
return CommonResult.errorResponse("本场比赛正在筹备中,禁止访问外榜!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = false;
|
||||
String currentUid = null;
|
||||
|
||||
if (userRolesVo != null) {
|
||||
currentUid = userRolesVo.getUid();
|
||||
isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
// 不是比赛创建者或者超管无权限开启强制实时榜单
|
||||
if (!isRoot && !contest.getUid().equals(currentUid)) {
|
||||
forceRefresh = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 校验该比赛是否开启了封榜模式,超级管理员和比赛创建者可以直接看到实际榜单
|
||||
boolean isOpenSealRank = contestService.isSealRank(currentUid, contest, forceRefresh, isRoot);
|
||||
|
||||
if (contest.getType().intValue() == Constants.Contest.TYPE_ACM.getCode()) { // ACM比赛
|
||||
|
||||
// 获取排行榜
|
||||
List<ACMContestRankVo> acmContestScoreboard = contestRecordService.getACMContestScoreboard(isOpenSealRank, removeStar, contest);
|
||||
return CommonResult.successResponse(acmContestScoreboard, "success");
|
||||
|
||||
} else { //OI比赛:以最后一次提交得分作为该题得分
|
||||
// 获取排行榜
|
||||
List<OIContestRankVo> oiContestScoreboard = contestRecordService.getOIContestScoreboard(isOpenSealRank, removeStar, contest);
|
||||
return CommonResult.successResponse(oiContestScoreboard, "success");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.dao.JudgeMapper;
|
||||
|
@ -446,8 +447,8 @@ public class JudgeController {
|
|||
|
||||
List<Long> submitIds = submitIdListDto.getSubmitIds();
|
||||
|
||||
if (submitIds.size() == 0) {
|
||||
return CommonResult.successResponse(new HashMap<>(), "已无数据可查询!");
|
||||
if (CollectionUtils.isEmpty(submitIds)) {
|
||||
return CommonResult.successResponse(new HashMap<>(), "查询的提交id列表为空!");
|
||||
}
|
||||
|
||||
QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -480,17 +481,23 @@ public class JudgeController {
|
|||
return CommonResult.errorResponse("查询比赛ID不能为空");
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(submitIdListDto.getSubmitIds())) {
|
||||
return CommonResult.successResponse(new HashMap<>(), "查询的提交id列表为空!");
|
||||
}
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
boolean root = SecurityUtils.getSubject().hasRole("root"); // 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root"); // 是否为超级管理员
|
||||
|
||||
Contest contest = contestService.getById(submitIdListDto.getCid());
|
||||
|
||||
boolean isSealRank = false;
|
||||
|
||||
boolean isContestAdmin = isRoot || userRolesVo.getUid().equals(contest.getUid());
|
||||
// 如果是封榜时间且不是比赛管理员和超级管理员
|
||||
if (contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode() &&
|
||||
contest.getSealRank() && contest.getSealRankTime().before(new Date()) &&
|
||||
!root && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
!isContestAdmin) {
|
||||
isSealRank = true;
|
||||
}
|
||||
|
||||
|
@ -508,7 +515,7 @@ public class JudgeController {
|
|||
judge.setVjudgeUsername(null);
|
||||
judge.setVjudgeSubmitId(null);
|
||||
judge.setVjudgePassword(null);
|
||||
if (!judge.getUid().equals(userRolesVo.getUid())){
|
||||
if (!judge.getUid().equals(userRolesVo.getUid()) && !isContestAdmin) {
|
||||
judge.setTime(null);
|
||||
judge.setMemory(null);
|
||||
judge.setLength(null);
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="top.hcode.hoj.dao.ContestMapper">
|
||||
<select id="getContestList" resultType="top.hcode.hoj.pojo.vo.ContestVo" useCache="true">
|
||||
select c.id,c.author,c.title,c.description,c.type,c.status,c.source,c.auth,c.start_time,c.end_time,c.duration
|
||||
select c.id,c.author,c.title,c.description,c.type,c.status,c.source,c.auth,c.start_time,
|
||||
c.end_time,c.duration,c.open_rank
|
||||
from contest c
|
||||
<where>
|
||||
c.visible=true
|
||||
|
@ -20,7 +21,7 @@
|
|||
</select>
|
||||
<select id="getContestInfoById" resultType="top.hcode.hoj.pojo.vo.ContestVo" useCache="true">
|
||||
select c.id,c.author,c.open_print,c.title,c.type,c.status,c.description,c.seal_rank,
|
||||
c.seal_rank_time,c.source,c.auth,c.start_time,c.end_time,c.duration,c.rank_show_name
|
||||
c.seal_rank_time,c.source,c.auth,c.start_time,c.end_time,c.duration,c.rank_show_name,c.open_rank
|
||||
from contest c where c.id = #{cid} and c.visible=true
|
||||
</select>
|
||||
<select id="getWithinNext14DaysContests" resultType="top.hcode.hoj.pojo.vo.ContestVo">
|
||||
|
|
|
@ -114,7 +114,10 @@ public class Dispatcher {
|
|||
.eq("oj", finalOj)
|
||||
.eq("username", data.getUsername())
|
||||
.set("status", true);
|
||||
remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);
|
||||
boolean isOK = remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);
|
||||
if (!isOK) { // 重试8次
|
||||
tryAgainUpdateAccount(remoteJudgeAccountUpdateWrapper, finalOj, data.getUsername());
|
||||
}
|
||||
}
|
||||
checkResult(null, submitId);
|
||||
scheduler.shutdown();
|
||||
|
@ -166,11 +169,11 @@ public class Dispatcher {
|
|||
judgeServerUpdateWrapper.setSql("task_number = task_number-1").eq("id", id);
|
||||
boolean isOk = judgeServerService.update(judgeServerUpdateWrapper);
|
||||
if (!isOk) { // 重试八次
|
||||
tryAgainUpdate(judgeServerUpdateWrapper);
|
||||
tryAgainUpdateJudge(judgeServerUpdateWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
public void tryAgainUpdate(UpdateWrapper<JudgeServer> updateWrapper) {
|
||||
public void tryAgainUpdateJudge(UpdateWrapper<JudgeServer> updateWrapper) {
|
||||
boolean retryable;
|
||||
int attemptNumber = 0;
|
||||
do {
|
||||
|
@ -191,4 +194,28 @@ public class Dispatcher {
|
|||
}
|
||||
} while (retryable);
|
||||
}
|
||||
|
||||
|
||||
public void tryAgainUpdateAccount(UpdateWrapper<RemoteJudgeAccount> updateWrapper, String remoteJudge, String username) {
|
||||
boolean retryable;
|
||||
int attemptNumber = 0;
|
||||
do {
|
||||
boolean success = remoteJudgeAccountService.update(updateWrapper);
|
||||
if (success) {
|
||||
return;
|
||||
} else {
|
||||
attemptNumber++;
|
||||
retryable = attemptNumber < 8;
|
||||
if (attemptNumber == 8) {
|
||||
log.error("远程判题:修正账号为可用状态失败----------->{}", "oj:" + remoteJudge + ",username:" + username);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} while (retryable);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ import cn.hutool.json.JSONObject;
|
|||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -20,15 +19,14 @@ import top.hcode.hoj.utils.RedisUtils;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Component
|
||||
public class RemoteJudgeReceiver {
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private RemoteJudgeDispatcher remoteJudgeDispatcher;
|
||||
|
||||
@Autowired
|
||||
private JudgeServiceImpl judgeService;
|
||||
|
||||
|
@ -74,96 +72,167 @@ public class RemoteJudgeReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
public void handleJudgeMsg(Judge judge, String token, String remoteJudgeProblem, Boolean isContest, Integer tryAgainNum,
|
||||
public void handleJudgeMsg(Judge judge, String token, String remoteJudgeProblem, Boolean isContest,
|
||||
Integer tryAgainNum,
|
||||
Boolean isHasSubmitIdRemoteReJudge, String remoteOJName) {
|
||||
|
||||
boolean isNeedAccountRejudge = remoteOJName.equals(Constants.RemoteOJ.POJ.getName())
|
||||
&& isHasSubmitIdRemoteReJudge;
|
||||
|
||||
String remoteOJAccountType = remoteOJName; // GYM与CF共用账号
|
||||
if (remoteOJName.equals(Constants.RemoteOJ.GYM.getName())) {
|
||||
remoteOJAccountType = Constants.RemoteOJ.CODEFORCES.getName();
|
||||
}
|
||||
|
||||
ToJudge toJudge = new ToJudge();
|
||||
toJudge.setJudge(judge)
|
||||
.setTryAgainNum(tryAgainNum)
|
||||
.setToken(token)
|
||||
.setRemoteJudgeProblem(remoteJudgeProblem);
|
||||
if (isHasSubmitIdRemoteReJudge && !isNeedAccountRejudge) { // 除POJ外的vJudge如果有submitId,则直接获取结果
|
||||
|
||||
if (remoteOJName.equals(Constants.RemoteOJ.CODEFORCES.getName())
|
||||
|| remoteOJName.equals(Constants.RemoteOJ.GYM.getName())) { // GYM与CF共用账号
|
||||
cfJudge(isHasSubmitIdRemoteReJudge, toJudge, judge);
|
||||
} else if (remoteOJName.equals(Constants.RemoteOJ.POJ.getName())) {
|
||||
pojJudge(isHasSubmitIdRemoteReJudge, toJudge, judge);
|
||||
} else {
|
||||
commonJudge(remoteOJName, isHasSubmitIdRemoteReJudge, toJudge, judge);
|
||||
}
|
||||
// 如果队列中还有任务,则继续处理
|
||||
processWaitingTask();
|
||||
}
|
||||
|
||||
|
||||
private void commonJudge(String OJName, Boolean isHasSubmitIdRemoteReJudge, ToJudge toJudge, Judge judge) {
|
||||
|
||||
if (isHasSubmitIdRemoteReJudge) {
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(true);
|
||||
toJudge.setUsername(judge.getVjudgeUsername());
|
||||
toJudge.setPassword(judge.getVjudgePassword());
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
// 如果队列中还有任务,则继续处理
|
||||
processWaitingTask();
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
RemoteJudgeAccount account = null;
|
||||
int index = 0;
|
||||
int size = 0;
|
||||
if (remoteOJAccountType.equals(Constants.RemoteOJ.CODEFORCES.getName())) {
|
||||
HashMap<String, Object> result = chooseUtils.chooseFixedAccount(remoteOJAccountType);
|
||||
if (result != null) {
|
||||
account = (RemoteJudgeAccount) result.get("account");
|
||||
index = (int) result.get("index");
|
||||
size = (int) result.get("size");
|
||||
// 尝试600s
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
AtomicInteger tryNum = new AtomicInteger(0);
|
||||
Runnable getResultTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tryNum.getAndIncrement();
|
||||
RemoteJudgeAccount account = chooseUtils.chooseRemoteAccount(OJName, judge.getVjudgeUsername(), false);
|
||||
if (account != null) {
|
||||
toJudge.setUsername(account.getUsername())
|
||||
.setPassword(account.getPassword());
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(false);
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
scheduler.shutdown();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
account = chooseUtils.chooseRemoteAccount(remoteOJAccountType, judge.getVjudgeUsername(), isNeedAccountRejudge);
|
||||
}
|
||||
|
||||
if (account == null) {
|
||||
if (tryAgainNum >= 200) {
|
||||
if (tryNum.get() > 200) {
|
||||
// 获取调用多次失败可能为系统忙碌,判为提交失败
|
||||
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
|
||||
judge.setErrorMessage("Submission failed! Please resubmit this submission again!" +
|
||||
"Cause: Waiting for account scheduling timeout");
|
||||
judgeService.updateById(judge);
|
||||
} else {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
scheduler.scheduleAtFixedRate(getResultTask, 0, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
if (isNeedAccountRejudge) {
|
||||
if (StringUtils.isEmpty(judge.getVjudgeUsername())) {
|
||||
// poj以往的账号丢失了,那么只能重新从头到尾提交
|
||||
remoteJudgeDispatcher.sendTask(judge, token, remoteJudgeProblem, isContest,
|
||||
tryAgainNum + 1, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QueryWrapper<RemoteJudgeAccount> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("oj", remoteOJAccountType).eq("username", judge.getVjudgeUsername());
|
||||
int count = remoteJudgeAccountService.count(queryWrapper);
|
||||
if (count == 0) {
|
||||
// poj以往的账号丢失了,那么只能重新从头到尾提交
|
||||
remoteJudgeDispatcher.sendTask(judge, token, remoteJudgeProblem, isContest,
|
||||
tryAgainNum + 1, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
private void pojJudge(Boolean isHasSubmitIdRemoteReJudge, ToJudge toJudge, Judge judge) {
|
||||
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
remoteJudgeDispatcher.sendTask(judge, token, remoteJudgeProblem, isContest,
|
||||
tryAgainNum + 1, isHasSubmitIdRemoteReJudge);
|
||||
|
||||
if (StringUtils.isEmpty(judge.getVjudgeUsername())) {
|
||||
isHasSubmitIdRemoteReJudge = false;
|
||||
}
|
||||
|
||||
if (isHasSubmitIdRemoteReJudge) {
|
||||
QueryWrapper<RemoteJudgeAccount> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("oj", Constants.RemoteOJ.POJ.getName())
|
||||
.eq("username", judge.getVjudgeUsername());
|
||||
int count = remoteJudgeAccountService.count(queryWrapper);
|
||||
if (count == 0) {
|
||||
// poj以往的账号丢失了,那么只能重新从头到尾提交
|
||||
isHasSubmitIdRemoteReJudge = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试600s
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
AtomicInteger tryNum = new AtomicInteger(0);
|
||||
boolean finalIsHasSubmitIdRemoteReJudge = isHasSubmitIdRemoteReJudge;
|
||||
Runnable getResultTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tryNum.getAndIncrement();
|
||||
RemoteJudgeAccount account = chooseUtils.chooseRemoteAccount(Constants.RemoteOJ.POJ.getName()
|
||||
, judge.getVjudgeUsername(), finalIsHasSubmitIdRemoteReJudge);
|
||||
if (account != null) {
|
||||
toJudge.setUsername(account.getUsername())
|
||||
.setPassword(account.getPassword());
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(finalIsHasSubmitIdRemoteReJudge);
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
scheduler.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
toJudge.setUsername(account.getUsername())
|
||||
.setPassword(account.getPassword());
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(isNeedAccountRejudge);
|
||||
toJudge.setIndex(index);
|
||||
toJudge.setSize(size);
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
// 如果队列中还有任务,则继续处理
|
||||
processWaitingTask();
|
||||
if (tryNum.get() > 200) {
|
||||
// 获取调用多次失败可能为系统忙碌,判为提交失败
|
||||
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
|
||||
judge.setErrorMessage("Submission failed! Please resubmit this submission again!" +
|
||||
"Cause: Waiting for account scheduling timeout");
|
||||
judgeService.updateById(judge);
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
scheduler.scheduleAtFixedRate(getResultTask, 0, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void cfJudge(Boolean isHasSubmitIdRemoteReJudge, ToJudge toJudge, Judge judge) {
|
||||
|
||||
if (isHasSubmitIdRemoteReJudge) {
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(true);
|
||||
toJudge.setUsername(judge.getVjudgeUsername());
|
||||
toJudge.setPassword(judge.getVjudgePassword());
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试600s
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
AtomicInteger tryNum = new AtomicInteger(0);
|
||||
Runnable getResultTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tryNum.getAndIncrement();
|
||||
HashMap<String, Object> result = chooseUtils.chooseFixedAccount(Constants.RemoteOJ.CODEFORCES.getName());
|
||||
if (result != null) {
|
||||
RemoteJudgeAccount account = (RemoteJudgeAccount) result.get("account");
|
||||
int index = (int) result.get("index");
|
||||
int size = (int) result.get("size");
|
||||
toJudge.setUsername(account.getUsername())
|
||||
.setPassword(account.getPassword());
|
||||
toJudge.setIsHasSubmitIdRemoteReJudge(false);
|
||||
toJudge.setIndex(index);
|
||||
toJudge.setSize(size);
|
||||
// 调用判题服务
|
||||
dispatcher.dispatcher("judge", "/remote-judge", toJudge);
|
||||
scheduler.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryNum.get() > 200) {
|
||||
// 获取调用多次失败可能为系统忙碌,判为提交失败
|
||||
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
|
||||
judge.setErrorMessage("Submission failed! Please resubmit this submission again!" +
|
||||
"Cause: Waiting for account scheduling timeout");
|
||||
judgeService.updateById(judge);
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
scheduler.scheduleAtFixedRate(getResultTask, 0, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ import java.util.HashMap;
|
|||
@Accessors(chain = true)
|
||||
public class ACMContestRankVo {
|
||||
|
||||
@ApiModelProperty(value = "排名,排名为-1则为打星队伍")
|
||||
private Integer rank;
|
||||
|
||||
@ApiModelProperty(value = "用户id")
|
||||
private String uid;
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package top.hcode.hoj.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/12/7 19:45
|
||||
* @Description:
|
||||
*/
|
||||
@ApiModel(value="管理比赛的回传实体", description="")
|
||||
@Data
|
||||
public class AdminContestVo {
|
||||
|
||||
@ApiModelProperty(value = "比赛id")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "比赛创建者id")
|
||||
private String uid;
|
||||
|
||||
@ApiModelProperty(value = "比赛创建者的用户名")
|
||||
private String author;
|
||||
|
||||
@ApiModelProperty(value = "比赛标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty(value = "0为acm赛制,1为比分赛制")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "比赛说明")
|
||||
private String description;
|
||||
|
||||
@ApiModelProperty(value = "比赛来源,原创为0,克隆赛为比赛id")
|
||||
private Integer source;
|
||||
|
||||
@ApiModelProperty(value = "0为公开赛,1为私有赛(访问有密码),2为保护赛(提交有密码)")
|
||||
private Integer auth;
|
||||
|
||||
@ApiModelProperty(value = "比赛密码")
|
||||
private String pwd;
|
||||
|
||||
@ApiModelProperty(value = "开始时间")
|
||||
private Date startTime;
|
||||
|
||||
@ApiModelProperty(value = "结束时间")
|
||||
private Date endTime;
|
||||
|
||||
@ApiModelProperty(value = "比赛时长(s)")
|
||||
private Long duration;
|
||||
|
||||
@ApiModelProperty(value = "是否开启封榜")
|
||||
private Boolean sealRank;
|
||||
|
||||
@ApiModelProperty(value = "封榜起始时间,一直到比赛结束,不刷新榜单")
|
||||
private Date sealRankTime;
|
||||
|
||||
@ApiModelProperty(value = "比赛结束是否自动解除封榜,自动转换成真实榜单")
|
||||
private Boolean autoRealRank;
|
||||
|
||||
@ApiModelProperty(value = "-1为未开始,0为进行中,1为已结束")
|
||||
private Integer status;
|
||||
|
||||
@ApiModelProperty(value = "是否可见")
|
||||
private Boolean visible;
|
||||
|
||||
@ApiModelProperty(value = "是否打开打印功能")
|
||||
private Boolean openPrint;
|
||||
|
||||
@ApiModelProperty(value = "是否打开账号限制")
|
||||
private Boolean openAccountLimit;
|
||||
|
||||
@ApiModelProperty(value = "账号限制规则 <prefix>**</prefix><suffix>**</suffix><start>**</start><end>**</end><extra>**</extra>")
|
||||
private String accountLimitRule;
|
||||
|
||||
@ApiModelProperty(value = "排行榜显示(username、nickname、realname)")
|
||||
private String rankShowName;
|
||||
|
||||
@ApiModelProperty(value = "打星用户列表")
|
||||
private List<String> starAccount;
|
||||
|
||||
@ApiModelProperty(value = "是否开放比赛榜单")
|
||||
private Boolean openRank;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package top.hcode.hoj.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import top.hcode.hoj.pojo.entity.contest.ContestProblem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/12/8 12:32
|
||||
* @Description:
|
||||
*/
|
||||
@ApiModel(value = "赛外排行榜所需的比赛信息,同时包括题目题号、气球颜色", description = "")
|
||||
@Data
|
||||
public class ContestOutsideInfo {
|
||||
|
||||
@ApiModelProperty(value = "比赛信息")
|
||||
private ContestVo contest;
|
||||
|
||||
@ApiModelProperty(value = "比赛题目信息列表")
|
||||
private List<ContestProblem> problemList;
|
||||
}
|
|
@ -65,4 +65,7 @@ public class ContestVo implements Serializable {
|
|||
|
||||
@ApiModelProperty(value = "排行榜显示(username、nickname、realname)")
|
||||
private String rankShowName;
|
||||
|
||||
@ApiModelProperty(value = "是否开放比赛榜单")
|
||||
private Boolean openRank;
|
||||
}
|
|
@ -15,6 +15,10 @@ import java.util.HashMap;
|
|||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OIContestRankVo {
|
||||
|
||||
@ApiModelProperty(value = "排名,排名为-1则为打星队伍")
|
||||
private Integer rank;
|
||||
|
||||
@ApiModelProperty(value = "用户id")
|
||||
private String uid;
|
||||
|
||||
|
|
|
@ -285,14 +285,14 @@ public class ScheduleServiceImpl implements ScheduleService {
|
|||
DateTime dateTime = DateUtil.offsetMonth(new Date(), -6);
|
||||
String threeMonthsBeforeDate = dateTime.toString("yyyy-MM-dd HH:mm:ss");
|
||||
sessionQueryWrapper.select("distinct uid");
|
||||
sessionQueryWrapper.apply("UNIX_TIMESTAMP(gmt_create) >= UNIX_TIMESTAMP('{0}')", threeMonthsBeforeDate);
|
||||
sessionQueryWrapper.apply("UNIX_TIMESTAMP(gmt_create) >= UNIX_TIMESTAMP('" + threeMonthsBeforeDate + "')");
|
||||
List<Session> sessionList = sessionService.list(sessionQueryWrapper);
|
||||
|
||||
if (sessionList.size() > 0) {
|
||||
List<String> uidList = sessionList.stream().map(Session::getUid).collect(Collectors.toList());
|
||||
UpdateWrapper<Session> sessionUpdateWrapper = new UpdateWrapper<>();
|
||||
sessionQueryWrapper.in("uid", uidList)
|
||||
.and(t -> t.apply("UNIX_TIMESTAMP(gmt_create) < UNIX_TIMESTAMP('{0}')", threeMonthsBeforeDate));
|
||||
.and(t -> t.apply("UNIX_TIMESTAMP(gmt_create) < UNIX_TIMESTAMP('" + threeMonthsBeforeDate + "')"));
|
||||
|
||||
boolean isSuccess = sessionService.remove(sessionUpdateWrapper);
|
||||
if (!isSuccess) {
|
||||
|
|
|
@ -16,7 +16,11 @@ public interface FileService extends IService<File> {
|
|||
|
||||
List<List<String>> getContestRankExcelHead(List<String> contestProblemDisplayIDList, Boolean isACM);
|
||||
|
||||
List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVo> acmContestRankVoList, List<String> contestProblemDisplayIDList);
|
||||
List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVo> acmContestRankVoList,
|
||||
List<String> contestProblemDisplayIDList,
|
||||
String rankShowName);
|
||||
|
||||
List<List<Object>> changOIContestRankToExcelRowList(List<OIContestRankVo> oiContestRankVoList, List<String> contestProblemDisplayIDList);
|
||||
List<List<Object>> changOIContestRankToExcelRowList(List<OIContestRankVo> oiContestRankVoList,
|
||||
List<String> contestProblemDisplayIDList,
|
||||
String rankShowName);
|
||||
}
|
||||
|
|
|
@ -34,37 +34,45 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<File> queryCarouselFileList(){
|
||||
public List<File> queryCarouselFileList() {
|
||||
return fileMapper.queryCarouselFileList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> getContestRankExcelHead(List<String> contestProblemDisplayIDList, Boolean isACM) {
|
||||
List<List<String>> headList = new LinkedList<>();
|
||||
|
||||
List<String> head0 = new LinkedList<>();
|
||||
head0.add("User ID");
|
||||
head0.add("Rank");
|
||||
|
||||
List<String> head1 = new LinkedList<>();
|
||||
head1.add("Username");
|
||||
List<String> head2 = new LinkedList<>();
|
||||
head2.add("Real Name");
|
||||
head2.add("ShowName");
|
||||
List<String> head3 = new LinkedList<>();
|
||||
head3.add("Real Name");
|
||||
List<String> head4 = new LinkedList<>();
|
||||
head4.add("School");
|
||||
|
||||
headList.add(head0);
|
||||
headList.add(head1);
|
||||
headList.add(head2);
|
||||
headList.add(head3);
|
||||
headList.add(head4);
|
||||
|
||||
List<String> head3 = new LinkedList<>();
|
||||
List<String> head5 = new LinkedList<>();
|
||||
if (isACM) {
|
||||
head3.add("AC");
|
||||
List<String> head4 = new LinkedList<>();
|
||||
head4.add("Total Submission");
|
||||
List<String> head5 = new LinkedList<>();
|
||||
head5.add("Total Penalty Time");
|
||||
headList.add(head3);
|
||||
headList.add(head4);
|
||||
head5.add("AC");
|
||||
List<String> head6 = new LinkedList<>();
|
||||
head6.add("Total Submission");
|
||||
List<String> head7 = new LinkedList<>();
|
||||
head7.add("Total Penalty Time");
|
||||
headList.add(head5);
|
||||
headList.add(head6);
|
||||
headList.add(head7);
|
||||
} else {
|
||||
head3.add("Total Score");
|
||||
headList.add(head3);
|
||||
head5.add("Total Score");
|
||||
headList.add(head5);
|
||||
}
|
||||
|
||||
// 添加题目头
|
||||
|
@ -77,13 +85,25 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVo> acmContestRankVoList, List<String> contestProblemDisplayIDList) {
|
||||
public List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVo> acmContestRankVoList,
|
||||
List<String> contestProblemDisplayIDList,
|
||||
String rankShowName) {
|
||||
List<List<Object>> allRowDataList = new LinkedList<>();
|
||||
for (ACMContestRankVo acmContestRankVo : acmContestRankVoList) {
|
||||
List<Object> rowData = new LinkedList<>();
|
||||
rowData.add(acmContestRankVo.getUid());
|
||||
rowData.add(acmContestRankVo.getRank() == -1 ? "*" : acmContestRankVo.getRank().toString());
|
||||
rowData.add(acmContestRankVo.getUsername());
|
||||
if ("username".equals(rankShowName)) {
|
||||
rowData.add(acmContestRankVo.getUsername());
|
||||
} else if ("realname".equals(rankShowName)) {
|
||||
rowData.add(acmContestRankVo.getRealname());
|
||||
} else if ("nickname".equals(rankShowName)) {
|
||||
rowData.add(acmContestRankVo.getNickname());
|
||||
} else {
|
||||
rowData.add("");
|
||||
}
|
||||
rowData.add(acmContestRankVo.getRealname());
|
||||
rowData.add(acmContestRankVo.getSchool());
|
||||
rowData.add(acmContestRankVo.getAc());
|
||||
rowData.add(acmContestRankVo.getTotal());
|
||||
rowData.add(acmContestRankVo.getTotalTime());
|
||||
|
@ -92,11 +112,23 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
|
|||
HashMap<String, Object> problemInfo = submissionInfo.getOrDefault(displayID, null);
|
||||
if (problemInfo != null) { // 如果是有提交记录的
|
||||
boolean isAC = (boolean) problemInfo.getOrDefault("isAC", false);
|
||||
String info;
|
||||
String info = "";
|
||||
int errorNum = (int) problemInfo.getOrDefault("errorNum", 0);
|
||||
int tryNum = (int) problemInfo.getOrDefault("tryNum", 0);
|
||||
if (isAC) {
|
||||
info = "AC(-" + problemInfo.getOrDefault("errorNum", 0) + ")";
|
||||
if (errorNum == 0) {
|
||||
info = "+(1)";
|
||||
} else {
|
||||
info = "-(" + (errorNum + 1) + ")";
|
||||
}
|
||||
} else {
|
||||
info = "WA(-" + problemInfo.getOrDefault("errorNum", 0) + ")";
|
||||
if (tryNum != 0 && errorNum != 0) {
|
||||
info = "-(" + errorNum + "+" + tryNum + ")";
|
||||
} else if (errorNum != 0) {
|
||||
info = "-(" + errorNum + ")";
|
||||
} else if (tryNum != 0) {
|
||||
info = "?(" + tryNum + ")";
|
||||
}
|
||||
}
|
||||
rowData.add(info);
|
||||
} else {
|
||||
|
@ -109,13 +141,24 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<List<Object>> changOIContestRankToExcelRowList(List<OIContestRankVo> oiContestRankVoList, List<String> contestProblemDisplayIDList) {
|
||||
public List<List<Object>> changOIContestRankToExcelRowList(List<OIContestRankVo> oiContestRankVoList,
|
||||
List<String> contestProblemDisplayIDList,
|
||||
String rankShowName) {
|
||||
List<List<Object>> allRowDataList = new LinkedList<>();
|
||||
for (OIContestRankVo oiContestRankVo : oiContestRankVoList) {
|
||||
List<Object> rowData = new LinkedList<>();
|
||||
rowData.add(oiContestRankVo.getUid());
|
||||
rowData.add(oiContestRankVo.getUsername());
|
||||
rowData.add(oiContestRankVo.getRank() == -1 ? "*" : oiContestRankVo.getRank().toString());
|
||||
if ("username".equals(rankShowName)) {
|
||||
rowData.add(oiContestRankVo.getUsername());
|
||||
} else if ("realname".equals(rankShowName)) {
|
||||
rowData.add(oiContestRankVo.getRealname());
|
||||
} else if ("nickname".equals(rankShowName)) {
|
||||
rowData.add(oiContestRankVo.getNickname());
|
||||
} else {
|
||||
rowData.add("");
|
||||
}
|
||||
rowData.add(oiContestRankVo.getRealname());
|
||||
rowData.add(oiContestRankVo.getSchool());
|
||||
rowData.add(oiContestRankVo.getTotalScore());
|
||||
HashMap<String, Integer> submissionInfo = oiContestRankVo.getSubmissionInfo();
|
||||
for (String displayID : contestProblemDisplayIDList) {
|
||||
|
@ -130,4 +173,6 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
|
|||
}
|
||||
return allRowDataList;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -30,16 +30,18 @@ public interface ContestRecordService extends IService<ContestRecord> {
|
|||
|
||||
IPage<ContestRecord> getACInfo(Integer currentPage, Integer limit, Integer status, Long cid, String contestCreatorId);
|
||||
|
||||
IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Contest contest, int currentPage, int limit);
|
||||
IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Boolean removeStar, Contest contest, int currentPage, int limit);
|
||||
|
||||
IPage<OIContestRankVo> getContestOIRank(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime, Date startTime, Date endTime, int currentPage, int limit);
|
||||
IPage<OIContestRankVo> getContestOIRank(Boolean isOpenSealRank, Boolean removeStar, Contest contest, int currentPage, int limit);
|
||||
|
||||
List<ContestRecordVo> getOIContestRecord(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime, Date startTime, Date endTime);
|
||||
|
||||
|
||||
List<ContestRecordVo> getACMContestRecord(String username, Long cid);
|
||||
|
||||
|
||||
List<UserInfo> getSuperAdminList();
|
||||
|
||||
List<ACMContestRankVo> getACMContestScoreboard(Boolean isOpenSealRank, Boolean removeStar, Contest contest);
|
||||
|
||||
List<OIContestRankVo> getOIContestScoreboard(Boolean isOpenSealRank, Boolean removeStar, Contest contest);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package top.hcode.hoj.service.contest.impl;
|
|||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
@ -9,6 +11,7 @@ import org.apache.shiro.SecurityUtils;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.dao.ContestProblemMapper;
|
||||
import top.hcode.hoj.dao.JudgeMapper;
|
||||
|
@ -211,15 +214,26 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
return userInfoMapper.getSuperAdminList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ACMContestRankVo> getACMContestScoreboard(Boolean isOpenSealRank, Boolean removeStar, Contest contest) {
|
||||
List<ContestRecordVo> acmContestRecord = getACMContestRecord(contest.getAuthor(), contest.getId());
|
||||
return calcACMRank(isOpenSealRank, removeStar, contest, acmContestRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Contest contest,
|
||||
public List<OIContestRankVo> getOIContestScoreboard(Boolean isOpenSealRank, Boolean removeStar, Contest contest) {
|
||||
return getOIContestOrderRank(isOpenSealRank, removeStar, contest);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Boolean removeStar, Contest contest,
|
||||
int currentPage, int limit) {
|
||||
|
||||
List<ContestRecordVo> acmContestRecord = getACMContestRecord(contest.getAuthor(), contest.getId());
|
||||
|
||||
// 进行排序计算
|
||||
List<ACMContestRankVo> orderResultList = calcACMRank(isOpenSealRank, contest, acmContestRecord);
|
||||
List<ACMContestRankVo> orderResultList = calcACMRank(isOpenSealRank, removeStar, contest, acmContestRecord);
|
||||
// 计算好排行榜,然后进行分页
|
||||
Page<ACMContestRankVo> page = new Page<>(currentPage, limit);
|
||||
int count = orderResultList.size();
|
||||
|
@ -239,33 +253,9 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
|
||||
|
||||
@Override
|
||||
public IPage<OIContestRankVo> getContestOIRank(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime,
|
||||
Date startTime, Date endTime, int currentPage, int limit) {
|
||||
|
||||
// 获取每个用户每道题最近一次提交
|
||||
if (!isOpenSealRank) {
|
||||
// 超级管理员和比赛管理员选择强制刷新 或者 比赛结束
|
||||
return getOIContestRank(cid, contestAuthor, false, sealTime, startTime, endTime, currentPage, limit);
|
||||
} else {
|
||||
String key = Constants.Contest.OI_CONTEST_RANK_CACHE.getName() + "_" + cid;
|
||||
Page<OIContestRankVo> page = (Page<OIContestRankVo>) redisUtils.get(key);
|
||||
if (page == null) {
|
||||
page = getOIContestRank(cid, contestAuthor, true, sealTime, startTime, endTime, currentPage, limit);
|
||||
redisUtils.set(key, page, 2 * 3600);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Page<OIContestRankVo> getOIContestRank(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime,
|
||||
Date startTime, Date endTime, int currentPage, int limit) {
|
||||
|
||||
List<ContestRecordVo> oiContestRecord = contestRecordMapper.getOIContestRecord(cid, contestAuthor, isOpenSealRank, sealTime, startTime, endTime);
|
||||
// 计算排名
|
||||
List<OIContestRankVo> orderResultList = calcOIRank(oiContestRecord);
|
||||
|
||||
public IPage<OIContestRankVo> getContestOIRank(Boolean isOpenSealRank, Boolean removeStar, Contest contest,
|
||||
int currentPage, int limit) {
|
||||
List<OIContestRankVo> orderResultList = getOIContestOrderRank(isOpenSealRank, removeStar, contest);
|
||||
// 计算好排行榜,然后进行分页
|
||||
Page<OIContestRankVo> page = new Page<>(currentPage, limit);
|
||||
int count = orderResultList.size();
|
||||
|
@ -282,6 +272,29 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
return page;
|
||||
}
|
||||
|
||||
public List<OIContestRankVo> getOIContestOrderRank(Boolean isOpenSealRank, Boolean removeStar, Contest contest) {
|
||||
List<OIContestRankVo> orderResultList;
|
||||
if (!isOpenSealRank) {
|
||||
// 封榜解除 获取最新数据
|
||||
// 获取每个用户每道题最近一次提交
|
||||
List<ContestRecordVo> oiContestRecord = getOIContestRecord(contest.getId(),
|
||||
contest.getAuthor(), false, contest.getSealRankTime(), contest.getStartTime(), contest.getEndTime());
|
||||
// 计算排名
|
||||
orderResultList = calcOIRank(oiContestRecord, contest, removeStar);
|
||||
} else {
|
||||
String key = Constants.Contest.OI_CONTEST_RANK_CACHE.getName() + "_" + contest.getId();
|
||||
orderResultList = (List<OIContestRankVo>) redisUtils.get(key);
|
||||
if (orderResultList == null) {
|
||||
List<ContestRecordVo> oiContestRecord = getOIContestRecord(contest.getId(),
|
||||
contest.getAuthor(), true, contest.getSealRankTime(), contest.getStartTime(), contest.getEndTime());
|
||||
// 计算排名
|
||||
orderResultList = calcOIRank(oiContestRecord, contest, removeStar);
|
||||
redisUtils.set(key, orderResultList, 2 * 3600);
|
||||
}
|
||||
}
|
||||
return orderResultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ContestRecordVo> getOIContestRecord(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime, Date startTime, Date endTime) {
|
||||
return contestRecordMapper.getOIContestRecord(cid, contestAuthor, isOpenSealRank, sealTime, startTime, endTime);
|
||||
|
@ -293,7 +306,7 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
}
|
||||
|
||||
|
||||
public List<ACMContestRankVo> calcACMRank(boolean isOpenSealRank, Contest contest, List<ContestRecordVo> contestRecordList) {
|
||||
public List<ACMContestRankVo> calcACMRank(boolean isOpenSealRank, boolean removeStar, Contest contest, List<ContestRecordVo> contestRecordList) {
|
||||
|
||||
List<UserInfo> superAdminList = getSuperAdminList();
|
||||
|
||||
|
@ -403,10 +416,41 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
.thenComparing(ACMContestRankVo::getTotalTime) //再以总耗时升序
|
||||
).collect(Collectors.toList());
|
||||
|
||||
// 需要打星的用户名列表
|
||||
List<String> starAccountList = starAccountToList(contest.getStarAccount());
|
||||
|
||||
// 如果选择了移除打星队伍,同时该用户属于打星队伍,则将其移除
|
||||
if (removeStar) {
|
||||
orderResultList.removeIf(acmContestRankVo -> starAccountList.contains(acmContestRankVo.getUsername()));
|
||||
}
|
||||
|
||||
int rankNum = 1;
|
||||
int len = orderResultList.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
ACMContestRankVo currentACMRankVo = orderResultList.get(i);
|
||||
if (starAccountList.contains(currentACMRankVo.getUsername())) {
|
||||
// 打星队伍排名为-1
|
||||
currentACMRankVo.setRank(-1);
|
||||
} else {
|
||||
if (i != 0) {
|
||||
ACMContestRankVo lastACMRankVo = orderResultList.get(i - 1);
|
||||
// 当前用户的总罚时和AC数跟前一个用户一样的话,排名则一样
|
||||
if (lastACMRankVo.getAc().equals(currentACMRankVo.getAc())
|
||||
&& lastACMRankVo.getTotalTime().equals(currentACMRankVo.getTotalTime())) {
|
||||
currentACMRankVo.setRank(lastACMRankVo.getRank());
|
||||
} else {
|
||||
currentACMRankVo.setRank(rankNum);
|
||||
}
|
||||
} else {
|
||||
currentACMRankVo.setRank(rankNum);
|
||||
}
|
||||
rankNum++;
|
||||
}
|
||||
}
|
||||
return orderResultList;
|
||||
}
|
||||
|
||||
public List<OIContestRankVo> calcOIRank(List<ContestRecordVo> oiContestRecord) {
|
||||
public List<OIContestRankVo> calcOIRank(List<ContestRecordVo> oiContestRecord, Contest contest, Boolean removeStar) {
|
||||
|
||||
List<UserInfo> superAdminList = getSuperAdminList();
|
||||
|
||||
|
@ -501,6 +545,38 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
.sorted(Comparator.comparing(OIContestRankVo::getTotalScore, Comparator.reverseOrder())
|
||||
.thenComparing(OIContestRankVo::getTotalTime, Comparator.naturalOrder()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 需要打星的用户名列表
|
||||
List<String> starAccountList = starAccountToList(contest.getStarAccount());
|
||||
|
||||
// 如果选择了移除打星队伍,同时该用户属于打星队伍,则将其移除
|
||||
if (removeStar) {
|
||||
orderResultList.removeIf(acmContestRankVo -> starAccountList.contains(acmContestRankVo.getUsername()));
|
||||
}
|
||||
|
||||
int rankNum = 1;
|
||||
int len = orderResultList.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
OIContestRankVo currentOIRankVo = orderResultList.get(i);
|
||||
if (starAccountList.contains(currentOIRankVo.getUsername())) {
|
||||
// 打星队伍排名为-1
|
||||
currentOIRankVo.setRank(-1);
|
||||
} else {
|
||||
if (i != 0) {
|
||||
OIContestRankVo lastOIRankVo = orderResultList.get(i - 1);
|
||||
// 当前用户的程序总运行时间和总得分跟前一个用户一样的话,排名则一样
|
||||
if (lastOIRankVo.getTotalScore().equals(currentOIRankVo.getTotalScore())
|
||||
&& lastOIRankVo.getTotalTime().equals(currentOIRankVo.getTotalTime())) {
|
||||
currentOIRankVo.setRank(lastOIRankVo.getRank());
|
||||
} else {
|
||||
currentOIRankVo.setRank(rankNum);
|
||||
}
|
||||
} else {
|
||||
currentOIRankVo.setRank(rankNum);
|
||||
}
|
||||
rankNum++;
|
||||
}
|
||||
}
|
||||
return orderResultList;
|
||||
}
|
||||
|
||||
|
@ -508,4 +584,12 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
|
|||
private boolean isInSealTimeSubmission(Contest contest, Date submissionDate) {
|
||||
return DateUtil.isIn(submissionDate, contest.getSealRankTime(), contest.getEndTime());
|
||||
}
|
||||
|
||||
private List<String> starAccountToList(String starAccountStr) {
|
||||
if (StringUtils.isEmpty(starAccountStr)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
JSONObject jsonObject = JSONUtil.parseObj(starAccountStr);
|
||||
return jsonObject.get("star_account", List.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
|
|||
@Override
|
||||
public Boolean isSealRank(String uid, Contest contest, Boolean forceRefresh, Boolean isRoot) {
|
||||
// 如果是管理员同时选择强制刷新榜单,则封榜无效
|
||||
if (forceRefresh && (isRoot || uid.equals(contest.getUid()))) {
|
||||
if (forceRefresh && (isRoot || contest.getUid().equals(uid))) {
|
||||
return false;
|
||||
} else if (contest.getSealRank() && contest.getSealRankTime() != null) { // 该比赛开启封榜模式
|
||||
Date now = new Date();
|
||||
|
|
|
@ -386,6 +386,9 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
String testcaseDir = problemDto.getUploadTestcaseDir();
|
||||
// 如果是io题目统计总分
|
||||
List<ProblemCase> problemCases = problemDto.getSamples();
|
||||
if (problemCases.size() == 0) {
|
||||
throw new RuntimeException("The test cases of problem must not be empty!");
|
||||
}
|
||||
for (ProblemCase problemCase : problemCases) {
|
||||
if (problemCase.getScore() != null) {
|
||||
sumScore += problemCase.getScore();
|
||||
|
|
|
@ -7,16 +7,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
|
||||
import top.hcode.hoj.pojo.entity.judge.JudgeServer;
|
||||
import top.hcode.hoj.pojo.entity.judge.RemoteJudgeAccount;
|
||||
import top.hcode.hoj.remoteJudge.task.RemoteJudgeFactory;
|
||||
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
|
||||
import top.hcode.hoj.service.impl.JudgeServerServiceImpl;
|
||||
|
||||
import top.hcode.hoj.service.impl.JudgeServiceImpl;
|
||||
import top.hcode.hoj.service.impl.RemoteJudgeAccountServiceImpl;
|
||||
|
||||
import top.hcode.hoj.service.impl.RemoteJudgeServiceImpl;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -35,10 +32,7 @@ public class RemoteJudgeGetResult {
|
|||
private JudgeServiceImpl judgeService;
|
||||
|
||||
@Autowired
|
||||
private RemoteJudgeAccountServiceImpl remoteJudgeAccountService;
|
||||
|
||||
@Autowired
|
||||
private JudgeServerServiceImpl judgeServerService;
|
||||
private RemoteJudgeServiceImpl remoteJudgeService;
|
||||
|
||||
@Transactional
|
||||
@Async
|
||||
|
@ -65,8 +59,8 @@ public class RemoteJudgeGetResult {
|
|||
.eq("submit_id", submitId);
|
||||
judgeService.update(judgeUpdateWrapper);
|
||||
|
||||
changeAccountStatus(remoteJudge, username);
|
||||
changeServerSubmitCFStatus(ip, port);
|
||||
remoteJudgeService.changeAccountStatus(remoteJudge, username);
|
||||
remoteJudgeService.changeServerSubmitCFStatus(ip, port);
|
||||
scheduler.shutdown();
|
||||
|
||||
return;
|
||||
|
@ -81,8 +75,8 @@ public class RemoteJudgeGetResult {
|
|||
status.intValue() != Constants.Judge.STATUS_COMPILING.getStatus()) {
|
||||
|
||||
// 将账号变为可用
|
||||
changeAccountStatus(remoteJudge, username);
|
||||
changeServerSubmitCFStatus(ip, port);
|
||||
remoteJudgeService.changeAccountStatus(remoteJudge, username);
|
||||
remoteJudgeService.changeServerSubmitCFStatus(ip, port);
|
||||
|
||||
Integer time = (Integer) result.getOrDefault("time", null);
|
||||
Integer memory = (Integer) result.getOrDefault("memory", null);
|
||||
|
@ -141,41 +135,6 @@ public class RemoteJudgeGetResult {
|
|||
|
||||
}
|
||||
|
||||
public void changeAccountStatus(String remoteJudge, String username) {
|
||||
|
||||
UpdateWrapper<RemoteJudgeAccount> remoteJudgeAccountUpdateWrapper = new UpdateWrapper<>();
|
||||
remoteJudgeAccountUpdateWrapper.set("status", true)
|
||||
.eq("status", false)
|
||||
.eq("username", username);
|
||||
if (remoteJudge.equals("GYM")) {
|
||||
remoteJudgeAccountUpdateWrapper.eq("oj", "CF");
|
||||
} else {
|
||||
remoteJudgeAccountUpdateWrapper.eq("oj", remoteJudge);
|
||||
}
|
||||
|
||||
boolean isOk = remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);
|
||||
|
||||
if (!isOk) {
|
||||
log.error("远程判题:修正账号为可用状态失败----------->{}", "oj:" + remoteJudge + ",username:" + username);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeServerSubmitCFStatus(String ip, Integer port) {
|
||||
|
||||
if (StringUtils.isEmpty(ip) || port == null) {
|
||||
return;
|
||||
}
|
||||
UpdateWrapper<JudgeServer> JudgeServerUpdateWrapper = new UpdateWrapper<>();
|
||||
JudgeServerUpdateWrapper.set("cf_submittable", true)
|
||||
.eq("ip", ip)
|
||||
.eq("is_remote", true)
|
||||
.eq("port", port);
|
||||
boolean isOk = judgeServerService.update(JudgeServerUpdateWrapper);
|
||||
|
||||
if (!isOk) {
|
||||
log.error("远程判题:修正判题机对CF可提交状态为可用的状态失败----------->{}", "ip:" + ip + ",port:" + port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,13 +7,10 @@ import org.springframework.beans.factory.annotation.Value;
|
|||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
import top.hcode.hoj.pojo.entity.judge.JudgeServer;
|
||||
import top.hcode.hoj.pojo.entity.judge.RemoteJudgeAccount;
|
||||
import top.hcode.hoj.remoteJudge.task.RemoteJudgeFactory;
|
||||
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
|
||||
import top.hcode.hoj.service.impl.JudgeServerServiceImpl;
|
||||
import top.hcode.hoj.service.impl.JudgeServiceImpl;
|
||||
import top.hcode.hoj.service.impl.RemoteJudgeAccountServiceImpl;
|
||||
import top.hcode.hoj.service.impl.RemoteJudgeServiceImpl;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -22,9 +19,6 @@ import java.util.Map;
|
|||
@Slf4j(topic = "hoj")
|
||||
public class RemoteJudgeToSubmit {
|
||||
|
||||
@Autowired
|
||||
private RemoteJudgeAccountServiceImpl remoteJudgeAccountService;
|
||||
|
||||
@Autowired
|
||||
private RemoteJudgeGetResult remoteJudgeGetResult;
|
||||
|
||||
|
@ -32,7 +26,7 @@ public class RemoteJudgeToSubmit {
|
|||
private JudgeServiceImpl judgeService;
|
||||
|
||||
@Autowired
|
||||
private JudgeServerServiceImpl judgeServerService;
|
||||
private RemoteJudgeServiceImpl remoteJudgeService;
|
||||
|
||||
|
||||
@Value("${hoj-judge-server.name}")
|
||||
|
@ -63,18 +57,18 @@ public class RemoteJudgeToSubmit {
|
|||
try {
|
||||
submitResult = remoteJudgeStrategy.submit(username, password, remotePid, language, userCode);
|
||||
} catch (Exception e) {
|
||||
log.error("{%s}", e);
|
||||
log.error("{}", e);
|
||||
errLog = e.getMessage();
|
||||
}
|
||||
|
||||
// 提交失败 前端手动按按钮再次提交 修改状态 STATUS_SUBMITTED_FAILED
|
||||
if (submitResult == null || (Long) submitResult.getOrDefault("runId", -1L) == -1L) {
|
||||
// 将使用的账号放回对应列表
|
||||
changeAccountStatus(remoteJudge, username);
|
||||
remoteJudgeService.changeAccountStatus(remoteJudge, username);
|
||||
if (remoteJudge.equals(Constants.RemoteJudge.GYM_JUDGE.getName())
|
||||
|| remoteJudge.equals(Constants.RemoteJudge.CF_JUDGE.getName())) {
|
||||
// 对CF特殊,归还账号及判题机权限
|
||||
changeServerSubmitCFStatus(serverIp, serverPort);
|
||||
remoteJudgeService.changeServerSubmitCFStatus(serverIp, serverPort);
|
||||
}
|
||||
|
||||
// 更新此次提交状态为提交失败!
|
||||
|
@ -113,37 +107,4 @@ public class RemoteJudgeToSubmit {
|
|||
vjudgeSubmitId, (String) submitResult.get("cookies"),serverIp,serverPort);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void changeAccountStatus(String remoteJudge, String username) {
|
||||
|
||||
UpdateWrapper<RemoteJudgeAccount> remoteJudgeAccountUpdateWrapper = new UpdateWrapper<>();
|
||||
remoteJudgeAccountUpdateWrapper.set("status", true)
|
||||
.eq("status", false)
|
||||
.eq("username", username);
|
||||
if (remoteJudge.equals("GYM")) {
|
||||
remoteJudgeAccountUpdateWrapper.eq("oj", "CF");
|
||||
} else {
|
||||
remoteJudgeAccountUpdateWrapper.eq("oj", remoteJudge);
|
||||
}
|
||||
|
||||
boolean isOk = remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);
|
||||
|
||||
if (!isOk) {
|
||||
log.error("远程判题:修正账号为可用状态失败----------->{}", "oj:" + remoteJudge + ",username:" + username);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeServerSubmitCFStatus(String ip, Integer port) {
|
||||
UpdateWrapper<JudgeServer> JudgeServerUpdateWrapper = new UpdateWrapper<>();
|
||||
JudgeServerUpdateWrapper.set("cf_submittable", true)
|
||||
.eq("ip", ip)
|
||||
.eq("is_remote", true)
|
||||
.eq("port", port);
|
||||
boolean isOk = judgeServerService.update(JudgeServerUpdateWrapper);
|
||||
|
||||
if (!isOk) {
|
||||
log.error("远程判题:修正判题机对CF可提交状态为可用的状态失败----------->{}", "ip:" + ip + ",port:" + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
submitCode(contestId, problemNum, getLanguage(language), userCode);
|
||||
} catch (HttpException e) {
|
||||
// 如果提交出现403可能是cookie失效了,再执行登录,重新提交
|
||||
this.cookies = null;
|
||||
Map<String, Object> loginUtils = getLoginUtils(username, password);
|
||||
int status = (int) loginUtils.get("status");
|
||||
if (status != HttpStatus.SC_MOVED_TEMPORARILY) {
|
||||
|
@ -144,7 +145,7 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
}
|
||||
}
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(2500);
|
||||
TimeUnit.MILLISECONDS.sleep(3000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package top.hcode.hoj.service;
|
||||
|
||||
public interface RemoteJudgeService {
|
||||
|
||||
public void changeAccountStatus(String remoteJudge, String username);
|
||||
|
||||
public void changeServerSubmitCFStatus(String ip, Integer port);
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package top.hcode.hoj.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.pojo.entity.judge.JudgeServer;
|
||||
import top.hcode.hoj.pojo.entity.judge.RemoteJudgeAccount;
|
||||
import top.hcode.hoj.service.JudgeServerService;
|
||||
import top.hcode.hoj.service.RemoteJudgeAccountService;
|
||||
import top.hcode.hoj.service.RemoteJudgeService;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/12/7 23:57
|
||||
* @Description:
|
||||
*/
|
||||
@Service
|
||||
@Slf4j(topic = "hoj")
|
||||
public class RemoteJudgeServiceImpl implements RemoteJudgeService {
|
||||
|
||||
@Autowired
|
||||
private RemoteJudgeAccountService remoteJudgeAccountService;
|
||||
|
||||
@Autowired
|
||||
private JudgeServerService judgeServerService;
|
||||
|
||||
@Override
|
||||
public void changeAccountStatus(String remoteJudge, String username) {
|
||||
|
||||
UpdateWrapper<RemoteJudgeAccount> remoteJudgeAccountUpdateWrapper = new UpdateWrapper<>();
|
||||
remoteJudgeAccountUpdateWrapper.set("status", true)
|
||||
.eq("status", false)
|
||||
.eq("username", username);
|
||||
if (remoteJudge.equals("GYM")) {
|
||||
remoteJudge = "CF";
|
||||
}
|
||||
remoteJudgeAccountUpdateWrapper.eq("oj", remoteJudge);
|
||||
|
||||
boolean isOk = remoteJudgeAccountService.update(remoteJudgeAccountUpdateWrapper);
|
||||
|
||||
if (!isOk) { // 重试8次
|
||||
tryAgainUpdateAccount(remoteJudgeAccountUpdateWrapper, remoteJudge, username);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAgainUpdateAccount(UpdateWrapper<RemoteJudgeAccount> updateWrapper, String remoteJudge, String username) {
|
||||
boolean retryable;
|
||||
int attemptNumber = 0;
|
||||
do {
|
||||
boolean success = remoteJudgeAccountService.update(updateWrapper);
|
||||
if (success) {
|
||||
return;
|
||||
} else {
|
||||
attemptNumber++;
|
||||
retryable = attemptNumber < 8;
|
||||
if (attemptNumber == 8) {
|
||||
log.error("远程判题:修正账号为可用状态失败----------->{}", "oj:" + remoteJudge + ",username:" + username);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} while (retryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeServerSubmitCFStatus(String ip, Integer port) {
|
||||
|
||||
if (StringUtils.isEmpty(ip) || port == null) {
|
||||
return;
|
||||
}
|
||||
UpdateWrapper<JudgeServer> judgeServerUpdateWrapper = new UpdateWrapper<>();
|
||||
judgeServerUpdateWrapper.set("cf_submittable", true)
|
||||
.eq("ip", ip)
|
||||
.eq("is_remote", true)
|
||||
.eq("port", port);
|
||||
boolean isOk = judgeServerService.update(judgeServerUpdateWrapper);
|
||||
|
||||
if (!isOk) { // 重试8次
|
||||
tryAgainUpdateServer(judgeServerUpdateWrapper, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAgainUpdateServer(UpdateWrapper<JudgeServer> updateWrapper, String ip, Integer port) {
|
||||
boolean retryable;
|
||||
int attemptNumber = 0;
|
||||
do {
|
||||
boolean success = judgeServerService.update(updateWrapper);
|
||||
if (success) {
|
||||
return;
|
||||
} else {
|
||||
attemptNumber++;
|
||||
retryable = attemptNumber < 8;
|
||||
if (attemptNumber == 8) {
|
||||
log.error("远程判题:修正判题机对CF可提交状态为可用的状态失败----------->{}", "ip:" + ip + ",port:" + port);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} while (retryable);
|
||||
}
|
||||
}
|
|
@ -93,6 +93,12 @@ public class Contest implements Serializable {
|
|||
@ApiModelProperty(value = "排行榜显示(username、nickname、realname)")
|
||||
private String rankShowName;
|
||||
|
||||
@ApiModelProperty(value = "打星用户列表 {\"star_account\":['a','b']}")
|
||||
private String starAccount;
|
||||
|
||||
@ApiModelProperty(value = "是否开放比赛榜单")
|
||||
private Boolean openRank;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date gmtCreate;
|
||||
|
||||
|
|
|
@ -274,7 +274,7 @@ a:hover {
|
|||
font-weight: normal;
|
||||
color: dimgrey;
|
||||
}
|
||||
.female-flag {
|
||||
.contest-rank-flag {
|
||||
margin-right: 20px !important;
|
||||
background-color: rgb(255, 193, 10);
|
||||
border-radius: 4px;
|
||||
|
@ -285,6 +285,9 @@ a:hover {
|
|||
.bg-female {
|
||||
background-color: rgb(255, 153, 203);
|
||||
}
|
||||
.bg-star {
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
|
||||
.oi-100 {
|
||||
background-color: #19be6b;
|
||||
|
|
|
@ -411,6 +411,18 @@ const ojApi = {
|
|||
params: {cid}
|
||||
})
|
||||
},
|
||||
// 获取赛外榜单比赛的信息
|
||||
getScoreBoardContestInfo(cid){
|
||||
return ajax('/api/get-contest-outsize-info','get',{
|
||||
params: {cid}
|
||||
})
|
||||
},
|
||||
// 提供比赛外榜排名数据
|
||||
getContestOutsideScoreboard(params){
|
||||
return ajax('/api/get-contest-outside-scoreboard','get',{
|
||||
params: params
|
||||
})
|
||||
},
|
||||
// 注册私有比赛权限
|
||||
registerContest(cid,password){
|
||||
return ajax('/api/register-contest','post',{
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
layout="prev, pager, next"
|
||||
@current-change="getPublicProblem"
|
||||
:page-size="limit"
|
||||
:current-page="page"
|
||||
:current-page.sync="page"
|
||||
:total="total"
|
||||
>
|
||||
</el-pagination>
|
||||
|
|
|
@ -319,13 +319,16 @@ export const m = {
|
|||
Contest_Duration_Check:'The duration of the contest cannot be less than or equal to zero!',
|
||||
Contets_Time_Check:'The start time should be earlier than the end time!',
|
||||
Print_Func:'Print Function',
|
||||
Open:'Open',
|
||||
Not_Support_Print:'Not Support Print',
|
||||
Support_Offline_Print:'Support Offline Print',
|
||||
Add_Star_User_Error:'Please do not add existing star user repeatedly!',
|
||||
Star_User_UserName:'Star User (Please use login username)',
|
||||
Rank_Show_Name:'The Name Showed in The Rank',
|
||||
Show_Username:'Username',
|
||||
Show_Nickname:'Nickname',
|
||||
Show_Realname:'Real name',
|
||||
Account_Limit:'Account Limit',
|
||||
Account_Limit:'Account Limit (Login Username)',
|
||||
The_allowed_account_will_be:'The allowed username will be ',
|
||||
|
||||
// /views/admin/discussion/Discussion.vue
|
||||
|
|
|
@ -319,11 +319,14 @@ export const m = {
|
|||
Print_Func:'打印功能',
|
||||
Not_Support_Print:'不支持打印',
|
||||
Support_Offline_Print:'支持线下打印',
|
||||
Open:'开启',
|
||||
Add_Star_User_Error:'请不要重复添加已有打星用户!',
|
||||
Star_User_UserName:'打星用户(请使用登录用户名)',
|
||||
Rank_Show_Name:'榜单显示用户名称',
|
||||
Show_Username:'用户名',
|
||||
Show_Nickname:'昵称',
|
||||
Show_Realname:'真实姓名',
|
||||
Account_Limit:'账号限制',
|
||||
Account_Limit:'账号限制(登录用户名)',
|
||||
The_allowed_account_will_be:'允许参加比赛的用户名是:',
|
||||
|
||||
// /views/admin/discussion/Discussion.vue
|
||||
|
|
|
@ -306,6 +306,7 @@ export const m = {
|
|||
Public_Tips:'Public - Any one can see and submit.',
|
||||
Private_Tips:'Private - Only users knowing contest password can see and submit.',
|
||||
Protected_Tips:'Protected - Any one can see, but only users knowing contest password can submit.',
|
||||
Contest_Outside_ScoreBoard:'OutSide Contest ScoreBoard',
|
||||
|
||||
// /views/oj/contest/ContestDetail.vue
|
||||
StartAt: 'StartAt',
|
||||
|
@ -334,9 +335,11 @@ export const m = {
|
|||
RealName: 'RealName',
|
||||
Force_Update: 'Force Update',
|
||||
Download_as_CSV: 'Download as CSV',
|
||||
TotalTime: 'TotalTime',
|
||||
TotalTime: 'Time',
|
||||
Top_10_Teams: 'Top 10 Teams',
|
||||
save_as_image: 'save as image',
|
||||
Contest_Rank_Seq:'Rank',
|
||||
Star_User:'Star User',
|
||||
|
||||
// /views/oj/contest/children/ACMInfo.vue
|
||||
AC_Time: 'AC Time',
|
||||
|
|
|
@ -309,6 +309,7 @@ export const m = {
|
|||
Public_Tips:'公开赛 - 每个用户都可查看与提交',
|
||||
Private_Tips:'私有赛 - 用户需要密码才可查看与提交',
|
||||
Protected_Tips:'保护赛 - 每个用户都可查看,但是提交需要密码',
|
||||
Contest_Outside_ScoreBoard:'赛外榜单',
|
||||
|
||||
// /views/oj/contest/ContestDetail.vue
|
||||
StartAt: '开始时间',
|
||||
|
@ -340,6 +341,8 @@ export const m = {
|
|||
TotalTime: '总时间',
|
||||
Top_10_Teams: 'Top 10 Teams',
|
||||
save_as_image: '保存成图片',
|
||||
Contest_Rank_Seq:'排名',
|
||||
Star_User:'打星用户',
|
||||
|
||||
// /views/oj/contest/children/ACMInfo.vue
|
||||
AC_Time: 'AC 时间',
|
||||
|
|
|
@ -7,6 +7,8 @@ import Logout from "@/views/oj/user/Logout.vue"
|
|||
import SubmissionList from "@/views/oj/status/SubmissionList.vue"
|
||||
import SubmissionDetails from "@/views/oj/status/SubmissionDetails.vue"
|
||||
import ContestList from "@/views/oj/contest/ContestList.vue"
|
||||
import ACMScoreBoard from "@/views/oj/contest/outside/ACMScoreBoard.vue"
|
||||
import OIScoreBoard from "@/views/oj/contest/outside/OIScoreBoard.vue"
|
||||
import Problem from "@/views/oj/problem/Problem.vue"
|
||||
import ACMRank from "@/views/oj/rank/ACMRank.vue"
|
||||
import OIRank from "@/views/oj/rank/OIRank.vue"
|
||||
|
@ -74,6 +76,18 @@ const ojRoutes = [
|
|||
component: ContestList,
|
||||
meta: { title: 'Contest' }
|
||||
},
|
||||
{
|
||||
path: '/contest/acm-scoreboard/:contestID',
|
||||
name: 'ACMScoreBoard',
|
||||
component: ACMScoreBoard,
|
||||
meta: { title: 'ACM Contest ScoreBoard' }
|
||||
},
|
||||
{
|
||||
path: '/contest/oi-scoreboard/:contestID',
|
||||
name: 'OIScoreBoard',
|
||||
component: OIScoreBoard,
|
||||
meta: { title: 'OI Contest ScoreBoard' }
|
||||
},
|
||||
{
|
||||
name: 'ContestDetails',
|
||||
path: '/contest/:contestID/',
|
||||
|
|
|
@ -6,18 +6,19 @@ const state = {
|
|||
now: moment(),
|
||||
intoAccess: false, // 比赛进入权限
|
||||
submitAccess:false, // 保护比赛的提交权限
|
||||
forceUpdate: false,
|
||||
forceUpdate: false, // 强制实时榜单
|
||||
removeStar: false, // 榜单去除打星队伍
|
||||
contest: {
|
||||
auth: CONTEST_TYPE.PUBLIC,
|
||||
openPrint: false,
|
||||
rankShowName:'username'
|
||||
rankShowName:'username',
|
||||
},
|
||||
contestProblems: [],
|
||||
itemVisible: {
|
||||
table: true,
|
||||
chart: true,
|
||||
},
|
||||
disPlayIdMapColor:{} // 展示id对应的气球颜色
|
||||
disPlayIdMapColor:{}, // 展示id对应的气球颜色
|
||||
}
|
||||
|
||||
const getters = {
|
||||
|
@ -154,6 +155,9 @@ const mutations = {
|
|||
changeRankForceUpdate (state, payload) {
|
||||
state.forceUpdate = payload.value
|
||||
},
|
||||
changeRankRemoveStar(state, payload){
|
||||
state.removeStar = payload.value
|
||||
},
|
||||
changeContestProblems(state, payload) {
|
||||
state.contestProblems = payload.contestProblems;
|
||||
let tmp={};
|
||||
|
@ -182,6 +186,7 @@ const mutations = {
|
|||
realName: false
|
||||
}
|
||||
state.forceUpdate = false
|
||||
state.removeStar = false
|
||||
},
|
||||
now(state, payload) {
|
||||
state.now = payload.now
|
||||
|
@ -209,6 +214,21 @@ const actions = {
|
|||
})
|
||||
})
|
||||
},
|
||||
getScoreBoardContestInfo ({commit, rootState, dispatch}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.getScoreBoardContestInfo(rootState.route.params.contestID).then((res) => {
|
||||
resolve(res)
|
||||
let contest = res.data.data.contest;
|
||||
let problemList = res.data.data.problemList;
|
||||
commit('changeContest', {contest: contest})
|
||||
commit('changeContestProblems', {contestProblems: problemList})
|
||||
commit('now', {now: moment(contest.now)})
|
||||
}, err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getContestProblems ({commit, rootState}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.getContestProblemList(rootState.route.params.contestID).then(res => {
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item
|
||||
:label="$t('m.Auto_Real_Rank')"
|
||||
required
|
||||
|
@ -130,7 +130,18 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item :label="$t('m.Contest_Outside_ScoreBoard')" required>
|
||||
<el-switch
|
||||
v-model="contest.openRank"
|
||||
:active-text="$t('m.Open')"
|
||||
:inactive-text="$t('m.Close')"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item :label="$t('m.Print_Func')" required>
|
||||
<el-switch
|
||||
v-model="contest.openPrint"
|
||||
|
@ -157,6 +168,45 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-form-item :label="$t('m.Star_User_UserName')" required>
|
||||
<el-tag
|
||||
v-for="username in contest.starAccount"
|
||||
closable
|
||||
:close-transition="false"
|
||||
:key="username"
|
||||
type="warning"
|
||||
size="medium"
|
||||
@close="removeStarUser(username)"
|
||||
style="margin-right: 7px;margin-top:4px"
|
||||
>{{ username }}</el-tag
|
||||
>
|
||||
<el-input
|
||||
v-if="inputVisible"
|
||||
size="medium"
|
||||
class="input-new-star-user"
|
||||
v-model="starUserInput"
|
||||
:trigger-on-focus="true"
|
||||
@keyup.enter.native="addStarUser"
|
||||
@blur="addStarUser"
|
||||
>
|
||||
</el-input>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Add')"
|
||||
placement="top"
|
||||
v-else
|
||||
>
|
||||
<el-button
|
||||
class="button-new-tag"
|
||||
size="small"
|
||||
@click="inputVisible = true"
|
||||
icon="el-icon-plus"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item :label="$t('m.Contest_Auth')" required>
|
||||
<el-select v-model="contest.auth">
|
||||
|
@ -298,6 +348,7 @@ export default {
|
|||
rankShowName: 'username',
|
||||
openAccountLimit: false,
|
||||
accountLimitRule: '',
|
||||
starAccount: [],
|
||||
},
|
||||
formRule: {
|
||||
prefix: '',
|
||||
|
@ -306,6 +357,8 @@ export default {
|
|||
number_to: 10,
|
||||
extra_account: '',
|
||||
},
|
||||
starUserInput: '',
|
||||
inputVisible: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -500,6 +553,29 @@ export default {
|
|||
extra_account: tmp[5],
|
||||
};
|
||||
},
|
||||
|
||||
addStarUser() {
|
||||
if (this.starUserInput) {
|
||||
for (var i = 0; i < this.contest.starAccount.length; i++) {
|
||||
if (this.contest.starAccount[i] == this.starUserInput) {
|
||||
myMessage.warning(this.$i18n.t('m.Add_Star_User_Error'));
|
||||
this.starUserInput = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.contest.starAccount.push(this.starUserInput);
|
||||
this.inputVisible = false;
|
||||
this.starUserInput = '';
|
||||
}
|
||||
},
|
||||
|
||||
// 根据UserName 从打星用户列表中移除
|
||||
removeStarUser(username) {
|
||||
this.contest.starAccount.splice(
|
||||
this.contest.starAccount.map((item) => item.name).indexOf(username),
|
||||
1
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -512,4 +588,7 @@ export default {
|
|||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.input-new-star-user {
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<span class="panel-title">{{ contest.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-row style="margin-top: 10px;">
|
||||
<el-col :span="12" class="text-align:left">
|
||||
<el-tooltip
|
||||
v-if="contest.auth != null && contest.auth != undefined"
|
||||
|
@ -91,7 +91,7 @@
|
|||
<el-button
|
||||
type="primary"
|
||||
@click="checkPassword"
|
||||
style="float:right;margin:5px"
|
||||
style="float:right;"
|
||||
>{{ $t('m.Enter') }}</el-button
|
||||
>
|
||||
</el-form>
|
||||
|
@ -444,6 +444,11 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.panel-title {
|
||||
font-variant: small-caps;
|
||||
font-size: 1.5rem !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.contest-time .left {
|
||||
text-align: left;
|
||||
|
|
|
@ -176,6 +176,23 @@
|
|||
</el-tag>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
<li v-if="contest.openRank">
|
||||
<el-tooltip
|
||||
:content="$t('m.Contest_Outside_ScoreBoard')"
|
||||
placement="top"
|
||||
effect="dark"
|
||||
>
|
||||
<el-button
|
||||
circle
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="el-icon-data-analysis"
|
||||
@click="
|
||||
toContestOutsideScoreBoard(contest.id, contest.type)
|
||||
"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</el-col>
|
||||
<el-col
|
||||
|
@ -312,6 +329,19 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
toContestOutsideScoreBoard(cid, type) {
|
||||
if (type == 0) {
|
||||
this.$router.push({
|
||||
name: 'ACMScoreBoard',
|
||||
params: { contestID: cid },
|
||||
});
|
||||
} else if (type == 1) {
|
||||
this.$router.push({
|
||||
name: 'OIScoreBoard',
|
||||
params: { contestID: cid },
|
||||
});
|
||||
}
|
||||
},
|
||||
getDuration(startTime, endTime) {
|
||||
return time.formatSpecificDuration(startTime, endTime);
|
||||
},
|
||||
|
|
|
@ -14,6 +14,13 @@
|
|||
<span>{{ $t('m.Table') }}</span>
|
||||
<el-switch v-model="showTable"></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Star_User') }}</span>
|
||||
<el-switch
|
||||
v-model="showStarUser"
|
||||
@change="getContestRankData(page)"
|
||||
></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Auto_Refresh') }}(10s)</span>
|
||||
<el-switch
|
||||
|
@ -25,7 +32,10 @@
|
|||
<template v-if="isContestAdmin">
|
||||
<p>
|
||||
<span>{{ $t('m.Force_Update') }}</span>
|
||||
<el-switch v-model="forceUpdate"></el-switch>
|
||||
<el-switch
|
||||
v-model="forceUpdate"
|
||||
@change="getContestRankData(page)"
|
||||
></el-switch>
|
||||
</p>
|
||||
</template>
|
||||
<template>
|
||||
|
@ -49,14 +59,17 @@
|
|||
align="center"
|
||||
:data="dataRank"
|
||||
:cell-class-name="cellClassName"
|
||||
:seq-config="{ startIndex: (this.page - 1) * this.limit }"
|
||||
>
|
||||
<vxe-table-column
|
||||
field="id"
|
||||
type="seq"
|
||||
field="rank"
|
||||
width="50"
|
||||
fixed="left"
|
||||
></vxe-table-column>
|
||||
:title="$t('m.Contest_Rank_Seq')"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row.rank == -1 ? '*' : row.rank }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
fixed="left"
|
||||
|
@ -79,9 +92,13 @@
|
|||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
|
@ -111,9 +128,13 @@
|
|||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
|
@ -387,7 +408,7 @@ export default {
|
|||
}
|
||||
|
||||
if (
|
||||
column.property !== 'id' &&
|
||||
column.property !== 'rank' &&
|
||||
column.property !== 'rating' &&
|
||||
column.property !== 'totalTime' &&
|
||||
column.property !== 'username' &&
|
||||
|
@ -432,6 +453,9 @@ export default {
|
|||
}
|
||||
});
|
||||
dataRank[i].cellClassName = cellClass;
|
||||
if (dataRank[i].rank == -1) {
|
||||
dataRank[i].userCellClassName = 'bg-star';
|
||||
}
|
||||
if (dataRank[i].gender == 'female') {
|
||||
dataRank[i].userCellClassName = 'bg-female';
|
||||
}
|
||||
|
|
|
@ -14,6 +14,13 @@
|
|||
<span>{{ $t('m.Table') }}</span>
|
||||
<el-switch v-model="showTable"></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Star_User') }}</span>
|
||||
<el-switch
|
||||
v-model="showStarUser"
|
||||
@change="getContestRankData(page)"
|
||||
></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Auto_Refresh') }}(10s)</span>
|
||||
<el-switch
|
||||
|
@ -25,7 +32,10 @@
|
|||
<template v-if="isContestAdmin">
|
||||
<p>
|
||||
<span>{{ $t('m.Force_Update') }}</span>
|
||||
<el-switch v-model="forceUpdate"></el-switch>
|
||||
<el-switch
|
||||
v-model="forceUpdate"
|
||||
@change="getContestRankData(page)"
|
||||
></el-switch>
|
||||
</p>
|
||||
</template>
|
||||
<template>
|
||||
|
@ -50,14 +60,18 @@
|
|||
ref="OIContestRank"
|
||||
:data="dataRank"
|
||||
:cell-class-name="cellClassName"
|
||||
:seq-config="{ startIndex: (this.page - 1) * this.limit }"
|
||||
>
|
||||
<vxe-table-column
|
||||
field="id"
|
||||
type="seq"
|
||||
min-width="50"
|
||||
field="rank"
|
||||
width="50"
|
||||
fixed="left"
|
||||
></vxe-table-column>
|
||||
:title="$t('m.Contest_Rank_Seq')"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row.rank == -1 ? '*' : row.rank }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
fixed="left"
|
||||
|
@ -80,9 +94,13 @@
|
|||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
|
@ -93,6 +111,7 @@
|
|||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
fixed="left"
|
||||
v-else
|
||||
min-width="300"
|
||||
:title="$t('m.User')"
|
||||
|
@ -112,9 +131,13 @@
|
|||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
|
@ -123,6 +146,7 @@
|
|||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column
|
||||
field="realname"
|
||||
min-width="96"
|
||||
|
@ -309,7 +333,7 @@ export default {
|
|||
}
|
||||
|
||||
if (
|
||||
column.property !== 'id' &&
|
||||
column.property !== 'rank' &&
|
||||
column.property !== 'totalScore' &&
|
||||
column.property !== 'username' &&
|
||||
column.property !== 'realname'
|
||||
|
@ -372,6 +396,9 @@ export default {
|
|||
}
|
||||
});
|
||||
dataRank[i].cellClassName = cellClass;
|
||||
if (dataRank[i].rank == -1) {
|
||||
dataRank[i].userCellClassName = 'bg-star';
|
||||
}
|
||||
if (dataRank[i].gender == 'female') {
|
||||
dataRank[i].userCellClassName = 'bg-female';
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ export default {
|
|||
currentPage:page,
|
||||
limit: this.limit,
|
||||
cid: this.$route.params.contestID,
|
||||
forceRefresh: this.forceUpdate ? true: false
|
||||
forceRefresh: this.forceUpdate ? true: false,
|
||||
removeStar: !this.showStarUser
|
||||
}
|
||||
api.getContestRank(params).then(res => {
|
||||
if (this.showChart && !refresh) {
|
||||
|
@ -28,8 +29,7 @@ export default {
|
|||
handleAutoRefresh (status) {
|
||||
if (status == true) {
|
||||
this.refreshFunc = setInterval(() => {
|
||||
this.page = 1
|
||||
this.getContestRankData(1, true)
|
||||
this.getContestRankData(this.page, true)
|
||||
}, 10000)
|
||||
} else {
|
||||
clearInterval(this.refreshFunc)
|
||||
|
@ -50,6 +50,14 @@ export default {
|
|||
this.$store.commit('changeContestItemVisible', {chart: value})
|
||||
}
|
||||
},
|
||||
showStarUser:{
|
||||
get () {
|
||||
return !this.$store.state.contest.removeStar
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('changeRankRemoveStar', {value: !value})
|
||||
}
|
||||
},
|
||||
showTable: {
|
||||
get () {
|
||||
return this.$store.state.contest.itemVisible.table
|
||||
|
@ -67,7 +75,7 @@ export default {
|
|||
}
|
||||
},
|
||||
refreshDisabled () {
|
||||
return this.contest.status === CONTEST_STATUS.ENDED
|
||||
return this.contest.status == CONTEST_STATUS.ENDED
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
|
|
@ -0,0 +1,478 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card shadow>
|
||||
<div class="contest-title">
|
||||
<div slot="header">
|
||||
<span class="panel-title">{{ contest.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-row style="margin-top: 10px;">
|
||||
<el-col :span="12" class="text-align:left">
|
||||
<el-tooltip
|
||||
v-if="contest.auth != null && contest.auth != undefined"
|
||||
:content="$t('m.' + CONTEST_TYPE_REVERSE[contest.auth]['tips'])"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag
|
||||
:type.sync="CONTEST_TYPE_REVERSE[contest.auth]['color']"
|
||||
effect="plain"
|
||||
>
|
||||
{{ $t('m.' + CONTEST_TYPE_REVERSE[contest.auth]['name']) }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align:right">
|
||||
<el-button size="small" plain v-if="contest != null">
|
||||
{{ contest.type | parseContestType }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="contest-time">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" class="left">
|
||||
<p>
|
||||
<i class="fa fa-hourglass-start" aria-hidden="true"></i>
|
||||
{{ $t('m.StartAt') }}:{{ contest.startTime | localtime }}
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" class="right">
|
||||
<p>
|
||||
<i class="fa fa-hourglass-end" aria-hidden="true"></i>
|
||||
{{ $t('m.EndAt') }}:{{ contest.endTime | localtime }}
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="slider">
|
||||
<el-slider
|
||||
v-model="progressValue"
|
||||
:format-tooltip="formatTooltip"
|
||||
:step="timeStep"
|
||||
></el-slider>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="24" style="text-align:center">
|
||||
<el-tag effect="dark" size="medium" :style="countdownColor">
|
||||
<i class="fa fa-circle" aria-hidden="true"></i>
|
||||
{{ countdown }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card shadow style="margin-top:15px;">
|
||||
<div class="contest-rank-switch">
|
||||
<span style="float:right;">
|
||||
<span>{{ $t('m.Auto_Refresh') }}(10s)</span>
|
||||
<el-switch
|
||||
:disabled="contestEnded"
|
||||
@change="handleAutoRefresh"
|
||||
v-model="autoRefresh"
|
||||
></el-switch>
|
||||
</span>
|
||||
<span style="float:right;" v-if="isContestAdmin">
|
||||
<span>{{ $t('m.Force_Update') }}</span>
|
||||
<el-switch
|
||||
v-model="forceUpdate"
|
||||
@change="getContestOutsideScoreboard"
|
||||
></el-switch>
|
||||
</span>
|
||||
<span style="float:right;">
|
||||
<span>{{ $t('m.Star_User') }}</span>
|
||||
<el-switch
|
||||
v-model="showStarUser"
|
||||
@change="getContestOutsideScoreboard"
|
||||
></el-switch>
|
||||
</span>
|
||||
</div>
|
||||
<vxe-table
|
||||
round
|
||||
border
|
||||
auto-resize
|
||||
size="small"
|
||||
align="center"
|
||||
:data="dataRank"
|
||||
:cell-class-name="cellClassName"
|
||||
>
|
||||
<vxe-table-column
|
||||
field="rank"
|
||||
width="50"
|
||||
fixed="left"
|
||||
:title="$t('m.Contest_Rank_Seq')"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row.rank == -1 ? '*' : row.rank }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
fixed="left"
|
||||
v-if="!isMobileView"
|
||||
min-width="300"
|
||||
:title="$t('m.User')"
|
||||
header-align="center"
|
||||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[contest.rankShowName]"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[contest.rankShowName]"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
v-else
|
||||
min-width="300"
|
||||
:title="$t('m.User')"
|
||||
header-align="center"
|
||||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[contest.rankShowName]"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[contest.rankShowName]"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="rating" :title="$t('m.AC')" min-width="60">
|
||||
<template v-slot="{ row }">
|
||||
<span
|
||||
style="color:rgb(87, 163, 243);font-weight: 600;font-size: 14px;"
|
||||
>{{ row.ac }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="totalTime"
|
||||
:title="$t('m.TotalTime')"
|
||||
min-width="60"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
{{ parseTimeToSpecific(row.totalTime) }}
|
||||
</div>
|
||||
<span>{{ parseInt(row.totalTime / 60) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="74"
|
||||
v-for="problem in contestProblems"
|
||||
:key="problem.displayId"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<span style="vertical-align: top;" v-if="problem.color">
|
||||
<svg
|
||||
t="1633685184463"
|
||||
class="icon"
|
||||
viewBox="0 0 1088 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5840"
|
||||
width="25"
|
||||
height="25"
|
||||
>
|
||||
<path
|
||||
d="M575.872 849.408c-104.576 0-117.632-26.56-119.232-31.808-6.528-22.528 32.896-70.592 63.744-96.768l-1.728-2.624c137.6-42.688 243.648-290.112 243.648-433.472A284.544 284.544 0 0 0 478.016 0a284.544 284.544 0 0 0-284.288 284.736c0 150.4 116.352 415.104 263.744 438.336-25.152 29.568-50.368 70.784-39.104 108.928 12.608 43.136 62.72 63.232 157.632 63.232 7.872 0 11.52 9.408 4.352 19.52-21.248 29.248-77.888 63.424-167.68 63.424V1024c138.944 0 215.936-74.816 215.936-126.528a46.72 46.72 0 0 0-16.32-36.608 56.32 56.32 0 0 0-36.416-11.456zM297.152 297.472c0 44.032-38.144 25.344-38.144-38.656 0-108.032 85.248-195.712 190.592-195.712 62.592 0 81.216 39.232 38.08 39.232-105.152 0.064-190.528 87.04-190.528 195.136z"
|
||||
:fill="problem.color"
|
||||
p-id="5841"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
{{ problem.displayId + '. ' + problem.displayTitle }}
|
||||
</div>
|
||||
<span class="emphasis" style="color:#495060;"
|
||||
>{{ problem.displayId }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<span v-if="row.submissionInfo[problem.displayId]">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
{{ row.submissionInfo[problem.displayId].specificTime }}
|
||||
</div>
|
||||
<span
|
||||
v-if="row.submissionInfo[problem.displayId].isAC"
|
||||
class="submission-time"
|
||||
>{{ row.submissionInfo[problem.displayId].ACTime }}<br />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
|
||||
<span
|
||||
class="submission-error"
|
||||
v-if="
|
||||
row.submissionInfo[problem.displayId].tryNum == null &&
|
||||
row.submissionInfo[problem.displayId].errorNum != 0
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.submissionInfo[problem.displayId].errorNum > 1
|
||||
? row.submissionInfo[problem.displayId].errorNum + ' tries'
|
||||
: row.submissionInfo[problem.displayId].errorNum + ' try'
|
||||
}}
|
||||
</span>
|
||||
<span v-if="row.submissionInfo[problem.displayId].tryNum != null"
|
||||
><template
|
||||
v-if="row.submissionInfo[problem.displayId].errorNum > 0"
|
||||
>
|
||||
{{
|
||||
row.submissionInfo[problem.displayId].errorNum
|
||||
}}+</template
|
||||
>{{ row.submissionInfo[problem.displayId].tryNum
|
||||
}}{{
|
||||
row.submissionInfo[problem.displayId].errorNum +
|
||||
row.submissionInfo[problem.displayId].tryNum >
|
||||
1
|
||||
? ' tries'
|
||||
: ' try'
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Avatar from 'vue-avatar';
|
||||
import time from '@/common/time';
|
||||
import ScoreBoardMixin from './scoreBoardMixin';
|
||||
export default {
|
||||
name: 'ACMScoreBoard',
|
||||
mixins: [ScoreBoardMixin],
|
||||
components: {
|
||||
Avatar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
autoRefresh: false,
|
||||
removeStar: false,
|
||||
contestID: '',
|
||||
dataRank: [],
|
||||
timer: null,
|
||||
CONTEST_STATUS: {},
|
||||
CONTEST_STATUS_REVERSE: {},
|
||||
CONTEST_TYPE_REVERSE: {},
|
||||
RULE_TYPE: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
mounted() {
|
||||
this.getContestOutsideScoreboard();
|
||||
},
|
||||
methods: {
|
||||
getUserHomeByUsername(uid, username) {
|
||||
this.$router.push({
|
||||
name: 'UserHome',
|
||||
query: { username: username, uid: uid },
|
||||
});
|
||||
},
|
||||
cellClassName({ row, rowIndex, column, columnIndex }) {
|
||||
if (column.property === 'username' && row.userCellClassName) {
|
||||
return row.userCellClassName;
|
||||
}
|
||||
|
||||
if (
|
||||
column.property !== 'rank' &&
|
||||
column.property !== 'rating' &&
|
||||
column.property !== 'totalTime' &&
|
||||
column.property !== 'username'
|
||||
) {
|
||||
return row.cellClassName[
|
||||
[this.contestProblems[columnIndex - 4].displayId]
|
||||
];
|
||||
}
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
dataRank.forEach((rank, i) => {
|
||||
let info = rank.submissionInfo;
|
||||
let cellClass = {};
|
||||
Object.keys(info).forEach((problemID) => {
|
||||
dataRank[i][problemID] = info[problemID];
|
||||
if (dataRank[i][problemID].ACTime != null) {
|
||||
dataRank[i][problemID].errorNum += 1;
|
||||
dataRank[i][problemID].specificTime = this.parseTimeToSpecific(
|
||||
dataRank[i][problemID].ACTime
|
||||
);
|
||||
dataRank[i][problemID].ACTime = parseInt(
|
||||
dataRank[i][problemID].ACTime / 60
|
||||
);
|
||||
}
|
||||
let status = info[problemID];
|
||||
if (status.isFirstAC) {
|
||||
cellClass[problemID] = 'first-ac';
|
||||
} else if (status.isAC) {
|
||||
cellClass[problemID] = 'ac';
|
||||
} else if (status.tryNum != null && status.tryNum > 0) {
|
||||
cellClass[problemID] = 'try';
|
||||
} else if (status.errorNum != 0) {
|
||||
cellClass[problemID] = 'wa';
|
||||
}
|
||||
});
|
||||
dataRank[i].cellClassName = cellClass;
|
||||
if (dataRank[i].rank == -1) {
|
||||
dataRank[i].userCellClassName = 'bg-star';
|
||||
}
|
||||
if (dataRank[i].gender == 'female') {
|
||||
dataRank[i].userCellClassName = 'bg-female';
|
||||
}
|
||||
});
|
||||
this.dataRank = dataRank;
|
||||
},
|
||||
parseTimeToSpecific(totalTime) {
|
||||
return time.secondFormat(totalTime);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isMobileView() {
|
||||
return window.screen.width < 768;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.contest-rank-switch {
|
||||
margin-bottom: 30px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.contest-rank-switch span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.contest-title {
|
||||
text-align: center;
|
||||
}
|
||||
.panel-title {
|
||||
font-variant: small-caps;
|
||||
font-size: 1.5rem !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
.contest-time {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.contest-time .left {
|
||||
text-align: left;
|
||||
}
|
||||
.contest-time .right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.contest-time .left,
|
||||
.contest-time .right {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-slider__button {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
background-color: #409eff !important;
|
||||
}
|
||||
/deep/.el-slider__button-wrapper {
|
||||
z-index: 500;
|
||||
}
|
||||
/deep/.el-slider__bar {
|
||||
height: 10px !important;
|
||||
background-color: #09be24 !important;
|
||||
}
|
||||
|
||||
.el-tag--dark {
|
||||
border-color: #fff;
|
||||
}
|
||||
.el-tag {
|
||||
color: rgb(25, 190, 107);
|
||||
background: #fff;
|
||||
border: 1px solid #e9eaec;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/deep/.el-card__body {
|
||||
padding: 15px !important;
|
||||
padding-top: 20px !important;
|
||||
}
|
||||
|
||||
.vxe-cell p,
|
||||
.vxe-cell span {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/deep/.vxe-table .vxe-body--column {
|
||||
line-height: 20px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
/deep/.vxe-body--column {
|
||||
min-width: 0;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/deep/.vxe-table .vxe-cell {
|
||||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
.submission-time {
|
||||
font-size: 15.6px;
|
||||
font-family: Roboto, sans-serif;
|
||||
}
|
||||
.submission-error {
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,401 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card shadow>
|
||||
<div class="contest-title">
|
||||
<div slot="header">
|
||||
<span class="panel-title">{{ contest.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-row style="margin-top: 10px;">
|
||||
<el-col :span="12" class="text-align:left">
|
||||
<el-tooltip
|
||||
v-if="contest.auth != null && contest.auth != undefined"
|
||||
:content="$t('m.' + CONTEST_TYPE_REVERSE[contest.auth]['tips'])"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag
|
||||
:type.sync="CONTEST_TYPE_REVERSE[contest.auth]['color']"
|
||||
effect="plain"
|
||||
>
|
||||
{{ $t('m.' + CONTEST_TYPE_REVERSE[contest.auth]['name']) }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align:right">
|
||||
<el-button size="small" plain v-if="contest != null">
|
||||
{{ contest.type | parseContestType }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="contest-time">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" class="left">
|
||||
<p>
|
||||
<i class="fa fa-hourglass-start" aria-hidden="true"></i>
|
||||
{{ $t('m.StartAt') }}:{{ contest.startTime | localtime }}
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" class="right">
|
||||
<p>
|
||||
<i class="fa fa-hourglass-end" aria-hidden="true"></i>
|
||||
{{ $t('m.EndAt') }}:{{ contest.endTime | localtime }}
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="slider">
|
||||
<el-slider
|
||||
v-model="progressValue"
|
||||
:format-tooltip="formatTooltip"
|
||||
:step="timeStep"
|
||||
></el-slider>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="24" style="text-align:center">
|
||||
<el-tag effect="dark" size="medium" :style="countdownColor">
|
||||
<i class="fa fa-circle" aria-hidden="true"></i>
|
||||
{{ countdown }}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card shadow style="margin-top:15px;">
|
||||
<div class="contest-rank-switch">
|
||||
<span style="float:right;">
|
||||
<span>{{ $t('m.Auto_Refresh') }}(10s)</span>
|
||||
<el-switch
|
||||
:disabled="contestEnded"
|
||||
@change="handleAutoRefresh"
|
||||
v-model="autoRefresh"
|
||||
></el-switch>
|
||||
</span>
|
||||
<span style="float:right;" v-if="isContestAdmin">
|
||||
<span>{{ $t('m.Force_Update') }}</span>
|
||||
<el-switch
|
||||
v-model="forceUpdate"
|
||||
@change="getContestOutsideScoreboard"
|
||||
></el-switch>
|
||||
</span>
|
||||
<span style="float:right;">
|
||||
<span>{{ $t('m.Star_User') }}</span>
|
||||
<el-switch
|
||||
v-model="showStarUser"
|
||||
@change="getContestOutsideScoreboard"
|
||||
></el-switch>
|
||||
</span>
|
||||
</div>
|
||||
<vxe-table
|
||||
round
|
||||
border
|
||||
auto-resize
|
||||
size="small"
|
||||
align="center"
|
||||
ref="OIContestRank"
|
||||
:data="dataRank"
|
||||
:cell-class-name="cellClassName"
|
||||
>
|
||||
<vxe-table-column
|
||||
field="rank"
|
||||
width="50"
|
||||
fixed="left"
|
||||
:title="$t('m.Contest_Rank_Seq')"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row.rank == -1 ? '*' : row.rank }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
fixed="left"
|
||||
v-if="!isMobileView"
|
||||
min-width="300"
|
||||
:title="$t('m.User')"
|
||||
header-align="center"
|
||||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[contest.rankShowName]"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[contest.rankShowName]"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
v-else
|
||||
min-width="300"
|
||||
:title="$t('m.User')"
|
||||
header-align="center"
|
||||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[contest.rankShowName]"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[contest.rankShowName]"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.rank == -1"
|
||||
>Star</span
|
||||
>
|
||||
<span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>
|
||||
{{ row[contest.rankShowName] }}</span
|
||||
>
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="totalScore"
|
||||
:title="$t('m.Total_Score')"
|
||||
min-width="100"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span
|
||||
><a style="color:rgb(87, 163, 243);">{{ row.totalScore }}</a>
|
||||
<br />
|
||||
<span class="problem-time">({{ row.totalTime }}ms)</span>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="120"
|
||||
v-for="problem in contestProblems"
|
||||
:key="problem.displayId"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<span style="vertical-align: top;" v-if="problem.color">
|
||||
<svg
|
||||
t="1633685184463"
|
||||
class="icon"
|
||||
viewBox="0 0 1088 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5840"
|
||||
width="25"
|
||||
height="25"
|
||||
>
|
||||
<path
|
||||
d="M575.872 849.408c-104.576 0-117.632-26.56-119.232-31.808-6.528-22.528 32.896-70.592 63.744-96.768l-1.728-2.624c137.6-42.688 243.648-290.112 243.648-433.472A284.544 284.544 0 0 0 478.016 0a284.544 284.544 0 0 0-284.288 284.736c0 150.4 116.352 415.104 263.744 438.336-25.152 29.568-50.368 70.784-39.104 108.928 12.608 43.136 62.72 63.232 157.632 63.232 7.872 0 11.52 9.408 4.352 19.52-21.248 29.248-77.888 63.424-167.68 63.424V1024c138.944 0 215.936-74.816 215.936-126.528a46.72 46.72 0 0 0-16.32-36.608 56.32 56.32 0 0 0-36.416-11.456zM297.152 297.472c0 44.032-38.144 25.344-38.144-38.656 0-108.032 85.248-195.712 190.592-195.712 62.592 0 81.216 39.232 38.08 39.232-105.152 0.064-190.528 87.04-190.528 195.136z"
|
||||
:fill="problem.color"
|
||||
p-id="5841"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content">
|
||||
{{ problem.displayId + '. ' + problem.displayTitle }}
|
||||
</div>
|
||||
<span class="emphasis" style="color:#495060;"
|
||||
>{{ problem.displayId }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<div v-if="row.submissionInfo[problem.displayId]">
|
||||
<span>{{ row.submissionInfo[problem.displayId] }}</span>
|
||||
<br />
|
||||
<span
|
||||
v-if="row.timeInfo && row.timeInfo[problem.displayId]"
|
||||
style="font-size:12px;"
|
||||
>({{ row.timeInfo[problem.displayId] }}ms)</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Avatar from 'vue-avatar';
|
||||
import ScoreBoardMixin from './scoreBoardMixin';
|
||||
export default {
|
||||
name: 'OIContestRank',
|
||||
components: {
|
||||
Avatar,
|
||||
},
|
||||
mixins: [ScoreBoardMixin],
|
||||
data() {
|
||||
return {
|
||||
contestID: '',
|
||||
dataRank: [],
|
||||
autoRefresh: false,
|
||||
timer: null,
|
||||
CONTEST_STATUS: {},
|
||||
CONTEST_STATUS_REVERSE: {},
|
||||
CONTEST_TYPE_REVERSE: {},
|
||||
RULE_TYPE: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
mounted() {
|
||||
this.getContestOutsideScoreboard();
|
||||
},
|
||||
computed: {
|
||||
isMobileView() {
|
||||
return window.screen.width < 768;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cellClassName({ row, rowIndex, column, columnIndex }) {
|
||||
if (column.property === 'username' && row.userCellClassName) {
|
||||
return row.userCellClassName;
|
||||
}
|
||||
|
||||
if (
|
||||
column.property !== 'rank' &&
|
||||
column.property !== 'totalScore' &&
|
||||
column.property !== 'username'
|
||||
) {
|
||||
return row.cellClassName[
|
||||
[this.contestProblems[columnIndex - 3].displayId]
|
||||
];
|
||||
}
|
||||
},
|
||||
getUserHomeByUsername(uid, username) {
|
||||
this.$router.push({
|
||||
name: 'UserHome',
|
||||
query: { username: username, uid: uid },
|
||||
});
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
dataRank.forEach((rank, i) => {
|
||||
let info = rank.submissionInfo;
|
||||
let cellClass = {};
|
||||
Object.keys(info).forEach((problemID) => {
|
||||
dataRank[i][problemID] = info[problemID];
|
||||
let score = info[problemID];
|
||||
if (score == 0) {
|
||||
cellClass[problemID] = 'oi-0';
|
||||
} else if (score > 0 && score < 100) {
|
||||
cellClass[problemID] = 'oi-between';
|
||||
} else if (score == 100) {
|
||||
cellClass[problemID] = 'oi-100';
|
||||
}
|
||||
});
|
||||
dataRank[i].cellClassName = cellClass;
|
||||
if (dataRank[i].rank == -1) {
|
||||
dataRank[i].userCellClassName = 'bg-star';
|
||||
}
|
||||
if (dataRank[i].gender == 'female') {
|
||||
dataRank[i].userCellClassName = 'bg-female';
|
||||
}
|
||||
});
|
||||
this.dataRank = dataRank;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.contest-title {
|
||||
text-align: center;
|
||||
}
|
||||
.panel-title {
|
||||
font-variant: small-caps;
|
||||
font-size: 1.5rem !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
.contest-time {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.contest-time .left {
|
||||
text-align: left;
|
||||
}
|
||||
.contest-time .right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.contest-time .left,
|
||||
.contest-time .right {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-slider__button {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
background-color: #409eff !important;
|
||||
}
|
||||
/deep/.el-slider__button-wrapper {
|
||||
z-index: 500;
|
||||
}
|
||||
/deep/.el-slider__bar {
|
||||
height: 10px !important;
|
||||
background-color: #09be24 !important;
|
||||
}
|
||||
|
||||
.el-tag--dark {
|
||||
border-color: #fff;
|
||||
}
|
||||
.el-tag {
|
||||
color: rgb(25, 190, 107);
|
||||
background: #fff;
|
||||
border: 1px solid #e9eaec;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/deep/.el-card__body {
|
||||
padding: 15px !important;
|
||||
padding-top: 20px !important;
|
||||
}
|
||||
|
||||
.vxe-cell p,
|
||||
.vxe-cell span {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/deep/.vxe-table .vxe-body--column {
|
||||
line-height: 20px !important;
|
||||
padding: 8px !important;
|
||||
}
|
||||
.problem-time {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/deep/.vxe-table .vxe-cell {
|
||||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
import api from '@/common/api'
|
||||
import time from '@/common/time';
|
||||
import { CONTEST_STATUS,CONTEST_STATUS_REVERSE,CONTEST_TYPE_REVERSE,RULE_TYPE} from '@/common/constants'
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import moment from 'moment';
|
||||
export default {
|
||||
methods: {
|
||||
init(){
|
||||
this.contestID = this.$route.params.contestID;
|
||||
this.CONTEST_TYPE_REVERSE = Object.assign({}, CONTEST_TYPE_REVERSE);
|
||||
this.CONTEST_STATUS = Object.assign({}, CONTEST_STATUS);
|
||||
this.CONTEST_STATUS_REVERSE = Object.assign({}, CONTEST_STATUS_REVERSE);
|
||||
this.RULE_TYPE = Object.assign({}, RULE_TYPE);
|
||||
|
||||
this.$store.dispatch('getScoreBoardContestInfo').then((res) => {
|
||||
if (!this.contestEnded) {
|
||||
this.autoRefresh = true;
|
||||
this.handleAutoRefresh(true);
|
||||
}
|
||||
this.changeDomTitle({ title: res.data.data.title });
|
||||
let data = res.data.data;
|
||||
let endTime = moment(data.endTime);
|
||||
// 如果当前时间还是在比赛结束前的时间,需要计算倒计时,同时开启获取比赛公告的定时器
|
||||
if (endTime.isAfter(moment(data.now))) {
|
||||
// 实时更新时间
|
||||
this.timer = setInterval(() => {
|
||||
this.$store.commit('nowAdd1s');
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
getContestOutsideScoreboard () {
|
||||
let params = {
|
||||
cid: this.$route.params.contestID,
|
||||
forceRefresh: this.forceUpdate ? true: false,
|
||||
removeStar: !this.showStarUser
|
||||
}
|
||||
api.getContestOutsideScoreboard(params).then(res => {
|
||||
this.applyToTable(res.data.data)
|
||||
},(err)=>{
|
||||
if(this.refreshFunc){
|
||||
this.autoRefresh = false;
|
||||
clearInterval(this.refreshFunc)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleAutoRefresh (status) {
|
||||
if (status == true) {
|
||||
this.refreshFunc = setInterval(() => {
|
||||
this.getContestOutsideScoreboard()
|
||||
}, 10000)
|
||||
} else {
|
||||
clearInterval(this.refreshFunc)
|
||||
}
|
||||
},
|
||||
...mapActions(['changeDomTitle']),
|
||||
formatTooltip(val) {
|
||||
if (this.contest.status == -1) {
|
||||
// 还未开始
|
||||
return '00:00:00';
|
||||
} else if (this.contest.status == 0) {
|
||||
return time.secondFormat(this.BeginToNowDuration); // 格式化时间
|
||||
} else {
|
||||
return time.secondFormat(this.contest.duration);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
contest: (state) => state.contest.contest,
|
||||
now: (state) => state.contest.now,
|
||||
contestProblems: state => state.contest.contestProblems
|
||||
}),
|
||||
...mapGetters([
|
||||
'countdown',
|
||||
'BeginToNowDuration',
|
||||
'isContestAdmin',
|
||||
]),
|
||||
forceUpdate: {
|
||||
get () {
|
||||
return this.$store.state.contest.forceUpdate
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('changeRankForceUpdate', {value: value})
|
||||
}
|
||||
},
|
||||
showStarUser:{
|
||||
get () {
|
||||
return !this.$store.state.contest.removeStar
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('changeRankRemoveStar', {value: !value})
|
||||
}
|
||||
},
|
||||
progressValue: {
|
||||
get: function() {
|
||||
return this.$store.getters.progressValue;
|
||||
},
|
||||
set: function() {},
|
||||
},
|
||||
timeStep() {
|
||||
// 时间段平分滑条长度
|
||||
return 100 / this.contest.duration;
|
||||
},
|
||||
countdownColor() {
|
||||
if (this.contest.status) {
|
||||
return 'color:' + CONTEST_STATUS_REVERSE[this.contest.status].color;
|
||||
}
|
||||
},
|
||||
contestEnded() {
|
||||
return this.contest.status == CONTEST_STATUS.ENDED;
|
||||
},
|
||||
},
|
||||
beforeDestroy () {
|
||||
clearInterval(this.refreshFunc)
|
||||
clearInterval(this.timer);
|
||||
this.$store.commit('clearContest');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue