Browse Source

Add testdata view and manage

pull/6/head
Menci 7 years ago
parent
commit
c417e1e660
  1. 1
      config-example.json
  2. 114
      models/problem.js
  3. 2
      models/user.js
  4. 8
      modules/api.js
  5. 2
      modules/contest.js
  6. 76
      modules/problem.js
  7. 1
      modules/user.js
  8. 150
      utility.js
  9. 10
      views/problem.ejs
  10. 262
      views/problem_data.ejs
  11. 144
      views/problem_manage.ejs
  12. 42
      views/problem_testcases.ejs

1
config-example.json

@ -29,6 +29,7 @@
"time_limit": 10000,
"memory_limit": 1024,
"data_size": 209715200,
"testdata": 209715200,
"submit_code": 102400,
"submit_answer": 10485760
},

114
models/problem.js

@ -232,7 +232,6 @@ let model = db.define('problem', {
time_limit: { type: Sequelize.INTEGER },
memory_limit: { type: Sequelize.INTEGER },
testdata_id: { type: Sequelize.INTEGER },
additional_file_id: { type: Sequelize.INTEGER },
ac_num: { type: Sequelize.INTEGER },
@ -286,14 +285,13 @@ class Problem extends Model {
file_io_input_name: '',
file_io_output_name: '',
type: ''
type: 'traditional'
}, val)));
}
async loadRelationships() {
this.user = await User.fromID(this.user_id);
this.publicizer = await User.fromID(this.publicizer_id);
this.testdata = await File.fromID(this.testdata_id);
this.additional_file = await File.fromID(this.additional_file_id);
}
@ -316,12 +314,102 @@ class Problem extends Model {
return user.is_admin;
}
getTestdataPath() {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', this.id.toString());
}
async updateTestdata(path) {
let AdmZip = require('adm-zip');
let zip = new AdmZip(path);
let unzipSize = 0;
for (let x of zip.getEntries()) unzipSize += x.header.size;
if (unzipSize > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
let dir = this.getTestdataPath();
let fs = Promise.promisifyAll(require('fs-extra'));
await fs.removeAsync(dir);
await fs.ensureDirAsync(dir);
zip.extractAllTo(dir);
await fs.moveAsync(path, dir + '.zip', { overwrite: true });
}
async uploadTestdataSingleFile(filename, filepath, size) {
let dir = this.getTestdataPath();
let fs = Promise.promisifyAll(require('fs-extra')), path = require('path');
await fs.ensureDirAsync(dir);
let oldSize = 0;
let list = await this.listTestdata();
if (list) {
for (let file of list.files) if (file.filename !== filename) oldSize += file.size;
}
if (oldSize + size > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
await fs.moveAsync(filepath, path.join(dir, filename), { overwrite: true });
let AdmZip = require('adm-zip');
let zip = new AdmZip();
list = await this.listTestdata();
for (let file of list.files) zip.addLocalFile(path.join(dir, file.filename), '', file.filename);
zip.writeZip(dir + '.zip');
}
async hasSpecialJudge() {
try {
let fs = Promise.promisifyAll(require('fs-extra'));
let dir = this.getTestdataPath();
let list = await fs.readdirAsync(dir);
return list.includes('spj.js') || list.find(x => x.startsWith('spj_')) !== undefined;
} catch (e) {
return false;
}
}
async listTestdata() {
try {
let fs = Promise.promisifyAll(require('fs-extra')), path = require('path');
let dir = this.getTestdataPath();
let list = await fs.readdirAsync(dir);
list = await list.mapAsync(async x => {
let stat = await fs.statAsync(path.join(dir, x));
if (!stat.isFile()) return undefined;
return {
filename: x,
size: stat.size
}
});
list = list.filter(x => x);
let res = {
files: list,
zip: null
};
try {
let stat = await fs.statAsync(this.getTestdataPath() + '.zip');
if (stat.isFile()) {
res.zip = {
size: stat.size
}
}
} catch (e) {}
return res;
} catch (e) {
console.log(e);
return null;
}
}
async updateFile(path, type) {
let file = await File.upload(path, type);
if (type === 'testdata') {
this.testdata_id = file.id;
} else if (type === 'additional_file') {
if (type === 'additional_file') {
this.additional_file_id = file.id;
}
@ -505,7 +593,21 @@ class Problem extends Model {
}
}
let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = oldTestdataDir + '.zip';
this.id = id;
// Move testdata
let newTestdataDir = this.getTestdataPath(), newTestdataZip = newTestdataDir + '.zip';
let fs = Promise.promisifyAll(require('fs-extra'));
if (await syzoj.utils.isDir(oldTestdataDir)) {
await fs.moveAsync(oldTestdataDir, newTestdataDir);
}
if (await syzoj.utils.isFile(oldTestdataZip)) {
await fs.moveAsync(oldTestdataZip, newTestdataZip);
}
await this.save();
}

2
models/user.js

@ -197,8 +197,6 @@ class User extends Model {
let oldPrivileges = await this.getPrivileges();
console.log(newPrivileges);
let delPrivileges = oldPrivileges.filter(x => !newPrivileges.includes(x));
let addPrivileges = newPrivileges.filter(x => !oldPrivileges.includes(x));

8
modules/api.js

@ -229,14 +229,6 @@ app.post('/api/update_judge/:id', async (req, res) => {
}
});
app.get('/static/uploads/testdata/:md5', async (req, res) => {
try {
res.sendFile(File.resolvePath('testdata', req.params.md5));
} catch (e) {
res.status(500).send(e);
}
});
app.get('/static/uploads/answer/:md5', async (req, res) => {
try {
res.sendFile(File.resolvePath('answer', req.params.md5));

2
modules/contest.js

@ -334,6 +334,8 @@ app.get('/contest/:id/:pid', async (req, res) => {
let problem_id = problems_id[pid - 1];
let problem = await Problem.fromID(problem_id);
problem.specialJudge = await problem.hasSpecialJudge();
await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]);
let state = await problem.getJudgeState(res.locals.user, false);

76
modules/problem.js

@ -190,6 +190,7 @@ app.get('/problem/:id', async (req, res) => {
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.allowedManage = await problem.isAllowedManageBy(res.locals.user);
problem.specialJudge = await problem.hasSpecialJudge();
if (problem.is_public || problem.allowedEdit) {
await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]);
@ -427,7 +428,7 @@ app.post('/problem/:id/import', async (req, res) => {
let fs = require('bluebird').promisifyAll(require('fs'));
try {
let data = await download(req.body.url + (req.body.url.endsWith('/') ? 'download' : '/download'));
let data = await download(req.body.url + (req.body.url.endsWith('/') ? 'testdata/download' : '/testdata/download'));
await fs.writeFileAsync(tmpFile.path, data);
await problem.updateTestdata(tmpFile.path);
} catch (e) {
@ -443,7 +444,8 @@ app.post('/problem/:id/import', async (req, res) => {
}
});
app.get('/problem/:id/data', async (req, res) => {
// The 'manage' is not `allow manage`'s 'manage', I just have no better name for it.
app.get('/problem/:id/manage', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -453,8 +455,11 @@ app.get('/problem/:id/data', async (req, res) => {
await problem.loadRelationships();
res.render('problem_data', {
problem: problem
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath());
res.render('problem_manage', {
problem: problem,
testcases: testcases
});
} catch (e) {
syzoj.log(e);
@ -464,7 +469,7 @@ app.get('/problem/:id/data', async (req, res) => {
}
});
app.post('/problem/:id/data', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -491,7 +496,7 @@ app.post('/problem/:id/data', app.multer.fields([{ name: 'testdata', maxCount: 1
if (validateMsg) throw new ErrorMessage('无效的题目数据配置。', null, validateMsg);
if (req.files['testdata']) {
await problem.updateFile(req.files['testdata'][0].path, 'testdata');
await problem.updateTestdata(req.files['testdata'][0].path);
}
if (req.files['additional_file']) {
@ -500,7 +505,7 @@ app.post('/problem/:id/data', app.multer.fields([{ name: 'testdata', maxCount: 1
await problem.save();
res.redirect(syzoj.utils.makeUrl(['problem', id, 'data']));
res.redirect(syzoj.utils.makeUrl(['problem', id, 'manage']));
} catch (e) {
syzoj.log(e);
res.render('error', {
@ -621,7 +626,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
}
});
app.get('/problem/:id/download/testdata', async (req, res) => {
app.get('/problem/:id/testdata', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -629,11 +634,60 @@ app.get('/problem/:id/download/testdata', async (req, res) => {
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
await problem.loadRelationships();
let testdata = await problem.listTestdata();
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath());
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user)
res.render('problem_data', {
problem: problem,
testdata: testdata,
testcases: testcases
});
} catch (e) {
syzoj.log(e);
res.status(404);
res.render('error', {
err: e
});
}
});
app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
if (!problem.testdata) throw new ErrorMessage('无测试数据。');
if (req.files) {
for (let file of req.files) {
await problem.uploadTestdataSingleFile(file.originalname, file.path, file.size);
}
}
res.redirect(syzoj.utils.makeUrl(['problem', id, 'testdata']));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/problem/:id/testdata/download/:filename?', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
res.download(problem.testdata.getPath(), `testdata_${id}.zip`);
let path = require('path');
let filename = req.params.filename ? path.join(problem.getTestdataPath(), req.params.filename) : (problem.getTestdataPath() + '.zip');
if (!await syzoj.utils.isFile(filename)) throw new ErrorMessage('文件不存在。');
res.download(filename, path.basename(filename));
} catch (e) {
syzoj.log(e);
res.status(404);

1
modules/user.js

@ -179,7 +179,6 @@ app.post('/user/:id/edit', async (req, res) => {
error_info: ''
});
} catch (e) {
console.log(e);
user.privileges = await user.getPrivileges();
res.locals.user.allowedManage = await res.locals.user.hasPrivilege('manage_user');

150
utility.js

@ -36,7 +36,9 @@ global.ErrorMessage = class ErrorMessage {
}
};
let Promise = require('bluebird');
let path = require('path');
let fs = Promise.promisifyAll(require('fs-extra'));
let util = require('util');
let renderer = require('moemark-renderer');
let moment = require('moment');
@ -44,7 +46,6 @@ let url = require('url');
let querystring = require('querystring');
let pygmentize = require('pygmentize-bundled-cached');
let gravatar = require('gravatar');
let AdmZip = require('adm-zip');
let filesize = require('file-size');
let AsyncLock = require('async-lock');
@ -195,82 +196,89 @@ module.exports = {
gravatar(email, size) {
return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn');
},
parseTestdata(filename) {
let zip = new AdmZip(filename);
let list = zip.getEntries().filter(e => !e.isDirectory).map(e => e.entryName);
let res = [];
if (!list.includes('data_rule.txt')) {
res[0] = {};
res[0].cases = [];
for (let file of list) {
let parsedName = path.parse(file);
if (parsedName.ext === '.in') {
if (list.includes(`${parsedName.name}.out`)) {
res[0].cases.push({
input: file,
output: `${parsedName.name}.out`
});
}
async parseTestdata(dir) {
if (!await syzoj.utils.isDir(dir)) return null;
try {
// Get list of *files*
let list = await (await fs.readdirAsync(dir)).filterAsync(async x => await syzoj.utils.isFile(path.join(dir, x)));
let res = [];
if (!list.includes('data_rule.txt')) {
res[0] = {};
res[0].cases = [];
for (let file of list) {
let parsedName = path.parse(file);
if (parsedName.ext === '.in') {
if (list.includes(`${parsedName.name}.out`)) {
res[0].cases.push({
input: file,
output: `${parsedName.name}.out`
});
}
if (list.includes(`${parsedName.name}.ans`)) {
res[0].cases.push({
input: file,
output: `${parsedName.name}.ans`
});
if (list.includes(`${parsedName.name}.ans`)) {
res[0].cases.push({
input: file,
output: `${parsedName.name}.ans`
});
}
}
}
}
res[0].type = 'sum';
res[0].score = 100;
res[0].cases.sort((a, b) => {
function getLastInteger(s) {
let re = /(\d+)\D*$/;
let x = re.exec(s);
if (x) return parseInt(x[1]);
else return -1;
}
res[0].type = 'sum';
res[0].score = 100;
res[0].cases.sort((a, b) => {
function getLastInteger(s) {
let re = /(\d+)\D*$/;
let x = re.exec(s);
if (x) return parseInt(x[1]);
else return -1;
}
return getLastInteger(a.input) - getLastInteger(b.input);
});
} else {
let lines = zip.readAsText('data_rule.txt').split('\r').join('').split('\n').filter(x => x.length !== 0);
return getLastInteger(a.input) - getLastInteger(b.input);
});
} else {
let lines = (await fs.readFileAsync(path.join(dir, 'data_rule.txt'))).toString().split('\r').join('').split('\n').filter(x => x.length !== 0);
if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。';
if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。';
let input = lines[lines.length - 2];
let output = lines[lines.length - 1];
let input = lines[lines.length - 2];
let output = lines[lines.length - 1];
for (let s = 0; s < lines.length - 2; ++s) {
res[s] = {};
res[s].cases = [];
let numbers = lines[s].split(' ').filter(x => x);
if (numbers[0].includes(':')) {
let tokens = numbers[0].split(':');
res[s].type = tokens[0] || 'sum';
res[s].score = parseFloat(tokens[1]) || (100 / (lines.length - 2));
numbers.shift();
} else {
res[s].type = 'sum';
res[s].score = 100;
}
for (let i of numbers) {
let testcase = {
input: input.replace('#', i),
output: output.replace('#', i)
};
for (let s = 0; s < lines.length - 2; ++s) {
res[s] = {};
res[s].cases = [];
let numbers = lines[s].split(' ').filter(x => x);
if (numbers[0].includes(':')) {
let tokens = numbers[0].split(':');
res[s].type = tokens[0] || 'sum';
res[s].score = parseFloat(tokens[1]) || (100 / (lines.length - 2));
numbers.shift();
} else {
res[s].type = 'sum';
res[s].score = 100;
}
for (let i of numbers) {
let testcase = {
input: input.replace('#', i),
output: output.replace('#', i)
};
if (!list.includes(testcase.input)) throw `找不到文件 ${testcase.input}`;
if (!list.includes(testcase.output)) throw `找不到文件 ${testcase.output}`;
res[s].cases.push(testcase);
if (!list.includes(testcase.input)) throw `找不到文件 ${testcase.input}`;
if (!list.includes(testcase.output)) throw `找不到文件 ${testcase.output}`;
res[s].cases.push(testcase);
}
}
res = res.filter(x => x.cases && x.cases.length !== 0);
}
res = res.filter(x => x.cases && x.cases.length !== 0);
res.spj = list.includes('spj.js') || list.some(s => s.startsWith('spj_'));
return res;
} catch (e) {
return { error: e };
}
res.spj = list.includes('spj.js') || list.some(s => s.startsWith('spj_'));
return res;
},
ansiToHTML(s) {
let Convert = require('ansi-to-html');
@ -340,5 +348,19 @@ module.exports = {
let crypto = require('crypto');
let decipher = crypto.createDecipher('aes-256-ctr', password);
return Buffer.concat([decipher.update(buffer), decipher.final()]);
},
async isFile(path) {
try {
return (await fs.statAsync(path)).isFile();
} catch (e) {
return false;
}
},
async isDir(path) {
try {
return (await fs.statAsync(path)).isDirectory();
} catch (e) {
return false;
}
}
};

10
views/problem.ejs

@ -52,6 +52,10 @@ if (contest) {
<span class="ui label">标准输入输出</span>
<% } %>
</div>
<div class="row" style="margin-top: -23px">
<span class="ui label">评测方式:<%= problem.specialJudge ? 'Special Judge' : '文本比较' %></span>
<span class="ui label">题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %></span>
</div>
<div class="row" style="margin-top: -23px">
<span class="ui label">上传者:
<% if (problem.is_anonymous && !problem.allowedManage) { %>
@ -88,15 +92,15 @@ if (contest) {
<a class="small ui primary button" href="#submit_code">提交</a>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['submissions'], { problem_id: problem.id }) %>">提交记录</a>
<a class="small ui orange button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', 'fastest']) %>">统计</a>
<% if (problem.testdata) { %><a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'testdata']) %>">下载测试数据</a><% } %>
<a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata']) %>">测试数据</a>
<% } %>
<% if (problem.additional_file) { %><a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file']) %>">下载附加文件</a><% } %>
<% if (problem.additional_file) { %><a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file']) %>">附加文件</a><% } %>
</div>
<% if (!contest) { %>
<div class="ui buttons right floated">
<% if (problem.allowedEdit) { %>
<a class="small ui button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'edit']) %>">编辑</a>
<a class="small ui button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'data']) %>">管理测试数据</a>
<a class="small ui button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'manage']) %>">管理</a>
<% } %>
<% if (problem.allowedManage) { %>
<% if (problem.is_public) { %>

262
views/problem_data.ejs

@ -1,188 +1,92 @@
<% this.title = '上传测试数据'; %>
<% include header %>
<%
let subtaskType = {
sum: '测试点分数按百分比相加',
min: '取各测试点最低分',
mul: '测试点分数按百分比相乘'
};
this.title = '测试数据';
function getIcon(filename) {
let a = {
'.cpp': 'file code outline',
'.c': 'file code outline',
'.cs': 'file code outline',
'.pas': 'file code outline',
'.py': 'file code outline',
'.js': 'file code outline',
'.java': 'file code outline',
'.hs': 'file code outline',
'.vala': 'file code outline',
'.lua': 'file code outline',
'.rb': 'file code outline',
'.vb': 'file code outline',
'.ml': 'file code outline',
'.in': 'file text outline',
'.out': 'file text outline',
'.ans': 'file text outline',
'.txt': 'file text outline',
'.md': 'file text outline',
'.md': 'file text outline',
'.docx': 'file word outline',
'.odt': 'file word outline',
'.xlsx': 'file excel outline',
'.ods': 'file excel outline',
'.pptx': 'file powerpoint outline',
'.odp': 'file powerpoint outline',
'.zip': 'file archive outline',
'.7z': 'file archive outline',
}
for (let x in a) if (filename.endsWith(x)) return a[x];
return 'file outline';
}
%>
<% include header %>
<div class="padding">
<div class="ui grid">
<div class="row">
<div class="seven wide column">
<% if (problem.testdata) { %>
<%
try {
let list = syzoj.utils.parseTestdata(problem.testdata.getPath());
%>
<% if (list.spj) { %>
<p>评测方式:Special Judge</p>
<% } else { %>
<p>评测方式:文本比较</p>
<% } %>
<table class="ui celled table">
<tbody>
<% let i = 0; %>
<% for (let subtask of list) { %>
<% if (list.length !== 1) { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">子任务 <%= ++i %></h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %>,总分值 <%= subtask.score %></span></th>
</tr>
<% } else { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">单个子任务</h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %></span></th>
</tr>
<% } %>
<% for (let testcase of subtask.cases) { %>
<tr>
<td><%= testcase.input %></td>
<td><%= testcase.output %></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
<% } catch (e) { %>
<h3>数据包错误:<%= e %></h3>
<div class="ui grid">
<div class="seven wide column">
<h3 style="text-align: center; ">测试点信息</h3>
<% include problem_testcases %>
</div>
<div class="nine wide column">
<h3 style="text-align: center; ">文件列表</h3>
<% if (testdata) { %>
<table class="ui very basic center aligned table">
<thead>
<tr>
<th class="left aligned">文件名</th>
<th style="width: 100px">文件大小</th>
<th style="width: 50px">操作</th>
</tr>
</thead>
<tbody>
<% if (testdata.zip) { %>
<tr>
<td class="left aligned"><i class="file archive outline icon"></i> 完整数据包</td>
<td><%= syzoj.utils.formatSize(testdata.zip.size) %></td>
<td><a style="color: #000; " href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata', 'download']) %>"><i class="download icon"></i></td>
</tr>
<% } %>
<% } else { %>
<h3>数据未上传</h3>
<% } %>
</div>
<div class="nine wide column">
<form class="ui form" method="post" enctype="multipart/form-data" onsubmit="return checkSubmit()">
<input type="hidden" name="type" value="<%= problem.type %>">
<div class="ui pointing secondary menu" id="problem-type-tab" style="margin-top: -10px; ">
<a class="<%= problem.type === 'traditional' ? 'active ' : '' %>item" data-tab="traditional">传统</a>
<a class="<%= problem.type === 'interaction' ? 'active ' : '' %>item" data-tab="interaction">交互</a>
<a class="<%= problem.type === 'submit-answer' ? 'active ' : '' %>item" data-tab="submit-answer">提交答案</a>
</div>
<div class="ui <%= problem.type !== 'submit-answer' ? 'active ' : '' %>tab" data-tab="traditional" data-tab="interaction">
<div class="two fields">
<div class="field">
<label for="doc-ds-ipt-1">时间限制(单位: ms)</label>
<input type="number" name="time_limit" value="<%= problem.time_limit %>">
</div>
<div class="field">
<label for="doc-ds-ipt-1">内存限制(单位: MiB)</label>
<input type="number" name="memory_limit" value="<%= problem.memory_limit %>">
</div>
</div>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
</div>
</div>
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
<% if (testdata.files) for (let file of testdata.files) { %>
<tr>
<td class="left aligned"><i class="<%= getIcon(file.filename) %> icon"></i> <%= file.filename %></td>
<td><%= syzoj.utils.formatSize(file.size) %></td>
<td><a style="color: #000; " href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata', 'download', file.filename]) %>"><i class="download icon"></i></td>
</tr>
<% } %>
</tbody>
</table>
<% } else { %>
<h4 style="text-align: center; ">无测试数据</h4>
<% } %>
<% if (problem.allowedEdit) { %>
<form class="ui form" action="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata', 'upload']) %>" method="post" enctype="multipart/form-data">
<div class="inline fields">
<div class="field" style="margin: 0 auto; ">
<label for="answer">上传文件(可一次性上传多个)</label>
<input type="file" name="file" multiple>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; ">
<button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
</div>
</div>
<% } %>
</div>
<div class="ui <%= problem.type === 'submit-answer' ? 'active ' : '' %>tab" data-tab="submit-answer" style="margin-bottom: 10px; ">
<b>为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。</b><br>
提交答案题目不需要设置时间限制、空间限制以及 IO 方式。<br>
提交时使用的答案文件名需要与测试数据中每个测试点输出文件相同,且后缀名为 <code>.out</code>。
</div>
<div class="field">
<label for="testdata">上传测试数据(请使用 ZIP 格式)</label>
<input type="file" id="testdata" name="testdata">
</div>
<div class="field">
<label for="additional_file">上传附加文件(请使用 ZIP 格式)</label>
<input type="file" id="additional_file" name="additional_file">
</div>
<button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
</form>
</div>
<% } %>
</div>
</div>
<div>
<script>
function goEnable() {
document.getElementById('file-io-input-name').disabled = false;
document.getElementById('file-io-output-name').disabled = false;
}
function goDisable() {
document.getElementById('file-io-input-name').disabled = true;
document.getElementById('file-io-output-name').disabled = true;
}
$(function () {
$('#file-io-input-name').on('input keyup change', function (e) {
var prob = $('#file-io-input-name').val();
if (prob.lastIndexOf('.') !== -1) prob = prob.substring(0, prob.lastIndexOf('.'));
$('#file-io-output-name').attr('placeholder', prob + '.out');
});
$('#file-io-output-name').focus(function (e) {
if (!$('#file-io-output-name').val()) {
$('#file-io-output-name').val($('#file-io-output-name').attr('placeholder'));
}
});
$('#problem-type-tab .item').tab();
$('a[data-tab="traditional"]').click(function () {
$('input[name=type]').val('traditional');
if ($('div[data-tab="interaction"]').attr('data-tab', 'traditional').length) $('a[data-tab="traditional"]').click();
});
$('a[data-tab="interaction"]').click(function () {
$('input[name=type]').val('interaction');
if ($('div[data-tab="traditional"]').attr('data-tab', 'interaction').length) $('a[data-tab="interaction"]').click();
});
$('a[data-tab="submit-answer"]').click(function () {
$('input[name=type]').val('submit-answer');
});
});
function checkSubmit() {
;
}
</script>
<% include footer %>
</div>
<% include footer %>

144
views/problem_manage.ejs

@ -0,0 +1,144 @@
<% this.title = '管理题目'; %>
<% include header %>
<div class="padding">
<div class="ui grid">
<div class="row">
<div class="seven wide column">
<% include problem_testcases %>
</div>
<div class="nine wide column">
<form class="ui form" method="post" enctype="multipart/form-data" onsubmit="return checkSubmit()">
<input type="hidden" name="type" value="<%= problem.type %>">
<div class="ui pointing secondary menu" id="problem-type-tab" style="margin-top: -10px; ">
<a class="<%= problem.type === 'traditional' ? 'active ' : '' %>item" data-tab="traditional">传统</a>
<a class="<%= problem.type === 'interaction' ? 'active ' : '' %>item" data-tab="interaction">交互</a>
<a class="<%= problem.type === 'submit-answer' ? 'active ' : '' %>item" data-tab="submit-answer">提交答案</a>
</div>
<div class="ui <%= problem.type !== 'submit-answer' ? 'active ' : '' %>tab" data-tab="traditional" data-tab="interaction">
<div class="two fields">
<div class="field">
<label for="doc-ds-ipt-1">时间限制(单位: ms)</label>
<input type="number" name="time_limit" value="<%= problem.time_limit %>">
</div>
<div class="field">
<label for="doc-ds-ipt-1">内存限制(单位: MiB)</label>
<input type="number" name="memory_limit" value="<%= problem.memory_limit %>">
</div>
</div>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
</div>
</div>
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
</div>
</div>
<% } %>
</div>
<div class="ui <%= problem.type === 'submit-answer' ? 'active ' : '' %>tab" data-tab="submit-answer" style="margin-bottom: 10px; ">
<b>为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。</b><br>
提交答案题目不需要设置时间限制、空间限制以及 IO 方式。<br>
提交时使用的答案文件名需要与测试数据中每个测试点输出文件相同,且后缀名为 <code>.out</code>。
</div>
<div class="field">
<label for="testdata">上传测试数据(请使用 ZIP 格式)</label>
<input type="file" id="testdata" name="testdata">
</div>
<div class="field">
<label for="additional_file">上传附加文件(请使用 ZIP 格式)</label>
<input type="file" id="additional_file" name="additional_file">
</div>
<button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
</form>
</div>
</div>
<div>
<script>
function goEnable() {
document.getElementById('file-io-input-name').disabled = false;
document.getElementById('file-io-output-name').disabled = false;
}
function goDisable() {
document.getElementById('file-io-input-name').disabled = true;
document.getElementById('file-io-output-name').disabled = true;
}
$(function () {
$('#file-io-input-name').on('input keyup change', function (e) {
var prob = $('#file-io-input-name').val();
if (prob.lastIndexOf('.') !== -1) prob = prob.substring(0, prob.lastIndexOf('.'));
$('#file-io-output-name').attr('placeholder', prob + '.out');
});
$('#file-io-output-name').focus(function (e) {
if (!$('#file-io-output-name').val()) {
$('#file-io-output-name').val($('#file-io-output-name').attr('placeholder'));
}
});
$('#problem-type-tab .item').tab();
$('a[data-tab="traditional"]').click(function () {
$('input[name=type]').val('traditional');
if ($('div[data-tab="interaction"]').attr('data-tab', 'traditional').length) $('a[data-tab="traditional"]').click();
});
$('a[data-tab="interaction"]').click(function () {
$('input[name=type]').val('interaction');
if ($('div[data-tab="traditional"]').attr('data-tab', 'interaction').length) $('a[data-tab="interaction"]').click();
});
$('a[data-tab="submit-answer"]').click(function () {
$('input[name=type]').val('submit-answer');
});
});
function checkSubmit() {
;
}
</script>
<% include footer %>

42
views/problem_testcases.ejs

@ -0,0 +1,42 @@
<%
let subtaskType = {
sum: '测试点分数按百分比相加',
min: '取各测试点最低分',
mul: '测试点分数按百分比相乘'
};
%>
<% if (testcases && testcases.error) { %>
<h4>数据包错误:<%= testcases.error %></h4>
<%
} else if (testcases) {
%>
<% if (testcases.spj) { %>
<p>评测方式:Special Judge</p>
<% } else { %>
<p>评测方式:文本比较</p>
<% } %>
<table class="ui celled table">
<tbody>
<% let i = 0; %>
<% for (let subtask of testcases) { %>
<% if (testcases.length !== 1) { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">子任务 <%= ++i %></h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %>,总分值 <%= subtask.score %></span></th>
</tr>
<% } else { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">单个子任务</h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %></span></th>
</tr>
<% } %>
<% for (let testcase of subtask.cases) { %>
<tr class="center aligned">
<td><%= testcase.input %></td>
<td><%= testcase.output %></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
<% } else { %>
<h4 style="text-align: center; ">无测试数据</h4>
<% } %>
Loading…
Cancel
Save