diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx index c6adc7683..d327dd0bf 100644 --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -1,25 +1,27 @@ /** * 视频库页面 — V21 设计系统 * 两栏布局:左侧视频库列表(260px)+ 右侧素材网格 - * 使用 useQuery 对接后端真实 API(api/assets.ts) + * + * 主组件仅保留 Hook 组装与整体布局 + * 数据查询 → hooks/useAssetsData + * 库管理 → hooks/useLibraryManagement + * 上传 → hooks/useAssetUpload + * 选中态 → hooks/useAssetSelection + * 素材操作 → hooks/useAssetOperations + * 上传区 → components/AssetUploadZone + * 网格区 → components/AssetGridSection + * 弹窗集合 → components/AssetModals */ import React, { useState } from "react" -import { Upload } from "antd" -import { InboxOutlined, PictureOutlined, ExclamationCircleOutlined } from "@ant-design/icons" -import { Button } from "@/components/ui" import type { AssetItem } from "@/pages/assets/types" -import AssetCard from "@/pages/assets/components/AssetCard" +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 CreateLibraryModal from "@/pages/assets/components/CreateLibraryModal" -import PlayModal from "@/pages/assets/components/PlayModal" -import BatchTagModal from "@/pages/assets/components/BatchTagModal" -import BatchClassifyModal from "@/pages/assets/components/BatchClassifyModal" -import BatchMarkModal from "@/pages/assets/components/BatchMarkModal" -import ResultDrawer from "@/pages/assets/components/ResultDrawer" -import UploadProgressModal from "@/pages/assets/components/UploadProgressModal" +import AssetUploadZone from "@/pages/assets/components/AssetUploadZone" +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" @@ -27,9 +29,6 @@ import { useAssetSelection } from "@/pages/assets/hooks/useAssetSelection" import { useAssetOperations } from "@/pages/assets/hooks/useAssetOperations" import "./assets.css" -/* ============================================================ - * 主组件 - * ============================================================ */ const AssetLibrary: React.FC = () => { /* ── 数据查询与筛选 ── */ const { @@ -129,9 +128,6 @@ const AssetLibrary: React.FC = () => { return (
- {/* ─── 上传进度弹窗 ─── */} - - {/* 两栏布局 */}
{/* ─── 左侧:视频库列表 ─── */} @@ -146,25 +142,11 @@ const AssetLibrary: React.FC = () => { {/* ─── 右侧:内容区 ─── */}
{/* 上传区域 */} - { - handleUpload(file as File) - return false - }} - showUploadList={false} - multiple - accept="video/*,image/*" - > -
-

- -

-

- {uploading ? "上传中..." : "点击或拖拽文件到此区域上传"} -

-

支持视频、图片,单文件不超过 2GB

