You've already forked DataMate
feat(template): 添加模板搜索功能和优化数据获取
- 添加 keyword 参数支持模板名称和描述模糊搜索 - 在 useFetchData hook 中添加 filterParamMapper 参数用于过滤参数映射 - 为模板列表页面实现内置标志过滤器映射功能 - 优化模板配置更新逻辑,改进数据验证和转换流程 - 完善模板服务中的条件查询,支持多字段模糊匹配 - 更新数据获取 hook 的依赖数组以正确处理轮询逻辑
This commit is contained in:
@@ -1,238 +1,252 @@
|
|||||||
// 首页数据获取
|
// 首页数据获取
|
||||||
// 支持轮询功能,使用示例:
|
// 支持轮询功能,使用示例:
|
||||||
// const { fetchData, startPolling, stopPolling, isPolling } = useFetchData(
|
// const { fetchData, startPolling, stopPolling, isPolling } = useFetchData(
|
||||||
// fetchFunction,
|
// fetchFunction,
|
||||||
// mapFunction,
|
// mapFunction,
|
||||||
// 5000, // 5秒轮询一次,默认30秒
|
// 5000, // 5秒轮询一次,默认30秒
|
||||||
// true, // 是否自动开始轮询,默认 true
|
// true, // 是否自动开始轮询,默认 true
|
||||||
// [fetchStatistics, fetchOtherData] // 额外的轮询函数数组
|
// [fetchStatistics, fetchOtherData] // 额外的轮询函数数组
|
||||||
// );
|
// );
|
||||||
//
|
//
|
||||||
// startPolling(); // 开始轮询
|
// startPolling(); // 开始轮询
|
||||||
// stopPolling(); // 停止轮询
|
// stopPolling(); // 停止轮询
|
||||||
// 手动调用 fetchData() 时,如果正在轮询,会重新开始轮询计时
|
// 手动调用 fetchData() 时,如果正在轮询,会重新开始轮询计时
|
||||||
// 轮询时会同时执行主要的 fetchFunction 和所有额外的轮询函数
|
// 轮询时会同时执行主要的 fetchFunction 和所有额外的轮询函数
|
||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
import { useState, useRef, useEffect, useCallback } from "react";
|
||||||
import { useDebouncedEffect } from "./useDebouncedEffect";
|
import { useDebouncedEffect } from "./useDebouncedEffect";
|
||||||
import Loading from "@/utils/loading";
|
import Loading from "@/utils/loading";
|
||||||
import { App } from "antd";
|
import { App } from "antd";
|
||||||
|
|
||||||
export default function useFetchData<T>(
|
type FetchParams = Record<string, unknown>;
|
||||||
fetchFunc: (params?: any) => Promise<any>,
|
type FetchResult<T> = {
|
||||||
mapDataFunc: (data: Partial<T>) => T = (data) => data as T,
|
data?: {
|
||||||
pollingInterval: number = 30000, // 默认30秒轮询一次
|
content?: Partial<T>[];
|
||||||
autoRefresh: boolean = false, // 是否自动开始轮询,默认 false
|
totalElements?: number;
|
||||||
additionalPollingFuncs: (() => Promise<any>)[] = [], // 额外的轮询函数
|
total?: number;
|
||||||
pageOffset: number = 1
|
};
|
||||||
) {
|
};
|
||||||
const { message } = App.useApp();
|
|
||||||
|
export default function useFetchData<T>(
|
||||||
// 轮询相关状态
|
fetchFunc: (params?: FetchParams) => Promise<FetchResult<T>>,
|
||||||
const [isPolling, setIsPolling] = useState(false);
|
mapDataFunc: (data: Partial<T>) => T = (data) => data as T,
|
||||||
const pollingTimerRef = useRef<NodeJS.Timeout | null>(null);
|
pollingInterval: number = 30000, // 默认30秒轮询一次
|
||||||
|
autoRefresh: boolean = false, // 是否自动开始轮询,默认 false
|
||||||
// 表格数据
|
additionalPollingFuncs: (() => Promise<unknown>)[] = [], // 额外的轮询函数
|
||||||
const [tableData, setTableData] = useState<T[]>([]);
|
pageOffset: number = 1,
|
||||||
// 设置加载状态
|
filterParamMapper?: (filters: Record<string, unknown>) => Record<string, unknown>
|
||||||
const [loading, setLoading] = useState(false);
|
) {
|
||||||
|
const { message } = App.useApp();
|
||||||
// 搜索参数
|
|
||||||
const [searchParams, setSearchParams] = useState({
|
// 轮询相关状态
|
||||||
keyword: "",
|
const [isPolling, setIsPolling] = useState(false);
|
||||||
filter: {
|
const pollingTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
type: [] as string[],
|
|
||||||
status: [] as string[],
|
// 表格数据
|
||||||
tags: [] as string[],
|
const [tableData, setTableData] = useState<T[]>([]);
|
||||||
// 通用分类筛选(如算子市场的分类 ID 列表)
|
// 设置加载状态
|
||||||
categories: [] as string[][],
|
const [loading, setLoading] = useState(false);
|
||||||
selectedStar: false,
|
|
||||||
},
|
// 搜索参数
|
||||||
current: 1,
|
const [searchParams, setSearchParams] = useState({
|
||||||
pageSize: 12,
|
keyword: "",
|
||||||
});
|
filter: {
|
||||||
|
type: [] as string[],
|
||||||
// 分页配置
|
status: [] as string[],
|
||||||
const [pagination, setPagination] = useState({
|
tags: [] as string[],
|
||||||
total: 0,
|
// 通用分类筛选(如算子市场的分类 ID 列表)
|
||||||
showSizeChanger: true,
|
categories: [] as string[][],
|
||||||
pageSizeOptions: ["12", "24", "48"],
|
selectedStar: false,
|
||||||
showTotal: (total: number) => `共 ${total} 条`,
|
},
|
||||||
onChange: (current: number, pageSize?: number) => {
|
current: 1,
|
||||||
setSearchParams((prev) => ({
|
pageSize: 12,
|
||||||
...prev,
|
});
|
||||||
current,
|
|
||||||
pageSize: pageSize || prev.pageSize,
|
// 分页配置
|
||||||
}));
|
const [pagination, setPagination] = useState({
|
||||||
},
|
total: 0,
|
||||||
});
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["12", "24", "48"],
|
||||||
const handleFiltersChange = (searchFilters: { [key: string]: string[] }) => {
|
showTotal: (total: number) => `共 ${total} 条`,
|
||||||
setSearchParams({
|
onChange: (current: number, pageSize?: number) => {
|
||||||
...searchParams,
|
setSearchParams((prev) => ({
|
||||||
current: 1,
|
...prev,
|
||||||
filter: { ...searchParams.filter, ...searchFilters },
|
current,
|
||||||
});
|
pageSize: pageSize || prev.pageSize,
|
||||||
};
|
}));
|
||||||
|
},
|
||||||
const handleKeywordChange = (keyword: string) => {
|
});
|
||||||
setSearchParams({
|
|
||||||
...searchParams,
|
const handleFiltersChange = (searchFilters: { [key: string]: string[] }) => {
|
||||||
current: 1,
|
setSearchParams({
|
||||||
keyword: keyword,
|
...searchParams,
|
||||||
});
|
current: 1,
|
||||||
};
|
filter: { ...searchParams.filter, ...searchFilters },
|
||||||
|
});
|
||||||
function getFirstOfArray(arr: string[]) {
|
};
|
||||||
if (!arr || arr.length === 0 || !Array.isArray(arr)) return undefined;
|
|
||||||
if (arr[0] === "all") return undefined;
|
const handleKeywordChange = (keyword: string) => {
|
||||||
return arr[0];
|
setSearchParams({
|
||||||
}
|
...searchParams,
|
||||||
|
current: 1,
|
||||||
// 清除轮询定时器
|
keyword: keyword,
|
||||||
const clearPollingTimer = useCallback(() => {
|
});
|
||||||
if (pollingTimerRef.current) {
|
};
|
||||||
clearTimeout(pollingTimerRef.current);
|
|
||||||
pollingTimerRef.current = null;
|
function getFirstOfArray(arr: string[]) {
|
||||||
}
|
if (!arr || arr.length === 0 || !Array.isArray(arr)) return undefined;
|
||||||
}, []);
|
if (arr[0] === "all") return undefined;
|
||||||
|
return arr[0];
|
||||||
const fetchData = useCallback(
|
}
|
||||||
async (extraParams = {}, skipPollingRestart = false) => {
|
|
||||||
const { keyword, filter, current, pageSize } = searchParams;
|
// 清除轮询定时器
|
||||||
if (!skipPollingRestart) {
|
const clearPollingTimer = useCallback(() => {
|
||||||
Loading.show();
|
if (pollingTimerRef.current) {
|
||||||
setLoading(true);
|
clearTimeout(pollingTimerRef.current);
|
||||||
}
|
pollingTimerRef.current = null;
|
||||||
|
}
|
||||||
// 如果正在轮询且不是轮询触发的调用,先停止当前轮询
|
}, []);
|
||||||
const wasPolling = isPolling && !skipPollingRestart;
|
|
||||||
if (wasPolling) {
|
const fetchData = useCallback(
|
||||||
clearPollingTimer();
|
async (extraParams = {}, skipPollingRestart = false) => {
|
||||||
}
|
const { keyword, filter, current, pageSize } = searchParams;
|
||||||
|
if (!skipPollingRestart) {
|
||||||
try {
|
Loading.show();
|
||||||
// 同时执行主要数据获取和额外的轮询函数
|
setLoading(true);
|
||||||
const promises = [
|
}
|
||||||
fetchFunc({
|
|
||||||
categories: filter.categories,
|
// 如果正在轮询且不是轮询触发的调用,先停止当前轮询
|
||||||
...extraParams,
|
const wasPolling = isPolling && !skipPollingRestart;
|
||||||
keyword,
|
if (wasPolling) {
|
||||||
isStar: filter.selectedStar ? true : undefined,
|
clearPollingTimer();
|
||||||
type: getFirstOfArray(filter?.type) || undefined,
|
}
|
||||||
status: getFirstOfArray(filter?.status) || undefined,
|
|
||||||
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
|
try {
|
||||||
page: current - pageOffset,
|
const mappedFilterParams = filterParamMapper ? filterParamMapper(filter) : {};
|
||||||
size: pageSize, // Use camelCase for HTTP query params
|
// 同时执行主要数据获取和额外的轮询函数
|
||||||
}),
|
const promises = [
|
||||||
...additionalPollingFuncs.map((func) => func()),
|
fetchFunc({
|
||||||
];
|
categories: filter.categories,
|
||||||
|
...extraParams,
|
||||||
const results = await Promise.all(promises);
|
keyword,
|
||||||
const { data } = results[0]; // 主要数据结果
|
isStar: filter.selectedStar ? true : undefined,
|
||||||
|
type: getFirstOfArray(filter?.type) || undefined,
|
||||||
setPagination((prev) => ({
|
status: getFirstOfArray(filter?.status) || undefined,
|
||||||
...prev,
|
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
|
||||||
total: data?.totalElements ?? data?.total ?? 0,
|
...mappedFilterParams,
|
||||||
}));
|
page: current - pageOffset,
|
||||||
let result = [];
|
size: pageSize, // Use camelCase for HTTP query params
|
||||||
if (mapDataFunc) {
|
}),
|
||||||
result = data?.content.map(mapDataFunc) ?? [];
|
...additionalPollingFuncs.map((func) => func()),
|
||||||
}
|
];
|
||||||
setTableData(result);
|
|
||||||
|
const results = await Promise.all(promises);
|
||||||
// 如果之前正在轮询且不是轮询触发的调用,重新开始轮询
|
const { data } = results[0]; // 主要数据结果
|
||||||
if (wasPolling) {
|
|
||||||
const poll = () => {
|
setPagination((prev) => ({
|
||||||
pollingTimerRef.current = setTimeout(() => {
|
...prev,
|
||||||
fetchData({}, true).then(() => {
|
total: data?.totalElements ?? data?.total ?? 0,
|
||||||
if (pollingTimerRef.current) {
|
}));
|
||||||
poll();
|
let result = [];
|
||||||
}
|
if (mapDataFunc) {
|
||||||
});
|
result = data?.content.map(mapDataFunc) ?? [];
|
||||||
}, pollingInterval);
|
}
|
||||||
};
|
setTableData(result);
|
||||||
poll();
|
|
||||||
}
|
// 如果之前正在轮询且不是轮询触发的调用,重新开始轮询
|
||||||
} catch (error) {
|
if (wasPolling) {
|
||||||
console.error(error);
|
const poll = () => {
|
||||||
message.error("数据获取失败,请稍后重试");
|
pollingTimerRef.current = setTimeout(() => {
|
||||||
} finally {
|
fetchData({}, true).then(() => {
|
||||||
Loading.hide();
|
if (pollingTimerRef.current) {
|
||||||
setLoading(false);
|
poll();
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
[
|
}, pollingInterval);
|
||||||
searchParams,
|
};
|
||||||
fetchFunc,
|
poll();
|
||||||
mapDataFunc,
|
}
|
||||||
isPolling,
|
} catch (error) {
|
||||||
clearPollingTimer,
|
console.error(error);
|
||||||
pollingInterval,
|
message.error("数据获取失败,请稍后重试");
|
||||||
message,
|
} finally {
|
||||||
additionalPollingFuncs,
|
Loading.hide();
|
||||||
]
|
setLoading(false);
|
||||||
);
|
}
|
||||||
|
},
|
||||||
// 开始轮询
|
[
|
||||||
const startPolling = useCallback(() => {
|
searchParams,
|
||||||
clearPollingTimer();
|
fetchFunc,
|
||||||
setIsPolling(true);
|
mapDataFunc,
|
||||||
|
isPolling,
|
||||||
const poll = () => {
|
clearPollingTimer,
|
||||||
pollingTimerRef.current = setTimeout(() => {
|
pageOffset,
|
||||||
fetchData({}, true).then(() => {
|
pollingInterval,
|
||||||
if (pollingTimerRef.current) {
|
message,
|
||||||
poll();
|
additionalPollingFuncs,
|
||||||
}
|
filterParamMapper,
|
||||||
});
|
]
|
||||||
}, pollingInterval);
|
);
|
||||||
};
|
|
||||||
|
// 开始轮询
|
||||||
poll();
|
const startPolling = useCallback(() => {
|
||||||
}, [pollingInterval, clearPollingTimer, fetchData]);
|
clearPollingTimer();
|
||||||
|
setIsPolling(true);
|
||||||
// 停止轮询
|
|
||||||
const stopPolling = useCallback(() => {
|
const poll = () => {
|
||||||
clearPollingTimer();
|
pollingTimerRef.current = setTimeout(() => {
|
||||||
setIsPolling(false);
|
fetchData({}, true).then(() => {
|
||||||
}, [clearPollingTimer]);
|
if (pollingTimerRef.current) {
|
||||||
|
poll();
|
||||||
// 搜索参数变化时,自动刷新数据
|
}
|
||||||
// keyword 变化时,防抖500ms后刷新
|
});
|
||||||
useDebouncedEffect(
|
}, pollingInterval);
|
||||||
() => {
|
};
|
||||||
fetchData();
|
|
||||||
},
|
poll();
|
||||||
[searchParams],
|
}, [pollingInterval, clearPollingTimer, fetchData]);
|
||||||
searchParams?.keyword ? 500 : 0
|
|
||||||
);
|
// 停止轮询
|
||||||
|
const stopPolling = useCallback(() => {
|
||||||
// 组件卸载时清理轮询
|
clearPollingTimer();
|
||||||
useEffect(() => {
|
setIsPolling(false);
|
||||||
if (autoRefresh) {
|
}, [clearPollingTimer]);
|
||||||
startPolling();
|
|
||||||
}
|
// 搜索参数变化时,自动刷新数据
|
||||||
return () => {
|
// keyword 变化时,防抖500ms后刷新
|
||||||
clearPollingTimer();
|
useDebouncedEffect(
|
||||||
};
|
() => {
|
||||||
}, [clearPollingTimer]);
|
fetchData();
|
||||||
|
},
|
||||||
return {
|
[searchParams],
|
||||||
loading,
|
searchParams?.keyword ? 500 : 0
|
||||||
tableData,
|
);
|
||||||
pagination: {
|
|
||||||
...pagination,
|
// 组件卸载时清理轮询
|
||||||
current: searchParams.current,
|
useEffect(() => {
|
||||||
pageSize: searchParams.pageSize,
|
if (autoRefresh) {
|
||||||
},
|
startPolling();
|
||||||
searchParams,
|
}
|
||||||
setSearchParams,
|
return () => {
|
||||||
setPagination,
|
clearPollingTimer();
|
||||||
handleFiltersChange,
|
};
|
||||||
handleKeywordChange,
|
}, [autoRefresh, startPolling, clearPollingTimer]);
|
||||||
fetchData,
|
|
||||||
isPolling,
|
return {
|
||||||
startPolling,
|
loading,
|
||||||
stopPolling,
|
tableData,
|
||||||
};
|
pagination: {
|
||||||
}
|
...pagination,
|
||||||
|
current: searchParams.current,
|
||||||
|
pageSize: searchParams.pageSize,
|
||||||
|
},
|
||||||
|
searchParams,
|
||||||
|
setSearchParams,
|
||||||
|
setPagination,
|
||||||
|
handleFiltersChange,
|
||||||
|
handleKeywordChange,
|
||||||
|
fetchData,
|
||||||
|
isPolling,
|
||||||
|
startPolling,
|
||||||
|
stopPolling,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,31 @@ const TemplateList: React.FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BUILT_IN_FLAG = {
|
||||||
|
TRUE: "true",
|
||||||
|
FALSE: "false",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const mapTemplateFilters = (filters: Record<string, string[]>) => {
|
||||||
|
const getFirstValue = (values?: string[]) =>
|
||||||
|
values && values.length > 0 ? values[0] : undefined;
|
||||||
|
|
||||||
|
const builtInRaw = getFirstValue(filters.builtIn);
|
||||||
|
const builtIn =
|
||||||
|
builtInRaw === BUILT_IN_FLAG.TRUE
|
||||||
|
? true
|
||||||
|
: builtInRaw === BUILT_IN_FLAG.FALSE
|
||||||
|
? false
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
category: getFirstValue(filters.category),
|
||||||
|
dataType: getFirstValue(filters.dataType),
|
||||||
|
labelingType: getFirstValue(filters.labelingType),
|
||||||
|
builtIn,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [isDetailVisible, setIsDetailVisible] = useState(false);
|
const [isDetailVisible, setIsDetailVisible] = useState(false);
|
||||||
@@ -71,7 +96,15 @@ const TemplateList: React.FC = () => {
|
|||||||
fetchData,
|
fetchData,
|
||||||
handleFiltersChange,
|
handleFiltersChange,
|
||||||
handleKeywordChange,
|
handleKeywordChange,
|
||||||
} = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined, 0);
|
} = useFetchData(
|
||||||
|
queryAnnotationTemplatesUsingGet,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
0,
|
||||||
|
mapTemplateFilters
|
||||||
|
);
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
setFormMode("create");
|
setFormMode("create");
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ async def get_template(
|
|||||||
async def list_template(
|
async def list_template(
|
||||||
page: int = Query(1, ge=1, description="页码"),
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
size: int = Query(10, ge=1, le=100, description="每页大小"),
|
size: int = Query(10, ge=1, le=100, description="每页大小"),
|
||||||
|
keyword: Optional[str] = Query(None, description="关键词"),
|
||||||
category: Optional[str] = Query(None, description="分类筛选"),
|
category: Optional[str] = Query(None, description="分类筛选"),
|
||||||
dataType: Optional[str] = Query(None, alias="dataType", description="数据类型筛选"),
|
dataType: Optional[str] = Query(None, alias="dataType", description="数据类型筛选"),
|
||||||
labelingType: Optional[str] = Query(None, alias="labelingType", description="标注类型筛选"),
|
labelingType: Optional[str] = Query(None, alias="labelingType", description="标注类型筛选"),
|
||||||
@@ -78,6 +79,7 @@ async def list_template(
|
|||||||
|
|
||||||
- **page**: 页码(从1开始)
|
- **page**: 页码(从1开始)
|
||||||
- **size**: 每页大小(1-100)
|
- **size**: 每页大小(1-100)
|
||||||
|
- **keyword**: 关键词(匹配名称/描述)
|
||||||
- **category**: 模板分类筛选
|
- **category**: 模板分类筛选
|
||||||
- **dataType**: 数据类型筛选
|
- **dataType**: 数据类型筛选
|
||||||
- **labelingType**: 标注类型筛选
|
- **labelingType**: 标注类型筛选
|
||||||
@@ -90,7 +92,8 @@ async def list_template(
|
|||||||
category=category,
|
category=category,
|
||||||
data_type=dataType,
|
data_type=dataType,
|
||||||
labeling_type=labelingType,
|
labeling_type=labelingType,
|
||||||
built_in=builtIn
|
built_in=builtIn,
|
||||||
|
keyword=keyword
|
||||||
)
|
)
|
||||||
return StandardResponse(code=200, message="success", data=templates)
|
return StandardResponse(code=200, message="success", data=templates)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Annotation Template Service
|
|||||||
"""
|
"""
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import select, func
|
from sqlalchemy import select, func, or_
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
@@ -185,7 +185,8 @@ class AnnotationTemplateService:
|
|||||||
category: Optional[str] = None,
|
category: Optional[str] = None,
|
||||||
data_type: Optional[str] = None,
|
data_type: Optional[str] = None,
|
||||||
labeling_type: Optional[str] = None,
|
labeling_type: Optional[str] = None,
|
||||||
built_in: Optional[bool] = None
|
built_in: Optional[bool] = None,
|
||||||
|
keyword: Optional[str] = None
|
||||||
) -> AnnotationTemplateListResponse:
|
) -> AnnotationTemplateListResponse:
|
||||||
"""
|
"""
|
||||||
获取模板列表
|
获取模板列表
|
||||||
@@ -213,6 +214,14 @@ class AnnotationTemplateService:
|
|||||||
conditions.append(AnnotationTemplate.labeling_type == labeling_type) # type: ignore
|
conditions.append(AnnotationTemplate.labeling_type == labeling_type) # type: ignore
|
||||||
if built_in is not None:
|
if built_in is not None:
|
||||||
conditions.append(AnnotationTemplate.built_in == built_in) # type: ignore
|
conditions.append(AnnotationTemplate.built_in == built_in) # type: ignore
|
||||||
|
if keyword:
|
||||||
|
like_keyword = f"%{keyword}%"
|
||||||
|
conditions.append(
|
||||||
|
or_(
|
||||||
|
AnnotationTemplate.name.ilike(like_keyword), # type: ignore
|
||||||
|
AnnotationTemplate.description.ilike(like_keyword) # type: ignore
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 查询总数
|
# 查询总数
|
||||||
count_result = await db.execute(
|
count_result = await db.execute(
|
||||||
@@ -273,13 +282,14 @@ class AnnotationTemplateService:
|
|||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
if field == 'configuration' and value is not None:
|
if field == 'configuration' and value is not None:
|
||||||
# 验证配置JSON
|
# 验证配置JSON
|
||||||
config_dict = value.model_dump(mode='json', by_alias=False)
|
config = value if isinstance(value, TemplateConfiguration) else TemplateConfiguration.model_validate(value)
|
||||||
|
config_dict = config.model_dump(mode='json', by_alias=False)
|
||||||
valid, error = LabelStudioConfigValidator.validate_configuration_json(config_dict)
|
valid, error = LabelStudioConfigValidator.validate_configuration_json(config_dict)
|
||||||
if not valid:
|
if not valid:
|
||||||
raise HTTPException(status_code=400, detail=f"Invalid configuration: {error}")
|
raise HTTPException(status_code=400, detail=f"Invalid configuration: {error}")
|
||||||
|
|
||||||
# 重新生成Label Studio XML配置(用于验证)
|
# 重新生成Label Studio XML配置(用于验证)
|
||||||
label_config = self.generate_label_studio_config(value)
|
label_config = self.generate_label_studio_config(config)
|
||||||
|
|
||||||
# 验证生成的XML
|
# 验证生成的XML
|
||||||
valid, error = LabelStudioConfigValidator.validate_xml(label_config)
|
valid, error = LabelStudioConfigValidator.validate_xml(label_config)
|
||||||
|
|||||||
Reference in New Issue
Block a user