From 6bb873810ed0c2f97f5a88579e1b0cb52a71ac73 Mon Sep 17 00:00:00 2001 From: Menci Date: Fri, 23 Jun 2017 23:47:22 +0800 Subject: [PATCH 01/12] Add non-traditional problem support; add problem additional_file --- models/file.js | 115 +++++++++++++ models/judge_state.js | 21 ++- models/problem.js | 109 ++++++++----- models/testdata.js | 64 -------- modules/api.js | 52 ++++-- modules/problem.js | 73 +++++++-- modules/submission.js | 17 +- .../.placeholder => additional_file/.gitkeep} | 0 uploads/answer/.gitkeep | 0 uploads/testdata/.gitkeep | 0 utility.js | 2 +- views/problem.ejs | 124 ++++++++------ views/problem_data.ejs | 153 +++++++++++------- views/statistics.ejs | 34 ++-- views/submission_content.ejs | 29 ++-- views/submissions.ejs | 3 +- views/submissions_item.ejs | 5 + 17 files changed, 527 insertions(+), 274 deletions(-) create mode 100644 models/file.js delete mode 100644 models/testdata.js rename uploads/{testdata/.placeholder => additional_file/.gitkeep} (100%) create mode 100644 uploads/answer/.gitkeep create mode 100644 uploads/testdata/.gitkeep diff --git a/models/file.js b/models/file.js new file mode 100644 index 0000000..2cc7b53 --- /dev/null +++ b/models/file.js @@ -0,0 +1,115 @@ +/* + * This file is part of SYZOJ. + * + * Copyright (c) 2016 Menci + * + * SYZOJ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * SYZOJ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with SYZOJ. If not, see . + */ + +'use strict'; + +let Sequelize = require('sequelize'); +let db = syzoj.db; + +let model = db.define('file', { + id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, + type: { type: Sequelize.STRING(80) }, + md5: { type: Sequelize.STRING(80), unique: true } +}, { + timestamps: false, + tableName: 'file', + indexes: [ + { + fields: ['type'], + }, + { + fields: ['md5'], + } + ] +}); + +let Model = require('./common'); +class File extends Model { + static create(val) { + return File.fromRecord(File.model.build(Object.assign({ + type: '', + md5: '' + }, val))); + } + + getPath() { + return File.resolvePath(this.type, this.md5); + } + + static resolvePath(type, md5) { + return syzoj.utils.resolvePath(syzoj.config.upload_dir, type, md5); + } + + static async upload(path, type) { + let fs = Promise.promisifyAll(require('fs-extra')); + + let buf = await fs.readFileAsync(path); + + if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。'); + + try { + let AdmZip = require('adm-zip'); + let zip = new AdmZip(buf); + this.unzipSize = 0; + for (let x of zip.getEntries()) this.unzipSize += x.header.size; + } catch (e) { + this.unzipSize = null; + } + + let key = syzoj.utils.md5(buf); + await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true }); + + let file = await File.findOne({ where: { md5: key } }); + if (!file) { + file = await File.create({ + type: type, + md5: key + }); + await file.save(); + } + + return file; + } + + async getUnzipSize() { + if (this.unzipSize === undefined) { + try { + let fs = Promise.promisifyAll(require('fs-extra')); + let buf = await fs.readFileAsync(this.getPath()); + + let AdmZip = require('adm-zip'); + let zip = new AdmZip(buf); + + this.unzipSize = 0; + for (let x of zip.getEntries()) this.unzipSize += x.header.size; + } catch (e) { + this.unzipSize = null; + } + } + + if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。'); + else return this.unzipSize; + } + + getModel() { return model; } +} + +File.model = model; + +module.exports = File; diff --git a/models/judge_state.js b/models/judge_state.js index a3e33b4..eb992c5 100644 --- a/models/judge_state.js +++ b/models/judge_state.js @@ -28,6 +28,8 @@ let Contest = syzoj.model('contest'); let model = db.define('judge_state', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, + + // The data zip's md5 if it's a submit-answer problem code: { type: Sequelize.TEXT('medium') }, language: { type: Sequelize.STRING(20) }, @@ -163,8 +165,11 @@ class JudgeState extends Model { this.score = result.score; this.pending = result.pending; this.status = result.status; - this.total_time = result.total_time; - this.max_memory = result.max_memory; + if (this.language) { + // language is empty if it's a submit-answer problem + this.total_time = result.total_time; + this.max_memory = result.max_memory; + } this.result = result; } @@ -203,8 +208,11 @@ class JudgeState extends Model { this.status = 'Waiting'; this.score = 0; - this.total_time = 0; - this.max_memory = 0; + if (this.language) { + // language is empty if it's a submit-answer problem + this.total_time = 0; + this.max_memory = 0; + } this.pending = true; this.result = { status: "Waiting", total_time: 0, max_memory: 0, score: 0, case_num: 0, compiler_output: "", pending: true }; await this.save(); @@ -233,6 +241,11 @@ class JudgeState extends Model { }); } + async getProblemType() { + await this.loadRelationships(); + return this.problem.type; + } + getModel() { return model; } } diff --git a/models/problem.js b/models/problem.js index 0678c28..bb8f68b 100644 --- a/models/problem.js +++ b/models/problem.js @@ -144,6 +144,56 @@ FROM `judge_state` `outer_table` \ WHERE \ `problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \ ORDER BY `submit_time` ASC \ +', + min: +' \ +SELECT \ + DISTINCT(`user_id`) AS `user_id`, \ + ( \ + SELECT \ + `id` \ + FROM `judge_state` `inner_table` \ + WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \ + ORDER BY `max_memory` ASC \ + LIMIT 1 \ + ) AS `id`, \ + ( \ + SELECT \ + `max_memory` \ + FROM `judge_state` `inner_table` \ + WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \ + ORDER BY `max_memory` ASC \ + LIMIT 1 \ + ) AS `max_memory` \ +FROM `judge_state` `outer_table` \ +WHERE \ + `problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \ +ORDER BY `max_memory` ASC \ +', + max: +' \ +SELECT \ + DISTINCT(`user_id`) AS `user_id`, \ + ( \ + SELECT \ + `id` \ + FROM `judge_state` `inner_table` \ + WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \ + ORDER BY `max_memory` ASC \ + LIMIT 1 \ + ) AS `id`, \ + ( \ + SELECT \ + `max_memory` \ + FROM `judge_state` `inner_table` \ + WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \ + ORDER BY `max_memory` ASC \ + LIMIT 1 \ + ) AS `max_memory` \ +FROM `judge_state` `outer_table` \ +WHERE \ + `problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \ +ORDER BY `max_memory` DESC \ ' }; @@ -151,7 +201,7 @@ let Sequelize = require('sequelize'); let db = syzoj.db; let User = syzoj.model('user'); -let TestData = syzoj.model('testdata'); +let File = syzoj.model('file'); let model = db.define('problem', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, @@ -182,13 +232,8 @@ let model = db.define('problem', { time_limit: { type: Sequelize.INTEGER }, memory_limit: { type: Sequelize.INTEGER }, - testdata_id: { - type: Sequelize.INTEGER, - references: { - model: 'file', - key: 'id' - } - }, + testdata_id: { type: Sequelize.INTEGER }, + additional_file_id: { type: Sequelize.INTEGER }, ac_num: { type: Sequelize.INTEGER }, submit_num: { type: Sequelize.INTEGER }, @@ -196,7 +241,12 @@ let model = db.define('problem', { file_io: { type: Sequelize.BOOLEAN }, file_io_input_name: { type: Sequelize.TEXT }, - file_io_output_name: { type: Sequelize.TEXT } + file_io_output_name: { type: Sequelize.TEXT }, + + type: { + type: Sequelize.ENUM, + values: ['traditional', 'submit-answer', 'interaction'] + } }, { timestamps: false, tableName: 'problem', @@ -234,14 +284,17 @@ class Problem extends Model { file_io: false, file_io_input_name: '', - file_io_output_name: '' + file_io_output_name: '', + + type: '' }, val))); } async loadRelationships() { this.user = await User.fromID(this.user_id); this.publicizer = await User.fromID(this.publicizer_id); - this.testdata = await TestData.fromID(this.testdata_id); + this.testdata = await File.fromID(this.testdata_id); + this.additional_file = await File.fromID(this.additional_file_id); } async isAllowedEditBy(user) { @@ -263,36 +316,15 @@ class Problem extends Model { return user.is_admin; } - async updateTestdata(path) { - let fs = Promise.promisifyAll(require('fs-extra')); - - let buf = await fs.readFileAsync(path); + async updateFile(path, type) { + let file = await File.upload(path, type); - if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('测试数据太大。'); - - let key = syzoj.utils.md5(buf); - await fs.moveAsync(path, TestData.resolvePath(key), { overwrite: true }); - - if (this.testdata_id) { - let tmp = this.testdata_id; - this.testdata_id = null; - await this.save(); - - let file = await TestData.fromID(tmp); - if (file) await file.destroy(); + if (type === 'testdata') { + this.testdata_id = file.id; + } else if (type === 'additional_file') { + this.additional_file_id = file.id; } - let filename = `test_data_${this.id}.zip`; - let file = await TestData.findOne({ where: { filename: filename } }); - if (file) await file.destroy(); - - file = await TestData.create({ - filename: filename, - md5: key - }); - await file.save(); - this.testdata_id = file.id; - await this.save(); } @@ -453,7 +485,6 @@ class Problem extends Model { await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); - await db.query('UPDATE `file` SET `filename` = ' + `"test_data_${id}.zip"` + ' WHERE `filename` = ' + `"test_data_${this.id}.zip"`); let Contest = syzoj.model('contest'); let contests = await Contest.all(); diff --git a/models/testdata.js b/models/testdata.js deleted file mode 100644 index 5da17f9..0000000 --- a/models/testdata.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of SYZOJ. - * - * Copyright (c) 2016 Menci - * - * SYZOJ is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * SYZOJ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with SYZOJ. If not, see . - */ - -'use strict'; - -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('file', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - filename: { type: Sequelize.STRING(80), unique: true }, - md5: { type: Sequelize.STRING(80), unique: true } -}, { - timestamps: false, - tableName: 'file', - indexes: [ - { - fields: ['filename'], - }, - { - fields: ['md5'], - } - ] -}); - -let Model = require('./common'); -class TestData extends Model { - static create(val) { - return TestData.fromRecord(TestData.model.build(Object.assign({ - filename: '', - md5: '' - }, val))); - } - - getPath() { - return TestData.resolvePath(this.md5); - } - - static resolvePath(md5) { - return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', md5); - } - - getModel() { return model; } -} - -TestData.model = model; - -module.exports = TestData; diff --git a/modules/api.js b/modules/api.js index 186f074..757102e 100644 --- a/modules/api.js +++ b/modules/api.js @@ -23,7 +23,7 @@ let User = syzoj.model('user'); let Problem = syzoj.model('problem'); let WaitingJudge = syzoj.model('waiting_judge'); let JudgeState = syzoj.model('judge_state'); -let TestData = syzoj.model('testdata'); +let File = syzoj.model('file'); function setLoginCookie(username, password, res) { res.cookie('login', JSON.stringify([username, password])); @@ -109,18 +109,32 @@ app.get('/api/waiting_judge', async (req, res) => { }); if (judge_state) { - res.send({ - have_task: 1, - judge_id: judge_state.id, - code: judge_state.code, - language: judge_state.language, - testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '', - time_limit: judge_state.problem.time_limit, - memory_limit: judge_state.problem.memory_limit, - file_io: judge_state.problem.file_io, - file_io_input_name: judge_state.problem.file_io_input_name, - file_io_output_name: judge_state.problem.file_io_output_name - }); + await judge_state.loadRelationships(); + await judge_state.problem.loadRelationships(); + + if (judge_state.problem.type === 'submit-answer') { + res.send({ + have_task: 1, + judge_id: judge_state.id, + answer_file: judge_state.code, + testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '', + problem_type: judge_state.problem.type + }); + } else { + res.send({ + have_task: 1, + judge_id: judge_state.id, + code: judge_state.code, + language: judge_state.language, + testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '', + time_limit: judge_state.problem.time_limit, + memory_limit: judge_state.problem.memory_limit, + file_io: judge_state.problem.file_io, + file_io_input_name: judge_state.problem.file_io_input_name, + file_io_output_name: judge_state.problem.file_io_output_name, + problem_type: judge_state.problem.type + }); + } } else { res.send({ have_task: 0 }); } @@ -145,9 +159,17 @@ app.post('/api/update_judge/:id', async (req, res) => { } }); -app.get('/static/uploads/:md5', 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(TestData.resolvePath(req.params.md5)); + res.sendFile(File.resolvePath('answer', req.params.md5)); } catch (e) { res.status(500).send(e); } diff --git a/modules/problem.js b/modules/problem.js index 7db7549..859b364 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -464,7 +464,7 @@ app.get('/problem/:id/data', async (req, res) => { } }); -app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) => { +app.post('/problem/:id/data', 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); @@ -480,11 +480,22 @@ app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) => problem.file_io_input_name = req.body.file_io_input_name; problem.file_io_output_name = req.body.file_io_output_name; + if (problem.type === 'submit-answer' && req.body.type !== 'submit-answer' || problem.type !== 'submit-answer' && req.body.type === 'submit-answer') { + if (await JudgeState.count({ problem_id: id }) !== 0) { + throw new ErrorMessage('已有提交的题目不允许在提交答案和非提交答案之间更改。'); + } + } + problem.type = req.body.type; + let validateMsg = await problem.validate(); if (validateMsg) throw new ErrorMessage('无效的题目数据配置。', null, validateMsg); - if (req.file) { - await problem.updateTestdata(req.file.path); + if (req.files['testdata']) { + await problem.updateFile(req.files['testdata'][0].path, 'testdata'); + } + + if (req.files['additional_file']) { + await problem.updateFile(req.files['additional_file'][0].path, 'additional_file'); } await problem.save(); @@ -529,21 +540,37 @@ app.get('/problem/:id/dis_public', async (req, res) => { await setPublic(req, res, false); }); -app.post('/problem/:id/submit', async (req, res) => { +app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => { try { let id = parseInt(req.params.id); let problem = await Problem.fromID(id); if (!problem) throw new ErrorMessage('无此题目。'); - if (!syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。'); + if (problem.type !== 'submit-answer' && !syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。'); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) }); - let judge_state = await JudgeState.create({ - code: req.body.code, - language: req.body.language, - user_id: res.locals.user.id, - problem_id: req.params.id - }); + let judge_state; + if (problem.type === 'submit-answer') { + let File = syzoj.model('file'); + let file = await File.upload(req.files['answer'][0].path, 'answer'); + let size = await file.getUnzipSize(); + + if (!file.md5) throw new ErrorMessage('上传答案文件失败。'); + judge_state = await JudgeState.create({ + code: file.md5, + max_memory: size, + language: '', + user_id: res.locals.user.id, + problem_id: req.params.id + }); + } else { + judge_state = await JudgeState.create({ + code: req.body.code, + language: req.body.language, + user_id: res.locals.user.id, + problem_id: req.params.id + }); + } let contest_id = parseInt(req.query.contest_id), redirectToContest = false; if (contest_id) { @@ -579,7 +606,7 @@ app.post('/problem/:id/submit', async (req, res) => { } }); -app.get('/problem/:id/download', async (req, res) => { +app.get('/problem/:id/download/testdata', async (req, res) => { try { let id = parseInt(req.params.id); let problem = await Problem.fromID(id); @@ -601,6 +628,28 @@ app.get('/problem/:id/download', async (req, res) => { } }); +app.get('/problem/:id/download/additional_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.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); + + await problem.loadRelationships(); + + if (!problem.additional_file) throw new ErrorMessage('无附加文件。'); + + res.download(problem.additional_file.getPath(), `additional_file_${id}.zip`); + } catch (e) { + syzoj.log(e); + res.status(404); + res.render('error', { + err: e + }); + } +}); + app.get('/problem/:id/statistics/:type', async (req, res) => { try { let id = parseInt(req.params.id); diff --git a/modules/submission.js b/modules/submission.js index 56b86bc..74c7f47 100644 --- a/modules/submission.js +++ b/modules/submission.js @@ -42,7 +42,10 @@ app.get('/submissions', async (req, res) => { } }; - if (req.query.language) where.language = req.query.language; + if (req.query.language) { + if (req.query.language === 'submit-answer') where.language = ''; + else where.language = req.query.language; + } if (req.query.status) where.status = { $like: req.query.status + '%' }; where.type = { $ne: 1 }; @@ -136,8 +139,10 @@ app.get('/submission/:id', async (req, res) => { await judge.loadRelationships(); - judge.codeLength = judge.code.length; - judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); + if (judge.problem.type !== 'submit-answer') { + judge.codeLength = judge.code.length; + judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); + } judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user); judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); @@ -181,8 +186,10 @@ app.get('/submission/:id/ajax', async (req, res) => { await judge.loadRelationships(); - judge.codeLength = judge.code.length; - judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); + if (judge.problem.type !== 'submit-answer') { + judge.codeLength = judge.code.length; + judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); + } judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user); judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); diff --git a/uploads/testdata/.placeholder b/uploads/additional_file/.gitkeep similarity index 100% rename from uploads/testdata/.placeholder rename to uploads/additional_file/.gitkeep diff --git a/uploads/answer/.gitkeep b/uploads/answer/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/uploads/testdata/.gitkeep b/uploads/testdata/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/utility.js b/utility.js index 94e6839..aadf487 100644 --- a/utility.js +++ b/utility.js @@ -195,7 +195,7 @@ module.exports = { gravatar(email, size) { return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn'); }, - parseTestData(filename) { + parseTestdata(filename) { let zip = new AdmZip(filename); let list = zip.getEntries().filter(e => !e.isDirectory).map(e => e.entryName); let res = []; diff --git a/views/problem.ejs b/views/problem.ejs index a49ef57..7037787 100644 --- a/views/problem.ejs +++ b/views/problem.ejs @@ -88,8 +88,9 @@ if (contest) { 提交 提交记录 统计 - 下载测试数据 + <% if (problem.testdata) { %>下载测试数据<% } %> <% } %> + <% if (problem.additional_file) { %>下载附加文件<% } %> <% if (!contest) { %>
@@ -187,71 +188,88 @@ if (contest) { if (contest) formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit'], { contest_id: contest.id }); else formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit']); %> -
- - -
-
-
<% } %>
- - + + +<% } else { %> + +<% } %> <% include footer %> diff --git a/views/problem_data.ejs b/views/problem_data.ejs index 81c1a31..cc1c904 100644 --- a/views/problem_data.ejs +++ b/views/problem_data.ejs @@ -14,7 +14,7 @@ let subtaskType = { <% if (problem.testdata) { %> <% try { - let list = syzoj.utils.parseTestData(problem.testdata.getPath()); + let list = syzoj.utils.parseTestdata(problem.testdata.getPath()); %> <% if (list.spj) { %>

评测方式:Special Judge

@@ -51,76 +51,93 @@ let subtaskType = { <% } %>
-
-
-
- - -
-
- - -
+ + + +
+
+
+ +
- <% if (!problem.file_io) { %> -
- -
-
- - +
+ +
-
-
- - + <% if (!problem.file_io) { %> +
+ +
+
+ + +
+
+
+
+ + +
-
-
-
- - -
-
- - +
+
+ + +
+
+ + +
-
- <% } else { %> -
- -
-
- - + <% } else { %> +
+ +
+
+ + +
+
+
+
+ + +
-
-
- - +
+
+ + +
+
+ +
+ <% } %>
-
-
- - -
-
- - -
+
+ 为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。
+ 提交答案题目不需要设置时间限制、空间限制以及 IO 方式。
+ 提交时使用的答案文件名需要与测试数据中每个测试点输出文件相同,且后缀名为 .out
- <% } %>
- - + +
- - 返回题目 +
+ + +
+ + 返回题目
@@ -135,7 +152,7 @@ function goDisable() { document.getElementById('file-io-output-name').disabled = true; } -$(document).ready(function () { +$(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('.')); @@ -146,6 +163,26 @@ $(document).ready(function () { $('#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() { + ; +} <% include footer %> diff --git a/views/statistics.ejs b/views/statistics.ejs index b7f2550..2f8a135 100644 --- a/views/statistics.ejs +++ b/views/statistics.ejs @@ -1,11 +1,13 @@ <% this.title = '统计'; let types = { - fastest: '最快', - slowest: '最慢', - shortest: '最短', - longest: '最长', - earliest: '最早' + fastest: problem.type === 'submit-answer' ? null : '最快', + slowest: problem.type === 'submit-answer' ? null : '最慢', + shortest: problem.type === 'submit-answer' ? null : '最短', + longest: problem.type === 'submit-answer' ? null : '最长', + earliest: '最早', + min: problem.type === 'submit-answer' ? '最小' : '最小内存', + max: problem.type === 'submit-answer' ? '最大' : '最大内存' }; %> <% include header %> @@ -38,7 +40,7 @@ function getColorOfScore(score) {