Browse Source

Finish

pull/6/head
t123yh 7 years ago
parent
commit
c0cf41c57c
  1. 56
      libs/submissions_process.js
  2. 77
      modules/contest.js
  3. 44
      modules/submission.js
  4. 260
      views/submission.ejs
  5. 247
      views/submission_content.ejs
  6. 28
      views/submissions.ejs
  7. 25
      views/submissions_item.ejs

56
libs/submissions_process.js

@ -4,13 +4,24 @@ const getSubmissionInfo = (s, displayConfig) => ({
userId: s.user_id, userId: s.user_id,
problemName: s.problem.title, problemName: s.problem.title,
problemId: s.problem_id, problemId: s.problem_id,
language: displayConfig.hideCode ? null : ((s.language != null && s.language !== '') ? syzoj.config.languages[s.language].show : null), language: displayConfig.showCode ? ((s.language != null && s.language !== '') ? syzoj.config.languages[s.language].show : null) : null,
codeSize: displayConfig.hideCode ? null : syzoj.utils.formatSize(s.code.length), codeSize: displayConfig.showCode ? syzoj.utils.formatSize(s.code.length) : null,
submitTime: syzoj.utils.formatDate(s.submit_time), submitTime: syzoj.utils.formatDate(s.submit_time),
}); });
const getRoughResult = (x, displayConfig) => { const getRoughResult = (x, displayConfig) => {
if (displayConfig.hideResult) { if (displayConfig.showResult) {
if (x.pending) {
return null;
} else {
return {
result: x.status,
time: displayConfig.showUsage ? x.total_time : null,
memory: displayConfig.showUsage ? x.max_memory : null,
score: displayConfig.showUsage ? x.score : null
};
}
} else {
// 0: Waiting 1: Running // 0: Waiting 1: Running
if (x.status === "System Error") if (x.status === "System Error")
return { result: "System Error" }; return { result: "System Error" };
@ -23,18 +34,41 @@ const getRoughResult = (x, displayConfig) => {
return { result: "Compile Error" }; return { result: "Compile Error" };
} }
} }
} else { }
if (x.pending) { }
const processOverallResult = (source, config) => {
if (source == null)
return null; return null;
} else { if (source.error != null) {
return { return {
result: x.status, error: source.error,
time: displayConfig.hideUsage ? null : x.total_time, systemMessage: source.systemMessage
memory: displayConfig.hideUsage ? null : x.max_memory,
score: displayConfig.hideUsage ? null : x.score
}; };
} }
return {
compile: source.compile,
judge: config.showDetailResult ? (source.judge && {
subtasks: source.judge.subtasks && source.judge.subtasks.map(st => ({
score: st.score,
cases: st.cases.map(cs => ({
status: cs.status,
result: cs.result && {
type: cs.result.type,
time: config.showUsage ? cs.result.time : undefined,
memory: config.showUsage ? cs.result.memory : undefined,
scoringRate: cs.result.scoringRate,
systemMessage: cs.result.systemMessage,
input: config.showTestdata ? cs.result.input : undefined,
output: config.showTestdata ? cs.result.output : undefined,
userOutput: config.showTestdata ? cs.result.userOutput : undefined,
userError: config.showTestdata ? cs.result.userError : undefined,
spjMessage: config.showTestdata ? cs.result.spjMessage : undefined,
} }
}))
}))
}) : null
};
} }
module.exports = { getRoughResult, getSubmissionInfo }; module.exports = { getRoughResult, getSubmissionInfo, processOverallResult };

77
modules/contest.js

