Skip to content

Commit c2ae281

Browse files
author
CodeJudge
committed
feat: 题目排序 + 输入输出提示 + 种子数据扩充
1 parent 70ee574 commit c2ae281

4 files changed

Lines changed: 80 additions & 4 deletions

File tree

backend/src/controllers/problemController.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { queryAll, queryOne, run } = require('../config/db');
22

33
function listProblems(req, res) {
4-
const { type, difficulty, search, page = 1, limit = 20 } = req.query;
4+
const { type, difficulty, search, page = 1, limit = 20, sort } = req.query;
55
let sql = 'SELECT id, title, type, difficulty, tags, accepted_count, submission_count FROM problems WHERE 1=1';
66
let countSql = 'SELECT COUNT(*) as count FROM problems WHERE 1=1';
77
let conditions = '';
@@ -13,7 +13,16 @@ function listProblems(req, res) {
1313

1414
const total = queryOne(countSql + conditions, params).count;
1515

16-
sql += conditions + ' ORDER BY id DESC LIMIT ? OFFSET ?';
16+
let orderClause;
17+
if (sort === 'acceptance') {
18+
orderClause = 'ORDER BY (accepted_count * 100.0 / CASE WHEN submission_count > 0 THEN submission_count ELSE 1 END) DESC';
19+
} else if (sort === 'submissions') {
20+
orderClause = 'ORDER BY submission_count DESC';
21+
} else {
22+
orderClause = 'ORDER BY id DESC';
23+
}
24+
25+
sql += conditions + ' ' + orderClause + ' LIMIT ? OFFSET ?';
1726
params.push(Number(limit), (Number(page) - 1) * Number(limit));
1827

1928
const problems = queryAll(sql, params);

backend/src/utils/seedLarge.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ async function seed() {
7878
{ title:'奇数之和', dd:'medium', tags:'数组,条件', gen(){ const n=rand(3,15);const a=Array.from({length:n},()=>rand(1,50));return {tc:[`${n}\n${a.join(' ')}`,String(a.filter(x=>x%2===1).reduce((s,x)=>s+x,0))]}; } },
7979
{ title:'第二大数', dd:'medium', tags:'数组,排序', gen(){ const n=rand(3,12);const a=Array.from({length:n},()=>rand(1,100));const s=[...new Set(a)].sort((x,y)=>y-x);return {tc:[`${n}\n${a.join(' ')}`,String(s[1]||s[0])]}; } },
8080
{ title:'数组众数', dd:'hard', tags:'数组,统计', gen(){ const n=rand(4,12);const pool=[rand(1,5),rand(1,5)];const a=[];for(let i=0;i<n-2;i++)a.push(pick(pool));a.push(pool[0]);a.push(pool[0]); const m={};a.forEach(x=>m[x]=(m[x]||0)+1);const mode=Object.keys(m).sort((x,y)=>m[y]-m[x])[0];return {tc:[`${n}\n${a.join(' ')}`,String(mode)]}; } },
81+
{ title:'数组元素求和(指定范围)', dd:'medium', tags:'数组,前缀和', gen(){ const n=rand(5,20);const a=Array.from({length:n},()=>rand(-50,50));const l=rand(1,n-1);const r=rand(l,n);const sum=a.slice(l-1,r).reduce((x,y)=>x+y,0); return {tc:[`${n}\n${a.join(' ')}\n${l} ${r}`,String(sum)]}; } },
82+
{ title:'判断闰年', dd:'easy', tags:'条件判断,数学', gen(){ const years=[2000,2004,2008,2012,2016,2020,2024,2100,2200,2300,1900,1800,2001,2002,2003,2005,2006,2007,2009,2010]; const y=pick(years); const leap=(y%400===0)||(y%4===0&&y%100!==0); return {tc:[String(y),leap?'YES':'NO']}; } },
8183
];
8284

8385
const descriptions = {
@@ -119,6 +121,8 @@ async function seed() {
119121
'奇数之和': '计算数组中所有奇数元素的和',
120122
'第二大数': '找出数组中第二大的数。如果所有元素相同,输出该元素',
121123
'数组众数': '找出数组中出现次数最多的元素(众数)。保证只有一个众数',
124+
'数组元素求和(指定范围)': '给定一个数组和两个整数 L 和 R,计算数组中从第 L 个到第 R 个元素(含两端)的和',
125+
'判断闰年': '判断一个年份是否为闰年。闰年规则:能被4整除但不能被100整除,或能被400整除。是输出 YES,否则输出 NO',
122126
};
123127

124128
for (let i = 0; i < progTarget; i++) {
@@ -311,4 +315,3 @@ async function seed() {
311315
await seed();
312316
process.exit(0);
313317
})();
314-

frontend/src/pages/ProblemDetail.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {
99
Share2,
1010
Star,
1111
Sparkles,
12+
Lightbulb,
13+
ChevronDown,
14+
ChevronUp,
1215
} from 'lucide-react';
1316
import toast from 'react-hot-toast';
1417
import api from '../services/api';
@@ -62,6 +65,9 @@ export default function ProblemDetail() {
6265
const [submitting, setSubmitting] = useState(false);
6366
const [submissionResult, setSubmissionResult] = useState<Submission | null>(null);
6467

68+
// Hint panel
69+
const [hintExpanded, setHintExpanded] = useState(true);
70+
6571
const CODE_SAVE_KEY = `cj_code_${id}_${language}`;
6672

6773
const fetchProblem = useCallback(async () => {
@@ -474,6 +480,38 @@ export default function ProblemDetail() {
474480
</button>
475481
))}
476482
</div>
483+
484+
{/* Hint panel */}
485+
<div className="mb-3 border border-dark-700 rounded-lg overflow-hidden">
486+
<button
487+
onClick={() => setHintExpanded(!hintExpanded)}
488+
className="w-full flex items-center gap-2 px-3 py-2 bg-dark-800/70 text-left hover:bg-dark-800 transition-colors"
489+
>
490+
<Lightbulb size={16} className="text-yellow-400" />
491+
<span className="text-sm text-gray-300">输入/输出格式提示</span>
492+
<span className="ml-auto text-gray-500">
493+
{hintExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
494+
</span>
495+
</button>
496+
{hintExpanded && (
497+
<div className="px-3 py-2 space-y-1 text-xs text-dark-400">
498+
<p>输入通过标准输入(stdin)读取</p>
499+
<p>使用 console.log() (JS) 或 print() (Python) 输出</p>
500+
<p>示例代码模板已自动填入</p>
501+
</div>
502+
)}
503+
</div>
504+
505+
{/* Sample test case */}
506+
{problem.test_cases && problem.test_cases.length > 0 && (
507+
<div className="mb-3 p-3 bg-dark-800/50 rounded-lg border border-dark-700">
508+
<p className="text-dark-400 text-xs mb-2">示例输入:</p>
509+
<pre className="text-dark-300 text-sm">{problem.test_cases[0].input}</pre>
510+
<p className="text-dark-400 text-xs mb-1 mt-2">示例输出:</p>
511+
<pre className="text-emerald-400 text-sm">{problem.test_cases[0].expected_output}</pre>
512+
</div>
513+
)}
514+
477515
<CodeEditor
478516
code={code}
479517
language={language === 'javascript' ? 'javascript' : 'python'}

frontend/src/pages/Problems.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ArrowRightLeft,
1515
Tag,
1616
Star,
17+
ArrowUpDown,
1718
} from 'lucide-react';
1819
import toast from 'react-hot-toast';
1920
import api from '../services/api';
@@ -36,6 +37,12 @@ const difficultyOptions = [
3637
{ label: '困难', value: 'hard' },
3738
];
3839

40+
const sortOptions = [
41+
{ label: '默认排序', value: '' },
42+
{ label: '通过率最高', value: 'acceptance' },
43+
{ label: '提交最多', value: 'submissions' },
44+
];
45+
3946
const PAGE_SIZE_OPTIONS = [10, 20, 50];
4047

4148
export default function Problems() {
@@ -45,6 +52,7 @@ export default function Problems() {
4552
const search = searchParams.get('search') || '';
4653
const type = searchParams.get('type') || '';
4754
const difficulty = searchParams.get('difficulty') || '';
55+
const sort = searchParams.get('sort') || '';
4856
const page = parseInt(searchParams.get('page') || '1', 10);
4957

5058
const [data, setData] = useState<PaginatedResponse<Problem> | null>(null);
@@ -93,6 +101,7 @@ export default function Problems() {
93101
if (type) params.type = type;
94102
if (difficulty) params.difficulty = difficulty;
95103
if (search) params.search = search;
104+
if (sort) params.sort = sort;
96105

97106
const res = await api.problems.list(params);
98107
setData(res);
@@ -103,7 +112,7 @@ export default function Problems() {
103112
} finally {
104113
setLoading(false);
105114
}
106-
}, [page, pageSize, type, difficulty, search]);
115+
}, [page, pageSize, type, difficulty, search, sort]);
107116

108117
useEffect(() => {
109118
fetchProblems();
@@ -295,6 +304,23 @@ export default function Problems() {
295304
仅显示收藏
296305
</button>
297306

307+
<div className="flex items-center gap-1.5">
308+
<ArrowUpDown size={14} className="text-gray-500" />
309+
{sortOptions.map((opt) => (
310+
<button
311+
key={opt.value}
312+
onClick={() => updateParams({ sort: opt.value, page: '' })}
313+
className={`px-3 py-1.5 rounded-md text-sm transition-colors ${
314+
sort === opt.value || (!sort && opt.value === '')
315+
? 'bg-blue-600 text-white'
316+
: 'bg-dark-800 text-gray-300 hover:bg-dark-700'
317+
}`}
318+
>
319+
{opt.label}
320+
</button>
321+
))}
322+
</div>
323+
298324
<div className="flex items-center gap-1.5 ml-auto">
299325
<ArrowRightLeft size={14} className="text-gray-500" />
300326
{PAGE_SIZE_OPTIONS.map((size) => (

0 commit comments

Comments
 (0)