core&ui: contest correction mode

This commit is contained in:
undefined committed 2025-06-30 16:46:33 +08:00
1 parent a9215698f9
commit e479cb94ea
10 files changed
+56 -16

No files matched your search

+2 -2
View File
@@ -143,11 +143,11 @@ Hydro 用户群:1085853538
- framework: 添加 Subscription API - framework: 添加 Subscription API
- core: 添加 fixStorage 脚本 - core: 添加 fixStorage 脚本
- core: websocket: 重构 Subscription API - core: websocket: 重构 Subscription API
- ui: 允许跨页选择题目 - ui: 允许跨页选择题目,支持批量选择题目
- judge: 重构 config.detail 设置 - judge: 重构 config.detail 设置
- core: 自动清理多余的静态文件 - core: 自动清理多余的静态文件
- ui: 优化比赛题目列表显示 - ui: 优化比赛题目列表显示
- core: 添加补题模式
### Bug Fixes ### Bug Fixes
+10 -1
View File
@@ -207,9 +207,18 @@ export class ContestProblemListHandler extends ContestDetailBaseHandler {
const psdocs: any[] = Object.values(this.response.body.psdict); const psdocs: any[] = Object.values(this.response.body.psdict);
const canViewRecord = contest.canShowSelfRecord.call(this, this.tdoc); const canViewRecord = contest.canShowSelfRecord.call(this, this.tdoc);
this.response.body.canViewRecord = canViewRecord; this.response.body.canViewRecord = canViewRecord;
const rids = psdocs.map((i) => i.rid);
if (contest.isDone(this.tdoc) && canViewRecord) {
const correction = await problem.getListStatus(domainId, this.user._id, this.tdoc.pids);
for (const pid in correction) {
if (this.tsdoc.detail[pid]?.rid === correction[pid].rid) delete correction[pid];
}
rids.push(...Object.values(correction).map((i) => i.rid));
this.response.body.correction = correction;
}
[this.response.body.rdict, this.response.body.rdocs] = canViewRecord [this.response.body.rdict, this.response.body.rdocs] = canViewRecord
? await Promise.all([ ? await Promise.all([
record.getList(domainId, psdocs.map((i: any) => i.rid)), record.getList(domainId, rids),
record.getMulti(domainId, { contest: tid, uid: this.user._id }) record.getMulti(domainId, { contest: tid, uid: this.user._id })
.sort({ _id: -1 }).toArray(), .sort({ _id: -1 }).toArray(),
]) ])
+5 -1
View File
@@ -334,6 +334,10 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler {
discussionCount: dcnt, discussionCount: dcnt,
tdoc: this.tdoc, tdoc: this.tdoc,
owner_udoc: (tid && this.tdoc.owner !== this.pdoc.owner) ? await user.getById(domainId, this.tdoc.owner) : null, owner_udoc: (tid && this.tdoc.owner !== this.pdoc.owner) ? await user.getById(domainId, this.tdoc.owner) : null,
mode: !tid ? 'normal'
: !this.tsdoc?.attend ? 'view'
: !contest.isDone(this.tdoc) ? 'contest'
: problem.canViewBy(this.pdoc, this.user) ? 'correction' : 'none',
}; };
if (this.tdoc && this.tsdoc) { if (this.tdoc && this.tsdoc) {
const fields = ['attend', 'startAt']; const fields = ['attend', 'startAt'];
@@ -802,7 +806,7 @@ export class ProblemFileDownloadHandler extends ProblemDetailHandler {
@param('filename', Types.Filename) @param('filename', Types.Filename)
@param('noDisposition', Types.Boolean) @param('noDisposition', Types.Boolean)
@query('tid', Types.ObjectId, true) @query('tid', Types.ObjectId, true)
async get(domainId: string, type = 'additional_file', filename: string, noDisposition = false, tid: ObjectId) { async get({ }, type = 'additional_file', filename: string, noDisposition = false, tid: ObjectId) {
if (!tid) this.checkPerm(PERM.PERM_VIEW_PROBLEM); if (!tid) this.checkPerm(PERM.PERM_VIEW_PROBLEM);
if (this.pdoc.reference) { if (this.pdoc.reference) {
if (type === 'testdata') throw new ProblemIsReferencedError('download testdata'); if (type === 'testdata') throw new ProblemIsReferencedError('download testdata');
+2
View File
@@ -223,6 +223,7 @@ Copy Selected: 复制选中
Copy WeChat Account: 复制微信号 Copy WeChat Account: 复制微信号
Copy: 复制 Copy: 复制
Copyright Complaint: 版权申诉 Copyright Complaint: 版权申诉
Correction: 补题
CPU Info: CPU信息 CPU Info: CPU信息
Create a contest: 创建一个比赛 Create a contest: 创建一个比赛
Create a Discussion: 创建一个讨论 Create a Discussion: 创建一个讨论
@@ -914,6 +915,7 @@ Testdata: 测试数据
Text: 文本 Text: 文本
The 'default' role applies to ALL REGISTERED USER.: default 角色作用于<b>所有已注册用户</b> The 'default' role applies to ALL REGISTERED USER.: default 角色作用于<b>所有已注册用户</b>
The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.: 本场比赛采用灵活时间模式,你需要在参加后的指定时间内完成比赛。 The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.: 本场比赛采用灵活时间模式,你需要在参加后的指定时间内完成比赛。
The contest is ended. New submissions will be treated as correction submissions and will not be counted in the contest.: 比赛已经结束。新提交将被视为补题提交,不计入比赛成绩。
The group to join when user joining the domain.: 加入域时自动加入的小组。 The group to join when user joining the domain.: 加入域时自动加入的小组。
The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。 The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。
The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。 The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。
+2 -2
View File
@@ -110,7 +110,7 @@ $highlight-button-color = #F6DF45
width: auto width: auto
.page--contest_problemlist .page--contest_problemlist
.col--status .col--status, .col--correction
width: rem(150px) width: rem(150px)
.col--submit-at .col--submit-at
@@ -128,7 +128,7 @@ $highlight-button-color = #F6DF45
border-left: 1px solid $table-border-color border-left: 1px solid $table-border-color
+mobile() +mobile()
.col--submit-at .col--submit-at, .col--correction
display: none display: none
+tablet() +tablet()
@@ -5,6 +5,7 @@
{% block content %} {% block content %}
{{ set(UiContext, 'tdoc', tdoc) }} {{ set(UiContext, 'tdoc', tdoc) }}
{{ set(UiContext, 'tsdoc', tsdoc) }} {{ set(UiContext, 'tsdoc', tsdoc) }}
{% set hasCorrection = correction and Object.keys(correction).length %}
<div class="row" data-sticky-parent> <div class="row" data-sticky-parent>
<div class="medium-9 columns"> <div class="medium-9 columns">
<div class="section"> <div class="section">
@@ -27,7 +28,11 @@
<tr> <tr>
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %} {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
<th class="col--status record-status--border">{{ _('Status') }}</th> <th class="col--status record-status--border">{{ _('Status') }}</th>
<th class="col--submit-at">{{ _('Last Submit At') }}</th> {% if hasCorrection %}
<th class="col--correction">{{ _('Correction') }}</th>
{% else %}
<th class="col--submit-at">{{ _('Last Submit At') }}</th>
{% endif %}
{% endif %} {% endif %}
{% if showScore %} {% if showScore %}
<th class="col--score">{{ _('Score') }}</th> <th class="col--score">{{ _('Score') }}</th>
@@ -47,10 +52,21 @@
{% else %} {% else %}
<td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td> <td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td>
{% endif %} {% endif %}
<td class="col--submit-at">{{ datetimeSpan(rdoc._id)|safe }}</td> {% if not hasCorrection %}
<td class="col--submit-at">{{ datetimeSpan(rdoc._id)|safe }}</td>
{% endif %}
{% else %} {% else %}
<td class="col--status record-status--border">{{ _('No Submissions') }}</td> <td class="col--status record-status--border">{{ _('No Submissions') }}</td>
<td class="col--submit-at">-</td> {% if not hasCorrection %}
<td class="col--submit-at">-</td>
{% endif %}
{% endif %}
{% if hasCorrection %}
{% if correction[pid] %}
{{ record.render_status_td(rdict[correction[pid].rid], class="col--correction") }}
{% else %}
<td class="col--correction">-</td>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if showScore %} {% if showScore %}
@@ -14,10 +14,16 @@
<p>{{ _('No submission language available for this problem.') }}</p> <p>{{ _('No submission language available for this problem.') }}</p>
</blockquote> </blockquote>
{% endif %} {% endif %}
{% if tdoc and tdoc.docType == model.document.TYPE_CONTEST and model.contest.isDone(tdoc) %} {% if tdoc and tdoc.docType == model.document.TYPE_CONTEST %}
{% if mode == 'view' %}
<blockquote class="note"> <blockquote class="note">
<p>{{ _('You cannot submit for this problem because the contest is ended. You can click "Open in Problem Set" to view this problem in normal mode.') }}</p> <p>{{ _('You cannot submit for this problem because the contest is ended. You can click "Open in Problem Set" to view this problem in normal mode.') }}</p>
</blockquote> </blockquote>
{% elif mode == 'correction' %}
<blockquote class="note">
<p>{{ _('The contest is ended. New submissions will be treated as correction submissions and will not be counted in the contest.') }}</p>
</blockquote>
{% endif %}
{% endif %} {% endif %}
{% if not renderredContent %} {% if not renderredContent %}
{% set preferredLang = handler.request.query.lang or handler.user.viewLang or handler.session.viewLang %} {% set preferredLang = handler.request.query.lang or handler.user.viewLang or handler.session.viewLang %}
@@ -7,7 +7,7 @@
{% endif %} {% endif %}
<div> <div>
<ol class="menu"> <ol class="menu">
{% if model.contest.isOngoing(tdoc, tsdoc) %} {% if mode == 'contest' or mode == 'correction' %}
{% if page_name == 'contest_detail_problem' %} {% if page_name == 'contest_detail_problem' %}
<li class="menu__item scratchpad--hide nojs--hide"><a class="menu__link highlight" name="problem-sidebar__open-scratchpad" href="javascript:;" data-global-hotkey="alt+e"> <li class="menu__item scratchpad--hide nojs--hide"><a class="menu__link highlight" name="problem-sidebar__open-scratchpad" href="javascript:;" data-global-hotkey="alt+e">
<span class="icon icon-enlarge"></span> {{ _('Open Scratchpad') }} (Alt+E) <span class="icon icon-enlarge"></span> {{ _('Open Scratchpad') }} (Alt+E)
@@ -20,10 +20,11 @@
<span class="icon icon-flag"></span> {{ _('View Problem') }} <span class="icon icon-flag"></span> {{ _('View Problem') }}
</a></li> </a></li>
{% endif %} {% endif %}
<li class="menu__item scratchpad--hide"><a class="menu__link{% if page_name == 'contest_detail_problem_submit' %} active{% endif %}" href="{{ url('problem_submit', pid=pdoc.pid|default(pdoc.docId), query={tid:tdoc.docId}) }}"> <li class="menu__item scratchpad--hide"><a class="menu__link{% if page_name == 'contest_detail_problem_submit' %} active{% endif %}" href="{{ url('problem_submit', pid=pdoc.pid|default(pdoc.docId), query={tid:tdoc.docId} if mode=='contest' else {}) }}">
<span class="icon icon-send"></span> {{ _('Submit') }} <span class="icon icon-send"></span> {{ _('Submit') }}
</a></li> </a></li>
{% elif model.contest.isDone(tdoc) %} {% endif %}
{% if mode != 'none' and model.contest.isDone(tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ url('problem_detail', pid=pdoc.pid|default(pdoc.docId)) }}"> <li class="menu__item"><a class="menu__link" href="{{ url('problem_detail', pid=pdoc.pid|default(pdoc.docId)) }}">
<span class="icon icon-send"></span> {{ _('Open in Problem Set') }} <span class="icon icon-send"></span> {{ _('Open in Problem Set') }}
</a></li> </a></li>
@@ -28,7 +28,7 @@
<li class="menu__seperator"></li> <li class="menu__seperator"></li>
{% endif %} {% endif %}
{% if page_name == 'problem_detail' %} {% if page_name == 'problem_detail' %}
{% if handler.user.hasPerm(perm.PERM_SUBMIT_PROBLEM) %} {% if handler.user.hasPerm(perm.PERM_SUBMIT_PROBLEM) and handler.ctx.setting.get('ui-default.enableScratchpad') %}
<li class="menu__item scratchpad--hide nojs--hide"><a class="menu__link highlight" name="problem-sidebar__open-scratchpad" href="javascript:;" data-global-hotkey="alt+e"> <li class="menu__item scratchpad--hide nojs--hide"><a class="menu__link highlight" name="problem-sidebar__open-scratchpad" href="javascript:;" data-global-hotkey="alt+e">
<span class="icon icon-enlarge"></span> {{ _('Open Scratchpad') }} (Alt+E) <span class="icon icon-enlarge"></span> {{ _('Open Scratchpad') }} (Alt+E)
</a></li> </a></li>
@@ -9,13 +9,15 @@
pdoc: pdoc pdoc: pdoc
}) }} }) }}
{% if tdoc %} {% if tdoc %}
{{ set(UiContext, 'tdoc', tdoc) }}
{{ set(UiContext, 'tsdoc', tsdoc) }}
{% endif %}
{% if tdoc and mode == 'contest' %}
{{ set(UiContext, 'canViewRecord', model.contest.canShowSelfRecord.call(handler, tdoc)) }} {{ set(UiContext, 'canViewRecord', model.contest.canShowSelfRecord.call(handler, tdoc)) }}
{{ set(UiContext, 'postSubmitUrl', url('problem_submit', pid=pdoc.docId, query={tid:tdoc.docId})) }} {{ set(UiContext, 'postSubmitUrl', url('problem_submit', pid=pdoc.docId, query={tid:tdoc.docId})) }}
{{ set(UiContext, 'getSubmissionsUrl', url('record_main', query={fullStatus:'true', pid:pdoc.docId, tid:tdoc.docId})) }} {{ set(UiContext, 'getSubmissionsUrl', url('record_main', query={fullStatus:'true', pid:pdoc.docId, tid:tdoc.docId})) }}
{{ set(UiContext, 'getRecordDetailUrl', url('record_detail', rid='{rid}', query={tid:tdoc.docId})) }} {{ set(UiContext, 'getRecordDetailUrl', url('record_detail', rid='{rid}', query={tid:tdoc.docId})) }}
{{ set(UiContext, 'pretestConnUrl', "record-conn?pretest=1&uidOrName=" + handler.user._id + "&pid=" + pdoc.docId + "&domainId=" + handler.args.domainId + "&tid=" + tdoc.docId) }} {{ set(UiContext, 'pretestConnUrl', "record-conn?pretest=1&uidOrName=" + handler.user._id + "&pid=" + pdoc.docId + "&domainId=" + handler.args.domainId + "&tid=" + tdoc.docId) }}
{{ set(UiContext, 'tdoc', tdoc) }}
{{ set(UiContext, 'tsdoc', tsdoc) }}
{% else %} {% else %}
{{ set(UiContext, 'canViewRecord', true) }} {{ set(UiContext, 'canViewRecord', true) }}
{{ set(UiContext, 'postSubmitUrl', url('problem_submit', pid=pdoc.docId)) }} {{ set(UiContext, 'postSubmitUrl', url('problem_submit', pid=pdoc.docId)) }}