|
| 1 | +import { useState, useEffect } from 'react'; |
| 2 | +import { X, Code2, ListChecks, PenLine, ArrowRight } from 'lucide-react'; |
| 3 | + |
| 4 | +const STEPS = [ |
| 5 | + { icon: <Code2 className="w-6 h-6 text-blue-400" />, title: '刷编程题', desc: '在线编写 JavaScript 或 Python 代码,实时判题反馈' }, |
| 6 | + { icon: <ListChecks className="w-6 h-6 text-violet-400" />, title: '做选择题', desc: '从四个选项中选择正确答案,即时评分' }, |
| 7 | + { icon: <PenLine className="w-6 h-6 text-cyan-400" />, title: '填填空题', desc: '输入答案,支持多个可接受答案匹配' }, |
| 8 | +]; |
| 9 | + |
| 10 | +export default function TutorialOverlay() { |
| 11 | + const [visible, setVisible] = useState(false); |
| 12 | + const [step, setStep] = useState(0); |
| 13 | + |
| 14 | + useEffect(() => { |
| 15 | + const seen = localStorage.getItem('cj_tutorial_seen'); |
| 16 | + if (!seen) { |
| 17 | + setTimeout(() => setVisible(true), 500); |
| 18 | + } |
| 19 | + }, []); |
| 20 | + |
| 21 | + const dismiss = () => { |
| 22 | + localStorage.setItem('cj_tutorial_seen', 'true'); |
| 23 | + setVisible(false); |
| 24 | + }; |
| 25 | + |
| 26 | + if (!visible) return null; |
| 27 | + |
| 28 | + return ( |
| 29 | + <div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/70 backdrop-blur-sm" onClick={dismiss}> |
| 30 | + <div className="bg-dark-800 border border-dark-600 rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl" onClick={e => e.stopPropagation()}> |
| 31 | + <div className="flex justify-between items-center mb-4"> |
| 32 | + <h2 className="text-xl font-bold text-white">欢迎来到 CodeJudge</h2> |
| 33 | + <button onClick={dismiss} className="text-dark-400 hover:text-white p-1"><X className="w-5 h-5" /></button> |
| 34 | + </div> |
| 35 | + |
| 36 | + {step === 0 && ( |
| 37 | + <div className="text-center py-4"> |
| 38 | + <p className="text-dark-300 mb-6">一个平台搞定三种题型</p> |
| 39 | + <div className="grid gap-4"> |
| 40 | + {STEPS.map(s => ( |
| 41 | + <div key={s.title} className="flex items-start gap-3 p-3 rounded-lg bg-dark-700/50"> |
| 42 | + {s.icon} |
| 43 | + <div className="text-left"> |
| 44 | + <p className="text-white font-medium text-sm">{s.title}</p> |
| 45 | + <p className="text-dark-400 text-xs">{s.desc}</p> |
| 46 | + </div> |
| 47 | + </div> |
| 48 | + ))} |
| 49 | + </div> |
| 50 | + </div> |
| 51 | + )} |
| 52 | + |
| 53 | + {step === 1 && ( |
| 54 | + <div className="py-4"> |
| 55 | + <p className="text-dark-300 mb-4 text-center">预置账户</p> |
| 56 | + <div className="space-y-3"> |
| 57 | + <div className="p-3 rounded-lg bg-dark-700/50"> |
| 58 | + <p className="text-white text-sm mb-1">管理员</p> |
| 59 | + <p className="text-dark-400 text-xs font-mono">admin@oj.com / admin123</p> |
| 60 | + </div> |
| 61 | + <div className="p-3 rounded-lg bg-dark-700/50"> |
| 62 | + <p className="text-white text-sm mb-1">学生</p> |
| 63 | + <p className="text-dark-400 text-xs font-mono">test@oj.com / test123</p> |
| 64 | + </div> |
| 65 | + </div> |
| 66 | + </div> |
| 67 | + )} |
| 68 | + |
| 69 | + <div className="flex items-center justify-between mt-6 pt-4 border-t border-dark-700"> |
| 70 | + <div className="flex gap-1.5"> |
| 71 | + {[0, 1].map(i => ( |
| 72 | + <div key={i} className={`w-2 h-2 rounded-full ${step === i ? 'bg-primary-500' : 'bg-dark-600'}`} /> |
| 73 | + ))} |
| 74 | + </div> |
| 75 | + <div className="flex gap-2"> |
| 76 | + <button onClick={dismiss} className="btn-ghost text-xs px-3 py-1.5">跳过</button> |
| 77 | + {step < 1 ? ( |
| 78 | + <button onClick={() => setStep(s => s + 1)} className="btn-primary text-xs inline-flex items-center gap-1"> |
| 79 | + 下一步 <ArrowRight className="w-3.5 h-3.5" /> |
| 80 | + </button> |
| 81 | + ) : ( |
| 82 | + <button onClick={dismiss} className="btn-primary text-xs px-4">开始使用</button> |
| 83 | + )} |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | + </div> |
| 88 | + ); |
| 89 | +} |
0 commit comments