|
|
|
<% this.title = '提交记录 #' + info.taskId %>
|
|
|
|
<% include util %>
|
|
|
|
<% include header %>
|
|
|
|
<script src="/textFit.js"></script>
|
|
|
|
<span id="submission_content">
|
|
|
|
<div class="padding" id="vueApp">
|
|
|
|
<table class="ui very basic center aligned table" id="status_table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>编号</th>
|
|
|
|
<th>题目</th>
|
|
|
|
<th>状态</th>
|
|
|
|
<th v-if="displayConfig.showScore">分数</th>
|
|
|
|
<th v-if="displayConfig.showUsage">总时间</th>
|
|
|
|
<th v-if="displayConfig.showUsage">内存</th>
|
|
|
|
<th v-if="displayConfig.showCode">代码 / 答案文件</th>
|
|
|
|
<th>提交者</th>
|
|
|
|
<th>提交时间</th>
|
|
|
|
<th v-if="showRejudge">重新评测</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig" :show-rejudge="showRejudge"></tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<code-box :escape="false" v-bind:content="code"></code-box>
|
|
|
|
<code-box v-if="detailResult && detailResult.compile" :escape="false" title="编译信息" v-bind:content="detailResult.compile.message"></code-box>
|
|
|
|
<code-box v-if="detailResult" title="系统信息" :escape="true" v-bind:content="detailResult.systemMessage"></code-box>
|
|
|
|
|
|
|
|
<div class="ui styled fluid accordion" v-if="detailResult && detailResult.judge && detailResult.judge.subtasks">
|
|
|
|
<template v-for="subtask, $index in detailResult.judge.subtasks">
|
|
|
|
<div class="title" :class="singleSubtask ? 'active' : ''">
|
|
|
|
<div class="ui grid">
|
|
|
|
<div class="three wide column">
|
|
|
|
<i class="dropdown icon"></i>
|
|
|
|
子任务 #{{ $index + 1 }}
|
|
|
|
</div>
|
|
|
|
<div class="four wide column">
|
|
|
|
<status-label :status="getSubtaskResult(subtask)"></status-label>
|
|
|
|
</div>
|
|
|
|
<div class="three wide column" v-if="subtask.score != null">得分
|
|
|
|
<span style="font-weight: normal; ">{{ Math.trunc(subtask.score) }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="content" :class="singleSubtask ? 'active' : ''">
|
|
|
|
<div class="accordion">
|
|
|
|
<template v-for="curCase, $caseIndex in subtask.cases">
|
|
|
|
<div class="title">
|
|
|
|
<div class="ui grid">
|
|
|
|
<div class="three wide column">
|
|
|
|
<i class="dropdown icon"></i>
|
|
|
|
测试点 #{{ $caseIndex + 1 }}
|
|
|
|
</div>
|
|
|
|
<div class="four wide column">
|
|
|
|
<status-label :status="getTestcaseStatus(curCase)"></status-label>
|
|
|
|
</div>
|
|
|
|
<template v-if="checkTestcaseOK(curCase)">
|
|
|
|
<div class="three wide column">得分率
|
|
|
|
<span style="font-weight: normal; ">{{ Math.trunc(curCase.result.scoringRate * 100) }}</span>
|
|
|
|
</div>
|
|
|
|
<div class="three wide column" v-if="curCase.result.time != null && curCase.result.time !== NaN">用时
|
|
|
|
<span style="font-weight: normal; ">{{ curCase.result.time }} ms</span>
|
|
|
|
</div>
|
|
|
|
<div class="three wide column" v-if="curCase.result.memory != null && curCase.result.memory !== NaN">内存
|
|
|
|
<span style="font-weight: normal; ">{{ curCase.result.memory }} KiB</span>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<template v-if="checkTestcaseOK(curCase)">
|
|
|
|
<code-box v-if="curCase.result.input" :title="'输入文件('+ curCase.result.input.name +')'" :content="curCase.result.input.content"></code-box>
|
|
|
|
<code-box v-if="curCase.result.output" :title="'答案文件('+ curCase.result.output.name +')'" :content="curCase.result.output.content"></code-box>
|
|
|
|
<code-box title="用户输出" :content="curCase.result.userOutput"></code-box>
|
|
|
|
<code-box title="标准错误流" :content="curCase.result.userError"></code-box>
|
|
|
|
<code-box title="Special Judge 信息" :content="curCase.result.spjMessage"></code-box>
|
|
|
|
<code-box title="系统信息" :content="curCase.result.systemMessage"></code-box>
|
|
|
|
</template>
|
|
|
|
<code-box title="错误信息" :content="curCase.errorMessage"></code-box>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script src="//cdn.bootcss.com/vue/2.4.2/vue.js"></script>
|
|
|
|
<script src="//cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script>
|
|
|
|
<script src="//cdn.bootcss.com/jsondiffpatch/0.2.4/jsondiffpatch.js"></script>
|
|
|
|
|
|
|
|
<% include submissions_item %>
|
|
|
|
|
|
|
|
<script type="text/x-template" id="codeBoxTemplate">
|
|
|
|
<div style="" v-if="content != null && content !== ''">
|
|
|
|
<div></div>
|
|
|
|
<h3 v-if="title" class="ui header">{{ title }}</h3>
|
|
|
|
<div class="ui existing segment">
|
|
|
|
<pre v-if="escape" style="margin-top: 0; margin-bottom: 0; "><code>{{ content }}</code></pre>
|
|
|
|
<pre v-if="!escape" style="margin-top: 0; margin-bottom: 0; "><code v-html="content"></code></pre>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
Vue.component("code-box", {
|
|
|
|
template: "#codeBoxTemplate",
|
|
|
|
props: ['title', 'content', 'escape'],
|
|
|
|
});
|
|
|
|
const socketUrl = <%- syzoj.utils.judgeServer('detail') %>;
|
|
|
|
const displayConfig = <%- JSON.stringify(displayConfig) %>;
|
|
|
|
const token = <%- JSON.stringify(socketToken) %>;
|
|
|
|
|
|
|
|
const TestcaseResultType = {};
|
|
|
|
(function (TestcaseResultType) {
|
|
|
|
TestcaseResultType[TestcaseResultType["Accepted"] = 1] = "Accepted";
|
|
|
|
TestcaseResultType[TestcaseResultType["WrongAnswer"] = 2] = "WrongAnswer";
|
|
|
|
TestcaseResultType[TestcaseResultType["PartiallyCorrect"] = 3] = "PartiallyCorrect";
|
|
|
|
TestcaseResultType[TestcaseResultType["MemoryLimitExceeded"] = 4] = "MemoryLimitExceeded";
|
|
|
|
TestcaseResultType[TestcaseResultType["TimeLimitExceeded"] = 5] = "TimeLimitExceeded";
|
|
|
|
TestcaseResultType[TestcaseResultType["OutputLimitExceeded"] = 6] = "OutputLimitExceeded";
|
|
|
|
TestcaseResultType[TestcaseResultType["FileError"] = 7] = "FileError";
|
|
|
|
TestcaseResultType[TestcaseResultType["RuntimeError"] = 8] = "RuntimeError";
|
|
|
|
TestcaseResultType[TestcaseResultType["JudgementFailed"] = 9] = "JudgementFailed";
|
|
|
|
TestcaseResultType[TestcaseResultType["InvalidInteraction"] = 10] = "InvalidInteraction";
|
|
|
|
})(TestcaseResultType);
|
|
|
|
|
|
|
|
const statusToString = {};
|
|
|
|
statusToString[TestcaseResultType.Accepted] = "Accepted";
|
|
|
|
statusToString[TestcaseResultType.WrongAnswer] = "Wrong Answer";
|
|
|
|
statusToString[TestcaseResultType.PartiallyCorrect] = "Partially Correct";
|
|
|
|
statusToString[TestcaseResultType.MemoryLimitExceeded] = "Memory Limit Exceeded";
|
|
|
|
statusToString[TestcaseResultType.TimeLimitExceeded] = "Time Limit Exceeded";
|
|
|
|
statusToString[TestcaseResultType.OutputLimitExceeded] = "Output Limit Exceeded";
|
|
|
|
statusToString[TestcaseResultType.RuntimeError] = "Runtime Error";
|
|
|
|
statusToString[TestcaseResultType.FileError] = "File Error";
|
|
|
|
statusToString[TestcaseResultType.JudgementFailed] = "Judgement Failed";
|
|
|
|
statusToString[TestcaseResultType.InvalidInteraction] = "Invalid Interaction";
|
|
|
|
|
|
|
|
const TaskStatus = {};
|
|
|
|
(function (TaskStatus) {
|
|
|
|
TaskStatus[TaskStatus["Waiting"] = 0] = "Waiting";
|
|
|
|
TaskStatus[TaskStatus["Running"] = 1] = "Running";
|
|
|
|
TaskStatus[TaskStatus["Done"] = 2] = "Done";
|
|
|
|
TaskStatus[TaskStatus["Failed"] = 3] = "Failed";
|
|
|
|
TaskStatus[TaskStatus["Skipped"] = 4] = "Skipped";
|
|
|
|
})(TaskStatus);
|
|
|
|
|
|
|
|
const vueApp = new Vue({
|
|
|
|
el: '#vueApp',
|
|
|
|
data: {
|
|
|
|
roughData: {
|
|
|
|
info: <%- JSON.stringify(info) %>,
|
|
|
|
result: <%- JSON.stringify(roughResult) %>,
|
|
|
|
running: false,
|
|
|
|
displayConfig: displayConfig
|
|
|
|
},
|
|
|
|
code: <%- JSON.stringify(code) %>,
|
|
|
|
detailResult: <%- JSON.stringify(detailResult) %>,
|
|
|
|
displayConfig: displayConfig,
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
singleSubtask() {
|
|
|
|
return this.detailResult.judge.subtasks.length === 1;
|
|
|
|
},
|
|
|
|
showRejudge() {
|
|
|
|
return this.displayConfig.showRejudge && (!this.roughData.running);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
getStatusString(statusCode) {
|
|
|
|
return statusToString[statusCode];
|
|
|
|
},
|
|
|
|
firstNonAC(t) {
|
|
|
|
if (t.every(v => v === TestcaseResultType.Accepted)) {
|
|
|
|
return TestcaseResultType.Accepted;
|
|
|
|
} else {
|
|
|
|
return t.find(r => r !== TestcaseResultType.Accepted);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getSubtaskResult(t) {
|
|
|
|
if (t.cases.some(c => c.status === TaskStatus.Running)) {
|
|
|
|
return "Running";
|
|
|
|
} else if (t.cases.some(c => c.status === TaskStatus.Waiting)) {
|
|
|
|
return "Waiting";
|
|
|
|
} else if (t.cases.every(c => c.status === TaskStatus.Done || c.status === TaskStatus.Skipped)) {
|
|
|
|
return this.getStatusString(this.firstNonAC(t.cases.filter(c => c.result).map(c => c.result.type)));
|
|
|
|
} else {
|
|
|
|
return "System Error";
|
|
|
|
}
|
|
|
|
},
|
|
|
|
getTestcaseStatus(c) {
|
|
|
|
if (c.status === TaskStatus.Done) {
|
|
|
|
return this.getStatusString(c.result.type);
|
|
|
|
} else if (c.status === TaskStatus.Waiting) {
|
|
|
|
return "Waiting";
|
|
|
|
} else if (c.status === TaskStatus.Running) {
|
|
|
|
return "Running";
|
|
|
|
} else if (c.status === TaskStatus.Skipped) {
|
|
|
|
return "Skipped";
|
|
|
|
} else {
|
|
|
|
return "System Error";
|
|
|
|
}
|
|
|
|
},
|
|
|
|
checkTestcaseOK(c) {
|
|
|
|
return c.status === TaskStatus.Done;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
$(document).ready(() => $('.ui.accordion').accordion());
|
|
|
|
},
|
|
|
|
updated() {
|
|
|
|
$('.ui.accordion').accordion("refresh");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (token != null) {
|
|
|
|
const loadSocketIO = function () {
|
|
|
|
let currentVersion = 0;
|
|
|
|
const socket = io(socketUrl);
|
|
|
|
socket.on('connect', () => {
|
|
|
|
socket.on('start', () => {
|
|
|
|
vueApp.roughData.running = true;
|
|
|
|
console.log("Judge start!");
|
|
|
|
vueApp.detailResult = {};
|
|
|
|
});
|
|
|
|
socket.on('update', (p) => {
|
|
|
|
console.log("Delta: " + JSON.stringify(p));
|
|
|
|
if (p.from === currentVersion) {
|
|
|
|
currentVersion = p.to;
|
|
|
|
jsondiffpatch.patch(vueApp.detailResult, p.delta);
|
|
|
|
} else { // Some packets are dropped. Let's reset.
|
|
|
|
socket.close();
|
|
|
|
setTimeout(loadSocketIO, 0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
socket.on('finish', (p) => {
|
|
|
|
console.log("Judge finished");
|
|
|
|
vueApp.roughData.running = false;
|
|
|
|
vueApp.roughData.result = p.roughResult;
|
|
|
|
vueApp.detailResult = p.result;
|
|
|
|
socket.close();
|
|
|
|
});
|
|
|
|
socket.emit('join', token, (data) => {
|
|
|
|
console.log("join! " + JSON.stringify(data));
|
|
|
|
if (data && data.ok) {
|
|
|
|
if (data.finished) {
|
|
|
|
vueApp.roughData.result = data.roughResult;
|
|
|
|
vueApp.detailResult = data.result || {
|
|
|
|
systemMessage: "系统出错,请刷新后重试。"
|
|
|
|
};
|
|
|
|
socket.close();
|
|
|
|
} else {
|
|
|
|
if (data.running) {
|
|
|
|
vueApp.roughData.running = true;
|
|
|
|
vueApp.detailResult = data.current.content;
|
|
|
|
currentVersion = data.current.version;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
alert("ERROR: " + JSON.stringify(data));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
loadSocketIO();
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</span>
|
|
|
|
<% include footer %>
|