From 04d530d692e91c917e9b2626333619aa8cc374e2 Mon Sep 17 00:00:00 2001 From: shay7sev Date: Mon, 20 Apr 2026 09:36:34 +0800 Subject: [PATCH 1/8] feat(shortcut): area --- auxiliary_area_threshold_plan.md | 52 +++ components/label/LabelNossr.tsx | 9 + .../label/components/PaperContainer.tsx | 438 +++++++++++++++--- components/label/components/TopTools.tsx | 122 +++-- components/label/usePaperStore.ts | 13 +- components/label/useTopToolsStore.ts | 9 + components/label/utils/geometry/areaFilter.ts | 69 +++ size_threshold_presets_plan_a.md | 197 ++++++++ 8 files changed, 815 insertions(+), 94 deletions(-) create mode 100644 auxiliary_area_threshold_plan.md create mode 100644 components/label/utils/geometry/areaFilter.ts create mode 100644 size_threshold_presets_plan_a.md diff --git a/auxiliary_area_threshold_plan.md b/auxiliary_area_threshold_plan.md new file mode 100644 index 0000000..29d3371 --- /dev/null +++ b/auxiliary_area_threshold_plan.md @@ -0,0 +1,52 @@ +# 辅助标注尺寸阈值过滤实施记录 + +## 背景 +在 `F1 -> 点选/框选 -> 请求 getAuxiliaryAnnotation -> 生成辅助轮廓 -> F1 确认生成对象` 的流程里,接口可能返回大量很小的区域或小镂空,影响最终对象质量与后续编辑效率。 + +## 目标 +- 在工具栏提供尺寸阈值输入框。 +- 提供按钮与快捷键,对小于阈值的区域/镂空进行过滤。 +- 该能力既能用于 `F1` 生成后的辅助预览,也能用于已确认生成并被选中的 `polygon` 对象。 +- 不影响正常标注流程。 + +## 链路分析 +1. `LabelNossr.tsx` + - `F1` 进入 `support` 模式。 + - 再次 `F1` 时调用 `PaperContainer.saveSupportAnnotationData()` 完成确认。 +2. `usePaperStore.ts` + - `supportTool` 收集点/框提示,调用 `PaperContainer.renderSupportAnnotation()`。 +3. `PaperContainer.tsx` + - `renderSupportAnnotation()` 请求 `getAuxiliaryAnnotation()`。 + - 接口返回 `contours/hollows` 后,在画布上生成 `support` 预览。 + - `saveSupportAnnotationData()` 将 `support` 预览转成正式标注对象。 + +## 本次实现 +### 已完成 +- 在 `components/label/useTopToolsStore.ts` 增加 `auxiliarySizeThreshold` 状态。 +- 新增 `components/label/utils/geometry/areaFilter.ts`,用于按尺寸过滤 multipolygon 外轮廓与洞。 +- 在 `components/label/components/TopTools.tsx` 增加: + - 尺寸阈值输入框 + - `过滤` 按钮 +- 在 `components/label/LabelNossr.tsx` 增加 `F2` 快捷键,触发尺寸过滤。 +- 在 `components/label/components/PaperContainer.tsx` 增加: + - 辅助预览 geometry 重绘能力 + - 对 `support` 预览执行尺寸过滤 + - 对已选中 `polygon` 对象执行尺寸过滤 + - 若过滤后对象为空,则直接删除对象 +- 快捷键说明中补充 `F2` 文案。 + +### 当前行为 +- 若当前存在 `support` 预览,`F2` / `过滤` 优先作用于辅助预览。 +- 若当前没有 `support` 预览,则对当前选中的一个或多个 `polygon` 对象执行批量过滤。 +- 输入值含义为“尺寸阈值”,实际面积阈值为“尺寸阈值²”。 +- 小外轮廓会被删除;小镂空会被填平。 +- 阈值需大于 `0` 才会生效。 + +## 验证 +- 已执行:`pnpm exec tsc --noEmit` +- 结果:通过 + +## 后续可选项 +- 支持一次过滤多个已选中 polygon 对象。 +- 在工具栏中增加更明确的阈值单位说明。 +- 如果需要,可再补一轮手动回归用例清单。 diff --git a/components/label/LabelNossr.tsx b/components/label/LabelNossr.tsx index 28721b9..29f6265 100644 --- a/components/label/LabelNossr.tsx +++ b/components/label/LabelNossr.tsx @@ -2192,6 +2192,12 @@ const LabelPage = ({ } } + if (e.key === "F2") { + e.preventDefault() + paperContainerRef.current?.filterBySizeThreshold?.() + return + } + // 追踪 if (e.key === "i") { e.preventDefault() @@ -2585,6 +2591,9 @@ const LabelPage = ({ updateOldWorkLoad={updateOldWorkLoad} isLabelTask={isLabelTask} onOperationChange={handleSelectOperation} + onFilterBySizeThreshold={() => { + paperContainerRef.current?.filterBySizeThreshold?.() + }} renderPolygons={ paperContainerRef.current ? paperContainerRef.current.renderPolygons diff --git a/components/label/components/PaperContainer.tsx b/components/label/components/PaperContainer.tsx index c6856b3..cfaf75e 100644 --- a/components/label/components/PaperContainer.tsx +++ b/components/label/components/PaperContainer.tsx @@ -46,7 +46,11 @@ import { usePermissionStore } from "../store/auth" import { useBottomToolsStore } from "../useBottomToolsStore" import { useKeyboardStore } from "../useKeyBoardStore" import { useOtherToolsStore } from "../useOtherToolsStore" -import { clearSupportAnnotationItem, usePaperStore } from "../usePaperStore" +import { + clearSupportAnnotationItem, + syncDeletedPathGroupState, + usePaperStore, +} from "../usePaperStore" import { usePaperSupportStore } from "../usePaperSupportStore" import { renderGroupPath } from "../useRenderGroupPath" import useRenderTag from "../useRenderTag" @@ -55,6 +59,12 @@ import { useTopToolsStore } from "../useTopToolsStore" import { buildSubAttributeFormValues, checkCommentsIsSame } from "../util" import { safeClone } from "../utils/clone" import { labelTypeMap } from "../utils/constants" +import { + contoursToMultiPolygon, + multiPolygonToStoredContours, + paperShapeToMultiPolygon, +} from "../utils/geometry/booleanAdapter" +import { filterMultiPolygonByAreaThreshold } from "../utils/geometry/areaFilter" import { adjustPoints } from "../utils/paperjs" import AssistShapeComponent from "./AssistShapeComponent" import CrosshairComponent from "./CrosshairComponent" @@ -158,6 +168,7 @@ const SHORTCUT_GUIDE_SECTIONS = [ title: "专项工具", items: [ ["F1", "进入辅助标注 / 在辅助模式下确认"], + ["F2", "按尺寸阈值过滤小区域 / 小镂空"], ["I", "执行追踪"], ["P", "QA 文本场景检查错词"], ], @@ -1042,6 +1053,347 @@ const PaperContainer = ( } }, [clearPendingFrameSwitch, loadingData]) + const getSupportPreviewColors = useCallback(() => { + const operationSchema = + projectDetail?.label_schema_list?.find( + (item) => + item.category_id === +useTopToolsStore.getState().activeOperation + ) || null + + return { + strokeColor: operationSchema + ? `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})` + : "rgb(0,0,0)", + fillColor: operationSchema + ? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)` + : "rgba(0,0,0,0.3)", + blankColor: operationSchema + ? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)` + : "rgba(0,0,0,0.01)", + } + }, [projectDetail?.label_schema_list]) + + const clearSupportPreviewShapeItems = useCallback(() => { + usePaperStore + .getState() + .group?.getItems({ + match: (item: paper.Item) => + item.data?.id === "support" && + ["polygon", "parent"].includes(item.data?.type || ""), + }) + .forEach((item) => { + item.remove() + }) + }, []) + + const renderSupportPreviewGeometry = useCallback( + (geometry: ReturnType) => { + const paperGroup = usePaperStore.getState().group + if (!paperGroup) return + + clearSupportPreviewShapeItems() + + const { points, hollowPoints } = multiPolygonToStoredContours(geometry) + if (!points.length) return + + const { strokeColor, fillColor, blankColor } = getSupportPreviewColors() + const outerPaths = points + .map((contour) => { + if (contour.length <= 3) return null + const path = new paper.Path({ + segments: contour.slice(0, -1), + fillColor: blankColor, + strokeColor, + closed: true, + data: { + id: "support", + type: "polygon", + isHollowPolygon: false, + fillColor, + strokeColor, + blankColor, + }, + parent: paperGroup, + strokeScaling: false, + }) + path.fillColor = new paper.Color(fillColor) + return path + }) + .filter((path): path is paper.Path => !!path) + const innerPaths = hollowPoints + .map((contour) => { + if (contour.length <= 3) return null + return new paper.Path({ + segments: contour.slice(0, -1), + fillColor: blankColor, + strokeColor, + closed: true, + data: { + id: "support", + type: "polygon", + isHollowPolygon: true, + fillColor, + strokeColor, + blankColor, + }, + parent: paperGroup, + strokeScaling: false, + }) + }) + .filter((path): path is paper.Path => !!path) + + const compoundPath = new paper.CompoundPath({ + children: [...outerPaths, ...innerPaths], + data: { + id: "support", + type: "parent", + }, + strokeColor, + strokeWidth: 2, + fillColor, + strokeScaling: false, + }) + compoundPath.parent = paperGroup + }, + [clearSupportPreviewShapeItems, getSupportPreviewColors] + ) + + function rerenderActiveImageObjects() { + const group = usePaperStore.getState().group + if (!group) return + + group + .getItems({}) + .filter((item) => item.data.id) + .forEach((item) => { + item.remove() + }) + + const imageData = useLabelStore.getState().label.get(activeImage) + if (!imageData) return + + for (const key of imageData.keys()) { + renderPolygons(key, imageData) + } + } + + const getMinAreaBySizeThreshold = useCallback( + (sizeThreshold: number) => { + const normalizedSizeThreshold = Math.max( + 0, + Number(sizeThreshold) || 0 + ) + const currentScale = usePaperStore.getState().rasterScale[activeImage] ?? 1 + return ( + normalizedSizeThreshold * + normalizedSizeThreshold * + currentScale * + currentScale + ) + }, + [activeImage] + ) + + function filterSupportPreviewBySizeThreshold(sizeThreshold: number) { + const group = usePaperStore.getState().group + if (!group) return false + + const compoundPath = group.getItems({ + data: { id: "support", type: "parent" }, + })?.[0] as paper.CompoundPath | undefined + if (!compoundPath) return false + + const { geometry, removedOuterCount, removedHoleCount } = + filterMultiPolygonByAreaThreshold({ + geometry: paperShapeToMultiPolygon(compoundPath), + minArea: getMinAreaBySizeThreshold(sizeThreshold), + }) + + if (!removedOuterCount && !removedHoleCount) { + notifications.show({ + color: "yellow", + message: "辅助结果中未发现小于尺寸阈值的区域或镂空", + }) + return true + } + + renderSupportPreviewGeometry(geometry) + notifications.show({ + color: "green", + message: `已按尺寸阈值过滤 ${removedOuterCount} 个小区域、${removedHoleCount} 个小镂空`, + }) + return true + } + + function filterSelectedPolygonBySizeThreshold(sizeThreshold: number) { + const selectedIds = + useObjectStore.getState().selectedPath[activeImage] || [] + + if (!selectedIds.length) { + notifications.show({ + color: "yellow", + message: "请先选中一个或多个 polygon 对象", + }) + return false + } + + const minArea = getMinAreaBySizeThreshold(sizeThreshold) + const nextLabel = safeClone(useLabelStore.getState().label) + let removedOuterCount = 0 + let removedHoleCount = 0 + let updatedObjectCount = 0 + let removedObjectCount = 0 + let skippedNonPolygonCount = 0 + let skippedMissingCount = 0 + const removedPathIds: number[] = [] + const removedParentGroupIds: Array = [] + + selectedIds.forEach((selectedId) => { + const selectedItems = usePaperStore.getState().getItemsById(selectedId) + const selectedShape = + selectedItems.find((item) => item instanceof paper.CompoundPath) || + selectedItems.find((item) => item.data?.type === "polygon") || + null + + if (!selectedShape || selectedShape.data?.type !== "polygon") { + skippedNonPolygonCount += 1 + return + } + + const operationId = String(selectedShape.data?.operationId || "") + if (!operationId) { + skippedMissingCount += 1 + return + } + + const operationData = nextLabel.get(activeImage)?.get(operationId) + const pathIndex = + operationData?.findIndex((item) => item[0] === selectedId) ?? -1 + const oldData = pathIndex >= 0 ? operationData?.[pathIndex] : null + + if (!operationData || !oldData) { + skippedMissingCount += 1 + return + } + + const { geometry, removedOuterCount: currentRemovedOuterCount, removedHoleCount: currentRemovedHoleCount } = + filterMultiPolygonByAreaThreshold({ + geometry: contoursToMultiPolygon({ + outerContours: oldData[1], + holeContours: oldData[3], + }), + minArea, + }) + + if (!currentRemovedOuterCount && !currentRemovedHoleCount) { + return + } + + removedOuterCount += currentRemovedOuterCount + removedHoleCount += currentRemovedHoleCount + + if (!geometry.length) { + nextLabel + .get(activeImage) + ?.set( + operationId, + operationData.filter((item) => item[0] !== selectedId) + ) + removedObjectCount += 1 + removedPathIds.push(selectedId) + removedParentGroupIds.push(oldData[2]?.parentGroupId) + return + } + + const { points, hollowPoints } = multiPolygonToStoredContours(geometry) + const currentDetail = oldData[2] + const nextDetail = currentDetail + ? { + ...currentDetail, + first_modified_timestamp: + currentDetail.first_modified_timestamp || dayjs().unix(), + first_modified_uid: + currentDetail.first_modified_uid || + usePermissionStore.getState().user_id, + last_modified_timestamp: dayjs().unix(), + last_modified_uid: usePermissionStore.getState().user_id, + } + : { + ...initialDetail, + create_timestamp: dayjs().unix(), + } + + operationData[pathIndex] = [selectedId, points, nextDetail, hollowPoints] + updatedObjectCount += 1 + }) + + if (!updatedObjectCount && !removedObjectCount) { + notifications.show({ + color: skippedNonPolygonCount ? "yellow" : "yellow", + message: + skippedNonPolygonCount === selectedIds.length + ? "当前选中对象仅 polygon 支持尺寸过滤" + : "当前选中对象中未发现小于尺寸阈值的区域或镂空", + }) + return true + } + + if (removedPathIds.length) { + syncDeletedPathGroupState({ + imageId: activeImage, + removedPathIds, + removedParentGroupIds, + labelData: nextLabel, + }) + removedPathIds.forEach((pathId) => { + useObjectStore.getState().updateSelectedPath(activeImage, "DELETE", pathId) + }) + } + + useLabelStore.getState().setLabel(nextLabel) + useLabelStore.getState().pushStateStack(nextLabel) + rerenderActiveImageObjects() + + const summaryParts = [ + `已按尺寸阈值过滤 ${updatedObjectCount + removedObjectCount} 个 polygon`, + `删除 ${removedOuterCount} 个小区域`, + `删除 ${removedHoleCount} 个小镂空`, + ] + if (removedObjectCount) { + summaryParts.push(`移除 ${removedObjectCount} 个空对象`) + } + if (skippedNonPolygonCount) { + summaryParts.push(`跳过 ${skippedNonPolygonCount} 个非 polygon 对象`) + } + if (skippedMissingCount) { + summaryParts.push(`忽略 ${skippedMissingCount} 个异常对象`) + } + + notifications.show({ + color: "green", + message: summaryParts.join(","), + }) + return true + } + + function filterBySizeThreshold() { + const sizeThreshold = Number(useTopToolsStore.getState().auxiliarySizeThreshold) + + if (!Number.isFinite(sizeThreshold) || sizeThreshold <= 0) { + notifications.show({ + color: "yellow", + message: "请先设置大于 0 的尺寸阈值", + }) + return + } + + if (filterSupportPreviewBySizeThreshold(sizeThreshold)) { + return + } + + filterSelectedPolygonBySizeThreshold(sizeThreshold) + } + const renderSupportAnnotation = useCallback( async ( points: Array<[number, number]>, @@ -1057,20 +1409,8 @@ const PaperContainer = ( loading: true, autoClose: false, }) - const operationSchema = - projectDetail?.label_schema_list?.find( - (item) => - item.category_id === +useTopToolsStore.getState().activeOperation - ) || null - const strokeColor = operationSchema - ? `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})` - : "rgb(0,0,0)" - const fillColor = operationSchema - ? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)` - : "rgba(0,0,0,0.3)" - const blankColor = operationSchema - ? `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)` - : "rgba(0,0,0,0.01)" + const { strokeColor, fillColor, blankColor } = + getSupportPreviewColors() const currentScale = usePaperStore.getState().rasterScale[activeImage] ?? 1 @@ -1143,13 +1483,11 @@ const PaperContainer = ( if (!promptPoints.length && !normalizedBoxes.length) { throw new Error("缺少有效提示,无法调用模型接口") } - // const picSize = usePaperStore.getState().rasterSize[activeImage] try { const data = await getAuxiliaryAnnotation({ project_id: projectId, image_name: activeImage, - // project_id: "image_test_bk", prompt: { ...(promptPoints.length ? { foreground_points: promptPoints } @@ -1162,50 +1500,29 @@ const PaperContainer = ( contour_approximation: 0, }) const { contours = [], hollows = [] } = data || {} - const outerPaths: any[] = [] - const innerPaths: any[] = [] - contours.forEach((contour: any, index: number) => { - const normalizedContour = normalizeContour(contour) - if (normalizedContour.length > 3) { - const path = new paper.Path({ - segments: normalizedContour.map(([x, y]) => [ - x * currentScale, - y * currentScale, - ]), - fillColor: blankColor, - strokeColor: strokeColor, - closed: true, - data: { - id: "support", - type: "polygon", - isHollowPolygon: false, - fillColor, - strokeColor, - blankColor, - }, - parent: usePaperStore.getState().group, - strokeScaling: false, + const geometry = contoursToMultiPolygon({ + outerContours: contours + .map((contour: any, index: number) => { + if (hollows[index]) return null + return normalizeContour(contour).map( + ([x, y]) => [x * currentScale, y * currentScale] as [number, number] + ) }) - if (hollows[index]) { - path.data.isHollowPolygon = true - innerPaths.push(path) - } else { - path.fillColor = new paper.Color(fillColor) - outerPaths.push(path) - } - } + .filter( + (contour): contour is Array<[number, number]> => !!contour + ), + holeContours: contours + .map((contour: any, index: number) => { + if (!hollows[index]) return null + return normalizeContour(contour).map( + ([x, y]) => [x * currentScale, y * currentScale] as [number, number] + ) + }) + .filter( + (contour): contour is Array<[number, number]> => !!contour + ), }) - const compoundPath = new paper.CompoundPath({ - children: [...outerPaths, ...innerPaths], - data: { - id: "support", - type: "parent", - }, - strokeColor: strokeColor, - strokeWidth: 2, - fillColor: fillColor, - }) - compoundPath.parent = usePaperStore.getState().group! + renderSupportPreviewGeometry(geometry) notifications.show({ id: "sam2", message: "模型生成成功", @@ -1230,7 +1547,7 @@ const PaperContainer = ( } } }, - [activeImage, projectDetail] + [activeImage, getSupportPreviewColors, projectDetail, renderSupportPreviewGeometry] ) // const renderTrackingAnnotation = useCallback(async () => { @@ -4155,6 +4472,7 @@ const PaperContainer = ( renderTrackingAnnotation, renderSupportAnnotation, saveSupportAnnotationData, + filterBySizeThreshold, copyAnnotationDataToMultiFrame, })) diff --git a/components/label/components/TopTools.tsx b/components/label/components/TopTools.tsx index 7d7770d..c4247c8 100644 --- a/components/label/components/TopTools.tsx +++ b/components/label/components/TopTools.tsx @@ -120,6 +120,7 @@ interface TopToolsComponentProps { updateOldWorkLoad: (data: WorkLoad) => void isLabelTask: boolean onOperationChange: (operationId: string) => void + onFilterBySizeThreshold?: () => void renderPolygons: ( operationId: string, imageData: Map | undefined @@ -164,6 +165,7 @@ const TopTools = ( updateOldWorkLoad, isLabelTask, onOperationChange, + onFilterBySizeThreshold, renderPolygons, } = props const { backUrl, basePath } = useBackUrlStore() @@ -230,6 +232,8 @@ const TopTools = ( setAssistToolEnabled, assistToolSize, setAssistToolSize, + auxiliarySizeThreshold, + setAuxiliarySizeThreshold, nodeSize, setNodeSize, needBackup, @@ -238,6 +242,9 @@ const TopTools = ( setMagnetFlag, } = useTopToolsStore() const [nodeSizeInput, setNodeSizeInput] = useState(nodeSize) + const [auxiliarySizeThresholdInput, setAuxiliarySizeThresholdInput] = useState< + number | string + >(auxiliarySizeThreshold) const { pathGroupMap } = useRightToolsStore() const isAutoSave = useIntervalStore((state) => state.isAutoSave) @@ -261,6 +268,10 @@ const TopTools = ( usePaperStore.getState().refreshNodeVisualSize() }, [nodeSize]) + useEffect(() => { + setAuxiliarySizeThresholdInput(auxiliarySizeThreshold) + }, [auxiliarySizeThreshold]) + const toggleRightPanel = useCallback( (visible: boolean, setter: (val: boolean) => void) => { const nextVisible = !visible @@ -2186,39 +2197,84 @@ const TopTools = ( - - - - 节点 - - { - setNodeSizeInput(value) - }} - onFocus={() => { - useKeyEventStore.getState().setFocusInput(true) - }} - onBlur={(event) => { - useKeyEventStore.getState().setFocusInput(false) - const rawValue = event.currentTarget.value.trim() - const nextValue = - rawValue === "" ? DEFAULT_NODE_SIZE : Number(rawValue) - setNodeSize( - Number.isFinite(nextValue) ? nextValue : DEFAULT_NODE_SIZE - ) - }} - /> - - + + + + + 节点 + + { + setNodeSizeInput(value) + }} + onFocus={() => { + useKeyEventStore.getState().setFocusInput(true) + }} + onBlur={(event) => { + useKeyEventStore.getState().setFocusInput(false) + const rawValue = event.currentTarget.value.trim() + const nextValue = + rawValue === "" ? DEFAULT_NODE_SIZE : Number(rawValue) + setNodeSize( + Number.isFinite(nextValue) ? nextValue : DEFAULT_NODE_SIZE + ) + }} + /> + + + + + + 尺寸 + + { + setAuxiliarySizeThresholdInput(value) + }} + onFocus={() => { + useKeyEventStore.getState().setFocusInput(true) + }} + onBlur={(event) => { + useKeyEventStore.getState().setFocusInput(false) + const rawValue = event.currentTarget.value.trim() + const nextValue = rawValue === "" ? 0 : Number(rawValue) + setAuxiliarySizeThreshold( + Number.isFinite(nextValue) ? nextValue : 0 + ) + }} + /> + + + + {renderEditIcon} @@ -276,12 +349,18 @@ const RightTaskTools: React.FC = (props) => { - + + color={!isParamsEmpty ? "blue" : "gray"} + onClick={() => setFilterOpened((opened) => !opened)}> From e30eb3b5895000b7b52b88f9e66db70964b530ea Mon Sep 17 00:00:00 2001 From: zhangheng Date: Mon, 20 Apr 2026 15:43:23 +0800 Subject: [PATCH 5/8] fix(task): table --- app/project/detail/OwnTaskTableContainer.tsx | 50 +++++++++++++------- app/project/detail/TaskTableContainer.tsx | 28 +++++------ 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/app/project/detail/OwnTaskTableContainer.tsx b/app/project/detail/OwnTaskTableContainer.tsx index 5b03352..32ece3c 100644 --- a/app/project/detail/OwnTaskTableContainer.tsx +++ b/app/project/detail/OwnTaskTableContainer.tsx @@ -20,6 +20,7 @@ import { Button, Collapse, Group, + MultiSelect, Select, SimpleGrid, Stack, @@ -63,10 +64,17 @@ const EMPTY_TASK_SIZE: Task.SizeProps = Object.freeze({ comment_question_size: 0, }) -function createInitialFilters() { +interface FilterFormRecord { + search_id: string + search_status: string + current_uid: string[] +} + +function createInitialFilters(): FilterFormRecord { return { search_id: "", search_status: "0", + current_uid: [], } } @@ -342,6 +350,20 @@ export default function OwnTaskTableContainer(props: { {searchExpanded ? "收起筛选" : "展开筛选"} + + + +