2026-04-29-21-27-10 - 组件目录扁平化重构
This commit is contained in:
140
src/components/CanvasArea.tsx
Normal file
140
src/components/CanvasArea.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Stage, Layer, Image as KonvaImage, Circle, Rect, Path, Group } from 'react-konva';
|
||||
import useImage from 'use-image';
|
||||
|
||||
interface CanvasAreaProps {
|
||||
activeTool: string;
|
||||
}
|
||||
|
||||
export function CanvasArea({ activeTool }: CanvasAreaProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [stageSize, setStageSize] = useState({ width: 800, height: 600 });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [points, setPoints] = useState<{ x: number, y: number, type: 'pos'|'neg' }[]>([]);
|
||||
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
|
||||
|
||||
// We load a mock image representing a frame
|
||||
const [image] = useImage('https://images.unsplash.com/photo-1549317661-bd32c8ce0be2?q=80&w=2070&auto=format&fit=crop');
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (containerRef.current) {
|
||||
setStageSize({
|
||||
width: containerRef.current.clientWidth,
|
||||
height: containerRef.current.clientHeight,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const handleWheel = (e: any) => {
|
||||
e.evt.preventDefault();
|
||||
const scaleBy = 1.1;
|
||||
const stage = e.target.getStage();
|
||||
const oldScale = stage.scaleX();
|
||||
|
||||
const mousePointTo = {
|
||||
x: stage.getPointerPosition().x / oldScale - stage.x() / oldScale,
|
||||
y: stage.getPointerPosition().y / oldScale - stage.y() / oldScale,
|
||||
};
|
||||
|
||||
const newScale = e.evt.deltaY < 0 ? oldScale * scaleBy : oldScale / scaleBy;
|
||||
setScale(newScale);
|
||||
setPosition({
|
||||
x: -(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale,
|
||||
y: -(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale,
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: any) => {
|
||||
const stage = e.target.getStage();
|
||||
if (!stage) return;
|
||||
const pos = stage.getPointerPosition();
|
||||
if (pos) {
|
||||
// Convert to image coordinates
|
||||
const imageX = (pos.x - position.x) / scale;
|
||||
const imageY = (pos.y - position.y) / scale;
|
||||
setCursorPos({ x: imageX, y: imageY });
|
||||
}
|
||||
};
|
||||
|
||||
const handleStageClick = (e: any) => {
|
||||
if (activeTool === 'move') return;
|
||||
|
||||
if (activeTool === 'point_pos' || activeTool === 'point_neg') {
|
||||
const stage = e.target.getStage();
|
||||
const pos = stage.getRelativePointerPosition();
|
||||
setPoints([...points, { x: pos.x, y: pos.y, type: activeTool === 'point_pos' ? 'pos' : 'neg' }]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full relative cursor-crosshair overflow-hidden rounded-sm">
|
||||
<Stage
|
||||
width={stageSize.width}
|
||||
height={stageSize.height}
|
||||
onWheel={handleWheel}
|
||||
onMouseMove={handleMouseMove}
|
||||
scaleX={scale}
|
||||
scaleY={scale}
|
||||
x={position.x}
|
||||
y={position.y}
|
||||
draggable={activeTool === 'move'}
|
||||
onClick={handleStageClick}
|
||||
>
|
||||
<Layer>
|
||||
{/* Background Image Layer */}
|
||||
{image && (
|
||||
<KonvaImage
|
||||
image={image}
|
||||
x={0}
|
||||
y={0}
|
||||
opacity={0.8}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mock Instance Mask overlapping */}
|
||||
<Group opacity={0.4}>
|
||||
<Path
|
||||
data="M 300 200 Q 400 150 450 250 T 400 350 Q 250 350 280 250 Z"
|
||||
fill="#06b6d4" // cyan-500
|
||||
/>
|
||||
<Path
|
||||
data="M 600 400 Q 700 350 750 450 T 650 550 Q 550 550 580 450 Z"
|
||||
fill="#a855f7" // purple-500
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* AI Prompts Point Regions */}
|
||||
{points.map((p, i) => (
|
||||
<Group key={i} x={p.x} y={p.y}>
|
||||
<Circle
|
||||
radius={6 / scale}
|
||||
fill={p.type === 'pos' ? '#22c55e' : '#ef4444'} // green or red
|
||||
stroke="#ffffff"
|
||||
strokeWidth={2 / scale}
|
||||
shadowColor="black"
|
||||
shadowBlur={4}
|
||||
/>
|
||||
<Circle
|
||||
radius={1.5 / scale}
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</Layer>
|
||||
</Stage>
|
||||
|
||||
<div className="absolute bottom-4 left-4 flex gap-4 text-[10px] font-mono text-gray-500 pointer-events-none">
|
||||
<span>光标: {cursorPos.x.toFixed(2)}, {cursorPos.y.toFixed(2)}</span>
|
||||
<span>当前图层树: OBJECT_VEHICLE_01</span>
|
||||
<span>缩放比: {(scale * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user