import { useAuth } from "@/_core/hooks/useAuth"; import { trpc } from "@/lib/trpc"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Progress } from "@/components/ui/progress"; import { ScrollArea } from "@/components/ui/scroll-area"; import { toast } from "sonner"; import { useState, useMemo } from "react"; import { BookOpen, Play, CheckCircle2, Star, Target, ChevronRight, Filter, AlertTriangle, Lightbulb, ArrowUpDown, Clock, Dumbbell } from "lucide-react"; const CATEGORY_LABELS: Record = { forehand: { label: "正手", icon: , color: "bg-green-100 text-green-700" }, backhand: { label: "反手", icon: , color: "bg-blue-100 text-blue-700" }, serve: { label: "发球", icon: , color: "bg-purple-100 text-purple-700" }, volley: { label: "截击", icon: , color: "bg-orange-100 text-orange-700" }, footwork: { label: "脚步", icon: , color: "bg-yellow-100 text-yellow-700" }, shadow: { label: "影子挥拍", icon: , color: "bg-indigo-100 text-indigo-700" }, wall: { label: "墙壁练习", icon: , color: "bg-pink-100 text-pink-700" }, fitness: { label: "体能", icon: , color: "bg-red-100 text-red-700" }, strategy: { label: "战术", icon: , color: "bg-teal-100 text-teal-700" }, }; const SKILL_LABELS: Record = { beginner: { label: "初级", color: "bg-emerald-100 text-emerald-700" }, intermediate: { label: "中级", color: "bg-amber-100 text-amber-700" }, advanced: { label: "高级", color: "bg-rose-100 text-rose-700" }, }; export default function Tutorials() { const { user } = useAuth(); const [selectedCategory, setSelectedCategory] = useState("all"); const [selectedSkill, setSelectedSkill] = useState("all"); const [selectedTutorial, setSelectedTutorial] = useState(null); const [notes, setNotes] = useState(""); const { data: tutorials, isLoading } = trpc.tutorial.list.useQuery({ category: selectedCategory === "all" ? undefined : selectedCategory, skillLevel: selectedSkill === "all" ? undefined : selectedSkill, }); const { data: progressData } = trpc.tutorial.progress.useQuery(undefined, { enabled: !!user }); const updateProgress = trpc.tutorial.updateProgress.useMutation({ onSuccess: () => toast.success("进度已更新"), }); const progressMap = useMemo(() => { const map: Record = {}; progressData?.forEach((p: any) => { map[p.tutorialId] = p; }); return map; }, [progressData]); const totalTutorials = tutorials?.length || 0; const watchedCount = tutorials?.filter((t: any) => progressMap[t.id]?.watched).length || 0; const progressPercent = totalTutorials > 0 ? Math.round((watchedCount / totalTutorials) * 100) : 0; const categories = useMemo(() => { const cats = new Set(); tutorials?.forEach((t: any) => cats.add(t.category)); return Array.from(cats); }, [tutorials]); const handleMarkWatched = (tutorialId: number) => { updateProgress.mutate({ tutorialId, watched: 1 }); }; const handleSaveNotes = (tutorialId: number) => { updateProgress.mutate({ tutorialId, notes }); setNotes(""); toast.success("笔记已保存"); }; const handleSelfScore = (tutorialId: number, score: number) => { updateProgress.mutate({ tutorialId, selfScore: score }); }; if (isLoading) { return (
); } return (
{/* Header */}

教程库

查看动作分解、要点说明和常见错误

{/* Progress Overview */}
学习进度 {watchedCount}/{totalTutorials} 已学习

{progressPercent}% 完成

{/* Filters */}
分类:
{Object.entries(CATEGORY_LABELS).map(([key, { label, icon }]) => ( ))}
级别:
{Object.entries(SKILL_LABELS).map(([key, { label }]) => ( ))}
{/* Tutorial Grid */}
{tutorials?.map((tutorial: any) => { const cat = CATEGORY_LABELS[tutorial.category] || { label: tutorial.category, color: "bg-gray-100 text-gray-700" }; const skill = SKILL_LABELS[tutorial.skillLevel] || { label: tutorial.skillLevel, color: "bg-gray-100 text-gray-700" }; const progress = progressMap[tutorial.id]; const isWatched = progress?.watched === 1; const keyPoints = typeof tutorial.keyPoints === "string" ? JSON.parse(tutorial.keyPoints) : tutorial.keyPoints || []; const mistakes = typeof tutorial.commonMistakes === "string" ? JSON.parse(tutorial.commonMistakes) : tutorial.commonMistakes || []; return (
{cat.label} {skill.label}
{isWatched && }
{tutorial.title} {tutorial.description}
{Math.round((tutorial.duration || 0) / 60)}分钟 {keyPoints.length}个要点
{progress?.selfScore && (
{[1, 2, 3, 4, 5].map(s => ( ))} 自评
)}
{cat.label} {skill.label} {Math.round((tutorial.duration || 0) / 60)}分钟
{tutorial.title}

{tutorial.description}

{/* Key Points */}

技术要点

{keyPoints.map((point: string, i: number) => (
{point}
))}
{/* Common Mistakes */}

常见错误

{mistakes.map((mistake: string, i: number) => (
{mistake}
))}
{/* Self Assessment */} {user && (

自我评估

掌握程度: {[1, 2, 3, 4, 5].map(s => ( ))}