Skip to content

Commit 3c0d9b2

Browse files
author
CodeJudge
committed
feat: 管理员重新判题
1 parent 373adf2 commit 3c0d9b2

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

backend/src/routes/submissions.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const router = require('express').Router();
22
const { submit, listSubmissions, getSubmission } = require('../controllers/submissionController');
33
const { authenticate, adminOnly } = require('../middleware/auth');
4-
const { queryAll } = require('../config/db');
4+
const { queryAll, queryOne, run } = require('../config/db');
5+
const { judgeCode, judgeChoice, judgeFillBlank } = require('../services/judgeService');
56

67
router.post('/', authenticate, submit);
78
router.get('/', authenticate, listSubmissions);
@@ -20,6 +21,38 @@ router.get('/admin/all', authenticate, adminOnly, (req, res) => {
2021
);
2122
res.json({ submissions, total, page: Number(page), totalPages: Math.ceil(total / Number(limit)) });
2223
});
24+
router.post('/admin/rejudge/:id', authenticate, adminOnly, (req, res) => {
25+
const submission = queryOne('SELECT * FROM submissions WHERE id = ?', [req.params.id]);
26+
if (!submission) return res.status(404).json({ error: '提交记录不存在' });
27+
const problem = queryOne('SELECT * FROM problems WHERE id = ?', [submission.problem_id]);
28+
if (!problem) return res.status(404).json({ error: '题目不存在' });
29+
30+
let result;
31+
try {
32+
let testCases;
33+
try { testCases = JSON.parse(problem.test_cases); } catch { testCases = []; }
34+
35+
if (submission.type === 'programming' || problem.type === 'programming') {
36+
result = judgeCode(submission.code, submission.language || 'javascript', testCases);
37+
} else if (submission.type === 'choice' || problem.type === 'choice') {
38+
let options; try { options = JSON.parse(problem.options); } catch { options = []; }
39+
result = judgeChoice(submission.answer, problem.blanks_answer, options);
40+
} else {
41+
let answers; try { answers = JSON.parse(problem.blanks_answer); } catch { answers = []; }
42+
result = judgeFillBlank(submission.answer, answers, problem.solution);
43+
}
44+
45+
run('UPDATE submissions SET status = ?, score = ?, details = ? WHERE id = ?',
46+
[result.status, result.score, JSON.stringify(result.details), submission.id]);
47+
if (result.status === 'accepted') {
48+
run('UPDATE problems SET accepted_count = accepted_count + 1 WHERE id = ?', [submission.problem_id]);
49+
}
50+
51+
res.json({ message: '重新判题完成', result });
52+
} catch (e) {
53+
res.status(500).json({ error: e.message });
54+
}
55+
});
2356
router.get('/:id', authenticate, getSubmission);
2457

2558
module.exports = router;

frontend/src/pages/Admin.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default function Admin() {
2222
const [allSubmissions, setAllSubmissions] = useState<any[]>([]);
2323
const [allLoading, setAllLoading] = useState(false);
2424
const [dbOptimizing, setDbOptimizing] = useState(false);
25+
const [rejudgingId, setRejudgingId] = useState<number | null>(null);
2526

2627
const fetchData = useCallback(async () => {
2728
setLoading(true);
@@ -131,6 +132,24 @@ export default function Admin() {
131132
finally { setAllLoading(false); }
132133
};
133134

135+
const handleRejudge = async (id: number) => {
136+
setRejudgingId(id);
137+
try {
138+
const token = localStorage.getItem('oj_token');
139+
const res = await fetch(`/api/submissions/admin/rejudge/${id}`, {
140+
method: 'POST', headers: { Authorization: `Bearer ${token}` },
141+
});
142+
const data = await res.json();
143+
if (res.ok) {
144+
toast.success(`重新判题完成: ${data.result.status}`);
145+
fetchAllSubmissions();
146+
} else {
147+
toast.error(data.error || '重判失败');
148+
}
149+
} catch { toast.error('重判失败'); }
150+
finally { setRejudgingId(null); }
151+
};
152+
134153
const TYPE_LABELS: Record<string, string> = {
135154
programming: '编程题',
136155
choice: '选择题',
@@ -452,6 +471,7 @@ export default function Admin() {
452471
<th className="text-left py-2 px-3 text-dark-400">题目</th>
453472
<th className="text-left py-2 px-3 text-dark-400">状态</th>
454473
<th className="text-left py-2 px-3 text-dark-400 hidden md:table-cell">时间</th>
474+
<th className="text-center py-2 px-3 text-dark-400">操作</th>
455475
</tr>
456476
</thead>
457477
<tbody>
@@ -468,6 +488,14 @@ export default function Admin() {
468488
<SubmissionStatus status={s.status} score={s.score} />
469489
</td>
470490
<td className="py-2 px-3 text-dark-400 hidden md:table-cell text-xs">{s.created_at?.slice(0, 16).replace('T', ' ')}</td>
491+
<td className="py-2 px-3 text-center">
492+
<button
493+
onClick={() => handleRejudge(s.id)}
494+
disabled={rejudgingId === s.id}
495+
className="text-xs text-cyan-400 hover:text-cyan-300 disabled:opacity-50 transition-colors"
496+
>
497+
{rejudgingId === s.id ? <Loader2 className="w-3.5 h-3.5 animate-spin mx-auto" /> : '重判'}
498+
</button>
471499
</tr>
472500
))}
473501
</tbody>

0 commit comments

Comments
 (0)