You've already forked DataMate
feat(annotation): 添加模板受限编辑模式
- 引入 restrictedMode 属性控制表单编辑权限 - 在数据对象区域显示锁定状态提示 - 禁用受限制字段的输入功能 - 隐藏受限制时的删除和添加按钮 - 在标签控件区域显示可编辑状态提示 - 更新XML编辑器为只读模式并显示相应提示 - 添加模板选择状态跟踪功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
|
||||
import { mapDataset } from "@/pages/DataManagement/dataset.const";
|
||||
import { Button, Form, Input, Modal, Select, message, Tabs, Radio, Typography } from "antd";
|
||||
import { Button, Form, Input, Modal, Select, message, Tabs, Radio } from "antd";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
@@ -33,6 +33,8 @@ export default function CreateAnnotationTask({
|
||||
const [previewTaskData, setPreviewTaskData] = useState<Record<string, any>>({});
|
||||
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
||||
const [templateEditTab, setTemplateEditTab] = useState<"visual" | "xml">("visual");
|
||||
// 是否已选择模板(用于启用受限编辑模式)
|
||||
const [hasSelectedTemplate, setHasSelectedTemplate] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -76,6 +78,7 @@ export default function CreateAnnotationTask({
|
||||
setPreviewTaskData({});
|
||||
setConfigMode("template");
|
||||
setTemplateEditTab("visual");
|
||||
setHasSelectedTemplate(false);
|
||||
}
|
||||
}, [open, manualForm]);
|
||||
|
||||
@@ -179,6 +182,15 @@ export default function CreateAnnotationTask({
|
||||
|
||||
// 当选择模板时,加载模板配置到表单
|
||||
const handleTemplateSelect = (value: string, option: any) => {
|
||||
// 处理清除选择的情况
|
||||
if (!value) {
|
||||
setHasSelectedTemplate(false);
|
||||
setCustomXml("");
|
||||
return;
|
||||
}
|
||||
|
||||
setHasSelectedTemplate(true);
|
||||
|
||||
if (option && option.config) {
|
||||
setCustomXml(option.config);
|
||||
}
|
||||
@@ -297,7 +309,7 @@ export default function CreateAnnotationTask({
|
||||
<Form.Item
|
||||
label="数据集"
|
||||
name="datasetId"
|
||||
rules={[{ required: true, message: "请选择数据集" }]}
|
||||
rules={[{ required: true, message: "请选择数据集" }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择数据集"
|
||||
@@ -440,23 +452,29 @@ export default function CreateAnnotationTask({
|
||||
setTemplateEditTab(key as "visual" | "xml");
|
||||
}}
|
||||
size="small"
|
||||
items={[
|
||||
items={[
|
||||
{
|
||||
key: "visual",
|
||||
label: "可视化配置",
|
||||
children: (
|
||||
<div style={{ maxHeight: '350px', overflowY: 'auto' }}>
|
||||
<TemplateConfigurationForm form={manualForm} />
|
||||
<TemplateConfigurationForm
|
||||
form={manualForm}
|
||||
restrictedMode={hasSelectedTemplate}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "xml",
|
||||
label: "XML编辑器(高级)",
|
||||
label: hasSelectedTemplate ? "XML配置(只读)" : "XML编辑器(高级)",
|
||||
children: (
|
||||
<div>
|
||||
<div className="mb-2 text-xs text-gray-500">
|
||||
直接编辑 Label Studio XML 配置。注意:在此修改后切换回可视化配置可能会丢失部分高级设置。
|
||||
{hasSelectedTemplate
|
||||
? "基于模板创建时,XML 配置为只读。如需完全自定义,请切换'自定义配置'模式。"
|
||||
: "直接编辑 Label Studio XML 配置。注意:在此修改后切换回可视化配置可能会丢失部分高级设置。"
|
||||
}
|
||||
</div>
|
||||
<TextArea
|
||||
rows={10}
|
||||
@@ -464,6 +482,7 @@ export default function CreateAnnotationTask({
|
||||
onChange={(e) => setCustomXml(e.target.value)}
|
||||
placeholder="<View>...</View>"
|
||||
style={{ fontFamily: 'monospace', fontSize: 12 }}
|
||||
disabled={hasSelectedTemplate}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
@@ -486,7 +505,7 @@ export default function CreateAnnotationTask({
|
||||
onCancel={() => setShowPreview(false)}
|
||||
title="标注界面预览"
|
||||
width={1000}
|
||||
footer={[
|
||||
footer={[
|
||||
<Button key="close" onClick={() => setShowPreview(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
@@ -506,4 +525,4 @@ export default function CreateAnnotationTask({
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,28 @@ import {
|
||||
Divider,
|
||||
Card,
|
||||
Checkbox,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
||||
import { PlusOutlined, MinusCircleOutlined, LockOutlined } from "@ant-design/icons";
|
||||
import TagSelector from "../Template/components/TagSelector";
|
||||
|
||||
const { Option } = Select;
|
||||
const { Text } = Typography;
|
||||
|
||||
interface TemplateConfigurationFormProps {
|
||||
form: any;
|
||||
/**
|
||||
* 受限编辑模式:选择现有模板时启用
|
||||
* - 数据对象部分完全只读
|
||||
* - 标签控件的结构(类型、来源名称、目标对象等)只读
|
||||
* - 只允许编辑标签/选项的值
|
||||
*/
|
||||
restrictedMode?: boolean;
|
||||
}
|
||||
|
||||
const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
form,
|
||||
restrictedMode = false,
|
||||
}) => {
|
||||
const needsOptions = (type: string) => {
|
||||
return ["Choices", "RectangleLabels", "PolygonLabels", "Labels"].includes(
|
||||
@@ -29,7 +39,14 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider orientation="left">数据对象</Divider>
|
||||
<Divider orientation="left">
|
||||
数据对象
|
||||
{restrictedMode && (
|
||||
<Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}>
|
||||
<LockOutlined /> 由模板定义,不可修改
|
||||
</Text>
|
||||
)}
|
||||
</Divider>
|
||||
<Form.List name="objects">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
@@ -43,7 +60,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
rules={[{ required: true, message: "必填" }]}
|
||||
style={{ marginBottom: 0, width: 150 }}
|
||||
>
|
||||
<Input placeholder="例如:image" />
|
||||
<Input placeholder="例如:image" disabled={restrictedMode} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -53,7 +70,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
rules={[{ required: true, message: "必填" }]}
|
||||
style={{ marginBottom: 0, width: 150 }}
|
||||
>
|
||||
<TagSelector type="object" />
|
||||
<TagSelector type="object" disabled={restrictedMode} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -66,10 +83,10 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
]}
|
||||
style={{ marginBottom: 0, width: 150 }}
|
||||
>
|
||||
<Input placeholder="$image" />
|
||||
<Input placeholder="$image" disabled={restrictedMode} />
|
||||
</Form.Item>
|
||||
|
||||
{fields.length > 1 && (
|
||||
{!restrictedMode && fields.length > 1 && (
|
||||
<MinusCircleOutlined
|
||||
style={{ marginTop: 30, color: "red" }}
|
||||
onClick={() => remove(field.name)}
|
||||
@@ -78,19 +95,28 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
添加对象
|
||||
</Button>
|
||||
{!restrictedMode && (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
添加对象
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
<Divider orientation="left">标签控件</Divider>
|
||||
<Divider orientation="left">
|
||||
标签控件
|
||||
{restrictedMode && (
|
||||
<Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}>
|
||||
仅可修改标签/选项值
|
||||
</Text>
|
||||
)}
|
||||
</Divider>
|
||||
<Form.List name="labels">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
@@ -134,10 +160,12 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<MinusCircleOutlined
|
||||
style={{ color: "red" }}
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
!restrictedMode && (
|
||||
<MinusCircleOutlined
|
||||
style={{ color: "red" }}
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Space
|
||||
@@ -162,7 +190,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
style={{ marginBottom: 0 }}
|
||||
tooltip="此控件的唯一标识符"
|
||||
>
|
||||
<Input placeholder="例如:choice" />
|
||||
<Input placeholder="例如:choice" disabled={restrictedMode} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -174,7 +202,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
tooltip="选择此控件将标注哪个数据对象"
|
||||
dependencies={["objects"]}
|
||||
>
|
||||
<Select placeholder="选择数据对象">
|
||||
<Select placeholder="选择数据对象" disabled={restrictedMode}>
|
||||
{(form.getFieldValue("objects") || []).map(
|
||||
(obj: any, idx: number) => (
|
||||
<Option key={idx} value={obj?.name || ""}>
|
||||
@@ -193,7 +221,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
rules={[{ required: true, message: "必填" }]}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<TagSelector type="control" />
|
||||
<TagSelector type="control" disabled={restrictedMode} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -203,7 +231,7 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
valuePropName="checked"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Checkbox>必填</Checkbox>
|
||||
<Checkbox disabled={restrictedMode}>必填</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
@@ -229,7 +257,16 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
return (
|
||||
<Form.Item
|
||||
{...field}
|
||||
label={controlType === "Choices" ? "选项" : "标签"}
|
||||
label={
|
||||
<>
|
||||
{controlType === "Choices" ? "选项" : "标签"}
|
||||
{restrictedMode && (
|
||||
<Text type="success" style={{ fontSize: 11, marginLeft: 6 }}>
|
||||
可编辑
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
name={[field.name, fieldName]}
|
||||
rules={[
|
||||
{ required: true, message: "至少需要一个选项" },
|
||||
@@ -264,26 +301,29 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
<Input
|
||||
placeholder="为标注人员提供此控件的使用说明"
|
||||
maxLength={200}
|
||||
disabled={restrictedMode}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() =>
|
||||
add({
|
||||
fromName: "",
|
||||
toName: "",
|
||||
type: "Choices",
|
||||
required: false,
|
||||
})
|
||||
}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
添加标签控件
|
||||
</Button>
|
||||
{!restrictedMode && (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() =>
|
||||
add({
|
||||
fromName: "",
|
||||
toName: "",
|
||||
type: "Choices",
|
||||
required: false,
|
||||
})
|
||||
}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
添加标签控件
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
Reference in New Issue
Block a user