fix: redirect /canvas to new canvas editor instead of list page
- Replace full-screen canvas list with auto-redirect to new canvas - Ensures users always enter canvas editor mode directly - Use Link component for reliable navigation in top navbar
This commit is contained in:
parent
676be1c70c
commit
2566c00cb1
@ -1,289 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Plus, LayoutGrid, Trash2, Edit2, MoreVertical, Clock, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { toast } from "sonner";
|
||||
import { canvasService } from "@/services/canvas";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
interface CanvasProject {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
thumbnail?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export default function CanvasListPage() {
|
||||
// 自动重定向到新画布
|
||||
export default function CanvasRedirectPage() {
|
||||
const router = useRouter();
|
||||
const [projects, setProjects] = useState<CanvasProject[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [projectToDelete, setProjectToDelete] = useState<CanvasProject | null>(null);
|
||||
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||
const [projectToRename, setProjectToRename] = useState<CanvasProject | null>(null);
|
||||
const [newTitle, setNewTitle] = useState("");
|
||||
|
||||
// 加载画布列表
|
||||
const loadProjects = async () => {
|
||||
setIsLoading(true);
|
||||
const result = await canvasService.getProjects();
|
||||
if (result.success && result.data) {
|
||||
setProjects(result.data);
|
||||
} else {
|
||||
toast.error(result.error || "加载失败");
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadProjects();
|
||||
}, []);
|
||||
|
||||
// 创建新画布
|
||||
const handleCreateProject = async () => {
|
||||
setIsCreating(true);
|
||||
const result = await canvasService.createProject({
|
||||
title: "未命名画布",
|
||||
});
|
||||
|
||||
if (result.success && result.data) {
|
||||
toast.success("创建成功");
|
||||
router.push(`/canvas/${result.data.id}`);
|
||||
} else {
|
||||
toast.error(result.error || "创建失败");
|
||||
setIsCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除画布
|
||||
const handleDeleteProject = async () => {
|
||||
if (!projectToDelete) return;
|
||||
|
||||
const result = await canvasService.deleteProject(projectToDelete.id);
|
||||
if (result.success) {
|
||||
toast.success("删除成功");
|
||||
setProjects(projects.filter(p => p.id !== projectToDelete.id));
|
||||
} else {
|
||||
toast.error(result.error || "删除失败");
|
||||
}
|
||||
setDeleteDialogOpen(false);
|
||||
setProjectToDelete(null);
|
||||
};
|
||||
|
||||
// 重命名画布
|
||||
const handleRenameProject = async () => {
|
||||
if (!projectToRename || !newTitle.trim()) return;
|
||||
|
||||
const result = await canvasService.updateProject(projectToRename.id, {
|
||||
title: newTitle.trim(),
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
toast.success("重命名成功");
|
||||
setProjects(projects.map(p =>
|
||||
p.id === projectToRename.id ? { ...p, title: newTitle.trim() } : p
|
||||
));
|
||||
} else {
|
||||
toast.error(result.error || "重命名失败");
|
||||
}
|
||||
setRenameDialogOpen(false);
|
||||
setProjectToRename(null);
|
||||
setNewTitle("");
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
// 生成新的画布 ID 并跳转
|
||||
const newId = crypto.randomUUID();
|
||||
router.replace(`/canvas/${newId}`);
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between p-6 border-b">
|
||||
<div className="flex items-center gap-3">
|
||||
<LayoutGrid className="h-6 w-6 text-purple-500" />
|
||||
<h1 className="text-2xl font-bold">无限画布</h1>
|
||||
</div>
|
||||
<Button onClick={handleCreateProject} disabled={isCreating}>
|
||||
{isCreating ? (
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
新建画布
|
||||
</Button>
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">正在创建新画布...</span>
|
||||
</div>
|
||||
|
||||
{/* 画布列表 */}
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : projects.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||
<LayoutGrid className="h-16 w-16 text-muted-foreground/50 mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">还没有画布</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
创建一个新画布,开始你的创作之旅
|
||||
</p>
|
||||
<Button onClick={handleCreateProject} disabled={isCreating}>
|
||||
{isCreating ? (
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
新建画布
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{projects.map((project) => (
|
||||
<Card
|
||||
key={project.id}
|
||||
className="group cursor-pointer hover:shadow-lg transition-shadow"
|
||||
onClick={() => router.push(`/canvas/${project.id}`)}
|
||||
>
|
||||
<CardHeader className="p-4 pb-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<CardTitle className="text-base font-medium line-clamp-1">
|
||||
{project.title}
|
||||
</CardTitle>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProjectToRename(project);
|
||||
setNewTitle(project.title);
|
||||
setRenameDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Edit2 className="h-4 w-4 mr-2" />
|
||||
重命名
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProjectToDelete(project);
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 pt-0">
|
||||
<div className="aspect-video bg-muted rounded-md flex items-center justify-center overflow-hidden">
|
||||
{project.thumbnail ? (
|
||||
<img
|
||||
src={project.thumbnail}
|
||||
alt={project.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<LayoutGrid className="h-12 w-12 text-muted-foreground/30" />
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="p-4 pt-0">
|
||||
<div className="flex items-center text-xs text-muted-foreground">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
{formatDate(project.updatedAt)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>确认删除</DialogTitle>
|
||||
<DialogDescription>
|
||||
确定要删除画布 "{projectToDelete?.title}" 吗?此操作无法撤销。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleDeleteProject}>
|
||||
删除
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 重命名对话框 */}
|
||||
<Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>重命名画布</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={newTitle}
|
||||
onChange={(e) => setNewTitle(e.target.value)}
|
||||
placeholder="输入新名称"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleRenameProject();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setRenameDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleRenameProject} disabled={!newTitle.trim()}>
|
||||
确定
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -174,17 +174,13 @@ export function TopNavbar({
|
||||
)}
|
||||
|
||||
{/* AI智能精选 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
||||
onClick={() => router.push("/featured")}
|
||||
type="button"
|
||||
title="AI智能精选"
|
||||
<Link
|
||||
href="/featured"
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
||||
>
|
||||
<Sparkles className="h-4 w-4 text-yellow-400" />
|
||||
<span className="text-sm font-medium">AI精选</span>
|
||||
</Button>
|
||||
<span>AI精选</span>
|
||||
</Link>
|
||||
|
||||
{/* 精选榜单 */}
|
||||
<DropdownMenu open={rankingOpen} onOpenChange={setRankingOpen}>
|
||||
@ -255,33 +251,27 @@ export function TopNavbar({
|
||||
</Button>
|
||||
|
||||
{/* 无限画布 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
||||
<button
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
||||
onClick={() => {
|
||||
const newId = crypto.randomUUID();
|
||||
router.push(`/canvas/${newId}`);
|
||||
window.location.href = `/canvas/${newId}`;
|
||||
}}
|
||||
type="button"
|
||||
title="无限画布"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4 text-purple-400" />
|
||||
<span className="text-sm font-medium">无限画布</span>
|
||||
</Button>
|
||||
<span>无限画布</span>
|
||||
</button>
|
||||
|
||||
{/* 我的图册 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
||||
onClick={() => router.push("/gallery")}
|
||||
type="button"
|
||||
title="我的图册"
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
||||
>
|
||||
<Images className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">我的图册</span>
|
||||
</Button>
|
||||
<span>我的图册</span>
|
||||
</Link>
|
||||
|
||||
{/* 分隔线 (如果有链接或图册按钮,且是管理员) */}
|
||||
{isAdmin && (
|
||||
@ -290,16 +280,13 @@ export function TopNavbar({
|
||||
|
||||
{/* 系统设置(仅管理员可见)*/}
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 text-white hover:bg-white/15 hover:text-white rounded-full"
|
||||
onClick={() => router.push("/settings")}
|
||||
type="button"
|
||||
<Link
|
||||
href="/settings"
|
||||
className="flex items-center justify-center h-8 w-8 text-white hover:bg-white/15 hover:text-white rounded-full"
|
||||
title="系统设置"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user