From ea7ca5474ed0eb0d1f9dfb83af989a12918b9f05 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 9 Feb 2026 23:34:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(annotation):=20=E6=A0=87=E6=B3=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=AF=E8=A7=86=E5=8C=96=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E7=88=B6=E8=8A=82=E7=82=B9=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=99=90=E5=88=B6=E5=AD=90=E6=A0=87=E7=AD=BE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据选中节点类型动态过滤"添加子节点"和"添加同级节点"下拉选项: - 标注控件(如 Choices/RectangleLabels)仅允许添加对应的子标签(Choice/Label) - 无子节点的控件(如 TextArea/Rating)和数据对象标签禁用添加子节点 - Choice 节点允许嵌套 Choice(支持 Taxonomy 层级结构) - View 容器允许添加所有标签类型但排除裸子标签 Co-Authored-By: Claude Opus 4.6 --- .../TemplateConfigurationTreeEditor.tsx | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/DataAnnotation/components/TemplateConfigurationTreeEditor.tsx b/frontend/src/pages/DataAnnotation/components/TemplateConfigurationTreeEditor.tsx index 9dffc18..6d5a44c 100644 --- a/frontend/src/pages/DataAnnotation/components/TemplateConfigurationTreeEditor.tsx +++ b/frontend/src/pages/DataAnnotation/components/TemplateConfigurationTreeEditor.tsx @@ -566,6 +566,76 @@ const TemplateConfigurationTreeEditor = ({ return options; }, [objectOptions, controlOptions]); + type TagOptionGroup = { label: string; options: { value: string; label: string }[] }; + + const getAllowedChildOptions = useCallback( + (node: XmlNode): TagOptionGroup[] => { + if (!config) return tagOptions; + + const controlConfig = config.controls?.[node.tag]; + const objectConfig = config.objects?.[node.tag]; + + // Control with child_tag: only allow that specific child tag + if (controlConfig?.requires_children && controlConfig.child_tag) { + const childTag = controlConfig.child_tag; + return [ + { + label: "子标签", + options: [ + { + value: childTag, + label: COMMON_TAG_DISPLAY_NAMES[childTag] || childTag, + }, + ], + }, + ]; + } + + // Control without requires_children: no children allowed + if (controlConfig && !controlConfig.requires_children) { + return []; + } + + // Object tag: no children + if (objectConfig) { + return []; + } + + // Child tag: Choice allows nested Choice (for Taxonomy), others don't + if (CHILD_TAGS.includes(node.tag)) { + if (node.tag === "Choice") { + return [ + { + label: "子标签", + options: [ + { + value: "Choice", + label: COMMON_TAG_DISPLAY_NAMES.Choice || "Choice", + }, + ], + }, + ]; + } + return []; + } + + // View or other containers: show all except bare child tags + return tagOptions.filter((group) => group.label !== "子标签"); + }, + [config, tagOptions] + ); + + const childTagOptions = useMemo( + () => getAllowedChildOptions(selectedNode), + [getAllowedChildOptions, selectedNode] + ); + + const siblingTagOptions = useMemo(() => { + const { parent } = findNodeWithParent(tree, selectedNode.id); + if (!parent) return []; + return getAllowedChildOptions(parent); + }, [tree, selectedNode, getAllowedChildOptions]); + const getTagDisplayName = useCallback( (tag: string) => { if (config?.objects?.[tag]) return getObjectDisplayName(tag); @@ -796,21 +866,21 @@ const TemplateConfigurationTreeEditor = ({
{ handleAddNode(value, "sibling"); }} - disabled={isStructureLocked || selectedNode.id === tree.id} + disabled={isStructureLocked || selectedNode.id === tree.id || siblingTagOptions.length === 0} />