-
-
+ {/* 筛选栏 */} { )} {/* 素材网格 */} - {assetsLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( - - ))} -
- ) : assetsError ? ( -
-
- -
-

{assetsErrorObj?.message || "加载失败"}

- -
- ) : filteredAssets.length > 0 ? ( -
- {filteredAssets.map((asset) => ( - toggleSelect(asset.id)} - onDiagnose={() => handleDiagnose(asset)} - onPlay={() => setPlayingAsset(asset)} - onDelete={() => handleSingleDelete(asset.id)} - /> - ))} -
- ) : ( -
-
- -
-

暂无素材,请上传或切换视频库

-
- )} +
- {/* ─── 新建视频库弹窗 ─── */} - setCreateModalOpen(false)} - onOk={handleCreateLibrary} - name={newLibName} - onNameChange={setNewLibName} - kind={newLibKind} - onKindChange={setNewLibKind} - confirmLoading={isCreating} - /> - - {/* ─── 视频/音频播放弹窗 ─── */} - setPlayingAsset(null)} /> - - {/* ─── 批量打标签弹窗 ─── */} - setCreateModalOpen(false)} + onCreateModalOk={handleCreateLibrary} + newLibName={newLibName} + onNewLibNameChange={setNewLibName} + newLibKind={newLibKind} + onNewLibKindChange={setNewLibKind} + createLoading={isCreating} + playingAsset={playingAsset} + onPlayClose={() => setPlayingAsset(null)} + tagModalOpen={tagModalOpen} selectedCount={selectedIds.size} - onCancel={() => { + onTagCancel={() => { setTagModalOpen(false) setBatchTags([]) setBatchTagInput("") }} - onOk={handleBatchTag} - tags={batchTags} + onTagOk={handleBatchTag} + batchTags={batchTags} + batchTagInput={batchTagInput} onTagInputChange={setBatchTagInput} onTagInputKeyDown={handleTagInputKeyDown} onRemoveTag={removeBatchTag} - tagInput={batchTagInput} - tagMode={tagMode} - onTagModeChange={setTagMode} - confirmLoading={batchLoading} - /> - - {/* ─── 批量改分类弹窗 ─── */} - { + tagMode={tagMode as "add" | "replace"} + onTagModeChange={setTagMode as (mode: "add" | "replace") => void} + batchLoading={batchLoading} + classifyModalOpen={classifyModalOpen} + onClassifyCancel={() => { setClassifyModalOpen(false) setBatchCategory("") }} - onOk={handleBatchClassify} - category={batchCategory} + onClassifyOk={handleBatchClassify} + batchCategory={batchCategory} onCategoryChange={setBatchCategory} - confirmLoading={batchLoading} - /> - - {/* ─── 批量智能标记弹窗 ─── */} - setMarkModalOpen(false)} - onOk={handleBatchMark} - smartView={batchSmartView} - onSmartViewChange={setBatchSmartView} - confirmLoading={batchLoading} - /> - - {/* ─── 操作结果 Drawer ─── */} - setMarkModalOpen(false)} + onMarkOk={handleBatchMark} + batchSmartView={batchSmartView as SmartViewType} + onSmartViewChange={setBatchSmartView as (val: SmartViewType) => void} + resultDrawerOpen={resultDrawerOpen} + operationTitle={operationTitle} + operationResult={operationResult} + onResultDrawerClose={handleResultDrawerClose} />
) diff --git a/apps/web/src/pages/assets/components/AssetGridSection.tsx b/apps/web/src/pages/assets/components/AssetGridSection.tsx new file mode 100644 index 000000000..81f206330 --- /dev/null +++ b/apps/web/src/pages/assets/components/AssetGridSection.tsx @@ -0,0 +1,93 @@ +/** + * AssetLibrary 素材网格区域(含加载/错误/空状态) + */ +import React from "react" +import { PictureOutlined, ExclamationCircleOutlined } from "@ant-design/icons" +import { Button } from "@/components/ui" +import type { AssetItem } from "../types" +import AssetCard from "./AssetCard" +import { SkeletonCard } from "./AssetSkeleton" + +export interface AssetGridSectionProps { + loading: boolean + error: boolean + errorMessage?: string + assets: AssetItem[] + selectedIds: Set + diagnosingId: string | null + onRetry?: () => void + onToggleSelect: (id: string) => void + onDiagnose: (asset: AssetItem) => void + onPlay: (asset: AssetItem) => void + onDelete: (id: string) => void +} + +export const AssetGridSection: React.FC = ({ + loading, + error, + errorMessage, + assets, + selectedIds, + diagnosingId, + onRetry, + onToggleSelect, + onDiagnose, + onPlay, + onDelete, +}) => { + if (loading) { + return ( +
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))} +
+ ) + } + + if (error) { + return ( +
+
+ +
+

{errorMessage || "加载失败"}

+ {onRetry && ( + + )} +
+ ) + } + + if (assets.length > 0) { + return ( +
+ {assets.map((asset) => ( + onToggleSelect(asset.id)} + onDiagnose={() => onDiagnose(asset)} + onPlay={() => onPlay(asset)} + onDelete={() => onDelete(asset.id)} + /> + ))} +
+ ) + } + + return ( +
+
+ +
+

暂无素材,请上传或切换视频库

+
+ ) +} + +export default AssetGridSection diff --git a/apps/web/src/pages/assets/components/AssetModals.tsx b/apps/web/src/pages/assets/components/AssetModals.tsx new file mode 100644 index 000000000..3fb4e7314 --- /dev/null +++ b/apps/web/src/pages/assets/components/AssetModals.tsx @@ -0,0 +1,179 @@ +/** + * AssetLibrary 弹窗集合 + */ +import React from "react" +import type { AssetItem, AssetKind } from "../types" +import type { SmartViewType } from "./BatchMarkModal" +import type { BatchOperationResult } from "@/api/assets" +import CreateLibraryModal from "./CreateLibraryModal" +import PlayModal from "./PlayModal" +import BatchTagModal from "./BatchTagModal" +import BatchClassifyModal from "./BatchClassifyModal" +import BatchMarkModal from "./BatchMarkModal" +import ResultDrawer from "./ResultDrawer" +import UploadProgressModal from "./UploadProgressModal" + +export interface AssetModalsProps { + /* 上传进度 */ + uploading: boolean + uploadProgress: number + + /* 新建视频库 */ + createModalOpen: boolean + onCreateModalCancel: () => void + onCreateModalOk: () => void + newLibName: string + onNewLibNameChange: (name: string) => void + newLibKind: AssetKind + onNewLibKindChange: (kind: AssetKind) => void + createLoading: boolean + + /* 播放弹窗 */ + playingAsset: AssetItem | null + onPlayClose: () => void + + /* 批量打标签 */ + tagModalOpen: boolean + selectedCount: number + onTagCancel: () => void + onTagOk: () => void + batchTags: string[] + batchTagInput: string + onTagInputChange: (val: string) => void + onTagInputKeyDown: (e: React.KeyboardEvent) => void + onRemoveTag: (tag: string) => void + tagMode: "add" | "replace" + onTagModeChange: (mode: "add" | "replace") => void + batchLoading: boolean + + /* 批量改分类 */ + classifyModalOpen: boolean + onClassifyCancel: () => void + onClassifyOk: () => void + batchCategory: string + onCategoryChange: (val: string) => void + + /* 批量智能标记 */ + markModalOpen: boolean + onMarkCancel: () => void + onMarkOk: () => void + batchSmartView: SmartViewType + onSmartViewChange: (val: SmartViewType) => void + + /* 操作结果 Drawer */ + resultDrawerOpen: boolean + operationTitle: string + operationResult: BatchOperationResult | null + onResultDrawerClose: () => void +} + +export const AssetModals: React.FC = ({ + uploading, + uploadProgress, + createModalOpen, + onCreateModalCancel, + onCreateModalOk, + newLibName, + onNewLibNameChange, + newLibKind, + onNewLibKindChange, + createLoading, + playingAsset, + onPlayClose, + tagModalOpen, + selectedCount, + onTagCancel, + onTagOk, + batchTags, + batchTagInput, + onTagInputChange, + onTagInputKeyDown, + onRemoveTag, + tagMode, + onTagModeChange, + batchLoading, + classifyModalOpen, + onClassifyCancel, + onClassifyOk, + batchCategory, + onCategoryChange, + markModalOpen, + onMarkCancel, + onMarkOk, + batchSmartView, + onSmartViewChange, + resultDrawerOpen, + operationTitle, + operationResult, + onResultDrawerClose, +}) => { + return ( + <> + {/* 上传进度弹窗 */} + + + {/* 新建视频库弹窗 */} + + + {/* 视频/音频播放弹窗 */} + + + {/* 批量打标签弹窗 */} + + + {/* 批量改分类弹窗 */} + + + {/* 批量智能标记弹窗 */} + + + {/* 操作结果 Drawer */} + + + ) +} + +export default AssetModals diff --git a/apps/web/src/pages/assets/components/AssetUploadZone.tsx b/apps/web/src/pages/assets/components/AssetUploadZone.tsx new file mode 100644 index 000000000..34f803b32 --- /dev/null +++ b/apps/web/src/pages/assets/components/AssetUploadZone.tsx @@ -0,0 +1,41 @@ +/** + * AssetLibrary 上传拖拽区域 + */ +import React from "react" +import { Upload } from "antd" +import { InboxOutlined } from "@ant-design/icons" + +export interface AssetUploadZoneProps { + uploading: boolean + uploadProgress: number + onUpload: (file: File) => void +} + +export const AssetUploadZone: React.FC = ({ + uploading, + onUpload, +}) => { + return ( + { + onUpload(file as File) + return false + }} + showUploadList={false} + multiple + accept="video/*,image/*" + > +
+

+ +

+

+ {uploading ? "上传中..." : "点击或拖拽文件到此区域上传"} +

+

支持视频、图片,单文件不超过 2GB

+
+
+ ) +} + +export default AssetUploadZone