Переглянути джерело

0905: UI update, contest update, certs update

ryp 1 місяць тому
батько
коміт
8014a974bc

BIN
app/controllers/.groups_manage.php.swp


BIN
app/controllers/.user_info.php.swp


+ 3 - 1
app/controllers/contest_inside.php

@@ -10,9 +10,11 @@
 		if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
 			header("Location: /contest/{$contest['id']}/register");
 			die();
+
 		} elseif ($contest['cur_progress'] == CONTEST_IN_PROGRESS) {
 			if ($myUser == null || !hasRegistered(Auth::user(), $contest)) {
-				becomeMsgPage("<h1>比赛正在进行中</h1><p>很遗憾,您尚未报名。比赛结束后再来看吧~</p>");
+				header("Location: /contest/{$contest['id']}/register");
+				die();
 			}
 		}
 	}

+ 2 - 1
app/controllers/contest_registration.php

@@ -7,7 +7,8 @@
 	
 	if ($myUser == null) {
 		redirectToLogin();
-	} elseif (hasContestPermission($myUser, $contest) || hasRegistered($myUser, $contest) || $contest['cur_progress'] != CONTEST_NOT_STARTED) {
+	} elseif (hasContestPermission($myUser, $contest) || hasRegistered($myUser, $contest) || 
+		($contest['cur_progress'] != CONTEST_NOT_STARTED && $contest['cur_progress'] != CONTEST_IN_PROGRESS)) {
 		redirectTo('/contests');
 	}
 	

+ 5 - 0
app/controllers/contests.php

@@ -25,6 +25,11 @@ EOD;
 			}
 		} elseif ($contest['cur_progress'] == CONTEST_IN_PROGRESS) {
 			$contest_name_link .= '<sup><a style="color:blue" href="/contest/'.$contest['id'].'">'.UOJLocale::get('contests::in progress').'</a></sup>';
+			if ($myUser != null && hasRegistered($myUser, $contest)) {
+				$contest_name_link .= '<sup><a style="color:green">'.UOJLocale::get('contests::registered').'</a></sup>';
+			} else {
+				$contest_name_link .= '<sup><a style="color:red" href="/contest/'.$contest['id'].'/register">'.UOJLocale::get('contests::register').'</a></sup>';
+			}
 		} elseif ($contest['cur_progress'] == CONTEST_PENDING_FINAL_TEST) {
 			$contest_name_link .= '<sup><a style="color:blue" href="/contest/'.$contest['id'].'">'.UOJLocale::get('contests::pending final test').'</a></sup>';
 		} elseif ($contest['cur_progress'] == CONTEST_TESTING) {

+ 31 - 1
app/controllers/group_manage.php

@@ -75,7 +75,35 @@
 		}
 	};
 	$member_form->runAtServer();
