mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
add contest_user
This commit is contained in:
1 parent
d67a0339fd
commit
e6ffdaf11e
8 files changed
+206
-8
No files matched your search
@@ -7,7 +7,7 @@ import {
|
||||
Counter, sortFiles, streamToBuffer, Time,
|
||||
} from '@hydrooj/utils/lib/utils';
|
||||
import {
|
||||
BadRequestError, ContestNotEndedError, ContestNotFoundError, ContestNotLiveError,
|
||||
BadRequestError, ContestNotAttendedError, ContestNotEndedError, ContestNotFoundError, ContestNotLiveError,
|
||||
ContestScoreboardHiddenError, FileLimitExceededError, FileUploadError, ForbiddenError,
|
||||
InvalidTokenError, NotAssignedError, PermissionError, ValidationError,
|
||||
} from '../error';
|
||||
@@ -592,6 +592,40 @@ export class ContestFileDownloadHandler extends ContestDetailBaseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export class ContestUserHandler extends ContestDetailBaseHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray();
|
||||
tsdocs.forEach((i) => {
|
||||
i.endAt = this.tdoc.duration ? moment(this.tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate() : null;
|
||||
});
|
||||
const udict = await user.getListForRender(domainId, [this.tdoc.owner, ...tsdocs.map((i) => i.uid)]);
|
||||
this.response.body = { tdoc: this.tdoc, tsdocs, udict };
|
||||
this.response.pjax = 'partials/contest_user.html';
|
||||
this.response.template = 'contest_user.html';
|
||||
}
|
||||
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('uids', Types.NumericArray)
|
||||
@param('unrank', Types.Boolean)
|
||||
async postAddUser(domainId: string, tid: ObjectID, uids: number[], unrank = false) {
|
||||
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
await Promise.all(uids.map((uid) => contest.attend(domainId, tid, uid, { unrank })));
|
||||
this.back();
|
||||
}
|
||||
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('uid', Types.PositiveInt)
|
||||
async postRank(domainId: string, tid: ObjectID, uid: number) {
|
||||
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
const tsdoc = await contest.getStatus(domainId, tid, uid);
|
||||
if (!tsdoc) throw new ContestNotAttendedError(uid);
|
||||
await contest.setStatus(domainId, tid, uid, { unrank: !tsdoc.unrank });
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(ctx) {
|
||||
ctx.Route('contest_create', '/contest/create', ContestEditHandler);
|
||||
ctx.Route('contest_main', '/contest', ContestListHandler, PERM.PERM_VIEW_CONTEST);
|
||||
@@ -604,4 +638,5 @@ export async function apply(ctx) {
|
||||
ctx.Route('contest_code', '/contest/:tid/code', ContestCodeHandler, PERM.PERM_VIEW_CONTEST);
|
||||
ctx.Route('contest_files', '/contest/:tid/file', ContestFilesHandler, PERM.PERM_VIEW_CONTEST);
|
||||
ctx.Route('contest_file_download', '/contest/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_CONTEST);
|
||||
ctx.Route('contest_user', '/contest/:tid/user', ContestUserHandler, PERM.PERM_VIEW_CONTEST);
|
||||
}
|
||||
@@ -9,12 +9,12 @@ const ranked: ranked = async (cursor, equ) => {
|
||||
const results = [];
|
||||
const docs = await cursor.toArray();
|
||||
for (const doc of docs) {
|
||||
count++;
|
||||
if (!last || !equ(last, doc)) r = count;
|
||||
if (doc.unrank) {
|
||||
results.push([0, doc]);
|
||||
continue;
|
||||
}
|
||||
count++;
|
||||
if (!last || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
results.push([r, doc]);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ Accepted Problems: 通过的题目
|
||||
Accepted Ratio: 通过率
|
||||
Accepted: 已通过
|
||||
Access Control: 访问控制
|
||||
According to the contest rules, you cannot view your submission details at current.: 根据比赛规则,你现在不能查看你的评测记录详情。
|
||||
Account Settings: 账户设置
|
||||
Action: 动作
|
||||
Active Sessions: 活动会话
|
||||
@@ -92,6 +93,7 @@ At least 4 characters: 至少 4 个字符
|
||||
Attend Contest: 参加比赛
|
||||
Attend contests: 参加比赛
|
||||
Attended: 已参加
|
||||
Attender Manage: 参赛者管理
|
||||
author: 作者
|
||||
Auto hide problems during the contest: 在比赛过程中自动隐藏题目
|
||||
Auto Read Tasks: 自动识别任务
|
||||
@@ -175,6 +177,7 @@ contest_detail_problem_submit: 递交比赛题目
|
||||
contest_edit: 编辑比赛
|
||||
contest_main: 比赛
|
||||
contest_scoreboard: 比赛成绩表
|
||||
contest_user: 参赛者管理
|
||||
Contest: 比赛
|
||||
Continue: 继续
|
||||
Contributed Problems: 贡献的题目
|
||||
@@ -551,6 +554,7 @@ objective: 客观题
|
||||
Oh, the user doesn't have any contributions!: 啊哦,这个用户还没贡献过题目~
|
||||
Oh, the user hasn't created any discussions yet!: 这个用户还没有发布过讨论
|
||||
Oh, the user hasn't submitted yet!: 这个用户还没有交过题 _(:зゝ∠)_
|
||||
Oh, there is no submission!: 喔,还没有评测记录呢!
|
||||
Oh, there is no task in the queue!: 喔,队列中目前没有任务。
|
||||
Oh, there is no task that matches the filter!: 喔,目前没有符合过滤条件的任务。
|
||||
Ok: 确定
|
||||
@@ -867,6 +871,7 @@ Unknown field {0}.: 未知字段 {0}。
|
||||
Unknown: 未知
|
||||
Unlimited: 无限制
|
||||
Unordered List: 无序列表
|
||||
Unrank: 打星
|
||||
Unstar Topic: 取消收藏
|
||||
Unstar: 取消收藏
|
||||
Unsupported configure this type of problem. Please refer to the documentation.: 暂不支持快捷配置此类题目。请参照文档操作。
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import $ from 'jquery';
|
||||
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
|
||||
import { ActionDialog } from 'vj/components/dialog';
|
||||
import Notification from 'vj/components/notification';
|
||||
import { NamedPage } from 'vj/misc/Page';
|
||||
import {
|
||||
i18n, pjax, request, tpl,
|
||||
} from 'vj/utils';
|
||||
|
||||
const page = new NamedPage('contest_user', () => {
|
||||
const addUserDialogContent = $(tpl`
|
||||
<div>
|
||||
<div class="row"><div class="columns">
|
||||
<h1>${i18n('Add User')}</h1>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label>
|
||||
${i18n('Users')}
|
||||
<input name="add_user_users" type="text" class="textbox" autocomplete="off">
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label>
|
||||
${i18n('Rank')}
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="unrank" class="checkbox">${i18n('UnRank')}
|
||||
</label>
|
||||
</label>
|
||||
</div></div>
|
||||
</div>
|
||||
`);
|
||||
addUserDialogContent.appendTo(document.body);
|
||||
const userSelect = UserSelectAutoComplete.getOrConstruct<UserSelectAutoComplete<true>>(
|
||||
addUserDialogContent.find('[name="add_user_users"]'),
|
||||
{ multi: true, height: 'auto' },
|
||||
);
|
||||
|
||||
const addUserDialog = new ActionDialog({
|
||||
$body: addUserDialogContent,
|
||||
onDispatch(action) {
|
||||
if (action === 'ok' && !userSelect.value()) {
|
||||
userSelect.focus();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
addUserDialog.clear = function () {
|
||||
userSelect.clear();
|
||||
addUserDialog.$dom.find('[name="unrank"]').prop('checked', false);
|
||||
return this;
|
||||
};
|
||||
|
||||
async function handleClickAddUser() {
|
||||
const action = await addUserDialog.clear().open();
|
||||
if (action !== 'ok') return;
|
||||
const unrank = addUserDialog.$dom.find('[name="unrank"]').prop('checked');
|
||||
const uids = userSelect.value();
|
||||
try {
|
||||
const res = await request.post('', {
|
||||
operation: 'add_user',
|
||||
uids: uids.join(','),
|
||||
unrank,
|
||||
});
|
||||
if (res.url && res.url !== window.location.href) window.location.href = res.url;
|
||||
else {
|
||||
Notification.success(i18n('User has been added.'));
|
||||
pjax.request({ push: false });
|
||||
}
|
||||
} catch (error) {
|
||||
Notification.error([error.message, ...error.params].join(' '));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEditRank(ev) {
|
||||
const uid = $(ev.target).data('uid');
|
||||
try {
|
||||
const res = await request.post('', {
|
||||
operation: 'rank',
|
||||
uid,
|
||||
});
|
||||
if (res.url && res.url !== window.location.href) window.location.href = res.url;
|
||||
else {
|
||||
Notification.success(i18n('User Rank has been edited.'));
|
||||
pjax.request({ push: false });
|
||||
}
|
||||
} catch (error) {
|
||||
Notification.error([error.message, ...error.params].join(' '));
|
||||
}
|
||||
}
|
||||
|
||||
$('[name="add_user"]').on('click', () => handleClickAddUser());
|
||||
$(document).on('click', '[name="edit_rank"]', handleEditRank);
|
||||
});
|
||||
|
||||
export default page;
|
||||
@@ -35,11 +35,6 @@
|
||||
<p>{{ _('The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.') }}</p>
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
{% if tdoc.code %}
|
||||
<blockquote class="note">
|
||||
<p>{{ _('The contest is a invitational contest. However it allow users attend contest without the invitation code. Those users will not be ranked.') }}</p>
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends "layout/basic.html" %}
|
||||
{% import "components/contest.html" as contest with context %}
|
||||
{% import "components/record.html" as record with context %}
|
||||
{% import "components/problem.html" as problem with context %}
|
||||
{% block content %}
|
||||
{{ set(UiContext, 'tdoc', tdoc) }}
|
||||
<div class="row">
|
||||
<div class="medium-9 columns">
|
||||
<div class="section">
|
||||
<div class="section__header">
|
||||
<h1 class="section__title">{{ _('Attender Manage') }}</h1>
|
||||
<div class="section__tools">
|
||||
<button class="primary rounded button" name="add_user"><span class="icon icon-search"></span>{{ _('Add User') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ noscript_note.render() }}
|
||||
<div class="section__body no-padding">
|
||||
<table class="data-table">
|
||||
<colgroup>
|
||||
<col class="col--uid">
|
||||
<col class="col--user">
|
||||
<col class="col--start-time">
|
||||
<col class="col--end-time">
|
||||
<col class="col--rank">
|
||||
<col class="col--actions">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col--uid">{{ _('User ID') }}</th>
|
||||
<th class="col--user">{{ _('Username') }}</th>
|
||||
<td class="col--start-time">{{ _('Begin Time') }}</th>
|
||||
<th class="col--end-time">{{ _('End Time') }}</th>
|
||||
<th class="col--rank">{{ _('Rank') }}</th>
|
||||
<th class="col--actions">{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% include "partials/contest_user.html" %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-3 columns">
|
||||
{% set owner_udoc = udict[tdoc.owner] %}
|
||||
{% include "partials/contest_sidebar.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -75,6 +75,9 @@
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('contest_files', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-file"></span> {{ _('Files') }}
|
||||
</a></li>
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('contest_user', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-user"></span> {{ _('Attender Manage') }}
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if handler.user.own(tdoc) or handler.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE) or handler.user.hasPerm(perm.PERM_READ_RECORD_CODE) %}
|
||||
{% if model.contest.canShowRecord.call(handler, tdoc, true) %}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{% import "components/user.html" as user with context %}
|
||||
{% import "components/contest.html" as contest with context %}
|
||||
<tbody data-fragment-id="constest_user-tbody">
|
||||
{%- for tsdoc in tsdocs -%}
|
||||
<tr data-uid="{{ tsdoc.uid }}">
|
||||
<td class="col--uid">{{ tsdoc.uid }}</td>
|
||||
<td class="col--user">{{ user.render_inline(udict[tsdoc.uid], badge=false) }}</td>
|
||||
<td class="col--start-time">{{ contest.render_time(tsdoc.startAt or tdoc.beginAt) }}</td>
|
||||
<td class="col--end-time">{{ contest.render_time(tsdoc.endAt or tdoc.endAt) }}</td>
|
||||
<td class="col--rank">{{ _('UnRank') if tsdoc.unrank else _('Rank') }}</td>
|
||||
<td class="col--actions">
|
||||
<a name="edit_rank" data-uid="{{ tsdoc.uid }}">{{ _('Rank') if tsdoc.unrank else _('UnRank') }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
</tbody>
|
||||
Reference in new issue
Block a user