Browse Source

feat: 基本完成oj平台练习功能。

feature/unlimited-reply
zjz1993 5 years ago
parent
commit
7348babbbd
  1. 8
      models-built/classify.js
  2. 2
      models-built/classify.js.map
  3. 53
      models-built/classify_to_problem.js
  4. 1
      models-built/classify_to_problem.js.map
  5. 31
      models-built/common.js
  6. 2
      models-built/common.js.map
  7. 5
      models-built/user.js
  8. 2
      models-built/user.js.map
  9. 73
      models-built/user_to_practice.js
  10. 1
      models-built/user_to_practice.js.map
  11. 6
      models/classify.ts
  12. 18
      models/classify_to_problem.ts
  13. 18
      models/common.ts
  14. 4
      models/user.ts
  15. 34
      models/user_to_practice.ts
  16. 340
      modules/practice.js
  17. 65
      modules/problem.js
  18. 66
      modules/submission.js
  19. 262
      views/admin_classify.ejs
  20. 1
      views/admin_config.ejs
  21. 5
      views/header.ejs
  22. 238
      views/practice.ejs
  23. 26
      views/practice_classify.ejs
  24. 392
      views/practice_problem.ejs
  25. 27
      views/submission.ejs

8
models-built/classify.js

@ -43,6 +43,14 @@ var Classify = /** @class */ (function (_super) {
TypeORM.Column({ nullable: true, type: "varchar", length: 100 }),
__metadata("design:type", String)
], Classify.prototype, "intro");
__decorate([
TypeORM.Column({ type: "integer" }),
__metadata("design:type", Number)
], Classify.prototype, "problem_num");
__decorate([
TypeORM.Column({ type: "integer" }),
__metadata("design:type", Number)
], Classify.prototype, "order");
__decorate([
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)

2
models-built/classify.js.map

@ -1 +1 @@
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../models/classify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmC;AACnC,mCAA6B;AAM7B;IAAsC,4BAAK;IAA3C;;IAkBA,CAAC;IAjBU,cAAK,GAAG,IAAI,CAAC;IAGpB;QADC,OAAO,CAAC,sBAAsB,EAAE;;gCACtB;IAIX;QAFC,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;kCACjE;IAGb;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;mCACnD;IAGd;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCAChC;IAGpB;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCAChC;IAjBH,QAAQ;QAD5B,OAAO,CAAC,MAAM,EAAE;OACI,QAAQ,CAkB5B;IAAD,eAAC;CAAA,AAlBD,CAAsC,mBAAK,GAkB1C;qBAlBoB,QAAQ"}
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../models/classify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmC;AACnC,mCAA6B;AAM7B;IAAsC,4BAAK;IAA3C;;IAwBA,CAAC;IAvBU,cAAK,GAAG,IAAI,CAAC;IAGpB;QADC,OAAO,CAAC,sBAAsB,EAAE;;gCACtB;IAIX;QAFC,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;kCACjE;IAGb;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;mCACnD;IAGd;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCAChB;IAGpB;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;mCACtB;IAGd;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCAChC;IAGpB;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCAChC;IAvBH,QAAQ;QAD5B,OAAO,CAAC,MAAM,EAAE;OACI,QAAQ,CAwB5B;IAAD,eAAC;CAAA,AAxBD,CAAsC,mBAAK,GAwB1C;qBAxBoB,QAAQ"}

53
models-built/classify_to_problem.js

@ -0,0 +1,53 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
exports.__esModule = true;
var TypeORM = require("typeorm");
var common_1 = require("./common");
var ClassifyToProblem = /** @class */ (function (_super) {
__extends(ClassifyToProblem, _super);
function ClassifyToProblem() {
return _super !== null && _super.apply(this, arguments) || this;
}
ClassifyToProblem.cache = true;
__decorate([
TypeORM.PrimaryGeneratedColumn(),
__metadata("design:type", Number)
], ClassifyToProblem.prototype, "id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], ClassifyToProblem.prototype, "c_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], ClassifyToProblem.prototype, "p_id");
ClassifyToProblem = __decorate([
TypeORM.Entity()
], ClassifyToProblem);
return ClassifyToProblem;
}(common_1["default"]));
exports["default"] = ClassifyToProblem;
//# sourceMappingURL=classify_to_problem.js.map

1
models-built/classify_to_problem.js.map

@ -0,0 +1 @@
{"version":3,"file":"classify_to_problem.js","sourceRoot":"","sources":["../models/classify_to_problem.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmC;AACnC,mCAA6B;AAG7B;IAA+C,qCAAK;IAApD;;IAaA,CAAC;IAZU,uBAAK,GAAG,IAAI,CAAC;IAGpB;QADC,OAAO,CAAC,sBAAsB,EAAE;;yCACtB;IAIX;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;2CACvC;IAIb;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;2CACvC;IAZI,iBAAiB;QADrC,OAAO,CAAC,MAAM,EAAE;OACI,iBAAiB,CAarC;IAAD,wBAAC;CAAA,AAbD,CAA+C,mBAAK,GAanD;qBAboB,iBAAiB"}

31
models-built/common.js

@ -218,6 +218,26 @@ var Model = /** @class */ (function (_super) {
});
});
};
Model.querySomeColumn = function (queryBuilder) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, queryBuilder.select('id').addSelect('title').getRawMany()];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
};
Model.updateColumnById = function (queryBuilder, updateObj, id) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, queryBuilder.set(updateObj).where("id = :id", { id: id }).execute()];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
};
Model.queryPage = function (paginater, where, order, largeData) {
if (largeData === void 0) { largeData = false; }
return __awaiter(this, void 0, void 0, function () {
@ -345,6 +365,17 @@ var Model = /** @class */ (function (_super) {
});
});
};
Model.queryBy = function (where) {
return __awaiter(this, void 0, void 0, function () {
var queryBuilder;
return __generator(this, function (_a) {
queryBuilder = where instanceof TypeORM.SelectQueryBuilder
? where
: this.createQueryBuilder().where(where);
return [2 /*return*/, queryBuilder.getMany()];
});
});
};
Model.cache = false;
return Model;
}(TypeORM.BaseEntity));

2
models-built/common.js.map

File diff suppressed because one or more lines are too long

5
models-built/user.js

@ -385,6 +385,11 @@ var User = /** @class */ (function (_super) {
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], User.prototype, "ac_num");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], User.prototype, "current_p_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),

2
models-built/user.js.map

File diff suppressed because one or more lines are too long

73
models-built/user_to_practice.js

