Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4642130a04 | |||
| 66e7faa06a | |||
| 2f92398212 | |||
| c9449a1dbd |
@@ -0,0 +1,76 @@
|
||||
import React from "react"
|
||||
import type { MediaAsset } from "@/api/template-editor"
|
||||
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS } from "@/api/template-editor"
|
||||
import { formatSize, formatDuration, getQualityLevel } from "../utils"
|
||||
|
||||
export interface AssetCardProps {
|
||||
asset: MediaAsset
|
||||
isSelected: boolean
|
||||
isDragging: boolean
|
||||
isDragOver: boolean
|
||||
showBatchSelect: boolean
|
||||
onToggleSelect: (e: React.MouseEvent) => void
|
||||
onMouseEnter: (e: React.MouseEvent) => void
|
||||
onMouseLeave: () => void
|
||||
}
|
||||
|
||||
export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
asset,
|
||||
isSelected,
|
||||
isDragging,
|
||||
isDragOver,
|
||||
showBatchSelect,
|
||||
onToggleSelect,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
}) => {
|
||||
const qualityLevel = getQualityLevel(asset.quality_score)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 缩略图 */}
|
||||
<div className="as-card-thumb">
|
||||
{asset.thumbnail_url ? (
|
||||
<img src={asset.thumbnail_url} alt={asset.name} loading="lazy" />
|
||||
) : (
|
||||
<span className="as-card-thumb-icon">{MATERIAL_TYPE_ICONS[asset.type]}</span>
|
||||
)}
|
||||
|
||||
{/* Checkbox */}
|
||||
{showBatchSelect && (
|
||||
<span
|
||||
data-checkbox
|
||||
className={`as-card-checkbox${isSelected ? " checked" : ""}`}
|
||||
onClick={onToggleSelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 类型角标 */}
|
||||
<span className="as-card-type-badge">{MATERIAL_TYPE_LABELS[asset.type]}</span>
|
||||
|
||||
{/* 时长角标 */}
|
||||
{asset.duration != null && (
|
||||
<span className="as-card-duration">{formatDuration(asset.duration)}</span>
|
||||
)}
|
||||
|
||||
{/* 质量分角标 */}
|
||||
{asset.quality_score != null && (
|
||||
<span
|
||||
className={`as-card-quality ${qualityLevel}`}
|
||||
title={`质量分: ${asset.quality_score}`}
|
||||
>
|
||||
{asset.quality_score}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 信息 */}
|
||||
<div className="as-card-info">
|
||||
<p className="as-card-name" title={asset.name}>
|
||||
{asset.name}
|
||||
</p>
|
||||
<div className="as-card-meta">{formatSize(asset.size)}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react"
|
||||
import type { MediaAsset } from "@/api/template-editor"
|
||||
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS } from "@/api/template-editor"
|
||||
import { formatSize, formatDuration, getQualityColor } from "../utils"
|
||||
|
||||
export interface AssetListItemProps {
|
||||
asset: MediaAsset
|
||||
isSelected: boolean
|
||||
isDragging: boolean
|
||||
isDragOver: boolean
|
||||
showBatchSelect: boolean
|
||||
onToggleSelect: (e: React.MouseEvent) => void
|
||||
onMouseEnter: (e: React.MouseEvent) => void
|
||||
onMouseLeave: () => void
|
||||
}
|
||||
|
||||
export const AssetListItem: React.FC<AssetListItemProps> = ({
|
||||
asset,
|
||||
isSelected,
|
||||
isDragging,
|
||||
isDragOver,
|
||||
showBatchSelect,
|
||||
onToggleSelect,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* 拖拽手柄 */}
|
||||
<span className="as-list-item-drag" title="拖拽排序">
|
||||
⠿
|
||||
</span>
|
||||
|
||||
{/* Checkbox */}
|
||||
{showBatchSelect && (
|
||||
<span
|
||||
data-checkbox
|
||||
className={`as-list-item-checkbox${isSelected ? " checked" : ""}`}
|
||||
onClick={onToggleSelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 图标 */}
|
||||
<span className="as-list-item-icon">{MATERIAL_TYPE_ICONS[asset.type]}</span>
|
||||
|
||||
{/* 信息 */}
|
||||
<div className="as-list-item-info">
|
||||
<div className="as-list-item-name">{asset.name}</div>
|
||||
<div className="as-list-item-meta">
|
||||
{MATERIAL_TYPE_LABELS[asset.type]}
|
||||
{asset.duration != null && ` · ${formatDuration(asset.duration)}`}
|
||||
{asset.size != null && ` · ${formatSize(asset.size)}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 质量分 */}
|
||||
{asset.quality_score != null && (
|
||||
<span
|
||||
className="as-list-item-quality"
|
||||
style={{ color: getQualityColor(asset.quality_score) }}
|
||||
>
|
||||
{asset.quality_score}分
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { MediaAsset } from "@/api/template-editor"
|
||||
|
||||
export interface AssetSelectorProps {
|
||||
assets: MediaAsset[]
|
||||
selectedIds?: string[]
|
||||
onSelectionChange?: (ids: string[]) => void
|
||||
onAssetDragStart?: (asset: MediaAsset) => void
|
||||
onReorder?: (fromIdx: number, toIdx: number) => void
|
||||
showQualityFilter?: boolean
|
||||
showBatchSelect?: boolean
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export type ViewMode = "grid" | "list"
|
||||
@@ -0,0 +1,41 @@
|
||||
/** 格式化文件大小 */
|
||||
export const formatSize = (bytes?: number): string => {
|
||||
if (!bytes) return ""
|
||||
if (bytes < 1024) return `${bytes}B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`
|
||||
}
|
||||
|
||||
/** 格式化时长 */
|
||||
export const formatDuration = (seconds?: number): string => {
|
||||
if (!seconds) return ""
|
||||
const m = Math.floor(seconds / 60)
|
||||
const s = Math.floor(seconds % 60)
|
||||
return m > 0 ? `${m}:${s.toString().padStart(2, "0")}` : `${s}s`
|
||||
}
|
||||
|
||||
/** 获取质量分等级 */
|
||||
export const getQualityLevel = (score?: number): string => {
|
||||
if (score == null) return "none"
|
||||
if (score >= 90) return "excellent"
|
||||
if (score >= 70) return "good"
|
||||
if (score >= 50) return "fair"
|
||||
return "poor"
|
||||
}
|
||||
|
||||
/** 质量分颜色 */
|
||||
export const getQualityColor = (score?: number): string => {
|
||||
if (score == null) return "var(--text-secondary)"
|
||||
if (score >= 90) return "var(--success-color, #10b981)"
|
||||
if (score >= 70) return "var(--primary-color, #6366f1)"
|
||||
if (score >= 50) return "var(--warning-color, #f59e0b)"
|
||||
return "var(--error-color, #ef4444)"
|
||||
}
|
||||
|
||||
/** 类型筛选选项 */
|
||||
export const TYPE_OPTIONS = [
|
||||
{ value: "", label: "全部类型" },
|
||||
{ value: "video", label: "🎬 视频" },
|
||||
{ value: "image", label: "🖼️ 图片" },
|
||||
{ value: "audio", label: "🎵 音频" },
|
||||
]
|
||||
Reference in New Issue
Block a user