Skip to content

Commit 3858b38

Browse files
author
CodeJudge
committed
feat: 每日刷题目标
1 parent e353911 commit 3858b38

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { useState, useEffect } from 'react';
2+
import { Target, TrendingUp, CheckCircle2 } from 'lucide-react';
3+
import toast from 'react-hot-toast';
4+
import { useAuth } from '../context/AuthContext';
5+
6+
const GOAL_KEY = 'cj_daily_goal';
7+
8+
export default function DailyGoal() {
9+
const { user } = useAuth();
10+
const [goal, setGoal] = useState(() => {
11+
return Number(localStorage.getItem(GOAL_KEY)) || 5;
12+
});
13+
const [editing, setEditing] = useState(false);
14+
const [inputValue, setInputValue] = useState(String(goal));
15+
const [todaySolved, setTodaySolved] = useState(0);
16+
17+
useEffect(() => {
18+
if (!user) return;
19+
const token = localStorage.getItem('oj_token');
20+
if (!token) return;
21+
const today = new Date().toISOString().slice(0, 10);
22+
fetch('/api/auth/solved-calendar', { headers: { Authorization: `Bearer ${token}` } })
23+
.then(r => r.json())
24+
.then(data => {
25+
const dates = data.dates || [];
26+
setTodaySolved(dates.filter((d: string) => d === today).length);
27+
})
28+
.catch(() => {});
29+
}, [user]);
30+
31+
const saveGoal = () => {
32+
const n = parseInt(inputValue);
33+
if (isNaN(n) || n < 1) { toast.error('请输入有效数字'); return; }
34+
setGoal(n);
35+
localStorage.setItem(GOAL_KEY, String(n));
36+
setEditing(false);
37+
toast.success(`每日目标已设为 ${n} 题`);
38+
};
39+
40+
if (!user) return null;
41+
42+
const progress = Math.min(100, (todaySolved / goal) * 100);
43+
const remaining = Math.max(0, goal - todaySolved);
44+
45+
return (
46+
<div className="card p-6">
47+
<div className="flex items-center gap-3 mb-4">
48+
<Target className="w-5 h-5 text-primary-400" />
49+
<h3 className="text-lg font-semibold text-white">每日目标</h3>
50+
</div>
51+
52+
{editing ? (
53+
<div className="flex items-center gap-2 mb-3">
54+
<input
55+
type="number"
56+
min="1"
57+
max="100"
58+
value={inputValue}
59+
onChange={e => setInputValue(e.target.value)}
60+
className="input w-20 text-center"
61+
autoFocus
62+
onKeyDown={e => e.key === 'Enter' && saveGoal()}
63+
/>
64+
<span className="text-dark-400 text-sm">题/天</span>
65+
<button onClick={saveGoal} className="btn-primary text-xs px-3 py-1.5">确定</button>
66+
<button onClick={() => setEditing(false)} className="btn-ghost text-xs px-3 py-1.5">取消</button>
67+
</div>
68+
) : (
69+
<div className="flex items-center justify-between mb-3">
70+
<div className="flex items-center gap-2">
71+
<TrendingUp className="w-4 h-4 text-emerald-400" />
72+
<span className="text-2xl font-bold text-white">{todaySolved}</span>
73+
<span className="text-dark-400 text-sm">/ {goal}</span>
74+
</div>
75+
<button onClick={() => { setInputValue(String(goal)); setEditing(true); }} className="text-xs text-dark-400 hover:text-white transition-colors">
76+
修改
77+
</button>
78+
</div>
79+
)}
80+
81+
{/* Progress bar */}
82+
<div className="h-2.5 bg-dark-700 rounded-full overflow-hidden">
83+
<div
84+
className={`h-full rounded-full transition-all duration-500 ${
85+
progress >= 100 ? 'bg-emerald-500' : 'bg-primary-500'
86+
}`}
87+
style={{ width: `${progress}%` }}
88+
/>
89+
</div>
90+
91+
<div className="flex items-center justify-between mt-2">
92+
{progress >= 100 ? (
93+
<span className="flex items-center gap-1 text-emerald-400 text-xs">
94+
<CheckCircle2 className="w-3.5 h-3.5" /> 今日目标已完成!
95+
</span>
96+
) : (
97+
<span className="text-dark-400 text-xs">还差 {remaining} 题完成今日目标</span>
98+
)}
99+
</div>
100+
</div>
101+
);
102+
}

frontend/src/pages/Profile.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import api from '../services/api';
66
import type { Submission } from '../types';
77
import { useDocumentTitle } from '../hooks/useDocumentTitle';
88
import GamificationSection from '../components/GamificationSection';
9+
import DailyGoal from '../components/DailyGoal';
910

1011
const STATUS_MAP: Record<string, { label: string; icon: React.ReactNode; className: string }> = {
1112
accepted: {
@@ -284,6 +285,7 @@ export default function Profile() {
284285
</div>
285286
)}
286287

288+
<DailyGoal />
287289
<GamificationSection />
288290

289291
{/* Solved calendar */}

frontend/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/app.tsx","./src/main.tsx","./src/components/choicequestion.tsx","./src/components/codeeditor.tsx","./src/components/errorboundary.tsx","./src/components/fillblankquestion.tsx","./src/components/footer.tsx","./src/components/gamificationsection.tsx","./src/components/keyboardshortcuts.tsx","./src/components/markdownrenderer.tsx","./src/components/navbar.tsx","./src/components/problemcard.tsx","./src/components/submissionstatus.tsx","./src/components/tutorialoverlay.tsx","./src/context/authcontext.tsx","./src/context/bookmarkcontext.tsx","./src/hooks/usedocumenttitle.ts","./src/pages/admin.tsx","./src/pages/adminproblemform.tsx","./src/pages/apidocs.tsx","./src/pages/home.tsx","./src/pages/leaderboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/problemdetail.tsx","./src/pages/problems.tsx","./src/pages/profile.tsx","./src/pages/register.tsx","./src/pages/submissions.tsx","./src/pages/systemhealth.tsx","./src/services/api.ts","./src/types/index.ts"],"version":"5.9.3"}
1+
{"root":["./src/app.tsx","./src/main.tsx","./src/components/choicequestion.tsx","./src/components/codeeditor.tsx","./src/components/dailygoal.tsx","./src/components/errorboundary.tsx","./src/components/fillblankquestion.tsx","./src/components/footer.tsx","./src/components/gamificationsection.tsx","./src/components/keyboardshortcuts.tsx","./src/components/markdownrenderer.tsx","./src/components/navbar.tsx","./src/components/problemcard.tsx","./src/components/submissionstatus.tsx","./src/components/tutorialoverlay.tsx","./src/context/authcontext.tsx","./src/context/bookmarkcontext.tsx","./src/hooks/usedocumenttitle.ts","./src/pages/admin.tsx","./src/pages/adminproblemform.tsx","./src/pages/apidocs.tsx","./src/pages/home.tsx","./src/pages/leaderboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/problemdetail.tsx","./src/pages/problems.tsx","./src/pages/profile.tsx","./src/pages/register.tsx","./src/pages/submissions.tsx","./src/pages/systemhealth.tsx","./src/services/api.ts","./src/types/index.ts"],"version":"5.9.3"}

0 commit comments

Comments
 (0)