修正自动删除测试数据的bug 优化排行榜显示

This commit is contained in:
Himit_ZH 2021-04-21 18:59:56 +08:00
parent dcf7d38c7a
commit 760aa4f61b
9 changed files with 454 additions and 487 deletions

View File

@ -1,112 +0,0 @@
package top.hcode.hoj.judge.self;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import top.hcode.hoj.pojo.entity.JudgeServer;
import top.hcode.hoj.service.impl.JudgeServerServiceImpl;
import top.hcode.hoj.utils.RedisUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Himit_ZH
* @Date: 2021/2/4 16:44
* @Description: 任务调度的自定义负载均衡策略
*/
@Slf4j
public class JudgeChooseRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties discoveryProperties;
private JudgeServerServiceImpl judgeServerService ;
@Autowired
public void setJudgeServerService (JudgeServerServiceImpl judgeServerService){
this.judgeServerService = judgeServerService;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
// // 获取配置文件中所配置的集群名称
// String clusterName = discoveryProperties.getClusterName();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
// 需要请求的微服务名称
String serviceId = loadBalancer.getName();
// 获取该微服务的所有健康实例
List<Instance> instances = getInstances(serviceId);
if (instances.size() <= 0) {
return null;
}
List<String> keyList = new ArrayList<>();
// 获取当前健康实例取出ip和port拼接
for (Instance instance : instances) {
keyList.add(instance.getIp() + ":" + instance.getPort());
}
// 过滤出小于或等于规定最大并发判题任务数的服务实例且健康的判题机
QueryWrapper<JudgeServer> judgeServerQueryWrapper = new QueryWrapper<>();
judgeServerQueryWrapper
.in("url", keyList)
.orderByAsc("task_num");
List<JudgeServer> judgeServerList = judgeServerService.list(judgeServerQueryWrapper);
System.out.println(judgeServerList);
// 使用乐观锁获取可用判题机
for (JudgeServer judgeServer : judgeServerList) {
if (judgeServer.getTaskNumber() <= judgeServer.getMaxTaskNumber()) {
judgeServer.setTaskNumber(judgeServer.getTaskNumber() + 1);
boolean isOk = judgeServerService.updateById(judgeServer);
if (isOk) {
int instanceIndex = keyList.indexOf(judgeServer.getIp() + ":" + judgeServer.getPort());
if (instanceIndex != -1) {
return new NacosServer(instances.get(instanceIndex));
}
}
}
}
return null;
}
private List<Instance> getInstances(String serviceId) {
// 获取服务发现的相关API
NamingService namingService = discoveryProperties.namingServiceInstance();
try {
// 获取该微服务的所有健康实例
return namingService.selectInstances(serviceId, true);
} catch (NacosException e) {
log.error("获取微服务健康实例发生异常--------->{}", e);
return Collections.emptyList();
}
}
}

View File