@ -0,0 +1,73 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
exports.__esModule = true;
var TypeORM = require("typeorm");
var common_1 = require("./common");
var UserToPractice = /** @class */ (function (_super) {
__extends(UserToPractice, _super);
function UserToPractice() {
return _super !== null && _super.apply(this, arguments) || this;
}
UserToPractice.cache = true;
__decorate([
TypeORM.PrimaryGeneratedColumn(),
__metadata("design:type", Number)
], UserToPractice.prototype, "id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "u_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "c_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "p_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "is_practice");
__decorate([
TypeORM.Index(),
TypeORM.Column({ type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "is_finished");
__decorate([
TypeORM.Index(),
TypeORM.Column({ type: "integer" }),
__metadata("design:type", Number)
], UserToPractice.prototype, "practice_num");
UserToPractice = __decorate([
TypeORM.Entity()
], UserToPractice);
return UserToPractice;
}(common_1["default"]));
exports["default"] = UserToPractice;
//# sourceMappingURL=user_to_practice.js.map

1
models-built/user_to_practice.js.map

@ -0,0 +1 @@
{"version":3,"file":"user_to_practice.js","sourceRoot":"","sources":["../models/user_to_practice.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmC;AACnC,mCAA6B;AAG7B;IAA4C,kCAAK;IAAjD;;IA6BA,CAAC;IA5BU,oBAAK,GAAG,IAAI,CAAC;IAGpB;QADC,OAAO,CAAC,sBAAsB,EAAE;;sCACtB;IAIX;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;wCACvC;IAIb;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;wCACvC;IAIb;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;wCACvC;IAIb;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;+CAChB;IAIpB;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;+CAChB;IAIpB;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;gDACf;IA5BJ,cAAc;QADlC,OAAO,CAAC,MAAM,EAAE;OACI,cAAc,CA6BlC;IAAD,qBAAC;CAAA,AA7BD,CAA4C,mBAAK,GA6BhD;qBA7BoB,cAAc"}

6
models/classify.ts

@ -18,6 +18,12 @@ export default class Classify extends Model {
@TypeORM.Column({ nullable: true, type: "varchar", length: 100 })
intro: string;
@TypeORM.Column({ type: "integer" })
problem_num: number;
@TypeORM.Column({ type: "integer" })
order: number;
@TypeORM.Column({ nullable: true, type: "integer" })
update_time: number;

18
models/classify_to_problem.ts

@ -0,0 +1,18 @@
import * as TypeORM from "typeorm";
import Model from "./common";
@TypeORM.Entity()
export default class ClassifyToProblem extends Model {
static cache = true;
@TypeORM.PrimaryGeneratedColumn()
id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
c_id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
p_id: number;
}

18
models/common.ts

@ -119,6 +119,15 @@ export default class Model extends TypeORM.BaseEntity {
return await queryBuilder.getMany();
}
static async querySomeColumn(queryBuilder) {
return await queryBuilder.select('id').addSelect('title').getRawMany();
}
static async updateColumnById(queryBuilder, updateObj: object, id:number) {
return await queryBuilder.set(updateObj).where("id = :id", { id }).execute();
}
static async queryPage(paginater: Paginater, where, order, largeData = false) {
if (!paginater.pageCnt) return [];
@ -208,4 +217,13 @@ export default class Model extends TypeORM.BaseEntity {
return queryBuilder.getMany();
}
static async queryBy(where) {
const queryBuilder = where instanceof TypeORM.SelectQueryBuilder
? where
: this.createQueryBuilder().where(where);
return queryBuilder.getMany();
}
}

4
models/user.ts

@ -37,6 +37,10 @@ export default class User extends Model {
@TypeORM.Column({ nullable: true, type: "integer" })
ac_num: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
current_p_id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
submit_num: number;

34
models/user_to_practice.ts

@ -0,0 +1,34 @@
import * as TypeORM from "typeorm";
import Model from "./common";
@TypeORM.Entity()
export default class UserToPractice extends Model {
static cache = true;
@TypeORM.PrimaryGeneratedColumn()
id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
u_id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
c_id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
p_id: number;
@TypeORM.Index()
@TypeORM.Column({ type: "integer" })
is_practice: number;
@TypeORM.Index()
@TypeORM.Column({ type: "integer" })
is_finished: number;
@TypeORM.Index()
@TypeORM.Column({ type: "integer" })
practice_num: number;
}

340
modules/practice.js

@ -1,9 +1,151 @@
let Classify = syzoj.model('classify');
let Problem = syzoj.model('problem');
let CToP = syzoj.model('classify_to_problem');
let Article = syzoj.model('article');
let User = syzoj.model('user');
let UToP = syzoj.model('user_to_practice');
app.get('/practice', async (req, res) => {
if (!res.locals.user) {
res.render('error', {
err: new ErrorMessage('请先登录。')
});
} else {
async function getUserInfo(userId) {
let userQuery = await User.createQueryBuilder();
const currentUserInfo = await userQuery.where("id = :id", { id: userId }).getOne();
return currentUserInfo.current_p_id;
}
try {
let classifyQuery = Classify.createQueryBuilder();
let utopQuery = UToP.createQueryBuilder();
let result = await Classify.queryAll(classifyQuery);
let userQuery = await User.createQueryBuilder();
const userId = res.locals.user.id;
let currentPId = await getUserInfo(userId);
const test = [];
if (!currentPId) {
// 用户第一次进入练习板块
await userQuery.update(User).set({current_p_id: 1}).where("id = :id", { id:userId }).execute();
} else {
await result.forEachAsync(async resultItem => {
const c_id = resultItem.id;
let utopInfo = await utopQuery.where('c_id=:c_id',{c_id}).andWhere('u_id=:u_id',{u_id:userId}).andWhere("is_finished=1").getMany();
resultItem.ac_num = utopInfo.length || 0;
if (resultItem.ac_num === resultItem.problem_num) {
if (resultItem.order < result.length ) {
currentPId = resultItem.order + 1
await userQuery.update(User).set({current_p_id: currentPId}).where("id = :id", { id:userId }).execute();
}
} else {
test.push(resultItem.order);
}
});
}
if (test.length !== 0) {
const minOrder = Math.min.apply(null,test);
currentPId = minOrder;
await userQuery.update(User).set({current_p_id: minOrder}).where("id = :id", { id:userId }).execute();
}
result.sort(function(a,b) {
return a.order - b.order;
});
result.forEach(function(item) {
if (item.order <= currentPId) {
item.href = `/practice/classify/${item.id}`;
}
item.isNow = item.order === currentPId;
});
let userACArray = await utopQuery.where('u_id=:u_id',{u_id:userId}).andWhere("is_finished=1").getMany();
const originRating = syzoj.config.default.user.rating;
const newRating = originRating + userACArray.length * syzoj.config.practice_rating;
await userQuery.update(User).set({rating: newRating}).where('id=:u_id',{u_id:userId}).execute();
res.render('practice', {
user:res.locals.user,
result,
newRating
})
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
}
});
app.get('/practice/classify/:cid/problem/:pid', async (req, res) => {
try {
res.render('practice', {
let id = parseInt(req.params.pid);
let cid = parseInt(req.params.cid);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) {
throw new ErrorMessage('您没有权限进行此操作。');
}
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.allowedManage = await problem.isAllowedManageBy(res.locals.user);
if (problem.is_public || problem.allowedEdit) {
await syzoj.utils.markdown(problem, ['description', 'input_format', 'output_format', 'example', 'limit_and_hint']);
} else {
throw new ErrorMessage('您没有权限进行此操作。');
}
let state = await problem.getJudgeState(res.locals.user, false);
problem.tags = await problem.getTags();
await problem.loadRelationships();
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
let discussionCount = await Article.count({ problem_id: id });
res.render('practice_problem', {
problem: problem,
state: state,
lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null,
testcases: testcases,
discussionCount: discussionCount,
pid: id,
cid
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/practice/classify/:id', async (req, res) => {
try {
const c_id = parseInt(req.params.id);
const u_id = res.locals.user.id;
let ctopQuery = CToP.createQueryBuilder();
let query = await Problem.createQueryBuilder();
let utopQuery = UToP.createQueryBuilder();
let problemArray = await ctopQuery.where("c_id = :id", { id:c_id }).getMany();
let utopResult = await utopQuery.where("c_id = :c_id", { c_id }).andWhere("u_id=:u_id", {u_id}).getMany();
let problems = await problemArray.mapAsync(async problemArrayInfo => query.where('id=:id', {id: problemArrayInfo.p_id}).getOne());
problems.forEach(function(problemItem) {
const obj = utopResult.find(function(item){return parseInt(item.p_id) === parseInt(problemItem.id)});
problemItem.href = `/practice/classify/${c_id}/problem/${problemItem.id}`;
if (obj) {
problemItem.statusStr = obj.is_practice && obj.is_finished ? '已练习且已通过' : '已练习但未通过';
} else {
problemItem.statusStr = '尚未练习'
}
});
problems.sort(function(a, b){
return a.id - b.id;
})
res.render('practice_classify', {
user:res.locals.user,
problems
})
} catch (e) {
syzoj.log(e);
@ -13,19 +155,211 @@ app.get('/practice', async (req, res) => {
}
});
app.get('/api/practice/classify/:id', async (req, res) => {
try {
let id = parseInt(req.params.id);
let query = await Classify.createQueryBuilder();
let classifyInfo = await query.where('id=:id',{id}).getOne();
let problem = await CToP.queryBy({c_id: id});
res.send({ classifyInfo, problem:problem.map(function(item){return item.p_id}) });
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});
app.get('/api/pass/:cid/:pid',async (req, res) => {
try {
const c_id = parseInt(req.params.cid);
const p_id = parseInt(req.params.pid);
const u_id = res.locals.user.id;
let utopQuery = UToP.createQueryBuilder();
let query = Classify.createQueryBuilder();
let userQuery = User.createQueryBuilder();
let classify = await Classify.queryAll(query);
classify.sort(function(a,b) {
return a.order - b.order;
});
let utopResult = await utopQuery.where("c_id = :c_id", { c_id }).andWhere("u_id=:u_id", {u_id}).andWhere("p_id=:p_id", {p_id}).getOne();
// 查询到结果了
if (utopResult ) {
// mock答题通过了
await utopQuery.update(UToP).set({is_finished: 1, practice_num: utopResult.practice_num + 1}).where("c_id = :c_id", { c_id }).andWhere("u_id=:u_id", {u_id}).andWhere("p_id=:p_id", {p_id}).execute();
// 说明两个题都练习过了
} else {
let utop = await UToP.create({
u_id,
c_id,
p_id,
is_practice: 1,
is_finished: 1,
practice_num: 1
});
await utop.save();
}
res.send();
} catch(e) {
res.send({ error_code: e.errno, error_msg: '练习失败,请稍后重试' });
}
});
app.get('/api/practice/nopass/:cid/:pid',async (req, res) => {
try {
const c_id = parseInt(req.params.cid);
const p_id = parseInt(req.params.pid);
const u_id = res.locals.user.id;
let utopQuery = UToP.createQueryBuilder();
let query = Classify.createQueryBuilder();
let utopResult = await utopQuery.where("c_id = :c_id", { c_id }).andWhere("u_id=:u_id", {u_id}).andWhere("p_id=:p_id", {p_id}).getOne();
// 查询到结果了
if (utopResult) {
// mock答题通过了
await utopQuery.update(UToP).set({is_practice:1, is_finished: 0, practice_num: utopResult.practice_num + 1}).where("c_id = :c_id", { c_id }).andWhere("u_id=:u_id", {u_id}).andWhere("p_id=:p_id", {p_id}).execute();
// 说明两个题都练习过了
} else {
let utop = await UToP.create({
u_id,
c_id,
p_id,
is_practice: 1,
practice_num: 1,
is_finished: 0
});
await utop.save();
}
res.send();
} catch(e) {
res.send({ error_code: e.errno, error_msg: '练习失败,请稍后重试' });
}
});
app.put('/api/practice/classify/update/:id', async (req, res) => {
try {
let id = parseInt(req.params.id);
let u_id = parseInt(res.locals.user.id);
let utopQuery = UToP.createQueryBuilder();
let classifyQuery = await Classify.createQueryBuilder();
let ctopQuery = await CToP.createQueryBuilder();
const {name, intro, problemIdArray, order} = req.body;
let classifyInfo = await classifyQuery.where('id=:id',{id}).getOne();
// 新增题
if (problemIdArray.length > classifyInfo.problem_num) {
/*
* 把当前练习的阶段设置为新增题的阶段因为新增题后当前阶段的题目就没答完
* */
}
// 删除题
if (problemIdArray.length < classifyInfo.problem_num) {
// 1。找出两个数组的差异
function findDifference(source, target){
const parseIntTarget = target.map(function(item){
return parseInt(item);
})
return source.filter(function(item){
return !parseIntTarget.includes(item);
})
}
let ctopInfo = await ctopQuery.where('c_id=:id',{id}).getMany();
const targetArray = ctopInfo.map(function(ctopInfoItem){
return ctopInfoItem.p_id;
})
// 2.从utop表里清除被删除题目的练习记录
const differenceArray = findDifference(targetArray, problemIdArray);
differenceArray.forEachAsync(async differenceItem => {
await utopQuery.delete().from(UToP).where("c_id = :id", { id }).andWhere("u_id=:u_id",{u_id}).andWhere("p_id=:p_id",{p_id:differenceItem}).execute();
})
}
let updateClassifyInfo = await classifyQuery.update(Classify).set({name, intro, order, problem_num: problemIdArray.length}).where("id = :id", { id }).execute();
await ctopQuery.delete().from(CToP).where("c_id = :id", { id }).execute();
problemIdArray.forEach(async function (item) {
let ctop = await CToP.create({
c_id: id,
p_id: item
});
await ctop.save();
})
res.send({ updateClassifyInfo });
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});
app.get('/api/getProblem/:id', async (req, res) => {
let id = parseInt(req.params.id);
try {
let query = await Problem.createQueryBuilder();
let problemInfo = await query.where('id=:id', {id}).getOne()
res.send({ problemInfo});
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});
app.get('/api/practice/all', async (req, res) => {
try {
let classifyQuery = Classify.createQueryBuilder();
let ctopQuery = CToP.createQueryBuilder();
let result = await Classify.queryAll(classifyQuery);
result.sort(function(a,b) {
return a.order - b.order;
})
// if (result.length !== 0) {
// const currentClassifyId = result[0].id;
//
// }
let problem = await CToP.queryAll(ctopQuery);
res.send({ result, problem});
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});
app.get('/api/admin/practice/all', async (req, res) => {
try {
let classifyQuery = Classify.createQueryBuilder();
let result = await Classify.queryAll(classifyQuery);
res.send({ result });
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});
app.post('/api/practice/create', async (req, res) => {
try {
const {name, intro} = req.body;
const {name, intro, problemIdArray, order} = req.body;
let classify = await Classify.create({
name,
intro,
updateTime: parseInt((new Date()).getTime() / 1000),
order,
problem_num: problemIdArray.length || 0,
updateTime: parseInt((new Date()).getTime() / 1000, 10),
createTime: parseInt((new Date()).getTime() / 1000)
});
await classify.save();
let query = Classify.createQueryBuilder();
let recordArray = await Classify.queryAll(query);
let lastRecordId = recordArray[recordArray.length - 1].id;
problemIdArray.forEach(async function (item) {
let ctop = await CToP.create({
c_id: lastRecordId,
p_id: item
})
await ctop.save();
})
res.send();
} catch(e) {
res.send({ error_code: e.errno, error_msg: '创建失败' });
}
});
app.get('/api/problem/all', async (req, res) => {
try {
let query = Problem.createQueryBuilder();
let result = await Problem.querySomeColumn(query);
res.send({ result });
} catch(e) {
res.send({ error_code: e.errno, error_msg: '失败' });
}
});

65
modules/problem.js

@ -220,7 +220,6 @@ app.get('/problem/:id', async (req, res) => {
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
let discussionCount = await Article.count({ problem_id: id });
res.render('problem', {
problem: problem,
state: state,
@ -599,6 +598,70 @@ app.post('/problem/:id/dis_public', async (req, res) => {
await setPublic(req, res, false);
});
app.post('/problem/practice/:pid/:cid/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try {
let pid = parseInt(req.params.pid);
let cid = parseInt(req.params.cid);
let problem = await Problem.findById(pid);
const curUser = res.locals.user;
if (!problem) throw new ErrorMessage('无此题目。');
if (problem.type !== 'submit-answer' && !syzoj.config.enabled_languages.includes(req.body.language)) throw new ErrorMessage('不支持该语言。');
if (!curUser) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
let judge_state;
let code;
if (req.files['answer']) {
if (req.files['answer'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码文件太大。');
code = (await fs.readFile(req.files['answer'][0].path)).toString();
} else {
if (Buffer.from(req.body.code).length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码太长。');
code = req.body.code;
}
judge_state = await JudgeState.create({
submit_time: parseInt((new Date()).getTime() / 1000),
status: 'Unknown',
task_id: randomstring.generate(10),
code: code,
code_length: Buffer.from(code).length,
language: req.body.language,
user_id: curUser.id,
problem_id: pid,
is_public: problem.is_public
});
if (!await problem.isAllowedUseBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。');
judge_state.type = 0;
await judge_state.save();
await judge_state.updateRelatedInfo(true);
if (problem.type !== 'submit-answer' && syzoj.languages[req.body.language].format) {
let key = syzoj.utils.getFormattedCodeKey(judge_state.code, req.body.language);
let formattedCode = await FormattedCode.findOne({
where: {
key: key
}
});
}
try {
await Judger.judge(judge_state, problem, 2);
judge_state.pending = true;
judge_state.status = 'Waiting';
await judge_state.save();
} catch (err) {
throw new ErrorMessage(`无法开始评测:${err.toString()}`);
}
res.redirect(syzoj.utils.makeUrl(['submission', 'practice', judge_state.id, pid, cid]));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);

66
modules/submission.js

@ -152,6 +152,72 @@ app.get('/submissions', async (req, res) => {
}
});
app.get('/submission/practice/:id/:pid/:cid', async (req, res) => {
try {
const id = parseInt(req.params.id);
const pid = parseInt(req.params.pid);
const cid = parseInt(req.params.cid);
const judge = await JudgeState.findById(id);
if (!judge) throw new ErrorMessage("提交记录 ID 不正确。");
const curUser = res.locals.user;
if (!await judge.isAllowedVisitBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。');
await judge.loadRelationships();
if (judge.problem.type !== 'submit-answer') {
let key = syzoj.utils.getFormattedCodeKey(judge.code, judge.language);
if (key) {
let formattedCode = await FormattedCode.findOne({
where: {
key: key
}
});
if (formattedCode) {
judge.formattedCode = await syzoj.utils.highlight(formattedCode.code, syzoj.languages[judge.language].highlight);
}
}
judge.code = await syzoj.utils.highlight(judge.code, syzoj.languages[judge.language].highlight);
}
let isAdmin = curUser === null ? false : curUser.is_admin;
let isAuthor = curUser === null ? false : curUser.id === judge.user_id;
let currentConfig = Object.assign({}, displayConfig);
currentConfig.showRejudge = isAuthor || await judge.problem.isAllowedEditBy(res.locals.user, judge.user_id);
if (isAdmin || isAuthor) {
currentConfig.showShare = true;
}
if (isAdmin || isAuthor || judge.is_share) {
judge.code = (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '';
} else {
judge.code = "作者没有开放此题代码,请联系作者分享。";
}
res.render('submission', {
info: getSubmissionInfo(judge, currentConfig),
roughResult: getRoughResult(judge, currentConfig, false),
code: judge.code,
formattedCode: judge.formattedCode ? judge.formattedCode.toString("utf8") : null,
preferFormattedCode: res.locals.user ? res.locals.user.prefer_formatted_code : true,
detailResult: processOverallResult(judge.result, currentConfig),
socketToken: (judge.pending && judge.task_id != null) ? jwt.sign({
taskId: judge.task_id,
type: 'detail',
displayConfig: displayConfig
}, syzoj.config.session_secret) : null,
displayConfig: currentConfig,
cid,
pid
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/submission/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);

262
views/admin_classify.ejs

@ -12,7 +12,7 @@
<div class="ui modal classify">
<i class="close icon"></i>
<div class="header">
新增练习阶段
{{calcModalTitle}}
</div>
<div class="content">
<div class="ui form">
@ -24,37 +24,55 @@
<label>练习阶段简介(100字以内)</label>
<input type="text" v-model="classifyIntro" placeholder="请输入分类简介">
</div>
<div class="field">
<label>Gender</label>
<div class="ui selection dropdown">
<input type="hidden" name="gender">
<i class="dropdown icon"></i>
<div class="default text">Gender</div>
<div class="menu">
<div class="item" data-value="1">Male</div>
<div class="item" data-value="0">Female</div>
</div>
</div>
<div class="field required" >
<label>练习阶段顺序</label>
<el-input-number disabled v-model="order" :min="1" :max="calcOrderMax" label="练习阶段顺序"></el-input-number>
</div>
<div class="field required" :class="{ error: problemIdArray.length === 0 }">
<label>题目选择(至少包含一道题)</label>
<el-select
style="width:100%"
v-model="problemIdArray"
multiple
filterable
remote
reserve-keyword
placeholder="请输入关键词"
:remote-method="remoteMethod"
:loading="loading">
<el-option
v-for="item in problemOptions"
:key="item.value"
:label="item.label"
:disabled="item.disabled"
:value="item.value">
</el-option>
</el-select>
</div>
<button class="ui button" @click="submitInfo">提交</button>
<button class="ui button" @click="submitInfo">{{calcModalTitle}}</button>
</div>
</div>
</div>
<div v-if="mockClassify.length === 0">
暂无练习阶段
<div v-if="classifyArray.length === 0">
<div class="ui placeholder segment">
<div class="ui icon header">
<i class="sticky note icon"></i>
暂无练习阶段
</div>
</div>
</div>
<div v-else>
<div class="current-classify">
<h4>目前练习阶段</h4>
<div class="ui cards">
<div class="card" v-for="item in mockClassify">
<div class="card" v-for="item in classifyArray">
<div class="content">
<div class="header">{{item.title}}</div>
<div class="header">{{item.name}}</div>
<div class="description">
{{item.description}}
{{item.intro}}
</div>
</div>
<div class="ui bottom attached button">
<div class="ui bottom attached button" @click="show(item.id)">
<i class="add icon"></i>
编辑练习阶段信息
</div>
@ -62,64 +80,208 @@
</div>
</div>
</div>
<div class="ui labeled button" tabindex="0" @click="show">
<div class="ui labeled button" tabindex="0" @click="show()">
<div class="ui button">
<i class="plus icon"></i> 新增练习阶段
</div>
</div>
</div>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new window.Vue({
el: '#classify',
data: {
problemIdArray: [],
order: 1,
classifyName: '',
classifyIntro: '',
message: 'Hello Vue!',
mockClassify: [{
title: '测试分类a',
description: '测试介绍'
}, {
title: '测试分类1',
description: '测试介绍1'
}]
classifyArray: [],
classifyId: -1,
problemOptions: [],
loading: false,
},
created: function() {
this.getClassifyInfo();
},
methods: {
show: function(){
remoteMethod(query) {
let that = this;
if (query !== '') {
if (this.usedProblemId.includes(parseInt(query))) {
this.$message({
message: '这道题已经分配过',
type: 'warning'
});
} else {
this.loading = true;
setTimeout(() => {
$.ajax({
url: `/api/v2/search/problems/${query}`,
type: 'GET',
success: function (data) {
that.problemOptions = data.results.map((item) => {
return {
id: item.value,
label: item.name,
value: item.value,
}
})
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('创建失败');
}
});
this.loading = false;
}, 200);
}
} else {
this.options = [];
}
},
getClassifyInfo: function() {
let that = this;
$.ajax({
url: '/api/practice/all',
type: 'GET',
success: function (data) {
that.classifyArray = data.result;
that.usedProblemId = data.problem.map(function(item){return item.p_id});
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('查询失败');
}
});
},
resetModal: function() {
this.classifyName = '';
this.classifyIntro = '';
this.problemIdArray = [];
this.classifyId = -1;
this.problemOptions=[];
this.order = this.calcOrderMax;
},
show: function(id){
let that = this;
if (id) {
this.classifyId = id;
$.ajax({
url: `/api/practice/classify/${id}`,
type: 'GET',
success: function (data) {
if (!data.error_code) {
const {classifyInfo: {id, intro, name, order}, problem} = data;
that.classifyName = name;
that.classifyIntro = intro;
that.problemIdArray = problem;
that.order = order;
problem.forEach(function(problemId) {
$.ajax({
url: `/api/v2/search/problems/${problemId}`,
type: 'GET',
success: function (data) {
that.problemOptions = data.results.map((item) => {
return {
id: item.value,
label: item.name,
disabled: true,
value: item.value,
}
})
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('创建失败');
}
});
})
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('创建失败');
}
});
} else {
this.resetModal();
}
$('.ui.modal.classify')
.modal('show')
},
checkStringLength: function(string, maxLen) {
return string.length > 0 && string.length <= maxLen;
},
submitInfo: function() {
let that = this;
let name = this.classifyName;
let intro = this.classifyIntro;
if(this.checkStringLength(name, 20) && this.checkStringLength(intro, 100)) {
if(this.checkStringLength(name, 20) && this.checkStringLength(intro, 100) && this.problemIdArray.length > 0) {
const obj = {
name: this.classifyName,
intro: this.classifyIntro
intro: this.classifyIntro,
order: this.order,
problemIdArray: this.problemIdArray
}
$.ajax({
url: '/api/practice/create',
type: 'POST',
async: true,
data: obj,
success: function (data) {
console.log(data);
if (data.error_code) {
if (this.classifyId === -1) {
$.ajax({
url: '/api/practice/create',
type: 'POST',
async: true,
data: obj,
success: function (data) {
if (data.error_code) {
alert('创建失败');
} else {
alert('添加成功');
that.resetModal();
$('.ui.modal.classify')
.modal('hide')
that.getClassifyInfo();
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('创建失败');
} else {
alert('添加成功')
$('.ui.modal.classify')
.modal('hide')
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(XMLHttpRequest);
alert('创建失败');
}
});
});
} else {
$.ajax({
url: `/api/practice/classify/update/${that.classifyId}`,
type: 'PUT',
async: true,
data: obj,
success: function (data) {
if (data.error_code) {
alert('更新失败');
} else {
alert('更新成功');
that.resetModal();
$('.ui.modal.classify')
.modal('hide')
that.getClassifyInfo();
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('更新时发生错误');
}
});
}
}
}
},
computed: {
calcModalTitle: function(){
if (this.classifyId === -1) {
return '新增练习阶段';
} else {
return '编辑练习阶段'
}
},
calcOrderMax: function() {
if (this.classifyId === -1) {
return this.classifyArray.length + 1;
} else {
return this.classifyArray.length
}
}
}

1
views/admin_config.ejs

@ -34,7 +34,6 @@ function showBooleanItem(name, text, val) {
</div>
<%
}
console.log(items);
for (let item in items) {
if (items[item] === null) {
showTitle(item);

5
views/header.ejs

@ -33,7 +33,6 @@
<link href="<%- this.builtInCdnUrl %>/google-fonts/exo-2.css" rel="stylesheet">
<% } %>
<script src="<%- lib('jquery/3.3.1/jquery.min.js') %>"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<% if (syzoj.config.google_analytics && syzoj.config.google_analytics !== 'UA-XXXXXXXX-X') { %>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@ -61,7 +60,7 @@
<% } %>
<a class="item<% if (active === '') { %> active<% } %>" href="/"><i class="home icon"></i> 首页</a>
<a class="item<% if (active.startsWith('problem')) { %> active<% } %>" href="/problems"><i class="list icon"></i> 题库</a>
<a class="item<% if (active.startsWith('contest')) { %> active<% } %>" href="/practice"><i class="pencil icon"></i> 练习</a>
<a class="item<% if (active.startsWith('practice')) { %> active<% } %>" href="/practice"><i class="pencil icon"></i> 练习</a>
<a class="item<% if (active.startsWith('contest')) { %> active<% } %>" href="/contests"><i class="calendar icon"></i> 比赛</a>
<a class="item<% if (active.startsWith('submission')) { %> active<% } %>" href="/submissions"><i class="tasks icon"></i> 评测</a>
<a class="item<% if (active.startsWith('ranklist')) { %> active<% } %>" href="/ranklist"><i class="signal icon"></i> 排名</a>
@ -100,7 +99,7 @@
<a class="item" href="#" onclick="$('#modal-restart').modal('show')"><i class="redo icon"></i>重启服务</a>
<% } %>
<a class="item" href-post="<%= syzoj.utils.makeUrl(['logout'], { url: req.originalUrl }) %>"><i class="power icon"></i>注销</a>
<a class="item" href-post="<%= syzoj.utils.makeUrl(['logout'], { url: '/' }) %>"><i class="power icon"></i>注销</a>
</div>
</div>
</a>

238
views/practice.ejs

@ -1,214 +1,40 @@
<% this.title = '练习' %>
<style>
.practice{
display: flex;
#practice{
width: 100%;
}
.practice .practice-main{
width: 80%;
margin-right: 30px;
}
.practice .practice-main .practice-main-navigation{
margin-bottom: 10px;
background-color: #efefef;
padding: 10px;
}
.practice .practice-main .practice-main-navigation .classify-card{
width: 90px;
height: 90px;
display: inline-block;
margin: 5px;
vertical-align: top;
cursor: pointer;
background-color: red;
}
.practice .practice-main .practice-main-navigation .classify-card small{
font-size: 12px;
color: #fff;
display: block;
}
.practice .practice-main .practice-main-navigation .classify-card .classify-name{
text-align: center;
line-height: 50px;
color: white;
}
.practice .practice-main .practice-main-body{
background-color: #efefef;
padding: 10px;
}
.practice .practice-main .practice-main-body .classify-card{
margin-bottom: 20px;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-title{
margin-bottom: 10px;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-description{
border-bottom:1px solid #e4e4e4;
margin-bottom: 10px;
padding-bottom: 5px;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-problem{
border-top:1px solid #e4e4e4;
padding-top: 10px;
display: flex;
flex-wrap: wrap;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-problem .problem{
border-radius: 5px;
width: 30%;
background-color: white;
margin: 10px;
height: 100px;
overflow: hidden;
cursor: pointer;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-problem .problem .problem-title{
text-align: center;
padding: 5px 0;
color: #14a6ef;
background-color: rgba(59,180,242,.15);
border-color: #caebfb;
}
.practice .practice-main .practice-main-body .classify-card .classify-card-problem .problem .problem-info{
font-size: 13px;
padding: 5px;
}
.practice .practice-right{
width: 300px;
padding: 30px 10px;
height:200px;
background-color: #efefef;
}
.practice .practice-right .username{
color:#3590D2;
}
</style>
<% include header %>
<div class="practice">
<div class="practice-main">
<div class="practice-main-navigation">
<div class="classify-card">
<small>#1</small>
<div class="classify-name">分类名字</div>
</div>
<div class="classify-card">
<small>#1</small>
<div class="classify-name">分类名字</div>
</div>
</div>
<div class="practice-main-body">
<div class="classify-card">
<div class="classify-card-title">
<h2>测试分类</h2>
</div>
<div class="classify-card-description">
测试分类的介绍
</div>
<div class="ui indicating progress" data-value="124" data-total="200">
<div class="bar">
<div class="progress"></div>
</div>
<div class="label">当前分类完成进度</div>
</div>
<div class="classify-card-problem">
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介发送到发送到范德萨发士大夫撒打发斯蒂芬
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
</div>
</div>
<div class="classify-card">
<div class="classify-card-title">
<h2>测试分类</h2>
</div>
<div class="classify-card-description">
测试分类的介绍
</div>
<div class="ui indicating progress" data-value="1" data-total="200">
<div class="bar">
<div class="progress"></div>
</div>
<div class="label">当前分类完成进度</div>
</div>
<div class="classify-card-problem">
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介发送到发送到范德萨发士大夫撒打发斯蒂芬
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
<div class="problem">
<div class="problem-title">
第一个问题
</div>
<div class="problem-info">
题目简介简介题目简介简介题目简介简介题目简介简介题目简介简介
</div>
</div>
</div>
</div>
</div>
</div>
<div class="practice-right">
<h1 class="username">用户名</h1>
<p>已通过数/分类总题数:5/20</p>
</div>
</div>
<div id="practice">
<% if(result.length === 0) { %> <div class="ui placeholder segment">
<div class="ui icon header">
<i class="sticky note icon"></i>
暂无题目处于可练习的状态下
</div>
</div> <% } else { %> <table class="ui very basic center aligned table">
<thead>
<tr>
<th>练习阶段名称</th>
<th>练习阶段介绍</th>
<th>练习阶段包含题目数量</th>
<th>已完成题目数量</th>
<th>练习阶段状态</th>
<th>是否是当前阶段</th>
</tr>
</thead>
<tbody>
<% for (let resultItem of result){%>
<tr>
<% if(resultItem.href) { %> <td><a href="<%= resultItem.href%>"><%= resultItem.name%></a></td> <% } else { %> <td><%= resultItem.name%></td> <% } %>
<td><%= resultItem.intro%></td>
<td><%= resultItem.problem_num%></td>
<td><%= resultItem.ac_num%></td>
<% if(resultItem.href) { %> <td>可练习</td> <% } else { %> <td>目前不可练习,完成当前阶段后解锁</td> <% } %>
<% if(resultItem.isNow) { %> <td>是</td> <% } else { %> <td>否</td> <% } %>
</tr>
<%}%>
</tbody>
</table> <% } %>
<script>
function getColor() {
let colorArray =["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];
let color="#";
for(let i=0;i<6;i++) {
color+=colorArray[Math.floor(Math.random()*16)];
}
return color;
}
</script>
</div>
<% include footer %>

26
views/practice_classify.ejs

@ -0,0 +1,26 @@
<% this.title = '练习' %>
<style>
</style>
<% include header %>
<div id="practice_classify">
<table class="ui selectable celled table">
<thead>
<tr>
<th>题目</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<% for (let problemItem of problems){%>
<tr>
<td><a href="<%= problemItem.href %>"><%= problemItem.title %></a></td>
<td><%= problemItem.statusStr %></td>
</tr>
<%}%>
</tbody>
</table>
</div>
<script>
</script>
<% include footer %>

392
views/practice_problem.ejs

@ -0,0 +1,392 @@
<% include util %>
<%
if (typeof contest === 'undefined') contest = null;
if (contest) {
this.title = this.alpha(pid) + '. ' + syzoj.utils.removeTitleTag(problem.title) + ' - ' + contest.title + ' - 比赛';
} else {
this.title = problem.title + ' - 题目';
}
%>
<% include header %>
<style>
#languages-menu::-webkit-scrollbar, #testcase-menu::-webkit-scrollbar {
width: 0px;
background: transparent;
}
#languages-menu .item::after, #testcase-menu .item::after {
display: none;
}
</style>
<% include monaco-editor %>
<div class="ui center aligned grid">
<div class="row">
<h1 class="ui header">
<% if (contest) { %>
<%= this.alpha(pid) %>. <%= syzoj.utils.removeTitleTag(problem.title) %>
<% } else { %>
#<%= problem.id %>. <%= problem.title %><% if (problem.allowedEdit && !problem.is_public) { %><span class="ui tiny red label">未公开</span><% } %>
<% } %>
</h1>
</div>
<% if (problem.type !== 'submit-answer') { %>
<div class="row" style="margin-top: -15px">
<span class="ui label">内存限制:<%= problem.memory_limit %> MiB</span>
<span class="ui label">时间限制:<%= problem.time_limit %> ms</span>
<% if (problem.type === 'interaction') { %>
<span class="ui label">题目类型:交互</span>
<% } else if (problem.file_io) { %>
<span class="ui label">输入文件:<%= problem.file_io_input_name %></span>
<span class="ui label">输出文件:<%= problem.file_io_output_name %></span>
<% } else { %>
<span class="ui label">标准输入输出</span>
<% } %>
</div>
<% } %>
<% if (problem.type !== 'interaction') { %>
<div class="row" style="margin-top: -<%= problem.type === 'submit-answer' ? 15 : 23 %>px">
<span class="ui label">题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %></span>
<span class="ui label">评测方式:<%= (testcases && !testcases.error) ? (testcases.spj ? 'Special Judge' : '文本比较') : '无测试数据' %></span>
</div>
<% } %>
<% if (!contest) { %>
<div class="row" style="margin-top: -23px">
<span class="ui label">上传者:
<% if (problem.is_anonymous && !problem.allowedManage) { %>
匿名
<% } else { %>
<a href="<%= syzoj.utils.makeUrl(['user', problem.user_id]) %>"><%= problem.user.username %></a>
<% } %>
</span>
<% if (problem.allowedManage) { %>
<span class="ui label">公开者:
<% if (problem.publicizer) { %>
<a href="<%= syzoj.utils.makeUrl(['user', problem.publicizer_id]) %>"><%= problem.publicizer.username %></a>
<% } else { %>
未知
<% } %>
</span>
<% } %>
</div>
<% } %>
</div>
<div class="ui grid">
<div class="row">
<div class="column">
<div class="ui buttons">
<% if (typeof contest !== 'undefined' && contest) { %>
<% if (!contest.ended) { %>
<a class="small ui primary button" href="#submit_code">提交</a>
<% } else { %>
<a class="small ui primary button" href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>">转到题库</a>
<% } %>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, 'submissions'], { problem_id: pid }) %>">提交记录</a>
<% if (problem.additional_file) { %>
<a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, pid, 'download', 'additional_file']) %>">附加文件</a>
<% } %>
<a href="<%= syzoj.utils.makeUrl(['contest', contest.id]) %>" class="ui orange button">返回比赛</a>
<% } else { %>
<% if (testcases && !testcases.error) { %>
<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', problem.type === 'submit-answer' ? 'shortest' : 'fastest']) %>">统计</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>
<% } %>
<a class="small ui brown button" href="<%= syzoj.utils.makeUrl(['discussion', 'problem', problem.id]) %>" style="position: relative; ">
讨论
<% if (discussionCount) { %>
<div class="floating ui red tiny circular label" style="background-color: #8c5633 !important; top: -0.6em; "><%= discussionCount %></div>
<% } %>
</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, 'manage']) %>">管理</a>
<% } %>
<% if (problem.allowedManage) { %>
<% if (problem.is_public) { %>
<a class="small ui button" id="dis_public" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'dis_public']) %>">取消公开</a>
<% } else { %>
<a class="small ui button" id="public" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'public']) %>">公开</a>
<% } %>
<div class="ui basic modal" id="modal-delete">
<div class="ui icon header">
<i class="trash icon"></i>
<p style="margin-top: 15px; ">删除题目</p>
</div>
<div class="content" style="text-align: center; ">
<p>确认删除此题目吗?提交记录、讨论以及测试数据将一并删除。<br>
删除题目导致的修改用户提交、通过数量可能会耗费一些时间。</p>
<b>警告:删除比赛中的题目会导致系统错乱!请确认没有比赛使用此题目。</b>
</div>
<div class="actions">
<div class="ui red basic cancel inverted button">
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'delete']) %>">
<i class="checkmark icon"></i>
</a>
</div>
</div>
<div class="small ui red button" onclick="$('#modal-delete').modal('show')">删除</div>
<% } %>
</div>
<% } %>
</div>
</div>
<% if (problem.description) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">题目描述</h4>
<div class="ui bottom attached segment font-content"><%- problem.description %></div>
</div>
</div>
<% } %>
<% if (problem.input_format) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">输入格式</h4>
<div class="ui bottom attached segment font-content"><%- problem.input_format %></div>
</div>
</div>
<% } %>
<% if (problem.output_format) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">输出格式</h4>
<div class="ui bottom attached segment font-content"><%- problem.output_format %></div>
</div>
</div>
<% } %>
<% if (problem.example) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">样例</h4>
<div class="ui bottom attached segment font-content"><%- problem.example %></div>
</div>
</div>
<% } %>
<% if (problem.limit_and_hint) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">数据范围与提示</h4>
<div class="ui bottom attached segment font-content"><%- problem.limit_and_hint %></div>
</div>
</div>
<% } %>
<% if (problem.tags && problem.tags.length && (typeof contest === 'undefined' || !contest)) { %>
<div class="row">
<div class="column">
<h4 class="ui block header" id="show_tag_title_div" style="margin-bottom: 0; margin-left: -1px; margin-right: -1px; "><a href="#" id="show_tag_button" style="color: #000; ">显示分类标签</a></h4>
<div class="ui bottom attached segment" style="display: none; " id="show_tag_div">
<% for (let tag of problem.tags) { %>
<a href="<%= syzoj.utils.makeUrl(['problems', 'tag', tag.id]) %>" class="ui medium <%= tag.color %> label">
<%= tag.name %>
</a>
<% } %>
</div>
</div>
</div>
<script>
$(function () {
$('#show_tag_button').click(function (e) {
e.preventDefault();
$('#show_tag_title_div').addClass('top');
$('#show_tag_title_div').addClass('top attached');
$('#show_tag_title_div').text('分类标签');
$('#show_tag_div').css('display', '');
})
});
</script>
<% } %>
<% let noSubmit = false; %>
<%
if (typeof contest !== 'undefined' && contest && contest.ended || (!testcases || testcases.error)) {
noSubmit = true;
}
%>
<% if (noSubmit) { %>
<div class="row">
<div class="column">
<%
const formUrl = syzoj.utils.makeUrl(['problem', 'practice', problem.id, cid, 'submit']);
%>
<form action="<%= formUrl %>" method="post" onsubmit="return submit_code()" id="submit_code" enctype="multipart/form-data">
<% if (problem.type === 'submit-answer') { %>
<%
let cases = [];
if (testcases && !testcases.error) {
for (let subtasks of testcases) {
for (let testcase of subtasks.cases) {
cases.push(testcase.answer);
}
}
}
%>
<script>
var cases = <%- serializejs(cases) %>, currCase = 0;
</script>
<div class="ui grid">
<% if (testcases) { %>
<div class="four wide column" style="margin-right: -25px; ">
<div class="ui attached vertical fluid pointing menu" id="testcase-menu" style="height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<% for (let i = 0; i < cases.length; i++) { %>
<a style="border-radius: 0; " class="item<%= i === 0 ? ' active' : '' %>" data-value="<%= i %>">
<%= cases[i] %>
</a>
<% } %>
</div>
</div>
<div class="twelve wide stretched column" style="position: relative; padding-left: 0; margin-left: calc(-1rem - 1px); width: calc(75% + 1rem + 13px) !important; ">
<% for (let i = 0; i < cases.length; i++) { %>
<div id="editor-<%= i %>" class="editor" style="position: absolute; width: 100%; height: calc(100% - 28px); border: 1px solid #D4D4D5; overflow: hidden; <%= i === 0 ? '' : 'visibility: hidden; ' %>">
<%- this.showLoadingEditor(); %>
</div>
<% } %>
</div>
<% } %>
<input id="answer_by_editor" name="answer_by_editor" type="hidden">
<script>
window.onEditorLoaded(function () {
window.editors = [];
for (var i = 0; i < cases.length; i++) {
var editor = window.createCodeEditor(document.getElementById("editor-" + i));
editors[i] = editor;
}
$(function () {
$('#testcase-menu .item').click(function() {
$(this)
.addClass('active')
.closest('.ui.menu')
.find('.item')
.not($(this))
.removeClass('active')
;
var x = $(this).attr('data-value');
if (currCase != x) {
$('#editor-' + currCase).css('visibility', 'hidden');
$('#editor-' + x).css('visibility', 'visible');
currCase = x;
}
});
});
});
</script>
<div class="ui form" style="width: 100%; ">
<div class="inline fields" style="width: 100%; ">
<div class="field" style="margin: 0 auto; ">
<label for="answer">或者,上传答案(请使用 ZIP 格式压缩)</label>
<input type="file" id="answer" name="answer">
</div>
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui labeled icon button"><i class="ui edit icon"></i>提交</button></div>
<% } else { %>
<input name="language" type="hidden" id="form">
<input name="code" type="hidden">
<div class="ui grid">
<div class="four wide column" style="margin-right: -25px; ">
<div class="ui attached vertical fluid pointing menu" id="languages-menu" style="height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<%
let language = syzoj.config.enabled_languages[0];
if (state) {
language = state.language;
} else if (lastLanguage) language = lastLanguage;
%>
<% for (lang of syzoj.config.enabled_languages) { %>
<a style="border-radius: 0; " class="item<%= lang === language ? ' active' : '' %>" data-value="<%= lang %>" data-mode="<%= syzoj.languages[lang].editor %>">
<%= syzoj.languages[lang].show %>
<div class="ui right floated" style="opacity: 0.4; margin-top: 8px; font-size: 0.7em; "><%= syzoj.languages[lang].version %></div>
</a>
<% } %>
</div>
</div>
<div class="twelve wide stretched column" style="position: relative; padding-left: 0; margin-left: calc(-1rem - 1px); width: calc(75% + 1rem + 13px) !important; ">
<div id="editor" style="position: absolute; width: 100%; height: calc(100% - 28px); border: 1px solid #D4D4D5; overflow: hidden; " class="editor">
<%- this.showLoadingEditor(); %>
</div>
</div>
<div class="ui form" style="width: 100%; ">
<div class="inline fields" style="width: 100%; ">
<div class="field" style="margin: 0 auto; ">
<label for="answer">或者,上传代码文件</label>
<input type="file" id="answer" name="answer">
</div>
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; ">
<button type="submit" class="ui labeled icon button"><i class="ui edit icon"></i>提交</button>
</div>
<% } %>
</form>
</div>
</div>
<% } %>
</div>
<% if (problem.type !== 'submit-answer') { %>
<script type="text/javascript">
var editor;
window.onEditorLoaded(function () {
var editorElement = document.getElementById('editor');
var content = '';
<% if (state) { %>content = <%- serializejs(state.code) %>;<% } %>
editor = window.createCodeEditor(editorElement, $('#languages-menu .item.active').data('mode'), content);
window.editor = editor;
});
var lastSubmitted = '';
function submit_code() {
if (!$('#submit_code input[name=answer]').val().trim() && !editor.getValue().trim()) return false;
$('#submit_code input[name=language]').val($('#languages-menu .item.active').data('value'));
lastSubmitted = editor.getValue();
$('#submit_code input[name=code]').val(editor.getValue());
return true;
}
$('#languages-menu')[0].scrollTop = $('#languages-menu .active')[0].offsetTop - $('#languages-menu')[0].firstElementChild.offsetTop;
$(function () {
$('#languages-menu .item').click(function() {
$(this)
.addClass('active')
.closest('.ui.menu')
.find('.item')
.not($(this))
.removeClass('active')
;
monaco.editor.setModelLanguage(editor.getModel(), $(this).data('mode'));
});
});
</script>
<% } else { %>
<script>
function submit_code() {
var a = [];
for (var i = 0; i < cases.length; i++) {
a.push({
filename: cases[i],
data: editors[i].getValue()
});
}
$('#answer_by_editor').val(JSON.stringify(a));
}
</script>
<% } %>
<% include footer %>

27
views/submission.ejs

@ -311,8 +311,11 @@ const vueApp = new Vue({
$('.ui.accordion').off().accordion({ selector: { trigger: '.title:not(.unexpandable)' } });
}
});
console.log(token);
if (token != null) {
const loadSocketIO = function () {
const cid = <%= cid%>;
const pid = <%= pid%>;
let currentVersion = 0;
const socket = io(socketUrl);
socket.on('connect', function () {
@ -337,6 +340,27 @@ if (token != null) {
console.log("Judge finished");
vueApp.roughData.running = false;
vueApp.roughData.result = p.roughResult;
if (p.roughResult.result === 'Accepted') {
$.ajax({
url: `/api/practice/pass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
} else {
$.ajax({
url: `/api/practice/nopass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
}
vueApp.detailResult = p.result;
socket.close();
});
@ -345,8 +369,9 @@ if (token != null) {
if (data && data.ok) {
if (data.finished) {
vueApp.roughData.result = data.roughResult;
if (!data.result) location.reload(true);
// if (!data.result) location.reload(true);
vueApp.detailResult = data.result;
vueApp.roughData.running = false;
socket.close();
} else {
if (data.running) {

Loading…
Cancel
Save