import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Alert, App, Button, Tooltip } from "antd"; import { ArrowLeft, BookOpenText, Images, LayoutDashboard, LayoutGrid, Plus, Settings2, type LucideIcon } from "lucide-react"; import { Link, Navigate, useNavigate, useParams } from "react-router"; import { createCanvasProjectWithRemoteSync } from "@/services/user-data-sync"; import { getProject } from "@/services/api/projects"; import { WorkspacePage } from "@/components/layout/workspace-page"; import { WorkspaceErrorState, WorkspaceLoadingState } from "@/components/layout/workspace-state"; import { WorkspaceSignalIcon } from "@/components/ui/aceternity/workspace-signal-icon"; import ProjectAssetsView from "./detail/assets"; import ProjectCanvasesView from "./detail/canvases"; import ProjectChaptersView from "./detail/chapters"; import ProjectOverviewView from "./detail/overview"; import ProjectSettingsView from "./detail/settings"; type DetailView = "overview" | "chapters" | "canvases" | "assets" | "settings"; const views: Array<{ key: DetailView; label: string; shortLabel: string; icon: LucideIcon }> = [ { key: "overview", label: "制作概览", shortLabel: "概览", icon: LayoutDashboard }, { key: "chapters", label: "剧情章节", shortLabel: "章节", icon: BookOpenText }, { key: "canvases", label: "项目画布", shortLabel: "画布", icon: LayoutGrid }, { key: "assets", label: "角色与资产", shortLabel: "资产", icon: Images }, { key: "settings", label: "项目设置", shortLabel: "设置", icon: Settings2 }, ]; export default function ProjectDetailPage() { const { projectId = "", view, chapterId } = useParams(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { message } = App.useApp(); const activeView: DetailView = chapterId ? "chapters" : views.some((item) => item.key === view) ? view as DetailView : "overview"; const detail = useQuery({ queryKey: ["project", projectId], queryFn: () => getProject(projectId), enabled: Boolean(projectId), refetchOnMount: "always" }); const refreshProject = () => { void queryClient.invalidateQueries({ queryKey: ["project", projectId] }); void queryClient.invalidateQueries({ queryKey: ["projects"] }); }; const createCanvas = () => { if (detail.data?.project.status === "archived") { message.warning("项目已归档,请先在项目设置中恢复"); return; } void createCanvasProjectWithRemoteSync(`${detail.data?.project.name || "项目"} · 新画布`, projectId).then(({ id, syncError }) => { if (syncError) message.warning(syncError instanceof Error ? `画布已创建,项目关联稍后重试:${syncError.message}` : "画布已创建,项目关联稍后重试"); else refreshProject(); navigate(`/canvas/${id}`); }).catch((error) => message.error(error instanceof Error ? error.message : "画布创建失败")); }; if (detail.isLoading) return ; if (detail.isError || !detail.data) return navigate("/projects")} />; if (!chapterId && (!view || !views.some((item) => item.key === view))) return ; const chapterHref = projectChapterHref(detail.data.units, projectId, chapterId); return ( navigate("/projects")} className="grid size-9 shrink-0 place-items-center rounded-md text-foreground/42 transition-colors hover:bg-foreground/[.055] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" aria-label="返回项目" title="返回项目"> {detail.data.project.name} {detail.data.project.status === "archived" ? "已归档" : "进行中"} {views.map((item) => { const Icon = item.icon; const active = item.key === activeView; const href = item.key === "chapters" ? chapterHref : `/projects/${projectId}/${item.key}`; return {item.shortLabel}{item.label}; })} } onClick={createCanvas} aria-label="新建项目画布">新建画布 {detail.data.project.status === "archived" ? : null} {activeView === "overview" ? : null} {activeView === "chapters" ? : null} {activeView === "canvases" ? : null} {activeView === "assets" ? : null} {activeView === "settings" ? : null} ); } function projectChapterHref(units: Array<{ id: string; position: number }>, projectId: string, routeChapterId?: string) { const rememberedId = sessionStorage.getItem(`project-active-chapter:${projectId}`) || ""; const targetId = [routeChapterId, rememberedId].find((id) => id && units.some((unit) => unit.id === id)) || units.slice().sort((left, right) => left.position - right.position)[0]?.id; return targetId ? `/projects/${projectId}/chapters/${targetId}` : `/projects/${projectId}/chapters`; }