From d3d9e8385b5d9b2fdc0a38ce8958c588987813b4 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Wed, 15 Apr 2026 11:04:05 +0800 Subject: [PATCH] fix(point): move --- components/label/usePaperStore.ts | 303 +++++++++++++++--------------- 1 file changed, 151 insertions(+), 152 deletions(-) diff --git a/components/label/usePaperStore.ts b/components/label/usePaperStore.ts index 73fa3dd..77fcd01 100644 --- a/components/label/usePaperStore.ts +++ b/components/label/usePaperStore.ts @@ -462,6 +462,122 @@ const getPaperSelectionPath = ( return nearestPath } +const getNearestPaperPathSegmentIndex = ( + path: paper.Path, + localPoint: paper.Point +) => { + let nearestIndex = 0 + let nearestDistance = Infinity + + path.segments.forEach((segment, index) => { + const distance = segment.point.getDistance(localPoint) + if (distance < nearestDistance) { + nearestDistance = distance + nearestIndex = index + } + }) + + return nearestIndex +} + +const normalizePointPanHitResult = ( + hitResult: paper.HitResult | null, + localPoint: paper.Point +) => { + if (!hitResult?.item?.data?.id || hitResult.item.data?.type !== "circle") { + return hitResult + } + + const selectedPath = getPaperSelectionPath(hitResult.item.data.id, localPoint) + if ( + !(selectedPath instanceof paper.Path) || + selectedPath.data?.type !== "point" || + !selectedPath.segments.length + ) { + return hitResult + } + + const segmentIndex = getNearestPaperPathSegmentIndex(selectedPath, localPoint) + + return { + ...hitResult, + item: selectedPath, + type: "segment", + segment: selectedPath.segments[segmentIndex], + } as paper.HitResult +} + +const getPointCirclesById = (id: number) => + (usePaperStore.getState().group?.getItems({ + data: { + id, + type: "circle", + }, + }) as paper.Path.Circle[]) || [] + +const createPointCircleForPath = ( + path: paper.Path, + point: paper.Point, + index: number +) => { + const circle = usePaperStore + .getState() + .getPointCircle(point, index, path.data.id, path.data.blankColor) + + circle.data = Object.assign({}, circle.data, { + id: path.data.id, + imageId: path.data.imageId, + operationId: path.data.operationId, + text: index + 1, + fillColor: path.data.blankColor, + strokeColor: path.data.strokeColor, + }) + circle.fillColor = path.data.blankColor || circle.fillColor || null + circle.strokeColor = path.data.strokeColor || circle.strokeColor || null + + return circle +} + +const syncPointCirclesWithPath = (path: paper.Path) => { + if (path.data?.type !== "point" || !path.data?.id) return [] + + const unusedCircles = getPointCirclesById(path.data.id).slice() + const orderedCircles = path.segments.map((segment, index) => { + const matchedCircleIndex = unusedCircles.findIndex((circle) => { + return ( + circle.position.getDistance(segment.point) <= 0.01 || + circle.contains(segment.point) + ) + }) + + const circle = + matchedCircleIndex >= 0 + ? unusedCircles.splice(matchedCircleIndex, 1)[0] + : createPointCircleForPath(path, segment.point, index) + + circle.position.x = segment.point.x + circle.position.y = segment.point.y + circle.data = Object.assign({}, circle.data, { + id: path.data.id, + imageId: path.data.imageId, + operationId: path.data.operationId, + text: index + 1, + fillColor: path.data.blankColor, + strokeColor: path.data.strokeColor, + }) + circle.fillColor = path.data.blankColor || circle.fillColor || null + circle.strokeColor = path.data.strokeColor || circle.strokeColor || null + + return circle + }) + + unusedCircles.forEach((circle) => { + circle.remove() + }) + + return orderedCircles +} + const trySelectPaperObjectByPoint = (clickPoint: paper.Point) => { const group = usePaperStore.getState().group if (!group) return false @@ -1090,8 +1206,6 @@ export const usePaperStore = create((set) => ({ ? baseTolerance / useTopToolsStore.getState().scale : 0 - false && getPanHitTolerance - const createPanHitOptions = ( tolerance: number, match: (hit: paper.HitResult) => boolean @@ -1115,7 +1229,7 @@ export const usePaperStore = create((set) => ({ if (hoveredCircleInfo) { const pointHit = group.hitTest( projectPoint, - createPanHitOptions(0, (hit) => { + createPanHitOptions(getPanHitTolerance(), (hit) => { return ( hit.item.data?.type === "pathCircle" && hit.item.data?.id === hoveredCircleInfo.id && @@ -1132,7 +1246,7 @@ export const usePaperStore = create((set) => ({ if (hoveredBufferInfo) { const edgeHit = group.hitTest( projectPoint, - createPanHitOptions(0, (hit) => { + createPanHitOptions(getPanHitTolerance(), (hit) => { return ( hit.item.data?.type === "pathBuff" && hit.item.data?.id === hoveredBufferInfo.id && @@ -1145,8 +1259,8 @@ export const usePaperStore = create((set) => ({ return group.hitTest( projectPoint, - createPanHitOptions(0, (hit) => - ["rectangle", "polygon", "brush", "point"].includes( + createPanHitOptions(getPanHitTolerance(), (hit) => + ["rectangle", "polygon", "brush", "point", "circle"].includes( hit.item.data?.type || "" ) ) @@ -1197,6 +1311,7 @@ export const usePaperStore = create((set) => ({ hasExceededPanDragThreshold = false let hitResult = resolvePanHitResult(e.point) + hitResult = normalizePointPanHitResult(hitResult, point) console.log("hitResult", hitResult) const hoveredBufferInfo = hoveredBufferPathInfo?.path.parent ? hoveredBufferPathInfo @@ -1475,27 +1590,26 @@ export const usePaperStore = create((set) => ({ activePath = hitResult.item as paper.Path } } else if (hitResult.item.data.type === "point") { - // 只有点击圆才视为选中 - if (hitResult.type === "segment") { - // Path 类型 - if (hitResult.item instanceof paper.Path) { - if (!hitResult.item.data.selected) { - hitResult.item.data.selected = true - // 绘制遮罩 - usePaperSupportStore.getState().handleMask(hitResult.item) - // 绘制点对应的数字 - usePaperSupportStore - .getState() - .handleText(hitResult.item as paper.Path) - // 绘制选中边 - usePaperSupportStore - .getState() - .handleBufferPaths(hitResult.item) - // 绘制选中圆 - usePaperSupportStore.getState().handleCircles(hitResult.item) - } - activePath = hitResult.item as paper.Path + if ( + ["segment", "stroke"].includes(hitResult.type) && + hitResult.item instanceof paper.Path + ) { + if (!hitResult.item.data.selected) { + hitResult.item.data.selected = true + // 绘制遮罩 + usePaperSupportStore.getState().handleMask(hitResult.item) + // 绘制点对应的数字 + usePaperSupportStore + .getState() + .handleText(hitResult.item as paper.Path) + // 绘制选中边 + usePaperSupportStore + .getState() + .handleBufferPaths(hitResult.item) + // 绘制选中圆 + usePaperSupportStore.getState().handleCircles(hitResult.item) } + activePath = hitResult.item as paper.Path } } else if ( hitResult.item.data.type === "pathBuff" || @@ -1598,21 +1712,7 @@ export const usePaperStore = create((set) => ({ } if (currentPath.data.type === "point") { usePaperSupportStore.getState().handleText(currentPath) - let circles = usePaperStore.getState().group!.getItems({ - data: { - id: currentPath.data.id, - type: "circle", - }, - }) as paper.Path[] - - currentPath.segments.forEach((segment, index) => { - let findIndex = circles.findIndex((circle) => { - return circle.contains(segment.point) - }) - circles[index].data = Object.assign({}, circles[index]?.data, { - text: findIndex + 1, - }) - }) + syncPointCirclesWithPath(currentPath) usePaperSupportStore.getState().handleBufferPaths(currentPath) usePaperSupportStore.getState().handleCircles(currentPath) } @@ -1677,15 +1777,9 @@ export const usePaperStore = create((set) => ({ } } else { activePath.insert(hitIndex + 1, hitResult.location.point) - activePath?.data?.type === "point" && - usePaperStore - .getState() - .getPointCircle( - hitResult.location.point, - hitIndex + 1, - activePath.data.id, - activePath.data.blankColor - ) + if (activePath?.data?.type === "point") { + syncPointCirclesWithPath(activePath) + } activePath.bringToFront() usePaperSupportStore.getState().handleBufferPaths(activePath) usePaperSupportStore.getState().handleCircles(activePath) @@ -1699,21 +1793,7 @@ export const usePaperStore = create((set) => ({ if (activePath?.data?.type === "point") { // 绘制点对应的数字 usePaperSupportStore.getState().handleText(activePath) - let circles = usePaperStore.getState().group!.getItems({ - data: { - id: activePath.data.id, - type: "circle", - }, - }) as paper.Path[] - - activePath.segments.forEach((segment, index) => { - let findIndex = circles.findIndex((circle) => { - return circle.contains(segment.point) - }) - circles[index].data = Object.assign({}, circles[index]?.data, { - text: findIndex + 1, - }) - }) + syncPointCirclesWithPath(activePath) usePaperSupportStore.getState().handleBufferPaths(activePath) usePaperSupportStore.getState().handleCircles(activePath) } @@ -1807,39 +1887,7 @@ export const usePaperStore = create((set) => ({ usePaperSupportStore.getState().handleMask(hitResult.item) } if (activePath?.data?.type === "point") { - let paths = usePaperStore.getState().group!.getItems({ - data: { - id: activePath.data.id, - // text: activePath.segments.length - hitIndex + 1, - type: "circle", - }, - }) as paper.Path[] - - // paths = paths.filter((item) => { - // if (item.data.text === hitIndex + 1) item.remove(); - // return item.data.text !== hitIndex + 1; - // }); - paths = paths.filter((circle) => { - let findIndex = activePath.segments.findIndex((segment) => { - return circle.contains(segment.point) - }) - if (findIndex === -1) { - circle.remove() - return false - } else { - return true - } - }) - - // 1. 对数组按照 text 属性进行排序 - let sortedArr = paths - .slice() - .sort((a, b) => a.data.text - b.data.text) - - // 2. 重新赋值 text 属性为从 1 开始的连续自然数 - sortedArr.forEach((item, index) => { - item.data.text = index + 1 - }) + syncPointCirclesWithPath(activePath) // paths.forEach((child) => { // if ( @@ -2083,27 +2131,11 @@ export const usePaperStore = create((set) => ({ ]!.position.y = point!.y usePaperSupportStore.getState().handleMask(path) } else if (path.data.type === "point") { - let circles = usePaperStore.getState().group!.getItems({ - data: { - id: path.data.id, - type: "circle", - }, - }) as paper.Path[] + const circles = syncPointCirclesWithPath(path) - // let index = path.segments.length - hitIndex - 1 - // let index = hitIndex; - - // 移动点 - if (circles && circles.length) { - // let circle = circles.find( - // (item) => item.data.text === index + 1 - // ); - - let circle = circles.find((circle) => { - return circle.contains(path.segments[hitIndex].point) - }) - circle!.position.x = point!.x - circle!.position.y = point!.y + if (circles[hitIndex]) { + circles[hitIndex].position.x = point!.x + circles[hitIndex].position.y = point!.y } // 移动路径 path.segments[hitIndex].point.x = point!.x @@ -2315,45 +2347,12 @@ export const usePaperStore = create((set) => ({ } const handlePointIntersectRaster = (path: paper.Path) => { - let circles = usePaperStore.getState().group!.getItems({ - data: { - id: path.data.id, - type: "circle", - }, - }) as paper.Path[] - // 去除超出图片边界的部分 - let newCircles = circles.filter((circle) => { - if ( - !usePaperStore - .getState() - .rasterPath!.bounds.contains(circle.bounds.center) - ) { - circle.remove() - return false - } else { - return true - } - }) - - // 1. 对数组按照 text 属性进行排序 - let sortedArr = newCircles - .slice() - .sort((a, b) => a.data.text - b.data.text) - - // 2. 重新赋值 text 属性为从 1 开始的连续自然数 - sortedArr.forEach((item, index) => { - item.data.text = index + 1 - }) - - // newCircles.forEach((circle, circleIndex) => { - // // 重新设置显示数字 - // circle.data.text = circleIndex + 1; - // }); path.segments = path.segments.filter((segment) => { return usePaperStore .getState() .rasterPath!.bounds.contains(segment.point) }) + syncPointCirclesWithPath(path) // 去除超出图片边界的部分 end usePaperSupportStore.getState().handleMask(path) usePaperSupportStore.getState().handleBufferPaths(path)