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 = ({