156 lines
4.5 KiB
TypeScript
156 lines
4.5 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import MaterialCard, { Material } from './MaterialCard'
|
|
|
|
// 用户会话类型
|
|
interface UserSession {
|
|
id: string
|
|
role: string
|
|
}
|
|
|
|
interface MaterialGridProps {
|
|
materials: Material[]
|
|
displayedCount: number
|
|
session: { user: UserSession } | null
|
|
loading: boolean
|
|
isTransitionLoading: boolean
|
|
// 音频播放状态
|
|
playingAudioId: string | null
|
|
audioElement: HTMLAudioElement | null
|
|
audioProgress: number
|
|
audioCurrentTime: number
|
|
audioDuration: number
|
|
audioRef: React.RefObject<HTMLAudioElement | null>
|
|
// 下载状态
|
|
downloadingId: string | null
|
|
downloadProgress: number
|
|
// 主题色
|
|
themeColor: string
|
|
// 筛选状态(用于空状态提示)
|
|
hasFilters: boolean
|
|
// 搜索关键词(用于高亮)
|
|
searchKeyword?: string
|
|
// 事件处理
|
|
onAudioPlay: (materialId: string, audioUrl: string) => void
|
|
onDownload: (id: string) => void
|
|
onLike: (id: string) => void
|
|
onFavorite: (id: string) => void
|
|
onShare: (id: string) => void
|
|
onEdit: (material: Material) => void
|
|
onDelete: (id: string) => void
|
|
onMaterialClick: (material: Material) => void
|
|
onRetryTranscode: (materialId: string) => void
|
|
showToast: (message: string, options?: { variant?: 'success' | 'error' }) => void
|
|
}
|
|
|
|
function MaterialGridSkeleton() {
|
|
return (
|
|
<div
|
|
className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 lg:grid-cols-3"
|
|
aria-busy="true"
|
|
aria-live="polite"
|
|
aria-label="素材列表加载中"
|
|
>
|
|
<span className="sr-only">加载中...</span>
|
|
{Array.from({ length: 8 }, (_, index) => (
|
|
<article
|
|
key={index}
|
|
className="overflow-hidden"
|
|
>
|
|
<div className="aspect-[4/3] animate-pulse rounded-[12px] bg-[#eceef0]" />
|
|
<div className="space-y-3 px-4 py-4">
|
|
<div className="h-4 w-3/4 animate-pulse bg-[#eceef0]" />
|
|
<div className="h-3 w-1/2 animate-pulse bg-[#f2f3f5]" />
|
|
<div className="flex items-center justify-between pt-1">
|
|
<div className="h-3 w-20 animate-pulse bg-[#f2f3f5]" />
|
|
<div className="h-3 w-12 animate-pulse bg-[#f2f3f5]" />
|
|
</div>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function MaterialGrid({
|
|
materials,
|
|
displayedCount,
|
|
session,
|
|
loading,
|
|
isTransitionLoading,
|
|
playingAudioId,
|
|
audioElement,
|
|
audioProgress,
|
|
audioCurrentTime,
|
|
audioDuration,
|
|
audioRef,
|
|
downloadingId,
|
|
downloadProgress,
|
|
themeColor,
|
|
hasFilters,
|
|
searchKeyword,
|
|
onAudioPlay,
|
|
onDownload,
|
|
onLike,
|
|
onFavorite,
|
|
onShare,
|
|
onEdit,
|
|
onDelete,
|
|
onMaterialClick,
|
|
onRetryTranscode,
|
|
showToast,
|
|
}: MaterialGridProps) {
|
|
return (
|
|
<div className="relative">
|
|
{/* 过渡加载遮罩 */}
|
|
{isTransitionLoading && (
|
|
<div className="absolute inset-0 z-20 flex items-center justify-center bg-[#fafafa]/70 backdrop-blur-[1px]">
|
|
<div className="h-6 w-6 animate-spin rounded-full border-2 border-[#e7e8ea] border-t-[#1a1c1d]" />
|
|
</div>
|
|
)}
|
|
|
|
{loading ? (
|
|
<MaterialGridSkeleton />
|
|
) : materials.length === 0 ? (
|
|
<div className="border-t border-[#e7e8ea] py-24 text-center">
|
|
<p className="text-[15px] tracking-[-0.02em] text-[#6b7075]">
|
|
{hasFilters ? '没有找到匹配的素材' : '还没有素材,快来上传第一个吧!'}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 lg:grid-cols-3">
|
|
{materials.slice(0, displayedCount).map((material) => (
|
|
<MaterialCard
|
|
key={material.id}
|
|
material={material}
|
|
session={session}
|
|
playingAudioId={playingAudioId}
|
|
audioElement={audioElement}
|
|
audioProgress={audioProgress}
|
|
audioCurrentTime={audioCurrentTime}
|
|
audioDuration={audioDuration}
|
|
audioRef={audioRef}
|
|
downloadingId={downloadingId}
|
|
downloadProgress={downloadProgress}
|
|
themeColor={themeColor}
|
|
onAudioPlay={onAudioPlay}
|
|
onDownload={onDownload}
|
|
onLike={onLike}
|
|
onFavorite={onFavorite}
|
|
onShare={onShare}
|
|
onEdit={onEdit}
|
|
onDelete={onDelete}
|
|
onClick={onMaterialClick}
|
|
onRetryTranscode={onRetryTranscode}
|
|
showToast={showToast}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export type { Material }
|