From fca9430246af1938a3d4433059c65d864bf0277e Mon Sep 17 00:00:00 2001 From: zhangheng Date: Thu, 16 Apr 2026 16:36:24 +0800 Subject: [PATCH] fix(tag): bug --- .../label/components/RightObjectTools.tsx | 72 +++++++++++++------ components/label/usePaperStore.ts | 50 +++++++++---- components/label/useRenderTag.ts | 20 +++--- components/label/utils/objectVisibility.ts | 28 ++++++-- 4 files changed, 121 insertions(+), 49 deletions(-) diff --git a/components/label/components/RightObjectTools.tsx b/components/label/components/RightObjectTools.tsx index 74d1e9f..cb1509e 100644 --- a/components/label/components/RightObjectTools.tsx +++ b/components/label/components/RightObjectTools.tsx @@ -166,6 +166,24 @@ const RightObjectTools: React.FC = (props) => { [activeImage, continueObjectId, storeLabel] ) + const getObjectItemsById = useCallback( + (id: number) => { + return getItemsById(id).filter((item) => item.data?.type !== "tag") + }, + [getItemsById] + ) + + const bringTagItemsToFront = useCallback( + (id: number) => { + getItemsById(id).forEach((item) => { + if (item.data?.type === "tag") { + item.bringToFront() + } + }) + }, + [getItemsById] + ) + // 切换选中对象时,清除所有其他对象的当前选中状态 const clearSelectedItems = () => { usePaperStore @@ -215,11 +233,16 @@ const RightObjectTools: React.FC = (props) => { const handleSelect = useCallback( (id: number, operationId: any) => { + if (pathStatus[activeImage + id]?.HIDE) return + + const objectItems = getObjectItemsById(id) + const targetItem = getItemById(id) + if (selectedPath[activeImage]?.length) { if (!selectedPath[activeImage]?.includes(id)) { if (pressShift) { - if (getItemsById(id)?.length) { - getItemsById(id).forEach((item) => { + if (objectItems.length && targetItem) { + objectItems.forEach((item) => { if (item instanceof paper.Path) { handleSelectedPath(item) } else if (item instanceof paper.CompoundPath) { @@ -228,13 +251,14 @@ const RightObjectTools: React.FC = (props) => { else item.data.selected = true } }) + bringTagItemsToFront(id) let category = useTopToolsStore .getState() .objectOperations.find( (item) => item.category_id.toString() === operationId ) - const globalPosition = getItemById(id)!.globalMatrix.transform( - getItemById(id)!.position + const globalPosition = targetItem.globalMatrix.transform( + targetItem.position ) const { top, left } = getShowContextMenuPosition( globalPosition, @@ -251,8 +275,9 @@ const RightObjectTools: React.FC = (props) => { } else { clearSelectedItems() selectedPath[activeImage].forEach((selectedId) => { - if (getItemsById(selectedId)?.length) { - getItemsById(selectedId).forEach((item) => { + const selectedObjectItems = getObjectItemsById(selectedId) + if (selectedObjectItems.length) { + selectedObjectItems.forEach((item) => { cancelSelectedPathColor(item) }) useOtherToolsStore.getState().setShowContextMenu({ @@ -267,8 +292,8 @@ const RightObjectTools: React.FC = (props) => { }) } }) - if (getItemsById(id)?.length) { - getItemsById(id).forEach((item) => { + if (objectItems.length && targetItem) { + objectItems.forEach((item) => { if (item instanceof paper.Path) { handleSelectedPath(item) } else if (item instanceof paper.CompoundPath) { @@ -277,13 +302,14 @@ const RightObjectTools: React.FC = (props) => { else item.data.selected = true } }) + bringTagItemsToFront(id) let category = useTopToolsStore .getState() .objectOperations.find( (item) => item.category_id.toString() === operationId ) - const globalPosition = getItemById(id)!.globalMatrix.transform( - getItemById(id)!.position + const globalPosition = targetItem.globalMatrix.transform( + targetItem.position ) const { top, left } = getShowContextMenuPosition( globalPosition, @@ -302,8 +328,8 @@ const RightObjectTools: React.FC = (props) => { } else { clearSelectedItems() updateSelectedPath(activeImage, "DELETE", id) - if (getItemsById(id)?.length) { - getItemsById(id).forEach((item) => { + if (objectItems.length) { + objectItems.forEach((item) => { cancelSelectedPathColor(item) }) useOtherToolsStore.getState().setShowContextMenu({ @@ -321,8 +347,8 @@ const RightObjectTools: React.FC = (props) => { } else { clearSelectedItems() updateSelectedPath(activeImage, "ADD", id) - if (getItemsById(id)?.length) { - getItemsById(id).forEach((item) => { + if (objectItems.length && targetItem) { + objectItems.forEach((item) => { if (item instanceof paper.Path) { handleSelectedPath(item) } else if (item instanceof paper.CompoundPath) { @@ -337,14 +363,15 @@ const RightObjectTools: React.FC = (props) => { // item.fillColor = item.data?.fillColor; // item.bringToFront(); }) + bringTagItemsToFront(id) let category = useTopToolsStore .getState() .objectOperations.find( (item) => item.category_id.toString() === operationId ) - const globalPosition = getItemById(id)!.globalMatrix.transform( - getItemById(id)!.position + const globalPosition = targetItem.globalMatrix.transform( + targetItem.position ) const { top, left } = getShowContextMenuPosition( globalPosition, @@ -362,8 +389,10 @@ const RightObjectTools: React.FC = (props) => { }, [ activeImage, + bringTagItemsToFront, + getObjectItemsById, getItemById, - getItemsById, + pathStatus, pressShift, selectedPath, updateSelectedPath, @@ -889,10 +918,13 @@ const RightObjectTools: React.FC = (props) => { : "var(--mantine-color-gray-0)" } c={isSelected ? "white" : undefined} - onClick={() => + onClick={() => { + if (isChildHide) return handleSelect(child[0], operationId) - } - style={{ cursor: "pointer" }}> + }} + style={{ + cursor: isChildHide ? "default" : "pointer", + }}> { + if (!item) return + const group = usePaperStore.getState().group + if (!group || item.parent !== group) return + const firstTagItem = group.getItems({ data: { type: "tag" } })[0] + if (!firstTagItem || firstTagItem === item) return + item.insertBelow(firstTagItem) +} + const handleDelete = () => { const group = usePaperStore.getState().group! const imageId = useBottomToolsStore.getState().activeImage @@ -450,12 +461,7 @@ const clearPaperSelectedObjects = () => { .getState() .getItemsById(id) .forEach((item) => { - if ( - (item instanceof paper.Path || item instanceof paper.CompoundPath) && - ["rectangle", "polygon"].includes(item.data?.type || "") - ) { - item.fillColor = item.data?.blankColor || item.fillColor || null - } + resetPaperShapeFillColor(item) }) }) @@ -464,6 +470,16 @@ const clearPaperSelectedObjects = () => { }) } +const resetPaperShapeFillColor = (item: paper.Item) => { + if (!(item instanceof paper.Path || item instanceof paper.CompoundPath)) { + return + } + if (!["rectangle", "polygon"].includes(item.data?.type || "")) { + return + } + item.fillColor = item.data?.blankColor || item.fillColor || null +} + const applyPaperSelectedPath = (item: paper.Path | paper.CompoundPath) => { if (item.data.type === "rectangle" && item instanceof paper.Path) { item.data.selected = true @@ -2011,15 +2027,14 @@ export const usePaperStore = create((set) => ({ type: "tag", }, }) - // 标识位为true时:选中路径隐藏tags; 没选中显示tags - const configs = useTopToolsStore.getState().showTagsConfigs + // 仅恢复当前应显示的标签,隐藏对象的标签保持不可见 if (useTopToolsStore.getState().showTags) { const setTagVisibleByConfig = (tag: any) => { - if (tag.data.tag_category === "mask") - tag.visible = configs.includes("外框") - else if (tag.data.tag_category === "point_text") - tag.visible = configs.includes("点ID") - else tag.visible = true + tag.visible = getObjectTagVisibility( + useBottomToolsStore.getState().activeImage, + Number(tag.data?.id), + tag.data?.tag_category + ) } lastTags.forEach(setTagVisibleByConfig) tags.forEach(setTagVisibleByConfig) @@ -3147,6 +3162,7 @@ export const usePaperStore = create((set) => ({ }) ) polygon.data = Object.assign({ type: "polygon", ...option }, data) + insertItemBelowTags(polygon) points?.forEach((point) => { polygon.add(point) }) @@ -3219,6 +3235,7 @@ export const usePaperStore = create((set) => ({ }) ) compoundPath.data = Object.assign({ type: "polygon", ...option }, data) + insertItemBelowTags(compoundPath) compoundPath.onMouseDown = (e: { event: MouseEvent point: paper.Point @@ -4606,8 +4623,7 @@ export const usePaperStore = create((set) => ({ .getState() .group!.getItems({}) .forEach((item) => { - // clear color - item.fillColor = item?.data?.blankColor + resetPaperShapeFillColor(item) }) } @@ -4803,6 +4819,7 @@ export const usePaperStore = create((set) => ({ Object.assign(option, { strokeScaling: false }) ) rect.data = Object.assign({ type: "rectangle", ...option }, data) + insertItemBelowTags(rect) rect.onMouseEnter = (_e: paper.MouseEvent) => { // if (usePaperStore.getState().mode === "pan") { // e.target.strokeWidth += 2; @@ -5045,6 +5062,7 @@ export const usePaperStore = create((set) => ({ ) ) brush.data = Object.assign({ type: "brush", ...option }, data) + insertItemBelowTags(brush) // points?.forEach((point) => { // brush.add(point); // }); @@ -5545,6 +5563,7 @@ export const usePaperStore = create((set) => ({ const newScale = useTopToolsStore.getState().scale / currentScaling myCircle.scale(newScale / currentScaling) myCircle.data = Object.assign({ type: "circle", ...option }, data) + insertItemBelowTags(myCircle) syncPaperCircleDiameter(myCircle) myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => { if (e.event && e.event.button === 2) { @@ -5614,6 +5633,7 @@ export const usePaperStore = create((set) => ({ ) ) myCircleLine.data = Object.assign({ type: "point", ...option }, data) + insertItemBelowTags(myCircleLine) return myCircleLine }, diff --git a/components/label/useRenderTag.ts b/components/label/useRenderTag.ts index 05b966c..7060171 100644 --- a/components/label/useRenderTag.ts +++ b/components/label/useRenderTag.ts @@ -4,6 +4,7 @@ import { useLabelStore } from "./store" import { useBottomToolsStore } from "./useBottomToolsStore" import { usePaperStore } from "./usePaperStore" import { useTopToolsStore } from "./useTopToolsStore" +import { getObjectTagVisibility } from "./utils/objectVisibility" const TAG_FONT_SIZE = 12 const TAG_VERTICAL_GAP = 8 @@ -159,13 +160,12 @@ const useRenderTag = () => { const activeImage = useBottomToolsStore.getState().activeImage const group = usePaperStore.getState().group if (!group) return - const visible = useTopToolsStore.getState().showTags const configs = useTopToolsStore.getState().showTagsConfigs const groupScale = Number.isFinite(group.scaling?.x) && group.scaling.x !== 0 ? group.scaling.x : 1 - const applyPointTagCompensation = ( + const applyScreenSpaceTagCompensation = ( item: paper.Item, anchor: [number, number] ) => { @@ -235,7 +235,7 @@ const useRenderTag = () => { tag_anchor: tagAnchor, }, parent: group, - visible, + visible: getObjectTagVisibility(activeImage, id, "text"), }) text.translate( new paper.Point( @@ -259,8 +259,10 @@ const useRenderTag = () => { tag_anchor: tagAnchor, }, parent: group, - visible, + visible: getObjectTagVisibility(activeImage, id, "text_mask"), }) + applyScreenSpaceTagCompensation(text, tagAnchor) + applyScreenSpaceTagCompensation(textMask, tagAnchor) text.bringToFront() } @@ -276,7 +278,7 @@ const useRenderTag = () => { tag_category: "mask", }, parent: group, - visible: visible && configs.includes("外框"), + visible: getObjectTagVisibility(activeImage, id, "mask"), }) // 标注对象点id if (path instanceof paper.CompoundPath) { @@ -301,9 +303,9 @@ const useRenderTag = () => { tag_anchor: pointTextPosition, }, parent: group, - visible: visible && configs.includes("点ID"), + visible: getObjectTagVisibility(activeImage, id, "point_text"), }) - applyPointTagCompensation(pointText, pointTextPosition) + applyScreenSpaceTagCompensation(pointText, pointTextPosition) index++ }) }) @@ -327,9 +329,9 @@ const useRenderTag = () => { tag_anchor: pointTextPosition, }, parent: group, - visible: visible && configs.includes("点ID"), + visible: getObjectTagVisibility(activeImage, id, "point_text"), }) - applyPointTagCompensation(pointText, pointTextPosition) + applyScreenSpaceTagCompensation(pointText, pointTextPosition) }) } }) diff --git a/components/label/utils/objectVisibility.ts b/components/label/utils/objectVisibility.ts index 3818f45..2ae76ca 100644 --- a/components/label/utils/objectVisibility.ts +++ b/components/label/utils/objectVisibility.ts @@ -5,7 +5,7 @@ import { usePaperStore } from "../usePaperStore" import { usePaperSupportStore } from "../usePaperSupportStore" import { useTopToolsStore } from "../useTopToolsStore" -const getTagVisibility = (tagCategory?: string) => { +const getTagVisibilityByConfig = (tagCategory?: string) => { const { showTags, showTagsConfigs } = useTopToolsStore.getState() if (!showTags) return false if (tagCategory === "mask") return showTagsConfigs.includes("外框") @@ -13,6 +13,16 @@ const getTagVisibility = (tagCategory?: string) => { return true } +export const getObjectTagVisibility = ( + imageId: string, + objectId: number, + tagCategory?: string +) => { + const { pathStatus } = useObjectStore.getState() + if (pathStatus[imageId + objectId]?.HIDE) return false + return getTagVisibilityByConfig(tagCategory) +} + const setSelectedItemFalse = (id: number) => { if (!id) return const sameIdItems = usePaperStore.getState().getItemsById(id) @@ -50,7 +60,11 @@ const clearHiddenObjectSelectionState = (imageId: string, objectId: number) => { } } -const syncObjectItemsVisibility = (objectId: number, hide: boolean) => { +const syncObjectItemsVisibility = ( + imageId: string, + objectId: number, + hide: boolean +) => { const sameIdItems = usePaperStore.getState().getItemsById(objectId) if (!sameIdItems?.length) return sameIdItems.forEach((item) => { @@ -59,7 +73,11 @@ const syncObjectItemsVisibility = (objectId: number, hide: boolean) => { return } if (item.data?.type === "tag") { - item.visible = getTagVisibility(item.data?.tag_category) + item.visible = getObjectTagVisibility( + imageId, + objectId, + item.data?.tag_category + ) return } item.visible = true @@ -77,7 +95,7 @@ export const toggleObjectHideByShortcut = ( if (hide) { updatePathStatus(imageId + objectId, "HIDE", hide) - syncObjectItemsVisibility(objectId, hide) + syncObjectItemsVisibility(imageId, objectId, hide) setSelectedItemFalse(objectId) clearHiddenObjectSelectionState(imageId, objectId) @@ -96,5 +114,5 @@ export const toggleObjectHideByShortcut = ( updateOperationStatus(imageId + operationId, "HIDE", hide) updatePathStatus(imageId + objectId, "HIDE", hide) - syncObjectItemsVisibility(objectId, hide) + syncObjectItemsVisibility(imageId, objectId, hide) }