@ -27,7 +27,7 @@ let JudgeState = syzoj.model('judge_state');
let User = syzoj.model('user'); let User = syzoj.model('user');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { getSubmissionInfo, getRoughResult } = require('../libs/submissions_process'); const { getSubmissionInfo, getRoughResult, processOverallResult } = require('../libs/submissions_process');
app.get('/contests', async (req, res) => { app.get('/contests', async (req, res) => {
try { try {
@ -263,14 +263,14 @@ app.get('/contest/:id/ranklist', async (req, res) => {
}); });
function getDisplayConfig(contest) { function getDisplayConfig(contest) {
const hideResult = !contest.allowedSeeingResult();
return { return {
hideScore: !contest.allowedSeeingScore(), showScore: contest.allowedSeeingScore(),
hideUsage: true, showUsage: false,
hideCode: true, showCode: false,
hideResult: hideResult, showResult: contest.allowedSeeingResult(),
hideOthers: !contest.allowedSeeingOthers(), showOthers: contest.allowedSeeingOthers(),
showDetailResult: contest.allowedSeeingTestcase(), showDetailResult: contest.allowedSeeingTestcase(),
showTestdata: false,
inContest: true inContest: true
}; };
} }
@ -292,19 +292,19 @@ app.get('/contest/:id/submissions', async (req, res) => {
let user = req.query.submitter && await User.fromName(req.query.submitter); let user = req.query.submitter && await User.fromName(req.query.submitter);
let where = {}; let where = {};
if (displayConfig.hideOthers) { if (displayConfig.showOthers) {
if (user) {
where.user_id = user.id;
}
} else {
if (curUser == null || // Not logined if (curUser == null || // Not logined
(user && user.id !== curUser.id)) { // Not querying himself (user && user.id !== curUser.id)) { // Not querying himself
throw new ErrorMessage("您没有权限执行此操作"); throw new ErrorMessage("您没有权限执行此操作");
} }
where.user_id = curUser.id; where.user_id = curUser.id;
} else {
if (user) {
where.user_id = user.id;
}
} }
if (!displayConfig.hideScore) { if (displayConfig.showScore) {
let minScore = parseInt(req.query.min_score); let minScore = parseInt(req.query.min_score);
let maxScore = parseInt(req.query.max_score); let maxScore = parseInt(req.query.max_score);
@ -325,7 +325,7 @@ app.get('/contest/:id/submissions', async (req, res) => {
else where.language = req.query.language; else where.language = req.query.language;
} }
if (!displayConfig.hideResult) { if (displayConfig.showResult) {
if (req.query.status) where.status = { $like: req.query.status + '%' }; if (req.query.status) where.status = { $like: req.query.status + '%' };
} }
@ -342,7 +342,7 @@ app.get('/contest/:id/submissions', async (req, res) => {
obj.problem.title = syzoj.utils.removeTitleTag(obj.problem.title); obj.problem.title = syzoj.utils.removeTitleTag(obj.problem.title);
}); });
const pushType = hideResult ? 'compile' : 'rough'; const pushType = displayConfig.showResult ? 'rough' : 'compile';
res.render('submissions', { res.render('submissions', {
contest: contest, contest: contest,
items: judge_state.map(x => ({ items: judge_state.map(x => ({
@ -358,6 +358,7 @@ app.get('/contest/:id/submissions', async (req, res) => {
paginate: paginate, paginate: paginate,
form: req.query, form: req.query,
displayConfig: displayConfig, displayConfig: displayConfig,
pushType: pushType
}); });
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);
@ -370,42 +371,44 @@ app.get('/contest/:id/submissions', async (req, res) => {
app.get('/contest/submission/:id', async (req, res) => { app.get('/contest/submission/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); const id = parseInt(req.params.id);
let judge = await JudgeState.fromID(id); const judge = await JudgeState.fromID(id);
if (!judge || !await judge.isAllowedVisitBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。");
const curUser = res.locals.user;
if ((!curUser) || judge.user_id !== curUser.id) throw new ErrorMessage("您没有权限执行此操作。");
if (judge.type !== 1) {
return res.redirect(syzoj.utils.makeUrl(['submission', id]));
}
let contest; const contest = await Contest.fromID(judge.type_info);
if (judge.type === 1) {
contest = await Contest.fromID(judge.type_info);
contest.ended = contest.isEnded(); contest.ended = contest.isEnded();
let problems_id = await contest.getProblems();
judge.problem_id = problems_id.indexOf(judge.problem_id) + 1;
judge.problem.title = syzoj.utils.removeTitleTag(judge.problem.title);
if (!contest.ended && !await judge.problem.isAllowedEditBy(res.locals.user)) { const displayConfig = getDisplayConfig(contest);
throw new Error("对不起,在比赛结束之前,您不能查看评测结果。"); displayConfig.showCode = true;
}
}
await judge.loadRelationships(); await judge.loadRelationships();
const problems_id = await contest.getProblems();
judge.problem_id = problems_id.indexOf(judge.problem_id) + 1;
judge.problem.title = syzoj.utils.removeTitleTag(judge.problem.title);
if (judge.problem.type !== 'submit-answer') { if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length; judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
} }
judge.allowedRejudge = await judge.problem.isAllowedEditBy(res.locals.user);
judge.allowedManage = await judge.problem.isAllowedManageBy(res.locals.user);
res.render('submission', { res.render('submission', {
info: getSubmissionInfo(judge), info: getSubmissionInfo(judge, displayConfig),
roughResult: getRoughResult(judge), roughResult: getRoughResult(judge, displayConfig),
code: (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '', code: (displayConfig.showCode && judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '',
detailResult: judge.result, detailResult: processOverallResult(judge.result, displayConfig),
socketToken: judge.pending ? jwt.sign({ socketToken: (displayConfig.showDetailResult && judge.pending) ? jwt.sign({
taskId: judge.id, taskId: judge.id,
displayConfig: displayConfig,
type: 'detail' type: 'detail'
}, syzoj.config.judge_token) : null }, syzoj.config.judge_token) : null,
displayConfig: displayConfig,
contest: contest
}); });
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);

44
modules/submission.js

@ -24,7 +24,18 @@ let User = syzoj.model('user');
let Contest = syzoj.model('contest'); let Contest = syzoj.model('contest');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { getSubmissionInfo, getRoughResult } = require('../libs/submissions_process'); const { getSubmissionInfo, getRoughResult, processOverallResult } = require('../libs/submissions_process');
const displayConfig = {
showScore: true,
showUsage: true,
showCode: true,
showResult: true,
showOthers: true,
showTestdata: true,
showDetailResult: true,
inContest: false,
};
// s is JudgeState // s is JudgeState
app.get('/submissions', async (req, res) => { app.get('/submissions', async (req, res) => {
@ -93,15 +104,6 @@ app.get('/submissions', async (req, res) => {
await judge_state.forEachAsync(async obj => obj.loadRelationships()); await judge_state.forEachAsync(async obj => obj.loadRelationships());
const displayConfig = {
hideScore: false,
hideUsage: false,
hideCode: false,
hideResult: false,
hideOthers: false,
inContest: false,
showDetailResult: true
};
res.render('submissions', { res.render('submissions', {
// judge_state: judge_state, // judge_state: judge_state,
items: judge_state.map(x => ({ items: judge_state.map(x => ({
@ -129,17 +131,15 @@ app.get('/submissions', async (req, res) => {
app.get('/submission/:id', async (req, res) => { app.get('/submission/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); const id = parseInt(req.params.id);
let judge = await JudgeState.fromID(id); const judge = await JudgeState.fromID(id);
if (!judge || !await judge.isAllowedVisitBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。");
if (!await judge.isAllowedVisitBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
let contest; let contest;
if (judge.type === 1) { if (judge.type === 1) {
contest = await Contest.fromID(judge.type_info); contest = await Contest.fromID(judge.type_info);
contest.ended = contest.isEnded(); contest.ended = contest.isEnded();
let problems_id = await contest.getProblems();
judge.problem_id = problems_id.indexOf(judge.problem_id) + 1;
judge.problem.title = syzoj.utils.removeTitleTag(judge.problem.title);
if (!contest.ended && !await judge.problem.isAllowedEditBy(res.locals.user)) { if (!contest.ended && !await judge.problem.isAllowedEditBy(res.locals.user)) {
throw new Error("对不起,在比赛结束之前,您不能查看评测结果。"); throw new Error("对不起,在比赛结束之前,您不能查看评测结果。");
@ -157,14 +157,16 @@ app.get('/submission/:id', async (req, res) => {
judge.allowedManage = await judge.problem.isAllowedManageBy(res.locals.user); judge.allowedManage = await judge.problem.isAllowedManageBy(res.locals.user);
res.render('submission', { res.render('submission', {
info: getSubmissionInfo(judge), info: getSubmissionInfo(judge, displayConfig),
roughResult: getRoughResult(judge), roughResult: getRoughResult(judge, displayConfig),
code: (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '', code: (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '',
detailResult: judge.result, detailResult: processOverallResult(judge.result, displayConfig),
socketToken: judge.pending ? jwt.sign({ socketToken: judge.pending ? jwt.sign({
taskId: judge.id, taskId: judge.id,
type: 'detail' type: 'detail',
}, syzoj.config.judge_token) : null displayConfig: displayConfig
}, syzoj.config.judge_token) : null,
displayConfig: displayConfig
}); });
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);

260
views/submission.ejs

@ -1,5 +1,263 @@
<% this.title = '提交记录 #' + info.taskId %> <% this.title = '提交记录 #' + info.taskId %>
<% include util %>
<% include header %> <% include header %>
<script src="/textFit.js"></script> <script src="/textFit.js"></script>
<span id="submission_content"><% include submission_content %></span> <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>
</tr>
</thead>
<tbody>
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig"></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">得分
<span style="font-weight: normal; ">{{ 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;
}
},
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;
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("Finish: " + JSON.stringify(p));
vueApp.roughData.running = false;
vueApp.roughData.result = p.roughResult;
vueApp.detailResult = p.result;
socket.close();
});
socket.emit('join', token, (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;
}
}
} else {
alert("ERROR: " + JSON.stringify(data));
}
});
});
};
loadSocketIO();
}
</script>
</span>
<% include footer %> <% include footer %>

247
views/submission_content.ejs

@ -1,247 +0,0 @@
<% include util %>
<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>分数</th>
<th>总时间</th>
<th>内存</th>
<th>代码 / 答案文件</th>
<th>提交者</th>
<th>提交时间</th>
</tr>
</thead>
<tbody>
<tr is="submission-item" v-bind:data="roughData"></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">得分
<span style="font-weight: normal; ">{{ 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="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script>
<script src="https://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 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
},
code: <%- JSON.stringify(code) %>,
detailResult: <%- JSON.stringify(detailResult) %>,
},
computed: {
singleSubtask() {
return this.detailResult.judge.subtasks.length === 1;
}
},
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 socket = io(socketUrl);
socket.on('connect', () => {
socket.on('start', () => {
vueApp.roughData.running = true;
});
socket.on('update', (p) => {
console.log("Delta: " + JSON.stringify(p));
const current = JSON.parse(JSON.stringify(vueApp.detailResult));
jsondiffpatch.patch(current, p.delta);
vueApp.detailResult = current;
});
socket.on('finish', (p) => {
console.log("Finish: " + JSON.stringify(p));
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.current.running) {
vueApp.roughData.running = true;
}
vueApp.detailResult = data.current.current;
}
} else {
alert("ERROR: " + JSON.stringify(data));
}
})
});
}
</script>

28
views/submissions.ejs

@ -6,23 +6,23 @@
<% if (displayConfig.inContest) { %> <% if (displayConfig.inContest) { %>
<div class="ui large info message"> <div class="ui large info message">
<div class="ui header">比赛 - <%= contest.title %></div> <div class="ui header">比赛 - <%= contest.title %></div>
<% if (displayConfig.hideOthers) { %> <% if (displayConfig.showOthers) { %>
<p>您只能看到自己的提交。</p>
<% } else { %>
<p>您可以看到其他人的提交。 <p>您可以看到其他人的提交。
<% } else { %>
<p>您只能看到自己的提交。</p>
<% } %> <% } %>
</div> </div>
<% } %> <% } %>
<form action="<%= syzoj.utils.makeUrl(['submissions']) %>" class="ui mini form" method="get" role="form" id="form"> <form action="<%= syzoj.utils.makeUrl(displayConfig.inContest ? ['contest', contest.id, 'submissions'] : ['submissions']) %>" class="ui mini form" method="get" role="form" id="form">
<div class="inline fields" style="margin-bottom: 25px; white-space: nowrap; "> <div class="inline fields" style="margin-bottom: 25px; white-space: nowrap; ">
<label style="font-size: 1.2em; margin-right: 1px; ">题目:</label> <label style="font-size: 1.2em; margin-right: 1px; ">题目:</label>
<div class="field"><input name="problem_id" style="width: 50px; " type="text" value="<%= form.problem_id %>"></div> <div class="field"><input name="problem_id" style="width: 50px; " type="text" value="<%= form.problem_id %>"></div>
<% if (!displayConfig.hideOthers) { %> <% if (displayConfig.showOthers) { %>
<label style="font-size: 1.2em; margin-right: 1px; ">提交者:</label> <label style="font-size: 1.2em; margin-right: 1px; ">提交者:</label>
<div class="field"><input name="submitter" style="width: 100px; " type="text" value="<%= form.submitter %>"></div> <div class="field"><input name="submitter" style="width: 100px; " type="text" value="<%= form.submitter %>"></div>
<% } %> <% } %>
<% if (!displayConfig.hideScore) { %> <% if (displayConfig.showScore) { %>
<label style="font-size: 1.2em; margin-right: 1px; ">分数:</label> <label style="font-size: 1.2em; margin-right: 1px; ">分数:</label>
<div class="field" style="padding-right: 6px; "><input name="min_score" style="width: 45px; " type="text" value="<%= form.min_score || 0 %>"></div> <div class="field" style="padding-right: 6px; "><input name="min_score" style="width: 45px; " type="text" value="<%= form.min_score || 0 %>"></div>
<label style="font-size: 1.2em; margin-right: 7px; ">~</label> <label style="font-size: 1.2em; margin-right: 7px; ">~</label>
@ -43,7 +43,7 @@
</div> </div>
</div> </div>
</div> </div>
<% if (!displayConfig.hideResult) { %> <% if (displayConfig.showResult) { %>
<label style="font-size: 1.2em; margin-right: 1px; ">状态:</label> <label style="font-size: 1.2em; margin-right: 1px; ">状态:</label>
<div class="field"> <div class="field">
<div class="ui fluid selection dropdown" id="select_status" style="width: 210px; "> <div class="ui fluid selection dropdown" id="select_status" style="width: 210px; ">
@ -64,7 +64,7 @@
<i class="search icon"></i> <i class="search icon"></i>
查询 查询
</button> </button>
<% if (user && !displayConfig.hideOthers) { %> <% if (user && displayConfig.showOthers) { %>
<a class="ui mini labeled icon blue button" style="margin-left: auto; " id="my_submit"> <a class="ui mini labeled icon blue button" style="margin-left: auto; " id="my_submit">
<i class="user icon"></i> <i class="user icon"></i>
我的提交 我的提交
@ -86,10 +86,10 @@
<th>编号</th> <th>编号</th>
<th>题目</th> <th>题目</th>
<th>状态</th> <th>状态</th>
<th v-if="!displayConfig.hideScore">分数</th> <th v-if="displayConfig.showScore">分数</th>
<th v-if="!displayConfig.hideUsage">总时间</th> <th v-if="displayConfig.showUsage">总时间</th>
<th v-if="!displayConfig.hideUsage">内存</th> <th v-if="displayConfig.showUsage">内存</th>
<th v-if="!displayConfig.hideCode">代码 / 答案文件</th> <th v-if="displayConfig.showCode">代码 / 答案文件</th>
<th>提交者</th> <th>提交者</th>
<th>提交时间</th> <th>提交时间</th>
</tr> </tr>
@ -103,8 +103,8 @@
<% include page %> <% include page %>
</div> </div>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script> <script src="//cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script> <script src="//cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script>
<% include submissions_item %> <% include submissions_item %>
<script> <script>

25
views/submissions_item.ejs

@ -1,9 +1,11 @@
<% include util %> <% include util %>
<% include status_label %> <% include status_label %>
<script src="//cdn.bootcss.com/textfit/2.3.1/textFit.min.js"></script>
<script> <script>
const submissionUrl = <%- JSON.stringify(displayConfig.inContest ? const submissionUrl = <%- JSON.stringify(displayConfig.inContest ?
syzoj.utils.makeUrl(['contest', contest.id, 'submission', 'VanDarkholme']) : syzoj.utils.makeUrl(['contest', 'submission', 'VanDarkholme']) :
syzoj.utils.makeUrl(['submission', 'VanDarkholme'])) %>; syzoj.utils.makeUrl(['submission', 'VanDarkholme'])) %>;
const problemUrl = <%- JSON.stringify(displayConfig.inContest ? const problemUrl = <%- JSON.stringify(displayConfig.inContest ?
syzoj.utils.makeUrl(['contest', contest.id, 'problem', 'VanDarkholme']) : syzoj.utils.makeUrl(['contest', contest.id, 'problem', 'VanDarkholme']) :
@ -18,11 +20,7 @@ Vue.component('submission-item', {
if (data.result) { if (data.result) {
return data.result.result; return data.result.result;
} else if (data.running) { } else if (data.running) {
if (this.config.hideResult) { return this.config.showResult ? 'Running' : 'Compiling';
return 'Compiling';
} else {
return 'Running';
}
} else return 'Waiting'; } else return 'Waiting';
}, },
submissionLink() { submissionLink() {
@ -39,6 +37,9 @@ Vue.component('submission-item', {
alpha(number) { alpha(number) {
if (number && parseInt(number) == number && parseInt(number) > 0) return String.fromCharCode('A'.charCodeAt(0) + parseInt(number) - 1); if (number && parseInt(number) == number && parseInt(number) > 0) return String.fromCharCode('A'.charCodeAt(0) + parseInt(number) - 1);
} }
},
mounted() {
textFit(this.$refs.problemLabel, { maxFontSize: 14 });
} }
}); });
</script> </script>
@ -46,18 +47,18 @@ Vue.component('submission-item', {
<script id="submissionItemTemplate" type="text/x-template"> <script id="submissionItemTemplate" type="text/x-template">
<tr> <tr>
<td><a :href="submissionLink">#{{ data.info.taskId }}</a></td> <td><a :href="submissionLink">#{{ data.info.taskId }}</a></td>
<td><a :href="problemLink">#{{ config.inContest ? alpha(data.info.problemId) : data.info.problemId }}. {{ data.info.problemName }}</a></td> <td ref="problemLabel"><a :href="problemLink">#{{ config.inContest ? alpha(data.info.problemId) : data.info.problemId }}. {{ data.info.problemName }}</a></td>
<td><a :href="submissionLink"><status-label :status="statusString"></status-label></a></td> <td><a :href="submissionLink"><status-label :status="statusString"></status-label></a></td>
<template v-if="data.result"> <template v-if="data.result">
<td v-if="!config.hideScore"><span class="score" :class="scoreClass">{{ (data.result.score != null && data.result.score !== NaN) ? Math.floor(data.result.score) : '' }}</span></td> <td v-if="config.showScore"><span class="score" :class="scoreClass">{{ (data.result.score != null && data.result.score !== NaN) ? Math.floor(data.result.score) : '' }}</span></td>
<td v-if="!config.hideUsage">{{ (data.result.time != null && data.result.time !== NaN) ? data.result.time.toString() + ' ms' : '' }}</td> <td v-if="config.showUsage">{{ (data.result.time != null && data.result.time !== NaN) ? data.result.time.toString() + ' ms' : '' }}</td>
<td v-if="!config.hideUsage">{{ (data.result.memory != null && data.result.memory !== NaN) ? data.result.memory.toString() + ' KiB' : '' }}</td> <td v-if="config.showUsage">{{ (data.result.memory != null && data.result.memory !== NaN) ? data.result.memory.toString() + ' KiB' : '' }}</td>
</template> <template v-else> </template> <template v-else>
<td v-if="!config.hideScore"/> <td v-if="!config.hideUsage"/> <td v-if="!config.hideUsage"/> <td v-if="config.showScore"/> <td v-if="config.showUsage"/> <td v-if="config.showUsage"/>
</template> </template>
<td v-if="!config.hideCode">{{ data.info.language != null ? data.info.language + ' / ' : '' }}{{ data.info.codeSize }}</td> <td v-if="config.showCode">{{ data.info.language != null ? data.info.language + ' / ' : '' }}{{ data.info.codeSize }}</td>
<td>{{ data.info.user }}</td> <td>{{ data.info.user }}</td>
<td>{{ data.info.submitTime }}</td> <td>{{ data.info.submitTime }}</td>
</tr> </tr>

Loading…
Cancel
Save