77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { generateVideoPoster } from "./videoPoster";
|
|
|
|
type Lesson = {
|
|
id: string;
|
|
title: string;
|
|
duration: string;
|
|
videoUrl?: string;
|
|
};
|
|
|
|
type Props = {
|
|
lesson: Lesson;
|
|
completed?: boolean;
|
|
onComplete?: () => void;
|
|
};
|
|
|
|
export function VideoCard({ lesson, completed = false, onComplete }: Props) {
|
|
const [poster, setPoster] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
setPoster(generateVideoPoster(lesson.title, lesson.id));
|
|
}, [lesson.title, lesson.id]);
|
|
|
|
const handleEnded = useCallback(() => {
|
|
onComplete?.();
|
|
}, [onComplete]);
|
|
|
|
return (
|
|
<article
|
|
className={`group overflow-hidden rounded-2xl border shadow-sm transition-all duration-200 hover:shadow-md ${
|
|
completed
|
|
? "border-emerald-200 bg-emerald-50/50 hover:border-emerald-300"
|
|
: "border-slate-100 bg-white hover:border-slate-200"
|
|
}`}
|
|
>
|
|
{/* 三级标题:视频上方,突出显示 */}
|
|
<div className="flex items-center gap-3 border-b border-slate-100 bg-white/80 px-4 py-3">
|
|
<span
|
|
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-xs font-bold ${
|
|
completed ? "bg-emerald-100 text-emerald-600" : "bg-sky-50 text-sky-600"
|
|
}`}
|
|
>
|
|
{lesson.id}
|
|
</span>
|
|
<h4 className="min-w-0 flex-1 text-base font-semibold leading-snug text-slate-800 line-clamp-2">
|
|
{lesson.title}
|
|
</h4>
|
|
</div>
|
|
<div className="relative aspect-video overflow-hidden bg-slate-900">
|
|
<video
|
|
src={lesson.videoUrl}
|
|
poster={poster ?? undefined}
|
|
controls
|
|
playsInline
|
|
onEnded={handleEnded}
|
|
className="h-full w-full object-contain transition-transform duration-300 group-hover:scale-[1.01]"
|
|
preload="metadata"
|
|
>
|
|
您的浏览器不支持视频播放
|
|
</video>
|
|
<span className="absolute bottom-2 right-2 rounded bg-black/60 px-2 py-0.5 text-xs font-medium tabular-nums text-white/90 backdrop-blur-sm">
|
|
{lesson.duration}
|
|
</span>
|
|
{completed && (
|
|
<span className="absolute top-2 right-2 flex h-8 w-8 items-center justify-center rounded-full bg-emerald-500 text-white shadow-md">
|
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</span>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|