feat(annotation): 替换模板配置表单为树形编辑器组件

- 移除 TemplateConfigurationForm 组件并引入 TemplateConfigurationTreeEditor
- 使用 useTagConfig Hook 获取标签配置
- 将自定义XML状态 customXml 替换为 labelConfig
- 删除模板编辑标签页和选择模板状态管理
- 更新XML解析逻辑支持更多对象和标注控件类型
- 添加配置验证功能确保至少包含数据对象和标注控件
- 在模板详情页面使用树形编辑器显示配置详情
- 更新任务创建页面集成新的树形配置编辑器
- 调整预览数据生成功能适配新的XML解析方式
This commit is contained in:
2026-01-23 16:11:59 +08:00
parent 76d06b9809
commit 3f566a0b08
14 changed files with 1383 additions and 900 deletions

View File

@@ -1,8 +1,7 @@
import React from "react";
import { Modal, Descriptions, Tag, Space, Divider, Card, Typography } from "antd";
import { Modal, Descriptions, Tag, Divider, Card } from "antd";
import type { AnnotationTemplate } from "../annotation.model";
const { Text, Paragraph } = Typography;
import TemplateConfigurationTreeEditor from "../components/TemplateConfigurationTreeEditor";
interface TemplateDetailProps {
visible: boolean;
@@ -64,88 +63,26 @@ const TemplateDetail: React.FC<TemplateDetailProps> = ({
<Divider></Divider>
<Card title="数据对象" size="small" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: "100%" }}>
{template.configuration.objects.map((obj, index) => (
<Card key={index} size="small" type="inner">
<Space>
<Text strong></Text>
<Tag>{obj.name}</Tag>
<Text strong></Text>
<Tag color="blue">{obj.type}</Tag>
<Text strong></Text>
<Tag color="green">{obj.value}</Tag>
</Space>
</Card>
))}
</Space>
</Card>
<Card title="标注控件" size="small" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: "100%" }} size="middle">
{template.configuration.labels.map((label, index) => (
<Card key={index} size="small" type="inner" title={`控件 ${index + 1}`}>
<Space direction="vertical" style={{ width: "100%" }}>
<div>
<Text strong></Text>
<Tag>{label.fromName}</Tag>
<Text strong style={{ marginLeft: 16 }}></Text>
<Tag>{label.toName}</Tag>
<Text strong style={{ marginLeft: 16 }}></Text>
<Tag color="purple">{label.type}</Tag>
{label.required && <Tag color="red"></Tag>}
</div>
{label.description && (
<div>
<Text strong></Text>
<Text type="secondary">{label.description}</Text>
</div>
)}
{label.options && label.options.length > 0 && (
<div>
<Text strong></Text>
<div style={{ marginTop: 4 }}>
{label.options.map((opt, i) => (
<Tag key={i} color="cyan">{opt}</Tag>
))}
</div>
</div>
)}
{label.labels && label.labels.length > 0 && (
<div>
<Text strong></Text>
<div style={{ marginTop: 4 }}>
{label.labels.map((lbl, i) => (
<Tag key={i} color="geekblue">{lbl}</Tag>
))}
</div>
</div>
)}
</Space>
</Card>
))}
</Space>
<Card title="标注配置树" size="small" style={{ marginBottom: 16 }}>
<TemplateConfigurationTreeEditor
value={template.labelConfig || ""}
readOnly={true}
readOnlyStructure={true}
height={360}
/>
</Card>
{template.labelConfig && (
<Card title="Label Studio XML 配置" size="small">
<Paragraph>
<pre style={{
background: "#f5f5f5",
padding: 12,
borderRadius: 4,
overflow: "auto",
maxHeight: 300
}}>
{template.labelConfig}
</pre>
</Paragraph>
<pre style={{
background: "#f5f5f5",
padding: 12,
borderRadius: 4,
overflow: "auto",
maxHeight: 300
}}>
{template.labelConfig}
</pre>
</Card>
)}
</Modal>

View File

@@ -13,7 +13,7 @@ import {
} from "../annotation.api";
import type { AnnotationTemplate } from "../annotation.model";
import { DataTypeMap, ClassificationMap, AnnotationTypeMap } from "../annotation.const";
import TemplateConfigurationForm from "../components/TemplateConfigurationForm";
import TemplateConfigurationTreeEditor from "../components/TemplateConfigurationTreeEditor";
const { TextArea } = Input;
const { Option } = Select;
@@ -35,6 +35,7 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [labelConfig, setLabelConfig] = useState("");
useEffect(() => {
if (visible && template && mode === "edit") {
@@ -45,24 +46,26 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
labelingType: template.labelingType,
style: template.style,
category: template.category,
labels: template.configuration.labels,
objects: template.configuration.objects,
});
setLabelConfig(template.labelConfig || "");
} else if (visible && mode === "create") {
form.resetFields();
// Set default values
form.setFieldsValue({
style: "horizontal",
category: "custom",
labels: [],
objects: [{ name: "image", type: "Image", value: "$image" }],
});
setLabelConfig("");
}
}, [visible, template, mode, form]);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (!labelConfig.trim()) {
message.error("请配置标注模板");
return;
}
setLoading(true);
console.log("Form values:", values);
@@ -74,10 +77,7 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
labelingType: values.labelingType,
style: values.style,
category: values.category,
configuration: {
labels: values.labels,
objects: values.objects,
},
labelConfig: labelConfig.trim(),
};
console.log("Request data:", requestData);
@@ -190,7 +190,13 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
</Form.Item>
</Space>
<TemplateConfigurationForm form={form} />
<div className="bg-gray-50 p-4 rounded-md border border-gray-200">
<TemplateConfigurationTreeEditor
value={labelConfig}
onChange={setLabelConfig}
height={420}
/>
</div>
</Form>
</Modal>
);