feat: refactor detail page && play page support position param

This commit is contained in:
shinya
2025-06-19 22:27:47 +08:00
parent e45feeb1a8
commit 909aee0d91
5 changed files with 210 additions and 20 deletions

View File

@@ -4,6 +4,9 @@ import Image from 'next/image';
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import type { PlayRecord } from '@/lib/db.client';
import { generateStorageKey, getAllPlayRecords } from '@/lib/db.client';
import PageLayout from '@/components/layout/PageLayout';
import { VideoDetail } from '../api/detail/route';
@@ -13,6 +16,18 @@ export default function DetailPage() {
const [detail, setDetail] = useState<VideoDetail | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [playRecord, setPlayRecord] = useState<PlayRecord | null>(null);
// 格式化剩余时间(如 1h 50m
const formatDuration = (seconds: number) => {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const parts: string[] = [];
if (h) parts.push(`${h}h`);
if (m) parts.push(`${m}m`);
if (parts.length === 0) parts.push('0m');
return parts.join(' ');
};
useEffect(() => {
const source = searchParams.get('source');
@@ -24,7 +39,7 @@ export default function DetailPage() {
return;
}
const fetchDetail = async () => {
const fetchData = async () => {
try {
const response = await fetch(`/api/detail?source=${source}&id=${id}`);
if (!response.ok) {
@@ -32,6 +47,11 @@ export default function DetailPage() {
}
const data = await response.json();
setDetail(data);
// 获取播放记录
const allRecords = await getAllPlayRecords();
const key = generateStorageKey(source, id);
setPlayRecord(allRecords[key] || null);
} catch (err) {
setError(err instanceof Error ? err.message : '获取详情失败');
} finally {
@@ -39,7 +59,7 @@ export default function DetailPage() {
}
};
fetchDetail();
fetchData();
}, [searchParams]);
return (
@@ -95,12 +115,12 @@ export default function DetailPage() {
</svg>
</button>
{/* 封面 */}
<div className='flex-shrink-0 w-full md:w-64'>
<div className='flex-shrink-0 w-full md:w-72'>
<Image
src={detail.videoInfo.cover || '/images/placeholder.png'}
alt={detail.videoInfo.title}
width={256}
height={384}
width={288}
height={432}
className='w-full rounded-xl object-cover'
style={{ aspectRatio: '2/3' }}
priority
@@ -109,7 +129,7 @@ export default function DetailPage() {
{/* 右侧信息 */}
<div
className='flex-1 flex flex-col min-h-0'
style={{ height: '384px' }}
style={{ height: '430px' }}
>
<h1 className='text-3xl font-bold mb-2 tracking-wide flex items-center flex-shrink-0'>
{detail.videoInfo.title}
@@ -130,16 +150,46 @@ export default function DetailPage() {
<span>{detail.videoInfo.type}</span>
)}
</div>
{/* 按钮区域 */}
<div className='flex items-center gap-4 mb-4 flex-shrink-0'>
<a
href={`/play?source=${searchParams.get(
'source'
)}&id=${searchParams.get('id')}`}
className='flex items-center justify-center gap-2 px-6 py-2 bg-green-500 hover:bg-green-600 rounded-lg transition-colors text-white'
>
<div className='w-0 h-0 border-t-[6px] border-t-transparent border-l-[10px] border-l-white border-b-[6px] border-b-transparent'></div>
<span></span>
</a>
{playRecord ? (
<>
{/* 恢复播放 */}
<a
href={`/play?source=${searchParams.get(
'source'
)}&id=${searchParams.get('id')}`}
className='flex items-center justify-center gap-2 px-6 py-2 bg-green-500 hover:bg-green-600 rounded-lg transition-colors text-white'
>
<div className='w-0 h-0 border-t-[6px] border-t-transparent border-l-[10px] border-l-white border-b-[6px] border-b-transparent'></div>
<span></span>
</a>
{/* 从头开始 */}
<a
href={`/play?source=${searchParams.get(
'source'
)}&id=${searchParams.get('id')}&index=1&position=0`}
className='flex items-center justify-center gap-2 px-6 py-2 bg-gray-500 hover:bg-gray-600 rounded-lg transition-colors text-white'
>
<div className='w-0 h-0 border-t-[6px] border-t-transparent border-l-[10px] border-l-white border-b-[6px] border-b-transparent'></div>
<span></span>
</a>
</>
) : (
<>
{/* 播放 */}
<a
href={`/play?source=${searchParams.get(
'source'
)}&id=${searchParams.get('id')}&index=1&position=0`}
className='flex items-center justify-center gap-2 px-6 py-2 bg-green-500 hover:bg-green-600 rounded-lg transition-colors text-white'
>
<div className='w-0 h-0 border-t-[6px] border-t-transparent border-l-[10px] border-l-white border-b-[6px] border-b-transparent'></div>
<span></span>
</a>
</>
)}
{/* 爱心按钮 */}
<button className='flex items-center justify-center w-10 h-10 bg-pink-400 hover:bg-pink-500 rounded-full transition-colors'>
<svg
xmlns='http://www.w3.org/2000/svg'
@@ -157,9 +207,32 @@ export default function DetailPage() {
</svg>
</button>
</div>
{/* 播放记录进度条 */}
{playRecord && (
<div className='mb-4 flex items-center gap-3 w-full max-w-sm'>
{/* 进度条 */}
<div className='flex-1 h-1 bg-gray-600 rounded-sm overflow-hidden'>
<div
className='h-full bg-green-500'
style={{
width: `${
(playRecord.play_time / playRecord.total_time) * 100
}%`,
}}
></div>
</div>
{/* 剩余时间 */}
<span className='text-gray-600/60 text-xs whitespace-nowrap'>
{' '}
{formatDuration(
playRecord.total_time - playRecord.play_time
)}
</span>
</div>
)}
{detail.videoInfo.desc && (
<div
className='mt-4 text-base leading-relaxed opacity-90 overflow-y-auto pr-2 flex-1 min-h-0 scrollbar-hide'
className='mt-0 text-base leading-relaxed opacity-90 overflow-y-auto pr-2 flex-1 min-h-0 scrollbar-hide'
style={{ whiteSpace: 'pre-line' }}
>
{detail.videoInfo.desc}