You've already forked DataMate
feat(DataAnnotation): 新增模板配置表单组件
- 实现了数据对象配置区域,支持添加、删除数据对象字段 - 添加了标签控件配置区域,支持多种控件类型的动态配置 - 集成了TagSelector组件用于对象类型和控件类型的选择 - 实现了表单验证规则,包括必填项和值格式校验 - 添加了动态选项渲染功能,根据控件类型显示相应配置项 - 实现了表单联动逻辑,支持对象选择和控件配置的关联 - 添加了用户友好的界面布局和交互提示功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Card, Button, Input, Select, Divider, Form, message } from "antd";
|
import { Card, Button, Input, Select, Divider, Form, message, Radio } from "antd";
|
||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
import {
|
import {
|
||||||
DatabaseOutlined,
|
DatabaseOutlined,
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { mockTemplates } from "@/mock/annotation";
|
import { mockTemplates } from "@/mock/annotation";
|
||||||
import CustomTemplateDialog from "./components/CustomTemplateDialog";
|
import CustomTemplateDialog from "./components/CustomTemplateDialog";
|
||||||
|
import TemplateConfigurationForm from "../../components/TemplateConfigurationForm";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api";
|
import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api";
|
||||||
@@ -36,6 +37,7 @@ export default function AnnotationTaskCreate() {
|
|||||||
const [showCustomTemplateDialog, setShowCustomTemplateDialog] =
|
const [showCustomTemplateDialog, setShowCustomTemplateDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [selectedCategory, setSelectedCategory] = useState("Computer Vision");
|
const [selectedCategory, setSelectedCategory] = useState("Computer Vision");
|
||||||
|
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [datasetFilter, setDatasetFilter] = useState("all");
|
const [datasetFilter, setDatasetFilter] = useState("all");
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
||||||
@@ -87,21 +89,67 @@ export default function AnnotationTaskCreate() {
|
|||||||
setFormValues({ ...formValues, ...allValues });
|
setFormValues({ ...formValues, ...allValues });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfigModeChange = (e) => {
|
||||||
|
const mode = e.target.value;
|
||||||
|
setConfigMode(mode);
|
||||||
|
if (mode === "custom") {
|
||||||
|
// Initialize default values for custom configuration
|
||||||
|
form.setFieldsValue({
|
||||||
|
objects: [{ name: "image", type: "Image", value: "$image" }],
|
||||||
|
labels: [],
|
||||||
|
});
|
||||||
|
// Clear template selection
|
||||||
|
setSelectedTemplate(null);
|
||||||
|
setFormValues((prev) => ({ ...prev, templateId: "" }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
const dataset = datasets.find((ds) => ds.id === values.datasetId);
|
const dataset = datasets.find((ds) => ds.id === values.datasetId);
|
||||||
const template = mockTemplates.find(
|
|
||||||
|
let template;
|
||||||
|
if (configMode === "template") {
|
||||||
|
template = mockTemplates.find(
|
||||||
(tpl) => tpl.id === values.templateId
|
(tpl) => tpl.id === values.templateId
|
||||||
);
|
);
|
||||||
if (!dataset) {
|
|
||||||
message.error("请选择数据集");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
message.error("请选择标注模板");
|
message.error("请选择标注模板");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Custom configuration
|
||||||
|
const objects = values.objects;
|
||||||
|
const labels = values.labels;
|
||||||
|
if (!objects || objects.length === 0) {
|
||||||
|
message.error("请至少配置一个数据对象");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!labels || labels.length === 0) {
|
||||||
|
message.error("请至少配置一个标签控件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a temporary custom template object
|
||||||
|
template = {
|
||||||
|
id: `custom-${Date.now()}`,
|
||||||
|
name: "自定义配置",
|
||||||
|
description: "任务特定的自定义配置",
|
||||||
|
type: selectedDataset?.type === DatasetType.PRETRAIN_TEXT ? "text" : "image",
|
||||||
|
isCustom: true,
|
||||||
|
configuration: {
|
||||||
|
objects,
|
||||||
|
labels
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataset) {
|
||||||
|
message.error("请选择数据集");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const taskData = {
|
const taskData = {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
description: values.description,
|
description: values.description,
|
||||||
@@ -109,10 +157,12 @@ export default function AnnotationTaskCreate() {
|
|||||||
template,
|
template,
|
||||||
};
|
};
|
||||||
// onCreateTask(taskData); // 实际创建逻辑
|
// onCreateTask(taskData); // 实际创建逻辑
|
||||||
|
console.log("Submitting task data:", taskData);
|
||||||
message.success("标注任务创建成功");
|
message.success("标注任务创建成功");
|
||||||
navigate("/data/annotation");
|
navigate("/data/annotation");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 校验失败
|
// 校验失败
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,10 +236,18 @@ export default function AnnotationTaskCreate() {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* 模板选择 */}
|
{/* 模板配置 */}
|
||||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2 flex items-center gap-2">
|
<div className="flex items-center justify-between mt-6 mb-2">
|
||||||
模板选择
|
<h2 className="font-medium text-gray-900 text-lg flex items-center gap-2">
|
||||||
|
模板配置
|
||||||
</h2>
|
</h2>
|
||||||
|
<Radio.Group value={configMode} onChange={handleConfigModeChange} buttonStyle="solid">
|
||||||
|
<Radio.Button value="template">选择现有模板</Radio.Button>
|
||||||
|
<Radio.Button value="custom">自定义配置</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{configMode === "template" ? (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="templateId"
|
name="templateId"
|
||||||
rules={[{ required: true, message: "请选择标注模板" }]}
|
rules={[{ required: true, message: "请选择标注模板" }]}
|
||||||
@@ -324,6 +382,11 @@ export default function AnnotationTaskCreate() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<div className="border border-gray-200 rounded-lg p-6 bg-gray-50">
|
||||||
|
<TemplateConfigurationForm form={form} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 justify-end border-t border-gray-200 p-6">
|
<div className="flex gap-2 justify-end border-t border-gray-200 p-6">
|
||||||
|
|||||||
@@ -4,21 +4,16 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
Button,
|
|
||||||
Space,
|
Space,
|
||||||
message,
|
message,
|
||||||
Divider,
|
|
||||||
Card,
|
|
||||||
Checkbox,
|
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
|
||||||
import {
|
import {
|
||||||
createAnnotationTemplateUsingPost,
|
createAnnotationTemplateUsingPost,
|
||||||
updateAnnotationTemplateByIdUsingPut,
|
updateAnnotationTemplateByIdUsingPut,
|
||||||
} from "../annotation.api";
|
} from "../annotation.api";
|
||||||
import type { AnnotationTemplate } from "../annotation.model";
|
import type { AnnotationTemplate } from "../annotation.model";
|
||||||
import { DataTypeMap, ClassificationMap, AnnotationTypeMap } from "../annotation.const";
|
import { DataTypeMap, ClassificationMap, AnnotationTypeMap } from "../annotation.const";
|
||||||
import TagSelector from "./components/TagSelector";
|
import TemplateConfigurationForm from "../../components/TemplateConfigurationForm";
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@@ -113,10 +108,6 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const needsOptions = (type: string) => {
|
|
||||||
return ["Choices", "RectangleLabels", "PolygonLabels", "Labels"].includes(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={mode === "create" ? "创建模板" : "编辑模板"}
|
title={mode === "create" ? "创建模板" : "编辑模板"}
|
||||||
@@ -199,222 +190,7 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Divider>数据对象</Divider>
|
<TemplateConfigurationForm form={form} />
|
||||||
|
|
||||||
<Form.List name="objects">
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map((field) => (
|
|
||||||
<Card key={field.key} size="small" style={{ marginBottom: 8 }}>
|
|
||||||
<Space align="start" style={{ width: "100%" }}>
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="名称"
|
|
||||||
name={[field.name, "name"]}
|
|
||||||
rules={[{ required: true, message: "必填" }]}
|
|
||||||
style={{ marginBottom: 0, width: 150 }}
|
|
||||||
>
|
|
||||||
<Input placeholder="例如:image" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="类型"
|
|
||||||
name={[field.name, "type"]}
|
|
||||||
rules={[{ required: true, message: "必填" }]}
|
|
||||||
style={{ marginBottom: 0, width: 150 }}
|
|
||||||
>
|
|
||||||
<TagSelector type="object" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="值"
|
|
||||||
name={[field.name, "value"]}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: "必填" },
|
|
||||||
{ pattern: /^\$/, message: "必须以 $ 开头" },
|
|
||||||
]}
|
|
||||||
style={{ marginBottom: 0, width: 150 }}
|
|
||||||
>
|
|
||||||
<Input placeholder="$image" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{fields.length > 1 && (
|
|
||||||
<MinusCircleOutlined
|
|
||||||
style={{ marginTop: 30, color: "red" }}
|
|
||||||
onClick={() => remove(field.name)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
|
||||||
添加对象
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
|
|
||||||
<Divider>标签控件</Divider>
|
|
||||||
|
|
||||||
<Form.List name="labels">
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map((field) => (
|
|
||||||
<Card
|
|
||||||
key={field.key}
|
|
||||||
size="small"
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
title={
|
|
||||||
<Space>
|
|
||||||
<span>控件 {fields.indexOf(field) + 1}</span>
|
|
||||||
<Form.Item noStyle shouldUpdate>
|
|
||||||
{() => {
|
|
||||||
const controlType = form.getFieldValue(["labels", field.name, "type"]);
|
|
||||||
const fromName = form.getFieldValue(["labels", field.name, "fromName"]);
|
|
||||||
if (controlType || fromName) {
|
|
||||||
return (
|
|
||||||
<span style={{ fontSize: 12, fontWeight: 'normal', color: '#999' }}>
|
|
||||||
({fromName || '未命名'} - {controlType || '未设置类型'})
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
extra={
|
|
||||||
<MinusCircleOutlined
|
|
||||||
style={{ color: "red" }}
|
|
||||||
onClick={() => remove(field.name)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
|
||||||
{/* Row 1: 控件名称, 标注目标对象, 控件类型 */}
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '180px 220px 1fr auto', gap: 12, alignItems: 'flex-end' }}>
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="来源名称"
|
|
||||||
name={[field.name, "fromName"]}
|
|
||||||
rules={[{ required: true, message: "必填" }]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
tooltip="此控件的唯一标识符"
|
|
||||||
>
|
|
||||||
<Input placeholder="例如:choice" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="标注目标对象"
|
|
||||||
name={[field.name, "toName"]}
|
|
||||||
rules={[{ required: true, message: "必填" }]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
tooltip="选择此控件将标注哪个数据对象"
|
|
||||||
dependencies={['objects']}
|
|
||||||
>
|
|
||||||
<Select placeholder="选择数据对象">
|
|
||||||
{(form.getFieldValue("objects") || []).map((obj: any, idx: number) => (
|
|
||||||
<Option key={idx} value={obj?.name || ''}>
|
|
||||||
{obj?.name || `对象 ${idx + 1}`} ({obj?.type || '未知类型'})
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="控件类型"
|
|
||||||
name={[field.name, "type"]}
|
|
||||||
rules={[{ required: true, message: "必填" }]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
>
|
|
||||||
<TagSelector type="control" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label=" "
|
|
||||||
name={[field.name, "required"]}
|
|
||||||
valuePropName="checked"
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
>
|
|
||||||
<Checkbox>必填</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 2: 取值范围定义(添加选项) - Conditionally rendered based on type */}
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
shouldUpdate={(prevValues, currentValues) => {
|
|
||||||
const prevType = prevValues.labels?.[field.name]?.type;
|
|
||||||
const currType = currentValues.labels?.[field.name]?.type;
|
|
||||||
return prevType !== currType;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const controlType = getFieldValue(["labels", field.name, "type"]);
|
|
||||||
const fieldName = controlType === "Choices" ? "options" : "labels";
|
|
||||||
|
|
||||||
if (needsOptions(controlType)) {
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label={controlType === "Choices" ? "选项" : "标签"}
|
|
||||||
name={[field.name, fieldName]}
|
|
||||||
rules={[{ required: true, message: "至少需要一个选项" }]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
open={false}
|
|
||||||
placeholder={
|
|
||||||
controlType === "Choices"
|
|
||||||
? "输入选项内容,按回车添加。例如:是、否、不确定"
|
|
||||||
: "输入标签名称,按回车添加。例如:人物、车辆、建筑物"
|
|
||||||
}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* Row 3: 描述 */}
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
label="描述"
|
|
||||||
name={[field.name, "description"]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
tooltip="向标注人员显示的帮助信息"
|
|
||||||
>
|
|
||||||
<Input placeholder="为标注人员提供此控件的使用说明" maxLength={200} />
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() =>
|
|
||||||
add({
|
|
||||||
fromName: "",
|
|
||||||
toName: "",
|
|
||||||
type: "Choices",
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
添加标签控件
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Button,
|
||||||
|
Space,
|
||||||
|
Divider,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
} from "antd";
|
||||||
|
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
||||||
|
import TagSelector from "../Template/components/TagSelector";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
interface TemplateConfigurationFormProps {
|
||||||
|
form: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||||
|
form,
|
||||||
|
}) => {
|
||||||
|
const needsOptions = (type: string) => {
|
||||||
|
return ["Choices", "RectangleLabels", "PolygonLabels", "Labels"].includes(
|
||||||
|
type
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider orientation="left">数据对象</Divider>
|
||||||
|
<Form.List name="objects">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<Card key={field.key} size="small" style={{ marginBottom: 8 }}>
|
||||||
|
<Space align="start" style={{ width: "100%" }}>
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="名称"
|
||||||
|
name={[field.name, "name"]}
|
||||||
|
rules={[{ required: true, message: "必填" }]}
|
||||||
|
style={{ marginBottom: 0, width: 150 }}
|
||||||
|
>
|
||||||
|
<Input placeholder="例如:image" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="类型"
|
||||||
|
name={[field.name, "type"]}
|
||||||
|
rules={[{ required: true, message: "必填" }]}
|
||||||
|
style={{ marginBottom: 0, width: 150 }}
|
||||||
|
>
|
||||||
|
<TagSelector type="object" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="值"
|
||||||
|
name={[field.name, "value"]}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "必填" },
|
||||||
|
{ pattern: /^\$/, message: "必须以 $ 开头" },
|
||||||
|
]}
|
||||||
|
style={{ marginBottom: 0, width: 150 }}
|
||||||
|
>
|
||||||
|
<Input placeholder="$image" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{fields.length > 1 && (
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginTop: 30, color: "red" }}
|
||||||
|
onClick={() => remove(field.name)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => add()}
|
||||||
|
block
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
添加对象
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
|
||||||
|
<Divider orientation="left">标签控件</Divider>
|
||||||
|
<Form.List name="labels">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<Card
|
||||||
|
key={field.key}
|
||||||
|
size="small"
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<span>控件 {fields.indexOf(field) + 1}</span>
|
||||||
|
<Form.Item noStyle shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
const controlType = form.getFieldValue([
|
||||||
|
"labels",
|
||||||
|
field.name,
|
||||||
|
"type",
|
||||||
|
]);
|
||||||
|
const fromName = form.getFieldValue([
|
||||||
|
"labels",
|
||||||
|
field.name,
|
||||||
|
"fromName",
|
||||||
|
]);
|
||||||
|
if (controlType || fromName) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "normal",
|
||||||
|
color: "#999",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
({fromName || "未命名"} -{" "}
|
||||||
|
{controlType || "未设置类型"})
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
extra={
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ color: "red" }}
|
||||||
|
onClick={() => remove(field.name)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
size="middle"
|
||||||
|
>
|
||||||
|
{/* Row 1: 控件名称, 标注目标对象, 控件类型 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "180px 220px 1fr auto",
|
||||||
|
gap: 12,
|
||||||
|
alignItems: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="来源名称"
|
||||||
|
name={[field.name, "fromName"]}
|
||||||
|
rules={[{ required: true, message: "必填" }]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
tooltip="此控件的唯一标识符"
|
||||||
|
>
|
||||||
|
<Input placeholder="例如:choice" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="标注目标对象"
|
||||||
|
name={[field.name, "toName"]}
|
||||||
|
rules={[{ required: true, message: "必填" }]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
tooltip="选择此控件将标注哪个数据对象"
|
||||||
|
dependencies={["objects"]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择数据对象">
|
||||||
|
{(form.getFieldValue("objects") || []).map(
|
||||||
|
(obj: any, idx: number) => (
|
||||||
|
<Option key={idx} value={obj?.name || ""}>
|
||||||
|
{obj?.name || `对象 ${idx + 1}`} (
|
||||||
|
{obj?.type || "未知类型"})
|
||||||
|
</Option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="控件类型"
|
||||||
|
name={[field.name, "type"]}
|
||||||
|
rules={[{ required: true, message: "必填" }]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<TagSelector type="control" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label=" "
|
||||||
|
name={[field.name, "required"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<Checkbox>必填</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 2: 取值范围定义(添加选项) - Conditionally rendered based on type */}
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
shouldUpdate={(prevValues, currentValues) => {
|
||||||
|
const prevType = prevValues.labels?.[field.name]?.type;
|
||||||
|
const currType = currentValues.labels?.[field.name]?.type;
|
||||||
|
return prevType !== currType;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const controlType = getFieldValue([
|
||||||
|
"labels",
|
||||||
|
field.name,
|
||||||
|
"type",
|
||||||
|
]);
|
||||||
|
const fieldName =
|
||||||
|
controlType === "Choices" ? "options" : "labels";
|
||||||
|
|
||||||
|
if (needsOptions(controlType)) {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label={controlType === "Choices" ? "选项" : "标签"}
|
||||||
|
name={[field.name, fieldName]}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "至少需要一个选项" },
|
||||||
|
]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
open={false}
|
||||||
|
placeholder={
|
||||||
|
controlType === "Choices"
|
||||||
|
? "输入选项内容,按回车添加。例如:是、否、不确定"
|
||||||
|
: "输入标签名称,按回车添加。例如:人物、车辆、建筑物"
|
||||||
|
}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* Row 3: 描述 */}
|
||||||
|
<Form.Item
|
||||||
|
{...field}
|
||||||
|
label="描述"
|
||||||
|
name={[field.name, "description"]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
tooltip="向标注人员显示的帮助信息"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="为标注人员提供此控件的使用说明"
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() =>
|
||||||
|
add({
|
||||||
|
fromName: "",
|
||||||
|
toName: "",
|
||||||
|
type: "Choices",
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
block
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
添加标签控件
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TemplateConfigurationForm;
|
||||||
Reference in New Issue
Block a user