refactor(canvas): derive toolbar offsets from panel geometry
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-04 00:07:29 +08:00
parent 93e5493993
commit f2b752dd7d

View File

@ -79,6 +79,10 @@ const SIZE_PRESETS = [
{ label: "9:16", value: "9:16", width: 1024, height: 1820 }, { label: "9:16", value: "9:16", width: 1024, height: 1820 },
]; ];
const FLOATING_PANEL_WIDTH = 460;
const FLOATING_PANEL_INSET = 16;
const FLOATING_UI_GAP = 16;
export default function CanvasEditorPage({ params }: PageProps) { export default function CanvasEditorPage({ params }: PageProps) {
// 使用 useParams 确保客户端导航时能正确获取更新的 id // 使用 useParams 确保客户端导航时能正确获取更新的 id
const routeParams = useParams(); const routeParams = useParams();
@ -124,6 +128,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
const [isPanelOpen, setIsPanelOpen] = useState(true); const [isPanelOpen, setIsPanelOpen] = useState(true);
// 面板模式: 'chat' 聊天面板, 'fileList' 文件列表 // 面板模式: 'chat' 聊天面板, 'fileList' 文件列表
const [panelMode, setPanelMode] = useState<'chat' | 'fileList'>('chat'); const [panelMode, setPanelMode] = useState<'chat' | 'fileList'>('chat');
const [viewportWidth, setViewportWidth] = useState(0);
const { data: siteSettingsData } = useSWR<{ const { data: siteSettingsData } = useSWR<{
siteName?: string; siteName?: string;
@ -145,6 +150,13 @@ export default function CanvasEditorPage({ params }: PageProps) {
? nodes.find(n => n.id === selectedNodeIds[0] && n.type === 'image') ? nodes.find(n => n.id === selectedNodeIds[0] && n.type === 'image')
: null; : null;
const resolvedPanelWidth = viewportWidth > 0
? Math.min(FLOATING_PANEL_WIDTH, Math.max(viewportWidth - FLOATING_PANEL_INSET, 0))
: FLOATING_PANEL_WIDTH;
const panelOcclusionRight = isPanelOpen ? resolvedPanelWidth + FLOATING_PANEL_INSET : 0;
const floatingToolbarOffset = isPanelOpen ? -(panelOcclusionRight / 2) : 0;
const compactControlsRight = isPanelOpen ? panelOcclusionRight + FLOATING_UI_GAP : FLOATING_UI_GAP;
// 加载画布数据 // 加载画布数据
useEffect(() => { useEffect(() => {
console.log('[Canvas] Loading project:', id); console.log('[Canvas] Loading project:', id);
@ -179,6 +191,13 @@ export default function CanvasEditorPage({ params }: PageProps) {
loadProject(); loadProject();
}, [id]); // 只依赖 id,使用 getState() 直接调用 store actions }, [id]); // 只依赖 id,使用 getState() 直接调用 store actions
useEffect(() => {
const updateViewportWidth = () => setViewportWidth(window.innerWidth);
updateViewportWidth();
window.addEventListener("resize", updateViewportWidth);
return () => window.removeEventListener("resize", updateViewportWidth);
}, []);
const persistCanvasState = useCallback( const persistCanvasState = useCallback(
async (reason: "autosave" | "delete" | "beforeunload") => { async (reason: "autosave" | "delete" | "beforeunload") => {
const canvasData = getCanvasState(); const canvasData = getCanvasState();
@ -561,7 +580,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
{/* 1. Canvas Layer (Full Screen) - 使用 key 强制在 id 变化时重新挂载 */} {/* 1. Canvas Layer (Full Screen) - 使用 key 强制在 id 变化时重新挂载 */}
<div className="absolute inset-0 z-0" key={`workspace-${id}`}> <div className="absolute inset-0 z-0" key={`workspace-${id}`}>
<CanvasWorkspace occlusionRight={isPanelOpen ? 476 : 0} /> <CanvasWorkspace occlusionRight={panelOcclusionRight} />
</div> </div>
{/* Brand Entry (Top Left) */} {/* Brand Entry (Top Left) */}
@ -641,7 +660,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
{/* 3. Floating Actions (Top Right) */} {/* 3. Floating Actions (Top Right) */}
{!isPanelOpen && ( {!isPanelOpen && (
<div className="absolute top-4 right-4 z-20"> <div className="absolute z-20" style={{ top: FLOATING_PANEL_INSET, right: FLOATING_PANEL_INSET }}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
@ -660,9 +679,16 @@ export default function CanvasEditorPage({ params }: PageProps) {
{/* 4. Floating Sidebar (Right) */} {/* 4. Floating Sidebar (Right) */}
<div className={cn( <div className={cn(
"absolute top-4 right-4 bottom-4 z-10 w-[460px] max-w-[calc(100vw-1rem)] transition-all duration-300 ease-in-out", "absolute z-10 transition-all duration-300 ease-in-out",
isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none" isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none"
)}> )}
style={{
top: FLOATING_PANEL_INSET,
right: FLOATING_PANEL_INSET,
bottom: FLOATING_PANEL_INSET,
width: FLOATING_PANEL_WIDTH,
maxWidth: `calc(100vw - ${FLOATING_PANEL_INSET}px)`,
}}>
<div className="h-full w-full rounded-2xl border bg-white dark:bg-zinc-950 shadow-2xl overflow-hidden flex flex-col"> <div className="h-full w-full rounded-2xl border bg-white dark:bg-zinc-950 shadow-2xl overflow-hidden flex flex-col">
{panelMode === 'chat' ? ( {panelMode === 'chat' ? (
// AI 对话内容 - 使用 key 强制在 id 变化时重新挂载 // AI 对话内容 - 使用 key 强制在 id 变化时重新挂载
@ -687,9 +713,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
<div className="absolute bottom-5 left-1/2 z-20 -translate-x-1/2"> <div className="absolute bottom-5 left-1/2 z-20 -translate-x-1/2">
<div <div
className="flex items-center gap-1 rounded-[24px] border bg-background/88 px-2.5 py-1.5 shadow-2xl backdrop-blur-md" className="flex items-center gap-1 rounded-[24px] border bg-background/88 px-2.5 py-1.5 shadow-2xl backdrop-blur-md"
style={{ style={{ marginLeft: `${floatingToolbarOffset}px` }}
marginLeft: isPanelOpen ? "-238px" : "0px",
}}
> >
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@ -797,7 +821,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
{/* 6. Bottom Right Compact Controls */} {/* 6. Bottom Right Compact Controls */}
<div <div
className="absolute bottom-5 z-20" className="absolute bottom-5 z-20"
style={{ right: isPanelOpen ? "492px" : "16px" }} style={{ right: `${compactControlsRight}px` }}
> >
<div className="flex items-center gap-1 rounded-2xl border bg-background/88 px-2 py-1.5 shadow-xl backdrop-blur-md"> <div className="flex items-center gap-1 rounded-2xl border bg-background/88 px-2 py-1.5 shadow-xl backdrop-blur-md">
<Tooltip> <Tooltip>