add private training
This commit is contained in:
parent
c42c6ade08
commit
f13330ee11
|
@ -46,4 +46,5 @@ Hcode Online Judge (HOJ) : 基于前后端分离,分布式架构的在线测
|
|||
## 联系我们
|
||||
|
||||
QQ: [372347736](https://wpa.qq.com/msgrd?v=3&uin=372347736&site=qq&menu=yes)
|
||||
HOJ交流群: 598587305
|
||||
HOJ交流群: [598587305](https://qm.qq.com/cgi-bin/qm/qr?k=WWGBZ5gfDiBZOcpNvM8xnZTfUq7BT4Rs&jump_from=webapi)
|
||||
|
||||
|
|
|
@ -37,20 +37,20 @@
|
|||
# redis的配置
|
||||
REDIS_HOST=172.20.0.2
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=hoj123456 # 正式部署请修改
|
||||
REDIS_PASSWORD=hoj123456
|
||||
|
||||
# mysql的配置
|
||||
MYSQL_HOST=172.20.0.3
|
||||
# 如果判题服务是分布式,请提供当前mysql所在服务器的公网ip
|
||||
MYSQL_PUBLIC_HOST=172.20.0.3
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_ROOT_PASSWORD=hoj123456 # 正式部署请修改
|
||||
MYSQL_ROOT_PASSWORD=hoj123456
|
||||
|
||||
# nacos的配置
|
||||
NACOS_HOST=172.20.0.4
|
||||
NACOS_PORT=8848
|
||||
NACOS_USERNAME=root
|
||||
NACOS_PASSWORD=hoj123456 # 正式部署请修改
|
||||
NACOS_PASSWORD=hoj123456
|
||||
|
||||
# backend后端服务的配置
|
||||
BACKEND_HOST=172.20.0.5
|
||||
|
@ -82,9 +82,17 @@
|
|||
JUDGE_SERVER_IP=172.20.0.7
|
||||
JUDGE_SERVER_PORT=8088
|
||||
JUDGE_SERVER_NAME=judger-alone
|
||||
# -1表示可接收最大判题任务数为cpu核心数+1
|
||||
MAX_TASK_NUM=-1
|
||||
# 当前判题服务器是否开启远程虚拟判题功能
|
||||
REMOTE_JUDGE_OPEN=true
|
||||
# -1表示可接收最大远程判题任务数为cpu核心数*2+1
|
||||
REMOTE_JUDGE_MAX_TASK_NUM=-1
|
||||
# 默认沙盒并行判题程序数为cpu核心数
|
||||
PARALLEL_TASK=default
|
||||
|
||||
# docker network的配置
|
||||
SUBNET=172.20.0.0/16
|
||||
# docker network的配置
|
||||
SUBNET=172.20.0.0/16
|
||||
```
|
||||
|
||||
|
||||
|
@ -137,8 +145,8 @@ docker ps -a
|
|||
- 判题并发数默认:cpu核心数+1
|
||||
- 默认开启vj判题,需要手动修改添加账号与密码,如果不添加不能vj判题!
|
||||
- vj判题并发数默认:cpu核心数*2+1
|
||||
:::
|
||||
|
||||
:::
|
||||
|
||||
|
||||
|
||||
**登录root账号到后台查看服务状态以及到`http://ip/admin/conf`修改服务配置!**
|
||||
|
@ -210,20 +218,20 @@ Password: 开启SMTP服务后生成的随机授权码
|
|||
# redis的配置
|
||||
REDIS_HOST=172.20.0.2
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=hoj123456 # 正式部署请修改
|
||||
REDIS_PASSWORD=hoj123456
|
||||
|
||||
# mysql的配置
|
||||
MYSQL_HOST=172.20.0.3
|
||||
# 请提供当前mysql所在服务器的公网ip
|
||||
MYSQL_PUBLIC_HOST=172.20.0.3
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_ROOT_PASSWORD=hoj123456 # 正式部署请修改
|
||||
MYSQL_ROOT_PASSWORD=hoj123456
|
||||
|
||||
# nacos的配置
|
||||
NACOS_HOST=172.20.0.4
|
||||
NACOS_PORT=8848
|
||||
NACOS_USERNAME=root
|
||||
NACOS_PASSWORD=hoj123456 # 正式部署请修改
|
||||
NACOS_PASSWORD=hoj123456
|
||||
|
||||
# backend后端服务的配置
|
||||
BACKEND_HOST=172.20.0.5
|
||||
|
@ -253,30 +261,30 @@ Password: 开启SMTP服务后生成的随机授权码
|
|||
|
||||
# 评测数据同步的配置
|
||||
# 请修改数据同步密码
|
||||
RSYNC_PASSWORD=hoj123456 # 正式部署请修改
|
||||
RSYNC_PASSWORD=hoj123456
|
||||
|
||||
# docker network的配置
|
||||
SUBNET=172.20.0.0/16
|
||||
```
|
||||
|
||||
|
||||
配置修改保存后,当前路径下启动该服务
|
||||
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
根据网速情况,大约十分钟即可安装完毕,全程无需人工干预。
|
||||
|
||||
|
||||
等待命令执行完毕后,查看容器状态
|
||||
|
||||
|
||||
```shell
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。
|
||||
|
||||
|
||||
|
||||
当看到所有的容器的状态status都为`UP`或`healthy`就代表 OJ 已经启动成功。
|
||||
|
||||
|
||||
|
||||
4. 接着,在另一台服务器上,依旧git clone该文件夹下来,然后进入`judgeserver`文件夹,修改`.env`的配置
|
||||
|
||||
```properties
|
||||
|
@ -298,12 +306,14 @@ Password: 开启SMTP服务后生成的随机授权码
|
|||
JUDGE_SERVER_IP=172.20.0.7
|
||||
JUDGE_SERVER_PORT=8088
|
||||
JUDGE_SERVER_NAME=judger-1
|
||||
# -1表示最大并行任务数为cpu核心数+1
|
||||
# -1表示可接收最大判题任务数为cpu核心数+1
|
||||
MAX_TASK_NUM=-1
|
||||
# 当前判题服务器是否开启远程虚拟判题功能
|
||||
REMOTE_JUDGE_OPEN=true
|
||||
# -1表示最大并行任务数为cpu核心数*2+1
|
||||
# -1表示可接收最大远程判题任务数为cpu核心数*2+1
|
||||
REMOTE_JUDGE_MAX_TASK_NUM=-1
|
||||
# 默认沙盒并行判题程序数为cpu核心数
|
||||
PARALLEL_TASK=default
|
||||
|
||||
# rsync评测数据同步的配置
|
||||
# 写入主服务器ip
|
||||
|
@ -317,7 +327,7 @@ Password: 开启SMTP服务后生成的随机授权码
|
|||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
:::tip
|
||||
提示:需要开启多台判题机,就如当前第4步的操作一样,在每台服务器上执行以上的操作即可。
|
||||
:::
|
||||
:::tip
|
||||
提示:需要开启多台判题机,就如当前第4步的操作一样,在每台服务器上执行以上的操作即可。
|
||||
:::
|
||||
5. 两个服务都启动完成,在浏览器输入主服务ip或域名进行访问,登录root账号到后台查看服务状态。
|
||||
|
|
|
@ -76,35 +76,37 @@
|
|||
JUDGE_SERVER_IP=172.20.0.7
|
||||
JUDGE_SERVER_PORT=8088
|
||||
JUDGE_SERVER_NAME=judger-1
|
||||
# -1表示最大并行任务数为cpu核心数*2
|
||||
# -1表示可接收最大判题任务数为cpu核心数+1
|
||||
MAX_TASK_NUM=-1
|
||||
# 当前判题服务器是否开启远程虚拟判题功能
|
||||
REMOTE_JUDGE_OPEN=true
|
||||
# -1表示最大并行任务数为(cpu核心数*2)*2
|
||||
# -1表示可接收最大远程判题任务数为cpu核心数*2+1
|
||||
REMOTE_JUDGE_MAX_TASK_NUM=-1
|
||||
# 默认沙盒并行判题程序数为cpu核心数
|
||||
PARALLEL_TASK=default
|
||||
|
||||
# rsync评测数据同步的配置
|
||||
# 写入主服务器ip
|
||||
RSYNC_MASTER_ADDR=127.0.0.1
|
||||
# 与主服务器的rsync密码一致
|
||||
RSYNC_PASSWORD=hoj123456
|
||||
RSYNC_PASSWORD=hoj123456
|
||||
```
|
||||
|
||||
3. 启动即可
|
||||
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
4. 验证:
|
||||
|
||||
|
||||
```
|
||||
访问 http://ip:8088/version
|
||||
如果返回信息正常即启动成功!
|
||||
如果返回信息正常即启动成功!
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -135,12 +137,14 @@
|
|||
JUDGE_SERVER_IP=172.20.0.7
|
||||
JUDGE_SERVER_PORT=8088
|
||||
JUDGE_SERVER_NAME=judger-1
|
||||
# -1表示最大并行任务数为cpu核心数*2
|
||||
# -1表示可接收最大判题任务数为cpu核心数+1
|
||||
MAX_TASK_NUM=-1
|
||||
# 当前判题服务器是否开启远程虚拟判题功能
|
||||
REMOTE_JUDGE_OPEN=true
|
||||
# -1表示最大并行任务数为(cpu核心数*2)*2
|
||||
# -1表示可接收最大远程判题任务数为cpu核心数*2+1
|
||||
REMOTE_JUDGE_MAX_TASK_NUM=-1
|
||||
# 默认沙盒并行判题程序数为cpu核心数
|
||||
PARALLEL_TASK=default
|
||||
|
||||
# rsync评测数据同步的配置
|
||||
# 写入主服务器ip
|
||||
|
|
|
@ -196,9 +196,10 @@ services:
|
|||
- NACOS_URL=172.20.0.4:8848 # nacos的url
|
||||
- NACOS_USERNAME=nacos # nacos的管理员账号
|
||||
- NACOS_PASSWORD=nacos # naocs的管理员账号密码
|
||||
- MAX_TASK_NUM=-1 # -1表示最大并行任务数为cpu核心数+1
|
||||
- MAX_TASK_NUM=-1 # -1表示最大可接收判题任务数为cpu核心数+1
|
||||
- REMOTE_JUDGE_OPEN=true # 当前判题服务器是否开启远程虚拟判题功能
|
||||
- REMOTE_JUDGE_MAX_TASK_NUM=-1 # -1表示最大并行任务数为cpu核心数*2+1
|
||||
- REMOTE_JUDGE_MAX_TASK_NUM=-1 # -1表示最大可接收远程判题任务数为cpu核心数*2+1
|
||||
- PARALLEL_TASK=default # 默认沙盒并行判题程序数为cpu核心数
|
||||
ports:
|
||||
- "0.0.0.0:8088:8088"
|
||||
# - "0.0.0.0:5050:5050" # 一般不开放安全沙盒端口
|
||||
|
@ -245,11 +246,26 @@ bash ./run.sh
|
|||
启动judgesever的springboot jar包 和SandBox判题安全沙盒
|
||||
|
||||
```shell
|
||||
ulimit -s unlimited
|
||||
|
||||
chmod +777 SandBox
|
||||
|
||||
nohup ./SandBox -release=true &
|
||||
if test -z "$PARALLEL_TASK";then
|
||||
nohup ./SandBox --silent=true --file-timeout=10m &
|
||||
echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m \033[0m"
|
||||
elif [ -z "$(echo $PARALLEL_TASK | sed 's#[0-9]##g')" ]; then
|
||||
nohup ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK &
|
||||
echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m --parallelism=$PARALLEL_TASK \033[0m"
|
||||
else
|
||||
nohup ./SandBox --silent=true --file-timeout=10m &
|
||||
echo -e "\033[42;34m ./SandBox --silent=true --file-timeout=10m \033[0m"
|
||||
fi
|
||||
|
||||
java -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom -jar ./app.jar
|
||||
if test -z "$JAVA_OPTS";then
|
||||
java -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom -jar ./app.jar
|
||||
else
|
||||
java -XX:+UseG1GC $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./app.jar
|
||||
fi
|
||||
```
|
||||
|
||||
### 4. Dockerfile
|
||||
|
|
|
@ -24,6 +24,7 @@ import top.hcode.hoj.pojo.dto.ProblemDto;
|
|||
import top.hcode.hoj.pojo.entity.contest.Contest;
|
||||
import top.hcode.hoj.pojo.entity.contest.ContestAnnouncement;
|
||||
import top.hcode.hoj.pojo.entity.contest.ContestProblem;
|
||||
import top.hcode.hoj.pojo.entity.contest.ContestRegister;
|
||||
import top.hcode.hoj.pojo.entity.judge.Judge;
|
||||
import top.hcode.hoj.pojo.entity.problem.Problem;
|
||||
import top.hcode.hoj.pojo.vo.AdminContestVo;
|
||||
|
@ -32,6 +33,7 @@ import top.hcode.hoj.pojo.vo.UserRolesVo;
|
|||
import top.hcode.hoj.service.common.impl.AnnouncementServiceImpl;
|
||||
import top.hcode.hoj.service.contest.impl.ContestAnnouncementServiceImpl;
|
||||
import top.hcode.hoj.service.contest.impl.ContestProblemServiceImpl;
|
||||
import top.hcode.hoj.service.contest.impl.ContestRegisterServiceImpl;
|
||||
import top.hcode.hoj.service.contest.impl.ContestServiceImpl;
|
||||
import top.hcode.hoj.service.judge.impl.JudgeServiceImpl;
|
||||
import top.hcode.hoj.service.problem.impl.ProblemServiceImpl;
|
||||
|
@ -41,10 +43,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;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -59,6 +58,9 @@ public class AdminContestController {
|
|||
@Autowired
|
||||
private ContestServiceImpl contestService;
|
||||
|
||||
@Autowired
|
||||
private ContestRegisterServiceImpl contestRegisterService;
|
||||
|
||||
@Autowired
|
||||
private ProblemServiceImpl problemService;
|
||||
|
||||
|
@ -125,9 +127,9 @@ public class AdminContestController {
|
|||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
AdminContestVo adminContestVo = BeanUtil.copyProperties(contest, AdminContestVo.class, "starAccount");
|
||||
if (StringUtils.isEmpty(contest.getStarAccount())){
|
||||
if (StringUtils.isEmpty(contest.getStarAccount())) {
|
||||
adminContestVo.setStarAccount(new ArrayList<>());
|
||||
}else {
|
||||
} else {
|
||||
JSONObject jsonObject = JSONUtil.parseObj(contest.getStarAccount());
|
||||
List<String> starAccount = jsonObject.get("star_account", List.class);
|
||||
adminContestVo.setStarAccount(starAccount);
|
||||
|
@ -169,6 +171,7 @@ public class AdminContestController {
|
|||
@PutMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public CommonResult updateContest(@RequestBody AdminContestVo adminContestVo, HttpServletRequest request) {
|
||||
|
||||
// 获取当前登录的用户
|
||||
|
@ -184,8 +187,16 @@ public class AdminContestController {
|
|||
JSONObject accountJson = new JSONObject();
|
||||
accountJson.set("star_account", adminContestVo.getStarAccount());
|
||||
contest.setStarAccount(accountJson.toString());
|
||||
Contest oldContest = contestService.getById(contest.getId());
|
||||
boolean result = contestService.saveOrUpdate(contest);
|
||||
if (result) {
|
||||
if (!contest.getAuth().equals(Constants.Contest.AUTH_PUBLIC.getCode())) {
|
||||
if (!Objects.equals(oldContest.getPwd(), contest.getPwd())) { // 改了比赛密码则需要删掉已有的注册比赛用户
|
||||
UpdateWrapper<ContestRegister> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("cid", contest.getId());
|
||||
contestRegisterService.remove(updateWrapper);
|
||||
}
|
||||
}
|
||||
return CommonResult.successResponse(null, "修改成功!");
|
||||
} else {
|
||||
return CommonResult.errorResponse("修改失败", CommonResult.STATUS_FAIL);
|
||||
|
|
|
@ -71,7 +71,8 @@ public class AdminProblemController {
|
|||
IPage<Problem> problemList;
|
||||
|
||||
QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByDesc("gmt_create");
|
||||
queryWrapper.orderByDesc("gmt_create")
|
||||
.orderByDesc("id");
|
||||
|
||||
// 根据oj筛选过滤
|
||||
if (oj != null && !"All".equals(oj)) {
|
||||
|
|
|
@ -140,7 +140,6 @@ public class AdminTrainingController {
|
|||
if (!isRoot && !userRolesVo.getUsername().equals(trainingDto.getTraining().getAuthor())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = trainingService.updateTraining(trainingDto);
|
||||
if (result) {
|
||||
trainingRecordService.checkSyncRecord(trainingDto.getTraining());
|
||||
|
|
|
@ -315,10 +315,23 @@ public class AccountController {
|
|||
return CommonResult.errorResponse("用户名长度不能超过20位!");
|
||||
}
|
||||
|
||||
String userIpAddr = IpUtils.getUserIpAddr(request);
|
||||
String key = Constants.Account.TRY_LOGIN_NUM.getCode() + loginDto.getUsername() + "_" + userIpAddr;
|
||||
Integer tryLoginCount = (Integer) redisUtils.get(key);
|
||||
|
||||
if (tryLoginCount != null && tryLoginCount >= 20) {
|
||||
return CommonResult.errorResponse("对不起!登录失败次数过多!您的账号有风险,半个小时内暂时无法登录!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
UserRolesVo userRoles = userRoleDao.getUserRoles(null, loginDto.getUsername());
|
||||
Assert.notNull(userRoles, "用户名不存在,请注意大小写!");
|
||||
Assert.notNull(userRoles, "用户名或密码错误!请注意大小写!");
|
||||
if (!userRoles.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) {
|
||||
return CommonResult.errorResponse("密码不正确");
|
||||
if (tryLoginCount == null) {
|
||||
redisUtils.set(key, 1, 60 * 30); // 三十分钟不尝试,该限制会自动清空消失
|
||||
} else {
|
||||
redisUtils.set(key, tryLoginCount + 1, 60 * 30);
|
||||
}
|
||||
return CommonResult.errorResponse("用户名或密码错误!请注意大小写!");
|
||||
}
|
||||
|
||||
if (userRoles.getStatus() != 0) {
|
||||
|
@ -334,6 +347,12 @@ public class AccountController {
|
|||
.setUid(userRoles.getUid())
|
||||
.setIp(IpUtils.getUserIpAddr(request))
|
||||
.setUserAgent(request.getHeader("User-Agent")));
|
||||
|
||||
// 登录成功,清除锁定限制
|
||||
if (tryLoginCount != null) {
|
||||
redisUtils.del(key);
|
||||
}
|
||||
|
||||
// 异步检查是否异地登录
|
||||
sessionService.checkRemoteLogin(userRoles.getUid());
|
||||
|
||||
|
|
|
@ -262,8 +262,7 @@ public class ContestController {
|
|||
List<ContestProblemVo> contestProblemList;
|
||||
boolean isAdmin = isRoot || contest.getAuthor().equals(userRolesVo.getUsername());
|
||||
// 如果比赛开启封榜
|
||||
if (contest.getSealRank() && contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode() &&
|
||||
contest.getSealRankTime().before(new Date())) {
|
||||
if (contestService.isSealRank(userRolesVo.getUid(), contest, false, isRoot)) {
|
||||
contestProblemList = contestProblemService.getContestProblemList(cid, contest.getStartTime(), contest.getEndTime(),
|
||||
contest.getSealRankTime(), isAdmin, contest.getAuthor());
|
||||
} else {
|
||||
|
@ -355,14 +354,9 @@ public class ContestController {
|
|||
tmpMap.put(language.getId(), language.getName());
|
||||
});
|
||||
|
||||
Date now = new Date();
|
||||
Date sealRankTime = null;
|
||||
//封榜时间除超级管理员和比赛管理员外 其它人不可看到最新数据
|
||||
if (contest.getSealRank() &&
|
||||
!isRoot &&
|
||||
!userRolesVo.getUid().equals(contest.getUid()) &&
|
||||
contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode() &&
|
||||
contest.getSealRankTime().before(now)) {
|
||||
if (contestService.isSealRank(userRolesVo.getUid(), contest, false, isRoot)) {
|
||||
sealRankTime = contest.getSealRankTime();
|
||||
}
|
||||
|
||||
|
@ -440,15 +434,11 @@ public class ContestController {
|
|||
}
|
||||
Date sealRankTime = null;
|
||||
|
||||
// 不是比赛管理员和超级管理,又有开启封榜,需要判断是否处于封榜期间
|
||||
if (contest.getSealRank() && !isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
// 当前是比赛期间 同时处于封榜时间
|
||||
if (contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode()
|
||||
&& contest.getSealRankTime().before(new Date())) {
|
||||
sealRankTime = contest.getSealRankTime();
|
||||
}
|
||||
// 需要判断是否需要封榜
|
||||
if (contestService.isSealRank(userRolesVo.getUid(), contest, false, isRoot)) {
|
||||
sealRankTime = contest.getSealRankTime();
|
||||
}
|
||||
// OI比赛封榜期间不更新,ACM比赛封榜期间可看到自己的提交,但是其它人的不更新
|
||||
// OI比赛封榜期间不更新,ACM比赛封榜期间可看到自己的提交,但是其它人的不可见
|
||||
IPage<JudgeVo> commonJudgeList = judgeService.getContestJudgeList(limit, currentPage, displayId, searchCid,
|
||||
searchStatus, searchUsername, uid, beforeContestSubmit, rule, contest.getStartTime(),
|
||||
sealRankTime, userRolesVo.getUid(), completeProblemID);
|
||||
|
|
|
@ -268,20 +268,9 @@ public class JudgeController {
|
|||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
|
||||
|
||||
boolean root = SecurityUtils.getSubject().hasRole("root"); // 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root"); // 是否为超级管理员
|
||||
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
Contest contest = null;
|
||||
if (judge.getCid() != 0 && !root) {
|
||||
contest = contestService.getById(judge.getCid());
|
||||
if (userRolesVo != null && !userRolesVo.getUid().equals(contest.getUid()) && contest.getSealRank()
|
||||
&& contest.getType().intValue() == Constants.Contest.TYPE_OI.getCode()
|
||||
&& contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode()
|
||||
&& contest.getSealRankTime().before(new Date())) {
|
||||
result.put("submission", new Judge().setStatus(Constants.Judge.STATUS_SUBMITTED_UNKNOWN_RESULT.getStatus()));
|
||||
return CommonResult.successResponse(result, "获取提交数据成功!");
|
||||
}
|
||||
}
|
||||
// 清空vj信息
|
||||
judge.setVjudgeUsername(null);
|
||||
judge.setVjudgeSubmitId(null);
|
||||
|
@ -291,16 +280,16 @@ public class JudgeController {
|
|||
// 如果不是本人或者并未分享代码,则不可查看
|
||||
// 当此次提交代码不共享
|
||||
// 比赛提交只有比赛创建者和root账号可看代码
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
if (judge.getCid() != 0) {
|
||||
if (contest == null) {
|
||||
contest = contestService.getById(judge.getCid());
|
||||
if (userRolesVo == null) {
|
||||
return CommonResult.errorResponse("请先登录!", CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
if (userRolesVo != null && !root && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
Contest contest = contestService.getById(judge.getCid());
|
||||
if (!isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
// 如果是比赛,那么还需要判断是否为封榜,比赛管理员和超级管理员可以有权限查看(ACM题目除外)
|
||||
if (contest.getSealRank()
|
||||
&& contest.getType().intValue() == Constants.Contest.TYPE_OI.getCode()
|
||||
&& contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode()
|
||||
&& contest.getSealRankTime().before(new Date())) {
|
||||
if (contest.getType().intValue() == Constants.Contest.TYPE_OI.getCode()
|
||||
&& contestService.isSealRank(userRolesVo.getUid(), contest, false, false)) {
|
||||
result.put("submission", new Judge().setStatus(Constants.Judge.STATUS_SUBMITTED_UNKNOWN_RESULT.getStatus()));
|
||||
return CommonResult.successResponse(result, "获取提交数据成功!");
|
||||
}
|
||||
|
@ -318,7 +307,7 @@ public class JudgeController {
|
|||
}
|
||||
} else {
|
||||
boolean admin = SecurityUtils.getSubject().hasRole("problem_admin");// 是否为题目管理员
|
||||
if (!judge.getShare() && !root && !admin) {
|
||||
if (!judge.getShare() && !isRoot && !admin) {
|
||||
if (userRolesVo != null) { // 当前是登陆状态
|
||||
// 需要判断是否为当前登陆用户自己的提交代码
|
||||
if (!judge.getUid().equals(userRolesVo.getUid())) {
|
||||
|
|
|
@ -136,10 +136,18 @@ public class ProblemController {
|
|||
HashMap<Long, Object> result = new HashMap<>();
|
||||
// 先查询判断该用户对于这些题是否已经通过,若已通过,则无论后续再提交结果如何,该题都标记为通过
|
||||
QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("distinct pid,status,submit_time,score").in("pid", pidListDto.getPidList())
|
||||
// 如果是比赛的提交记录需要判断cid
|
||||
.eq(pidListDto.getIsContestProblemList(), "cid", pidListDto.getCid())
|
||||
.eq("uid", userRolesVo.getUid()).orderByDesc("submit_time");
|
||||
queryWrapper.select("distinct pid,status,submit_time,score")
|
||||
.in("pid", pidListDto.getPidList())
|
||||
.eq("uid", userRolesVo.getUid())
|
||||
.orderByDesc("submit_time");
|
||||
|
||||
if (pidListDto.getIsContestProblemList()) {
|
||||
// 如果是比赛的提交记录需要判断cid
|
||||
queryWrapper.eq("cid", pidListDto.getCid());
|
||||
} else {
|
||||
queryWrapper.eq("cid", 0);
|
||||
}
|
||||
|
||||
List<Judge> judges = judgeService.list(queryWrapper);
|
||||
|
||||
boolean isACMContest = true;
|
||||
|
@ -162,11 +170,8 @@ public class ProblemController {
|
|||
if (!result.containsKey(judge.getPid())) { // IO比赛的,如果还未写入,则使用最新一次提交的结果
|
||||
// 判断该提交是否为封榜之后的提交,OI赛制封榜后的提交看不到提交结果,
|
||||
// 只有比赛结束可以看到,比赛管理员与超级管理员的提交除外
|
||||
if (contest.getSealRank() &&
|
||||
!contest.getUid().equals(userRolesVo.getUid()) &&
|
||||
!SecurityUtils.getSubject().hasRole("root") &&
|
||||
contest.getStatus().intValue() == Constants.Contest.STATUS_RUNNING.getCode() &&
|
||||
judge.getSubmitTime().after(contest.getSealRankTime())) {
|
||||
if (contestService.isSealRank(userRolesVo.getUid(), contest, false,
|
||||
SecurityUtils.getSubject().hasRole("root"))) {
|
||||
temp.put("status", Constants.Judge.STATUS_SUBMITTED_UNKNOWN_RESULT.getStatus());
|
||||
temp.put("score", null);
|
||||
} else {
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
|||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.pojo.entity.contest.Contest;
|
||||
import top.hcode.hoj.pojo.entity.training.Training;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingCategory;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingRegister;
|
||||
|
@ -179,8 +180,16 @@ public class TrainingController {
|
|||
QueryWrapper<TrainingRegister> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("tid", tid).eq("uid", userRolesVo.getUid());
|
||||
TrainingRegister trainingRegister = trainingRegisterService.getOne(queryWrapper, false);
|
||||
boolean access = false;
|
||||
if (trainingRegister != null) {
|
||||
access = true;
|
||||
Training training = trainingService.getById(tid);
|
||||
if (training == null || !training.getStatus()) {
|
||||
return CommonResult.errorResponse("对不起,该训练不存在!");
|
||||
}
|
||||
}
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
result.put("access", trainingRegister != null);
|
||||
result.put("access", access);
|
||||
return CommonResult.successResponse(result);
|
||||
}
|
||||
|
||||
|
@ -215,7 +224,7 @@ public class TrainingController {
|
|||
// 页数,每页数若为空,设置默认值
|
||||
if (currentPage == null || currentPage < 1) currentPage = 1;
|
||||
if (limit == null || limit < 1) limit = 30;
|
||||
IPage<TrainingRankVo> trainingRankPager = trainingRecordService.getTrainingRank(tid, currentPage, limit);
|
||||
IPage<TrainingRankVo> trainingRankPager = trainingRecordService.getTrainingRank(tid, training.getAuthor(), currentPage, limit);
|
||||
return CommonResult.successResponse(trainingRankPager, "success");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="top.hcode.hoj.dao.TrainingRecordMapper">
|
||||
<select id="getTrainingRecord" resultType="top.hcode.hoj.pojo.vo.TrainingRecordVo">
|
||||
SELECT tr.tid,tr.uid,tr.pid,tr.tpid,tr.submit_id,j.status,j.score,j.use_time,
|
||||
SELECT tr.tid,tr.uid,tr.pid,tr.tpid,tr.submit_id,j.status,j.score,j.time as use_time,
|
||||
u.gender,u.realname as realname,u.username,u.avatar,u.school,u.nickname
|
||||
FROM training_record tr,user_info u,judge j
|
||||
WHERE tr.uid = u.uuid
|
||||
|
|
|
@ -36,9 +36,6 @@ public class TrainingRankVo {
|
|||
@ApiModelProperty(value = "头像")
|
||||
private String avatar;
|
||||
|
||||
@ApiModelProperty(value = "总提交数")
|
||||
private Integer total;
|
||||
|
||||
@ApiModelProperty(value = "ac题目数")
|
||||
private Integer ac;
|
||||
|
||||
|
|
|
@ -97,11 +97,12 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
|
|||
return false;
|
||||
} else if (contest.getSealRank() && contest.getSealRankTime() != null) { // 该比赛开启封榜模式
|
||||
Date now = new Date();
|
||||
// 如果现在时间处于封榜开始到比赛结束之间或者没有开启自动解除封榜,不可刷新榜单
|
||||
if ((now.after(contest.getSealRankTime()) && now.before(contest.getEndTime()))
|
||||
|| !contest.getAutoRealRank()) {
|
||||
// 如果现在时间处于封榜开始到比赛结束之间
|
||||
if (now.after(contest.getSealRankTime()) && now.before(contest.getEndTime())) {
|
||||
return true;
|
||||
}
|
||||
// 或者没有开启赛后自动解除封榜,不可刷新榜单
|
||||
return !contest.getAutoRealRank() && now.after(contest.getEndTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public interface TrainingRecordService extends IService<TrainingRecord> {
|
|||
|
||||
public CommonResult submitTrainingProblem(ToJudgeDto judgeDto, UserRolesVo userRolesVo, Judge judge);
|
||||
|
||||
public IPage<TrainingRankVo> getTrainingRank(Long tid, int currentPage, int limit);
|
||||
public IPage<TrainingRankVo> getTrainingRank(Long tid,String username, int currentPage, int limit);
|
||||
|
||||
public void syncUserSubmissionToRecordByTid(Long tid, String uid);
|
||||
|
||||
|
|
|
@ -5,12 +5,14 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.tomcat.util.bcel.Const;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.dao.TrainingRecordMapper;
|
||||
import top.hcode.hoj.dao.UserInfoMapper;
|
||||
import top.hcode.hoj.pojo.dto.ToJudgeDto;
|
||||
import top.hcode.hoj.pojo.entity.contest.Contest;
|
||||
import top.hcode.hoj.pojo.entity.contest.ContestProblem;
|
||||
|
@ -19,6 +21,7 @@ import top.hcode.hoj.pojo.entity.problem.Problem;
|
|||
import top.hcode.hoj.pojo.entity.training.Training;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingProblem;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingRecord;
|
||||
import top.hcode.hoj.pojo.entity.user.UserInfo;
|
||||
import top.hcode.hoj.pojo.vo.TrainingRankVo;
|
||||
import top.hcode.hoj.pojo.vo.TrainingRecordVo;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
|
@ -44,8 +47,8 @@ public class TrainingRecordServiceImpl extends ServiceImpl<TrainingRecordMapper,
|
|||
@Resource
|
||||
private TrainingProblemServiceImpl trainingProblemService;
|
||||
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
@Autowired
|
||||
private UserInfoMapper userInfoMapper;
|
||||
|
||||
@Resource
|
||||
private TrainingRecordMapper trainingRecordMapper;
|
||||
|
@ -109,16 +112,27 @@ public class TrainingRecordServiceImpl extends ServiceImpl<TrainingRecordMapper,
|
|||
}
|
||||
|
||||
@Override
|
||||
public IPage<TrainingRankVo> getTrainingRank(Long tid, int currentPage, int limit) {
|
||||
public IPage<TrainingRankVo> getTrainingRank(Long tid, String username, int currentPage, int limit) {
|
||||
|
||||
Map<Long, String> tpIdMapDisplayId = getTPIdMapDisplayId(tid);
|
||||
List<TrainingRecordVo> trainingRecordVoList = trainingRecordMapper.getTrainingRecord(tid);
|
||||
|
||||
List<UserInfo> superAdminList = userInfoMapper.getSuperAdminList();
|
||||
|
||||
List<String> superAdminUidList = superAdminList.stream().map(UserInfo::getUuid).collect(Collectors.toList());
|
||||
|
||||
|
||||
List<TrainingRankVo> result = new ArrayList<>();
|
||||
|
||||
HashMap<String, Integer> uidMapIndex = new HashMap<>();
|
||||
int pos = 0;
|
||||
for (TrainingRecordVo trainingRecordVo : trainingRecordVoList) {
|
||||
// 超级管理员和训练创建者的提交不入排行榜
|
||||
if (username.equals(trainingRecordVo.getUsername())
|
||||
|| superAdminUidList.contains(trainingRecordVo.getUid())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TrainingRankVo trainingRankVo;
|
||||
Integer index = uidMapIndex.get(trainingRecordVo.getUid());
|
||||
if (index == null) {
|
||||
|
@ -131,8 +145,7 @@ public class TrainingRecordServiceImpl extends ServiceImpl<TrainingRecordMapper,
|
|||
.setUsername(trainingRecordVo.getUsername())
|
||||
.setNickname(trainingRecordVo.getNickname())
|
||||
.setAc(0)
|
||||
.setTotalRunTime(0)
|
||||
.setTotal(0);
|
||||
.setTotalRunTime(0);
|
||||
HashMap<String, HashMap<String, Object>> submissionInfo = new HashMap<>();
|
||||
trainingRankVo.setSubmissionInfo(submissionInfo);
|
||||
|
||||
|
@ -146,7 +159,6 @@ public class TrainingRecordServiceImpl extends ServiceImpl<TrainingRecordMapper,
|
|||
HashMap<String, Object> problemSubmissionInfo = trainingRankVo
|
||||
.getSubmissionInfo()
|
||||
.getOrDefault(displayId, new HashMap<>());
|
||||
trainingRankVo.setTotal(trainingRankVo.getTotal() + 1);
|
||||
|
||||
// 如果该题目已经AC过了,只比较运行时间取最小
|
||||
if ((Boolean) problemSubmissionInfo.getOrDefault("isAC", false)) {
|
||||
|
@ -294,18 +306,11 @@ public class TrainingRecordServiceImpl extends ServiceImpl<TrainingRecordMapper,
|
|||
saveBatchNewRecordByJudgeList(judgeList, tid, null, pidMapTPid);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<Long, String> getTPIdMapDisplayId(Long tid) {
|
||||
String key = Constants.Training.MAP_REDIS_KEY_PRE.getValue() + tid;
|
||||
Map<Long, String> res = (Map<Long, String>) redisUtils.get(key);
|
||||
if (res == null) {
|
||||
QueryWrapper<TrainingProblem> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("tid", tid);
|
||||
List<TrainingProblem> trainingProblemList = trainingProblemService.list(queryWrapper);
|
||||
res = trainingProblemList.stream().collect(Collectors.toMap(TrainingProblem::getId, TrainingProblem::getDisplayId));
|
||||
redisUtils.set(key, res, 12 * 3600);
|
||||
}
|
||||
return res;
|
||||
QueryWrapper<TrainingProblem> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("tid", tid);
|
||||
List<TrainingProblem> trainingProblemList = trainingProblemService.list(queryWrapper);
|
||||
return trainingProblemList.stream().collect(Collectors.toMap(TrainingProblem::getId, TrainingProblem::getDisplayId));
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ package top.hcode.hoj.service.training.impl;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
|
@ -35,6 +36,7 @@ public class TrainingRegisterServiceImpl extends ServiceImpl<TrainingRegisterMap
|
|||
private TrainingServiceImpl trainingService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private TrainingRecordServiceImpl trainingRecordService;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,19 +11,23 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.dao.MappingTrainingCategoryMapper;
|
||||
import top.hcode.hoj.dao.TrainingMapper;
|
||||
import top.hcode.hoj.dao.TrainingRegisterMapper;
|
||||
import top.hcode.hoj.pojo.dto.TrainingDto;
|
||||
import top.hcode.hoj.pojo.entity.training.MappingTrainingCategory;
|
||||
import top.hcode.hoj.pojo.entity.training.Training;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingCategory;
|
||||
import top.hcode.hoj.pojo.entity.training.TrainingRegister;
|
||||
import top.hcode.hoj.pojo.vo.TrainingVo;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
import top.hcode.hoj.service.training.TrainingService;
|
||||
import top.hcode.hoj.utils.Constants;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
|
@ -42,6 +46,9 @@ public class TrainingServiceImpl extends ServiceImpl<TrainingMapper, Training> i
|
|||
@Resource
|
||||
private MappingTrainingCategoryMapper mappingTrainingCategoryMapper;
|
||||
|
||||
@Resource
|
||||
private TrainingRegisterMapper trainingRegisterMapper;
|
||||
|
||||
@Override
|
||||
public IPage<TrainingVo> getTrainingList(int limit, int currentPage, Long categoryId, String auth, String keyword) {
|
||||
List<TrainingVo> trainingList = trainingMapper.getTrainingList(categoryId, auth, keyword);
|
||||
|
@ -87,7 +94,19 @@ public class TrainingServiceImpl extends ServiceImpl<TrainingMapper, Training> i
|
|||
public boolean updateTraining(TrainingDto trainingDto) {
|
||||
|
||||
Training training = trainingDto.getTraining();
|
||||
Training oldTraining = trainingMapper.selectById(training.getId());
|
||||
trainingMapper.updateById(training);
|
||||
|
||||
// 私有训练 修改密码 需要清空之前注册训练的记录
|
||||
if (training.getAuth().equals(Constants.Training.AUTH_PRIVATE.getValue())) {
|
||||
if (!Objects.equals(training.getPrivatePwd(), oldTraining.getPrivatePwd())) {
|
||||
UpdateWrapper<TrainingRegister> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("tid", training.getId());
|
||||
trainingRegisterMapper.delete(updateWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TrainingCategory trainingCategory = trainingDto.getTrainingCategory();
|
||||
if (trainingCategory.getId() == null) {
|
||||
try {
|
||||
|
|
|
@ -265,9 +265,7 @@ public class Constants {
|
|||
public enum Training {
|
||||
|
||||
AUTH_PRIVATE("Private"),
|
||||
AUTH_PUBLIC("Public"),
|
||||
|
||||
MAP_REDIS_KEY_PRE("training_TPId_map_DisplayId:");
|
||||
AUTH_PUBLIC("Public");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
|
|
@ -368,13 +368,6 @@ const ojApi = {
|
|||
params: {displayId,cid}
|
||||
})
|
||||
},
|
||||
// 获取训练提交列表
|
||||
getTrainingSubmissionList (limit, params) {
|
||||
params.limit = limit
|
||||
return ajax('/api/submissions', 'get', {
|
||||
params
|
||||
})
|
||||
},
|
||||
// 获取训练记录榜单
|
||||
getTrainingRank(params){
|
||||
return ajax('/api/get-training-rank', 'get', {
|
||||
|
|
|
@ -98,7 +98,7 @@ const adminRoutes= [
|
|||
},
|
||||
{
|
||||
path: 'training/create',
|
||||
name: 'admin-training-contest',
|
||||
name: 'admin-create-training',
|
||||
component: Training,
|
||||
meta: { title:'Create Training'},
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@ import SysMsg from "@/views/oj/message/SysMsg.vue"
|
|||
import TrainingList from "@/views/oj/training/TrainingList.vue"
|
||||
import TrainingDetails from "@/views/oj/training/TrainingDetails.vue"
|
||||
import TrainingProblemList from "@/views/oj/training/TrainingProblemList.vue"
|
||||
import TrainingRank from "@/views/oj/training/TrainingRank.vue"
|
||||
import NotFound from "@/views/404.vue"
|
||||
|
||||
const ojRoutes = [
|
||||
|
@ -82,6 +83,12 @@ const ojRoutes = [
|
|||
component: Problem,
|
||||
meta: { title: 'Training Problem Details' }
|
||||
},
|
||||
{
|
||||
name: 'TrainingRank',
|
||||
path: 'rank',
|
||||
component: TrainingRank,
|
||||
meta: { title: 'Training Rank' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -200,7 +200,7 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.training.auth != 'Public' && !this.training.pwd) {
|
||||
if (this.training.auth != 'Public' && !this.training.privatePwd) {
|
||||
myMessage.error(
|
||||
this.$i18n.t('m.Training_Password') +
|
||||
' ' +
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
@keyup.enter.native="filterByKeyword"
|
||||
></vxe-input>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="goCreateTraining"
|
||||
icon="el-icon-plus"
|
||||
>{{ $t('m.Create') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<vxe-table
|
||||
|
@ -209,6 +218,9 @@ export default {
|
|||
filterByKeyword() {
|
||||
this.currentChange(1);
|
||||
},
|
||||
goCreateTraining() {
|
||||
this.$router.push({ name: 'admin-create-training' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
max-height="500px"
|
||||
:loading="loading.recent7ACRankLoading"
|
||||
>
|
||||
<vxe-table-column type="seq" min-width="30">
|
||||
<vxe-table-column type="seq" min-width="50">
|
||||
<template v-slot="{ rowIndex }">
|
||||
<span :class="getRankTagClass(rowIndex)"
|
||||
>{{ rowIndex + 1 }}
|
||||
|
@ -119,7 +119,7 @@
|
|||
<vxe-table-column
|
||||
field="username"
|
||||
:title="$t('m.Username')"
|
||||
min-width="130"
|
||||
min-width="100"
|
||||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
|
@ -141,7 +141,7 @@
|
|||
<vxe-table-column
|
||||
field="ac"
|
||||
:title="$t('m.AC')"
|
||||
min-width="30"
|
||||
min-width="50"
|
||||
align="left"
|
||||
>
|
||||
</vxe-table-column>
|
||||
|
|
|
@ -488,8 +488,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
applyToTable(dataRank) {
|
||||
dataRank.forEach((rank, i) => {
|
||||
let info = rank.submissionInfo;
|
||||
let cellClass = {};
|
||||
|
|
|
@ -14,32 +14,32 @@
|
|||
field="status"
|
||||
title=""
|
||||
width="50"
|
||||
v-if="
|
||||
isAuthenticated && isGetStatusOk && contestRuleType == RULE_TYPE.OI
|
||||
"
|
||||
v-if="isAuthenticated && contestRuleType == RULE_TYPE.OI"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span :class="getScoreColor(row.score)" v-if="row.score != null">{{
|
||||
row.score
|
||||
}}</span>
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
v-else-if="row.myStatus == -5"
|
||||
>
|
||||
<i class="fa fa-question" :style="getIconColor(row.myStatus)"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
v-else
|
||||
>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
<template v-if="isGetStatusOk">
|
||||
<span :class="getScoreColor(row.score)" v-if="row.score != null">{{
|
||||
row.score
|
||||
}}</span>
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
v-else-if="row.myStatus == -5"
|
||||
>
|
||||
<i class="fa fa-question" :style="getIconColor(row.myStatus)"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
v-else
|
||||
>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
|
@ -48,26 +48,26 @@
|
|||
field="status"
|
||||
title=""
|
||||
width="50"
|
||||
v-if="
|
||||
isAuthenticated && isGetStatusOk && contestRuleType == RULE_TYPE.ACM
|
||||
"
|
||||
v-if="isAuthenticated && contestRuleType == RULE_TYPE.ACM"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
>
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus == 0"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-else-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
<template v-if="isGetStatusOk">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
>
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus == 0"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-else-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="displayId" width="80" title="#">
|
||||
|
|
|
@ -455,8 +455,7 @@ export default {
|
|||
this.options.xAxis[0].data = user;
|
||||
this.options.series[0].data = scores;
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
applyToTable(dataRank) {
|
||||
dataRank.forEach((rank, i) => {
|
||||
let info = rank.submissionInfo;
|
||||
let cellClass = {};
|
||||
|
|
|
@ -387,8 +387,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
applyToTable(dataRank) {
|
||||
let acCountMap = {};
|
||||
let errorCountMap = {};
|
||||
dataRank.forEach((rank, i) => {
|
||||
|
|
|
@ -349,8 +349,7 @@ export default {
|
|||
query: { username: username, uid: uid },
|
||||
});
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
applyToTable(dataRank) {
|
||||
let acCountMap = {};
|
||||
let errorCountMap = {};
|
||||
dataRank.forEach((rank, i) => {
|
||||
|
|
|
@ -117,27 +117,28 @@
|
|||
@cell-mouseenter="cellHover"
|
||||
:data="problemList"
|
||||
>
|
||||
<vxe-table-column
|
||||
title=""
|
||||
width="30"
|
||||
v-if="isAuthenticated && isGetStatusOk"
|
||||
>
|
||||
<vxe-table-column title="" width="30" v-if="isAuthenticated">
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus].name"
|
||||
placement="top"
|
||||
>
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus == 0"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-else-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
<template v-if="isGetStatusOk">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
>
|
||||
<template v-if="row.myStatus == 0">
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
></i>
|
||||
</template>
|
||||
|
||||
<template v-else-if="row.myStatus != -10">
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
></i>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
|
|
|
@ -84,7 +84,9 @@
|
|||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span v-katex class="rank-signature-body">{{ row.signature }}</span>
|
||||
<span v-katex class="rank-signature-body" v-if="row.signature">{{
|
||||
row.signature
|
||||
}}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
|
|
@ -90,7 +90,9 @@
|
|||
align="left"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span v-katex class="rank-signature-body">{{ row.signature }}</span>
|
||||
<span v-katex class="rank-signature-body" v-if="row.signature">{{
|
||||
row.signature
|
||||
}}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<span>
|
||||
<span>{{ $t('m.Training_Auth') }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span v-if="training.auth">
|
||||
<el-tag
|
||||
:type="TRAINING_TYPE[training.auth]['color']"
|
||||
effect="dark"
|
||||
|
@ -263,6 +263,9 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$store.commit('clearTraining');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -13,6 +13,28 @@
|
|||
@search-click="filterByChange"
|
||||
></vxe-input>
|
||||
</section>
|
||||
<section>
|
||||
<b class="training-category">{{ $t('m.Training_Auth') }}</b>
|
||||
<div>
|
||||
<el-tag
|
||||
size="medium"
|
||||
class="category-item"
|
||||
:effect="query.auth ? 'plain' : 'dark'"
|
||||
@click="filterByAuthType(null)"
|
||||
>{{ $t('m.All') }}</el-tag
|
||||
>
|
||||
<el-tag
|
||||
size="medium"
|
||||
class="category-item"
|
||||
v-for="(key, index) in TRAINING_TYPE"
|
||||
:type="key.color"
|
||||
:effect="query.auth == key.name ? 'dark' : 'plain'"
|
||||
:key="index"
|
||||
@click="filterByAuthType(key.name)"
|
||||
>{{ key.name }}</el-tag
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<b class="training-category">{{ $t('m.Training_Category') }}</b>
|
||||
<div>
|
||||
|
@ -159,6 +181,7 @@ export default {
|
|||
query: {
|
||||
keyword: '',
|
||||
categoryId: null,
|
||||
auth: null,
|
||||
},
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
|
@ -182,6 +205,7 @@ export default {
|
|||
this.query.keyword = route.keyword || '';
|
||||
this.currentPage = parseInt(route.currentPage) || 1;
|
||||
this.categoryId = route.categoryId || null;
|
||||
this.query.auth = route.auth || null;
|
||||
this.getTrainingList(1);
|
||||
},
|
||||
filterByCategory(categoryId) {
|
||||
|
@ -189,6 +213,11 @@ export default {
|
|||
this.filterByChange();
|
||||
},
|
||||
|
||||
filterByAuthType(auth) {
|
||||
this.query.auth = auth;
|
||||
this.filterByChange();
|
||||
},
|
||||
|
||||
filterByChange() {
|
||||
let query = Object.assign({}, this.query);
|
||||
query.currentPage = this.currentPage;
|
||||
|
|
|
@ -13,24 +13,29 @@
|
|||
field="status"
|
||||
title=""
|
||||
width="50"
|
||||
v-if="isAuthenticated && isGetStatusOk"
|
||||
v-if="isAuthenticated"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
>
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-if="row.myStatus == 0"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
v-else-if="row.myStatus != -10"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
<template v-if="isGetStatusOk">
|
||||
<el-tooltip
|
||||
:content="JUDGE_STATUS[row.myStatus]['name']"
|
||||
placement="top"
|
||||
>
|
||||
<template v-if="row.myStatus == 0">
|
||||
<i
|
||||
class="el-icon-check"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
></i>
|
||||
</template>
|
||||
|
||||
<template v-else-if="row.myStatus != -10">
|
||||
<i
|
||||
class="el-icon-minus"
|
||||
:style="getIconColor(row.myStatus)"
|
||||
></i>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
|
|
|
@ -1,40 +1,9 @@
|
|||
<template>
|
||||
<el-card shadow>
|
||||
<div slot="header">
|
||||
<span class="panel-title">{{ $t('m.Training_Rank') }}</span>
|
||||
<span style="float:right;font-size: 20px;">
|
||||
<el-popover trigger="hover" placement="left-start">
|
||||
<i class="el-icon-s-tools" slot="reference"></i>
|
||||
<div id="switches">
|
||||
<p>
|
||||
<span>{{ $t('m.Chart') }}</span>
|
||||
<el-switch v-model="showChart"></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Table') }}</span>
|
||||
<el-switch v-model="showTable"></el-switch>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ $t('m.Auto_Refresh') }}(10s)</span>
|
||||
<el-switch
|
||||
:disabled="refreshDisabled"
|
||||
v-model="autoRefresh"
|
||||
@change="handleAutoRefresh"
|
||||
></el-switch>
|
||||
</p>
|
||||
<template>
|
||||
<el-button type="primary" size="small" @click="downloadRankCSV">{{
|
||||
$t('m.Download_as_CSV')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</el-popover>
|
||||
</span>
|
||||
<div slot="header" class="rank-title">
|
||||
<span class="panel-title">{{ $t('m.Record_List') }}</span>
|
||||
</div>
|
||||
<div v-show="showChart" class="echarts">
|
||||
<ECharts :options="options" ref="chart" :autoresize="true"></ECharts>
|
||||
</div>
|
||||
<div v-show="showTable">
|
||||
<div>
|
||||
<vxe-table
|
||||
round
|
||||
border
|
||||
|
@ -62,22 +31,22 @@
|
|||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[training.rankShowName]"
|
||||
:username="row.username"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[training.rankShowName]"
|
||||
:title="row.username"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="training-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[training.rankShowName] }}</span
|
||||
>{{ row.username }}</span
|
||||
>
|
||||
<span class="training-school" v-if="row.school">{{
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
|
@ -94,22 +63,22 @@
|
|||
>
|
||||
<template v-slot="{ row }">
|
||||
<avatar
|
||||
:username="row[training.rankShowName]"
|
||||
:username="row.username"
|
||||
:inline="true"
|
||||
:size="37"
|
||||
color="#FFF"
|
||||
:src="row.avatar"
|
||||
:title="row[training.rankShowName]"
|
||||
:title="row.username"
|
||||
></avatar>
|
||||
|
||||
<span style="float:right;text-align:right">
|
||||
<a @click="getUserHomeByUsername(row.uid, row.username)">
|
||||
<span class="training-username"
|
||||
><span class="female-flag" v-if="row.gender == 'female'"
|
||||
<span class="contest-username"
|
||||
><span class="contest-rank-flag" v-if="row.gender == 'female'"
|
||||
>Girl</span
|
||||
>{{ row[training.rankShowName] }}</span
|
||||
>{{ row.username }}</span
|
||||
>
|
||||
<span class="training-school" v-if="row.school">{{
|
||||
<span class="contest-school" v-if="row.school">{{
|
||||
row.school
|
||||
}}</span>
|
||||
</a>
|
||||
|
@ -125,49 +94,59 @@
|
|||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="rating"
|
||||
:title="$t('m.AC') + ' / ' + $t('m.Total')"
|
||||
min-width="80"
|
||||
:title="$t('m.Total_Score')"
|
||||
min-width="90"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span
|
||||
>{{ row.ac }} /
|
||||
<a
|
||||
@click="getUserTotalSubmit(row.username)"
|
||||
style="color:rgb(87, 163, 243);"
|
||||
>{{ row.total }}</a
|
||||
><a
|
||||
@click="getUserACSubmit(row.username)"
|
||||
style="color:rgb(87, 163, 243);font-size:16px"
|
||||
>{{ row.ac }}</a
|
||||
>
|
||||
<br />
|
||||
<span class="judge-time">({{ row.totalRunTime }}ms)</span>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="totalTime"
|
||||
:title="$t('m.TotalTime')"
|
||||
min-width="100"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ parseTotalTime(row.totalTime) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="120"
|
||||
min-width="70"
|
||||
v-for="problem in trainingProblemList"
|
||||
:key="problem.displayId"
|
||||
:key="problem.problemId"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<span
|
||||
><a
|
||||
@click="getTrainingProblemById(problem.displayId)"
|
||||
@click="getTrainingProblemById(problem.problemId)"
|
||||
class="emphasis"
|
||||
style="color:#495060;"
|
||||
>{{ problem.displayId }}</a
|
||||
>{{ problem.problemId }}</a
|
||||
></span
|
||||
>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<span v-if="row.submissionInfo[problem.displayId]">
|
||||
<span v-if="row.submissionInfo[problem.displayId].isAC"
|
||||
>{{ row.submissionInfo[problem.displayId].ACTime }}<br
|
||||
/></span>
|
||||
<span v-if="row.submissionInfo[problem.problemId]">
|
||||
<span
|
||||
class="judge-status"
|
||||
:style="
|
||||
'color:' +
|
||||
JUDGE_STATUS[row.submissionInfo[problem.problemId].status]
|
||||
.color
|
||||
"
|
||||
>
|
||||
{{
|
||||
JUDGE_STATUS[row.submissionInfo[problem.problemId].status]
|
||||
.short
|
||||
}}
|
||||
</span>
|
||||
<br />
|
||||
<span class="judge-time">
|
||||
({{
|
||||
row.submissionInfo[problem.problemId].runTime
|
||||
? row.submissionInfo[problem.problemId].runTime
|
||||
: 0
|
||||
}}ms)
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
@ -185,11 +164,12 @@
|
|||
</template>
|
||||
<script>
|
||||
import Avatar from 'vue-avatar';
|
||||
import moment from 'moment';
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { JUDGE_STATUS } from '@/common/constants';
|
||||
const Pagination = () => import('@/components/oj/common/Pagination');
|
||||
import api from '@/common/api';
|
||||
import { mapState } from 'vuex';
|
||||
import time from '@/common/time';
|
||||
import utils from '@/common/utils';
|
||||
|
||||
export default {
|
||||
name: 'TrainingRank',
|
||||
|
@ -202,91 +182,43 @@ export default {
|
|||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
autoRefresh: false,
|
||||
trainingID: '',
|
||||
dataRank: [],
|
||||
options: {
|
||||
title: {
|
||||
text: this.$i18n.t('m.Top_10_Teams'),
|
||||
left: 'center',
|
||||
top: 0,
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
filterMode: 'none',
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
saveAsImage: { show: true, title: this.$i18n.t('m.save_as_image') },
|
||||
},
|
||||
right: '0',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
axis: 'x',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
x: 'center',
|
||||
top: '8%',
|
||||
right: 0,
|
||||
data: [],
|
||||
formatter: (value) => {
|
||||
return utils.breakLongWords(value, 16);
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
x: 80,
|
||||
x2: 100,
|
||||
left: '5%', //设置canvas图距左的距离
|
||||
top: '25%',
|
||||
right: '5%',
|
||||
bottom: '10%',
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'time',
|
||||
splitLine: false,
|
||||
axisPointer: {
|
||||
show: true,
|
||||
snap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [0],
|
||||
},
|
||||
],
|
||||
series: [],
|
||||
},
|
||||
JUDGE_STATUS: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.JUDGE_STATUS = Object.assign({}, JUDGE_STATUS);
|
||||
if (!this.trainingProblemList.length) {
|
||||
this.getTrainingProblemList();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.trainingID = this.$route.params.trainingID;
|
||||
this.getTrainingRankData(1);
|
||||
this.addChartCategory(this.trainingProblemList);
|
||||
this.getTrainingRankData();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getTrainingProblems']),
|
||||
getUserTotalSubmit(username) {
|
||||
...mapActions(['getTrainingProblemList']),
|
||||
|
||||
getTrainingRankData() {
|
||||
let data = {
|
||||
tid: this.trainingID,
|
||||
limit: this.limit,
|
||||
currentPage: this.page,
|
||||
};
|
||||
api.getTrainingRank(data).then(
|
||||
(res) => {
|
||||
this.total = res.data.data.total;
|
||||
this.applyToTable(res.data.data.records);
|
||||
},
|
||||
(err) => {}
|
||||
);
|
||||
},
|
||||
|
||||
getUserACSubmit(username) {
|
||||
this.$router.push({
|
||||
name: 'TrainingSubmissionList',
|
||||
query: { username: username },
|
||||
name: 'SubmissionList',
|
||||
query: { username: username, status: 0 },
|
||||
});
|
||||
},
|
||||
getUserHomeByUsername(uid, username) {
|
||||
|
@ -308,113 +240,24 @@ export default {
|
|||
if (column.property === 'username' && row.userCellClassName) {
|
||||
return row.userCellClassName;
|
||||
}
|
||||
|
||||
if (
|
||||
column.property !== 'id' &&
|
||||
column.property !== 'rating' &&
|
||||
column.property !== 'totalTime' &&
|
||||
column.property !== 'username' &&
|
||||
column.property !== 'realname'
|
||||
) {
|
||||
if (this.isTrainingAdmin) {
|
||||
return row.cellClassName[
|
||||
[this.trainingProblemList[columnIndex - 5].displayId]
|
||||
];
|
||||
} else {
|
||||
return row.cellClassName[
|
||||
[this.trainingProblemList[columnIndex - 4].displayId]
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
applyToTable(data) {
|
||||
let dataRank = JSON.parse(JSON.stringify(data));
|
||||
applyToTable(dataRank) {
|
||||
dataRank.forEach((rank, i) => {
|
||||
let info = rank.submissionInfo;
|
||||
let cellClass = {};
|
||||
Object.keys(info).forEach((problemID) => {
|
||||
dataRank[i][problemID] = info[problemID];
|
||||
dataRank[i][problemID].ACTime = time.secondFormat(
|
||||
dataRank[i][problemID].ACTime
|
||||
);
|
||||
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].gender == 'female') {
|
||||
dataRank[i].userCellClassName = 'bg-female';
|
||||
}
|
||||
});
|
||||
this.dataRank = dataRank;
|
||||
},
|
||||
addChartCategory(trainingProblemList) {
|
||||
let category = [];
|
||||
for (let i = 0; i <= trainingProblemList.length; ++i) {
|
||||
category.push(i);
|
||||
}
|
||||
this.options.yAxis[0].data = category;
|
||||
},
|
||||
applyToChart(rankData) {
|
||||
let [users, seriesData] = [[], []];
|
||||
rankData.forEach((rank) => {
|
||||
users.push(rank[this.training.rankShowName]);
|
||||
let info = rank.submissionInfo;
|
||||
// 提取出已AC题目的时间
|
||||
let timeData = [];
|
||||
Object.keys(info).forEach((problemID) => {
|
||||
if (info[problemID].isAC) {
|
||||
timeData.push(info[problemID].ACTime);
|
||||
}
|
||||
});
|
||||
timeData.sort((a, b) => {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
let data = [];
|
||||
data.push([this.training.startTime, 0]);
|
||||
|
||||
for (let [index, value] of timeData.entries()) {
|
||||
let realTime = moment(this.training.startTime)
|
||||
.add(value, 'seconds')
|
||||
.format();
|
||||
data.push([realTime, index + 1]);
|
||||
}
|
||||
seriesData.push({
|
||||
name: rank[this.training.rankShowName],
|
||||
type: 'line',
|
||||
data,
|
||||
});
|
||||
});
|
||||
this.options.legend.data = users;
|
||||
this.options.series = seriesData;
|
||||
},
|
||||
parseTotalTime(totalTime) {
|
||||
return time.secondFormat(totalTime);
|
||||
},
|
||||
downloadRankCSV() {
|
||||
utils.downloadFile(
|
||||
`/api/file/download-training-rank?cid=${
|
||||
this.$route.params.trainingID
|
||||
}&forceRefresh=${this.forceUpdate ? true : false}`
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
trainingProblemList(newVal, OldVal) {
|
||||
if (newVal.length != 0) {
|
||||
this.addChartCategory(this.trainingProblemList);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
trainingProblemList: (state) => state.training.trainingProblemList,
|
||||
}),
|
||||
...mapGetters(['isTrainingAdmin']),
|
||||
training() {
|
||||
return this.$store.state.training.training;
|
||||
},
|
||||
|
@ -425,39 +268,20 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.echarts {
|
||||
margin: 20px auto;
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
.rank-title {
|
||||
margin-bottom: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
/deep/.el-card__body {
|
||||
padding: 20px !important;
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
|
||||
.screen-full {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#switches p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
#switches p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
#switches p span {
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.vxe-cell p,
|
||||
.vxe-cell span {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/deep/.vxe-table .vxe-body--column {
|
||||
line-height: 20px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
/deep/.el-card__body {
|
||||
padding: 0 !important;
|
||||
|
@ -469,6 +293,18 @@ a.emphasis {
|
|||
a.emphasis:hover {
|
||||
color: #2d8cf0 !important;
|
||||
}
|
||||
|
||||
/deep/.vxe-table .vxe-header--column:not(.col--ellipsis) {
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
/deep/.vxe-table .vxe-body--column {
|
||||
padding: 4px 0 !important;
|
||||
line-height: 20px !important;
|
||||
}
|
||||
/deep/.vxe-table .vxe-body--column:not(.col--ellipsis) {
|
||||
line-height: 20px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
/deep/.vxe-body--column {
|
||||
min-width: 0;
|
||||
height: 48px;
|
||||
|
@ -481,4 +317,12 @@ a.emphasis:hover {
|
|||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
.judge-status {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.judge-time {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue