diff --git a/apps/web/src/pages/products/ProductLibrary.tsx b/apps/web/src/pages/products/ProductLibrary.tsx
old mode 100755
new mode 100644
index af70965eb..d2c4ca906
--- a/apps/web/src/pages/products/ProductLibrary.tsx
+++ b/apps/web/src/pages/products/ProductLibrary.tsx
@@ -1,38 +1,29 @@
/**
* 成片库页面 — V21 设计系统
* 卡片网格布局,支持视频播放/下载/分享、批量操作、筛选
- * 使用 useQuery 对接后端真实 API(api/products.ts)
*
- * 代码结构(三阶段重构后):
- * - types.ts: 类型定义
- * - constants.ts: 常量配置
- * - utils/index.ts: 工具函数
- * - components/ProductCard.tsx: 产品卡片组件
- * - components/VideoPlayer.tsx: 视频播放器组件
- * - hooks/useProductList.ts: 列表查询与筛选
- * - hooks/useProductActions.ts: 单个/批量操作
+ * 主组件仅保留 Hook 组装与整体布局
+ * 列表查询 → hooks/useProductList
+ * 操作逻辑 → hooks/useProductActions
+ * 筛选栏 → components/ProductFilterBar
+ * 批量操作栏 → components/ProductBatchBar
+ * 空状态 → components/ProductEmptyState
+ * 产品卡片 → components/ProductCard
+ * 视频播放 → components/VideoPlayer
*/
import React, { useState } from "react"
-import { Popconfirm, message } from "antd"
-import {
- SearchOutlined,
- VideoCameraOutlined,
- DownloadOutlined,
- DeleteOutlined,
- CheckOutlined,
- CloudUploadOutlined,
-} from "@ant-design/icons"
-import { Button, Input, Select } from "@/components/ui"
+import { VideoCameraOutlined, DownloadOutlined } from "@ant-design/icons"
+import { Button } from "@/components/ui"
import type { ProductItem } from "./types"
import { ProductCard } from "./components/ProductCard"
import { VideoPlayer } from "./components/VideoPlayer"
+import { ProductFilterBar } from "./components/ProductFilterBar"
+import { ProductBatchBar } from "./components/ProductBatchBar"
+import { ProductEmptyState } from "./components/ProductEmptyState"
import { useProductList } from "./hooks/useProductList"
import { useProductActions } from "./hooks/useProductActions"
import "./products.css"
-/* ============================================================
- * 主组件
- * ============================================================ */
const ProductLibrary: React.FC = () => {
const {
products,
@@ -83,58 +74,20 @@ const ProductLibrary: React.FC = () => {
setPlayingProduct,
})
+ // ── Loading 状态 ──
if (isLoading) {
- return (
-
- )
+ return
}
// ── Error 状态 ──
if (isError) {
console.error("[ProductLibrary] 加载失败:", error)
const errorMsg = error?.message || "加载失败"
- // 404 视为空数据(API 尚未就绪或无数据)
const is404 = errorMsg.includes("404") || errorMsg.includes("Not Found")
if (is404) {
- return (
-
-
-
- 成片库
-
-
-
-
🎬
-
暂无成片数据
-
- 完成视频生成后,成片将自动保存到这里
-
-
-
- )
+ return
}
- return (
-
-
-
❌
-
{errorMsg || "加载失败,请稍后重试"}
-
-
-
- )
+ return
}
return (
@@ -153,129 +106,35 @@ const ProductLibrary: React.FC = () => {
{/* 批量操作栏 */}
{batchMode && (
-
-
-
- {allSelected && }
-
-
- {allSelected ? "取消全选" : "全选"}
-
-
已选择 {selectedIds.size} 项
-
-
-
}
- onClick={handleBatchDownload}
- disabled={batchDownloading}
- >
- {batchDownloading ? "打包中..." : "批量下载"}
-
-
}
- onClick={handleBatchPublish}
- >
- 批量发布
-
-
- }>
- 批量删除
-
-
-
-
-
+
)}
{/* 筛选栏 */}
-
-
- }
- value={searchText}
- onChange={(e) => setSearchText(e.target.value)}
- allowClear
- style={{ width: 220 }}
- />
-
-
-
-
-
- 共 {filteredProducts.length} 个成片
-
-
-
+
{/* 卡片网格 */}
{filteredProducts.length > 0 ? (
@@ -297,19 +156,7 @@ const ProductLibrary: React.FC = () => {
))}
) : (
-
-
-
-
-
暂无成片,去智能剪辑吧
-
-
+
)}
{/* 视频播放弹窗 */}
diff --git a/apps/web/src/pages/products/components/ProductBatchBar.tsx b/apps/web/src/pages/products/components/ProductBatchBar.tsx
new file mode 100644
index 000000000..4eaedc95d
--- /dev/null
+++ b/apps/web/src/pages/products/components/ProductBatchBar.tsx
@@ -0,0 +1,85 @@
+/**
+ * ProductLibrary 批量操作栏
+ */
+import React from "react"
+import {
+ CheckOutlined,
+ DownloadOutlined,
+ DeleteOutlined,
+ CloudUploadOutlined,
+} from "@ant-design/icons"
+import { Button } from "@/components/ui"
+import { Popconfirm } from "antd"
+
+export interface ProductBatchBarProps {
+ allSelected: boolean
+ selectedCount: number
+ batchDownloading: boolean
+ onSelectAll: () => void
+ onBatchDownload: () => void
+ onBatchPublish: () => void
+ onBatchDelete: () => void
+ onClearSelection: () => void
+}
+
+export const ProductBatchBar: React.FC = ({
+ allSelected,
+ selectedCount,
+ batchDownloading,
+ onSelectAll,
+ onBatchDownload,
+ onBatchPublish,
+ onBatchDelete,
+ onClearSelection,
+}) => {
+ return (
+
+
+
+ {allSelected && }
+
+
+ {allSelected ? "取消全选" : "全选"}
+
+
已选择 {selectedCount} 项
+
+
+
}
+ onClick={onBatchDownload}
+ disabled={batchDownloading}
+ >
+ {batchDownloading ? "打包中..." : "批量下载"}
+
+
}
+ onClick={onBatchPublish}
+ >
+ 批量发布
+
+
+ }>
+ 批量删除
+
+
+
+
+
+ )
+}
+
+export default ProductBatchBar
diff --git a/apps/web/src/pages/products/components/ProductEmptyState.tsx b/apps/web/src/pages/products/components/ProductEmptyState.tsx
new file mode 100644
index 000000000..1ac3c3923
--- /dev/null
+++ b/apps/web/src/pages/products/components/ProductEmptyState.tsx
@@ -0,0 +1,92 @@
+/**
+ * ProductLibrary 空状态/加载/错误页面
+ */
+import React from "react"
+import { VideoCameraOutlined } from "@ant-design/icons"
+import { Button } from "@/components/ui"
+import { message } from "antd"
+
+export type ProductEmptyStateType = "loading" | "empty" | "404" | "error"
+
+export interface ProductEmptyStateProps {
+ type: ProductEmptyStateType
+ errorMessage?: string
+ onRetry?: () => void
+}
+
+export const ProductEmptyState: React.FC = ({
+ type,
+ errorMessage,
+ onRetry,
+}) => {
+ if (type === "loading") {
+ return (
+
+ )
+ }
+
+ if (type === "404") {
+ return (
+
+
+
+ 成片库
+
+
+
+
🎬
+
暂无成片数据
+
+ 完成视频生成后,成片将自动保存到这里
+
+
+
+ )
+ }
+
+ if (type === "error") {
+ return (
+
+
+
❌
+
{errorMessage || "加载失败,请稍后重试"}
+ {onRetry && (
+
+ )}
+
+
+ )
+ }
+
+ // empty
+ return (
+
+
+
+
+
暂无成片,去智能剪辑吧
+
+
+ )
+}
+
+export default ProductEmptyState
diff --git a/apps/web/src/pages/products/components/ProductFilterBar.tsx b/apps/web/src/pages/products/components/ProductFilterBar.tsx
new file mode 100644
index 000000000..ac95f3699
--- /dev/null
+++ b/apps/web/src/pages/products/components/ProductFilterBar.tsx
@@ -0,0 +1,119 @@
+/**
+ * ProductLibrary 筛选栏
+ */
+import React from "react"
+import { SearchOutlined } from "@ant-design/icons"
+import { Input, Select } from "@/components/ui"
+
+export interface ProductFilterBarProps {
+ searchText: string
+ onSearchChange: (text: string) => void
+ filterStatus: string
+ onFilterStatusChange: (val: string) => void
+ filterTime: string
+ onFilterTimeChange: (val: string) => void
+ filterDuration: string
+ onFilterDurationChange: (val: string) => void
+ filterProject: string
+ onFilterProjectChange: (val: string) => void
+ filterReviewStatus: string
+ onFilterReviewStatusChange: (val: string) => void
+ projectOptions: Array<{ value: string; label: string }>
+ resultCount: number
+}
+
+export const ProductFilterBar: React.FC = ({
+ searchText,
+ onSearchChange,
+ filterStatus,
+ onFilterStatusChange,
+ filterTime,
+ onFilterTimeChange,
+ filterDuration,
+ onFilterDurationChange,
+ filterProject,
+ onFilterProjectChange,
+ filterReviewStatus,
+ onFilterReviewStatusChange,
+ projectOptions,
+ resultCount,
+}) => {
+ return (
+
+
+ }
+ value={searchText}
+ onChange={(e) => onSearchChange(e.target.value)}
+ allowClear
+ style={{ width: 220 }}
+ />
+
+
+ 3分钟" },
+ ]}
+ />
+
+
+
+
+
+ 共 {resultCount} 个成片
+
+
+
+ )
+}
+
+export default ProductFilterBar