diff --git a/public/empty_project.png b/public/empty_project.png new file mode 100644 index 00000000..978987c3 Binary files /dev/null and b/public/empty_project.png differ diff --git a/src/components/HomeProjectsContent.tsx b/src/components/HomeProjectsContent.tsx index 2853b1b2..1a9c2588 100644 --- a/src/components/HomeProjectsContent.tsx +++ b/src/components/HomeProjectsContent.tsx @@ -1,7 +1,7 @@ "use client"; -import Link from "next/link"; -import { useEffect, useState, type ReactNode } from "react"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useState, type ReactNode } from "react"; import { ProjectActionsDropdown } from "@/components/ProjectActionsDropdown"; import { ArrowIcon, @@ -10,8 +10,14 @@ import { 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; @@ -23,6 +29,7 @@ type ShowData = { image: string; avatar: string; vip?: boolean; + data: string; }; type TemplateCategoryData = { @@ -42,6 +49,7 @@ type CanvasTemplateItem = { categoryId: number; type: number; coverUrl: string; + properties: string; name: string; desp: string; deleted: boolean; @@ -59,17 +67,53 @@ type HomeProjectsContentProps = { heroSection: ReactNode; }; -function ProjectCard({ title, date, image }: ProjectData) { +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 (
-
- -
+
-

- {title} -

+

{date}

@@ -78,11 +122,11 @@ function ProjectCard({ title, date, image }: ProjectData) { ); } -function ShowCard({ title, subtitle, image, avatar, vip }: ShowData) { +function ShowCard({ title, subtitle, image, avatar, vip, data, onOpen }: ShowData & { onOpen: (data: ShowData) => void }) { return ( -
+
onOpen({ title, subtitle, image, avatar, vip, data })}>
- +
@@ -115,7 +159,67 @@ function ProjectsSection({ 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 (
@@ -145,17 +249,28 @@ function ProjectsSection({ )}
- 开始创作 - + + {isLoadingProjects && projects.length === 0 ? ( +
+ Loading... +
+ ) : null} {projects.map((project) => ( - + router.push(`/canvas?workflowId=${project.id}`)} + /> ))} {onAllProjectsClick && projects.length === 4 && ( <> @@ -176,13 +291,14 @@ function ProjectsSection({ } -function mapTemplateToShow(item: CanvasTemplateItem, index: number): ShowData { +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 || '', }; } @@ -194,6 +310,9 @@ function ShowsSection() { 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; @@ -257,7 +376,7 @@ function ShowsSection() { setDataList( list .filter((item) => !item.deleted) - .map((item, index) => mapTemplateToShow(item, index)) + .map((item) => mapTemplateToShow(item)) ); } catch { if (!controller.signal.aborted) { @@ -272,6 +391,24 @@ function ShowsSection() { }; }, [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 (
@@ -308,7 +445,7 @@ function ShowsSection() {
{dataList.map((show) => ( - + ))}
diff --git a/src/lib/canvasWorkflowApi.ts b/src/lib/canvasWorkflowApi.ts index 05800154..abc6195f 100644 --- a/src/lib/canvasWorkflowApi.ts +++ b/src/lib/canvasWorkflowApi.ts @@ -19,13 +19,19 @@ export interface CreateCanvasWorkflowResponse { export interface CanvasWorkflowListItem { id: number; + userId?: number; + gatewayUserId?: number; title?: string; desp?: string; properties?: string; code?: string; + apikey?: string; + apikeyId?: number; status?: number; createTime?: string; updateTime?: string; + deleted?: boolean; + deleteTime?: string | null; } export interface CanvasWorkflowPageInfo {