"use client"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState, type ReactNode } from "react"; import { ProjectActionsDropdown } from "@/components/ProjectActionsDropdown"; import { ArrowIcon, LeftOutlinedIcon, SearchIcon, StartCreation, } from "@/components/icons"; import { authFetch } from "@/utils/authFetch"; import { createCanvasWorkflow, listCanvasWorkflows, type CanvasWorkflowListItem, } from "@/lib/canvasWorkflowApi"; import { message } from 'antd'; type ProjectData = { id: number; title: string; date: string; image: string; }; type ShowData = { title: string; subtitle: string; image: string; avatar: string; vip?: boolean; data: string; }; type TemplateCategoryData = { id: number | null; name: string; }; type CanvasTemplateCategoryItem = { id: number; name: string; sort: number; deleted: boolean; }; type CanvasTemplateItem = { id: number; categoryId: number; type: number; coverUrl: string; properties: string; name: string; desp: string; deleted: boolean; categoryName: string; }; type ListResponse = { data?: { list?: T[]; }; status?: string; }; type HomeProjectsContentProps = { heroSection: ReactNode; }; function formatProjectTime(value?: string): string { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleDateString(); } function mapWorkflowToProject(item: CanvasWorkflowListItem): ProjectData { return { id: item.id, title: item.title?.trim() || item.code?.trim() || `#${item.id}`, date: formatProjectTime(item.updateTime || item.createTime), image: "", }; } function buildEmptyWorkflowProperties(title: string): string { return JSON.stringify({ name: title, nodes: [], edges: [], edgeStyle: "curved", }); } function ProjectCard({ title, date, image, onOpen, }: ProjectData & { onOpen: () => void }) { return (

{date}

); } function ShowCard({ title, subtitle, image, avatar, vip, data, onOpen }: ShowData & { onOpen: (data: ShowData) => void }) { return (
onOpen({ title, subtitle, image, avatar, vip, data })}>
{avatar}

{title}

{vip ? ( VIP ) : null}

{subtitle}

); } function ProjectsSection({ className = "", onAllProjectsClick, onBackClick, }: { className?: string; onBackClick?: () => void; onAllProjectsClick?: () => void; }) { const router = useRouter(); const [projects, setProjects] = useState([]); const [isLoadingProjects, setIsLoadingProjects] = useState(false); const [isCreating, setIsCreating] = useState(false); const pageSize = onAllProjectsClick ? 4 : 20; useEffect(() => { let canceled = false; async function loadProjects() { setIsLoadingProjects(true); try { const data = await listCanvasWorkflows({ page: 1, pageSize }); if (canceled) return; setProjects( data.list .filter((item) => !item.deleted) .map((item) => mapWorkflowToProject(item)) ); } catch { if (!canceled) { setProjects([]); } } finally { if (!canceled) { setIsLoadingProjects(false); } } } void loadProjects(); return () => { canceled = true; }; }, [pageSize]); const handleStartCreation = async () => { if (isCreating) { message.warning("项目创建中,请稍稍后试"); return; }; setIsCreating(true); const title = "未命名项目"; try { const result = await createCanvasWorkflow({ title, desp: "", properties: buildEmptyWorkflowProperties(title), }); const workflowId = (result.data as any)?.id router.push(workflowId ? `/canvas?workflowId=${workflowId}` : "/canvas"); message.success("项目创建成功"); } catch (error) { message.error(error instanceof Error ? error.message : "Failed to create workflow."); setIsCreating(false); } }; return (
{onBackClick && (
返回
)}

最近项目

{!!onAllProjectsClick && ( )}
{isLoadingProjects && projects.length === 0 ? (
Loading...
) : null} {projects.map((project) => ( router.push(`/canvas?workflowId=${project.id}`)} /> ))} {onAllProjectsClick && projects.length === 4 && ( <>
)}
); } function mapTemplateToShow(item: CanvasTemplateItem): ShowData { return { title: item.name || "未命名", subtitle: item.desp || item.categoryName || "画布模板", image: item.coverUrl || '', avatar: item.categoryName?.slice(0, 1) || item.name?.slice(0, 1) || "画", vip: item.type === 1, data: item.properties || '', }; } function ShowsSection() { const [activeTab, setActiveTab] = useState(0); const [categoryList, setCategoryList] = useState([ { id: null, name: "全部" }, ]); const [dataList, setDataList] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const selectedCategoryId = categoryList[activeTab]?.id ?? null; const [isCreating, setIsCreating] = useState(false); const router = useRouter(); useEffect(() => { let canceled = false; async function loadCategories() { try { const params = new URLSearchParams({ keyword: "" }); const response = await authFetch(`/api/canvas-templates/categories?${params.toString()}`); if (!response.ok) return; const result = (await response.json()) as ListResponse; const list = result.data?.list; if (!Array.isArray(list)) return; const categories = list .filter((item) => !item.deleted) .sort((a, b) => a.sort - b.sort) .map((item) => ({ id: item.id, name: item.name })) .filter((item) => item.name); if (!canceled && categories.length > 0) { setCategoryList([{ id: null, name: "全部" }, ...categories]); } } catch { // Keep fallback category. } } loadCategories(); return () => { canceled = true; }; }, []); useEffect(() => { const controller = new AbortController(); const timeout = window.setTimeout(async () => { try { const params = new URLSearchParams({ keyword: searchQuery, categoryId: selectedCategoryId ? String(selectedCategoryId) : "", type: "", userId: "", }); const response = await authFetch(`/api/canvas-templates?${params.toString()}`, { signal: controller.signal, }); if (!response.ok) { setDataList([]); return; } const result = (await response.json()) as ListResponse; const list = result.data?.list; if (!Array.isArray(list)) { setDataList([]); return; } setDataList( list .filter((item) => !item.deleted) .map((item) => mapTemplateToShow(item)) ); } catch { if (!controller.signal.aborted) { setDataList([]); } } }, 250); return () => { window.clearTimeout(timeout); controller.abort(); }; }, [searchQuery, selectedCategoryId]); const onOpen = useCallback(async (data: ShowData) => { if (isCreating) return; setIsCreating(true); const title = data.title || "未命名项目"; try { const result = await createCanvasWorkflow({ title, desp: data.subtitle || "", properties: data.data || buildEmptyWorkflowProperties(title), }); const workflowId = (result.data as any)?.id router.push(workflowId ? `/canvas?workflowId=${workflowId}` : "/canvas"); } catch (error) { window.alert(error instanceof Error ? error.message : "Failed to create workflow."); setIsCreating(false); } }, []); return (

TV show

{categoryList.map((tab, index) => ( ))}
setSearchQuery(e.target.value)} placeholder="请输入搜索的内容" className="min-w-0 flex-1 bg-transparent text-[14px] outline-none placeholder:text-[#999]" />
{dataList.map((show) => ( ))}
); } export function HomeProjectsContent({ heroSection, }: HomeProjectsContentProps) { const [isShowAll, setIsShowAll] = useState(false); return (
{heroSection} setIsShowAll(true)} />
setIsShowAll(false)} />
); }