5c45629495
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4m37s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4m37s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 4m50s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 5m5s
AI Code Review / AI Code Review (pull_request) Successful in 7m56s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m40s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 8m15s
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m37s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m48s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m55s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 10m20s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m1s
CI/CD Pipeline / Retag skipped Staging API Image (push) Successful in 2m18s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1m49s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Successful in 2m16s
CI/CD Pipeline / Integration Tests (push) Successful in 4m27s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m0s
CI/CD Pipeline / Unit Tests (push) Successful in 15m57s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (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 / ACR Image Cleanup (push) Successful in 2m25s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m10s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m44s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
277 lines
8.5 KiB
TypeScript
277 lines
8.5 KiB
TypeScript
/**
|
|
* 视频库页面 — V21 设计系统
|
|
* 两栏布局:左侧视频库列表(260px)+ 右侧素材网格
|
|
*
|
|
* 主组件仅保留 Hook 组装与整体布局
|
|
* 数据查询 → hooks/useAssetsData
|
|
* 库管理 → hooks/useLibraryManagement
|
|
* 上传 → hooks/useAssetUpload
|
|
* 选中态 → hooks/useAssetSelection
|
|
* 素材操作 → hooks/useAssetOperations
|
|
* 上传区 → components/AssetUploadZone
|
|
* 网格区 → components/AssetGridSection
|
|
* 弹窗集合 → components/AssetModals
|
|
*/
|
|
import React, { useState } from "react"
|
|
import type { AssetItem } from "@/pages/assets/types"
|
|
import type { SmartViewType } from "@/pages/assets/components/BatchMarkModal"
|
|
import { SkeletonCard } from "@/pages/assets/components/AssetSkeleton"
|
|
import LibrarySidebar from "@/pages/assets/components/LibrarySidebar"
|
|
import AssetFilterBar from "@/pages/assets/components/AssetFilterBar"
|
|
import BatchOperationBar from "@/pages/assets/components/BatchOperationBar"
|
|
import AssetUploadZone from "@/pages/assets/components/AssetUploadZone"
|
|
import UploadQueuePanel from "@/pages/assets/components/UploadQueuePanel"
|
|
import AssetGridSection from "@/pages/assets/components/AssetGridSection"
|
|
import AssetModals from "@/pages/assets/components/AssetModals"
|
|
import { useAssetsData } from "@/pages/assets/hooks/useAssetsData"
|
|
import { useLibraryManagement } from "@/pages/assets/hooks/useLibraryManagement"
|
|
import { useAssetUpload } from "@/pages/assets/hooks/useAssetUpload"
|
|
import { useAssetSelection } from "@/pages/assets/hooks/useAssetSelection"
|
|
import { useAssetOperations } from "@/pages/assets/hooks/useAssetOperations"
|
|
import "./assets.css"
|
|
|
|
const AssetLibrary: React.FC = () => {
|
|
/* ── 数据查询与筛选 ── */
|
|
const {
|
|
libraries,
|
|
libLoading,
|
|
activeLibId,
|
|
setActiveLibId,
|
|
effectiveLibId,
|
|
assetsLoading,
|
|
assetsError,
|
|
assetsErrorObj,
|
|
refetchAssets,
|
|
searchText,
|
|
setSearchText,
|
|
filterType,
|
|
setFilterType,
|
|
filterTime,
|
|
setFilterTime,
|
|
filteredAssets,
|
|
} = useAssetsData()
|
|
|
|
/* ── 视频库管理 ── */
|
|
const {
|
|
createModalOpen,
|
|
setCreateModalOpen,
|
|
newLibName,
|
|
setNewLibName,
|
|
newLibKind,
|
|
setNewLibKind,
|
|
isCreating,
|
|
handleCreateLibrary,
|
|
handleDeleteLibrary,
|
|
} = useLibraryManagement({
|
|
libraries,
|
|
activeLibId,
|
|
setActiveLibId,
|
|
effectiveLibId,
|
|
})
|
|
|
|
/* ── 上传 ── */
|
|
const {
|
|
uploadItems,
|
|
enqueueUploads,
|
|
retryUpload,
|
|
removeUpload,
|
|
clearFinished,
|
|
uploading,
|
|
activeCount,
|
|
pendingCount,
|
|
} = useAssetUpload({ effectiveLibId })
|
|
|
|
/* ── 上传中 asset_id → 进度/状态映射,合并进网格卡片展示真实进度 ── */
|
|
const uploadProgressMap = React.useMemo(() => {
|
|
const map = new Map<string, { progress: number; uploading: boolean }>()
|
|
for (const it of uploadItems) {
|
|
if (it.assetId && (it.status === "uploading" || it.status === "ingesting")) {
|
|
map.set(it.assetId, {
|
|
progress: it.status === "ingesting" ? 100 : it.progress,
|
|
uploading: it.status === "uploading",
|
|
})
|
|
}
|
|
}
|
|
return map
|
|
}, [uploadItems])
|
|
|
|
/* ── 选中态管理 ── */
|
|
const { selectedIds, setSelectedIds, toggleSelect, selectAll, deselectAll } = useAssetSelection({
|
|
filteredAssets,
|
|
})
|
|
|
|
/* ── 素材操作 ── */
|
|
const {
|
|
diagnosingId,
|
|
handleDiagnose,
|
|
handleSingleDelete,
|
|
batchLoading,
|
|
tagModalOpen,
|
|
setTagModalOpen,
|
|
batchTagInput,
|
|
setBatchTagInput,
|
|
batchTags,
|
|
setBatchTags,
|
|
tagMode,
|
|
setTagMode,
|
|
handleBatchTag,
|
|
handleTagInputKeyDown,
|
|
removeBatchTag,
|
|
classifyModalOpen,
|
|
setClassifyModalOpen,
|
|
batchCategory,
|
|
setBatchCategory,
|
|
handleBatchClassify,
|
|
markModalOpen,
|
|
setMarkModalOpen,
|
|
batchSmartView,
|
|
setBatchSmartView,
|
|
handleBatchMark,
|
|
handleBatchDelete,
|
|
resultDrawerOpen,
|
|
operationResult,
|
|
operationTitle,
|
|
handleResultDrawerClose,
|
|
} = useAssetOperations({ selectedIds, setSelectedIds })
|
|
|
|
/* ── 视频播放 ── */
|
|
const [playingAsset, setPlayingAsset] = useState<AssetItem | null>(null)
|
|
|
|
// ── Loading 状态 ──
|
|
if (libLoading) {
|
|
return (
|
|
<div className="xx-assets-page">
|
|
<div className="xx-assets-skeleton-grid">
|
|
{Array.from({ length: 8 }).map((_, i) => (
|
|
<SkeletonCard key={i} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="xx-assets-page">
|
|
{/* 两栏布局 */}
|
|
<div className="xx-assets-layout">
|
|
{/* ─── 左侧:视频库列表 ─── */}
|
|
<LibrarySidebar
|
|
libraries={libraries}
|
|
activeLibId={effectiveLibId}
|
|
onSelect={setActiveLibId}
|
|
onDelete={handleDeleteLibrary}
|
|
onCreateClick={() => setCreateModalOpen(true)}
|
|
/>
|
|
|
|
{/* ─── 右侧:内容区 ─── */}
|
|
<div className="xx-assets-content">
|
|
{/* 上传区域 */}
|
|
<AssetUploadZone
|
|
uploading={uploading}
|
|
activeCount={activeCount}
|
|
pendingCount={pendingCount}
|
|
onUpload={enqueueUploads}
|
|
/>
|
|
|
|
{/* 上传队列:独立进度 + 失败重试/移除 */}
|
|
<UploadQueuePanel
|
|
items={uploadItems}
|
|
onRetry={retryUpload}
|
|
onRemove={removeUpload}
|
|
onClearFinished={clearFinished}
|
|
/>
|
|
|
|
{/* 筛选栏 */}
|
|
<AssetFilterBar
|
|
searchText={searchText}
|
|
onSearchChange={setSearchText}
|
|
filterType={filterType}
|
|
onFilterTypeChange={setFilterType}
|
|
filterTime={filterTime}
|
|
onFilterTimeChange={setFilterTime}
|
|
onSelectAll={selectAll}
|
|
totalCount={filteredAssets.length}
|
|
/>
|
|
|
|
{/* 批量操作栏 */}
|
|
{selectedIds.size > 0 && (
|
|
<BatchOperationBar
|
|
selectedCount={selectedIds.size}
|
|
onDeselectAll={deselectAll}
|
|
onTagClick={() => setTagModalOpen(true)}
|
|
onClassifyClick={() => setClassifyModalOpen(true)}
|
|
onMarkClick={() => setMarkModalOpen(true)}
|
|
onBatchDelete={handleBatchDelete}
|
|
/>
|
|
)}
|
|
|
|
{/* 素材网格 */}
|
|
<AssetGridSection
|
|
loading={assetsLoading}
|
|
error={assetsError}
|
|
errorMessage={assetsErrorObj?.message}
|
|
assets={filteredAssets}
|
|
selectedIds={selectedIds}
|
|
diagnosingId={diagnosingId}
|
|
uploadProgressMap={uploadProgressMap}
|
|
onRetry={refetchAssets}
|
|
onToggleSelect={toggleSelect}
|
|
onDiagnose={handleDiagnose}
|
|
onPlay={setPlayingAsset}
|
|
onDelete={handleSingleDelete}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ─── 弹窗集合 ─── */}
|
|
<AssetModals
|
|
createModalOpen={createModalOpen}
|
|
onCreateModalCancel={() => setCreateModalOpen(false)}
|
|
onCreateModalOk={handleCreateLibrary}
|
|
newLibName={newLibName}
|
|
onNewLibNameChange={setNewLibName}
|
|
newLibKind={newLibKind}
|
|
onNewLibKindChange={setNewLibKind}
|
|
createLoading={isCreating}
|
|
playingAsset={playingAsset}
|
|
onPlayClose={() => setPlayingAsset(null)}
|
|
tagModalOpen={tagModalOpen}
|
|
selectedCount={selectedIds.size}
|
|
onTagCancel={() => {
|
|
setTagModalOpen(false)
|
|
setBatchTags([])
|
|
setBatchTagInput("")
|
|
}}
|
|
onTagOk={handleBatchTag}
|
|
batchTags={batchTags}
|
|
batchTagInput={batchTagInput}
|
|
onTagInputChange={setBatchTagInput}
|
|
onTagInputKeyDown={handleTagInputKeyDown}
|
|
onRemoveTag={removeBatchTag}
|
|
tagMode={tagMode as "add" | "replace"}
|
|
onTagModeChange={setTagMode as (mode: "add" | "replace") => void}
|
|
batchLoading={batchLoading}
|
|
classifyModalOpen={classifyModalOpen}
|
|
onClassifyCancel={() => {
|
|
setClassifyModalOpen(false)
|
|
setBatchCategory("")
|
|
}}
|
|
onClassifyOk={handleBatchClassify}
|
|
batchCategory={batchCategory}
|
|
onCategoryChange={setBatchCategory}
|
|
markModalOpen={markModalOpen}
|
|
onMarkCancel={() => setMarkModalOpen(false)}
|
|
onMarkOk={handleBatchMark}
|
|
batchSmartView={batchSmartView as SmartViewType}
|
|
onSmartViewChange={setBatchSmartView as (val: SmartViewType) => void}
|
|
resultDrawerOpen={resultDrawerOpen}
|
|
operationTitle={operationTitle}
|
|
operationResult={operationResult}
|
|
onResultDrawerClose={handleResultDrawerClose}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default AssetLibrary
|