fix(annotation): 标注配置可视化编辑器根据父节点类型限制子标签选项

根据选中节点类型动态过滤"添加子节点"和"添加同级节点"下拉选项:
- 标注控件(如 Choices/RectangleLabels)仅允许添加对应的子标签(Choice/Label)
- 无子节点的控件(如 TextArea/Rating)和数据对象标签禁用添加子节点
- Choice 节点允许嵌套 Choice(支持 Taxonomy 层级结构)
- View 容器允许添加所有标签类型但排除裸子标签

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 23:34:41 +08:00
parent 8ffa131fad
commit ea7ca5474e

View File

@@ -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 = ({
<div className="grid grid-cols-2 gap-2">
<Select
placeholder="添加子节点"
options={tagOptions}
options={childTagOptions}
value={null}
onChange={(value) => {
handleAddNode(value, "child");
}}
disabled={isStructureLocked}
disabled={isStructureLocked || childTagOptions.length === 0}
/>
<Select
placeholder="添加同级节点"
options={tagOptions}
options={siblingTagOptions}
value={null}
onChange={(value) => {
handleAddNode(value, "sibling");
}}
disabled={isStructureLocked || selectedNode.id === tree.id}
disabled={isStructureLocked || selectedNode.id === tree.id || siblingTagOptions.length === 0}
/>
<Button
danger