@ -109,7 +109,7 @@ public class ScheduleServiceImpl implements ScheduleService {
// @Scheduled(cron = "0/5 * * * * *")
@Override
public void deleteTestCase() {
boolean result = FileUtil.del(Constants.File.TESTCASE_BASE_FOLDER.getPath());
boolean result = FileUtil.del(Constants.File.TESTCASE_TMP_FOLDER.getPath());
if (!result){
log.error("每日定时任务异常------------------------>{}","清除本地的题目测试数据失败!");
}

View File

@ -90,15 +90,6 @@ logging:
naming: error
root: error
# 开启Hystrix断路器
feign:
hystrix:
enabled: true
client:
config:
default:
connectTimeout: 15000
readTimeout: 60000
hystrix:
command:
default:

View File

@ -1,9 +1,9 @@
hoj-judge-server:
max-task-num: -1 # -1表示最大并行任务数为cpu核心数*2
ip: 39.108.148.172 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
ip: 127.0.0.1 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
port: 8088 # 端口号
name: hoj-judger-1 # 判题机名字 唯一不可重复!!!
nacos-url: 129.204.177.72:8848 # nacos地址
nacos-url: 127.0.0.1:8848 # nacos地址
remote-judge:
open: true # 当前判题服务器是否开启远程虚拟判题功能
max-task-num: -1 # -1表示最大并行任务数为(cpu核心数*2)*2

View File

@ -195,9 +195,7 @@
<el-tag
effect="dark"
:color="submissionStatus.color"
@click.native="
handleRoute('/submission-detail/' + submissionId)
"
@click.native="submissionRoute"
>
<i class="fa fa-circle" aria-hidden="true"></i>
{{ submissionStatus.text }}
@ -739,15 +737,20 @@ export default {
},
submissionRoute() {
if (this.contestID) {
return {
name: 'contest-submission-list',
query: { problemID: this.problemID },
};
//
this.$router.push({
name: 'ContestSubmissionDeatil',
params: {
contestID: this.contestID,
problemID: this.problemID,
submitID: this.submissionId,
},
});
} else {
return {
name: 'submission-list',
query: { problemID: this.problemID },
};
this.$router.push({
name: 'SubmissionDeatil',
params: { submitID: this.submissionId },
});
}
},
},

View File

@ -1,204 +1,244 @@
<template>
<el-row type="flex" justify="space-around">
<el-col :span="24">
<el-card :padding="10">
<div slot="header"><span class="panel-title">ACM Ranklist</span></div>
<div class="echarts">
<ECharts :options="options" ref="chart" :autoresize="true"></ECharts>
</div>
</el-card>
<vxe-table :data="dataRank" :loading="loadingTable" align="center" highlight-hover-row auto-resize style="font-weight: 500;">
<vxe-table-column type="seq" min-width="50"></vxe-table-column>
<vxe-table-column field="username" title="User" min-width="150">
<template v-slot="{ row }">
<a @click="getInfoByUsername(row.uid,row.username)" style="color:rgb(87, 163, 243);">{{row.username}}</a>
</template>
</vxe-table-column>
<vxe-table-column field="nickname" title="Nickname" min-width="180"></vxe-table-column>
<vxe-table-column field="signature" title="Mood" min-width="180"></vxe-table-column>
<vxe-table-column field="solved" title="Solved" min-width="80"></vxe-table-column>
<vxe-table-column title="AC" min-width="80">
<template v-slot="{ row }">
<a @click="goUserACStatus(row.username)" style="color:rgb(87, 163, 243);">{{row.ac}}</a>
</template>
</vxe-table-column>
<vxe-table-column field="total" title="Total" min-width="80"></vxe-table-column>
<vxe-table-column title="Rating" min-width="80">
<template v-slot="{row}">
<span>{{getACRate(row.ac,row.total)}}</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination :total="total" :page-size.sync="limit" :current.sync="page"
@on-change="getRankData" show-sizer
@on-page-size-change="getRankData(1)"></Pagination>
<el-card :padding="10">
<div slot="header"><span class="panel-title">ACM Ranklist</span></div>
<div class="echarts">
<ECharts :options="options" ref="chart" :autoresize="true"></ECharts>
</div>
</el-card>
<vxe-table
:data="dataRank"
:loading="loadingTable"
align="center"
highlight-hover-row
auto-resize
style="font-weight: 500;"
>
<vxe-table-column type="seq" min-width="50"></vxe-table-column>
<vxe-table-column field="username" title="User" min-width="150">
<template v-slot="{ row }">
<a
@click="getInfoByUsername(row.uid, row.username)"
style="color:rgb(87, 163, 243);"
>{{ row.username }}</a
>
</template>
</vxe-table-column>
<vxe-table-column
field="nickname"
title="Nickname"
min-width="180"
></vxe-table-column>
<vxe-table-column
field="signature"
title="Mood"
min-width="180"
></vxe-table-column>
<vxe-table-column
field="solved"
title="Solved"
min-width="80"
></vxe-table-column>
<vxe-table-column title="AC" min-width="80">
<template v-slot="{ row }">
<a
@click="goUserACStatus(row.username)"
style="color:rgb(87, 163, 243);"
>{{ row.ac }}</a
>
</template>
</vxe-table-column>
<vxe-table-column
field="total"
title="Total"
min-width="80"
></vxe-table-column>
<vxe-table-column title="Rating" min-width="80">
<template v-slot="{ row }">
<span>{{ getACRate(row.ac, row.total) }}</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size.sync="limit"
:current.sync="page"
@on-change="getRankData"
show-sizer
@on-page-size-change="getRankData(1)"
></Pagination>
</el-col>
</el-row>
</template>
<script>
import api from '@/common/api'
import Pagination from '@/components/oj/common/Pagination'
import utils from '@/common/utils'
import { RULE_TYPE } from '@/common/constants'
import api from '@/common/api';
import Pagination from '@/components/oj/common/Pagination';
import utils from '@/common/utils';
import { RULE_TYPE } from '@/common/constants';
import { mapGetters } from 'vuex';
export default {
name: 'acm-rank',
components: {
Pagination
},
data () {
return {
page: 1,
limit: 30,
total: 0,
loadingTable: false,
dataRank: [],
options: {
tooltip: {
trigger: 'axis'
export default {
name: 'acm-rank',
components: {
Pagination,
},
data() {
return {
page: 1,
limit: 30,
total: 0,
loadingTable: false,
dataRank: [],
options: {
tooltip: {
trigger: 'axis',
},
legend: {
data: ['AC', 'Total'],
},
grid: {
x: '3%',
x2: '3%',
left: '8%',
right: '8%',
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: true },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
saveAsImage: { show: true },
},
legend: {
data: ['AC', 'Total']
},
grid: {
x: '3%',
x2: '3%',
left:'8%',
right:'8%'
},
toolbox: {
show: true,
feature: {
dataView: {show: true, readOnly: true},
magicType: {show: true, type: ['line', 'bar', 'stack']},
saveAsImage: {show: true}
},
right: '8%',
top:'5%'
},
calculable: true,
xAxis: [
{
type: 'category',
data: ['root'],
axisLabel: {
interval: 0,
showMinLabel: true,
showMaxLabel: true,
align: 'center',
formatter: (value, index) => {
return utils.breakLongWords(value, 13)
right: '8%',
top: '5%',
},
calculable: true,
xAxis: [
{
type: 'category',
data: ['root'],
axisLabel: {
interval: 0,
showMinLabel: true,
showMaxLabel: true,
align: 'center',
formatter: (value, index) => {
if (this.isAuthenticated && this.userInfo.username == value) {
return utils.breakLongWords(value, 14);
} else {
return '';
}
}
}
],
yAxis: [
{
type: 'value',
axisLabel: {
rotate:50,
textStyle:{
fontSize:'12em',
},
}
}
],
series: [
{
name: 'AC',
type: 'bar',
data: [0],
itemStyle: {
color:'#91c7ae'
},
markPoint: {
data: [
{type: 'max', name: 'max'}
]
}
},
{
name: 'Total',
type: 'bar',
data: [0],
itemStyle:{
color:'#6ab0b8'
},
],
yAxis: [
{
type: 'value',
axisLabel: {
rotate: 50,
textStyle: {
fontSize: '12em',
},
markPoint: {
data: [
{type: 'max', name: 'max'}
]
}
}
]
}
}
},
mounted () {
this.getRankData(1)
},
methods: {
getRankData (page) {
let bar = this.$refs.chart
bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})
this.loadingTable = true
api.getUserRank(page, this.limit, RULE_TYPE.ACM).then(res => {
this.loadingTable = false
},
},
],
series: [
{
name: 'AC',
type: 'bar',
data: [0],
itemStyle: {
color: '#91c7ae',
},
markPoint: {
data: [{ type: 'max', name: 'max' }],
},
},
{
name: 'Total',
type: 'bar',
data: [0],
itemStyle: {
color: '#6ab0b8',
},
markPoint: {
data: [{ type: 'max', name: 'max' }],
},
},
],
},
};
},
mounted() {
this.getRankData(1);
},
methods: {
getRankData(page) {
let bar = this.$refs.chart;
bar.showLoading({ maskColor: 'rgba(250, 250, 250, 0.8)' });
this.loadingTable = true;
api
.getUserRank(page, this.limit, RULE_TYPE.ACM)
.then((res) => {
this.loadingTable = false;
if (page === 1) {
this.changeCharts(res.data.data.records.slice(0, 10))
this.changeCharts(res.data.data.records.slice(0, 10));
}
this.total = res.data.data.total
this.dataRank = res.data.data.records
bar.hideLoading()
}).catch(() => {
this.loadingTable = false
bar.hideLoading()
this.total = res.data.data.total;
this.dataRank = res.data.data.records;
bar.hideLoading();
})
},
changeCharts (rankData) {
let [usernames, acData, totalData] = [[], [], []]
rankData.forEach(ele => {
usernames.push(ele.username)
acData.push(ele.ac)
totalData.push(ele.total)
})
this.options.xAxis[0].data = usernames
this.options.series[0].data = acData
this.options.series[1].data = totalData
},
getInfoByUsername(uid,username){
this.$router.push({
path: '/user-home',
query: {uid,username},
.catch(() => {
this.loadingTable = false;
bar.hideLoading();
});
},
goUserACStatus(username){
this.$router.push({
path: '/status',
query: {username,status:0},
});
},
getACRate(ac,total){
return utils.getACRate(ac,total)
}
},
computed:{
}
}
changeCharts(rankData) {
let [usernames, acData, totalData] = [[], [], []];
rankData.forEach((ele) => {
usernames.push(ele.username);
acData.push(ele.ac);
totalData.push(ele.total);
});
this.options.xAxis[0].data = usernames;
this.options.series[0].data = acData;
this.options.series[1].data = totalData;
},
getInfoByUsername(uid, username) {
this.$router.push({
path: '/user-home',
query: { uid, username },
});
},
goUserACStatus(username) {
this.$router.push({
path: '/status',
query: { username, status: 0 },
});
},
getACRate(ac, total) {
return utils.getACRate(ac, total);
},
},
computed: {
...mapGetters(['isAuthenticated', 'userInfo']),
},
};
</script>
<style scoped>
.echarts {
margin: 0 auto;
width: 100%;
height: 400px;
}
@media screen and (max-width: 768px) {
/deep/.el-card__body {
padding: 0!important;
}
.echarts {
margin: 0 auto;
width: 100%;
height: 400px;
}
@media screen and (max-width: 768px) {
/deep/.el-card__body {
padding: 0 !important;
}
}
</style>

View File

@ -1,187 +1,232 @@
<template>
<el-row type="flex" justify="space-around">
<el-col :span="24">
<el-card :padding="10">
<div slot="header"><span class="panel-title">OI Ranklist</span></div>
<div class="echarts">
<ECharts :options="options" ref="chart" auto-resize></ECharts>
</div>
</el-card>
<vxe-table :data="dataRank" :loading="loadingTable" align="center" highlight-hover-row auto-resize style="font-weight: 500;">
<vxe-table-column type="seq" min-width="50"></vxe-table-column>
<vxe-table-column field="username" title="User" min-width="150">
<template v-slot="{ row }">
<a @click="getInfoByUsername(row.uid,row.username)" style="color:rgb(87, 163, 243);">{{row.username}}</a>
</template>
</vxe-table-column>
<vxe-table-column field="nickname" title="Nickname" min-width="180"></vxe-table-column>
<vxe-table-column field="signature" title="Mood" min-width="180"></vxe-table-column>
<vxe-table-column field="score" title="Score" min-width="80"></vxe-table-column>
<vxe-table-column title="AC" min-width="80">
<template v-slot="{ row }">
<a @click="goUserACStatus(row.username)" style="color:rgb(87, 163, 243);">{{row.ac}}</a>
</template>
</vxe-table-column>
<vxe-table-column field="total" title="Total" min-width="80"></vxe-table-column>
<vxe-table-column title="Rating" min-width="80">
<template v-slot="{row}">
<span>{{getACRate(row.ac,row.total)}}</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination :total="total" :page-size.sync="limit" :current.sync="page"
@on-change="getRankData"
show-sizer @on-page-size-change="getRankData(1)"></Pagination>
<el-card :padding="10">
<div slot="header"><span class="panel-title">OI Ranklist</span></div>
<div class="echarts">
<ECharts :options="options" ref="chart" auto-resize></ECharts>
</div>
</el-card>
<vxe-table
:data="dataRank"
:loading="loadingTable"
align="center"
highlight-hover-row
auto-resize
style="font-weight: 500;"
>
<vxe-table-column type="seq" min-width="50"></vxe-table-column>
<vxe-table-column field="username" title="User" min-width="150">
<template v-slot="{ row }">
<a
@click="getInfoByUsername(row.uid, row.username)"
style="color:rgb(87, 163, 243);"
>{{ row.username }}</a
>
</template>
</vxe-table-column>
<vxe-table-column
field="nickname"
title="Nickname"
min-width="180"
></vxe-table-column>
<vxe-table-column
field="signature"
title="Mood"
min-width="180"
></vxe-table-column>
<vxe-table-column
field="score"
title="Score"
min-width="80"
></vxe-table-column>
<vxe-table-column title="AC" min-width="80">
<template v-slot="{ row }">
<a
@click="goUserACStatus(row.username)"
style="color:rgb(87, 163, 243);"
>{{ row.ac }}</a
>
</template>
</vxe-table-column>
<vxe-table-column
field="total"
title="Total"
min-width="80"
></vxe-table-column>
<vxe-table-column title="Rating" min-width="80">
<template v-slot="{ row }">
<span>{{ getACRate(row.ac, row.total) }}</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size.sync="limit"
:current.sync="page"
@on-change="getRankData"
show-sizer
@on-page-size-change="getRankData(1)"
></Pagination>
</el-col>
</el-row>
</template>
<script>
import api from '@/common/api'
import Pagination from '@/components/oj/common/Pagination'
import utils from '@/common/utils'
import { RULE_TYPE } from '@/common/constants'
import api from '@/common/api';
import Pagination from '@/components/oj/common/Pagination';
import utils from '@/common/utils';
import { RULE_TYPE } from '@/common/constants';
import { mapGetters } from 'vuex';
export default {
name: 'acm-rank',
components: {
Pagination
},
data () {
return {
page: 1,
limit: 30,
total: 0,
dataRank:[],
loadingTable:false,
options: {
tooltip: {
trigger: 'axis'
export default {
name: 'acm-rank',
components: {
Pagination,
},
data() {
return {
page: 1,
limit: 30,
total: 0,
dataRank: [],
loadingTable: false,
options: {
tooltip: {
trigger: 'axis',
},
legend: {
data: ['Score'],
},
grid: {
x: '3%',
x2: '3%',
left: '8%',
right: '8%',
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: true },
magicType: { show: true, type: ['line', 'bar'] },
saveAsImage: { show: true },
},
legend: {
data: ['Score']
},
grid: {
x: '3%',
x2: '3%',
left:'8%',
right:'8%'
},
toolbox: {
show: true,
feature: {
dataView: {show: true, readOnly: true},
magicType: {show: true, type: ['line', 'bar']},
saveAsImage: {show: true}
},
right: '8%',
top:'5%',
},
calculable: true,
xAxis: [
{
type: 'category',
data: ['root'],
boundaryGap: true,
axisLabel: {
interval: 0,
showMinLabel: true,
showMaxLabel: true,
align: 'center',
formatter: (value, index) => {
return utils.breakLongWords(value, 14)
right: '8%',
top: '5%',
},
calculable: true,
xAxis: [
{
type: 'category',
data: ['root'],
boundaryGap: true,
axisLabel: {
interval: 0,
showMinLabel: true,
showMaxLabel: true,
align: 'center',
formatter: (value, index) => {
if (this.isAuthenticated && this.userInfo.username == value) {
return utils.breakLongWords(value, 14);
} else {
return '';
}
},
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value',
axisLabel: {
rotate:50,
textStyle:{
fontSize:'12em',
},
}
}
],
series: [
{
name: 'Score',
type: 'bar',
data: [0],
barMaxWidth: '80',
markPoint: {
data: [
{type: 'max', name: 'max'}
]
}
}
]
}
}
},
mounted () {
this.getRankData(1)
},
methods: {
getRankData (page) {
let bar = this.$refs.chart
bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})
this.loadingTable = true
api.getUserRank(page, this.limit, RULE_TYPE.OI).then(res => {
},
axisTick: {
alignWithLabel: true,
},
},
],
yAxis: [
{
type: 'value',
axisLabel: {
rotate: 50,
textStyle: {
fontSize: '12em',
},
},
},
],
series: [
{
name: 'Score',
type: 'bar',
data: [0],
barMaxWidth: '80',
markPoint: {
data: [{ type: 'max', name: 'max' }],
},
},
],
},
};
},
mounted() {
this.getRankData(1);
},
methods: {
getRankData(page) {
let bar = this.$refs.chart;
bar.showLoading({ maskColor: 'rgba(250, 250, 250, 0.8)' });
this.loadingTable = true;
api.getUserRank(page, this.limit, RULE_TYPE.OI).then(
(res) => {
if (page === 1) {
this.changeCharts(res.data.data.records.slice(0, 10))
this.changeCharts(res.data.data.records.slice(0, 10));
}
this.total = res.data.data.total
this.dataRank = res.data.data.records
this.loadingTable = false
bar.hideLoading()
},err=>{
this.loadingTable = false
bar.hideLoading()
})
},
changeCharts (rankData) {
let [usernames, scores] = [[], []]
rankData.forEach(ele => {
usernames.push(ele.username)
scores.push(ele.score)
})
this.options.xAxis[0].data = usernames
this.options.series[0].data = scores
},
getInfoByUsername(uid,username){
this.$router.push({
path: '/user-home',
query: {uid,username},
});
},
goUserACStatus(username){
this.$router.push({
path: '/status',
query: {username,status:0},
});
},
getACRate(ac,total){
return utils.getACRate(ac,total)
}
}
}
this.total = res.data.data.total;
this.dataRank = res.data.data.records;
this.loadingTable = false;
bar.hideLoading();
},
(err) => {
this.loadingTable = false;
bar.hideLoading();
}
);
},
changeCharts(rankData) {
let [usernames, scores] = [[], []];
rankData.forEach((ele) => {
usernames.push(ele.username);
scores.push(ele.score);
});
this.options.xAxis[0].data = usernames;
this.options.series[0].data = scores;
},
getInfoByUsername(uid, username) {
this.$router.push({
path: '/user-home',
query: { uid, username },
});
},
goUserACStatus(username) {
this.$router.push({
path: '/status',
query: { username, status: 0 },
});
},
getACRate(ac, total) {
return utils.getACRate(ac, total);
},
},
computed: {
...mapGetters(['isAuthenticated', 'userInfo']),
},
};
</script>
<style scoped>
.echarts {
margin: 0 auto;
width: 100%;
height: 400px;
}
@media screen and (max-width: 768px) {
/deep/.el-card__body {
padding: 0!important;
}
.echarts {
margin: 0 auto;
width: 100%;
height: 400px;
}
@media screen and (max-width: 768px) {
/deep/.el-card__body {
padding: 0 !important;
}
}
</style>

View File

@ -66,7 +66,7 @@
}}</span>
</template>
</vxe-table-column>
<vxe-table-column title="Time" min-width="64">
<vxe-table-column title="Time" min-width="96">
<template v-slot="{ row }">
<span>{{ submissionTimeFormat(row.time) }}</span>
</template>
@ -81,7 +81,7 @@
<span>{{ row.score }}</span>
</template>
</vxe-table-column>
<vxe-table-column title="Length" min-width="60">
<vxe-table-column title="Length" min-width="80">
<template v-slot="{ row }">
<span>{{ submissionLengthFormat(row.length) }}</span>
</template>

View File

@ -152,7 +152,7 @@
</span>
</template>
</vxe-table-column>
<vxe-table-column field="time" title="Time" min-width="64">
<vxe-table-column field="time" title="Time" min-width="96">
<template v-slot="{ row }">
<span>{{ submissionTimeFormat(row.time) }}</span>
</template>
@ -163,7 +163,7 @@
</template>
</vxe-table-column>
<vxe-table-column field="length" title="Length" min-width="60">
<vxe-table-column field="length" title="Length" min-width="80">
<template v-slot="{ row }">
<span>{{ submissionLengthFormat(row.length) }}</span>
</template>