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