展开收起动画

This commit is contained in:
董宏凯 committed 2026-05-20 15:09:50 +08:00
1 parent 0196b0a445
commit 13130c7f27
1 file changed
+62 -18
@@ -5,7 +5,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { buildIntranetSourceUrl } from '@/core/config'; import { buildIntranetSourceUrl } from '@/core/config';
import type { CompletedIntervention } from '../../types/ai-writing'; import type { AIMaterial, CompletedIntervention } from '../../types/ai-writing';
import { OutlineCard } from './OutlineCard'; import { OutlineCard } from './OutlineCard';
import { ReviewCard } from './ReviewCard'; import { ReviewCard } from './ReviewCard';
import { ScoreBar } from './ScoreBar'; import { ScoreBar } from './ScoreBar';
@@ -65,36 +65,80 @@ function MaterialCompleted({ record, expanded, onToggle }: { record: CompletedIn
{expanded && record.materials && record.materials.length > 0 && ( {expanded && record.materials && record.materials.length > 0 && (
<div className="px-2.5 pb-2.5 pt-1 space-y-1.5 border-t border-green-200/40 dark:border-green-900/20"> <div className="px-2.5 pb-2.5 pt-1 space-y-1.5 border-t border-green-200/40 dark:border-green-900/20">
{record.materials.map((m) => { {record.materials.map((m) => (
const pct = Math.round(m.relevanceScore * 100); <CompletedMaterialItem key={m.id} material={m} />
))}
</div>
)}
</div>
);
}
function relevanceLabel(score: number): string {
if (score >= 0.8) return '高相关';
if (score >= 0.5) return '中相关';
return '低相关';
}
function CompletedMaterialItem({ material }: { material: AIMaterial }) {
const [open, setOpen] = useState(false);
const pct = Math.round(material.relevanceScore * 100);
const barColor = const barColor =
m.relevanceScore >= 0.8 ? 'bg-green-500' material.relevanceScore >= 0.8 ? 'bg-green-500'
: m.relevanceScore >= 0.5 ? 'bg-orange-400' : material.relevanceScore >= 0.5 ? 'bg-orange-400'
: 'bg-red-400'; : 'bg-red-400';
const hasDetail = !!material.content || !!material.recUuid;
return ( return (
<div key={m.id} className="rounded border border-border/50 bg-background px-2.5 py-1.5 space-y-1"> <div className="rounded border border-border/50 bg-background overflow-hidden">
<p className="font-medium leading-snug line-clamp-2">{m.title}</p> <button
type="button"
onClick={hasDetail ? () => setOpen(v => !v) : undefined}
className={cn(
'w-full px-2.5 py-1.5 space-y-1 text-left',
hasDetail && 'hover:bg-muted/30 transition-colors',
)}
>
<div className="flex items-start gap-1.5">
<span className="flex-1 font-medium leading-snug line-clamp-2">{material.title}</span>
{hasDetail && (
open
? <ChevronDown className="mt-0.5 size-3 shrink-0 text-muted-foreground" />
: <ChevronRight className="mt-0.5 size-3 shrink-0 text-muted-foreground" />
)}
</div>
<div className="flex items-center gap-2 text-muted-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
<span className="truncate max-w-[100px]">{m.source}</span> <span className="truncate max-w-[100px]">{material.source}</span>
{m.publishedAt && <><span className="text-muted-foreground/40">·</span><span>{m.publishedAt}</span></>} {material.publishedAt && <><span className="text-muted-foreground/40">·</span><span>{material.publishedAt}</span></>}
<div className="flex flex-1 items-center gap-1 min-w-0"> <div className="flex flex-1 items-center gap-1 min-w-0">
<div className="flex-1 h-1 rounded-full bg-muted overflow-hidden min-w-[40px]"> <div className="flex-1 h-1 rounded-full bg-muted overflow-hidden min-w-[40px]">
<div className={cn('h-full rounded-full', barColor)} style={{ width: `${pct}%` }} /> <div className={cn('h-full rounded-full', barColor)} style={{ width: `${pct}%` }} />
</div> </div>
<span className={cn('shrink-0 text-[10px]', barColor.replace('bg-', 'text-'))}>{pct}%</span> <span className={cn('shrink-0 text-[10px]', barColor.replace('bg-', 'text-'))}>{pct}%</span>
<span className="text-muted-foreground/60 shrink-0 text-[10px]">({relevanceLabel(material.relevanceScore)})</span>
</div> </div>
{m.recUuid && ( </div>
<button type="button" </button>
onClick={() => window.open(buildIntranetSourceUrl(m.recUuid), '_blank', 'noopener,noreferrer')}
className="shrink-0 text-primary hover:underline inline-flex items-center gap-0.5"> {open && hasDetail && (
原文 <ExternalLink className="size-2.5" /> <div className="px-2.5 pb-2 pt-1.5 space-y-1.5 border-t border-border/40">
{material.content && (
<p className="text-muted-foreground leading-relaxed line-clamp-6">
{material.content.slice(0, 300)}
{material.content.length > 300 && '…'}
</p>
)}
{material.recUuid && (
<button
type="button"
onClick={() => window.open(buildIntranetSourceUrl(material.recUuid), '_blank', 'noopener,noreferrer')}
className="inline-flex items-center gap-1 text-primary hover:underline"
>
查看原文
<ExternalLink className="size-2.5" />
</button> </button>
)} )}
</div> </div>
</div>
);
})}
</div>
)} )}
</div> </div>
); );