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