前端管理端页面基本完成
This commit is contained in:
parent
3b18df5563
commit
acbf59ad77
84
README.md
84
README.md
|
@ -16,6 +16,7 @@
|
|||
| 2020-11-22 | 前端比赛首页,比赛题目列表,比赛排行榜,比赛公告,首页布局调整 | Himit_ZH |
|
||||
| 2020-11-24 | 介绍页,导航栏移动端优化,首页优化,公告栏优化 | Himit_ZH |
|
||||
| 2020-11-28 | 前端项目重构,加入管理端部分页面,增加case表 | Himit_ZH |
|
||||
| 2020-12-01 | 前端管理端基本完成,准备开始前后端接口对接与测试 | Himit_ZH |
|
||||
|
||||
# 二、系统架构
|
||||
|
||||
|
@ -175,16 +176,19 @@ problem表
|
|||
| id | long | primary key | auto_increment 1000开始 |
|
||||
| title | String | | 题目 |
|
||||
| author | String | | 默认可为无 |
|
||||
| type | int | | 题目类型 0为ACM,1为OI |
|
||||
| time_limit | int | | 时间限制(ms),默认为c/c++限制,其它语言为2倍 |
|
||||
| memory_limit | int | | 空间限制(k),默认为c/c++限制,其它语言为2倍 |
|
||||
| memory_limit | int | | 空间限制(mb),默认为c/c++限制,其它语言为2倍 |
|
||||
| description | String | | 内容描述 |
|
||||
| input | String | | 输入描述 |
|
||||
| output | String | | 输出描述 |
|
||||
| sample_input | Srting | | 输入样例,多样例用(#)隔开 |
|
||||
| sample_output | String | | 输出样例 |
|
||||
| source | int | | 题目来源(比赛id),默认为hoj,可能为爬虫vj |
|
||||
| comment | String | | 备注 |
|
||||
| difficulty | int | | 题目难度,0简单,1中等,2困难 |
|
||||
| hint | String | | 备注 提醒 |
|
||||
| auth | int | | 默认为1公开,2为私有,3为比赛中。 |
|
||||
| code_share | boolean | | 该题目对应的相关提交代码,用户是否可用分享 |
|
||||
| gmt_create | datetime | | 创建时间 |
|
||||
| gmt_modified | datetime | | 修改时间 |
|
||||
|
||||
|
@ -322,25 +326,69 @@ jugdeCase表 评测单个样例结果表
|
|||
|
||||
## 比赛模块
|
||||
|
||||
|
||||
更新比赛状态的存储过程
|
||||
|
||||
```sql
|
||||
DELIMITER |
|
||||
|
||||
DROP PROCEDURE IF EXISTS contest_status |
|
||||
CREATE PROCEDURE contest_status()
|
||||
|
||||
BEGIN
|
||||
UPDATE contest
|
||||
SET STATUS = (
|
||||
CASE
|
||||
WHEN NOW() < start_time THEN STATUS = -1
|
||||
WHEN NOW() >= start_time AND NOW()<end_time THEN STATUS = 0
|
||||
WHEN NOW() > end_time THEN STATUS = 1
|
||||
END);
|
||||
END
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
设置定时器
|
||||
|
||||
```sql
|
||||
SET GLOBAL event_scheduler = 1; // 开启定时器
|
||||
CREATE EVENT IF NOT EXISTS contest_event
|
||||
|
||||
ON SCHEDULE EVERY 1 SECOND // 每秒执行一次
|
||||
|
||||
ON COMPLETION PRESERVE
|
||||
|
||||
DO CALL contest_status(); // 调用存储过程
|
||||
```
|
||||
|
||||
开启或关闭定时器
|
||||
|
||||
```sql
|
||||
ALTER EVENT contest_event ON COMPLETION PRESERVE ENABLE; -- 开启事件
|
||||
ALTER EVENT contest_event ON COMPLETION PRESERVE DISABLE; -- 关闭事件
|
||||
```
|
||||
|
||||
|
||||
|
||||
contest表
|
||||
|
||||
| 列名 | 实体属性类型 | 键 | 备注 |
|
||||
| ------------ | ------------ | ---- | ----------------------------------------------------- |
|
||||
| id | long | 主键 | auto_increment 1000起步 |
|
||||
| uid | String | 外键 | 创建者id |
|
||||
| title | String | | 比赛标题 |
|
||||
| type | int | | Acm赛制或者Rating |
|
||||
| source | int | | 比赛来源,原创为0,克隆赛为比赛id |
|
||||
| auth | int | | 0为公开赛,1为私有赛(有密码),3为保护赛(有密码)。 |
|
||||
| pwd | string | | 比赛密码 |
|
||||
| start_time | datetime | | 开始时间 |
|
||||
| end_time | datetime | | 结束时间 |
|
||||
| duration | int | | 比赛时长(分) |
|
||||
| explain | Srting | | 比赛说明 |
|
||||
| gmt_create | datetime | | 创建时间 |
|
||||
| gmt_modified | datetime | | 修改时间 |
|
||||
| 列名 | 实体属性类型 | 键 | 备注 |
|
||||
| -------------- | ------------ | ---- | ----------------------------------------------------- |
|
||||
| id | long | 主键 | auto_increment 1000起步 |
|
||||
| uid | String | 外键 | 创建者id |
|
||||
| title | String | | 比赛标题 |
|
||||
| type | int | | Acm赛制或者Rating |
|
||||
| source | int | | 比赛来源,原创为0,克隆赛为比赛id |
|
||||
| auth | int | | 0为公开赛,1为私有赛(有密码),3为保护赛(有密码)。 |
|
||||
| pwd | string | | 比赛密码 |
|
||||
| start_time | datetime | | 开始时间 |
|
||||
| end_time | datetime | | 结束时间 |
|
||||
| duration | long | | 比赛时长(s) |
|
||||
| explain | Srting | | 比赛说明 |
|
||||
| seal_rank | boolean | | 是否开启封榜 |
|
||||
| seal_rank_time | datetime | | 封榜起始时间,一直到比赛结束,不刷新榜单。 |
|
||||
| status | int | | -1为未开始,0为进行中,1为已结束 |
|
||||
| gmt_create | datetime | | 创建时间 |
|
||||
| gmt_modified | datetime | | 修改时间 |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="top.hcode.hoj.dao.ProblemMapper">
|
||||
<select id="getProblemList" resultType="top.hcode.hoj.pojo.vo.ProblemVo" useCache="false">
|
||||
select p.id,title,author,source,total,ac,mle,tle,re,pe,ce,wa,se,score from problem p,problem_count pc
|
||||
select p.id,title,author,type,source,total,ac,mle,tle,re,pe,ce,wa,se,score from problem p,problem_count pc
|
||||
<where>
|
||||
auth = 1 and p.id = pc.pid
|
||||
<if test="pid!=0">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="top.hcode.hoj.dao.ProblemMapper">
|
||||
<select id="getProblemList" resultType="top.hcode.hoj.pojo.vo.ProblemVo" useCache="false">
|
||||
select p.id,title,author,source,total,ac,mle,tle,re,pe,ce,wa,se,score from problem p,problem_count pc
|
||||
select p.id,title,author,type,source,total,ac,mle,tle,re,pe,ce,wa,se,score from problem p,problem_count pc
|
||||
<where>
|
||||
auth = 1 and p.id = pc.pid
|
||||
<if test="pid!=0">
|
||||
|
|
|
@ -48,7 +48,7 @@ public class Contest implements Serializable {
|
|||
@ApiModelProperty(value = "比赛来源,原创为0,克隆赛为比赛id")
|
||||
private Integer source;
|
||||
|
||||
@ApiModelProperty(value = "0为公开赛,1为私有赛(有密码),2为报名赛")
|
||||
@ApiModelProperty(value = "0为公开赛,1为私有赛(有密码),2为保护赛")
|
||||
private Integer auth;
|
||||
|
||||
@ApiModelProperty(value = "比赛密码")
|
||||
|
@ -60,8 +60,17 @@ public class Contest implements Serializable {
|
|||
@ApiModelProperty(value = "结束时间")
|
||||
private Date endTime;
|
||||
|
||||
@ApiModelProperty(value = "比赛时长(分)")
|
||||
private Integer duration;
|
||||
@ApiModelProperty(value = "比赛时长(s)")
|
||||
private Long duration;
|
||||
|
||||
@ApiModelProperty(value = "是否开启封榜")
|
||||
private Boolean sealRank;
|
||||
|
||||
@ApiModelProperty(value = "封榜起始时间,一直到比赛结束,不刷新榜单")
|
||||
private Date sealRankTime;
|
||||
|
||||
@ApiModelProperty(value = "-1为未开始,0为进行中,1为已结束")
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date gmtCreate;
|
||||
|
|
|
@ -32,14 +32,19 @@ public class Problem implements Serializable {
|
|||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "题目")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty(value = "作者")
|
||||
private String author;
|
||||
|
||||
@ApiModelProperty(value = "0为ACM,1为OI")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "单位ms")
|
||||
private Integer timeLimit;
|
||||
|
||||
@ApiModelProperty(value = "单位kb")
|
||||
@ApiModelProperty(value = "单位mb")
|
||||
private Integer memoryLimit;
|
||||
|
||||
@ApiModelProperty(value = "描述")
|
||||
|
@ -63,12 +68,15 @@ public class Problem implements Serializable {
|
|||
@ApiModelProperty(value = "题目难度")
|
||||
private String difficulty;
|
||||
|
||||
@ApiModelProperty(value = "备注")
|
||||
private String comment;
|
||||
@ApiModelProperty(value = "备注,提醒")
|
||||
private String hint;
|
||||
|
||||
@ApiModelProperty(value = "默认为1公开,2为私有,3为比赛中")
|
||||
private Integer auth;
|
||||
|
||||
@ApiModelProperty(value = "该题目对应的相关提交代码,用户是否可用分享")
|
||||
private Boolean codeShare;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date gmtCreate;
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -96,13 +96,13 @@ samp {
|
|||
a {
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
color: #495060!important;
|
||||
color: #495060;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
a:hover {
|
||||
color: #2196f3;
|
||||
color: #2196f3!important;
|
||||
}
|
||||
.drop-menu {
|
||||
padding-top: 7px;
|
||||
|
@ -165,6 +165,10 @@ a:hover {
|
|||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
.row--hover{
|
||||
cursor: pointer;
|
||||
background-color: #ebf7ff!important;
|
||||
}
|
||||
.vxe-table .vxe-body--column:not(.col--ellipsis),
|
||||
.vxe-table .vxe-footer--column:not(.col--ellipsis),
|
||||
.vxe-table .vxe-header--column:not(.col--ellipsis) {
|
||||
|
|
|
@ -16,11 +16,19 @@ function parseRole(num){
|
|||
return '用户'
|
||||
}
|
||||
}
|
||||
function parseContestType(num){
|
||||
if(num==0){
|
||||
return 'ACM'
|
||||
}else if(num==1){
|
||||
return 'OI'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
submissionMemory: utils.submissionMemoryFormat,
|
||||
submissionTime: utils.submissionTimeFormat,
|
||||
localtime: time.utcToLocal,
|
||||
fromNow: fromNow,
|
||||
parseContestType:parseContestType,
|
||||
parseRole:parseRole
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="accordion">
|
||||
<header>
|
||||
<h2>{{title}}</h2>
|
||||
<span class="title">{{ title }}</span>
|
||||
<div class="header_right">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
@ -9,68 +9,69 @@
|
|||
<div class="body" v-show="isOpen">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<footer @click="isOpen = !isOpen"><i :class="{'rotate': !isOpen}" class="el-icon-caret-top"></i></footer>
|
||||
<footer @click="isOpen = !isOpen">
|
||||
<i :class="{ rotate: !isOpen }" class="el-icon-caret-top"></i>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default{
|
||||
name: 'Accordion',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
export default {
|
||||
name: "Accordion",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isOpen: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.accordion{
|
||||
<style scoped>
|
||||
.tit
|
||||
.accordion {
|
||||
border: 1px solid #eaeefb;
|
||||
header{
|
||||
position: relative;
|
||||
h2{
|
||||
font-size: 14px;
|
||||
margin: 0 0 0 10px;
|
||||
line-height: 50px;
|
||||
}
|
||||
.header_right{
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
.body{
|
||||
background-color: #f9fafc;
|
||||
border-top: 1px solid #eaeefb;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
footer{
|
||||
border-top: 1px solid #eaeefb;
|
||||
height: 36px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
text-align: center;
|
||||
margin-top: -1px;
|
||||
color: #d3dce6;
|
||||
cursor: pointer;
|
||||
transition: .2s;
|
||||
&:hover{
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
.rotate{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.accordion header {
|
||||
position: relative;
|
||||
}
|
||||
.title {
|
||||
font-size: 14px;
|
||||
margin: 0 0 0 10px;
|
||||
line-height: 50px;
|
||||
}
|
||||
.header_right {
|
||||
float: right;
|
||||
}
|
||||
.body {
|
||||
background-color: #f9fafc;
|
||||
border-top: 1px solid #eaeefb;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
footer {
|
||||
border-top: 1px solid #eaeefb;
|
||||
height: 36px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
text-align: center;
|
||||
margin-top: -1px;
|
||||
color: #d3dce6;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
}
|
||||
footer:hover {
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
mode: 'text/x-csrc',
|
||||
lineNumbers: true,
|
||||
lineWrapping: false,
|
||||
theme: 'solarized',
|
||||
theme: 'monokai',
|
||||
tabSize: 4,
|
||||
line: true,
|
||||
foldGutter: true,
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div>
|
||||
<vxe-input v-model="keyword" placeholder="Enter keyword"
|
||||
type="search" size="medium" @search-click="filterByKeyword" style="margin-bottom:10px"></vxe-input>
|
||||
<vxe-table
|
||||
:data="problems" :loading="loading"
|
||||
auto-resize
|
||||
stripe
|
||||
align="center"
|
||||
>
|
||||
<vxe-table-column
|
||||
title="ID"
|
||||
min-width="100"
|
||||
field="id">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="150"
|
||||
title="Title"
|
||||
field="title">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="option"
|
||||
align="center"
|
||||
min-width="100">
|
||||
<template v-slot="{row}">
|
||||
<el-tooltip effect="dark" content="添加该题目到比赛中" placement="top">
|
||||
<el-button icon="el-icon-plus" size="mini" @click.native="handleAddProblem(row.id)" type="primary">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
||||
<el-pagination
|
||||
class="page"
|
||||
layout="prev, pager, next"
|
||||
@current-change="getPublicProblem"
|
||||
:page-size="limit"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import api from '@/common/api'
|
||||
|
||||
export default {
|
||||
name: 'add-problem-from-public',
|
||||
fields: ['contestID'],
|
||||
data () {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
problems: [
|
||||
|
||||
{id:1000,title:'测试'}
|
||||
|
||||
],
|
||||
contest: {},
|
||||
keyword: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// api.getContest(this.contestID).then(res => {
|
||||
// this.contest = res.data.data
|
||||
// this.getPublicProblem()
|
||||
// }).catch(() => {
|
||||
// })
|
||||
},
|
||||
methods: {
|
||||
getPublicProblem (page) {
|
||||
this.loading = true
|
||||
let params = {
|
||||
keyword: this.keyword,
|
||||
offset: (page - 1) * this.limit,
|
||||
limit: this.limit,
|
||||
rule_type: this.contest.rule_type
|
||||
}
|
||||
api.getProblemList(params).then(res => {
|
||||
this.loading = false
|
||||
this.total = res.data.data.total
|
||||
this.problems = res.data.data.results
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
handleAddProblem (problemID) {
|
||||
this.$prompt('Please input display id for the contest problem', 'confirm').then(({value}) => {
|
||||
let data = {
|
||||
problem_id: problemID,
|
||||
contest_id: this.contestID,
|
||||
display_id: value
|
||||
}
|
||||
api.addProblemFromPublic(data).then(() => {
|
||||
this.$emit('on-change')
|
||||
}, () => {
|
||||
})
|
||||
}, () => {
|
||||
})
|
||||
},
|
||||
filterByKeyword(){
|
||||
this.getPublicProblem(this.page)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.page {
|
||||
margin-top: 20px;
|
||||
text-align: right
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,18 +0,0 @@
|
|||
<template>
|
||||
<div class="breadcrumb">
|
||||
<el-breadcrumb separator=">">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">Home page</el-breadcrumb-item>
|
||||
<el-breadcrumb-item><slot name="topNavName">PLEASE OVERIDE ME</slot></el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.breadcrumb {
|
||||
margin: 10px;
|
||||
margin-right: 25px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
|
@ -65,6 +65,11 @@ export default {
|
|||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@media screen and (max-width: 1080px) {
|
||||
.info-card {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.info-card-container {
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
|
|
@ -137,61 +137,67 @@
|
|||
</mu-appbar>
|
||||
|
||||
<mu-drawer :open.sync="opendrawer" :docked="false" :right="false">
|
||||
<mu-list @change="opendrawer = false">
|
||||
<mu-list-item button to="/home">
|
||||
<mu-list toggle-nested>
|
||||
<mu-list-item button to="/home" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-s-home" style="font-size: 20px;"></i>
|
||||
<mu-icon value="home" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Home</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/problem">
|
||||
<mu-list-item button to="/problem" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-s-grid" style="font-size: 20px;"></i>
|
||||
<mu-icon value=":el-icon-s-grid" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Problem</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/contest">
|
||||
<mu-list-item button to="/contest" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-trophy" style="font-size: 20px;"></i>
|
||||
<mu-icon value=":el-icon-trophy" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Contest</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/status">
|
||||
<mu-list-item button to="/status" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-s-marketing" style="font-size: 20px;"></i>
|
||||
<mu-icon value=":el-icon-s-marketing" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Status</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/acm-rank">
|
||||
|
||||
<mu-list-item button :ripple="false"
|
||||
nested :open="openSideMenu === 'rank'" @toggle-nested="openSideMenu = arguments[0] ? 'rank' : ''">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-s-data" style="font-size: 20px;"></i>
|
||||
<mu-icon value=":el-icon-s-data" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Rank-ACM</mu-list-item-title>
|
||||
<mu-list-item-title>Rank</mu-list-item-title>
|
||||
<mu-list-item-action>
|
||||
<mu-icon class="toggle-icon" size="24" value="keyboard_arrow_down"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/acm-rank" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-title>ACM Rank</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/oi-rank" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-title>OI Rank</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/oi-rank">
|
||||
<mu-list-item button :ripple="false"
|
||||
nested :open="openSideMenu === 'about'" @toggle-nested="openSideMenu = arguments[0] ? 'about' : ''">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-data-analysis" style="font-size: 20px;"></i>
|
||||
<mu-icon value=":el-icon-info" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Rank-OI</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/introduction" style="font-size: 20px;">
|
||||
<mu-list-item-title>About</mu-list-item-title>
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-info"></i>
|
||||
<mu-icon class="toggle-icon" size="24" value="keyboard_arrow_down"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>About-Introduction</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button to="/developer" style="font-size: 20px;">
|
||||
<mu-list-item-action>
|
||||
<i class="el-icon-user-solid"></i>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>About-Developer</mu-list-item-title>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/introduction" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-title>Introduction</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/developer" @click="opendrawer =!opendrawer">
|
||||
<mu-list-item-title>Developer</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
</mu-list-item>
|
||||
|
||||
</mu-list>
|
||||
|
@ -239,6 +245,7 @@ export default {
|
|||
mobileNar: false,
|
||||
opendrawer:false,
|
||||
openusermenu:false,
|
||||
openSideMenu:'',
|
||||
imgUrl: require("@/assets/logo.png"),
|
||||
};
|
||||
},
|
||||
|
|
|
@ -6,11 +6,16 @@ import Dashboard from '@/views/admin/Dashboard'
|
|||
import User from '@/views/admin/general/User'
|
||||
import Announcement from '@/views/admin/general/Announcement'
|
||||
import SystemConfig from '@/views/admin/general/SystemConfig'
|
||||
import PruneTestCase from '@/views/admin/general/PruneTestCase'
|
||||
import DeleteTestCase from '@/views/admin/general/DeleteTestCase'
|
||||
import ProblemList from '@/views/admin/problem/ProblemList'
|
||||
import Problem from '@/views/admin/problem/Problem'
|
||||
import ProblemImportAndExport from '@/views/admin/problem/ImportAndExport'
|
||||
import Contest from '@/views/admin/contest/Contest'
|
||||
import ContestList from '@/views/admin/contest/ContestList'
|
||||
const adminRoutes= [
|
||||
{
|
||||
path: '/admin/login',
|
||||
name: 'login',
|
||||
name: 'admin-login',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
|
@ -19,88 +24,88 @@ const adminRoutes= [
|
|||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'dashboard',
|
||||
name: 'admin-dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
name: 'user',
|
||||
name: 'admin-user',
|
||||
component: User
|
||||
},
|
||||
{
|
||||
path: 'announcement',
|
||||
name: 'announcement',
|
||||
name: 'admin-announcement',
|
||||
component: Announcement
|
||||
},
|
||||
{
|
||||
path: 'conf',
|
||||
name: 'conf',
|
||||
name: 'admin-conf',
|
||||
component: SystemConfig
|
||||
},
|
||||
{
|
||||
path: 'prune-test-case',
|
||||
name: 'prune-test-case',
|
||||
component: PruneTestCase
|
||||
path: 'delete-test-case',
|
||||
name: 'admin-delete-test-case',
|
||||
component: DeleteTestCase
|
||||
},
|
||||
// {
|
||||
// path: '/problems',
|
||||
// name: 'problem-list',
|
||||
// component: ProblemList
|
||||
// },
|
||||
// {
|
||||
// path: '/problem/create',
|
||||
// name: 'create-problem',
|
||||
// component: Problem
|
||||
// },
|
||||
// {
|
||||
// path: '/problem/edit/:problemId',
|
||||
// name: 'edit-problem',
|
||||
// component: Problem
|
||||
// },
|
||||
// {
|
||||
// path: '/problem/batch_ops',
|
||||
// name: 'problem_batch_ops',
|
||||
// component: ProblemImportOrExport
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/create',
|
||||
// name: 'create-contest',
|
||||
// component: Contest
|
||||
// },
|
||||
// {
|
||||
// path: '/contest',
|
||||
// name: 'contest-list',
|
||||
// component: ContestList
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/:contestId/edit',
|
||||
// name: 'edit-contest',
|
||||
// component: Contest
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/:contestId/announcement',
|
||||
// name: 'contest-announcement',
|
||||
// component: Announcement
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/:contestId/problems',
|
||||
// name: 'contest-problem-list',
|
||||
// component: ProblemList
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/:contestId/problem/create',
|
||||
// name: 'create-contest-problem',
|
||||
// component: Problem
|
||||
// },
|
||||
// {
|
||||
// path: '/contest/:contestId/problem/:problemId/edit',
|
||||
// name: 'edit-contest-problem',
|
||||
// component: Problem
|
||||
// }
|
||||
{
|
||||
path: 'problems',
|
||||
name: 'admin-problem-list',
|
||||
component: ProblemList
|
||||
},
|
||||
{
|
||||
path: 'problem/create',
|
||||
name: 'admin-create-problem',
|
||||
component: Problem
|
||||
},
|
||||
{
|
||||
path: 'problem/edit/:problemId',
|
||||
name: 'admin-edit-problem',
|
||||
component: Problem
|
||||
},
|
||||
{
|
||||
path: 'problem/batch-operation',
|
||||
name: 'admin-problem_batch_operation',
|
||||
component: ProblemImportAndExport
|
||||
},
|
||||
{
|
||||
path: 'contest/create',
|
||||
name: 'admin-create-contest',
|
||||
component: Contest
|
||||
},
|
||||
{
|
||||
path: 'contest',
|
||||
name: 'admin-contest-list',
|
||||
component: ContestList
|
||||
},
|
||||
{
|
||||
path: 'contest/:contestId/edit',
|
||||
name: 'admin-edit-contest',
|
||||
component: Contest
|
||||
},
|
||||
{
|
||||
path: 'contest/:contestId/announcement',
|
||||
name: 'admin-contest-announcement',
|
||||
component: Announcement
|
||||
},
|
||||
{
|
||||
path: 'contest/:contestId/problems',
|
||||
name: 'admin-contest-problem-list',
|
||||
component: ProblemList
|
||||
},
|
||||
{
|
||||
path: 'contest/:contestId/problem/create',
|
||||
name: 'admin-create-contest-problem',
|
||||
component: Problem
|
||||
},
|
||||
{
|
||||
path: 'contest/:contestId/problem/:problemId/edit',
|
||||
name: 'admin-edit-contest-problem',
|
||||
component: Problem
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '*', redirect: '/login'
|
||||
path: '/admin/*', redirect: '/admin/login'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card>
|
||||
<el-card style="margin-top:10px">
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">Judger Service</span>
|
||||
</div>
|
||||
|
@ -295,11 +295,6 @@ export default {
|
|||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media screen and (max-width: 1080px) {
|
||||
.info-container{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
.info-container .info-item {
|
||||
flex: 1 0 auto;
|
||||
min-width: 200px;
|
||||
|
|
|
@ -1,60 +1,96 @@
|
|||
<template>
|
||||
<div class="admin-container">
|
||||
<div v-if="!mobileNar">
|
||||
<el-menu class="vertical_menu"
|
||||
:router="true" :default-active="currentPath" >
|
||||
<div class="logo">
|
||||
<img src="@/assets/logo.png" alt="oj admin"/>
|
||||
</div>
|
||||
<el-menu-item index="/admin/">
|
||||
<i class="fa fa-tachometer" aria-hidden="true"></i>Dashboard
|
||||
</el-menu-item>
|
||||
<!-- <el-submenu v-if="isSuperAdmin" index="general"> -->
|
||||
<el-submenu index="general">
|
||||
<template slot="title"><i class="el-icon-menu"></i>General</template>
|
||||
<el-menu-item index="/admin/user">User</el-menu-item>
|
||||
<el-menu-item index="/admin/announcement">Announcement</el-menu-item>
|
||||
<el-menu-item index="/admin/conf">System Config</el-menu-item>
|
||||
<el-menu-item index="/admin/prune-test-case">Prune Test Case</el-menu-item>
|
||||
</el-submenu>
|
||||
<!-- <el-submenu index="problem" v-if="hasProblemPermission"> -->
|
||||
<el-menu
|
||||
class="vertical_menu"
|
||||
:router="true"
|
||||
:default-active="currentPath"
|
||||
>
|
||||
<div class="logo">
|
||||
<img src="@/assets/logo.png" alt="oj admin" />
|
||||
</div>
|
||||
<el-menu-item index="/admin/">
|
||||
<i class="fa fa-tachometer" aria-hidden="true"></i>Dashboard
|
||||
</el-menu-item>
|
||||
<!-- <el-submenu v-if="isSuperAdmin" index="general"> -->
|
||||
<el-submenu index="general">
|
||||
<template slot="title"><i class="el-icon-menu"></i>General</template>
|
||||
<el-menu-item index="/admin/user">User</el-menu-item>
|
||||
<el-menu-item index="/admin/announcement">Announcement</el-menu-item>
|
||||
<el-menu-item index="/admin/conf">System Config</el-menu-item>
|
||||
<el-menu-item index="/admin/delete-test-case"
|
||||
>Delete Test Case</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
<!-- <el-submenu index="problem" v-if="hasProblemPermission"> -->
|
||||
<el-submenu index="problem">
|
||||
<template slot="title"><i class="fa fa-bars" aria-hidden="true"></i>Problem</template>
|
||||
<el-menu-item index="/admin/problems">Problem List</el-menu-item>
|
||||
<el-menu-item index="/admin/problem/create">Create Problem</el-menu-item>
|
||||
<el-menu-item index="/admin/problem/batch_ops">Export&Import Problem</el-menu-item>
|
||||
|
||||
</el-submenu>
|
||||
<el-submenu index="contest">
|
||||
<template slot="title"><i class="fa fa-trophy" aria-hidden="true"></i>Contest</template>
|
||||
<el-menu-item index="/admin/contest">Contest List</el-menu-item>
|
||||
<el-menu-item index="/admin/contest/create">Create Contest</el-menu-item>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
<template slot="title"
|
||||
><i class="fa fa-bars" aria-hidden="true"></i>Problem</template
|
||||
>
|
||||
<el-menu-item index="/admin/problems">Problem List</el-menu-item>
|
||||
<el-menu-item index="/admin/problem/create"
|
||||
>Create Problem</el-menu-item
|
||||
>
|
||||
<el-menu-item index="/admin/problem/batch-operation"
|
||||
>Export&Import Problem</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
<el-submenu index="contest">
|
||||
<template slot="title"
|
||||
><i class="fa fa-trophy" aria-hidden="true"></i>Contest</template
|
||||
>
|
||||
<el-menu-item index="/admin/contest">Contest List</el-menu-item>
|
||||
<el-menu-item index="/admin/contest/create"
|
||||
>Create Contest</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
<div id="header">
|
||||
<i class="fa fa-font katex-editor" @click="katexVisible=true" ></i>
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span>{{user.username}}<i class="el-icon-caret-bottom el-icon--right"></i></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">Logout</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<div class="breadcrumb-container">
|
||||
<el-breadcrumb separator-class="el-icon-arrow-right">
|
||||
<el-breadcrumb-item :to="{ path: '/admin/' }"
|
||||
>Home page</el-breadcrumb-item
|
||||
>
|
||||
<el-breadcrumb-item v-for="item in routeList" :key="item.path">
|
||||
{{ item.name }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<i class="fa fa-font katex-editor" @click="katexVisible = true"></i>
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span
|
||||
>{{ user.username
|
||||
}}<i class="el-icon-caret-bottom el-icon--right"></i
|
||||
></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">Logout</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else>
|
||||
<mu-appbar class="mobile-nav" color="primary">
|
||||
<mu-appbar class="mobile-nav" color="primary">
|
||||
<mu-button icon slot="left" @click="opendrawer = !opendrawer">
|
||||
<i class="el-icon-s-unfold"></i>
|
||||
</mu-button>
|
||||
HOJ
|
||||
HOJ Admin
|
||||
<mu-menu slot="right" v-show="isAuthenticated">
|
||||
<mu-button flat @click="katexVisible=true" >
|
||||
<i class="fa fa-font katex-editor"></i>
|
||||
<mu-button flat @click="katexVisible = true">
|
||||
<i class="fa fa-font katex-editor"></i>
|
||||
</mu-button>
|
||||
</mu-menu>
|
||||
<mu-menu slot="right" v-show="isAuthenticated" :open.sync="openusermenu">
|
||||
<mu-menu
|
||||
slot="right"
|
||||
v-show="isAuthenticated"
|
||||
:open.sync="openusermenu"
|
||||
>
|
||||
<mu-button flat>
|
||||
{{ user.username }}<i class="el-icon-caret-bottom"></i>
|
||||
</mu-button>
|
||||
|
@ -68,75 +104,161 @@
|
|||
</mu-menu>
|
||||
</mu-appbar>
|
||||
|
||||
|
||||
<mu-drawer :open.sync="opendrawer" :docked="false" :right="false">
|
||||
|
||||
<mu-list toggle-nested>
|
||||
<mu-list-item button :ripple="true" nested to="/admin/">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="true"
|
||||
nested
|
||||
to="/admin/"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-action>
|
||||
<mu-icon value="dashboard" size="24"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Dashboard</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button :ripple="false" nested :open="openSideMenu === 'general'" @toggle-nested="openSideMenu = arguments[0] ? 'general' : ''">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
nested
|
||||
:open="openSideMenu === 'general'"
|
||||
@toggle-nested="openSideMenu = arguments[0] ? 'general' : ''"
|
||||
>
|
||||
<mu-list-item-action>
|
||||
<mu-icon value="view_list"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>General</mu-list-item-title>
|
||||
<mu-list-item-action>
|
||||
<mu-icon class="toggle-icon" size="24" value="keyboard_arrow_down"></mu-icon>
|
||||
<mu-icon
|
||||
class="toggle-icon"
|
||||
size="24"
|
||||
value="keyboard_arrow_down"
|
||||
></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/user"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>User</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/announcement"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Announcement</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/conf"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>System Config</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested">
|
||||
<mu-list-item-title>Prune Test Case</mu-list-item-title>
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/delete-test-case"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Delete Test Case</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
</mu-list-item>
|
||||
|
||||
|
||||
<mu-list-item button :ripple="false" nested :open="openSideMenu === 'problem'" @toggle-nested="openSideMenu = arguments[0] ? 'problem' : ''">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
nested
|
||||
:open="openSideMenu === 'problem'"
|
||||
@toggle-nested="openSideMenu = arguments[0] ? 'problem' : ''"
|
||||
>
|
||||
<mu-list-item-action>
|
||||
<mu-icon value="menu"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Problem</mu-list-item-title>
|
||||
<mu-list-item-action>
|
||||
<mu-icon class="toggle-icon" size="24" value="keyboard_arrow_down"></mu-icon>
|
||||
<mu-icon
|
||||
class="toggle-icon"
|
||||
size="24"
|
||||
value="keyboard_arrow_down"
|
||||
></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/admin/problems">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/problems"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Problem List</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/admin/problem/create">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/problem/create"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Create Problem</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/admin/problem/batch_ops">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/problem/batch-operation"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Export&Import Problem</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
</mu-list-item>
|
||||
|
||||
<mu-list-item button :ripple="false" nested :open="openSideMenu === 'contest'" @toggle-nested="openSideMenu = arguments[0] ? 'contest' : ''">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
nested
|
||||
:open="openSideMenu === 'contest'"
|
||||
@toggle-nested="openSideMenu = arguments[0] ? 'contest' : ''"
|
||||
>
|
||||
<mu-list-item-action>
|
||||
<mu-icon value="assessment"></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item-title>Contest</mu-list-item-title>
|
||||
<mu-list-item-action>
|
||||
<mu-icon class="toggle-icon" size="24" value="keyboard_arrow_down"></mu-icon>
|
||||
<mu-icon
|
||||
class="toggle-icon"
|
||||
size="24"
|
||||
value="keyboard_arrow_down"
|
||||
></mu-icon>
|
||||
</mu-list-item-action>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/admin/contest">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/contest"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Contest List</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item button :ripple="false" slot="nested" to="/admin/contest/create">
|
||||
<mu-list-item
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
to="/admin/contest/create"
|
||||
@click="opendrawer = !opendrawer"
|
||||
>
|
||||
<mu-list-item-title>Create Contest</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
</mu-list-item>
|
||||
|
||||
</mu-list>
|
||||
</mu-drawer>
|
||||
</div>
|
||||
|
@ -146,169 +268,176 @@
|
|||
</transition>
|
||||
</div>
|
||||
|
||||
<el-dialog title="Latex Editor" :visible.sync="katexVisible" width="350px" >
|
||||
<el-dialog title="Latex Editor" :visible.sync="katexVisible" width="350px">
|
||||
<KatexEditor></KatexEditor>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import KatexEditor from '@/components/admin/KatexEditor.vue'
|
||||
import api from '@/common/api'
|
||||
import { mapGetters } from "vuex";
|
||||
import KatexEditor from "@/components/admin/KatexEditor.vue";
|
||||
import api from "@/common/api";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
mounted () {
|
||||
this.currentPath = this.$route.path
|
||||
window.onresize = () => {
|
||||
this.page_width();
|
||||
};
|
||||
export default {
|
||||
name: "app",
|
||||
mounted() {
|
||||
this.currentPath = this.$route.path;
|
||||
this.getBreadcrumb();
|
||||
window.onresize = () => {
|
||||
this.page_width();
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isAuthenticated:true,
|
||||
openusermenu:false,
|
||||
openSideMenu:'',
|
||||
katexVisible: false,
|
||||
opendrawer:false,
|
||||
mobileNar: false,
|
||||
currentPath: '',
|
||||
user:{
|
||||
username:"Himit_ZH"
|
||||
}
|
||||
};
|
||||
this.page_width();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAuthenticated: true,
|
||||
openusermenu: false,
|
||||
openSideMenu: "",
|
||||
katexVisible: false,
|
||||
opendrawer: false,
|
||||
mobileNar: false,
|
||||
currentPath: "",
|
||||
user: {
|
||||
username: "Himit_ZH",
|
||||
},
|
||||
routeList: [],
|
||||
};
|
||||
},
|
||||
components: {
|
||||
KatexEditor,
|
||||
},
|
||||
methods: {
|
||||
handleCommand(command) {
|
||||
if (command === "logout") {
|
||||
api.logout().then(() => {
|
||||
this.$router.push({ name: "/admin/login" });
|
||||
});
|
||||
}
|
||||
},
|
||||
components: {
|
||||
KatexEditor,
|
||||
page_width() {
|
||||
let screenWidth = window.screen.width;
|
||||
if (screenWidth < 1080) {
|
||||
this.mobileNar = true;
|
||||
} else {
|
||||
this.mobileNar = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCommand (command) {
|
||||
if (command === 'logout') {
|
||||
api.logout().then(() => {
|
||||
this.$router.push({name: '/admin/login'})
|
||||
})
|
||||
}
|
||||
},
|
||||
page_width() {
|
||||
let screenWidth = window.screen.width;
|
||||
if (screenWidth < 1080) {
|
||||
this.mobileNar = true;
|
||||
} else {
|
||||
this.mobileNar = false;
|
||||
}
|
||||
},
|
||||
getBreadcrumb() {
|
||||
let matched = this.$route.matched.filter((item) => item.name); //获取路由信息,并过滤保留路由名称信息存入数组
|
||||
this.routeList = matched;
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'isSuperAdmin', 'hasProblemPermission'])
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["userInfo", "isSuperAdmin", "hasProblemPermission"]),
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getBreadcrumb(); //监听路由变化
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vertical_menu {
|
||||
overflow: auto;
|
||||
width: 205px;
|
||||
height: 100%;
|
||||
position: fixed !important;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.vertical_menu .logo {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.vertical_menu .logo img {
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
.fa{
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
.vertical_menu {
|
||||
overflow: auto;
|
||||
width: 205px;
|
||||
height: 100%;
|
||||
position: fixed !important;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.vertical_menu .logo {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.vertical_menu .logo img {
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
.fa {
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:active, a:hover {
|
||||
outline-width: 0
|
||||
}
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none
|
||||
}
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.admin-container {
|
||||
overflow: auto;
|
||||
font-weight: 400;
|
||||
height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: #EDECEC;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.admin-container {
|
||||
overflow: auto;
|
||||
font-weight: 400;
|
||||
height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: #edecec;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.breadcrumb-container {
|
||||
padding: 17px;
|
||||
background-color: #fff;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#header {
|
||||
text-align: right;
|
||||
padding-left: 210px;
|
||||
padding-right: 30px;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
#header {
|
||||
text-align: right;
|
||||
@media screen and (min-width: 1080px) {
|
||||
.content-app {
|
||||
padding-top: 20px;
|
||||
padding-right: 10px;
|
||||
padding-left: 210px;
|
||||
padding-right: 30px;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
background: #F9FAFC;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1080px) {
|
||||
.content-app {
|
||||
padding: 0 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(0, 30px);
|
||||
}
|
||||
|
||||
#header .screen-full {
|
||||
margin-right: 8px;
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1080px) {
|
||||
.content-app {
|
||||
padding-top: 20px;
|
||||
padding-right: 10px;
|
||||
padding-left: 210px;
|
||||
}
|
||||
|
||||
}
|
||||
@media screen and (max-width: 1080px) {
|
||||
.content-app {
|
||||
padding: 0 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(0, 30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInUp-enter-active {
|
||||
animation: fadeInUp .8s;
|
||||
}
|
||||
|
||||
.katex-editor {
|
||||
margin-right: 5px ;
|
||||
cursor: pointer;
|
||||
/*font-size: 18px;*/
|
||||
}
|
||||
|
||||
|
||||
.fadeInUp-enter-active {
|
||||
animation: fadeInUp 0.8s;
|
||||
}
|
||||
|
||||
.katex-editor {
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
/*font-size: 18px;*/
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
<template>
|
||||
<div class="view">
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">
|
||||
{{title}}
|
||||
</span>
|
||||
</div>
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="Contest Title" required>
|
||||
<el-input v-model="contest.title" placeholder="Enter the Contest Title"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="Contest Description" required>
|
||||
<Simditor v-model="contest.explain"></Simditor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest Start Time" required>
|
||||
<el-date-picker
|
||||
v-model="contest.startTime"
|
||||
@change="changeDuration"
|
||||
type="datetime"
|
||||
placeholder="Enter the Contest Start Time">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest End Time" required>
|
||||
<el-date-picker
|
||||
v-model="contest.endTime"
|
||||
@change="changeDuration"
|
||||
type="datetime"
|
||||
placeholder="Enter the Contest End Time">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest Duration" required>
|
||||
<el-input v-model="durationText" disabled>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest Type">
|
||||
<el-radio class="radio" v-model="contest.type" :label="0" :disabled="disableRuleType">ACM</el-radio>
|
||||
<el-radio class="radio" v-model="contest.type" :label="1" :disabled="disableRuleType">OI</el-radio>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24" v-if="contest.sealRank">
|
||||
<el-form-item label="Real Time Rank">
|
||||
<el-switch
|
||||
v-model="contest.sealRank"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="16" :xs="24" v-else>
|
||||
<el-form-item label="Seal Rank">
|
||||
<el-switch
|
||||
v-model="contest.sealRank"
|
||||
active-color="#13ce66"
|
||||
inactive-color="">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Seal Rank Time" :required="contest.sealRank" v-show="contest.sealRank">
|
||||
<el-select v-model="seal_rank_time" >
|
||||
<el-option label="比赛结束前半小时" :value="0" :disabled="contest.duration<1800"></el-option>
|
||||
<el-option label="比赛结束前一小时" :value="1" :disabled="contest.duration<3600"></el-option>
|
||||
<el-option label="比赛全程时间" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest Auth" required>
|
||||
<el-select v-model="contest.auth" >
|
||||
<el-option label="公开赛" :value="0"></el-option>
|
||||
<el-option label="私有赛" :value="1"></el-option>
|
||||
<el-option label="保护赛" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Contest Password" v-show="contest.auth!=0" :required="contest.auth!=0">
|
||||
<el-input v-model="contest.pwd" placeholder="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> -->
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-button type="primary" @click.native="saveContest">Save</el-button>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/common/api'
|
||||
import Simditor from '@/components/admin/Simditor.vue'
|
||||
import time from '@/common/time'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'CreateContest',
|
||||
components: {
|
||||
Simditor
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
title: 'Create Contest',
|
||||
disableRuleType: false,
|
||||
durationText:'', // 比赛时长文本表示
|
||||
seal_rank_time:0, // 当开启封榜模式,即实时榜单关闭时,可选择前半小时,前一小时,全程封榜,默认半小时1800s
|
||||
contest: {
|
||||
title: '',
|
||||
explain: '',
|
||||
startTime: '2020-12-12 12:00:00',
|
||||
endTime: '2020-12-12 17:00:00',
|
||||
duration:0,
|
||||
type: 0,
|
||||
password: '',
|
||||
sealRank: true,
|
||||
sealRankTime:'',//封榜时间
|
||||
auth: 0,
|
||||
allowed_ip_ranges: [{
|
||||
value: ''
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveContest () {
|
||||
let funcName = this.$route.name === 'edit-contest' ? 'editContest' : 'createContest'
|
||||
|
||||
switch(this.seal_rank_time){
|
||||
case 0: // 结束前半小时
|
||||
this.contest.sealRankTime = moment(this.contest.endTime).subtract(1800,'seconds');
|
||||
break;
|
||||
case 1: // 结束前一小时
|
||||
this.contest.sealRankTime = moment(this.contest.endTime).subtract(3600,'seconds');
|
||||
break;
|
||||
case 2: // 全程
|
||||
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
|
||||
api[funcName](data).then(res => {
|
||||
this.$router.push({name: 'admin-contest-list', query: {refresh: 'true'}})
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
changeDuration(){
|
||||
let start = this.contest.startTime;
|
||||
let end = this.contest.endTime
|
||||
let durationMS = time.durationMs(start,end);
|
||||
if(durationMS<0){
|
||||
this.durationText = '比赛起始时间不应该晚于结束时间!'
|
||||
this.contest.duration = 0;
|
||||
return;
|
||||
}
|
||||
if(start!=''&&end!=''){
|
||||
this.durationText = time.duration(start,end);
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
},
|
||||
mounted () {
|
||||
this.changeDuration()
|
||||
if (this.$route.name === 'admin-edit-contest') {
|
||||
this.title = 'Edit Contest'
|
||||
this.disableRuleType = true
|
||||
api.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
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">Contest List</span>
|
||||
<div class="filter-row">
|
||||
<span>
|
||||
<vxe-input v-model="keyword" placeholder="Enter keyword" type="search" size="medium" @search-click="filterByKeyword"></vxe-input>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<vxe-table
|
||||
:loading="loading"
|
||||
ref="xTable"
|
||||
:data="contestList"
|
||||
auto-resize
|
||||
stripe>
|
||||
<vxe-table-column
|
||||
field="id"
|
||||
min-width="80"
|
||||
title="ID">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="title"
|
||||
min-width="150"
|
||||
title="Title">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="Type"
|
||||
min-width="130">
|
||||
<template v-slot="{row}">
|
||||
<el-tag type="gray">{{row.type|parseContestType}}</el-tag>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="Auth"
|
||||
min-width="150">
|
||||
<template v-slot="{row}">
|
||||
<el-tooltip :content="CONTEST_TYPE_REVERSE[row.auth].tips" placement="top" effect="light">
|
||||
<el-tag
|
||||
:type="CONTEST_TYPE_REVERSE[row.auth].color"
|
||||
effect="plain">
|
||||
{{CONTEST_TYPE_REVERSE[row.auth].name}}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="Status"
|
||||
min-width="130">
|
||||
<template v-slot="{row}">
|
||||
<el-tag
|
||||
effect="dark"
|
||||
:color="CONTEST_STATUS_REVERSE[row.status].color"
|
||||
size="medium"
|
||||
>
|
||||
{{CONTEST_STATUS_REVERSE[row.status].name}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="210"
|
||||
title="More"
|
||||
>
|
||||
<template v-slot="{row}">
|
||||
<p>Start Time: {{row.startTime | localtime }}</p>
|
||||
<p>End Time: {{row.endTime | localtime }}</p>
|
||||
<p>Create Time: {{row.gmtGreate | localtime}}</p>
|
||||
<p>Creator: {{row.author}}</p>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="250"
|
||||
title="Option">
|
||||
<template v-slot="{row}">
|
||||
<el-tooltip effect="dark" content="编辑比赛" placement="top">
|
||||
<el-button icon="el-icon-edit" size="mini" @click.native="goEdit(row.id)" type="primary">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="查看比赛题目列表" placement="top">
|
||||
<el-button icon="el-icon-tickets" size="mini" @click.native="goContestProblemList(row.id)" type="success">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="查看比赛公告列表" placement="top">
|
||||
<el-button icon="el-icon-info" size="mini" @click.native="goContestAnnouncement(row.id)" type="info">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="下载通过的提交列表" placement="top">
|
||||
<el-button icon="el-icon-download" size="mini" @click.native="openDownloadOptions(row.id)" type="danger">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
<div class="panel-options">
|
||||
<el-pagination
|
||||
class="page"
|
||||
layout="prev, pager, next"
|
||||
@current-change="currentChange"
|
||||
:page-size="pageSize"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-dialog title="Download Contest Submissions"
|
||||
width="350px"
|
||||
:visible.sync="downloadDialogVisible">
|
||||
<el-switch v-model="excludeAdmin" active-text="Exclude admin submissions"></el-switch>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="downloadSubmissions">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/common/api'
|
||||
import utils from '@/common/utils'
|
||||
import {CONTEST_STATUS_REVERSE,CONTEST_TYPE_REVERSE} from '@/common/constants'
|
||||
|
||||
export default {
|
||||
name: 'ContestList',
|
||||
data () {
|
||||
return {
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
contestList: [
|
||||
{id:1000,startTime:'2020-12-12 12:00:00',endTime:'2020-12-12 17:00:00',gmtGreate:'2020-12-11 12:00:00',
|
||||
author:'Himit_ZH',title:'测试比赛',type:0,auth:0,status:-1
|
||||
}
|
||||
],
|
||||
keyword: '',
|
||||
loading: false,
|
||||
excludeAdmin: true,
|
||||
currentPage: 1,
|
||||
currentId: 1,
|
||||
downloadDialogVisible: false,
|
||||
CONTEST_TYPE_REVERSE:{},
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.CONTEST_TYPE_REVERSE = Object.assign({},CONTEST_TYPE_REVERSE)
|
||||
this.CONTEST_STATUS_REVERSE = Object.assign({},CONTEST_STATUS_REVERSE)
|
||||
// this.getContestList(this.currentPage)
|
||||
},
|
||||
methods: {
|
||||
// 切换页码回调
|
||||
currentChange (page) {
|
||||
this.currentPage = page
|
||||
this.getContestList(page)
|
||||
},
|
||||
getContestList (page) {
|
||||
this.loading = true
|
||||
api.getContestList((page - 1) * this.pageSize, this.pageSize, this.keyword).then(res => {
|
||||
this.loading = false
|
||||
this.total = res.data.data.total
|
||||
this.contestList = res.data.data.results
|
||||
}, res => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openDownloadOptions (contestId) {
|
||||
this.downloadDialogVisible = true
|
||||
this.currentId = contestId
|
||||
},
|
||||
downloadSubmissions () {
|
||||
let excludeAdmin = this.excludeAdmin ? '1' : '0'
|
||||
let url = `/admin/download_submissions?contest_id=${this.currentId}&exclude_admin=${excludeAdmin}`
|
||||
utils.downloadFile(url)
|
||||
},
|
||||
goEdit (contestId) {
|
||||
this.$router.push({name: 'admin-edit-contest', params: {contestId}})
|
||||
},
|
||||
goContestAnnouncement (contestId) {
|
||||
this.$router.push({name: 'admin-contest-announcement', params: {contestId}})
|
||||
},
|
||||
goContestProblemList (contestId) {
|
||||
this.$router.push({name: 'admin-contest-problem-list', params: {contestId}})
|
||||
},
|
||||
filterByKeyword(){
|
||||
this.currentChange(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.filter-row{
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
.el-tag--dark{
|
||||
border-color: #FFF;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<span slot="header" class="panel-title home-title">Prune Test Case
|
||||
<span slot="header" class="panel-title home-title">Delete Test Case
|
||||
<el-popover placement="right" trigger="hover">
|
||||
这些测试用例不属于任何问题,您可以安全地清理它们。
|
||||
<i slot="reference" class="el-icon-question"></i>
|
|
@ -44,11 +44,11 @@
|
|||
</vxe-table-column>
|
||||
<vxe-table-column title="Option" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<el-tooltip class="item" effect="dark" content="编辑用户" placement="top">
|
||||
<el-tooltip effect="dark" content="编辑用户" placement="top">
|
||||
<el-button icon="el-icon-edit-outline" size="mini" @click.native="openUserDialog(row)" type="primary">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="删除用户" placement="top">
|
||||
<el-tooltip effect="dark" content="删除用户" placement="top">
|
||||
<el-button icon="el-icon-delete-solid" size="mini" @click.native="deleteUsers([row.uid])" type="danger">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
@ -78,7 +78,7 @@
|
|||
:show-file-list="false"
|
||||
accept=".csv"
|
||||
:before-upload="handleUsersCSV">
|
||||
<el-button size="small" icon="el-icon-upload" type="primary">Choose File</el-button>
|
||||
<el-button size="small" icon="el-icon-folder-opened" type="primary">Choose File</el-button>
|
||||
</el-upload>
|
||||
<template v-else>
|
||||
|
||||
|
@ -319,7 +319,7 @@
|
|||
})
|
||||
},
|
||||
filterByKeyword(){
|
||||
console.log("ssssssss")
|
||||
this.currentChange(1)
|
||||
},
|
||||
// 打开用户对话框
|
||||
openUserDialog (row) {
|
||||
|
@ -418,9 +418,6 @@
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
'keyword' () {
|
||||
this.currentChange(1)
|
||||
},
|
||||
'uploadUsersCurrentPage' (page) {
|
||||
this.uploadUsersPage = this.uploadUsers.slice((page - 1) * this.uploadUsersPageSize, page * this.uploadUsersPageSize)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">Export Problems</span>
|
||||
<div class="filter-row">
|
||||
<span>
|
||||
<el-button type="primary" size="small"
|
||||
@click="exportProblems" icon="el-icon-arrow-down">Export
|
||||
</el-button>
|
||||
</span>
|
||||
<span>
|
||||
<vxe-input v-model="keyword" placeholder="Enter keyword" type="search" size="medium" @search-click="filterByKeyword"></vxe-input>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<vxe-table :data="problems" stripe auto-resize
|
||||
ref="xTable"
|
||||
:loading="loadingProblems"
|
||||
:checkbox-config="{labelField: '', highlight: true, range: true}"
|
||||
@checkbox-change="handleSelectionChange"
|
||||
@checkbox-all="handlechangeAll">
|
||||
<vxe-table-column type="checkbox" width="60">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="ID"
|
||||
min-width="100"
|
||||
field="id">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="150"
|
||||
title="Title"
|
||||
field="title">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="150"
|
||||
field="author"
|
||||
title="Author">
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column
|
||||
field="gmtCreate"
|
||||
title="Create Time">
|
||||
<template v-slot="{row}">
|
||||
{{row.create_time | localtime }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
||||
<div class="panel-options">
|
||||
<el-pagination
|
||||
class="page"
|
||||
layout="prev, pager, next"
|
||||
@current-change="getProblems"
|
||||
:page-size="limit"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<el-card style="margin-top:15px">
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">Import QDUOJ Problems</span>
|
||||
</div>
|
||||
<el-upload
|
||||
ref="QDU"
|
||||
action="/api/admin/import_problem"
|
||||
name="file"
|
||||
:file-list="fileList1"
|
||||
:show-file-list="true"
|
||||
:with-credentials="true"
|
||||
:limit="3"
|
||||
:on-change="onFile1Change"
|
||||
:auto-upload="false"
|
||||
:on-success="uploadSucceeded"
|
||||
:on-error="uploadFailed">
|
||||
<el-button size="small" type="primary" slot="trigger" icon="el-icon-folder-opened">Choose File</el-button>
|
||||
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload('QDU')" icon="el-icon-upload">Upload</el-button>
|
||||
</el-upload>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import api from '@/common/api'
|
||||
import utils from '@/common/utils'
|
||||
|
||||
export default {
|
||||
name: 'import_and_export',
|
||||
data () {
|
||||
return {
|
||||
fileList1: [],
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loadingProblems: false,
|
||||
loadingImporting: false,
|
||||
keyword: '',
|
||||
problems: [
|
||||
{id:1001,author:'Himit_ZH',title:'测试题目',gmtCreate:'2020-11-11 11:11:11'}
|
||||
],
|
||||
selected_problems: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// this.getProblems()
|
||||
},
|
||||
methods: {
|
||||
// 题目表部分勾选 改变选中的内容
|
||||
handleSelectionChange ({records }) {
|
||||
this.selected_problems = records
|
||||
},
|
||||
|
||||
// 一键全部选中,改变选中的内容列表
|
||||
handlechangeAll () {
|
||||
this.selected_problems = this.$refs.xTable.getCheckboxRecords();
|
||||
},
|
||||
|
||||
getProblems (page = 1) {
|
||||
let params = {
|
||||
keyword: this.keyword,
|
||||
offset: (page - 1) * this.limit,
|
||||
limit: this.limit
|
||||
}
|
||||
this.loadingProblems = true
|
||||
api.getProblemList(params).then(res => {
|
||||
this.problems = res.data.data.results
|
||||
this.total = res.data.data.total
|
||||
this.loadingProblems = false
|
||||
})
|
||||
},
|
||||
exportProblems () {
|
||||
let params = []
|
||||
for (let p of this.selected_problems) {
|
||||
params.push('problem_id=' + p.id)
|
||||
}
|
||||
let url = '/admin/export_problem?' + params.join('&')
|
||||
utils.downloadFile(url)
|
||||
},
|
||||
submitUpload (ref) {
|
||||
this.$refs[ref].submit()
|
||||
},
|
||||
onFile1Change (file, fileList) {
|
||||
this.fileList1 = fileList.slice(-1)
|
||||
},
|
||||
onFile2Change (file, fileList) {
|
||||
this.fileList2 = fileList.slice(-1)
|
||||
},
|
||||
uploadSucceeded (response) {
|
||||
if (response.error) {
|
||||
this.$error(response.data)
|
||||
} else {
|
||||
this.$success('Successfully imported ' + response.data.import_count + ' problems')
|
||||
this.getProblems()
|
||||
}
|
||||
},
|
||||
uploadFailed () {
|
||||
this.$error('Upload failed')
|
||||
},
|
||||
filterByKeyword(){
|
||||
this.getProblems()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-row{
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,917 @@
|
|||
<template>
|
||||
<div class="problem">
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">{{ title }}</span>
|
||||
</div>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="problem"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
label-width="70px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item prop="title" label="Title" required>
|
||||
<el-input
|
||||
placeholder="Enter the title of problem"
|
||||
v-model="problem.title"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item prop="description" label="Description" required>
|
||||
<Simditor v-model="problem.description"></Simditor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Time Limit(ms)" required>
|
||||
<el-input
|
||||
type="Number"
|
||||
placeholder="Enter the time limit of problem"
|
||||
v-model="problem.timeLimit"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Memory Limit(mb)" required>
|
||||
<el-input
|
||||
type="Number"
|
||||
placeholder="Enter the memory limit of problem"
|
||||
v-model="problem.memoryLimit"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="8" :xs="24">
|
||||
<el-form-item label="Level" required>
|
||||
<el-select
|
||||
class="difficulty-select"
|
||||
|
||||
placeholder="Enter the level of problem"
|
||||
v-model="problem.difficulty"
|
||||
>
|
||||
<el-option label="Easy" :value="0"></el-option>
|
||||
<el-option label="Mid" :value="1"></el-option>
|
||||
<el-option label="Hard" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
prop="input_description"
|
||||
label="Input Description"
|
||||
required
|
||||
>
|
||||
<Simditor v-model="problem.input"></Simditor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
prop="output_description"
|
||||
label="Output Description"
|
||||
required
|
||||
>
|
||||
<Simditor v-model="problem.output"></Simditor>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="4" :xs="24">
|
||||
<el-form-item label="Auth">
|
||||
<el-select v-model="problem.auth" size="small">
|
||||
<el-option label="公开" :value="1"></el-option>
|
||||
<el-option label="私有" :value="2"></el-option>
|
||||
<el-option label="比赛中" :value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="4" :xs="24">
|
||||
<el-form-item label="Code Shareable">
|
||||
<el-switch
|
||||
v-model="problem.code_share"
|
||||
active-text=""
|
||||
inactive-text="">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :md="16" :xs="24">
|
||||
<el-form-item label="Tags" required>
|
||||
<el-tag
|
||||
v-for="tag in problem.tags"
|
||||
closable
|
||||
:close-transition="false"
|
||||
:key="tag.name"
|
||||
size="small"
|
||||
@close="closeTag(tag.name)"
|
||||
style="margin-right: 7px;margin-top:4px"
|
||||
>{{ tag.name }}</el-tag
|
||||
>
|
||||
<!-- 输入时建议,回车,选择,光标消失触发更新 -->
|
||||
<el-autocomplete
|
||||
v-if="inputVisible"
|
||||
size="mini"
|
||||
class="input-new-tag"
|
||||
v-model="tagInput"
|
||||
:trigger-on-focus="false"
|
||||
@keyup.enter.native="addTag"
|
||||
@blur="addTag"
|
||||
@select="addTag"
|
||||
:fetch-suggestions="querySearch"
|
||||
>
|
||||
</el-autocomplete>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="添加新标签"
|
||||
placement="top"
|
||||
v-else
|
||||
>
|
||||
<el-button
|
||||
class="button-new-tag"
|
||||
size="small"
|
||||
@click="inputVisible = true"
|
||||
icon="el-icon-plus"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :md="24" :xs="24">
|
||||
<el-form-item label="Language" :error="error.languages" required>
|
||||
<el-checkbox-group v-model="problem.languages">
|
||||
<el-tooltip
|
||||
class="spj-radio"
|
||||
v-for="lang in allLanguage.languages"
|
||||
:key="lang.name"
|
||||
effect="dark"
|
||||
:content="lang.description"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-checkbox :label="lang.name"></el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<div class="panel-title home-title">
|
||||
Problem Examples
|
||||
<el-popover placement="right" trigger="hover">
|
||||
<p>题目样例:请最好不要超过2个题目样例,题面样例不纳入评测数据。</p>
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-form-item
|
||||
v-for="(example, index) in problem.examples"
|
||||
:key="'example' + index"
|
||||
>
|
||||
<Accordion :title="'Example' + (index + 1)">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-delete"
|
||||
slot="header"
|
||||
@click="deleteExample(index)"
|
||||
>
|
||||
Delete
|
||||
</el-button>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Input Example" required>
|
||||
<el-input
|
||||
:rows="5"
|
||||
type="textarea"
|
||||
placeholder="Input Example"
|
||||
v-model="example.input"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Output Example" required>
|
||||
<el-input
|
||||
:rows="5"
|
||||
type="textarea"
|
||||
placeholder="Output Example"
|
||||
v-model="example.output"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Accordion>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="add-example-btn">
|
||||
<el-button
|
||||
class="add-examples"
|
||||
@click="addExample()"
|
||||
icon="el-icon-plus"
|
||||
type="small"
|
||||
>Add Example
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="panel-title home-title">
|
||||
Judge Samples
|
||||
<el-popover placement="right" trigger="hover">
|
||||
<p>评测数据:判题机对该题目的相关提交进行评测的数据来源。</p>
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-form-item
|
||||
v-for="(sample, index) in problem.samples"
|
||||
:key="'sample' + index"
|
||||
>
|
||||
<Accordion :title="'Sample' + (index + 1)">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-delete"
|
||||
slot="header"
|
||||
@click="deleteSample(index)"
|
||||
>
|
||||
Delete
|
||||
</el-button>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Input Sample" required>
|
||||
<el-input
|
||||
:rows="5"
|
||||
type="textarea"
|
||||
placeholder="Input Sample"
|
||||
v-model="sample.input"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Output Sample" required>
|
||||
<el-input
|
||||
:rows="5"
|
||||
type="textarea"
|
||||
placeholder="Output Sample"
|
||||
v-model="sample.output"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Accordion>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="add-sample-btn">
|
||||
<el-button
|
||||
class="add-samples"
|
||||
@click="addSample()"
|
||||
icon="el-icon-plus"
|
||||
type="small"
|
||||
>Add Sample
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="panel-title home-title">
|
||||
Special Judge
|
||||
<el-popover placement="right" trigger="hover">
|
||||
<p>使用特殊判题的原因:</p>
|
||||
<p>1. 题目要求的输出结果可能不唯一,允许不同结果存在</p>
|
||||
<p>2. 题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以。
|
||||
例如题目要求保留几位小数,输出结果后几位小数不相同也是正确的。</p>
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-form-item label="" :error="error.spj">
|
||||
<el-col :span="24">
|
||||
<el-checkbox
|
||||
v-model="problem.spj"
|
||||
@click.native.prevent="switchSpj()"
|
||||
>Use Special Judge</el-checkbox
|
||||
>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="problem.spj">
|
||||
<Accordion title="Special Judge Code">
|
||||
<template slot="header">
|
||||
<span style="margin-right:5px;">SPJ language:</span>
|
||||
<el-radio-group v-model="problem.spj_language">
|
||||
<el-tooltip
|
||||
class="spj-radio"
|
||||
v-for="lang in allLanguage.spj_languages"
|
||||
:key="lang.name"
|
||||
effect="dark"
|
||||
:content="lang.description"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-radio :label="lang.name">{{ lang.name }}</el-radio>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="el-icon-fa-random"
|
||||
@click="compileSPJ"
|
||||
:loading="loadingCompile"
|
||||
style="margin-left:10px"
|
||||
>Complie
|
||||
</el-button>
|
||||
</template>
|
||||
<code-mirror
|
||||
v-model="problem.spj_code"
|
||||
:mode="spjMode"
|
||||
></code-mirror>
|
||||
</Accordion>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="margin-top: 20px" label="Hint">
|
||||
<Simditor v-model="problem.hint"></Simditor>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-form-item label="Type">
|
||||
<el-radio-group
|
||||
v-model="problem.type"
|
||||
:disabled="disableRuleType"
|
||||
>
|
||||
<el-radio :label="0" >ACM</el-radio>
|
||||
<el-radio :label="1" >OI</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-form-item label="TestCase" :error="error.testcase">
|
||||
<el-upload
|
||||
action="/api/admin/test_case"
|
||||
name="file"
|
||||
:data="{ spj: problem.spj }"
|
||||
:show-file-list="true"
|
||||
:on-success="uploadSucceeded"
|
||||
:on-error="uploadFailed"
|
||||
>
|
||||
<el-button size="small" type="primary" icon="el-icon-upload"
|
||||
>Choose File</el-button
|
||||
>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
|
||||
<vxe-table stripe auto-resize :data="problem.test_case_score">
|
||||
<vxe-table-column field="input_name" title="Input" min-width="150">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="output_name" title="Output" min-width="150">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="score" title="Score" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="Score"
|
||||
v-model="row.score"
|
||||
:disabled="problem.type !== 'OI'"
|
||||
>
|
||||
</el-input>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="Source">
|
||||
<el-input
|
||||
placeholder="Enter the problem where from"
|
||||
v-model="problem.source"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click.native="submit()" size="small"
|
||||
>Save</el-button
|
||||
>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Simditor from "@/components/admin/Simditor";
|
||||
import Accordion from "@/components/admin/Accordion";
|
||||
import CodeMirror from "@/components/admin/CodeMirror";
|
||||
import api from "@/common/api";
|
||||
|
||||
export default {
|
||||
name: "Problem",
|
||||
components: {
|
||||
Simditor,
|
||||
Accordion,
|
||||
CodeMirror,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
title: {
|
||||
required: true,
|
||||
message: "Title is required",
|
||||
trigger: "blur",
|
||||
},
|
||||
input_description: {
|
||||
required: true,
|
||||
message: "Input Description is required",
|
||||
trigger: "blur",
|
||||
},
|
||||
output_description: {
|
||||
required: true,
|
||||
message: "Output Description is required",
|
||||
trigger: "blur",
|
||||
},
|
||||
},
|
||||
loadingCompile: false,
|
||||
mode: "", // 该题目是编辑或者创建
|
||||
contest: {},
|
||||
problem: {
|
||||
id: "",
|
||||
title: "",
|
||||
description: "",
|
||||
input_description: "",
|
||||
output_description: "",
|
||||
time_limit: 1000,
|
||||
memory_limit: 256,
|
||||
difficulty: 0,
|
||||
auth: 1,
|
||||
code_share: true,
|
||||
tags: [
|
||||
{
|
||||
id:1001,
|
||||
name:'模拟题'
|
||||
},
|
||||
{
|
||||
id:1002,
|
||||
name:'递归题'
|
||||
}
|
||||
],
|
||||
languages: [],
|
||||
examples:[{ input: "", output: "" }], // 题面上的样例输入输出
|
||||
samples: [{ input: "", output: "" }], // 判题机使用的样例
|
||||
spj: false,
|
||||
spj_language: "",
|
||||
spj_code: "",
|
||||
spj_compile_ok: false,
|
||||
test_case_id: "",
|
||||
test_case_score: [
|
||||
{input_name:'1.in',output_name:'1.out',score:100}
|
||||
],
|
||||
type: 0,
|
||||
hint: "",
|
||||
source: "",
|
||||
},
|
||||
|
||||
reProblem: {
|
||||
languages: [],
|
||||
},
|
||||
testCaseUploaded: false,
|
||||
allLanguage: {
|
||||
languages:[
|
||||
{
|
||||
content_type: "text/x-csrc",
|
||||
description: "GCC 5.4",
|
||||
name: "C",
|
||||
compile_command: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c11 {src_path} -lm -o {exe_path}",
|
||||
template: "#include <stdio.h>\nint add(int a, int b) {\n return a+b;\n}\nint main() {\n printf(\"%d\", add(1, 2));\n return 0;\n}"
|
||||
},
|
||||
{
|
||||
content_type: "text/x-c++src",
|
||||
description: "G++ 5.4",
|
||||
name: "C++",
|
||||
compile_command: "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {src_path} -lm -o {exe_path}",
|
||||
template: "#include <iostream>\nint add(int a, int b) {\n return a+b;\n}\nint main() {\n std::cout << add(1, 2);\n return 0;\n}",
|
||||
},
|
||||
{ content_type: "text/x-java",
|
||||
description: "OpenJDK 1.8",
|
||||
name: "Java",
|
||||
compile_command: "/usr/bin/javac {src_path} -d {exe_dir} -encoding UTF8",
|
||||
template: "import java.util.Scanner;\npublic class Main{\n public static void main(String[] args){\n Scanner in=new Scanner(System.in);\n int a=in.nextInt();\n int b=in.nextInt();\n System.out.println((a+b));\n }\n}"
|
||||
},
|
||||
{
|
||||
content_type: "text/x-python",
|
||||
description: "Python 3.7",
|
||||
name: "Python3",
|
||||
template: "a, b = map(int, input().split())\nprint(a + b)",
|
||||
compile_command: "/usr/bin/python3 -m py_compile {src_path}"
|
||||
}
|
||||
],
|
||||
spj_languages:[
|
||||
{
|
||||
content_type: "text/x-csrc",
|
||||
description: "GCC 5.4",
|
||||
name: "C",
|
||||
compile_command: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c11 {src_path} -lm -o {exe_path}",
|
||||
template: "#include <stdio.h>\nint add(int a, int b) {\n return a+b;\n}\nint main() {\n printf(\"%d\", add(1, 2));\n return 0;\n}"
|
||||
},
|
||||
{
|
||||
content_type: "text/x-c++src",
|
||||
description: "G++ 5.4",
|
||||
name: "C++",
|
||||
compile_command: "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {src_path} -lm -o {exe_path}",
|
||||
template: "#include <iostream>\nint add(int a, int b) {\n return a+b;\n}\nint main() {\n std::cout << add(1, 2);\n return 0;\n}",
|
||||
},
|
||||
]
|
||||
},
|
||||
inputVisible: false,
|
||||
tagInput: '',
|
||||
title: "",
|
||||
spjMode: "",
|
||||
disableRuleType: false,
|
||||
routeName: "",
|
||||
error: {
|
||||
tags: "",
|
||||
spj: "",
|
||||
languages: "",
|
||||
testCase: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.routeName = this.$route.name;
|
||||
if (
|
||||
this.routeName === "edit-problem" ||
|
||||
this.routeName === "edit-contest-problem"
|
||||
) {
|
||||
this.mode = "edit";
|
||||
} else {
|
||||
this.mode = "add";
|
||||
}
|
||||
api.getLanguages().then((res) => {
|
||||
this.problem = this.reProblem = {
|
||||
id: "",
|
||||
title: "",
|
||||
description: "",
|
||||
input_description: "",
|
||||
output_description: "",
|
||||
time_limit: 1000,
|
||||
memory_limit: 256,
|
||||
difficulty: 0,
|
||||
visible: true,
|
||||
share_submission: false,
|
||||
tags: [],
|
||||
languages: [],
|
||||
samples: [{ input: "", output: "" }],
|
||||
spj: false,
|
||||
spj_language: "",
|
||||
spj_code: "",
|
||||
spj_compile_ok: false,
|
||||
test_case_id: "",
|
||||
test_case_score: [],
|
||||
type: 0,
|
||||
hint: "",
|
||||
source: "",
|
||||
};
|
||||
let contestID = this.$route.params.contestId;
|
||||
if (contestID) {
|
||||
this.problem.contest_id = this.reProblem.contest_id = contestID;
|
||||
this.disableRuleType = true;
|
||||
api.getContest(contestID).then((res) => {
|
||||
this.problem.type = this.reProblem.type =
|
||||
res.data.data.type;
|
||||
this.contest = res.data.data;
|
||||
});
|
||||
}
|
||||
|
||||
this.problem.spj_language = "C";
|
||||
let allLanguage = res.data.data;
|
||||
this.allLanguage = allLanguage;
|
||||
|
||||
// get problem after getting languages list to avoid find undefined value in `watch problem.languages`
|
||||
if (this.mode === "edit") {
|
||||
this.title = "Edit Problem";
|
||||
let funcName = {
|
||||
"admin-edit-problem": "getProblem",
|
||||
"admin-edit-contest-problem": "getContestProblem",
|
||||
}[this.routeName];
|
||||
// api[funcName](this.$route.params.problemId).then((problemRes) => {
|
||||
// let data = problemRes.data.data;
|
||||
// if (!data.spj_code) {
|
||||
// data.spj_code = "";
|
||||
// }
|
||||
// data.spj_language = data.spj_language || "C";
|
||||
// this.problem = data;
|
||||
// this.testCaseUploaded = true;
|
||||
// });
|
||||
} else {
|
||||
this.title = "Add Problem";
|
||||
for (let item of allLanguage.languages) {
|
||||
this.problem.languages.push(item.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.$refs.form.resetFields();
|
||||
this.problem = this.reProblem;
|
||||
},
|
||||
|
||||
"problem.spj_language"(newVal) {
|
||||
this.spjMode = this.allLanguage.spj_languages.find((item) => {
|
||||
return item.name === this.problem.spj_language;
|
||||
}).content_type;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
switchSpj() {
|
||||
if (this.testCaseUploaded) {
|
||||
this.$confirm(
|
||||
"If you change problem judge method, you need to re-upload test cases",
|
||||
"Warning",
|
||||
{
|
||||
confirmButtonText: "Yes",
|
||||
cancelButtonText: "Cancel",
|
||||
type: "warning",
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.problem.spj = !this.problem.spj;
|
||||
this.resetTestCase();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
this.problem.spj = !this.problem.spj;
|
||||
}
|
||||
},
|
||||
querySearch(queryString, callback) {
|
||||
// api
|
||||
// .getProblemTagList()
|
||||
// .then((res) => {
|
||||
// let tagList = [];
|
||||
// for (let tag of res.data.data) {
|
||||
// tagList.push({ value: tag.name});
|
||||
// }
|
||||
// callback(tagList);
|
||||
// })
|
||||
// .catch(() => {});
|
||||
let tagList = [{value:'简单题'}];
|
||||
callback(tagList);
|
||||
},
|
||||
resetTestCase() {
|
||||
this.testCaseUploaded = false;
|
||||
this.problem.test_case_score = [];
|
||||
this.problem.test_case_id = "";
|
||||
},
|
||||
addTag() {
|
||||
let newTag ={
|
||||
name:this.tagInput,
|
||||
}
|
||||
if (newTag) {
|
||||
this.problem.tags.push(newTag);
|
||||
}
|
||||
this.inputVisible = false;
|
||||
this.tagInput = '';
|
||||
},
|
||||
|
||||
// 根据tagID从题目的tags列表中移除
|
||||
closeTag(tag) {
|
||||
this.problem.tags.splice(this.problem.tags.indexOf(tag), 1);
|
||||
},
|
||||
// 添加题目样例
|
||||
addExample(){
|
||||
this.problem.examples.push({ input: "", output: "" });
|
||||
},
|
||||
// 添加判题机的测试样例
|
||||
addSample() {
|
||||
this.problem.samples.push({ input: "", output: "" });
|
||||
},
|
||||
//根据下标删除特定的题目样例
|
||||
deleteExample(index){
|
||||
this.problem.examples.splice(index, 1);
|
||||
},
|
||||
//根据下标删除特定的判题机测试样例
|
||||
deleteSample(index) {
|
||||
this.problem.samples.splice(index, 1);
|
||||
},
|
||||
uploadSucceeded(response) {
|
||||
if (response.error) {
|
||||
this.$error(response.data);
|
||||
return;
|
||||
}
|
||||
let fileList = response.data.info;
|
||||
for (let file of fileList) {
|
||||
file.score = (100 / fileList.length).toFixed(0);
|
||||
if (!file.output_name && this.problem.spj) {
|
||||
file.output_name = "-";
|
||||
}
|
||||
}
|
||||
this.problem.test_case_score = fileList;
|
||||
this.testCaseUploaded = true;
|
||||
this.problem.test_case_id = response.data.id;
|
||||
},
|
||||
uploadFailed() {
|
||||
this.$error("Upload failed");
|
||||
},
|
||||
|
||||
compileSPJ() {
|
||||
let data = {
|
||||
id: this.problem.id,
|
||||
spj_code: this.problem.spj_code,
|
||||
spj_language: this.problem.spj_language,
|
||||
};
|
||||
this.loadingCompile = true;
|
||||
api.compileSPJ(data).then(
|
||||
(res) => {
|
||||
this.loadingCompile = false;
|
||||
this.problem.spj_compile_ok = true;
|
||||
this.error.spj = "";
|
||||
},
|
||||
(err) => {
|
||||
this.loadingCompile = false;
|
||||
this.problem.spj_compile_ok = false;
|
||||
const h = this.$createElement;
|
||||
this.$msgbox({
|
||||
title: "Compile Error",
|
||||
type: "error",
|
||||
message: h("pre", err.data.data),
|
||||
showCancelButton: false,
|
||||
closeOnClickModal: false,
|
||||
customClass: "dialog-compile-error",
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
submit() {
|
||||
if (!this.problem.samples.length) {
|
||||
this.$error("Sample is required");
|
||||
return;
|
||||
}
|
||||
for (let sample of this.problem.samples) {
|
||||
if (!sample.input || !sample.output) {
|
||||
this.$error("Sample input and output is required");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!this.problem.tags.length) {
|
||||
this.error.tags = "Please add at least one tag";
|
||||
this.$error(this.error.tags);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.problem.spj) {
|
||||
if (!this.problem.spj_code) {
|
||||
this.error.spj = "Spj code is required";
|
||||
this.$error(this.error.spj);
|
||||
} else if (!this.problem.spj_compile_ok) {
|
||||
this.error.spj = "SPJ code has not been successfully compiled";
|
||||
}
|
||||
if (this.error.spj) {
|
||||
this.$error(this.error.spj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.problem.languages.length) {
|
||||
this.error.languages =
|
||||
"Please choose at least one language for problem";
|
||||
this.$error(this.error.languages);
|
||||
return;
|
||||
}
|
||||
if (!this.testCaseUploaded) {
|
||||
this.error.testCase = "Test case is not uploaded yet";
|
||||
this.$error(this.error.testCase);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.problem.type === "OI") {
|
||||
for (let item of this.problem.test_case_score) {
|
||||
try {
|
||||
if (parseInt(item.score) <= 0) {
|
||||
this.$error("Invalid test case score");
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.$error("Test case score must be an integer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.problem.languages = this.problem.languages.sort();
|
||||
let funcName = {
|
||||
"create-problem": "createProblem",
|
||||
"edit-problem": "editProblem",
|
||||
"create-contest-problem": "createContestProblem",
|
||||
"edit-contest-problem": "editContestProblem",
|
||||
}[this.routeName];
|
||||
// edit contest problem 时, contest_id会被后来的请求覆盖掉
|
||||
if (funcName === "editContestProblem") {
|
||||
this.problem.contest_id = this.contest.id;
|
||||
}
|
||||
api[funcName](this.problem)
|
||||
.then((res) => {
|
||||
if (
|
||||
this.routeName === "create-contest-problem" ||
|
||||
this.routeName === "edit-contest-problem"
|
||||
) {
|
||||
this.$router.push({
|
||||
name: "contest-problem-list",
|
||||
params: { contestId: this.$route.params.contestId },
|
||||
});
|
||||
} else {
|
||||
this.$router.push({ name: "problem-list" });
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/.el-form-item__label {
|
||||
padding: 0!important;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 10px!important;
|
||||
}
|
||||
.difficulty-select {
|
||||
width: 120px;
|
||||
}
|
||||
.input-new-tag {
|
||||
width: 78px;
|
||||
}
|
||||
.button-new-tag {
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.accordion {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.add-examples {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border: 1px dashed #2d8cf0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: #2d8cf0;
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.add-examples i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.add-examples:hover {
|
||||
border:0px;
|
||||
background-color: #2d8cf0;
|
||||
color: #fff;
|
||||
}
|
||||
.add-example-btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.add-samples {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border: 1px dashed #19be6b;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: #19be6b;
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.add-samples i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.add-samples:hover {
|
||||
border:0px;
|
||||
background-color: #19be6b;
|
||||
color: #fff;
|
||||
}
|
||||
.add-sample-btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.dialog-compile-error {
|
||||
width: auto;
|
||||
max-width: 80%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,245 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span class="panel-title home-title">{{contestId ?'Contest Problem List': 'Problem List'}}</span>
|
||||
<div class="filter-row">
|
||||
<span>
|
||||
<el-button type="primary" size="small"
|
||||
@click="goCreateProblem" icon="el-icon-plus">Create
|
||||
</el-button>
|
||||
<el-button v-if="contestId" type="primary"
|
||||
size="small" icon="el-icon-plus"
|
||||
@click="addProblemDialogVisible = true">Add From Public Problem
|
||||
</el-button>
|
||||
</span>
|
||||
<span>
|
||||
<vxe-input v-model="keyword" placeholder="Enter keyword" type="search" size="medium" @search-click="filterByKeyword"></vxe-input>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<vxe-table stripe auto-resize :data="problemList"
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
@row-dblclick="handleDblclick"
|
||||
align="center"
|
||||
>
|
||||
<vxe-table-column
|
||||
min-width="100"
|
||||
field="id"
|
||||
title="ID">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="title"
|
||||
min-width="150"
|
||||
title="Title">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="author"
|
||||
min-width="150"
|
||||
title="Author">
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="150"
|
||||
field="gmtCreate"
|
||||
title="Create Time">
|
||||
<template v-slot="{row}">
|
||||
{{row.create_time | localtime }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
min-width="100"
|
||||
field="auth"
|
||||
title="Auth">
|
||||
<template v-slot="{row}">
|
||||
<el-select v-model="row.auth" @change="updateProblem(row)" size="small">
|
||||
<el-option label="公开" :value="1"></el-option>
|
||||
<el-option label="私有" :value="2"></el-option>
|
||||
<el-option label="比赛中" :value="3"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
title="Option"
|
||||
min-width="200">
|
||||
<template v-slot="{row}">
|
||||
<el-tooltip effect="dark" content="编辑题目" placement="top">
|
||||
<el-button icon="el-icon-edit-outline" size="mini" @click.native="goEdit(row)" type="primary">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="下载测试样例" placement="top">
|
||||
<el-button icon="el-icon-download" size="mini" @click.native="downloadTestCase(row.id)" type="success">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="删除题目" placement="top">
|
||||
<el-button icon="el-icon-delete-solid" size="mini" @click.native="deleteProblem(row.id)" type="danger">
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
|
||||
<div class="panel-options">
|
||||
<el-pagination
|
||||
class="page"
|
||||
layout="prev, pager, next"
|
||||
@current-change="currentChange"
|
||||
:page-size="pageSize"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="Add Contest Problem"
|
||||
v-if="contestId"
|
||||
width="90%"
|
||||
:visible.sync="addProblemDialogVisible"
|
||||
@close-on-click-modal="false">
|
||||
<ContestAddProblem :contestID="contestId" @on-change="getProblemList"></ContestAddProblem>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/common/api'
|
||||
import utils from '@/common/utils'
|
||||
import ContestAddProblem from '@/components/admin/ContestAddProblem.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProblemList',
|
||||
components: {
|
||||
ContestAddProblem
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
problemList: [
|
||||
{id:1001,title:'测试标题',author:'Himit_ZH',gmtCreate:'2020-11-11 22:22:22',auth:1}
|
||||
],
|
||||
keyword: '',
|
||||
loading: false,
|
||||
currentPage: 1,
|
||||
routeName: '',
|
||||
contestId: '',
|
||||
// for make public use
|
||||
currentProblemID: '',
|
||||
currentRow: {},
|
||||
addProblemDialogVisible: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.routeName = this.$route.name
|
||||
this.contestId = this.$route.params.contestId
|
||||
// this.getProblemList(this.currentPage)
|
||||
},
|
||||
methods: {
|
||||
handleDblclick (row) {
|
||||
row.isEditing = true
|
||||
},
|
||||
goEdit (problemId) {
|
||||
if (this.routeName === 'admin-problem-list') {
|
||||
this.$router.push({name: 'admin-edit-problem', params: {problemId}})
|
||||
} else if (this.routeName === 'admin-contest-problem-list') {
|
||||
this.$router.push({name: 'admin-edit-contest-problem', params: {problemId: problemId, contestId: this.contestId}})
|
||||
}
|
||||
},
|
||||
goCreateProblem () {
|
||||
if (this.routeName === 'admin-problem-list') {
|
||||
this.$router.push({name: 'admin-create-problem'})
|
||||
} else if (this.routeName === 'admin-contest-problem-list') {
|
||||
this.$router.push({name: 'admin-create-contest-problem', params: {contestId: this.contestId}})
|
||||
}
|
||||
},
|
||||
// 切换页码回调
|
||||
currentChange (page) {
|
||||
this.currentPage = page
|
||||
this.getProblemList(page)
|
||||
},
|
||||
getProblemList (page = 1) {
|
||||
this.loading = true
|
||||
let funcName = this.routeName === 'admin-problem-list' ? 'getProblemList' : 'getContestProblemList'
|
||||
let params = {
|
||||
limit: this.pageSize,
|
||||
offset: (page - 1) * this.pageSize,
|
||||
keyword: this.keyword,
|
||||
contest_id: this.contestId
|
||||
}
|
||||
api[funcName](params).then(res => {
|
||||
this.loading = false
|
||||
this.total = res.data.data.total
|
||||
for (let problem of res.data.data.results) {
|
||||
problem.isEditing = false
|
||||
}
|
||||
this.problemList = res.data.data.results
|
||||
}, err => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
deleteProblem (id) {
|
||||
this.$confirm('确定要删除此问题吗?注意:该问题的相关提交数据也将被删除。', '删除题目', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
let funcName = this.routeName === 'admin-problem-list' ? 'deleteProblem' : 'deleteContestProblem'
|
||||
api[funcName](id).then(() => [
|
||||
this.getProblemList(this.currentPage - 1)
|
||||
]).catch(() => {
|
||||
})
|
||||
}, () => {
|
||||
})
|
||||
},
|
||||
updateProblem (row) {
|
||||
let data = Object.assign({}, row)
|
||||
let funcName = ''
|
||||
if (this.contestId) {
|
||||
data.contest_id = this.contestId
|
||||
funcName = 'editContestProblem'
|
||||
} else {
|
||||
funcName = 'editProblem'
|
||||
}
|
||||
api[funcName](data).then(res => {
|
||||
this.getProblemList(this.currentPage)
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
downloadTestCase (problemID) {
|
||||
let url = '/admin/test_case?problem_id=' + problemID
|
||||
utils.downloadFile(url)
|
||||
},
|
||||
getPublicProblem () {
|
||||
api.getProblemList()
|
||||
},
|
||||
filterByKeyword(){
|
||||
this.currentChange()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route' (newVal, oldVal) {
|
||||
this.contestId = newVal.params.contestId
|
||||
this.routeName = newVal.name
|
||||
this.getProblemList(this.currentPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-row{
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.filter-row span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -64,7 +64,7 @@
|
|||
</vxe-table-column>
|
||||
<vxe-table-column min-width="80" v-for="problem in problems" :key="problem.id">
|
||||
<template v-slot:header>
|
||||
<span><a @click="getContestProblemById(problem.id)">{{problem.id}}</a></span>
|
||||
<span><a @click="getContestProblemById(problem.id)" style="color:#495060;">{{problem.id}}</a></span>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<span v-if="row.submission_info[problem.id].is_ac">{{ row.submission_info[problem.id].ac_time }}<br></span>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
border="inner"
|
||||
stripe
|
||||
auto-resize
|
||||
highlight-hover-row
|
||||
:data="problems"
|
||||
@cell-click="goContestProblem">
|
||||
<vxe-table-column field="status" title="" width="50">
|
||||
|
|
|
@ -477,9 +477,9 @@
|
|||
padding: 8px!important;
|
||||
}
|
||||
a.emphasis{
|
||||
color:#495060
|
||||
color:#495060!important;
|
||||
}
|
||||
a.emphasis:hover{
|
||||
color:#2d8cf0
|
||||
color:#2d8cf0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -251,7 +251,6 @@ export default {
|
|||
},
|
||||
],
|
||||
tags: ['简单题','模拟题'],
|
||||
io_mode: { io_mode: "Standard IO" },
|
||||
},
|
||||
pie: pie,
|
||||
largePie: largePie,
|
||||
|
|
|
@ -53,9 +53,9 @@
|
|||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
<vxe-table-column field="level" title="Level" min-width="100">
|
||||
<vxe-table-column field="difficulty" title="Level" min-width="100">
|
||||
<template v-slot="{ row }">
|
||||
<span :class="getLevelColor(row.level)">{{PROBLEM_LEVEL[row.level].name}}</span>
|
||||
<span :class="getLevelColor(row.difficulty)">{{PROBLEM_LEVEL[row.difficulty].name}}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
|
||||
|
@ -136,15 +136,15 @@
|
|||
{status:5,count:70},
|
||||
],
|
||||
problemList: [
|
||||
{myStatus:-10,pid:'1000',title:'测试标题',level:0,
|
||||
{myStatus:-10,pid:'1000',title:'测试标题',difficulty:0,
|
||||
tags:['简单题','模拟题'],
|
||||
total:'10000',ACRate:'59.12%'
|
||||
},
|
||||
{myStatus:-1,pid:'1000',title:'测试标题',level:1,
|
||||
{myStatus:-1,pid:'1000',title:'测试标题',difficulty:1,
|
||||
tags:['简单题','模拟题','递归题','递归题'],
|
||||
total:'10000',ACRate:'59.12%'
|
||||
},
|
||||
{myStatus:0,pid:'1000',title:'测试标题',level:2,
|
||||
{myStatus:0,pid:'1000',title:'测试标题',difficulty:2,
|
||||
tags:['简单题','模拟题'],
|
||||
total:'10000',ACRate:'59.12%'
|
||||
},
|
||||
|
@ -246,8 +246,8 @@
|
|||
getProblemUri(pid){
|
||||
return '/problem/'+pid;
|
||||
},
|
||||
getLevelColor(level){
|
||||
return 'el-tag el-tag--small status-'+PROBLEM_LEVEL[level].color;
|
||||
getLevelColor(difficulty){
|
||||
return 'el-tag el-tag--small status-'+PROBLEM_LEVEL[difficulty].color;
|
||||
},
|
||||
getIconColor(status){
|
||||
console.log(JUDGE_STATUS[status])
|
||||
|
|
Loading…
Reference in New Issue