-		
+
+	$assign_certs_form = new UOJForm('group_cert_form');
+	$color_options = array('red' => '红色', 'blue' => '蓝色', 'green' => '绿色', 'purple' => '紫色', 'pink' => '粉色', 'olive' => '屎黄色', 'deepblue' => '深蓝');
+	$assign_certs_form->addSelect('color-type', $color_options, '勋章颜色', '');
+	$assign_certs_form->addInput('certs_name', 'text', '称号', '',
+		function ($text) {
+			if (strpos($text, '#') != false || strpos($text, ',') != false)
+				return '不能含有井号或逗号';
+			if (mb_strlen($text) > 8) return '称号过长' . $text;
+			return '';
+		}, null
+	);
+
+	$assign_certs_form->handle = function() {
+		global $group;	
+		$text = $_POST['certs_name'];
+		$color = $_POST['color-type'];
+
+		foreach (queryGroupMembers($group) as $i => $user) {
+			$username = $user['username'];
+			$newcerts = $user['certslist'];
+			if ($user['certslist'] != '') $newcerts = $newcerts . ',' . $color . '#' . $text;
+			else $newcerts = $color . '#' . $text;
+			DB::update("update user_info set certslist = '{$newcerts}' where username = '{$username}'");
+			if ($user['certs'] == '') DB::update("update user_info set certs = '{$newcerts}' where username = '{$username}'");
+		}
+	};
+	$assign_certs_form->runAtServer();
+
 	$user_form = new UOJForm('user');
 	$user_form->addInput('usernamep', 'text', '用户名', '',
 		function ($username) {
@@ -135,6 +163,8 @@
 			<div>
 				<h4>更改信息</h4>
 				<?php $nick_form->printHTML() ?>
+				<h4>给成员添加勋章</h4>
+				<?php $assign_certs_form->printHTML() ?>
 			</div>
 		<?php elseif ($cur_tab === 'members'): ?>
 			<div>

+ 145 - 70
app/controllers/index.php

@@ -1,15 +1,39 @@
 <?php
 $blogs = DB::selectAll("select blogs.id, title, poster, post_time from important_blogs, blogs where is_hidden = 0 and important_blogs.blog_id = blogs.id order by level desc, important_blogs.blog_id desc limit 8");
+$activateusers = DB::selectAll("select submitter, count(*) as cnt from submissions where datediff(now(), submit_time) <= 30 group by submitter order by cnt desc limit 12;");
 $topusers = DB::selectAll("select username, rating, qq, motto from user_info order by rating desc, username asc limit 12;");
 $topblogs = DB::selectAll("select id from blogs order by zan desc limit 10;");
 $songs_data = htmlspecialchars(json_encode(DB::selectAll("select * from songs;")), ENT_QUOTES, 'UTF-8');
 
-$submdata = DB::selectAll("select yearweek(submit_time, 1) as weeknr, date(submit_time - interval weekday(submit_time) day) as weekst, count(*) as cnt from submissions group by weeknr, weekst order by weekst;");
-$submjson = htmlspecialchars(json_encode($submdata), ENT_QUOTES, 'UTF-8');
-requireLib('flot');
+// 服务器数据看板
+$today_submit_num = DB::selectCount("select count(*) from submissions where date(submit_time) = date(now())");
+$today_ac_num = DB::selectCount("select count(*) from submissions where date(submit_time) = date(now()) and score = 100");
+$month_submit_num = DB::selectCount("select count(*) from submissions where year(submit_time) = year(now()) and month(submit_time) = month(now())");
+$month_ac_num = DB::selectCount("select count(*) from submissions where year(submit_time) = year(now()) and month(submit_time) = month(now()) and score = 100");
+$total_submit_num = DB::selectCount("select count(*) from submissions");
+$total_ac_num = DB::selectCount("select count(*) from submissions where score = 100");
+$total_problem_num = DB::selectCount("select count(*) from problems where is_hidden = 0");
+$total_user_num = DB::selectCount("select count(*) from user_info");
+
+// 最近比赛:未结束的按开始时间升序取,不足 5 场时补最近结束的
+$recent_contests = array();
+$result = DB::selectAll("select * from contests where status != 'finished' order by start_time asc, id asc limit 5");
+foreach ($result as $contest) {
+	genMoreContestInfo($contest);
+	$recent_contests[] = $contest;
+}
+if (count($recent_contests) < 5) {
+	$need = 5 - count($recent_contests);
+	$result = DB::selectAll("select * from contests where status = 'finished' order by start_time desc, id desc limit $need");
+	foreach ($result as $contest) {
+		genMoreContestInfo($contest);
+		$recent_contests[] = $contest;
+	}
+}
 ?>
 <?php echoUOJPageHeader(UOJConfig::$data['profile']['oj-name-short']) ?>
 <div id="songs-data" data-songs='<?php echo $songs_data; ?>'></div>
+
 <div class="row shadow my-4 wyoj-topcard">
 	<div class="card-body">
 		<div class="row">
@@ -186,31 +210,123 @@ requireLib('flot');
 	</div>
 </div>
 
-<!--
-<div class="row shadow my-4 wyoj-topcard" id="recommender-zone">
-	<div class="col-12 p-4">
-		<h3>服务器信息</h3>
-		<div id="submissions-data" data-submissions_data='<?= $submjson ?>'></div>
-		<div style="position: relative; width: 100%; padding-top: 20%;">
-			<div id="submissions-plot" class="w-100 h-100" style="position: absolute; top: 0; left: 0;"></div>
-		</div>
-		<div style="position: relative; width: 100%; padding-top: 20%;">
-			<div id="storage-plot" class="w-100 h-100" style="position: absolute; top: 0; left: 0;"></div>
+<div class="row shadow my-4 wyoj-topcard">
+	<div class="col-lg-6 p-4">
+		<h3>服务器数据</h3>
+		<div class="row">
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-flash"></span></div>
+					<div class="stat-num"><?= $today_submit_num ?></div>
+					<div class="stat-label">今日提交</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-ok"></span></div>
+					<div class="stat-num"><?= $today_ac_num ?></div>
+					<div class="stat-label">今日通过</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-send"></span></div>
+					<div class="stat-num"><?= $month_submit_num ?></div>
+					<div class="stat-label">本月提交</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-check"></span></div>
+					<div class="stat-num"><?= $month_ac_num ?></div>
+					<div class="stat-label">本月通过</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-tasks"></span></div>
+					<div class="stat-num"><?= $total_submit_num ?></div>
+					<div class="stat-label">总提交</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-star"></span></div>
+					<div class="stat-num"><?= $total_ac_num ?></div>
+					<div class="stat-label">总通过</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-list-alt"></span></div>
+					<div class="stat-num"><?= $total_problem_num ?></div>
+					<div class="stat-label">题目总数</div>
+				</div>
+			</div>
+			<div class="col-6 col-lg-3 mb-3">
+				<div class="card h-100 p-3 text-center stat-card">
+					<div class="stat-icon"><span class="glyphicon glyphicon-user"></span></div>
+					<div class="stat-num"><?= $total_user_num ?></div>
+					<div class="stat-label">注册用户</div>
+				</div>
+			</div>
 		</div>
 	</div>
+	<div class="col-lg-6 p-4">
+		<h3>最近比赛</h3>
+		<?php if (empty($recent_contests)): ?>
+		<p class="text-muted">暂无比赛</p>
+		<?php else: ?>
+		<table class="table contest-table">
+			<tbody>
+			<?php foreach ($recent_contests as $contest): ?>
+				<tr>
+					<td class="contest-name">
+						<a href="<?= HTML::url('/contest/' . $contest['id']) ?>"><?= $contest['name'] ?></a>
+						<?php if ($contest['cur_progress'] == CONTEST_NOT_STARTED): ?>
+						<span class="badge badge-info">即将开始</span>
+						<span class="contest-countdown" id="recent-contest-countdown-<?= $contest['id'] ?>"></span>
+						<?php elseif ($contest['cur_progress'] == CONTEST_IN_PROGRESS): ?>
+						<span class="badge badge-success">进行中</span>
+						<?php elseif ($contest['cur_progress'] == CONTEST_PENDING_FINAL_TEST): ?>
+						<span class="badge badge-warning">等待出分</span>
+						<?php elseif ($contest['cur_progress'] == CONTEST_TESTING): ?>
+						<span class="badge badge-warning">最终评测中</span>
+						<?php else: ?>
+						<span class="badge badge-secondary">已结束</span>
+						<?php endif ?>
+					</td>
+					<td class="contest-info"><?= $contest['start_time_str'] ?></td>
+					<td class="contest-info"><?= round($contest['last_min'] / 60, 2) ?>h</td>
+				</tr>
+			<?php endforeach ?>
+			</tbody>
+		</table>
+		<?php endif ?>
+	</div>
 </div>
--->
-
+<script type="text/javascript">
+<?php foreach ($recent_contests as $contest): ?>
+<?php if ($contest['cur_progress'] == CONTEST_NOT_STARTED): ?>
+$('#recent-contest-countdown-<?= $contest['id'] ?>').countdown(<?= $contest['start_time']->getTimestamp() - UOJTime::$time_now->getTimestamp() ?>);
+<?php endif ?>
+<?php endforeach ?>
+</script>
 <div class="row shadow my-4 wyoj-topcard">
 	<div class="col-12 p-4">
-		<h3>博客广场</h3>
+		<h3>活跃排行榜</h3>
 	</div>
-
-	<?php 
-		foreach ($topblogs as $id) echo echoBlogCard(queryBlog($id['id']));
-	?>
+	<?php foreach($activateusers as $username): ?>
+		<?php $user = queryUser($username['submitter']); ?>
+		<div class="col-md-3 mb-4">
+			<div class="card h-100 p-3 text-center">
+				<img class="uoj-avatar mx-auto mb-2" src=<?= "https://q.qlogo.cn/g?b=qq&nk=" . $user['qq'] . "&s=100" ?> alt="...">
+				<span class="mb-2"><?= getUserLink($user['username']) ?></span>
+				<span class="text-center text-bottom"><?= $user['motto'] ?></span>
+			</div>
+		</div>
+	<?php endforeach ?>
 </div>
-
 <div class="row shadow my-4 wyoj-topcard">
 	<div class="col-12 p-4">
 		<h3>比赛排行榜</h3>
@@ -225,55 +341,14 @@ requireLib('flot');
 		</div>
 	<?php endforeach ?>
 </div>
-<script>
-(function() {
-    const submissions_data_raw = JSON.parse(document.getElementById('submissions-data').dataset.submissions_data);
-    const submissions_data = submissions_data_raw.map(x => [Number(new Date(x['weekst'])), Number(x['cnt'])]);
-    console.log(submissions_data);
-
-    var dataset = [
-        {
-            label: "提交数",
-            data: submissions_data,
-            yaxis: 1,
-            lines: { show: true },
-            points: { show: true }
-        },
-    ];
-
-    var options1 = {
-        yaxes: [
-            {
-                position: "left",
-                min: 0,
-                max: 10000,
-                axisLabelUseCanvas: true,
-                axisLabelFontSizePixels: 12
-            },
-            {
-                position: "right",
-                min: 0,
-                max: 10000,
-                tickDecimals: 0,
-                axisLabelUseCanvas: true,
-                axisLabelFontSizePixels: 12
-            }
-	],
-        xais: {
-            mode: 'time',
-            timeformat: "%Y-%m-%d",
-	    minTickSize: [1, 'day']
-        },
-        grid: {
-            hoverable: true,
-        },
-        legend: {
-            position: "ne"
-        }
-    };
-    $.plot($("#submissions-plot"), dataset, options1);
-}) ();
-</script>
 
+<div class="row shadow my-4 wyoj-topcard">
+	<div class="col-12 p-4">
+		<h3>博客广场</h3>
+	</div>
 
+	<?php 
+		foreach ($topblogs as $id) echo echoBlogCard(queryBlog($id['id']));
+	?>
+</div>
 <?php echoUOJPageFooter() ?>

+ 70 - 13
app/controllers/problem.php

@@ -212,23 +212,12 @@ EOD
 	$time_limit = $limit['time_limit'];
 	$memory_limit = $limit['memory_limit'];
 ?>
-<div class="row d-flex justify-content-center">
-	<span class="badge badge-secondary mr-1">时间限制:<?=$time_limit!=null?"$time_limit s":"N/A"?></span>
-	<span class="badge badge-secondary mr-1">空间限制:<?=$memory_limit!=null?"$memory_limit MB":"N/A"?></span>
-	<span class="badge badge-secondary mr-1">控制组: <?php echo $problem['ctrl'] == '' ? '默认' : $problem['ctrl'] ?></span>
-	<span class="badge badge-secondary mr-1">压缩包大小: <?php echo printSize(queryProblemSize($problem['id'])) ?></span>
-</div>
-<div class="float-right">
-	<?= getClickZanBlock('P', $problem['id'], $problem['zan']) ?>
-</div>
-
 <?php if ($contest): ?>
 <div class="page-header row">
 	<h1 class="col-md-3 text-left"><small><?= $contest['name'] ?></small></h1>
 	<h1 class="col-md-7 text-center"><?= $problem_letter ?>. <?= $problem['title'] ?></h1>
 	<div class="col-md-2 text-right" id="contest-countdown"></div>
 </div>
-<a role="button" class="btn btn-info float-right" href="/contest/<?= $contest['id'] ?>/problem/<?= $problem['id'] ?>/statistics"><span class="glyphicon glyphicon-stats"></span> <?= UOJLocale::get('problems::statistics') ?></a>
 <?php if ($contest['cur_progress'] <= CONTEST_IN_PROGRESS): ?>
 <script type="text/javascript">
 checkContestNotice(<?= $contest['id'] ?>, '<?= UOJTime::$time_now_str ?>');
@@ -237,9 +226,21 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
 <?php endif ?>
 <?php else: ?>
 <h1 class="page-header text-center">#<?= $problem['id']?>. <?= $problem['title'] ?></h1>
-<a role="button" class="btn btn-info float-right" href="/problem/<?= $problem['id'] ?>/statistics"><span class="glyphicon glyphicon-stats"></span> <?= UOJLocale::get('problems::statistics') ?></a>
 <?php endif ?>
 
+<div class="row">
+	<div class="col-lg-9">
+		<div class="d-flex justify-content-between align-items-center problem-title-bar">
+			<div>
+				<?= getClickZanBlock('P', $problem['id'], $problem['zan']) ?>
+			</div>
+			<?php if ($contest): ?>
+			<a role="button" class="btn btn-info btn-sm" href="/contest/<?= $contest['id'] ?>/problem/<?= $problem['id'] ?>/statistics"><span class="glyphicon glyphicon-stats"></span> <?= UOJLocale::get('problems::statistics') ?></a>
+			<?php else: ?>
+			<a role="button" class="btn btn-info btn-sm" href="/problem/<?= $problem['id'] ?>/statistics"><span class="glyphicon glyphicon-stats"></span> <?= UOJLocale::get('problems::statistics') ?></a>
+			<?php endif ?>
+		</div>
+
 <ul class="nav nav-tabs" role="tablist">
 	<li class="nav-item"><a class="nav-link active" href="#tab-statement" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-book"></span> <?= UOJLocale::get('problems::statement') ?></a></li>
 	<li class="nav-item"><a class="nav-link" href="#tab-submit-answer" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-upload"></span> <?= UOJLocale::get('problems::submit') ?></a></li>
@@ -255,7 +256,7 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
 </ul>
 <div class="tab-content">
 	<div class="tab-pane active" id="tab-statement">
-		<article class="top-buffer-md"><?= $problem_content['statement'] ?></article>
+		<article class="problem-statement top-buffer-md"><?= $problem_content['statement'] ?></article>
 	</div>
 	<div class="tab-pane" id="tab-submit-answer">
 		<div class="top-buffer-sm"></div>
@@ -272,5 +273,61 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
 		<?php $custom_test_form->printHTML(); ?>
 	</div>
 	<?php endif ?>
+</div>
+	</div>
+	<div class="col-lg-3">
+		<div class="problem-sidebar">
+			<div class="card problem-meta-card">
+				<div class="card-header"><span class="glyphicon glyphicon-info-sign"></span> 题目信息</div>
+				<div class="card-body">
+					<ul class="list-unstyled mb-0">
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">难度</span>
+							<span class="badge badge-danger">UKE</span>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">控制组</span>
+							<?php if ($problem['ctrl'] == ''): ?>
+							<span>默认</span>
+							<?php else: ?>
+							<a href="<?= HTML::url('/user/profile/' . $problem['ctrl']) ?>"><?= queryUser($problem['ctrl'])['email'] ?></a>
+							<?php endif ?>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">时间限制</span>
+							<span><?=$time_limit!=null?"$time_limit s":"N/A"?></span>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">空间限制</span>
+							<span><?=$memory_limit!=null?"$memory_limit MB":"N/A"?></span>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">数据大小</span>
+							<span><?php echo printSize(queryProblemSize($problem['id'])) ?></span>
+						</li>
+					</ul>
+				</div>
+			</div>
+			<div class="card problem-meta-card">
+				<div class="card-header"><span class="glyphicon glyphicon-stats"></span> 提交统计</div>
+				<div class="card-body">
+					<ul class="list-unstyled mb-0">
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">提交数</span>
+							<span><?= isset($problem['submit_num']) ? $problem['submit_num'] : 'N/A' ?></span>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">通过数</span>
+							<span><?= isset($problem['ac_num']) ? $problem['ac_num'] : 'N/A' ?></span>
+						</li>
+						<li class="d-flex justify-content-between">
+							<span class="meta-label">通过率</span>
+							<span><?= isset($problem['submit_num']) && $problem['submit_num'] > 0 ? round($problem['ac_num'] / $problem['submit_num'] * 100, 1) . '%' : 'N/A' ?></span>
+						</li>
+					</ul>
+				</div>
+			</div>
+		</div>
+	</div>
 </div>
 <?php echoUOJPageFooter() ?>

+ 6 - 3
app/controllers/problem_data_manage.php

@@ -745,9 +745,12 @@ EOD
     							<label for="use_builtin_checker" class="col-sm-5 control-label">比对函数</label>
     							<div class="col-sm-7">
 								<select class="form-control" id="use_builtin_checker" name="use_builtin_checker">
-  									<option value="ncmp">单行整数序列</option>
-  									<option value="wcmp">单行字符串序列</option>
-  									<option value="fcmp">多行数据(不忽略行末空格,但忽略文末回车)</option>
+  									<option value="ncmp">单行整数序列(ncmp)</option>
+  									<option value="wcmp">单行字符串序列(wcmp)</option>
+									<option value="fcmp">多行数据(不忽略行末空格,但忽略文末回车)(fcmp)</option>
+									<option value="rcmp4">多行浮点数,精度 1e-4(不忽略行末空格,但忽略文末回车)(rcmp4)</option>
+									<option value="rcmp6">多行浮点数,精度 1e-6(不忽略行末空格,但忽略文末回车)(rcmp6)</option>
+									<option value="rcmp9">多行浮点数,精度 1e-9(不忽略行末空格,但忽略文末回车)(rcmp9)</option>
 									<option value="ownchk">自定义校验器</option>
 								</select>
       								<!--<input type="hidden" class="form-control" id="use_builtin_checker" name="use_builtin_checker" placeholder="比对函数">-->

+ 2 - 0
app/controllers/problem_set.php

@@ -78,6 +78,7 @@
 				echo ' <span class="text-danger">[隐藏]</span> ';	
 			}
 			echo '<a href="/problem/', $problem['id'], '">', $problem['title'], '</a>';
+			echo '<td><a href="/user/profile/', $problem['ctrl'], '">', queryUser($problem['ctrl'])['email'], ' (', $problem['ctrl'], ')</a></td>';
 			if (isset($_COOKIE['show_tags_mode'])) {
 				foreach (queryProblemTags($problem['id']) as $tag) {
 					echo '<a class="uoj-problem-tag">', '<span class="badge badge-pill badge-secondary">', HTML::escape($tag), '</span>', '</a>';
@@ -192,6 +193,7 @@ EOD;
 	$header = '<tr>';
 	$header .= '<th class="text-center" style="width:5em;">ID</th>';
 	$header .= '<th>'.UOJLocale::get('problems::problem').'</th>';
+	$header .= '<th>组</th>';
 	if (isset($_COOKIE['show_submit_mode'])) {
 		$header .= '<th class="text-center" style="width:5em;">'.UOJLocale::get('problems::ac').'</th>';
 		$header .= '<th class="text-center" style="width:5em;">'.UOJLocale::get('problems::submit').'</th>';

+ 3 - 1
app/controllers/super_manage.php

@@ -45,7 +45,9 @@
 
 	$assign_certs_form = new UOJForm('assign_certs_form');
 
-	$color_options = array('red' => '红色', 'blue' => '蓝色', 'green' => '绿色', 'purple' => '紫色', 'pink' => '粉色', 'olive' => '屎黄色', 'deepblue' => '深蓝');
+	$color_options = array('red' => '红色', 'blue' => '蓝色', 'green' => '绿色', 'purple' => '紫色', 'pink' => '粉色', 
+				'olive' => '屎黄色', 'deepblue' => '深蓝',
+				'pro' => 'Pro', 'max' => 'Max', 'ultra' => 'Ultra');
 	$assign_certs_form->addSelect('color-type', $color_options, '勋章颜色', '');
 	$assign_certs_form->addInput('certs_user', 'text', '用户名', '',
 		function ($username) {

+ 1 - 1
app/controllers/user_info.php

@@ -121,7 +121,7 @@
 var rating_data = [[
 <?php
 	$user_rating_min = $user_rating_max = 1500;
-	$result = DB::query("select contest_id, `rank`, user_rating from contests_registrants where username = '{$user['username']}' and has_participated = 1 order by contest_id");
+	$result = DB::query("select contest_id, `rank`, user_rating from contests_registrants join contests on id = contest_id where username = '{$user['username']}' and has_participated = 1 order by contests.start_time");
 	$is_first_row = true;
 	$last_rating = 1500;
 	while ($row = DB::fetch($result)) {

BIN
app/libs/.uoj-contest-lib.php.swp


+ 10 - 4
app/libs/uoj-html-lib.php

@@ -426,11 +426,14 @@ function echoShortList($header, $rows, $print_row) {
 }
 
 function echoBelongGroupList($user) {
-	echoShortList('<tr><th>组名</th><th>题目</th></tr>', queryUserExtgroups($user), function($grp) {
-		$link = getUserLink(queryUser($grp)['username']);
+	echoShortList('<tr><th>组名</th><th>显示名</th><th>题目</th></tr>', queryUserExtgroups($user), function($grp) {
+		$qp = queryUser($grp);
+		$link = getUserLink($qp['username']);
+		$name = $qp['email'];
 		echo <<<HTML
 <tr>
 	<td>$link</td>
+	<td>$name</td>
 	<td><a href="/problems/group/{$grp}">题目</a></td>
 </tr>
 HTML;
@@ -487,11 +490,14 @@ HTML;
 		});
 }
 function echoControlGroupList($user) {
-	echoShortList('<tr><th>组名</th><th>管理</th></tr>', queryUserCtrlgroups($user), function($grp) {
-		$link = getUserLink(queryUser($grp)['username']);
+	echoShortList('<tr><th>组名</th><th>显示名</th><th>管理</th></tr>', queryUserCtrlgroups($user), function($grp) {
+		$qp = queryUser($grp);
+		$link = getUserLink($qp['username']);
+		$name = $qp['email'];
 		echo <<<HTML
 <tr>
 	<td>$link</td>
+	<td>$name</td>
 	<td><a href="/group-manage/{$grp}">管理</a></td>
 </tr>
 HTML;

+ 1 - 1
app/libs/uoj-query-lib.php

@@ -100,7 +100,7 @@ function queryGroupControllers($grp) {
 
 function queryGroupMembers($grp) {
 	$grpname = $grp['username'];
-	return DB::selectAll("select username from user_info where find_in_set('$grpname', extgroups)");
+	return DB::selectAll("select username, certslist, certs from user_info where find_in_set('$grpname', extgroups)");
 }
 
 function queryGroupProblems($grp) {

+ 159 - 1
css/uoj-theme.css

@@ -451,16 +451,174 @@ pre {
 .uoj-certs-deepblue { background-color: #0000cd; }
 .uoj-certs-olive { background-color: #808000; }
 
+@keyframes sideBlue {
+	0% { background-position: 0% 50%; } 
+	50% { background-position: 100% 50%; } 
+	100% { background-position: 0% 50%; } 
+}
+
+.uoj-certs-pro { background: linear-gradient(90deg, #8a00ff, #ff0000); }
+.uoj-certs-max { background: linear-gradient(90deg, #000000, #ff0000); }
+.uoj-certs-ultra {
+	background: linear-gradient(90deg, #9589fe, #0484e2, #980ecc);
+	background-size: 300% 100%;
+	animation: sideBlue 4s ease-in-out infinite;
+}
+
+.uoj-certs-promax {
+	background: linear-gradient(90deg, #000000, #ff0000);
+	background-size: 300% 100%;
+	animation: sideBlue 4s ease-in-out infinite;
+
+}
+
 .table {
 	margin-bottom: 0;
 }
 
 .wyoj-topcard {
 	background: #f8f9fa;
-	border-raduis: 12px;
+	border-radius: 12px;
 	overflow: hidden;
 }
 
+/* ===== 首页服务器数据看板 ===== */
+.stat-card {
+	border: 1px solid #e3e3e3;
+	border-radius: 6px;
+	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+	transition: box-shadow 0.2s ease, transform 0.1s ease;
+}
+
+.stat-card:hover {
+	box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
+	transform: translateY(-2px);
+}
+
+.stat-icon {
+	font-size: 1.3rem;
+	color: #6c757d;
+	margin-bottom: 4px;
+}
+
+.stat-num {
+	font-size: 1.3rem;
+	font-weight: 800;
+	color: #343a40;
+	line-height: 1.2;
+}
+
+.stat-label {
+	font-size: 0.8rem;
+	color: #6c757d;
+	margin-top: 2px;
+}
+
+/* ===== 首页最近比赛 ===== */
+.contest-table {
+	margin-bottom: 0;
+}
+
+.contest-table td {
+	vertical-align: middle;
+	padding: 12px 12px;
+	border-top: 1px dashed #e9ecef;
+}
+
+.contest-table tbody tr:first-child td {
+	border-top: none;
+}
+
+.contest-table .contest-name {
+	white-space: nowrap;
+}
+
+.contest-table .contest-name a {
+	font-size: 1rem;
+	font-weight: 700;
+	color: #212529;
+}
+
+.contest-table .contest-info {
+	font-size: 0.95rem;
+	color: #343a40;
+	white-space: nowrap;
+}
+
+.contest-table .contest-info .glyphicon {
+	font-size: 0.85rem;
+	margin-right: 2px;
+	color: #495057;
+}
+
+.contest-table .contest-countdown {
+	font-size: 0.95rem;
+	margin-left: 6px;
+	color: #e67e22;
+}
+
+.contest-table .contest-countdown span {
+	font-size: 0.95rem !important;
+}
+
+/* ===== 题目详情页 ===== */
+.problem-title-bar {
+	margin-bottom: 8px;
+}
+
+.problem-statement {
+	background: #ffffff;
+	border: 1px solid #e3e3e3;
+	border-radius: 6px;
+	padding: 24px 28px;
+	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+	line-height: 1.7;
+	overflow-wrap: break-word;
+}
+
+.problem-sidebar {
+	position: sticky;
+	top: 12px;
+}
+
+.problem-meta-card {
+	border: 1px solid #e3e3e3;
+	border-radius: 6px;
+	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+	margin-bottom: 16px;
+}
+
+.problem-meta-card .card-header {
+	background: #f8f9fa;
+	border-bottom: 1px solid #e3e3e3;
+	font-weight: 700;
+	color: #495057;
+	padding: 10px 16px;
+	font-size: 0.95rem;
+}
+
+.problem-meta-card .card-body {
+	padding: 0;
+}
+
+.problem-meta-card .card-body ul li {
+	padding: 9px 16px;
+	border-bottom: 1px dashed #e9ecef;
+	font-size: 0.9rem;
+}
+
+.problem-meta-card .card-body ul li:last-child {
+	border-bottom: none;
+}
+
+.problem-meta-card .meta-label {
+	color: #6c757d;
+}
+
+.problem-meta-card .badge-danger {
+	background-color: #dc3545;
+}
+
 .blog-content.collapsed {
 	max-height: 300px;
 	overflow: hidden;