feat: contest self reviews
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2022-09-19 19:27:57 +08:00
parent 419be8ab49
commit 9c2b2a96ab
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
8 changed files with 101 additions and 11 deletions

View File

@ -333,6 +333,30 @@ LOCK TABLES `contests_submissions` WRITE;
/*!40000 ALTER TABLE `contests_submissions` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `contests_reviews`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `contests_reviews` (
`contest_id` int(11) NOT NULL,
`problem_id` int(11) NOT NULL DEFAULT 0,
`poster` varchar(20) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`contest_id`,`problem_id`,`poster`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `contests_reviews`
--
LOCK TABLES `contests_reviews` WRITE;
/*!40000 ALTER TABLE `contests_reviews` DISABLE KEYS */;
/*!40000 ALTER TABLE `contests_reviews` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `countdowns`
--

View File

@ -44,11 +44,18 @@
'name' => UOJLocale::get('contests::contest standings'),
'url' => "/contest/{$contest['id']}/standings"
),
'after_contest_standings' => array(
);
if ($contest['cur_progress'] > CONTEST_TESTING) {
$tabs_info['after_contest_standings'] = array(
'name' => UOJLocale::get('contests::after contest standings'),
'url' => "/contest/{$contest['id']}/after_contest_standings"
)
);
$tabs_info['self_reviews'] = array(
'name' => UOJLocale::get('contests::contest self reviews'),
'url' => "/contest/{$contest['id']}/self_reviews"
);
}
if (hasContestPermission(Auth::user(), $contest)) {
$tabs_info['backstage'] = array(
@ -392,6 +399,21 @@ EOD;
]);
}
function echoReviews() {
global $contest;
$contest_data = queryContestData($contest, array());
calcStandings($contest, $contest_data, $score, $standings, false, true);
uojIncludeView('contest-standings', [
'contest' => $contest,
'standings' => $standings,
'score' => $score,
'contest_data' => $contest_data,
'show_self_reviews' => true
]);
}
function echoContestCountdown() {
global $contest;
$rest_second = $contest['end_time']->getTimestamp() - UOJTime::$time_now->getTimestamp();
@ -454,7 +476,7 @@ EOD;
<?= getClickZanBlock('C', $contest['id'], $contest['zan']) ?>
</div>
<div class="row">
<?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings'): ?>
<?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings' || $cur_tab == 'self_reviews'): ?>
<div class="col-sm-12">
<?php else: ?>
<div class="col-sm-9">
@ -472,12 +494,14 @@ EOD;
echoStandings(true);
} elseif ($cur_tab == 'backstage') {
echoBackstage();
} elseif ($cur_tab == 'self_reviews') {
echoReviews();
}
?>
</div>
</div>
<?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings'): ?>
<?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings' || $cur_tab == 'self_reviews'): ?>
<div class="col-sm-12">
<hr />
</div>

View File

@ -92,8 +92,8 @@ function queryContestData($contest, $config = array(), $is_after_contest_query =
return ['problems' => $problems, 'data' => $data, 'people' => $people];
}
function calcStandings($contest, $contest_data, &$score, &$standings, $update_contests_submissions = false) {
// score: username, problem_pos => score, penalty, id
function calcStandings($contest, $contest_data, &$score, &$standings, $update_contests_submissions = false, $show_reviews = false) {
// score: username, problem_pos => score, penalty, id, ?review
$score = array();
$n_people = count($contest_data['people']);
$n_problems = count($contest_data['problems']);
@ -107,10 +107,19 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $update_co
$penalty = 0;
}
}
$score[$submission[2]][$submission[3]] = array($submission[4], $penalty, $submission[0]);
if ($show_reviews) {
$review_result = DB::selectFirst("select content from contests_reviews where contest_id = {$contest['id']} and problem_id = {$contest_data['problems'][$submission[3]]} and poster = '{$person[0]}'");
if ($review_result['content']) {
$score[$submission[2]][$submission[3]][] = $review_result['content'];
}
}
}
// standings: rank => score, penalty, [username, realname], virtual_rank
// standings: rank => score, penalty, [username, realname], virtual_rank, ?review
$standings = array();
foreach ($contest_data['people'] as $person) {
$cur = array(0, 0, $person);
@ -124,6 +133,15 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $update_co
}
}
}
if ($show_reviews) {
$review_result = DB::selectFirst("select content from contests_reviews where contest_id = {$contest['id']} and poster = '{$person[0]}'");
if ($review_result['content']) {
$cur[] = $review_result['content'];
}
}
$standings[] = $cur;
}
@ -143,8 +161,10 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $update_co
for ($i = 0; $i < $n_people; $i++) {
if ($i == 0 || !$is_same_rank($standings[$i - 1], $standings[$i])) {
$standings[$i][] = $i + 1;
$standings[$i][] = $standings[$i][3];
$standings[$i][3] = $i + 1;
} else {
$standings[$i][] = $standings[$i][3];
$standings[$i][] = $standings[$i - 1][3];
}
}

View File

@ -26,5 +26,6 @@ return [
'contest pending final test' => 'Pending final test',
'contest final testing' => 'Final testing',
'contest ended' => 'Contest Ended',
'contest registrants' => 'Registrants'
'contest registrants' => 'Registrants',
'contest self reviews' => 'Contest self reviews'
];

View File

@ -26,5 +26,6 @@ return [
'contest pending final test' => '等待评测',
'contest final testing' => '正在测评',
'contest ended' => '比赛已结束',
'contest registrants' => '报名选手列表'
'contest registrants' => '报名选手列表',
'contest self reviews' => '赛后总结'
];

View File

@ -31,6 +31,7 @@ Route::group([
Route::any('/contest/{id}/submissions', '/contest_inside.php?tab=submissions');
Route::any('/contest/{id}/standings', '/contest_inside.php?tab=standings');
Route::any('/contest/{id}/after_contest_standings', '/contest_inside.php?tab=after_contest_standings');
Route::any('/contest/{id}/self_reviews', '/contest_inside.php?tab=self_reviews');
Route::any('/contest/{id}/backstage', '/contest_inside.php?tab=backstage');
Route::any('/contest/{contest_id}/problem/{id}', '/problem.php');
Route::any('/contest/{contest_id}/problem/{id}/statistics', '/problem_statistics.php');

View File

@ -6,6 +6,7 @@
<script type="text/javascript">
standings_version=<?=$contest['extra_config']['standings_version']?>;
show_self_reviews=<?=isset($show_self_reviews) && $show_self_reviews ? 'true' : 'false' ?>;
contest_id=<?=$contest['id']?>;
standings=<?=json_encode($standings)?>;
score=<?=json_encode($score)?>;

View File

@ -1071,6 +1071,7 @@ function showStandings() {
$.map(problems, function(col, idx) {
return '<th style="width:8em;">' + '<a href="/contest/' + contest_id + '/problem/' + col + '">' + String.fromCharCode('A'.charCodeAt(0) + idx) + '</a>' + '</th>';
}).join('') +
(show_self_reviews ? '<th style="width:16em;">赛后总结</th>' : '') +
'</tr>',
function(row) {
var col_tr = '<tr>';
@ -1089,9 +1090,26 @@ function showStandings() {
col_tr += '<div>' + getPenaltyTimeStr(col[1]) + '</div>';
}
}
if (show_self_reviews) {
col_tr += '<div id="review-' + row[2][0] + '-' + i + '"></div>'
+ '<script>'
+ '$(function() {'
+ 'var purify_result = DOMPurify.sanitize(\'' + String(col[3] || '').replace(/'/g, '\\\'').replace(new RegExp('</scr' + 'ipt>', 'gi'), '</scr\' + \'ipt>') + '\', {ALLOWED_TAGS: ["a", "b", "i", "u", "em", "strong", "sub", "sup", "small", "del"], ALLOWED_ATTR: ["href"]});'
+ '$("#review-' + row[2][0] + '-' + i + '")'
+ '.html(purify_result ? \'<div class="mt-3 pt-2 border-top">\' + purify_result + \'</div>\' : \'\'); });'
+ '</scr' + 'ipt>';
}
}
col_tr += '</td>';
}
if (show_self_reviews) {
col_tr += '<td><div id="review-' + row[2][0] + '"></div>'
+ '<script>'
+ '$(function() { $("#review-' + row[2][0] + '")'
+ '.html(DOMPurify.sanitize(\'' + String(row[4] || '').replace(/'/g, '\\\'').replace(new RegExp('</scr' + 'ipt>', 'gi'), '</scr\' + \'ipt>') + '\', {ALLOWED_TAGS: ["a", "b", "i", "u", "em", "strong", "sub", "sup", "small", "del"], ALLOWED_ATTR: ["href"]})); });'
+ '</scr' + 'ipt></td>';
}
col_tr += '</tr>';
return col_tr;
}, {