Files
xiaoxia-saas/apps/web/src/components/asset-selector/AssetSelector.tsx
T
xiaoxia 4578b65965
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m30s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m37s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m3s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m18s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m18s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m17s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m33s
CI/CD Pipeline / Integration Tests (push) Successful in 2m22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 6m19s
CI/CD Pipeline / Unit Tests (push) Failing after 6m57s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m31s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 16s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m46s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m59s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
refactor(asset-selector): 三阶段重构 AssetSelector(504→168行, -67%) (#937)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-28 01:02:50 +08:00

160 lines
5.2 KiB
TypeScript

/**
* 素材选择器 — V21 设计系统
* 支持批量选择、拖拽排序、预览缩略图、质量分筛选、视图切换
*
* 功能:
* - 批量选择:checkbox 多选 + 全选/反选 + Shift 连选
* - 拖拽排序:HTML5 DnD,已选素材可拖拽调整顺序
* - 预览缩略图:悬浮放大预览(视频显示时长角标)
* - 筛选增强:类型筛选 + 质量分筛选
* - 视图切换:网格视图 / 列表视图
*/
import React, { useCallback } from "react"
import "./AssetSelector.css"
import type { AssetSelectorProps } from "./types"
import useAssetFilter from "./hooks/useAssetFilter"
import useAssetSelection from "./hooks/useAssetSelection"
import useDragReorder from "./hooks/useDragReorder"
import useAssetPreview from "./hooks/useAssetPreview"
import SelectorToolbar from "./SelectorToolbar"
import SelectorBatchBar from "./SelectorBatchBar"
import AssetCard from "./AssetCard"
import AssetListItem from "./AssetListItem"
import PreviewOverlay from "./PreviewOverlay"
import EmptyState from "./EmptyState"
import type { MediaAsset } from "@/api/template-editor"
const AssetSelector: React.FC<AssetSelectorProps> = ({
assets,
selectedIds = [],
onSelectionChange,
onAssetDragStart,
onReorder,
showQualityFilter = true,
showBatchSelect = true,
compact = false,
}) => {
// 搜索 & 筛选
const {
searchText,
filterType,
filterQuality,
viewMode,
filteredAssets,
setSearchText,
setFilterType,
setFilterQuality,
setViewMode,
} = useAssetFilter(assets)
// 选中操作
const { selectedSet, toggleSelect, clearSelection } = useAssetSelection({
filteredAssets,
selectedIds,
onSelectionChange,
})
// 拖拽排序
const { dragIdx, dragOverIdx, handleDragStart, handleDragOver, handleDrop, handleDragEnd } =
useDragReorder({
filteredAssets,
selectedIds,
onAssetDragStart,
onReorder,
})
// 悬浮预览
const { previewAsset, previewPos, handleMouseEnter, handleMouseLeave } = useAssetPreview()
/* ── 点击卡片 ── */
const handleCardClick = useCallback(
(asset: MediaAsset, idx: number, e: React.MouseEvent) => {
const target = e.target as HTMLElement
if (target.closest("[data-checkbox]")) return
toggleSelect(asset, idx, e.shiftKey)
},
[toggleSelect],
)
const hasSelection = selectedIds.length > 0
return (
<div className="as-container">
{/* 工具栏 */}
<SelectorToolbar
searchText={searchText}
filterType={filterType}
filterQuality={filterQuality}
viewMode={viewMode}
showQualityFilter={showQualityFilter}
onSearchChange={setSearchText}
onTypeChange={setFilterType}
onQualityChange={setFilterQuality}
onViewModeChange={setViewMode}
/>
{/* 批量操作栏 */}
{showBatchSelect && hasSelection && (
<SelectorBatchBar selectedCount={selectedIds.length} onClear={clearSelection} />
)}
{/* 素材列表 */}
<div className="as-body">
{filteredAssets.length === 0 ? (
<EmptyState />
) : viewMode === "grid" ? (
/* 网格视图 */
<div className={`as-grid${compact ? " compact" : ""}`}>
{filteredAssets.map((asset, idx) => (
<AssetCard
key={asset.id}
asset={asset}
isSelected={selectedSet.has(asset.id)}
isDragging={dragIdx === idx}
isDragOver={dragOverIdx === idx}
showCheckbox={showBatchSelect}
compact={compact}
onToggleSelect={(a, shiftKey) => toggleSelect(a, idx, shiftKey)}
onCardClick={(a, e) => handleCardClick(a, idx, e)}
onDragStart={(e) => handleDragStart(e, idx)}
onDragOver={(e) => handleDragOver(e, idx)}
onDrop={(e) => handleDrop(e, idx)}
onDragEnd={handleDragEnd}
onMouseEnter={(e) => handleMouseEnter(asset, e)}
onMouseLeave={handleMouseLeave}
/>
))}
</div>
) : (
/* 列表视图 */
<div className="as-list">
{filteredAssets.map((asset, idx) => (
<AssetListItem
key={asset.id}
asset={asset}
isSelected={selectedSet.has(asset.id)}
isDragging={dragIdx === idx}
isDragOver={dragOverIdx === idx}
showCheckbox={showBatchSelect}
onToggleSelect={(a, shiftKey) => toggleSelect(a, idx, shiftKey)}
onCardClick={(a, e) => handleCardClick(a, idx, e)}
onDragStart={(e) => handleDragStart(e, idx)}
onDragOver={(e) => handleDragOver(e, idx)}
onDrop={(e) => handleDrop(e, idx)}
onDragEnd={handleDragEnd}
onMouseEnter={(e) => handleMouseEnter(asset, e)}
onMouseLeave={handleMouseLeave}
/>
))}
</div>
)}
</div>
{/* 悬浮预览 */}
{previewAsset && <PreviewOverlay asset={previewAsset} position={previewPos} />}
</div>
)
}
export default AssetSelector