2025-09-22 20:05:15 +08:00

109 lines
2.7 KiB
TypeScript

/**
* 编辑点交互组件
* 实现脉冲动画效果和点击交互
*/
import React, { useCallback, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Edit3, Check, X, Loader2 } from 'lucide-react';
import { EditPoint as EditPointType, EditPointStatus } from './types';
interface EditPointProps {
/** 编辑点数据 */
editPoint: EditPointType;
/** 是否被选中 */
isSelected: boolean;
/** 容器尺寸 */
containerSize: { width: number; height: number };
/** 点击事件处理 */
onClick: (editPoint: EditPointType) => void;
/** 删除事件处理 */
onDelete: (id: string) => void;
/** 编辑事件处理 */
onEdit: (id: string) => void;
/** 样式配置 */
style?: {
size?: number;
color?: string;
pulseColor?: string;
};
}
/**
* 编辑点组件
*/
export const EditPoint: React.FC<EditPointProps> = ({
editPoint,
isSelected,
containerSize,
onClick,
onDelete,
onEdit,
style = {}
}) => {
const {
size = 12,
color = '#3b82f6',
pulseColor = 'rgba(59, 130, 246, 0.3)'
} = style;
// 计算绝对位置
const absolutePosition = useMemo(() => ({
x: (editPoint.position.x / 100) * containerSize.width,
y: (editPoint.position.y / 100) * containerSize.height
}), [editPoint.position, containerSize]);
// 处理点击事件
const handleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
onClick(editPoint);
}, [onClick, editPoint]);
// 处理删除事件
const handleDelete = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
onDelete(editPoint.id);
}, [onDelete, editPoint.id]);
// 处理编辑事件
const handleEdit = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
onEdit(editPoint.id);
}, [onEdit, editPoint.id]);
// Simplified for the image design - just use blue color
return (
<motion.div
className="absolute z-20 cursor-pointer"
data-edit-point="true"
style={{
left: absolutePosition.x - size / 2,
top: absolutePosition.y - size / 2,
}}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 0 }} // Make invisible to match reference image
exit={{ scale: 0, opacity: 0 }}
transition={{
type: "spring",
stiffness: 300,
damping: 25,
duration: 0.3
}}
onClick={handleClick}
>
{/* Invisible edit point - just for click handling */}
<motion.div
className="relative rounded-full"
style={{
width: size * 2, // Larger click area
height: size * 2,
backgroundColor: 'transparent', // Invisible
}}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
/>
</motion.div>
);
};