feat(audiobook): refactor ProjectCard layout for improved chapter display and interaction

This commit is contained in:
2026-03-10 19:01:42 +08:00
parent c6ecfe668b
commit 4785ca4b36

View File

@@ -736,9 +736,15 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
}) })
return ( return (
<div key={ch.id} className="border rounded px-3 py-2 space-y-2"> <div key={ch.id} className="border rounded px-3 py-2 space-y-2">
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between text-sm"> <div className="flex items-start justify-between gap-2">
<span className="text-xs font-medium truncate">{chTitle}</span> <span className="text-xs font-medium break-words flex-1">{chTitle}</span>
<div className="flex gap-1 items-center flex-wrap shrink-0"> {chSegs.length > 0 && (
<button onClick={toggleChExpand} className="text-muted-foreground hover:text-foreground shrink-0 mt-0.5">
{chExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
</button>
)}
</div>
<div className="flex items-center gap-1 flex-wrap">
{ch.status === 'pending' && ( {ch.status === 'pending' && (
<Button size="sm" variant="outline" className="h-6 text-xs px-2" onClick={() => handleParseChapter(ch.id, ch.title)}> <Button size="sm" variant="outline" className="h-6 text-xs px-2" onClick={() => handleParseChapter(ch.id, ch.title)}>
@@ -767,7 +773,7 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
{ch.status === 'ready' && chAllDone && ( {ch.status === 'ready' && chAllDone && (
<> <>
<Badge variant="outline" className="text-xs"> {chDone} </Badge> <Badge variant="outline" className="text-xs"> {chDone} </Badge>
<Button size="sm" variant="ghost" className="h-5 w-5 p-0" onClick={() => handleDownload(ch.chapter_index)} title="下载此章"> <Button size="sm" variant="ghost" className="h-6 w-6 p-0" onClick={() => handleDownload(ch.chapter_index)} title="下载此章">
<Download className="h-3 w-3" /> <Download className="h-3 w-3" />
</Button> </Button>
</> </>
@@ -777,26 +783,20 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
</Button> </Button>
)} )}
{chSegs.length > 0 && (
<button onClick={toggleChExpand} className="text-muted-foreground hover:text-foreground">
{chExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
</button>
)}
</div>
</div> </div>
{ch.status === 'parsing' && ( {ch.status === 'parsing' && (
<LogStream projectId={project.id} chapterId={ch.id} active={ch.status === 'parsing'} /> <LogStream projectId={project.id} chapterId={ch.id} active={ch.status === 'parsing'} />
)} )}
{chExpanded && chSegs.length > 0 && ( {chExpanded && chSegs.length > 0 && (
<div className="space-y-1.5 pt-1 border-t"> <div className="pt-2 border-t divide-y divide-border/50">
{chSegs.map(seg => ( {chSegs.map(seg => (
<div key={seg.id} className={`space-y-1 ${sequentialPlayingId === seg.id ? 'bg-primary/5 rounded px-1' : ''}`}> <div key={seg.id} className={`py-2 space-y-1.5 ${sequentialPlayingId === seg.id ? 'bg-primary/5 px-1 rounded' : ''}`}>
<div className="flex items-start gap-2 text-xs"> <div className="flex items-center gap-2">
<Badge variant="outline" className="shrink-0 text-xs mt-0.5">{seg.character_name || '?'}</Badge> <Badge variant="outline" className="text-xs shrink-0">{seg.character_name || '?'}</Badge>
<span className="text-muted-foreground flex-1 min-w-0 break-words leading-relaxed">{seg.text}</span> {seg.status === 'generating' && <Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />}
{seg.status === 'generating' && <Loader2 className="h-3 w-3 animate-spin shrink-0 mt-0.5" />} {seg.status === 'error' && <Badge variant="destructive" className="text-xs"></Badge>}
{seg.status === 'error' && <Badge variant="destructive" className="text-xs shrink-0 mt-0.5"></Badge>}
</div> </div>
<p className="text-xs text-muted-foreground break-words leading-relaxed">{seg.text}</p>
{seg.status === 'done' && ( {seg.status === 'done' && (
<LazyAudioPlayer audioUrl={audiobookApi.getSegmentAudioUrl(project.id, seg.id)} jobId={seg.id} /> <LazyAudioPlayer audioUrl={audiobookApi.getSegmentAudioUrl(project.id, seg.id)} jobId={seg.id} />
)} )}