Files
gitlab-instance-0a899031_di…/app/[locale]/course/[moduleIndex]/[lessonIndex]/page.tsx
2026-03-12 03:54:22 -05:00

167 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { Link, useTranslation } from "@/i18n/navigation";
import { VideoPlayer } from "@/app/components/course/VideoPlayer";
import { LessonMarkdown } from "./LessonMarkdown";
import { getLesson } from "@/config/lessons";
import { canWatchLesson } from "@/app/lib/course-payment";
import { useAuthAndVip } from "@/app/lib/useAuthAndVip";
import { setLastWatched, markCompleted } from "@/app/lib/learning";
import { DOWNLOAD_CONFIG } from "@/config/course";
import Header from "@/app/components/Header";
export default function LessonPage() {
const params = useParams();
const router = useRouter();
const { t } = useTranslation("community");
const { vip } = useAuthAndVip();
const [toast, setToast] = useState(false);
const moduleIndex = Number(params.moduleIndex);
const lessonIndex = Number(params.lessonIndex);
const lesson = getLesson(moduleIndex, lessonIndex);
const unlocked = canWatchLesson(moduleIndex, lessonIndex, vip);
const isFreeTrial = moduleIndex === 0 && lessonIndex < 2;
useEffect(() => {
if (lesson && unlocked) setLastWatched(moduleIndex, lessonIndex, lesson.name);
}, [moduleIndex, lessonIndex, lesson?.name, unlocked]);
if (!lesson || isNaN(moduleIndex) || isNaN(lessonIndex)) {
return (
<div className="min-h-screen bg-[var(--background)]">
<Header />
<main className="pt-20 pb-16">
<div className="mx-auto max-w-4xl px-4 py-16 text-center">
<h1 className="mb-4 text-2xl font-bold"></h1>
<p className="mb-6 text-[var(--muted-foreground)]">
</p>
<Link
href="/course"
className="inline-flex items-center gap-2 rounded-full bg-[var(--accent)] px-6 py-3 font-medium text-[var(--accent-foreground)] transition hover:opacity-90"
>
</Link>
</div>
</main>
</div>
);
}
return (
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)]">
<Header />
<main className="pt-14 pb-16 md:pt-16">
<div className="mx-auto max-w-4xl px-4 sm:px-6">
{/* 返回导航 */}
<nav className="mb-6 flex items-center gap-2 text-sm">
<button
type="button"
onClick={() => router.back()}
className="flex items-center gap-1 text-[var(--muted-foreground)] transition hover:text-[var(--foreground)]"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<span className="text-[var(--muted-foreground)]">/</span>
<Link href="/course" className="text-[var(--muted-foreground)] transition hover:text-[var(--foreground)]">
</Link>
</nav>
{/* 标题 */}
<h1 className="mb-2 flex items-center gap-2 text-xl font-bold sm:text-2xl md:text-3xl">
{lesson.name}
{isFreeTrial && (
<span className="rounded bg-[var(--accent)]/20 px-2 py-0.5 text-sm font-normal text-[var(--accent)]">
</span>
)}
</h1>
<p className="mb-8 text-sm text-[var(--muted-foreground)]">
{lesson.duration}
</p>
{/* 视频播放器 / 锁定 overlay */}
<div className="relative mb-12">
{unlocked ? (
<VideoPlayer
src={lesson.videoUrl}
title={lesson.name}
videoId={`course-${moduleIndex}-${lessonIndex}`}
className="shadow-xl"
onComplete={() => markCompleted(moduleIndex, lessonIndex)}
/>
) : (
<div className="relative overflow-hidden rounded-xl bg-black md:rounded-2xl">
<div className="aspect-video flex flex-col items-center justify-center gap-4 bg-zinc-900">
<span className="text-6xl">🔒</span>
<p className="text-lg text-white/90"></p>
<Link
href="/join"
className="rounded-full bg-[var(--accent)] px-6 py-3 font-medium text-[var(--accent-foreground)] transition hover:opacity-90"
>
</Link>
</div>
</div>
)}
</div>
{/* 配套文档 - 仅解锁后显示 */}
{unlocked && (
<div className="relative rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm md:rounded-2xl md:p-8">
<h2 className="mb-6 flex items-center gap-2 text-lg font-semibold">
<svg className="h-5 w-5 text-[var(--accent)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</h2>
<LessonMarkdown content={lesson.markdown} />
</div>
)}
{/* 网盘下载 */}
{DOWNLOAD_CONFIG.enabled && unlocked && (
<div className="mt-8 rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm md:rounded-2xl md:p-8">
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold">
<svg className="h-5 w-5 text-[var(--accent)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
{DOWNLOAD_CONFIG.title}
</h2>
<p className="mb-4 text-sm text-[var(--muted-foreground)]">
</p>
<a
href={DOWNLOAD_CONFIG.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-full bg-[var(--accent)] px-6 py-3 font-medium text-[var(--accent-foreground)] transition hover:opacity-90"
>
{DOWNLOAD_CONFIG.buttonText}
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
)}
</div>
</main>
{toast && (
<div
className="fixed bottom-8 left-1/2 z-50 -translate-x-1/2 rounded-lg bg-slate-800 px-6 py-3 text-sm font-medium text-white shadow-lg dark:bg-slate-700"
role="alert"
>
{t("updating")}
</div>
)}
</div>
);
}