import { useCallback, useRef } from 'react'; import { DEFAULT_ACTIVITY_POSTER_QR_SLOT, type ActivityPosterQrSlot } from '@dukang/shared-types'; type Props = { imageUrl?: string; value?: ActivityPosterQrSlot; onChange?: (slot: ActivityPosterQrSlot) => void; }; export default function ActivityPosterQrSlotEditor({ imageUrl, value, onChange }: Props) { const boxRef = useRef(null); const dragRef = useRef<{ mode: 'move' | 'resize'; startX: number; startY: number; start: ActivityPosterQrSlot; } | null>(null); const slot = value ?? DEFAULT_ACTIVITY_POSTER_QR_SLOT; const apply = useCallback((next: ActivityPosterQrSlot) => { const size = Math.min(50, Math.max(5, next.qrSizePct)); const box = boxRef.current; const ratio = box && box.clientHeight > 0 ? box.clientWidth / box.clientHeight : 1; const heightPct = size * ratio; const x = Math.min(100 - size, Math.max(0, next.qrXPct)); const y = Math.min(Math.max(0, 100 - heightPct), Math.max(0, next.qrYPct)); onChange?.({ qrXPct: Number(x.toFixed(2)), qrYPct: Number(y.toFixed(2)), qrSizePct: Number(size.toFixed(2)), }); }, [onChange]); function onPointerDown(mode: 'move' | 'resize', e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); dragRef.current = { mode, startX: e.clientX, startY: e.clientY, start: { ...slot } }; } function onPointerMove(e: React.PointerEvent) { const drag = dragRef.current; const box = boxRef.current; if (!drag || !box) return; const dx = ((e.clientX - drag.startX) / box.clientWidth) * 100; const dy = ((e.clientY - drag.startY) / box.clientHeight) * 100; if (drag.mode === 'move') { apply({ ...drag.start, qrXPct: drag.start.qrXPct + dx, qrYPct: drag.start.qrYPct + dy, }); } else { apply({ ...drag.start, qrSizePct: drag.start.qrSizePct + dx }); } } function endDrag() { dragRef.current = null; } if (!imageUrl) { return
请先上传活动图,再拖拽定位方形码栏
; } return (
活动图预览
onPointerDown('move', e)} >
onPointerDown('resize', e)} />
拖拽移动码栏,拉右下角调整大小(边长相对图宽 {slot.qrSizePct}%)
); }