feat(canvas): 左侧面板可调宽收起 + 元素多选导出与跳转动画

- 左侧画布面板支持拖拽调整宽度、展开/收起(带动画),状态与宽度持久化
- 顶栏菜单左侧新增面板开关按钮,并缩小菜单/开关按钮
- 顶栏菜单新增「导出当前画布」为压缩包
- 画布元素列表支持多选并批量导出:图片/视频/音频为文件、文本为 txt、其余各自为 json
- 画布/资产切换改为滑动下划线动画,点击元素跳转带缓动动画
- 移除文本/配置/视频等非图片元素图标的灰色底色,并写入 AGENTS.md 规范

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
HouYunFei
2026-07-17 16:21:26 +08:00
parent a3411cf9d9
commit 219b3e84db
8 changed files with 303 additions and 25 deletions
+23 -6
View File
@@ -1,9 +1,10 @@
import { useEffect, useRef, useState } from "react";
import { BookOpen, Bot, Home, Images, Menu, Plus, Redo2, Trash2, Undo2, Upload } from "lucide-react";
import { Button, Dropdown, Modal } from "antd";
import { BookOpen, Bot, Download, Home, Images, Menu, PanelLeftClose, PanelLeftOpen, Plus, Redo2, Trash2, Undo2, Upload } from "lucide-react";
import { Button, Dropdown, Modal, Tooltip } from "antd";
import { UserStatusActions } from "@/components/layout/user-status-actions";
import { canvasThemes } from "@/lib/canvas-theme";
import { useCanvasSidePanelStore } from "@/stores/use-canvas-side-panel-store";
import { useThemeStore } from "@/stores/use-theme-store";
import { DOCS_URL } from "@/constant/env";
@@ -21,6 +22,7 @@ export function CanvasTopBar({
onProjects,
onCreateProject,
onDeleteProject,
onExportProject,
onImportImage,
onOpenPlugins,
onUndo,
@@ -42,6 +44,7 @@ export function CanvasTopBar({
onProjects: () => void;
onCreateProject: () => void;
onDeleteProject: () => void;
onExportProject: () => void;
onImportImage: () => void;
onOpenPlugins: () => void;
onUndo: () => void;
@@ -54,6 +57,8 @@ export function CanvasTopBar({
const theme = canvasThemes[colorTheme];
const titleRef = useRef<HTMLDivElement>(null);
const [shortcutsOpen, setShortcutsOpen] = useState(false);
const sidePanelOpen = useCanvasSidePanelStore((state) => state.panelOpen);
const toggleSidePanel = useCanvasSidePanelStore((state) => state.togglePanel);
useEffect(() => {
if (!isTitleEditing) return;
@@ -66,8 +71,19 @@ export function CanvasTopBar({
return (
<>
<div className="pointer-events-none absolute left-0 right-0 top-0 z-50 flex h-16 items-center justify-between px-4">
<div className="pointer-events-auto flex min-w-0 items-center gap-3">
<div className="pointer-events-none absolute left-0 right-0 top-0 z-50 flex h-16 items-center justify-between pl-1 pr-4">
<div className="pointer-events-auto flex min-w-0 items-center gap-2">
<Tooltip title={sidePanelOpen ? "收起面板" : "展开面板"}>
<button
type="button"
onClick={toggleSidePanel}
aria-label={sidePanelOpen ? "收起面板" : "展开面板"}
className="grid size-7 place-items-center rounded-full transition hover:bg-black/5 dark:hover:bg-white/10"
style={{ color: theme.node.text }}
>
{sidePanelOpen ? <PanelLeftClose className="size-4" /> : <PanelLeftOpen className="size-4" />}
</button>
</Tooltip>
<Dropdown
trigger={["click"]}
menu={{
@@ -80,14 +96,15 @@ export function CanvasTopBar({
{ key: "delete", danger: true, icon: <Trash2 className="size-4" />, label: "删除当前画布", onClick: onDeleteProject },
{ type: "divider" },
{ key: "import", icon: <Upload className="size-4" />, label: "导入资产", onClick: onImportImage },
{ key: "export", icon: <Download className="size-4" />, label: "导出当前画布", onClick: onExportProject },
{ type: "divider" },
{ key: "undo", disabled: !canUndo, icon: <Undo2 className="size-4" />, label: <MenuLabel text="撤销" shortcut="⌘ Z" />, onClick: onUndo },
{ key: "redo", disabled: !canRedo, icon: <Redo2 className="size-4" />, label: <MenuLabel text="重做" shortcut="⌘ ⇧ Z / ⌘ Y" />, onClick: onRedo },
],
}}
>
<button type="button" className="grid size-9 place-items-center rounded-full transition hover:bg-black/5 dark:hover:bg-white/10" style={{ color: theme.node.text }} aria-label="打开画布菜单">
<Menu className="size-5" />
<button type="button" className="grid size-7 place-items-center rounded-full transition hover:bg-black/5 dark:hover:bg-white/10" style={{ color: theme.node.text }} aria-label="打开画布菜单">
<Menu className="size-4" />
</button>
</Dropdown>