From b891eeab43ff6fd7ef8d0e7f91e98bb343d3bf85 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 10:09:26 +0800 Subject: [PATCH] =?UTF-8?q?refactor(templates):=20=E6=8B=86=E5=88=86=20MyT?= =?UTF-8?q?emplates=20=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=8A=BD=E7=A6=BB?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=20Hook=20+=20=E5=8D=A1=E7=89=87=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20(#1019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/my-templates/MyTemplates.tsx | 185 +++--------------- .../my-templates/components/TemplateCard.tsx | 92 +++++++++ .../my-templates/hooks/useMyTemplates.ts | 100 ++++++++++ 3 files changed, 215 insertions(+), 162 deletions(-) create mode 100644 apps/web/src/pages/my-templates/components/TemplateCard.tsx create mode 100644 apps/web/src/pages/my-templates/hooks/useMyTemplates.ts diff --git a/apps/web/src/pages/my-templates/MyTemplates.tsx b/apps/web/src/pages/my-templates/MyTemplates.tsx index cf60fafea..bacf0e9f4 100644 --- a/apps/web/src/pages/my-templates/MyTemplates.tsx +++ b/apps/web/src/pages/my-templates/MyTemplates.tsx @@ -3,126 +3,43 @@ * 卡片视图展示用户已保存的剪辑模板 * 支持搜索、分类筛选、编辑/复制/删除/使用模板生成 */ -import React, { useState } from "react" -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" -import { - Typography, - Card, - Input, - Select, - Tag, - Button, - Space, - Empty, - Spin, - Tooltip, - message, - Popconfirm, - Row, - Col, -} from "antd" +import React from "react" +import { Typography, Input, Select, Button, Empty, Spin, Row, Col } from "antd" import { SearchOutlined, - EditOutlined, - CopyOutlined, - DeleteOutlined, - VideoCameraOutlined, AppstoreOutlined, PlusOutlined, } from "@ant-design/icons" import { useNavigate } from "react-router-dom" -import { - getEditingTemplates, - getTemplateCategories, - deleteEditingTemplate, - createEditingTemplate, - MODE_LABELS, - MODE_COLORS, - type EditingTemplate, - type TemplateMode, -} from "@/api/editing-planner" +import type { EditingTemplate } from "@/api/editing-planner" +import { useMyTemplates } from "./hooks/useMyTemplates" +import { TemplateCard } from "./components/TemplateCard" import "./MyTemplates.css" const { Title, Text } = Typography const MyTemplates: React.FC = () => { const navigate = useNavigate() - const queryClient = useQueryClient() + const { + searchText, + setSearchText, + filterCategory, + setFilterCategory, + templates, + categories, + isLoading, + handleCopy, + handleDelete, + } = useMyTemplates() - const [searchText, setSearchText] = useState("") - const [filterCategory, setFilterCategory] = useState("") - - /* ── 数据查询 ── */ - const { data: templates = [], isLoading } = useQuery({ - queryKey: ["editing-templates", filterCategory, searchText], - queryFn: () => - getEditingTemplates({ - category: filterCategory || undefined, - tag: searchText || undefined, - }), - }) - - const { data: categories = [] } = useQuery({ - queryKey: ["template-categories"], - queryFn: getTemplateCategories, - }) - - /* ── Mutations ── */ - const deleteMutation = useMutation({ - mutationFn: deleteEditingTemplate, - onSuccess: () => { - message.success("模板已删除") - queryClient.invalidateQueries({ queryKey: ["editing-templates"] }) - }, - onError: (err: unknown) => { - if (!(err as { __msgShown?: boolean })?.__msgShown) message.error("删除失败") - }, - }) - - const copyMutation = useMutation({ - mutationFn: (tpl: EditingTemplate) => - createEditingTemplate({ - name: `${tpl.name}(副本)`, - mode: tpl.mode, - category: tpl.category, - tags: tpl.tags, - title_config: tpl.title_config, - subtitle_config: tpl.subtitle_config, - bgm_config: tpl.bgm_config, - estimated_duration: - tpl.estimated_duration ?? - Math.round( - tpl.segments.reduce((s, seg) => s + (seg.duration_min + seg.duration_max) / 2, 0), - ), - segments: tpl.segments.map(({ id: _id, ...rest }) => rest), - }), - onSuccess: () => { - message.success("模板已复制") - queryClient.invalidateQueries({ queryKey: ["editing-templates"] }) - }, - onError: (err: unknown) => { - if (!(err as { __msgShown?: boolean })?.__msgShown) message.error("复制失败") - }, - }) - - /* ── 操作 ── */ const handleEdit = (tpl: EditingTemplate) => { navigate(`/editing-planner?template=${tpl.id}`) } const handleGenerate = (tpl: EditingTemplate) => { - // 跳转到智能剪辑页面,统一从智能剪辑出片 navigate(`/generate?templateId=${tpl.id}`) } - const handleCopy = (tpl: EditingTemplate) => { - copyMutation.mutate(tpl) - } - - const handleDelete = (id: string) => { - deleteMutation.mutate(id) - } - return (
{/* 页面头部 */} @@ -179,69 +96,13 @@ const MyTemplates: React.FC = () => { {templates.map((tpl) => ( - - handleEdit(tpl)} /> - , - - handleCopy(tpl)} /> - , - - handleGenerate(tpl)} /> - , - handleDelete(tpl.id)} - okText="删除" - cancelText="取消" - > - - - - , - ]} - > -
- - {tpl.name} - - - {MODE_LABELS[tpl.mode as TemplateMode] || tpl.mode} - - 用户自制 -
- -
- - {tpl.segments.length} 片段 · 预估 ~{tpl.estimated_duration}s - - {tpl.category && ( - {tpl.category} - )} -
- - {tpl.tags.length > 0 && ( -
- {tpl.tags.map((tag) => ( - - {tag} - - ))} -
- )} - -
- - {tpl.title_config.ai_auto_select && AI标题} - {tpl.subtitle_config.enabled && 字幕} - {tpl.bgm_config.enabled && BGM} - -
-
+ ))}
diff --git a/apps/web/src/pages/my-templates/components/TemplateCard.tsx b/apps/web/src/pages/my-templates/components/TemplateCard.tsx new file mode 100644 index 000000000..2979228c3 --- /dev/null +++ b/apps/web/src/pages/my-templates/components/TemplateCard.tsx @@ -0,0 +1,92 @@ +import React from "react" +import { Card, Tag, Tooltip, Popconfirm, Space, Typography } from "antd" +import { EditOutlined, CopyOutlined, DeleteOutlined, VideoCameraOutlined } from "@ant-design/icons" +import { + MODE_LABELS, + MODE_COLORS, + type EditingTemplate, + type TemplateMode, +} from "@/api/editing-planner" + +const { Text } = Typography + +interface TemplateCardProps { + tpl: EditingTemplate + onEdit: (tpl: EditingTemplate) => void + onCopy: (tpl: EditingTemplate) => void + onGenerate: (tpl: EditingTemplate) => void + onDelete: (id: string) => void +} + +/** + * 单个模板卡片组件 + */ +export const TemplateCard: React.FC = ({ + tpl, + onEdit, + onCopy, + onGenerate, + onDelete, +}) => ( + + onEdit(tpl)} /> + , + + onCopy(tpl)} /> + , + + onGenerate(tpl)} /> + , + onDelete(tpl.id)} + okText="删除" + cancelText="取消" + > + + + + , + ]} + > +
+ + {tpl.name} + + + {MODE_LABELS[tpl.mode as TemplateMode] || tpl.mode} + + 用户自制 +
+ +
+ + {tpl.segments.length} 片段 · 预估 ~{tpl.estimated_duration}s + + {tpl.category && {tpl.category}} +
+ + {tpl.tags.length > 0 && ( +
+ {tpl.tags.map((tag) => ( + + {tag} + + ))} +
+ )} + +
+ + {tpl.title_config.ai_auto_select && AI标题} + {tpl.subtitle_config.enabled && 字幕} + {tpl.bgm_config.enabled && BGM} + +
+
+) diff --git a/apps/web/src/pages/my-templates/hooks/useMyTemplates.ts b/apps/web/src/pages/my-templates/hooks/useMyTemplates.ts new file mode 100644 index 000000000..76610297c --- /dev/null +++ b/apps/web/src/pages/my-templates/hooks/useMyTemplates.ts @@ -0,0 +1,100 @@ +import { useState } from "react" +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { message } from "antd" +import { + getEditingTemplates, + getTemplateCategories, + deleteEditingTemplate, + createEditingTemplate, + type EditingTemplate, +} from "@/api/editing-planner" + +/** + * 我的模板数据 Hook + * 封装模板列表查询、筛选、删除、复制等数据操作 + */ +export function useMyTemplates() { + const queryClient = useQueryClient() + const [searchText, setSearchText] = useState("") + const [filterCategory, setFilterCategory] = useState("") + + /* 模板列表 */ + const { data: templates = [], isLoading } = useQuery({ + queryKey: ["editing-templates", filterCategory, searchText], + queryFn: () => + getEditingTemplates({ + category: filterCategory || undefined, + tag: searchText || undefined, + }), + }) + + /* 分类列表 */ + const { data: categories = [] } = useQuery({ + queryKey: ["template-categories"], + queryFn: getTemplateCategories, + }) + + /* 删除 mutation */ + const deleteMutation = useMutation({ + mutationFn: deleteEditingTemplate, + onSuccess: () => { + message.success("模板已删除") + queryClient.invalidateQueries({ queryKey: ["editing-templates"] }) + }, + onError: (err: unknown) => { + if (!(err as { __msgShown?: boolean })?.__msgShown) message.error("删除失败") + }, + }) + + /* 复制 mutation */ + const copyMutation = useMutation({ + mutationFn: (tpl: EditingTemplate) => + createEditingTemplate({ + name: `${tpl.name}(副本)`, + mode: tpl.mode, + category: tpl.category, + tags: tpl.tags, + title_config: tpl.title_config, + subtitle_config: tpl.subtitle_config, + bgm_config: tpl.bgm_config, + estimated_duration: + tpl.estimated_duration ?? + Math.round( + tpl.segments.reduce((s, seg) => s + (seg.duration_min + seg.duration_max) / 2, 0), + ), + segments: tpl.segments.map(({ id: _id, ...rest }) => rest), + }), + onSuccess: () => { + message.success("模板已复制") + queryClient.invalidateQueries({ queryKey: ["editing-templates"] }) + }, + onError: (err: unknown) => { + if (!(err as { __msgShown?: boolean })?.__msgShown) message.error("复制失败") + }, + }) + + const handleCopy = (tpl: EditingTemplate) => { + copyMutation.mutate(tpl) + } + + const handleDelete = (id: string) => { + deleteMutation.mutate(id) + } + + return { + // 状态 + searchText, + setSearchText, + filterCategory, + setFilterCategory, + // 数据 + templates, + categories, + isLoading, + // 操作 + handleCopy, + handleDelete, + isDeleting: deleteMutation.isPending, + isCopying: copyMutation.isPending, + } +}