feat(DataAnnotation): 新增模板配置表单组件

- 实现了数据对象配置区域,支持添加、删除数据对象字段
- 添加了标签控件配置区域,支持多种控件类型的动态配置
- 集成了TagSelector组件用于对象类型和控件类型的选择
- 实现了表单验证规则,包括必填项和值格式校验
- 添加了动态选项渲染功能,根据控件类型显示相应配置项
- 实现了表单联动逻辑,支持对象选择和控件配置的关联
- 添加了用户友好的界面布局和交互提示功能
This commit is contained in:
2026-01-18 21:08:47 +08:00
parent 668432cc1b
commit 6e08255820
3 changed files with 486 additions and 353 deletions

View File

@@ -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;