增加比赛打印功能和账号限制功能
This commit is contained in:
parent
971f3f8506
commit
1e4215459e
|
@ -16,5 +16,5 @@ features:
|
|||
details: 判题使用 cgroup 隔离用户程序,网站权限控制完善
|
||||
- title: 多样化
|
||||
details: 独有自身判题服务,同时支持其它知名OJ题目的提交判题
|
||||
footer: MIT Licensed | Copyright © 2021.08.08 @Author Himit_ZH QQ Group:598587305
|
||||
footer: MIT Licensed | Copyright © 2021.09.21 @Author Himit_ZH QQ Group:598587305
|
||||
---
|
|
@ -324,5 +324,79 @@ hoj-frontend:
|
|||
|
||||
## 四、更新最新版本
|
||||
|
||||
> 2021.09.21之后部署hoj的请看下面操作
|
||||
|
||||
请在对应的docker-compose.yml当前文件夹下执行`docker-compose pull`拉取最新镜像,然后重新`docker-compose up -d`即可。
|
||||
|
||||
|
||||
|
||||
> 2021.09.21之前部署hoj的请看下面操作
|
||||
|
||||
###1、修改MySQL8.0默认的密码加密方式
|
||||
|
||||
(1)进行hoj-mysql容器
|
||||
|
||||
```shell
|
||||
docker exec -it hoj-mysql bash
|
||||
```
|
||||
|
||||
(2) 输入对应的mysql密码,进入mysql数据库
|
||||
|
||||
注意:-p 后面跟着数据库密码例如hoj123456
|
||||
|
||||
```shell
|
||||
mysql -uroot -p数据库密码
|
||||
```
|
||||
|
||||
(3)成功进入后,执行以下命令
|
||||
|
||||
```shell
|
||||
mysql> use mysql;
|
||||
|
||||
mysql> grant all PRIVILEGES on *.* to root@'%' WITH GRANT OPTION;
|
||||
|
||||
|
||||
mysql> ALTER user 'root'@'%' IDENTIFIED BY '数据库密码' PASSWORD EXPIRE NEVER;
|
||||
|
||||
|
||||
mysql> ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY '数据库密码';
|
||||
|
||||
|
||||
mysql> FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
(4) 两次exit 退出mysql和容器
|
||||
|
||||
### 2、 添加hoj-mysql-checker模块
|
||||
|
||||
(1)可以选择拉取仓库最新的docker-compose.yml文件(跟部署操作一样)或者访问:
|
||||
|
||||
https://gitee.com/himitzh0730/hoj-deploy/blob/master/standAlone/docker-compose.yml
|
||||
|
||||
(2)或者编辑docker-compose.yml文件,手动添加新模块
|
||||
|
||||
```yaml
|
||||
hoj-mysql-checker:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/hcode/hoj_database_checker
|
||||
container_name: hoj-mysql-checker
|
||||
depends_on:
|
||||
- hoj-mysql
|
||||
links:
|
||||
- hoj-mysql:mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-hoj123456}
|
||||
networks:
|
||||
hoj-network:
|
||||
ipv4_address: 172.20.0.8
|
||||
```
|
||||
|
||||
(3) 保存后重启容器即可
|
||||
|
||||
```shell
|
||||
docker-compose down
|
||||
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
|||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.pojo.entity.UserInfo;
|
||||
|
@ -136,7 +137,8 @@ public class AdminUserController {
|
|||
.setUuid(uuid)
|
||||
.setUsername(user.get(0))
|
||||
.setPassword(SecureUtil.md5(user.get(1)))
|
||||
.setEmail(user.get(2)));
|
||||
.setEmail(StringUtils.isEmpty(user.get(2)) ? null : user.get(2))
|
||||
.setRealname(user.get(3)));
|
||||
userRoleList.add(new UserRole()
|
||||
.setRoleId(1002L)
|
||||
.setUid(uuid));
|
||||
|
|
|
@ -91,6 +91,9 @@ public class FileController {
|
|||
@Autowired
|
||||
private TagServiceImpl tagService;
|
||||
|
||||
@Autowired
|
||||
private ContestPrintServiceImpl contestPrintService;
|
||||
|
||||
|
||||
@RequestMapping("/generate-user-excel")
|
||||
@RequiresAuthentication
|
||||
|
@ -140,7 +143,7 @@ public class FileController {
|
|||
//将文件保存指定目录
|
||||
image.transferTo(FileUtil.file(Constants.File.USER_AVATAR_FOLDER.getPath() + File.separator + filename));
|
||||
} catch (Exception e) {
|
||||
log.error("头像文件上传异常-------------->{}", e.getMessage());
|
||||
log.error("头像文件上传异常-------------->", e);
|
||||
return CommonResult.errorResponse("服务器异常:头像上传失败!", CommonResult.STATUS_ERROR);
|
||||
}
|
||||
|
||||
|
@ -189,7 +192,6 @@ public class FileController {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@RequestMapping(value = "/upload-carouse-img", method = RequestMethod.POST)
|
||||
@RequiresAuthentication
|
||||
@ResponseBody
|
||||
|
@ -440,7 +442,7 @@ public class FileController {
|
|||
.orderByAsc("time");
|
||||
List<ContestRecord> contestRecordList = contestRecordService.list(wrapper);
|
||||
Assert.notEmpty(contestRecordList, "比赛暂无排行榜记录!");
|
||||
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.calcACMRank(isOpenSealRank,contest,contestRecordList);
|
||||
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.calcACMRank(isOpenSealRank, contest, contestRecordList);
|
||||
EasyExcel.write(response.getOutputStream())
|
||||
.head(fileService.getContestRankExcelHead(contestProblemDisplayIDList, true))
|
||||
.sheet("rank")
|
||||
|
@ -575,7 +577,7 @@ public class FileController {
|
|||
// 刷新缓存
|
||||
bouts.flush();
|
||||
} catch (IOException e) {
|
||||
log.error("下载比赛AC提交代码的压缩文件异常------------>{}", e.getMessage());
|
||||
log.error("下载比赛AC提交代码的压缩文件异常------------>", e);
|
||||
response.reset();
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
|
@ -681,7 +683,7 @@ public class FileController {
|
|||
//将文件保存指定目录
|
||||
image.transferTo(FileUtil.file(Constants.File.MARKDOWN_FILE_FOLDER.getPath() + File.separator + filename));
|
||||
} catch (Exception e) {
|
||||
log.error("图片文件上传异常-------------->{}", e.getMessage());
|
||||
log.error("图片文件上传异常-------------->", e);
|
||||
return CommonResult.errorResponse("服务器异常:图片文件上传失败!", CommonResult.STATUS_ERROR);
|
||||
}
|
||||
|
||||
|
@ -721,7 +723,7 @@ public class FileController {
|
|||
//将文件保存指定目录
|
||||
file.transferTo(FileUtil.file(Constants.File.MARKDOWN_FILE_FOLDER.getPath() + File.separator + filename));
|
||||
} catch (Exception e) {
|
||||
log.error("文件上传异常-------------->{}", e.getMessage());
|
||||
log.error("文件上传异常-------------->", e);
|
||||
return CommonResult.errorResponse("服务器异常:文件上传失败!", CommonResult.STATUS_ERROR);
|
||||
}
|
||||
|
||||
|
@ -1242,7 +1244,7 @@ public class FileController {
|
|||
try {
|
||||
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("线程池异常--------------->{}", e.getMessage());
|
||||
log.error("线程池异常--------------->", e);
|
||||
}
|
||||
|
||||
String fileName = "problem_export_" + System.currentTimeMillis() + ".zip";
|
||||
|
@ -1266,7 +1268,7 @@ public class FileController {
|
|||
}
|
||||
bouts.flush();
|
||||
} catch (IOException e) {
|
||||
log.error("导出题目数据的压缩文件异常------------>{}", e.getMessage());
|
||||
log.error("导出题目数据的压缩文件异常------------>", e);
|
||||
response.reset();
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
|
@ -1297,5 +1299,63 @@ public class FileController {
|
|||
}
|
||||
}
|
||||
|
||||
@GetMapping("/download-contest-print-text")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public void downloadContestPrintText(@RequestParam("id") Long id,
|
||||
HttpServletResponse response) {
|
||||
ContestPrint contestPrint = contestPrintService.getById(id);
|
||||
String filename = contestPrint.getUsername() + "_Contest_Print.txt";
|
||||
String filePath = Constants.File.CONTEST_TEXT_PRINT_FOLDER.getPath() + File.separator +filename;
|
||||
if (!FileUtil.exist(filePath)) {
|
||||
|
||||
FileWriter fileWriter = new FileWriter(filePath);
|
||||
fileWriter.write(contestPrint.getContent());
|
||||
}
|
||||
|
||||
FileReader zipFileReader = new FileReader(filePath);
|
||||
BufferedInputStream bins = new BufferedInputStream(zipFileReader.getInputStream());//放到缓冲流里面
|
||||
OutputStream outs = null;//获取文件输出IO流
|
||||
BufferedOutputStream bouts = null;
|
||||
try {
|
||||
outs = response.getOutputStream();
|
||||
bouts = new BufferedOutputStream(outs);
|
||||
response.setContentType("application/x-download");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||
int bytesRead = 0;
|
||||
byte[] buffer = new byte[1024 * 10];
|
||||
//开始向网络传输文件流
|
||||
while ((bytesRead = bins.read(buffer, 0, 1024 * 10)) != -1) {
|
||||
bouts.write(buffer, 0, bytesRead);
|
||||
}
|
||||
// 刷新缓存
|
||||
bouts.flush();
|
||||
} catch (IOException e) {
|
||||
log.error("下载比赛打印文本文件异常------------>", e);
|
||||
response.reset();
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("status", CommonResult.STATUS_ERROR);
|
||||
map.put("msg", "下载文件失败,请重新尝试!");
|
||||
map.put("data", null);
|
||||
try {
|
||||
response.getWriter().println(JSONUtil.toJsonStr(map));
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
bins.close();
|
||||
if (outs != null) {
|
||||
outs.close();
|
||||
}
|
||||
if (bouts != null) {
|
||||
bouts.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -576,8 +576,8 @@ public class AccountController {
|
|||
|
||||
String realname = (String) params.get("realname");
|
||||
String nickname = (String) params.get("nickname");
|
||||
if (!StringUtils.isEmpty(realname) && realname.length() > 10) {
|
||||
return CommonResult.errorResponse("真实姓名不能超过10位");
|
||||
if (!StringUtils.isEmpty(realname) && realname.length() > 50) {
|
||||
return CommonResult.errorResponse("真实姓名不能超过50位");
|
||||
}
|
||||
if (!StringUtils.isEmpty(nickname) && nickname.length() > 20) {
|
||||
return CommonResult.errorResponse("昵称不能超过20位");
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
package top.hcode.hoj.controller.oj;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.pojo.dto.CheckACDto;
|
||||
import top.hcode.hoj.pojo.entity.Contest;
|
||||
import top.hcode.hoj.pojo.entity.ContestPrint;
|
||||
import top.hcode.hoj.pojo.entity.ContestRecord;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
import top.hcode.hoj.service.ContestRecordService;
|
||||
import top.hcode.hoj.service.impl.ContestPrintServiceImpl;
|
||||
import top.hcode.hoj.service.impl.ContestServiceImpl;
|
||||
import top.hcode.hoj.utils.Constants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/20 13:15
|
||||
* @Description: 处理比赛管理模块的相关数据请求
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ContestAdminController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private ContestServiceImpl contestService;
|
||||
|
||||
@Autowired
|
||||
private ContestRecordService contestRecordService;
|
||||
|
||||
@Autowired
|
||||
private ContestPrintServiceImpl contestPrintService;
|
||||
|
||||
/**
|
||||
* @MethodName getContestACInfo
|
||||
* @Params * @param null
|
||||
* @Description 获取各个用户的ac情况,仅限于比赛管理者可查看
|
||||
* @Return
|
||||
* @Since 2021/1/17
|
||||
*/
|
||||
@GetMapping("/get-contest-ac-info")
|
||||
@RequiresAuthentication
|
||||
public CommonResult getContestACInfo(@RequestParam("cid") Long cid,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "limit", required = false) Integer limit,
|
||||
HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权查看!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if (currentPage == null || currentPage < 1) currentPage = 1;
|
||||
if (limit == null || limit < 1) limit = 30;
|
||||
|
||||
// 获取当前比赛的,状态为ac,未被校验的排在签名
|
||||
IPage<ContestRecord> contestRecords = contestRecordService.getACInfo(currentPage,
|
||||
limit, Constants.Contest.RECORD_AC.getCode(), cid, contest.getUid());
|
||||
|
||||
return CommonResult.successResponse(contestRecords, "查询成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @MethodName checkContestACInfo
|
||||
* @Params * @param null
|
||||
* @Description 比赛管理员确定该次提交的ac情况
|
||||
* @Return
|
||||
* @Since 2021/1/17
|
||||
*/
|
||||
@PutMapping("/check-contest-ac-info")
|
||||
@RequiresAuthentication
|
||||
public CommonResult checkContestACInfo(@RequestBody CheckACDto checkACDto,
|
||||
HttpServletRequest request) {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(checkACDto.getCid());
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = contestRecordService.updateById(
|
||||
new ContestRecord().setChecked(checkACDto.getChecked()).setId(checkACDto.getId()));
|
||||
|
||||
if (result) {
|
||||
return CommonResult.successResponse(null, "修改校验确定成功!");
|
||||
} else {
|
||||
return CommonResult.errorResponse("修改校验确定失败!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/get-contest-print")
|
||||
@RequiresAuthentication
|
||||
public CommonResult getContestPrint(@RequestParam("cid") Long cid,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "limit", required = false) Integer limit,
|
||||
HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权查看!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if (currentPage == null || currentPage < 1) currentPage = 1;
|
||||
if (limit == null || limit < 1) limit = 30;
|
||||
|
||||
// 获取当前比赛的,未被确定的排在签名
|
||||
|
||||
IPage<ContestPrint> contestPrintIPage = new Page<>(currentPage,limit);
|
||||
|
||||
QueryWrapper<ContestPrint> contestPrintQueryWrapper = new QueryWrapper<>();
|
||||
contestPrintQueryWrapper.select("id","cid","username","realname","status","gmt_create")
|
||||
.eq("cid", cid)
|
||||
.orderByAsc("status")
|
||||
.orderByDesc("gmt_create");
|
||||
|
||||
IPage<ContestPrint> contestPrintList = contestPrintService.page(contestPrintIPage, contestPrintQueryWrapper);
|
||||
|
||||
return CommonResult.successResponse(contestPrintList, "查询成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* @param cid
|
||||
* @param request
|
||||
* @MethodName checkContestStatus
|
||||
* @Description 更新该打印为确定状态
|
||||
* @Return
|
||||
* @Since 2021/9/20
|
||||
*/
|
||||
@PutMapping("/check-contest-print-status")
|
||||
@RequiresAuthentication
|
||||
public CommonResult checkContestStatus(@RequestParam("id") Long id,
|
||||
@RequestParam("cid") Long cid,
|
||||
HttpServletRequest request) {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = contestPrintService.updateById(new ContestPrint().setId(id).setStatus(1));
|
||||
|
||||
if (result) {
|
||||
return CommonResult.successResponse(null, "确定成功!");
|
||||
} else {
|
||||
return CommonResult.errorResponse("确定失败!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package top.hcode.hoj.controller.oj;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
@ -8,9 +9,11 @@ import org.apache.shiro.SecurityUtils;
|
|||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.pojo.dto.CheckACDto;
|
||||
import top.hcode.hoj.pojo.dto.ContestPrintDto;
|
||||
import top.hcode.hoj.pojo.dto.UserReadContestAnnouncementDto;
|
||||
import top.hcode.hoj.pojo.entity.*;
|
||||
import top.hcode.hoj.pojo.vo.*;
|
||||
|
@ -72,6 +75,9 @@ public class ContestController {
|
|||
@Autowired
|
||||
private CodeTemplateServiceImpl codeTemplateService;
|
||||
|
||||
@Autowired
|
||||
private ContestPrintServiceImpl contestPrintService;
|
||||
|
||||
/**
|
||||
* @MethodName getContestList
|
||||
* @Params * @param null
|
||||
|
@ -152,6 +158,19 @@ public class ContestController {
|
|||
return CommonResult.errorResponse("比赛密码错误!");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 需要校验当前比赛是否开启账号规则限制,如果有,需要对当前用户的用户名进行验证
|
||||
*
|
||||
*/
|
||||
|
||||
if (contest.getOpenAccountLimit()
|
||||
&&!contestService.checkAccountRule(contest.getAccountLimitRule(),userRolesVo.getUsername())){
|
||||
return CommonResult.errorResponse("对不起!本次比赛只允许特定账号规则的用户参赛!",CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QueryWrapper<ContestRegister> wrapper = new QueryWrapper<ContestRegister>().eq("cid", Long.valueOf(cidStr))
|
||||
.eq("uid", userRolesVo.getUid());
|
||||
if (contestRegisterService.getOne(wrapper) != null) {
|
||||
|
@ -415,7 +434,7 @@ public class ContestController {
|
|||
searchStatus, searchUsername, uid, beforeContestSubmit, rule, contest.getStartTime(), sealRankTime, userRolesVo.getUid());
|
||||
|
||||
if (commonJudgeList.getTotal() == 0) { // 未查询到一条数据
|
||||
return CommonResult.successResponse(null, "暂无数据");
|
||||
return CommonResult.successResponse(commonJudgeList, "暂无数据");
|
||||
} else {
|
||||
|
||||
// 比赛还是进行阶段,同时不是超级管理员与比赛管理员,需要将除自己之外的提交的时间、空间、长度隐藏
|
||||
|
@ -578,75 +597,47 @@ public class ContestController {
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @MethodName getContestACInfo
|
||||
* @Params * @param null
|
||||
* @Description 获取各个用户的ac情况,仅限于比赛管理者可查看
|
||||
* @param contestPrintDto
|
||||
* @param request
|
||||
* @MethodName submitPrintText
|
||||
* @Description 提交比赛文本打印内容
|
||||
* @Return
|
||||
* @Since 2021/1/17
|
||||
* @Since 2021/9/20
|
||||
*/
|
||||
@GetMapping("/get-contest-ac-info")
|
||||
@PostMapping("/submit-print-text")
|
||||
@RequiresAuthentication
|
||||
public CommonResult getContestACInfo(@RequestParam("cid") Long cid,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "limit", required = false) Integer limit,
|
||||
HttpServletRequest request) {
|
||||
public CommonResult submitPrintText(@RequestBody ContestPrintDto contestPrintDto,
|
||||
HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
Contest contest = contestService.getById(contestPrintDto.getCid());
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权查看!", CommonResult.STATUS_FORBIDDEN);
|
||||
/**
|
||||
* 需要对该比赛做判断,是否处于开始或结束状态才可以提交打印内容,同时若是私有赛需要判断是否已注册(比赛管理员包括超级管理员可以直接获取)
|
||||
*/
|
||||
CommonResult commonResult = contestService.checkContestAuth(contest, userRolesVo, isRoot);
|
||||
|
||||
if (commonResult != null) {
|
||||
return commonResult;
|
||||
}
|
||||
|
||||
if (currentPage == null || currentPage < 1) currentPage = 1;
|
||||
if (limit == null || limit < 1) limit = 30;
|
||||
|
||||
// 获取当前比赛的,状态为ac,未被校验的排在签名
|
||||
IPage<ContestRecord> contestRecords = contestRecordService.getACInfo(currentPage,
|
||||
limit, Constants.Contest.RECORD_AC.getCode(), cid, contest.getUid());
|
||||
|
||||
return CommonResult.successResponse(contestRecords, "查询成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @MethodName checkContestACInfo
|
||||
* @Params * @param null
|
||||
* @Description 比赛管理员确定该次提交的ac情况
|
||||
* @Return
|
||||
* @Since 2021/1/17
|
||||
*/
|
||||
@PutMapping("/check-contest-ac-info")
|
||||
@RequiresAuthentication
|
||||
public CommonResult checkContestACInfo(@RequestBody CheckACDto checkACDto,
|
||||
HttpServletRequest request) {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(checkACDto.getCid());
|
||||
|
||||
// 超级管理员或者该比赛的创建者,则为比赛管理者
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = contestRecordService.updateById(
|
||||
new ContestRecord().setChecked(checkACDto.getChecked()).setId(checkACDto.getId()));
|
||||
boolean result = contestPrintService.saveOrUpdate(new ContestPrint().setCid(contestPrintDto.getCid())
|
||||
.setContent(contestPrintDto.getContent())
|
||||
.setUsername(userRolesVo.getUsername())
|
||||
.setRealname(userRolesVo.getRealname()));
|
||||
|
||||
if (result) {
|
||||
return CommonResult.successResponse(null, "修改校验确定成功!");
|
||||
return CommonResult.successResponse(null, "提交成功,请等待工作人员打印!");
|
||||
} else {
|
||||
return CommonResult.errorResponse("修改校验确定失败!");
|
||||
return CommonResult.errorResponse("提交失败!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -53,9 +53,10 @@ public class DiscussionController {
|
|||
@RequestParam(value = "currentPage", required = false, defaultValue = "1") Integer currentPage,
|
||||
@RequestParam(value = "cid", required = false) Integer categoryId,
|
||||
@RequestParam(value = "pid", required = false) String pid,
|
||||
@RequestParam(value = "onlyMine", required = false,defaultValue = "false") Boolean onlyMine,
|
||||
@RequestParam(value = "onlyMine", required = false, defaultValue = "false") Boolean onlyMine,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
HttpServletRequest request){
|
||||
@RequestParam(value = "admin", defaultValue = "false") Boolean admin,
|
||||
HttpServletRequest request) {
|
||||
|
||||
QueryWrapper<Discussion> discussionQueryWrapper = new QueryWrapper<>();
|
||||
|
||||
|
@ -79,15 +80,18 @@ public class DiscussionController {
|
|||
discussionQueryWrapper.eq("pid", pid);
|
||||
}
|
||||
|
||||
|
||||
boolean isAdmin = SecurityUtils.getSubject().hasRole("root")
|
||||
|| SecurityUtils.getSubject().hasRole("problem_admin")
|
||||
|| SecurityUtils.getSubject().hasRole("admin");
|
||||
discussionQueryWrapper
|
||||
.eq("status", 0)
|
||||
.eq(!(admin && isAdmin), "status", 0)
|
||||
.orderByDesc("top_priority")
|
||||
.orderByDesc("gmt_modified")
|
||||
.orderByDesc("like_num")
|
||||
.orderByDesc("view_num");
|
||||
|
||||
if (onlyMine){
|
||||
// 获取当前登录的用户
|
||||
if (onlyMine) {
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
discussionQueryWrapper.eq("uid", userRolesVo.getUid());
|
||||
|
@ -122,7 +126,7 @@ public class DiscussionController {
|
|||
return CommonResult.errorResponse("对不起,该讨论不存在!", CommonResult.STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (discussion.getStatus() == 2) {
|
||||
if (discussion.getStatus() == 1) {
|
||||
return CommonResult.errorResponse("对不起,该讨论已被封禁!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
|
@ -167,11 +171,11 @@ public class DiscussionController {
|
|||
@PutMapping("/discussion")
|
||||
@RequiresPermissions("discussion_edit")
|
||||
@RequiresAuthentication
|
||||
public CommonResult updateDiscussion(@RequestBody Discussion discussion) {
|
||||
public CommonResult updateDiscussion(@RequestBody Discussion discussion) {
|
||||
boolean isOk = discussionService.updateById(discussion);
|
||||
if (isOk){
|
||||
if (isOk) {
|
||||
return CommonResult.successResponse(null, "修改成功");
|
||||
}else{
|
||||
} else {
|
||||
return CommonResult.errorResponse("修改失败");
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +192,7 @@ public class DiscussionController {
|
|||
// 如果不是是管理员,则需要附加当前用户的uid条件
|
||||
if (!SecurityUtils.getSubject().hasRole("root")
|
||||
&& !SecurityUtils.getSubject().hasRole("admin")
|
||||
&&!SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
&& !SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
discussionUpdateWrapper.eq("uid", userRolesVo.getUid());
|
||||
}
|
||||
boolean isOk = discussionService.remove(discussionUpdateWrapper);
|
||||
|
@ -252,14 +256,14 @@ public class DiscussionController {
|
|||
|
||||
/**
|
||||
* @MethodName addDiscussionReport
|
||||
* @Params * @param uid content reporter
|
||||
* @Params * @param uid content reporter
|
||||
* @Description 添加讨论举报
|
||||
* @Return
|
||||
* @Since 2021/5/11
|
||||
*/
|
||||
@PostMapping("/discussion-report")
|
||||
@RequiresAuthentication
|
||||
public CommonResult addDiscussionReport(@RequestBody DiscussionReport discussionReport){
|
||||
public CommonResult addDiscussionReport(@RequestBody DiscussionReport discussionReport) {
|
||||
boolean isOk = discussionReportService.saveOrUpdate(discussionReport);
|
||||
if (isOk) {
|
||||
return CommonResult.successResponse(null, "举报成功");
|
||||
|
|
|
@ -465,7 +465,7 @@ public class JudgeController {
|
|||
|
||||
|
||||
if (commonJudgeList.getTotal() == 0) { // 未查询到一条数据
|
||||
return CommonResult.successResponse(null, "暂无数据");
|
||||
return CommonResult.successResponse(commonJudgeList, "暂无数据");
|
||||
} else {
|
||||
return CommonResult.successResponse(commonJudgeList, "获取成功");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package top.hcode.hoj.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import top.hcode.hoj.pojo.entity.ContestPrint;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/19 21:04
|
||||
* @Description:
|
||||
*/
|
||||
@Mapper
|
||||
@Repository
|
||||
public interface ContestPrintMapper extends BaseMapper<ContestPrint> {
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
order by c.status ASC, c.start_time DESC
|
||||
</select>
|
||||
<select id="getContestInfoById" resultType="top.hcode.hoj.pojo.vo.ContestVo" useCache="true">
|
||||
select c.id,c.author,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
|
||||
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
|
||||
from contest c where c.id = #{cid} and c.visible=true
|
||||
</select>
|
||||
<select id="getWithinNext14DaysContests" resultType="top.hcode.hoj.pojo.vo.ContestVo">
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package top.hcode.hoj.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/20 13:00
|
||||
* @Description:
|
||||
*/
|
||||
@Data
|
||||
public class ContestPrintDto {
|
||||
|
||||
@NotBlank(message = "比赛id不能为空")
|
||||
private Long cid;
|
||||
|
||||
@NotBlank(message = "打印内容不能为空")
|
||||
private String content;
|
||||
}
|
|
@ -57,6 +57,9 @@ public class ContestVo implements Serializable {
|
|||
@ApiModelProperty(value = "是否开启封榜")
|
||||
private Boolean sealRank;
|
||||
|
||||
@ApiModelProperty(value = "是否打开打印功能")
|
||||
private Boolean openPrint;
|
||||
|
||||
@ApiModelProperty(value = "封榜起始时间,一直到比赛结束,不刷新榜单")
|
||||
private Date sealRankTime;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package top.hcode.hoj.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import top.hcode.hoj.pojo.entity.ContestPrint;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/19 21:05
|
||||
* @Description:
|
||||
*/
|
||||
public interface ContestPrintService extends IService<ContestPrint> {
|
||||
}
|
|
@ -26,4 +26,6 @@ public interface ContestService extends IService<Contest> {
|
|||
Boolean isSealRank(String uid, Contest contest, Boolean forceRefresh, Boolean isRoot);
|
||||
|
||||
CommonResult checkJudgeAuth(Contest contest, String uid);
|
||||
|
||||
boolean checkAccountRule(String accountRule, String username);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ public interface ScheduleService {
|
|||
|
||||
void deleteTestCase();
|
||||
|
||||
void deleteContestPrintText();
|
||||
|
||||
void getOjContestsList();
|
||||
|
||||
void getCodeforcesRating();
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package top.hcode.hoj.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import top.hcode.hoj.dao.ContestPrintMapper;
|
||||
import top.hcode.hoj.pojo.entity.ContestPrint;
|
||||
import top.hcode.hoj.service.ContestPrintService;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/19 21:05
|
||||
* @Description:
|
||||
*/
|
||||
@Service
|
||||
public class ContestPrintServiceImpl extends ServiceImpl<ContestPrintMapper, ContestPrint> implements ContestPrintService {
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
package top.hcode.hoj.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
import top.hcode.hoj.pojo.entity.ContestRegister;
|
||||
import top.hcode.hoj.pojo.vo.ContestVo;
|
||||
|
@ -14,7 +16,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||
import org.springframework.stereotype.Service;
|
||||
import top.hcode.hoj.utils.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -65,6 +69,11 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
|
|||
|
||||
if (!isRoot && !contest.getUid().equals(userRolesVo.getUid())) { // 若不是比赛管理者
|
||||
|
||||
if (contest.getOpenAccountLimit()
|
||||
&&!checkAccountRule(contest.getAccountLimitRule(),userRolesVo.getUsername())){
|
||||
return CommonResult.errorResponse("对不起!本次比赛只允许特定账号规则的用户参赛!",CommonResult.STATUS_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
// 判断一下比赛的状态,还未开始不能查看题目。
|
||||
if (contest.getStatus().intValue() != Constants.Contest.STATUS_RUNNING.getCode() &&
|
||||
contest.getStatus().intValue() != Constants.Contest.STATUS_ENDED.getCode()) {
|
||||
|
@ -113,4 +122,28 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAccountRule(String accountRule, String username) {
|
||||
|
||||
String prefix = ReUtil.get("<prefix>([\\s\\S]*?)</prefix>",
|
||||
accountRule, 1);
|
||||
String suffix = ReUtil.get("<suffix>([\\s\\S]*?)</suffix>",
|
||||
accountRule, 1);
|
||||
String start = ReUtil.get("<start>([\\s\\S]*?)</start>",
|
||||
accountRule, 1);
|
||||
String end = ReUtil.get("<end>([\\s\\S]*?)</end>",
|
||||
accountRule, 1);
|
||||
|
||||
int startNum = Integer.parseInt(start);
|
||||
int endNum = Integer.parseInt(end);
|
||||
|
||||
for (int i = startNum; i <= endNum; i++) {
|
||||
if (username.equals(prefix + i + suffix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -436,8 +436,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
result.set("isSpj", isSpj);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", problemCaseList.size());
|
||||
result.set("testCases", new JSONArray());
|
||||
|
||||
JSONArray testCaseList = new JSONArray(problemCaseList.size());
|
||||
|
||||
for (ProblemCase problemCase : problemCaseList) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
|
@ -469,9 +469,11 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
jsonObject.set("EOFStrippedOutputMd5", DigestUtils.md5DigestAsHex(rtrim(output).getBytes()));
|
||||
}
|
||||
|
||||
((JSONArray) result.get("testCases")).put(jsonObject);
|
||||
testCaseList.add(jsonObject);
|
||||
}
|
||||
|
||||
result.set("testCases", testCaseList);
|
||||
|
||||
FileWriter infoFile = new FileWriter(testCasesDir + "/info", CharsetUtil.UTF_8);
|
||||
// 写入记录文件
|
||||
infoFile.write(JSONUtil.toJsonStr(result));
|
||||
|
@ -491,7 +493,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
result.set("isSpj", isSpj);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", problemCaseList.size());
|
||||
result.set("testCases", new JSONArray());
|
||||
|
||||
JSONArray testCaseList = new JSONArray(problemCaseList.size());
|
||||
|
||||
String testCasesDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + problemId;
|
||||
FileUtil.del(testCasesDir);
|
||||
|
@ -530,9 +533,11 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
|
|||
jsonObject.set("EOFStrippedOutputMd5", DigestUtils.md5DigestAsHex(rtrim(outputData).getBytes()));
|
||||
}
|
||||
|
||||
((JSONArray) result.get("testCases")).put(index, jsonObject);
|
||||
testCaseList.add(jsonObject);
|
||||
}
|
||||
|
||||
result.set("testCases", testCaseList);
|
||||
|
||||
FileWriter infoFile = new FileWriter(testCasesDir + "/info", CharsetUtil.UTF_8);
|
||||
// 写入记录文件
|
||||
infoFile.write(JSONUtil.toJsonStr(result));
|
||||
|
|
|
@ -125,6 +125,21 @@ public class ScheduleServiceImpl implements ScheduleService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @MethodName deleteContestPrintText
|
||||
* @Params * @param null
|
||||
* @Description 每天4点定时删除本地的比赛打印数据
|
||||
* @Return
|
||||
* @Since 2021/9/19
|
||||
*/
|
||||
@Scheduled(cron = "0 0 4 * * *")
|
||||
@Override
|
||||
public void deleteContestPrintText() {
|
||||
boolean result = FileUtil.del(Constants.File.CONTEST_TEXT_PRINT_FOLDER.getPath());
|
||||
if (!result) {
|
||||
log.error("每日定时任务异常------------------------>{}", "清除本地的比赛打印数据失败!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每两小时获取其他OJ的比赛列表,并保存在redis里
|
||||
|
|
|
@ -162,6 +162,8 @@ public class Constants {
|
|||
|
||||
MARKDOWN_FILE_FOLDER("/hoj/file/md"),
|
||||
|
||||
CONTEST_TEXT_PRINT_FOLDER("/hoj/file/contest_print"),
|
||||
|
||||
IMG_API("/api/public/img/"),
|
||||
|
||||
FILE_API("/api/public/file/"),
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
<fileNamePattern>${logging.path}/hoj.info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!--保存时长-->
|
||||
<MaxHistory>15</MaxHistory>
|
||||
<!--单个文件最大-->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--总大小-->
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
@ -53,8 +51,6 @@
|
|||
<fileNamePattern>${logging.path}/hoj.error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!--保存时长-->
|
||||
<MaxHistory>15</MaxHistory>
|
||||
<!--单个文件最大-->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--总大小-->
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
|
|
@ -47,7 +47,8 @@ public class ProblemTestCaseUtils {
|
|||
result.set("isSpj", isSpj);
|
||||
result.set("version", version);
|
||||
result.set("testCasesSize", testCases.size());
|
||||
result.set("testCases", new JSONArray());
|
||||
|
||||
JSONArray testCaseList = new JSONArray(testCases.size());
|
||||
|
||||
String testCasesDir = Constants.JudgeDir.TEST_CASE_DIR.getContent() + "/problem_" + problemId;
|
||||
|
||||
|
@ -84,9 +85,11 @@ public class ProblemTestCaseUtils {
|
|||
jsonObject.set("EOFStrippedOutputMd5", DigestUtils.md5DigestAsHex(rtrim(outputData).getBytes()));
|
||||
}
|
||||
|
||||
((JSONArray) result.get("testCases")).put(index, jsonObject);
|
||||
testCaseList.add(jsonObject);
|
||||
}
|
||||
|
||||
result.set("testCases",testCaseList);
|
||||
|
||||
FileWriter infoFile = new FileWriter(testCasesDir + File.separator + "info", CharsetUtil.UTF_8);
|
||||
// 写入记录文件
|
||||
infoFile.write(JSONUtil.toJsonStr(result));
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
<fileNamePattern>${logging.path}/hoj.info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!--保存时长-->
|
||||
<MaxHistory>15</MaxHistory>
|
||||
<!--单个文件最大-->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--总大小-->
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
@ -53,8 +51,6 @@
|
|||
<fileNamePattern>${logging.path}/hoj.error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!--保存时长-->
|
||||
<MaxHistory>15</MaxHistory>
|
||||
<!--单个文件最大-->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--总大小-->
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
|
|
@ -78,6 +78,15 @@ public class Contest implements Serializable {
|
|||
@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>")
|
||||
private String accountLimitRule;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date gmtCreate;
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package top.hcode.hoj.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author: Himit_ZH
|
||||
* @Date: 2021/9/19 21:00
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@ApiModel(value="ContestPrint", description="")
|
||||
public class ContestPrint {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long cid;
|
||||
|
||||
@ApiModelProperty(value = "提交打印文本的用户")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty(value = "真实姓名")
|
||||
private String realname;
|
||||
|
||||
@ApiModelProperty(value = "内容")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty(value = "状态")
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date gmtCreate;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date gmtModified;
|
||||
}
|
|
@ -169,9 +169,8 @@ export default {
|
|||
}
|
||||
body {
|
||||
background-color: #eff3f5 !important;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
|
||||
Microsoft YaHei, Arial, sans-serif !important;
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
|
||||
'Microsoft YaHei', '微软雅黑', Arial, sans-serif !important;
|
||||
color: #495060 !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
@ -181,6 +180,28 @@ pre,
|
|||
samp {
|
||||
font-family: Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
display: block;
|
||||
min-height: 12px;
|
||||
min-width: 10px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(217, 217, 217);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
display: block;
|
||||
min-height: 12px;
|
||||
min-width: 10px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(159, 159, 159);
|
||||
}
|
||||
|
||||
#admin-content {
|
||||
background-color: #1e9fff;
|
||||
position: absolute;
|
||||
|
|
|
@ -405,6 +405,29 @@ const ojApi = {
|
|||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 提交打印文本
|
||||
submitPrintText(data){
|
||||
return ajax('/api/submit-print-text', 'post', {
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取比赛打印文本列表
|
||||
getContestPrintList(params){
|
||||
return ajax('/api/get-contest-print', 'get', {
|
||||
params
|
||||
})
|
||||
},
|
||||
|
||||
// 更新比赛打印的状态
|
||||
updateContestPrintStatus(params){
|
||||
return ajax('/api/check-contest-print-status', 'put', {
|
||||
params
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
// 比赛题目对应的提交重判
|
||||
ContestRejudgeProblem(params){
|
||||
return ajax('/api/admin/judge/rejudge-contest-problem', 'get', {
|
||||
|
|
|
@ -110,7 +110,7 @@ function getLanguages (all=true) {
|
|||
}
|
||||
|
||||
function stringToExamples(value){
|
||||
let reg = "<input>([\\s\\S]+?)</input><output>([\\s\\S]+?)</output>";
|
||||
let reg = "<input>([\\s\\S]*?)</input><output>([\\s\\S]*?)</output>";
|
||||
let re = RegExp(reg,"g");
|
||||
let objList = []
|
||||
let tmp;
|
||||
|
|
|
@ -290,7 +290,7 @@ export default {
|
|||
|
||||
<style>
|
||||
.CodeMirror {
|
||||
height: 545px !important;
|
||||
height: 600px !important;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
min-height: 549px;
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
<el-row :gutter="30" justify="space-around">
|
||||
<el-col :md="10" :xs="24">
|
||||
<el-form-item :label="$t('m.RealName')">
|
||||
<el-input v-model="formProfile.realname" :maxlength="10" />
|
||||
<el-input v-model="formProfile.realname" :maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('m.Nickname')">
|
||||
<el-input v-model="formProfile.nickname" :maxlength="20" />
|
||||
|
@ -140,7 +140,7 @@
|
|||
<el-input v-model="formProfile.school" :maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('m.Student_Number')">
|
||||
<el-input v-model="formProfile.number" :maxlength="25" />
|
||||
<el-input v-model="formProfile.number" :maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="4" :lg="4">
|
||||
|
|
|
@ -62,8 +62,8 @@ export const m = {
|
|||
Delete_User:'Delete User',
|
||||
Import_User: 'Import User',
|
||||
Import_User_Tips1:'The imported user data only supports user data in CSV format.',
|
||||
Import_User_Tips2:'There are three columns of data: user name, password, and mailbox. Any column cannot be empty, otherwise the data in this row may fail to be imported.',
|
||||
Import_User_Tips3:'The first line does not need to write the three column names ("username", "password", "email").',
|
||||
Import_User_Tips2:'There are three columns of data: username, password, email, and realname. The username and password cannot be empty, email and realname can be enmpty, otherwise the data in this row may fail to be imported.',
|
||||
Import_User_Tips3:'The first line does not need to write the three column names ("username", "password", "email","realname").',
|
||||
Import_User_Tips4:'Please import the file saved as UTF-8 code, otherwise Chinese may be garbled.',
|
||||
Choose_File:'Choose File',
|
||||
Password: 'Password',
|
||||
|
@ -88,7 +88,7 @@ export const m = {
|
|||
The_number_of_users_selected_cannot_be_empty:'The number of users selected cannot be empty',
|
||||
Error_Please_check_your_choice:'Wrong, please check your choice.',
|
||||
Generate_User_Success:'All users in the specified format have been created successfully, and the user table has been downloaded to your computer successfully!',
|
||||
Generate_Skipped_Reason:'rows user data are filtered because it may be an empty row or a column value is empty.',
|
||||
Generate_Skipped_Reason:'rows user data are filtered because it may be an empty row or a column(username or password) value is empty.',
|
||||
Upload_Users_Successfully:'Upload Users Successfully',
|
||||
|
||||
// /views/admin/general/Announcement.vue
|
||||
|
@ -222,7 +222,6 @@ export const m = {
|
|||
View_Contest_Announcement_List:'View Contest Announcement List',
|
||||
Download_Contest_AC_Submission:'Download Contest AC Submissions',
|
||||
Exclude_admin_submissions:'Exclude admin submissions',
|
||||
|
||||
Delete_Contest_Tips:'This operation will delete the contest and its submission, discussion, announcement, record and other data. Do you want to continue?',
|
||||
|
||||
// /views/admin/contest/Contest.vue
|
||||
|
@ -244,6 +243,11 @@ export const m = {
|
|||
Create_Contest:'Create Contest',
|
||||
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',
|
||||
Not_Support_Print:'Not Support Print',
|
||||
Support_Offline_Print:'Support Offline Print',
|
||||
Account_Limit:'Account Limit',
|
||||
The_allowed_account_will_be:'The allowed username will be ',
|
||||
|
||||
// /views/admin/discussion/Discussion.vue
|
||||
Discussion_ID:'Discussion ID',
|
||||
|
|
|
@ -62,8 +62,8 @@ export const m = {
|
|||
Delete_User:'删除用户',
|
||||
Import_User: '导入用户',
|
||||
Import_User_Tips1:'用户数据导入仅支持csv格式的用户数据。',
|
||||
Import_User_Tips2:'共三列数据:用户名,密码,邮箱,任一列不能为空,否则该行数据可能导入失败。',
|
||||
Import_User_Tips3:'第一行不必写(“用户名”,“密码”,“邮箱”)这三个列名',
|
||||
Import_User_Tips2:'共三列数据:用户名和密码不能为空,邮箱和真实姓名可选填,否则该行数据可能导入失败。',
|
||||
Import_User_Tips3:'第一行不必写(“用户名”,“密码”,“邮箱”,"真实姓名")这三个列名',
|
||||
Import_User_Tips4:'请导入保存为UTF-8编码的文件,否则中文可能会乱码。',
|
||||
Choose_File:'选择文件',
|
||||
Password: '密码',
|
||||
|
@ -88,7 +88,7 @@ export const m = {
|
|||
The_number_of_users_selected_cannot_be_empty:'选择的用户不能为空',
|
||||
Error_Please_check_your_choice:'错误,请检查你的输入或选择是否准确',
|
||||
Generate_User_Success:'所有用户已经被成功创建, 用户的列表数据文件将下载到你的电脑里',
|
||||
Generate_Skipped_Reason:'行用户数据被过滤,原因是可能为空行或某个列值为空',
|
||||
Generate_Skipped_Reason:'行用户数据被过滤,原因是可能为空行或某个列值(用户名或密码)为空',
|
||||
Upload_Users_Successfully:'上传用户成功',
|
||||
|
||||
// /views/admin/general/Announcement.vue
|
||||
|
@ -241,6 +241,11 @@ export const m = {
|
|||
Create_Contest:'创建比赛',
|
||||
Contest_Duration_Check:'比赛时长不能小于0',
|
||||
Contets_Time_Check:'开始时间应该早于结束时间',
|
||||
Print_Func:'打印功能',
|
||||
Not_Support_Print:'不支持打印',
|
||||
Support_Offline_Print:'支持线下打印',
|
||||
Account_Limit:'账号限制',
|
||||
The_allowed_account_will_be:'允许参加比赛的用户名是:',
|
||||
|
||||
// /views/admin/discussion/Discussion.vue
|
||||
Discussion_ID:'讨论ID',
|
||||
|
|
|
@ -164,6 +164,9 @@ export const m = {
|
|||
Good_luck_to_you:'Good luck to you!',
|
||||
|
||||
// /views/oj/problem/Problem.vue
|
||||
Problem_Description:'Problem Description',
|
||||
My_Submission:'My Submission',
|
||||
Login_to_view_your_submission_history:'Login to view your submission history',
|
||||
Shrink_Sidebar:'Shrink Sidebar',
|
||||
View_Problem_Content:'View Problem Content',
|
||||
Only_View_Problem:'Only View Problem',
|
||||
|
@ -172,7 +175,7 @@ export const m = {
|
|||
Show_Tags:'Show tags',
|
||||
No_tag:'No tag',
|
||||
Statistic: 'Statistic',
|
||||
Solution:'Solution',
|
||||
Solutions:'Solutions',
|
||||
Problem_Discussion:'Discussion',
|
||||
Description: 'Description',
|
||||
Input: 'Input',
|
||||
|
@ -311,6 +314,8 @@ export const m = {
|
|||
Submissions: 'Submissions',
|
||||
Rankings: 'Rankings',
|
||||
Comment:'Comment',
|
||||
Print:'Print',
|
||||
Admin_Print:'Admin Print',
|
||||
Admin_Helper: 'AC Info',
|
||||
Register_contest_successfully:'Register contest successfully',
|
||||
Please_check_the_contest_announcement_for_details:'Please check the contest announcement for details',
|
||||
|
@ -336,6 +341,19 @@ export const m = {
|
|||
Check_It: 'Check It',
|
||||
Accepted:'Accepted',
|
||||
|
||||
// /views/oj/contest/children/ContestPrint.vue
|
||||
Print_Title:'Contest Text Printing',
|
||||
Print_tips:'Please put the text to be printed into the content box, and then submit. Note: please do not submit maliciously!',
|
||||
Content:'Content',
|
||||
Content_cannot_be_empty:'Tne content cannot be empty!',
|
||||
The_number_of_content_cannot_be_less_than_50:'The number of words cannot be less than 50',
|
||||
Success_submit_tips:'Submitted successfully! Please wait patiently for the staff to print!',
|
||||
|
||||
// /views/oj/contest/children/ContestAdminPrint.vue
|
||||
Download:'Download',
|
||||
Printed:'Printed',
|
||||
Not_Printed:'Not Printed',
|
||||
|
||||
// /views/oj/contest/children/ContestRejudgeAdmin.vue
|
||||
Contest_Rejudge:'Contest Rejudge',
|
||||
ID: 'ID',
|
||||
|
|
|
@ -165,6 +165,9 @@ export const m = {
|
|||
Good_luck_to_you:'祝你好运!',
|
||||
|
||||
// /views/oj/problem/Problem.vue
|
||||
Problem_Description:'题目描述',
|
||||
My_Submission:'我的提交',
|
||||
Login_to_view_your_submission_history:'登录以查看您的提交记录',
|
||||
Shrink_Sidebar:'收缩侧边栏',
|
||||
View_Problem_Content:'查看题目内容',
|
||||
Only_View_Problem:'只看题目内容',
|
||||
|
@ -173,7 +176,7 @@ export const m = {
|
|||
Show_Tags:'显示标签',
|
||||
No_tag:'暂无标签',
|
||||
Statistic: '题目统计',
|
||||
Solution:'提交记录',
|
||||
Solutions:'全部提交',
|
||||
Problem_Discussion:'题目讨论',
|
||||
Description: '题目描述',
|
||||
Input: '输入描述',
|
||||
|
@ -214,7 +217,7 @@ export const m = {
|
|||
Mine:'我的',
|
||||
ID: 'ID',
|
||||
Time: '运行时间',
|
||||
Memory: '内存',
|
||||
Memory: '运行内存',
|
||||
Length:'代码长度',
|
||||
Language:'语言',
|
||||
View_submission_details:'查看提交详情',
|
||||
|
@ -314,6 +317,8 @@ export const m = {
|
|||
Submissions: '提交记录',
|
||||
Rankings: '排行榜',
|
||||
Comment:'评论',
|
||||
Print:'打印',
|
||||
Admin_Print:'管理打印',
|
||||
Admin_Helper: 'AC助手',
|
||||
Register_contest_successfully:'比赛报名成功',
|
||||
Please_check_the_contest_announcement_for_details:'具体内容请查看比赛公告',
|
||||
|
@ -339,6 +344,19 @@ export const m = {
|
|||
Check_It: '检查',
|
||||
Accepted:'Accepted',
|
||||
|
||||
// /views/oj/contest/children/ContestPrint.vue
|
||||
Print_Title:'比赛文本打印',
|
||||
Print_tips:'请将需要打印的文本放入内容框内提交。注意:请不要恶意提交!',
|
||||
Content:'内容',
|
||||
Content_cannot_be_empty:'内容不能为空',
|
||||
The_number_of_content_cannot_be_less_than_50:'内容字符数不能低于50!',
|
||||
Success_submit_tips:'提交成功!请耐心等待工作人员打印!',
|
||||
|
||||
// /views/oj/contest/children/ContestAdminPrint.vue
|
||||
Download:'下载',
|
||||
Printed:'已打印',
|
||||
Not_Printed:'未打印',
|
||||
|
||||
// /views/oj/contest/children/ContestRejudgeAdmin.vue
|
||||
Contest_Rejudge:'比赛重新测评',
|
||||
ID: 'ID',
|
||||
|
|
|
@ -16,6 +16,8 @@ import ContestRank from "@/views/oj/contest/children/ContestRank.vue"
|
|||
import ACMInfoAdmin from "@/views/oj/contest/children/ACMInfoAdmin.vue"
|
||||
import Announcements from "@/components/oj/common/Announcements.vue"
|
||||
import ContestComment from "@/views/oj/contest/children/ContestComment.vue"
|
||||
import ContestPrint from "@/views/oj/contest/children/ContestPrint.vue"
|
||||
import ContestAdminPrint from "@/views/oj/contest/children/ContestAdminPrint.vue"
|
||||
import ContestRejudgeAdmin from "@/views/oj/contest/children/ContestRejudgeAdmin.vue"
|
||||
import DiscussionList from "@/views/oj/discussion/discussionList.vue"
|
||||
import Discussion from "@/views/oj/discussion/discussion.vue"
|
||||
|
@ -161,6 +163,18 @@ const ojRoutes = [
|
|||
path:'comment',
|
||||
component: ContestComment,
|
||||
meta: { title: 'Contest Comment'}
|
||||
},
|
||||
{
|
||||
name: 'ContestPrint',
|
||||
path:'print',
|
||||
component: ContestPrint,
|
||||
meta: { title: 'Contest Print'}
|
||||
},
|
||||
{
|
||||
name: 'ContestAdminPrint',
|
||||
path:'admin-print',
|
||||
component: ContestAdminPrint,
|
||||
meta: { title: 'Contest Admin Print'}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -8,7 +8,8 @@ const state = {
|
|||
submitAccess:false, // 保护比赛的提交权限
|
||||
forceUpdate: false,
|
||||
contest: {
|
||||
auth: CONTEST_TYPE.PUBLIC
|
||||
auth: CONTEST_TYPE.PUBLIC,
|
||||
openPrint: false,
|
||||
},
|
||||
contestProblems: [],
|
||||
itemVisible: {
|
||||
|
|
|
@ -115,42 +115,106 @@
|
|||
</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">
|
||||
<el-option :label="$t('m.Public')" :value="0"></el-option>
|
||||
<el-option :label="$t('m.Private')" :value="1"></el-option>
|
||||
<el-option :label="$t('m.Protected')" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item
|
||||
:label="$t('m.Contest_Password')"
|
||||
v-show="contest.auth != 0"
|
||||
:required="contest.auth != 0"
|
||||
<el-row :gutter="30">
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item :label="$t('m.Print_Func')" required>
|
||||
<el-switch
|
||||
v-model="contest.openPrint"
|
||||
:active-text="$t('m.Support_Offline_Print')"
|
||||
:inactive-text="$t('m.Not_Support_Print')"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="30">
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item :label="$t('m.Contest_Auth')" required>
|
||||
<el-select v-model="contest.auth">
|
||||
<el-option :label="$t('m.Public')" :value="0"></el-option>
|
||||
<el-option :label="$t('m.Private')" :value="1"></el-option>
|
||||
<el-option :label="$t('m.Protected')" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item
|
||||
:label="$t('m.Contest_Password')"
|
||||
v-show="contest.auth != 0"
|
||||
:required="contest.auth != 0"
|
||||
>
|
||||
<el-input
|
||||
v-model="contest.pwd"
|
||||
:placeholder="$t('m.Contest_Password')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item
|
||||
:label="$t('m.Account_Limit')"
|
||||
v-show="contest.auth != 0"
|
||||
:required="contest.auth != 0"
|
||||
>
|
||||
<el-switch v-model="contest.openAccountLimit"> </el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template v-if="contest.openAccountLimit">
|
||||
<el-form :model="formRule">
|
||||
<el-col :md="6" :xs="24">
|
||||
<el-form-item :label="$t('m.Prefix')" prop="prefix">
|
||||
<el-input
|
||||
v-model="formRule.prefix"
|
||||
placeholder="Prefix"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="6" :xs="24">
|
||||
<el-form-item :label="$t('m.Suffix')" prop="suffix">
|
||||
<el-input
|
||||
v-model="formRule.suffix"
|
||||
placeholder="Suffix"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="6" :xs="24">
|
||||
<el-form-item :label="$t('m.Start_Number')" prop="number_from">
|
||||
<el-input-number
|
||||
v-model="formRule.number_from"
|
||||
style="width: 100%"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="6" :xs="24">
|
||||
<el-form-item :label="$t('m.End_Number')" prop="number_to">
|
||||
<el-input-number
|
||||
v-model="formRule.number_to"
|
||||
style="width: 100%"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
<div
|
||||
class="userPreview"
|
||||
v-if="formRule.number_from <= formRule.number_to"
|
||||
>
|
||||
<el-input
|
||||
v-model="contest.pwd"
|
||||
:placeholder="$t('m.Contest_Password')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- <el-col :span="24">
|
||||
<el-form-item label="Allowed IP Ranges">
|
||||
<div v-for="(range, index) in contest.allowed_ip_ranges" :key="index">
|
||||
<el-row :gutter="20" style="margin-bottom: 15px">
|
||||
<el-col :span="8">
|
||||
<el-input v-model="range.value" placeholder="CIDR Network"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-button icon="el-icon-plus" @click="addIPRange" type="primary"></el-button>
|
||||
<el-button icon="el-icon-delete-solid" @click.native="removeIPRange(range)" type="danger"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
{{ $t('m.The_allowed_account_will_be') }}
|
||||
{{ formRule.prefix + formRule.number_from + formRule.suffix }},
|
||||
<span v-if="formRule.number_from + 1 < formRule.number_to">
|
||||
{{
|
||||
formRule.prefix +
|
||||
(formRule.number_from + 1) +
|
||||
formRule.suffix +
|
||||
'...'
|
||||
}}
|
||||
</span>
|
||||
<span v-if="formRule.number_from + 1 <= formRule.number_to">
|
||||
{{ formRule.prefix + formRule.number_to + formRule.suffix }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-button type="primary" @click.native="saveContest">{{
|
||||
|
@ -189,9 +253,15 @@ export default {
|
|||
sealRank: true,
|
||||
sealRankTime: '', //封榜时间
|
||||
auth: 0,
|
||||
// allowed_ip_ranges: [{
|
||||
// value: ''
|
||||
// }]
|
||||
openPrint: false,
|
||||
openAccountLimit: false,
|
||||
accountLimitRule: '',
|
||||
},
|
||||
formRule: {
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
number_from: 0,
|
||||
number_to: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -224,14 +294,6 @@ export default {
|
|||
.admin_getContest(this.$route.params.contestId)
|
||||
.then((res) => {
|
||||
let data = res.data.data;
|
||||
// let ranges = []
|
||||
// for (let v of data.allowed_ip_ranges) {
|
||||
// ranges.push({value: v})
|
||||
// }
|
||||
// if (ranges.length === 0) {
|
||||
// ranges.push({value: ''})
|
||||
// }
|
||||
// data.allowed_ip_ranges = ranges
|
||||
this.contest = data;
|
||||
this.changeDuration();
|
||||
// 封榜时间转换
|
||||
|
@ -243,6 +305,9 @@ export default {
|
|||
.toString();
|
||||
let allHour = moment(this.contest.startTime).toString();
|
||||
let sealRankTime = moment(this.contest.sealRankTime).toString();
|
||||
this.formRule = this.changeStrToAccountRule(
|
||||
this.contest.accountLimitRule
|
||||
);
|
||||
switch (sealRankTime) {
|
||||
case halfHour:
|
||||
this.seal_rank_time = 0;
|
||||
|
@ -302,6 +367,12 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.contest.openAccountLimit) {
|
||||
this.contest.accountLimitRule = this.changeAccountRuleToStr(
|
||||
this.formRule
|
||||
);
|
||||
}
|
||||
|
||||
let funcName =
|
||||
this.$route.name === 'admin-edit-contest'
|
||||
? 'admin_editContest'
|
||||
|
@ -324,13 +395,6 @@ export default {
|
|||
this.contest.sealRankTime = moment(this.contest.startTime);
|
||||
}
|
||||
let data = Object.assign({}, this.contest);
|
||||
// let ranges = []
|
||||
// for (let v of data.allowed_ip_ranges) {
|
||||
// if (v.value !== '') {
|
||||
// ranges.push(v.value)
|
||||
// }
|
||||
// }
|
||||
// data.allowed_ip_ranges = ranges
|
||||
if (funcName === 'admin_createContest') {
|
||||
data['uid'] = this.userInfo.uid;
|
||||
data['author'] = this.userInfo.username;
|
||||
|
@ -360,15 +424,41 @@ export default {
|
|||
this.contest.duration = durationMS;
|
||||
}
|
||||
},
|
||||
// addIPRange () {
|
||||
// this.contest.allowed_ip_ranges.push({value: ''})
|
||||
// },
|
||||
// removeIPRange (range) {
|
||||
// let index = this.contest.allowed_ip_ranges.indexOf(range)
|
||||
// if (index !== -1) {
|
||||
// this.contest.allowed_ip_ranges.splice(index, 1)
|
||||
// }
|
||||
// }
|
||||
changeAccountRuleToStr(formRule) {
|
||||
let result =
|
||||
'<prefix>' +
|
||||
formRule.prefix +
|
||||
'</prefix><suffix>' +
|
||||
formRule.suffix +
|
||||
'</suffix><start>' +
|
||||
formRule.number_from +
|
||||
'</start><end>' +
|
||||
formRule.number_to +
|
||||
'</end>';
|
||||
return result;
|
||||
},
|
||||
changeStrToAccountRule(value) {
|
||||
let reg =
|
||||
'<prefix>([\\s\\S]*?)</prefix><suffix>([\\s\\S]*?)</suffix><start>([\\s\\S]*?)</start><end>([\\s\\S]*?)</end>';
|
||||
let re = RegExp(reg, 'g');
|
||||
let tmp = re.exec(value);
|
||||
return {
|
||||
prefix: tmp[1],
|
||||
suffix: tmp[2],
|
||||
number_from: tmp[3],
|
||||
number_to: tmp[4],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.userPreview {
|
||||
padding-left: 10px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
color: red;
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -285,6 +285,7 @@ export default {
|
|||
let searchParams = {
|
||||
currentPage: page,
|
||||
keyword: this.keyword,
|
||||
admin: true,
|
||||
};
|
||||
api.getDiscussionList(this.pageSize, searchParams).then(
|
||||
(res) => {
|
||||
|
|
|
@ -172,7 +172,8 @@
|
|||
<vxe-table-column
|
||||
:title="$t('m.Username')"
|
||||
field="username"
|
||||
min-width="150"
|
||||
min-width="96"
|
||||
show-overflow
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row[0] }}
|
||||
|
@ -182,6 +183,7 @@
|
|||
:title="$t('m.Password')"
|
||||
field="password"
|
||||
min-width="150"
|
||||
show-overflow
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row[1] }}
|
||||
|
@ -190,12 +192,23 @@
|
|||
<vxe-table-column
|
||||
:title="$t('m.Email')"
|
||||
field="email"
|
||||
min-width="150"
|
||||
min-width="120"
|
||||
show-overflow
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row[2] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
:title="$t('m.RealName')"
|
||||
field="realname"
|
||||
min-width="150"
|
||||
show-overflow
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
{{ row[3] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
||||
<div class="panel-options">
|
||||
|
@ -764,7 +777,7 @@ export default {
|
|||
papa.parse(file, {
|
||||
complete: (results) => {
|
||||
let data = results.data.filter((user) => {
|
||||
return user[0] && user[1] && user[2];
|
||||
return user[0] && user[1];
|
||||
});
|
||||
let delta = results.data.length - data.length;
|
||||
if (delta > 0) {
|
||||
|
|
|
@ -182,6 +182,20 @@
|
|||
</transition>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
name="ContestPrint"
|
||||
lazy
|
||||
:disabled="contestMenuDisabled"
|
||||
v-if="contest.openPrint"
|
||||
>
|
||||
<span slot="label"
|
||||
><i class="el-icon-printer"></i> {{ $t('m.Print') }}</span
|
||||
>
|
||||
<transition name="el-zoom-in-bottom">
|
||||
<router-view v-if="route_name === 'ContestPrint'"></router-view>
|
||||
</transition>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
name="ContestACInfo"
|
||||
lazy
|
||||
|
@ -198,6 +212,24 @@
|
|||
</transition>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
name="ContestAdminPrint"
|
||||
lazy
|
||||
:disabled="contestMenuDisabled"
|
||||
v-if="isSuperAdmin && contest.openPrint"
|
||||
>
|
||||
<span slot="label"
|
||||
><i class="el-icon-printer"></i> {{
|
||||
$t('m.Admin_Print')
|
||||
}}</span
|
||||
>
|
||||
<transition name="el-zoom-in-bottom">
|
||||
<router-view
|
||||
v-if="route_name === 'ContestAdminPrint'"
|
||||
></router-view>
|
||||
</transition>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
name="ContestRejudgeAdmin"
|
||||
lazy
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
}}</el-tag>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="option" title="Option" min-width="150">
|
||||
<vxe-table-column field="option" :title="$t('m.Option')" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
|
@ -109,7 +109,6 @@
|
|||
</el-card>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import api from '@/common/api';
|
||||
import myMessage from '@/common/message';
|
||||
const Pagination = () => import('@/components/oj/common/Pagination');
|
||||
|
@ -121,7 +120,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 30,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
btnLoading: false,
|
||||
autoRefresh: false,
|
||||
|
@ -129,7 +128,6 @@ export default {
|
|||
};
|
||||
},
|
||||
mounted() {
|
||||
this.contestID = this.$route.params.contestID;
|
||||
this.getACInfo(1);
|
||||
},
|
||||
methods: {
|
||||
|
@ -182,11 +180,6 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
contest: (state) => state.contest.contest,
|
||||
}),
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.refreshFunc);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<el-card shadow="always">
|
||||
<div slot="header">
|
||||
<span class="panel-title">{{ $t('m.Admin_Print') }}</span>
|
||||
<div class="filter-row">
|
||||
<span>
|
||||
{{ $t('m.Auto_Refresh') }}(10s)
|
||||
<el-switch
|
||||
@change="handleAutoRefresh"
|
||||
v-model="autoRefresh"
|
||||
></el-switch>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="getContestPrint(1)"
|
||||
size="small"
|
||||
icon="el-icon-refresh"
|
||||
:loading="btnLoading"
|
||||
>{{ $t('m.Refresh') }}</el-button
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<vxe-table
|
||||
border="inner"
|
||||
stripe
|
||||
auto-resize
|
||||
align="center"
|
||||
:data="printList"
|
||||
>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
:title="$t('m.Username')"
|
||||
min-width="150"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span
|
||||
><a
|
||||
@click="getUserTotalSubmit(row.username)"
|
||||
style="color:rgb(87, 163, 243);"
|
||||
>{{ row.username }}</a
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="realname"
|
||||
:title="$t('m.RealName')"
|
||||
min-width="150"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="gmtCreate"
|
||||
min-width="150"
|
||||
:title="$t('m.Submit_Time')"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ row.submitTime | localtime }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="status" :title="$t('m.Status')" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag effect="dark" color="#19be6b" v-if="row.status == 1">{{
|
||||
$t('m.Printed')
|
||||
}}</el-tag>
|
||||
<el-tag effect="dark" color="#f90" v-if="row.status == 0">{{
|
||||
$t('m.Not_Printed')
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="option" :title="$t('m.Option')" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="el-icon-download"
|
||||
@click="downloadSubmissions(row.id)"
|
||||
round
|
||||
>{{ $t('m.Download') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
icon="el-icon-circle-check"
|
||||
@click="updateStatus(row.id)"
|
||||
round
|
||||
>{{ $t('m.OK') }}</el-button
|
||||
>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
:page-size.sync="limit"
|
||||
:current.sync="page"
|
||||
@on-change="getContestPrint"
|
||||
></Pagination>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/common/api';
|
||||
import myMessage from '@/common/message';
|
||||
import utils from '@/common/utils';
|
||||
const Pagination = () => import('@/components/oj/common/Pagination');
|
||||
|
||||
export default {
|
||||
name: 'Contest-Print-Admin',
|
||||
components: {
|
||||
Pagination,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 15,
|
||||
total: 0,
|
||||
btnLoading: false,
|
||||
autoRefresh: false,
|
||||
contestID: null,
|
||||
printList: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.contestID = this.$route.params.contestID;
|
||||
this.getContestPrint(1);
|
||||
},
|
||||
methods: {
|
||||
updateStatus(id) {
|
||||
let params = {
|
||||
id: id,
|
||||
cid: this.contestID,
|
||||
};
|
||||
api.updateContestPrintStatus(params).then((res) => {
|
||||
myMessage.success(this.$i18n.t('m.Update_Successfully'));
|
||||
this.getContestPrint(1);
|
||||
});
|
||||
},
|
||||
getContestPrint(page = 1) {
|
||||
let params = {
|
||||
cid: this.contestID,
|
||||
currentPage: page,
|
||||
limit: this.limit,
|
||||
};
|
||||
this.btnLoading = true;
|
||||
api
|
||||
.getContestPrintList(params)
|
||||
.then((res) => {
|
||||
this.btnLoading = false;
|
||||
this.printList = res.data.data.records;
|
||||
this.total = res.data.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
this.btnLoading = false;
|
||||
});
|
||||
},
|
||||
downloadSubmissions(id) {
|
||||
let url = `/api/file/download-contest-print-text?id=${id}`;
|
||||
utils.downloadFile(url);
|
||||
},
|
||||
handleAutoRefresh() {
|
||||
if (this.autoRefresh) {
|
||||
this.refreshFunc = setInterval(() => {
|
||||
this.page = 1;
|
||||
this.getContestPrint(1);
|
||||
}, 10000);
|
||||
} else {
|
||||
clearInterval(this.refreshFunc);
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.refreshFunc);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-row {
|
||||
float: right;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
/deep/ .el-tag--dark {
|
||||
border-color: #fff;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<el-card shadow="always">
|
||||
<div slot="header">
|
||||
<span class="panel-title">{{ $t('m.Print') }}</span>
|
||||
</div>
|
||||
<div class="print-tips">
|
||||
<el-alert
|
||||
:title="$t('m.Print_Title')"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
center
|
||||
:description="$t('m.Print_tips')"
|
||||
show-icon
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
<el-form
|
||||
:model="ruleForm"
|
||||
:rules="rules"
|
||||
ref="ruleForm"
|
||||
label-width="100px"
|
||||
class="demo-ruleForm"
|
||||
>
|
||||
<el-form-item :label="$t('m.Content')" prop="content">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="ruleForm.content"
|
||||
:rows="20"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item style="text-align: center;">
|
||||
<el-button type="primary" @click="onSubmit">{{
|
||||
$t('m.Submit')
|
||||
}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mMessage from '@/common/message';
|
||||
import api from '@/common/api';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ruleForm: {
|
||||
content: '',
|
||||
},
|
||||
rules: {
|
||||
content: [{ required: true, trigger: 'blur' }],
|
||||
},
|
||||
contestID: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.contestID = this.$route.params.contestID;
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
if (!this.ruleForm.content) {
|
||||
mMessage.error(this.$i18n.t('m.Content_cannot_be_empty'));
|
||||
return;
|
||||
}
|
||||
if (this.ruleForm.content.length < 50) {
|
||||
mMessage.error(
|
||||
this.$i18n.t('m.The_number_of_content_cannot_be_less_than_50')
|
||||
);
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
cid: this.contestID,
|
||||
content: this.ruleForm.content,
|
||||
};
|
||||
api.submitPrintText(data).then((res) => {
|
||||
this.$confirm(
|
||||
this.$i18n.t('m.Success_submit_tips'),
|
||||
this.$i18n.t('m.Submit_code_successfully'),
|
||||
{
|
||||
type: 'success',
|
||||
center: true,
|
||||
confirmButtonText: this.$i18n.t('m.OK'),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.print-tips {
|
||||
margin-left: 50px;
|
||||
padding: 30px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
|
@ -401,7 +401,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
total: 0,
|
||||
limit: 15,
|
||||
limit: 10,
|
||||
currentPage: 1,
|
||||
showEditDiscussionDialog: false,
|
||||
discussion: {
|
||||
|
|
|
@ -4,208 +4,387 @@
|
|||
<!--problem main-->
|
||||
<el-row class="problem-box">
|
||||
<el-col :sm="24" :md="24" :lg="12" class="problem-left">
|
||||
<el-card :padding="10" shadow class="problem-detail">
|
||||
<div slot="header" class="panel-title">
|
||||
<span>{{ problemData.problem.title }}</span
|
||||
><br />
|
||||
<span v-if="contestID && !contestEnded"
|
||||
><el-tag effect="plain" size="small">{{
|
||||
$t('m.Contest_Problem')
|
||||
}}</el-tag></span
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
type="border-card"
|
||||
@tab-click="handleClickTab"
|
||||
>
|
||||
<el-tab-pane name="problemDetail">
|
||||
<span slot="label"
|
||||
><i class="fa fa-list-alt"></i>
|
||||
{{ $t('m.Problem_Description') }}</span
|
||||
>
|
||||
<div v-else-if="problemData.tags.length > 0" class="problem-tag">
|
||||
<el-popover placement="right-start" width="60" trigger="hover">
|
||||
<el-tag
|
||||
slot="reference"
|
||||
size="small"
|
||||
type="primary"
|
||||
style="cursor: pointer;"
|
||||
effect="plain"
|
||||
>{{ $t('m.Show_Tags') }}</el-tag
|
||||
<div :padding="10" shadow class="problem-detail">
|
||||
<div slot="header" class="panel-title">
|
||||
<span>{{ problemData.problem.title }}</span
|
||||
><br />
|
||||
<span v-if="contestID && !contestEnded"
|
||||
><el-tag effect="plain" size="small">{{
|
||||
$t('m.Contest_Problem')
|
||||
}}</el-tag></span
|
||||
>
|
||||
<el-tag
|
||||
v-for="tag in problemData.tags"
|
||||
:key="tag"
|
||||
effect="plain"
|
||||
size="small"
|
||||
style="margin-right:5px;margin-top:2px"
|
||||
>{{ tag }}</el-tag
|
||||
<div
|
||||
v-else-if="problemData.tags.length > 0"
|
||||
class="problem-tag"
|
||||
>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-else-if="problemData.tags.length == 0" class="problem-tag">
|
||||
<el-tag effect="plain" size="small">{{
|
||||
$t('m.No_tag')
|
||||
}}</el-tag>
|
||||
</div>
|
||||
<div class="problem-menu">
|
||||
<span v-if="!contestID">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="goProblemDiscussion"
|
||||
><i class="fa fa-comments" aria-hidden="true"></i>
|
||||
{{ $t('m.Problem_Discussion') }}</el-link
|
||||
></span
|
||||
>
|
||||
<span>
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="graphVisible = !graphVisible"
|
||||
><i class="fa fa-pie-chart" aria-hidden="true"></i>
|
||||
{{ $t('m.Statistic') }}</el-link
|
||||
></span
|
||||
>
|
||||
<span>
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="goProblemSubmission"
|
||||
><i class="fa fa-bars" aria-hidden="true"></i>
|
||||
{{ $t('m.Solution') }}</el-link
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="question-intr">
|
||||
<template v-if="!isCFProblem">
|
||||
<span
|
||||
>{{ $t('m.Time_Limit') }}:C/C++
|
||||
{{ problemData.problem.timeLimit }}MS,{{ $t('m.Other') }}
|
||||
{{ problemData.problem.timeLimit * 2 }}MS</span
|
||||
><br />
|
||||
<span
|
||||
>{{ $t('m.Memory_Limit') }}:C/C++
|
||||
{{ problemData.problem.memoryLimit }}MB,{{
|
||||
$t('m.Other')
|
||||
}}
|
||||
{{ problemData.problem.memoryLimit * 2 }}MB</span
|
||||
><br />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<span
|
||||
>{{ $t('m.Time_Limit') }}:{{
|
||||
problemData.problem.timeLimit
|
||||
}}MS</span
|
||||
>
|
||||
<br />
|
||||
<span
|
||||
>{{ $t('m.Memory_Limit') }}:{{
|
||||
problemData.problem.memoryLimit
|
||||
}}MB</span
|
||||
><br />
|
||||
</template>
|
||||
|
||||
<span
|
||||
>{{ $t('m.Level') }}:{{
|
||||
PROBLEM_LEVEL[problemData.problem.difficulty]['name']
|
||||
}}</span
|
||||
>
|
||||
<br />
|
||||
<template v-if="problemData.problem.type == 1">
|
||||
<span
|
||||
>{{ $t('m.Score') }}:{{ problemData.problem.ioScore }}
|
||||
</span>
|
||||
<span v-if="!contestID" style="margin-left:5px;">
|
||||
{{ $t('m.OI_Rank_Score') }}:{{
|
||||
calcOIRankScore(
|
||||
problemData.problem.ioScore,
|
||||
problemData.problem.difficulty
|
||||
)
|
||||
}}(0.1*{{ $t('m.Score') }}+2*{{ $t('m.Level') }})
|
||||
</span>
|
||||
<br />
|
||||
</template>
|
||||
|
||||
<template v-if="problemData.problem.author">
|
||||
<span
|
||||
>{{ $t('m.Created') }}:{{
|
||||
problemData.problem.author
|
||||
}}</span
|
||||
><br />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="problem-content">
|
||||
<p class="title">{{ $t('m.Description') }}</p>
|
||||
<p
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.description"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
<p class="title">{{ $t('m.Input') }}</p>
|
||||
<p
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.input"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
|
||||
<p class="title">{{ $t('m.Output') }}</p>
|
||||
<p
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.output"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
|
||||
<div
|
||||
v-for="(example, index) of problemData.problem.examples"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex-container example">
|
||||
<div class="example-input">
|
||||
<p class="title">
|
||||
{{ $t('m.Sample_Input') }} {{ index + 1 }}
|
||||
<a
|
||||
class="copy"
|
||||
v-clipboard:copy="example.input"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onCopyError"
|
||||
<el-popover
|
||||
placement="right-start"
|
||||
width="60"
|
||||
trigger="hover"
|
||||
>
|
||||
<el-tag
|
||||
slot="reference"
|
||||
size="small"
|
||||
type="primary"
|
||||
style="cursor: pointer;"
|
||||
effect="plain"
|
||||
>{{ $t('m.Show_Tags') }}</el-tag
|
||||
>
|
||||
<i class="el-icon-document-copy"></i>
|
||||
</a>
|
||||
</p>
|
||||
<pre>{{ example.input }}</pre>
|
||||
<el-tag
|
||||
v-for="tag in problemData.tags"
|
||||
:key="tag"
|
||||
effect="plain"
|
||||
size="small"
|
||||
style="margin-right:5px;margin-top:2px"
|
||||
>{{ tag }}</el-tag
|
||||
>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="example-output">
|
||||
<p class="title">
|
||||
{{ $t('m.Sample_Output') }} {{ index + 1 }}
|
||||
<a
|
||||
class="copy"
|
||||
v-clipboard:copy="example.output"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onCopyError"
|
||||
<div
|
||||
v-else-if="problemData.tags.length == 0"
|
||||
class="problem-tag"
|
||||
>
|
||||
<el-tag effect="plain" size="small">{{
|
||||
$t('m.No_tag')
|
||||
}}</el-tag>
|
||||
</div>
|
||||
<div class="problem-menu">
|
||||
<span v-if="!contestID">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="goProblemDiscussion"
|
||||
><i class="fa fa-comments" aria-hidden="true"></i>
|
||||
{{ $t('m.Problem_Discussion') }}</el-link
|
||||
></span
|
||||
>
|
||||
<span>
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="graphVisible = !graphVisible"
|
||||
><i class="fa fa-pie-chart" aria-hidden="true"></i>
|
||||
{{ $t('m.Statistic') }}</el-link
|
||||
></span
|
||||
>
|
||||
<span>
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="goProblemSubmission"
|
||||
><i class="fa fa-bars" aria-hidden="true"></i>
|
||||
{{ $t('m.Solutions') }}</el-link
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="question-intr">
|
||||
<template v-if="!isCFProblem">
|
||||
<span
|
||||
>{{ $t('m.Time_Limit') }}:C/C++
|
||||
{{ problemData.problem.timeLimit }}MS,{{
|
||||
$t('m.Other')
|
||||
}}
|
||||
{{ problemData.problem.timeLimit * 2 }}MS</span
|
||||
><br />
|
||||
<span
|
||||
>{{ $t('m.Memory_Limit') }}:C/C++
|
||||
{{ problemData.problem.memoryLimit }}MB,{{
|
||||
$t('m.Other')
|
||||
}}
|
||||
{{ problemData.problem.memoryLimit * 2 }}MB</span
|
||||
><br />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<span
|
||||
>{{ $t('m.Time_Limit') }}:{{
|
||||
problemData.problem.timeLimit
|
||||
}}MS</span
|
||||
>
|
||||
<i class="el-icon-document-copy"></i>
|
||||
</a>
|
||||
</p>
|
||||
<pre>{{ example.output }}</pre>
|
||||
<br />
|
||||
<span
|
||||
>{{ $t('m.Memory_Limit') }}:{{
|
||||
problemData.problem.memoryLimit
|
||||
}}MB</span
|
||||
><br />
|
||||
</template>
|
||||
|
||||
<span
|
||||
>{{ $t('m.Level') }}:{{
|
||||
PROBLEM_LEVEL[problemData.problem.difficulty]['name']
|
||||
}}</span
|
||||
>
|
||||
<br />
|
||||
<template v-if="problemData.problem.type == 1">
|
||||
<span
|
||||
>{{ $t('m.Score') }}:{{ problemData.problem.ioScore }}
|
||||
</span>
|
||||
<span v-if="!contestID" style="margin-left:5px;">
|
||||
{{ $t('m.OI_Rank_Score') }}:{{
|
||||
calcOIRankScore(
|
||||
problemData.problem.ioScore,
|
||||
problemData.problem.difficulty
|
||||
)
|
||||
}}(0.1*{{ $t('m.Score') }}+2*{{ $t('m.Level') }})
|
||||
</span>
|
||||
<br />
|
||||
</template>
|
||||
|
||||
<template v-if="problemData.problem.author">
|
||||
<span
|
||||
>{{ $t('m.Created') }}:{{
|
||||
problemData.problem.author
|
||||
}}</span
|
||||
><br />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="problemData.problem.hint">
|
||||
<p class="title">{{ $t('m.Hint') }}</p>
|
||||
<el-card dis-hover>
|
||||
<div id="problem-content">
|
||||
<p class="title">{{ $t('m.Description') }}</p>
|
||||
<p
|
||||
class="hint-content markdown-body"
|
||||
v-html="problemData.problem.hint"
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.description"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
<p class="title">{{ $t('m.Input') }}</p>
|
||||
<p
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.input"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<template v-if="problemData.problem.source && !contestID">
|
||||
<p class="title">{{ $t('m.Source') }}</p>
|
||||
<p class="content" v-html="problemData.problem.source"></p>
|
||||
<p class="title">{{ $t('m.Output') }}</p>
|
||||
<p
|
||||
class="content markdown-body"
|
||||
v-html="problemData.problem.output"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
|
||||
<div
|
||||
v-for="(example, index) of problemData.problem.examples"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex-container example">
|
||||
<div class="example-input">
|
||||
<p class="title">
|
||||
{{ $t('m.Sample_Input') }} {{ index + 1 }}
|
||||
<a
|
||||
class="copy"
|
||||
v-clipboard:copy="example.input"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onCopyError"
|
||||
>
|
||||
<i class="el-icon-document-copy"></i>
|
||||
</a>
|
||||
</p>
|
||||
<pre>{{ example.input }}</pre>
|
||||
</div>
|
||||
<div class="example-output">
|
||||
<p class="title">
|
||||
{{ $t('m.Sample_Output') }} {{ index + 1 }}
|
||||
<a
|
||||
class="copy"
|
||||
v-clipboard:copy="example.output"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onCopyError"
|
||||
>
|
||||
<i class="el-icon-document-copy"></i>
|
||||
</a>
|
||||
</p>
|
||||
<pre>{{ example.output }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="problemData.problem.hint">
|
||||
<p class="title">{{ $t('m.Hint') }}</p>
|
||||
<el-card dis-hover>
|
||||
<p
|
||||
class="hint-content markdown-body"
|
||||
v-html="problemData.problem.hint"
|
||||
v-katex
|
||||
v-highlight
|
||||
></p>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<template v-if="problemData.problem.source && !contestID">
|
||||
<p class="title">{{ $t('m.Source') }}</p>
|
||||
<p class="content" v-html="problemData.problem.source"></p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="mySubmission">
|
||||
<span slot="label"
|
||||
><i class="el-icon-time"></i> {{ $t('m.My_Submission') }}</span
|
||||
>
|
||||
<template v-if="!isAuthenticated">
|
||||
<div style="margin:50px 0px;margin-left:-20px;">
|
||||
<el-alert
|
||||
:title="$t('m.Please_login_first')"
|
||||
type="warning"
|
||||
center
|
||||
:closable="false"
|
||||
:description="$t('m.Login_to_view_your_submission_history')"
|
||||
show-icon
|
||||
>
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
<template v-else>
|
||||
<div style="margin:20px 0px;margin-right:10px;">
|
||||
<vxe-table
|
||||
align="center"
|
||||
:data="mySubmissions"
|
||||
stripe
|
||||
auto-resize
|
||||
border="inner"
|
||||
:loading="loadingTable"
|
||||
>
|
||||
<vxe-table-column
|
||||
:title="$t('m.Submit_Time')"
|
||||
min-width="96"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span
|
||||
><el-tooltip
|
||||
:content="row.submitTime | localtime"
|
||||
placement="top"
|
||||
>
|
||||
<span>{{ row.submitTime | fromNow }}</span>
|
||||
</el-tooltip></span
|
||||
>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="status"
|
||||
:title="$t('m.Status')"
|
||||
min-width="160"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<span :class="getStatusColor(row.status)">{{
|
||||
JUDGE_STATUS[row.status].name
|
||||
}}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column :title="$t('m.Time')" min-width="96">
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ submissionTimeFormat(row.time) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column :title="$t('m.Memory')" min-width="96">
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ submissionMemoryFormat(row.memory) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
:title="$t('m.Score')"
|
||||
min-width="64"
|
||||
v-if="problemData.problem.type == 1"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<template v-if="contestID && row.score != null">
|
||||
<el-tag
|
||||
effect="plain"
|
||||
size="medium"
|
||||
:type="JUDGE_STATUS[row.status]['type']"
|
||||
>{{ row.score }}</el-tag
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="row.score != null">
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">
|
||||
{{ $t('m.Problem_Score') }}:{{
|
||||
row.score != null ? row.score : $t('m.Unknown')
|
||||
}}<br />{{ $t('m.OI_Rank_Score') }}:{{
|
||||
row.oiRankScore != null
|
||||
? row.oiRankScore
|
||||
: $t('m.Unknown')
|
||||
}}<br />
|
||||
{{
|
||||
$t('m.OI_Rank_Calculation_Rule')
|
||||
}}:(score*0.1+difficulty*2)*(ac_cases/sum_cases)
|
||||
</div>
|
||||
<el-tag
|
||||
effect="plain"
|
||||
size="medium"
|
||||
:type="JUDGE_STATUS[row.status]['type']"
|
||||
>{{ row.score }}</el-tag
|
||||
>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
row.status == JUDGE_STATUS_RESERVE['Pending'] ||
|
||||
row.status == JUDGE_STATUS_RESERVE['Compiling'] ||
|
||||
row.status == JUDGE_STATUS_RESERVE['Judging']
|
||||
"
|
||||
>
|
||||
<el-tag
|
||||
effect="plain"
|
||||
size="medium"
|
||||
:type="JUDGE_STATUS[row.status]['type']"
|
||||
>
|
||||
<i class="el-icon-loading"></i>
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag
|
||||
effect="plain"
|
||||
size="medium"
|
||||
:type="JUDGE_STATUS[row.status]['type']"
|
||||
>--</el-tag
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="language"
|
||||
:title="$t('m.Language')"
|
||||
show-overflow
|
||||
min-width="130"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('m.View_submission_details')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
type="text"
|
||||
@click="showSubmitDetail(row)"
|
||||
>{{ row.language }}</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
<Pagination
|
||||
:total="mySubmission_total"
|
||||
:page-size="mySubmission_limit"
|
||||
@on-change="getMySubmission"
|
||||
:current.sync="mySubmission_currentPage"
|
||||
></Pagination>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
<div
|
||||
class="problem-resize hidden-md-and-down"
|
||||
|
@ -452,6 +631,7 @@ import api from '@/common/api';
|
|||
import myMessage from '@/common/message';
|
||||
import { addCodeBtn } from '@/common/codeblock';
|
||||
const CodeMirror = () => import('@/components/oj/common/CodeMirror.vue');
|
||||
import Pagination from '@/components/oj/common/Pagination';
|
||||
// 只显示这些状态的图形占用
|
||||
const filtedStatus = ['wa', 'ce', 'ac', 'pa', 'tle', 'mle', 're', 'pe'];
|
||||
|
||||
|
@ -459,6 +639,7 @@ export default {
|
|||
name: 'ProblemDetails',
|
||||
components: {
|
||||
CodeMirror,
|
||||
Pagination,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -500,10 +681,17 @@ export default {
|
|||
height: '380',
|
||||
},
|
||||
JUDGE_STATUS_RESERVE: {},
|
||||
JUDGE_STATUS: {},
|
||||
PROBLEM_LEVEL: {},
|
||||
RULE_TYPE: {},
|
||||
toResetWatch: false,
|
||||
toWatchProblem: false,
|
||||
activeName: 'problemDetail',
|
||||
loadingTable: false,
|
||||
mySubmission_total: 0,
|
||||
mySubmission_limit: 15,
|
||||
mySubmission_currentPage: 1,
|
||||
mySubmissions: [],
|
||||
};
|
||||
},
|
||||
// 获取缓存中的该题的做题代码,代码语言,代码风格
|
||||
|
@ -524,6 +712,7 @@ export default {
|
|||
|
||||
created() {
|
||||
this.JUDGE_STATUS_RESERVE = Object.assign({}, JUDGE_STATUS_RESERVE);
|
||||
this.JUDGE_STATUS = Object.assign({}, JUDGE_STATUS);
|
||||
this.PROBLEM_LEVEL = Object.assign({}, PROBLEM_LEVEL);
|
||||
this.RULE_TYPE = Object.assign({}, RULE_TYPE);
|
||||
},
|
||||
|
@ -533,6 +722,76 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['changeDomTitle']),
|
||||
handleClickTab({ name }) {
|
||||
if (name == 'mySubmission') {
|
||||
this.getMySubmission();
|
||||
}
|
||||
},
|
||||
getMySubmission() {
|
||||
let params = {
|
||||
onlyMine: true,
|
||||
currentPage: this.mySubmission_currentPage,
|
||||
problemID: this.problemID,
|
||||
contestID: this.contestID,
|
||||
limit: this.mySubmission_limit,
|
||||
};
|
||||
if (this.contestID) {
|
||||
if (this.contestStatus == CONTEST_STATUS.SCHEDULED) {
|
||||
params.beforeContestSubmit = true;
|
||||
} else {
|
||||
params.beforeContestSubmit = false;
|
||||
}
|
||||
}
|
||||
let func = this.contestID
|
||||
? 'getContestSubmissionList'
|
||||
: 'getSubmissionList';
|
||||
this.loadingTable = true;
|
||||
api[func](this.mySubmission_limit, utils.filterEmptyValue(params))
|
||||
.then(
|
||||
(res) => {
|
||||
let data = res.data.data;
|
||||
this.mySubmissions = data.records;
|
||||
this.mySubmission_total = data.total;
|
||||
this.loadingTable = false;
|
||||
},
|
||||
(err) => {
|
||||
this.loadingTable = false;
|
||||
}
|
||||
)
|
||||
.catch(() => {
|
||||
this.loadingTable = false;
|
||||
});
|
||||
},
|
||||
getStatusColor(status) {
|
||||
return 'el-tag el-tag--medium status-' + JUDGE_STATUS[status].color;
|
||||
},
|
||||
submissionTimeFormat(time) {
|
||||
return utils.submissionTimeFormat(time);
|
||||
},
|
||||
|
||||
submissionMemoryFormat(memory) {
|
||||
return utils.submissionMemoryFormat(memory);
|
||||
},
|
||||
|
||||
showSubmitDetail(row) {
|
||||
if (row.cid != 0) {
|
||||
// 比赛提交详情
|
||||
this.$router.push({
|
||||
name: 'ContestSubmissionDeatil',
|
||||
params: {
|
||||
contestID: this.$route.params.contestID,
|
||||
problemID: row.displayId,
|
||||
submitID: row.submitId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'SubmissionDeatil',
|
||||
params: { submitID: row.submitId },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
dragControllerDiv() {
|
||||
var resize = document.getElementsByClassName('problem-resize');
|
||||
var left = document.getElementsByClassName('problem-left');
|
||||
|
@ -1089,13 +1348,20 @@ a {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
/deep/.el-tabs--border-card > .el-tabs__content {
|
||||
padding-top: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.problem-detail {
|
||||
padding-right: 15px;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.problem-detail {
|
||||
height: 700px !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.submit-detail {
|
||||
height: 700px !important;
|
||||
height: 755px !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.problem-tag {
|
||||
|
@ -1205,7 +1471,7 @@ a {
|
|||
}
|
||||
|
||||
#problem-content {
|
||||
margin-top: -50px;
|
||||
margin-top: -40px;
|
||||
}
|
||||
#problem-content .title {
|
||||
font-size: 16px;
|
||||
|
|
|
@ -97,19 +97,24 @@
|
|||
v-if="isIOProblem"
|
||||
>
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">
|
||||
{{ $t('m.Problem_Score') }}:{{
|
||||
row.score != null ? row.score : $t('m.Unknown')
|
||||
}}<br />{{ $t('m.OI_Rank_Score') }}:{{
|
||||
row.oiRankScore != null ? row.oiRankScore : $t('m.Unknown')
|
||||
}}<br />
|
||||
{{
|
||||
$t('m.OI_Rank_Calculation_Rule')
|
||||
}}:(score*0.1+diffculty*2)*(ac_testcase/sum_testcase)
|
||||
</div>
|
||||
<span>{{ row.score }}</span>
|
||||
</el-tooltip>
|
||||
<template v-if="row.score != null">
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">
|
||||
{{ $t('m.Problem_Score') }}:{{
|
||||
row.score != null ? row.score : $t('m.Unknown')
|
||||
}}<br />{{ $t('m.OI_Rank_Score') }}:{{
|
||||
row.oiRankScore != null ? row.oiRankScore : $t('m.Unknown')
|
||||
}}<br />
|
||||
{{
|
||||
$t('m.OI_Rank_Calculation_Rule')
|
||||
}}:(score*0.1+diffculty*2)*(ac_testcase/sum_testcase)
|
||||
</div>
|
||||
<span>{{ row.score }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>--</span>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column :title="$t('m.Length')" min-width="80">
|
||||
|
|
Loading…
Reference in New Issue