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