"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { getTopicCurriculum } from "../config"; import { canWatchLesson, isPaid } from "@/app/cloudphone/lib/payment"; import { isBookmarked, toggleBookmark, getBookmarks, getCompletedLessons } from "@/app/cloudphone/lib/learning"; type TopicCurriculumProps = { topicId: string; }; export function TopicCurriculum({ topicId }: TopicCurriculumProps) { const [expanded, setExpanded] = useState>({}); const [bookmarks, setBookmarks] = useState>(new Set()); const [completed, setCompleted] = useState>(new Set()); const [paid, setPaid] = useState(false); const [showUpdatingModal, setShowUpdatingModal] = useState(false); const [updatingLessonName, setUpdatingLessonName] = useState(""); const curriculum = getTopicCurriculum(topicId); useEffect(() => { setBookmarks(getBookmarks()); setCompleted(getCompletedLessons()); setPaid(isPaid()); const onB = () => setBookmarks(getBookmarks()); const onP = () => setCompleted(getCompletedLessons()); const onPayUpdate = () => setPaid(isPaid()); window.addEventListener("learning:bookmarks", onB); window.addEventListener("learning:progress", onP); window.addEventListener("pay:updated", onPayUpdate); return () => { window.removeEventListener("learning:bookmarks", onB); window.removeEventListener("learning:progress", onP); window.removeEventListener("pay:updated", onPayUpdate); }; }, []); useEffect(() => { const curr = getTopicCurriculum(topicId); if (curr?.modules?.length) { setExpanded((prev) => { const next = { ...prev }; curr.modules.forEach((_, i) => { if (next[i] === undefined) next[i] = i === 0; }); return next; }); } }, [topicId]); useEffect(() => { if (!showUpdatingModal) return; const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") setShowUpdatingModal(false); }; const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; window.addEventListener("keydown", onKeyDown); return () => { document.body.style.overflow = prevOverflow; window.removeEventListener("keydown", onKeyDown); }; }, [showUpdatingModal]); const toggle = (i: number) => { setExpanded((prev) => ({ ...prev, [i]: !prev[i] })); }; if (!curriculum?.hasContent || !curriculum.modules.length) return null; const modules = curriculum.modules; const isLivestreamTopic = topicId === "livestream"; const isLivestreamUpdating = isLivestreamTopic && paid; const openUpdatingModal = (lessonName: string) => { setUpdatingLessonName(lessonName); setShowUpdatingModal(true); }; const closeUpdatingModal = () => { setShowUpdatingModal(false); }; return (

实战课程

从入门到进阶,完整学习路径

{modules.map((module, i) => { const isOpen = expanded[i] ?? true; return (
{isOpen && (
    {module.lessons.map((lesson, li) => { const origMod = module.originalModuleIndex; const free = false; /* 暂时全部加锁 */ const unlocked = canWatchLesson(origMod, li); const key = `${origMod}-${li}`; const bookmarked = isBookmarked(origMod, li); const isCompleted = completed.has(key); const textClass = unlocked ? "text-[var(--foreground)]" : "text-[var(--muted-foreground)]"; return (
  • {isLivestreamUpdating ? (
    { if (paid) openUpdatingModal(lesson.name); }} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (paid) openUpdatingModal(lesson.name); } }} className="flex w-full flex-col gap-1 px-4 py-3 text-left transition hover:bg-[var(--muted)]/50 sm:flex-row sm:items-center sm:justify-between sm:px-5 sm:py-3" > {lesson.name} 🛠️ {lesson.duration} ···
    ) : ( {lesson.name} {free && ( 试看 )} {isCompleted && ( )} {!unlocked && ( 🔒 )} {lesson.duration} )}
  • ); })}
)}
); })}
{showUpdatingModal && (
e.stopPropagation()} >
🚧

正在更新

{updatingLessonName} 正在升级内容, 将在完成后第一时间开放。

现在可以先学习「云手机」课程,后续会自动同步最新章节。

)}
); }