Merge branch 'zh' into 'main'

Zh

See merge request prism/lable/labelimage!10
This commit is contained in:
刘耀勇
2026-04-13 18:00:46 +08:00
13 changed files with 754 additions and 283 deletions

View File

@@ -17,6 +17,7 @@ fetch 参数:
"use client"
import { useUserStore } from "@/app/store/user"
import { usePermissionStore } from "@/components/label/store/auth"
import { deleteCookie, refreshKey } from "@/components/login/api"
import { isNumber } from "@/libs/util"
@@ -40,6 +41,7 @@ interface OptionsProps {
async function httpfetch<T>(options: OptionsProps): Promise<T> {
const { removeUserInfo, refreshToken } = useUserStore.getState()
const { logout } = usePermissionStore.getState()
const access_token = useUserStore.getState().access_token
const refresh_token = useUserStore.getState().refresh_token
@@ -197,6 +199,7 @@ async function httpfetch<T>(options: OptionsProps): Promise<T> {
} finally {
// 清空身份信息并跳转至登录页面
removeUserInfo()
logout()
window.location.href = "/login"
}
}
@@ -241,6 +244,7 @@ async function httpfetch<T>(options: OptionsProps): Promise<T> {
} finally {
// 清空身份信息并跳转至登录页面
removeUserInfo()
logout()
window.location.href = "/login"
}
} else if (status >= 510) {

View File

@@ -181,7 +181,8 @@ export default function TaskTableContainer(props: {
}) {
const { info, userEnums } = props
const { user_id } = usePermissionStore()
const { user_id, detailInfo } = usePermissionStore()
const hasRolePermissions = Boolean(detailInfo?.roles?.length)
const urlParams = useSearchParams()
const id = urlParams.get("id")
@@ -775,22 +776,24 @@ export default function TaskTableContainer(props: {
textAlign: "center",
render: (record) => (
<Flex gap={6} justify="space-around" wrap="nowrap">
<Button
px={0}
variant="transparent"
onClick={() => {
setDispatchModalConfig({
open: true,
dispatchData: {
status: record.label_status,
uid: record.current_uid || null,
reject: record.rejected,
},
task_ids: [record],
})
}}>
</Button>
{hasRolePermissions ? (
<Button
px={0}
variant="transparent"
onClick={() => {
setDispatchModalConfig({
open: true,
dispatchData: {
status: record.label_status,
uid: record.current_uid || null,
reject: record.rejected,
},
task_ids: [record],
})
}}>
</Button>
) : null}
<Button
px={0}
variant="transparent"
@@ -802,7 +805,7 @@ export default function TaskTableContainer(props: {
},
]
return cols
}, [info, projectId, router, setBackProps, type])
}, [hasRolePermissions, info, projectId, router, setBackProps, type])
return (
<>
@@ -984,31 +987,37 @@ export default function TaskTableContainer(props: {
}
actions={
<SettingHeaderActions>
<Button
variant="default"
leftSection={<IconRefresh size={16} />}
onClick={handleRefresh}
loading={loading}>
</Button>
{hasRolePermissions ? (
<Button
variant="default"
leftSection={<IconRefresh size={16} />}
onClick={handleRefresh}
loading={loading}>
</Button>
) : null}
<Button
variant="default"
disabled={!selectedRecords.length}
onClick={handleExport}>
</Button>
<Button
variant="default"
disabled={!selectedRecords.length}
onClick={clickDispatchTask}>
</Button>
<Button
variant="default"
disabled={!selectedRecords.length}
onClick={clickReleaseTask}>
</Button>
{hasRolePermissions ? (
<Button
variant="default"
disabled={!selectedRecords.length}
onClick={clickDispatchTask}>
</Button>
) : null}
{hasRolePermissions ? (
<Button
variant="default"
disabled={!selectedRecords.length}
onClick={clickReleaseTask}>
</Button>
) : null}
</SettingHeaderActions>
}
/>

View File

@@ -1512,27 +1512,37 @@ const LabelPage = ({
]
)
const handleToggleHideSelectedObject = useCallback(() => {
const selectedContext = resolveSelectedObjectContext()
if (!selectedContext) {
notifications.show({
color: "yellow",
message: "请先选中单个对象",
})
return
}
const handleToggleHideAllObjects = useCallback(() => {
const activeImageLabel = labelData.get(activeImage)
if (!activeImageLabel?.size) return
const currentHideStatus =
useObjectStore.getState().pathStatus[
selectedContext.imageId + selectedContext.objectId
]?.HIDE || false
toggleObjectHideByShortcut(
selectedContext.imageId,
selectedContext.operationId,
selectedContext.objectId,
!currentHideStatus
const activeImageOperations = Array.from(activeImageLabel.keys())
const totalObjectCount = activeImageOperations.reduce(
(count, operationId) => {
return count + (activeImageLabel.get(operationId)?.length || 0)
},
0
)
}, [resolveSelectedObjectContext])
if (!totalObjectCount) return
const pathStatus = useObjectStore.getState().pathStatus
const areAllObjectsHidden = activeImageOperations.every((operationId) => {
const operationChildren = activeImageLabel.get(operationId) || []
return operationChildren.every(
([objectId]) => pathStatus[activeImage + objectId]?.HIDE ?? false
)
})
const nextHide = !areAllObjectsHidden
const { updateOperationStatus } = useObjectStore.getState()
activeImageOperations.forEach((operationId) => {
updateOperationStatus(activeImage + operationId, "HIDE", nextHide)
activeImageLabel.get(operationId)?.forEach(([objectId]) => {
toggleObjectHideByShortcut(activeImage, operationId, objectId, nextHide)
})
})
}, [activeImage, labelData])
const rerenderActiveImageByLabel = useCallback(
(
@@ -1617,6 +1627,8 @@ const LabelPage = ({
objectId: selectedContext.objectId,
})
usePaperStore.getState().setPaperMode("polygon")
const started = usePaperStore.getState().beginContinuePolygonDraft()
if (!started) return
notifications.show({
color: "green",
message: "已进入继续标注模式",
@@ -2123,7 +2135,7 @@ const LabelPage = ({
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") {
e.preventDefault()
handleToggleHideSelectedObject()
handleToggleHideAllObjects()
return
}
@@ -2424,7 +2436,7 @@ const LabelPage = ({
enterSelectedObjectDrawMode,
focusInput,
getOperationSchema,
handleToggleHideSelectedObject,
handleToggleHideAllObjects,
handleMergeSelectedObjects,
handleContinueSelectedPolygon,
handleShortcut,

View File

@@ -1291,11 +1291,6 @@ const PaperContainer = (
setup,
])
const [paperSelectedPath] = useState<{
// key = imageId
[key: string]: number[]
}>(selectedPath)
const getOperationSchema = useCallback(
(operationId: string) => {
return (
@@ -1320,6 +1315,8 @@ const PaperContainer = (
const renderImageId = imageIdOverride ?? activeImage
if (operationSchema && imageData) {
const selectedIds =
useObjectStore.getState().selectedPath[renderImageId] || []
const toPaperPoint = (segment: any): paper.Point | null => {
if (segment instanceof paper.Point) return segment
if (segment instanceof paper.Segment) {
@@ -1401,9 +1398,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(
item[0]
),
selected: selectedIds.includes(item[0]),
}
)
})
@@ -1430,9 +1425,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(
item[0]
),
selected: selectedIds.includes(item[0]),
isHollowPolygon: true,
}
)
@@ -1456,7 +1449,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(item[0]),
selected: selectedIds.includes(item[0]),
}
)
@@ -1534,9 +1527,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(
item[0]
),
selected: selectedIds.includes(item[0]),
}
)
})
@@ -1567,9 +1558,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(
item[0]
),
selected: selectedIds.includes(item[0]),
}
)
})
@@ -1592,7 +1581,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(item[0]),
selected: selectedIds.includes(item[0]),
}
)
})
@@ -1632,9 +1621,7 @@ const PaperContainer = (
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[renderImageId]?.includes(
item[0]
),
selected: selectedIds.includes(item[0]),
}
)
})
@@ -1655,7 +1642,6 @@ const PaperContainer = (
addRectangle,
getOperationSchema,
paperPathStatus,
paperSelectedPath,
]
)
@@ -1765,6 +1751,7 @@ const PaperContainer = (
detail,
hollow_segmentation,
])
useObjectStore.getState().updateSelectedPath(activeImage, "ADD", id)
console.log(usePaperStore.getState().group)
// console.log(first)
// 更新当前页绘制数据

View File

@@ -25,8 +25,8 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
import { renderGroupPath } from "../useRenderGroupPath"
import { useRightToolsStore } from "../useRightToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { labelTypeMap } from "../utils/constants"
import { safeClone } from "../utils/clone"
import { labelTypeMap } from "../utils/constants"
import RightGroupEditModal from "./RightGroupEditModal"
interface RightGroupToolsComponentProps {
@@ -56,9 +56,25 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
const { activeImage } = useBottomToolsStore()
const { groupStatus, updateGroupStatus, selectedPath, updateSelectedPath } =
useObjectStore()
const continuePolygonTarget = usePaperStore(
(state) => state.continuePolygonTarget
)
const { getItemById, getItemsById } = usePaperStore()
const { shift: pressShift } = useKeyEventStore()
const [isGroupCollapsed, setIsGroupCollapsed] = useState<boolean>(true)
const continueObjectId =
continuePolygonTarget?.imageId === activeImage
? continuePolygonTarget.objectId
: null
const getGroupChildren = useCallback(
(groupId: number) => {
const groupChildren = pathGroupMap.get(activeImage)?.get(groupId) || []
if (continueObjectId === null) return groupChildren
return groupChildren.filter((childId) => childId !== continueObjectId)
},
[activeImage, continueObjectId, pathGroupMap]
)
const handleSelect = useCallback(
(id: number) => {
@@ -322,8 +338,10 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
<ScrollArea style={{ flex: 1 }} px="md">
<Stack gap="xs" pb="md">
{pathGroupMap.get(activeImage) &&
Array.from(pathGroupMap.get(activeImage)!.keys())?.map(
(groupId) => {
Array.from(pathGroupMap.get(activeImage)!.keys())
.filter((groupId) => getGroupChildren(groupId).length)
.map((groupId) => {
const groupChildren = getGroupChildren(groupId)
const isOpen =
!groupStatus[activeImage + groupId]?.["COLLAPSE"]
return (
@@ -376,10 +394,7 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
</Group>
</Group>
<Group gap="xs">
<Text size="sm">
{pathGroupMap.get(activeImage)?.get(groupId)
?.length || 0}
</Text>
<Text size="sm">{groupChildren.length}</Text>
<ActionIcon
variant="subtle"
size="sm"
@@ -401,51 +416,47 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
</Paper>
<Collapse in={isOpen}>
<Stack gap="xs" mt="xs" pl="xs">
{pathGroupMap
.get(activeImage)
?.get(groupId)
?.map((child) => {
const isSelected =
selectedPath[activeImage]?.includes(child)
return (
<Paper
withBorder
p="xs"
radius="md"
shadow="sm"
key={child}
bg={
isSelected
? "var(--mantine-primary-color-filled)"
: undefined
}
c={isSelected ? "white" : undefined}
onClick={() => {
handleSelect(child)
}}
style={{ cursor: "pointer" }}>
<Flex
justify="flex-end"
align="center"
gap="md">
<Text size="sm">
{child}-
{labelTypeMap.get(
getOperationSchema(
getOperationIdByPathId(child)
)?.label_type || 0
)}
</Text>
</Flex>
</Paper>
)
})}
{groupChildren.map((child) => {
const isSelected =
selectedPath[activeImage]?.includes(child)
return (
<Paper
withBorder
p="xs"
radius="md"
shadow="sm"
key={child}
bg={
isSelected
? "var(--mantine-primary-color-filled)"
: undefined
}
c={isSelected ? "white" : undefined}
onClick={() => {
handleSelect(child)
}}
style={{ cursor: "pointer" }}>
<Flex
justify="flex-end"
align="center"
gap="md">
<Text size="sm">
{child}-
{labelTypeMap.get(
getOperationSchema(
getOperationIdByPathId(child)
)?.label_type || 0
)}
</Text>
</Flex>
</Paper>
)
})}
</Stack>
</Collapse>
</Box>
)
}
)}
})}
</Stack>
</ScrollArea>
</Flex>

View File

@@ -42,8 +42,8 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
import { usePaperSupportStore } from "../usePaperSupportStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { getSubAttrStatus } from "../util"
import { toggleObjectHideByShortcut } from "../utils/objectVisibility"
import { labelTypeMap } from "../utils/constants"
import { toggleObjectHideByShortcut } from "../utils/objectVisibility"
interface RightObjectToolsComponentProps {
labelState: LabelState
@@ -143,7 +143,27 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const { shift: pressShift } = useKeyEventStore()
const continuePolygonTarget = usePaperStore(
(state) => state.continuePolygonTarget
)
const { getItemById, getItemsById } = usePaperStore()
const continueObjectId =
continuePolygonTarget?.imageId === activeImage
? continuePolygonTarget.objectId
: null
const getOperationChildren = useCallback(
(imageId: string, operationId: string) => {
const operationChildren = storeLabel.get(imageId)?.get(operationId) || []
if (continueObjectId === null || imageId !== activeImage) {
return operationChildren
}
return operationChildren.filter(
([objectId]) => objectId !== continueObjectId
)
},
[activeImage, continueObjectId, storeLabel]
)
// 切换选中对象时,清除所有其他对象的当前选中状态
const clearSelectedItems = () => {
@@ -381,14 +401,11 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
(operationId: string, hide: boolean) => {
updateOperationStatus(activeImage + operationId, "HIDE", hide)
storeLabel
.get(activeImage)
?.get(operationId)
?.map((child) => {
toggleObjectHideByShortcut(activeImage, operationId, child[0], hide)
})
getOperationChildren(activeImage, operationId).map((child) => {
toggleObjectHideByShortcut(activeImage, operationId, child[0], hide)
})
},
[activeImage, storeLabel, updateOperationStatus]
[activeImage, getOperationChildren, updateOperationStatus]
)
const objectHide = useCallback(
@@ -398,14 +415,16 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
[activeImage]
)
const activeImageOperations = labelState.get(activeImage) || []
const activeImageOperations = (labelState.get(activeImage) || []).filter(
(operationId) => getOperationChildren(activeImage, operationId).length
)
const isObjectHidden = (imageId: string, objectId: number) => {
return pathStatus[imageId + objectId]?.["HIDE"] ?? false
}
const isOperationHidden = (imageId: string, operationId: string) => {
const operationChildren = storeLabel.get(imageId)?.get(operationId) || []
const operationChildren = getOperationChildren(imageId, operationId)
return (
operationChildren.length > 0 &&
operationChildren.every(([objectId]) => isObjectHidden(imageId, objectId))
@@ -414,9 +433,7 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const totalObjectCount = activeImageOperations.reduce(
(count, operationId) => {
return (
count + (storeLabel.get(activeImage)?.get(operationId)?.length || 0)
)
return count + getOperationChildren(activeImage, operationId).length
},
0
)
@@ -424,8 +441,7 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const areAllObjectsHidden =
totalObjectCount > 0 &&
activeImageOperations.every((operationId) => {
const operationChildren =
storeLabel.get(activeImage)?.get(operationId) || []
const operationChildren = getOperationChildren(activeImage, operationId)
return operationChildren.every(([objectId]) =>
isObjectHidden(activeImage, objectId)
)
@@ -451,6 +467,9 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
.get(activeImage)
?.get(operationId)
?.some((child) => {
if (continueObjectId !== null && child[0] === continueObjectId) {
return false
}
return getSubAttrStatus(
getOperationSchema(operationId),
child[2]?.sub_attributes
@@ -458,7 +477,7 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
})
return bool
},
[activeImage, getOperationSchema, storeLabel]
[activeImage, continueObjectId, getOperationSchema, storeLabel]
)
// 搜索标注对象id
@@ -468,7 +487,8 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const id = Number(searchId)
const labelObjMap = storeLabel.get(activeImage)
Array.from(labelObjMap!.entries()).forEach(([operationId, objList]) => {
objList.forEach((obj) => {
false && objList
getOperationChildren(activeImage, operationId).forEach((obj) => {
if (id === obj[0]) {
handleSelect(id, operationId)
}
@@ -483,7 +503,7 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
if (e.key === "Enter") {
Array.from(storeLabel.entries()).forEach(([imgId, objectMap]) => {
if (objectMap.get(currentOperationId)) {
const list = objectMap.get(currentOperationId)
const list = getOperationChildren(imgId, currentOperationId)
list!.forEach((item) => {
const { sub_attributes } = item[2]
// 如果没有子属性搜索值则把对象HIDE设为false
@@ -616,8 +636,10 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
const isHide = isOperationHidden(activeImage, operationId)
const hasSubAttrError = getOperationSubAttrStatus(operationId)
const operationLabel = operationSchema?.label_class
const operationChildren =
storeLabel.get(activeImage)?.get(operationId) || []
const operationChildren = getOperationChildren(
activeImage,
operationId
)
const objectCount = operationChildren.length
const visibleChildren = operationChildren.filter((child) => {
const { comment } = child[2]

View File

@@ -5,8 +5,8 @@ import { persist, PersistStorage, StorageValue } from "zustand/middleware"
import { Comment } from "./api/label/typing"
import { safeClone } from "./utils/clone"
type LabelData = Map<string, Map<string, [number, any[], any, any[]][]>>
type LabelPathTuple = [number, any[], any, any[]]
export type LabelData = Map<string, Map<string, [number, any[], any, any[]][]>>
export type LabelPathTuple = [number, any[], any, any[]]
type LabelTime = {
label: number
review1: number

View File

@@ -7,6 +7,7 @@ import { Comment } from "./api/label/typing"
import { Project } from "./api/project/typing"
import {
initialDetail,
type LabelPathTuple,
useKeyEventStore,
useLabelStore,
useObjectStore,
@@ -180,6 +181,7 @@ interface PaperState {
getItemById: (id: number) => paper.Path | null
getItemsById: (id: number) => Array<paper.Path | paper.CompoundPath>
setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void
beginContinuePolygonDraft: () => boolean
setLoadingData: (flag: boolean) => void
resizeGhostRequestToken: number
requestResizeGhost: () => void
@@ -844,6 +846,7 @@ export const usePaperStore = create<PaperState>((set) => ({
continuePolygonTarget: null,
currentMousePosition: [0, 0],
loadingData: false,
beginContinuePolygonDraft: () => false,
init: (
initEl: HTMLCanvasElement,
initScope: paper.PaperScope,
@@ -3158,6 +3161,11 @@ export const usePaperStore = create<PaperState>((set) => ({
let continueSourceItems: Array<paper.Path | paper.CompoundPath> = []
let continueTargetContourIndex: number | null = null
let continueTargetContext: ContinuePolygonTarget | null = null
let continueTargetSnapshot: LabelPathTuple | null = null
let continueDraftHints = new Map<
string,
{ contourIndex: number; points: paper.Point[] }
>()
const handleCircles = (path: paper.Path | paper.CompoundPath | null) => {
// 更新选中的点
@@ -3243,6 +3251,146 @@ export const usePaperStore = create<PaperState>((set) => ({
return null
}
const getContinueCandidatePaths = (objectId: number) => {
const flattenedPaths: paper.Path[] = []
const visitedPaths = new Set<paper.Path>()
usePaperStore
.getState()
.getItemsById(objectId)
.filter((item) => item.data?.type === "polygon")
.forEach((item) => {
if (item instanceof paper.CompoundPath) {
;(item.children as paper.Path[]).forEach((child) => {
if (visitedPaths.has(child)) return
visitedPaths.add(child)
flattenedPaths.push(child)
})
return
}
if (item instanceof paper.Path && !visitedPaths.has(item)) {
visitedPaths.add(item)
flattenedPaths.push(item)
}
})
const candidatePaths = flattenedPaths.filter(
(path) => path.segments?.length > 3
)
if (!candidatePaths.length) return [] as paper.Path[]
const preferredPaths = candidatePaths.filter(
(path) => !path.data?.isHollowPolygon
)
return preferredPaths.length ? preferredPaths : candidatePaths
}
const clonePaperPoints = (pathPoints: paper.Point[]) => {
return pathPoints.map((point) => new paper.Point(point))
}
const removeDuplicatedClosingPoint = (pathPoints: paper.Point[]) => {
if (pathPoints.length <= 1) return pathPoints
const firstPoint = pathPoints[0]
const lastPoint = pathPoints[pathPoints.length - 1]
if (
Math.abs(firstPoint.x - lastPoint.x) < 0.000001 &&
Math.abs(firstPoint.y - lastPoint.y) < 0.000001
) {
pathPoints.pop()
}
return pathPoints
}
const getContinueContourPoints = (segments: any[] | undefined) => {
return removeDuplicatedClosingPoint(
(segments || [])
.map((segment) => getPointFromSegment(segment))
.filter((point): point is paper.Point => point !== null)
)
}
const getContinueDraftHintKey = (target: ContinuePolygonTarget) => {
return `${target.imageId}::${target.operationId}::${target.objectId}`
}
const normalizeComparableContour = (pathPoints: paper.Point[]) => {
return removeDuplicatedClosingPoint(clonePaperPoints(pathPoints)).map(
(point) => [point.x, point.y] as [number, number]
)
}
const isSameComparablePoint = (
pointA: [number, number],
pointB: [number, number]
) => {
return (
Math.abs(pointA[0] - pointB[0]) < 0.000001 &&
Math.abs(pointA[1] - pointB[1]) < 0.000001
)
}
const isSameContourOrder = (
contourA: [number, number][],
contourB: [number, number][]
) => {
if (contourA.length !== contourB.length) return false
if (!contourA.length) return false
for (let offset = 0; offset < contourB.length; offset += 1) {
let matched = true
for (let index = 0; index < contourA.length; index += 1) {
if (
!isSameComparablePoint(
contourA[index],
contourB[(index + offset) % contourB.length]
)
) {
matched = false
break
}
}
if (matched) return true
}
return false
}
const isSameContour = (pathA: paper.Point[], pathB: paper.Point[]) => {
const contourA = normalizeComparableContour(pathA)
const contourB = normalizeComparableContour(pathB)
if (contourA.length !== contourB.length || !contourA.length) {
return false
}
return (
isSameContourOrder(contourA, contourB) ||
isSameContourOrder(contourA, [...contourB].reverse())
)
}
const findMatchingContinueContourIndex = (
candidatePoints: paper.Point[],
contours: any[]
) => {
for (let index = contours.length - 1; index >= 0; index -= 1) {
const contourPoints = getContinueContourPoints(contours[index])
if (isSameContour(candidatePoints, contourPoints)) {
return index
}
}
return -1
}
const setContinueDraftHint = (
target: ContinuePolygonTarget,
contourIndex: number,
contourPoints: paper.Point[]
) => {
if (contourIndex < 0 || !contourPoints.length) return
continueDraftHints.set(getContinueDraftHintKey(target), {
contourIndex,
points: removeDuplicatedClosingPoint(clonePaperPoints(contourPoints)),
})
}
const clearContinueSourceItems = (restoreVisible: boolean) => {
continueSourceItems.forEach((item) => {
if (restoreVisible) {
@@ -3258,18 +3406,26 @@ export const usePaperStore = create<PaperState>((set) => ({
clearContinueSourceItems(restoreVisible)
continueTargetContourIndex = null
continueTargetContext = null
continueTargetSnapshot = null
usePaperStore.getState().setContinuePolygonTarget(null)
}
const hasContinuePolygonDraft = () => {
return !!(
continueTargetContext ||
continueTargetSnapshot ||
usePaperStore.getState().continuePolygonTarget
)
}
const startContinuePolygonDraft = () => {
const target = usePaperStore.getState().continuePolygonTarget
if (!target) return false
const imageLabel = useLabelStore.getState().label.get(target.imageId)
const operationPaths = imageLabel?.get(target.operationId) || []
const targetTuple = operationPaths.find(
(path) => path[0] === target.objectId
)
const targetTuple =
operationPaths.find((path) => path[0] === target.objectId) || null
if (!targetTuple) {
notifications.show({
color: "yellow",
@@ -3280,14 +3436,71 @@ export const usePaperStore = create<PaperState>((set) => ({
}
const nextPoints = safeClone(targetTuple[1] || [])
const continuePaths = getContinueCandidatePaths(target.objectId)
const continuePath = continuePaths[continuePaths.length - 1]
const continuePathPoints = continuePath?.segments?.length
? removeDuplicatedClosingPoint(
continuePath.segments.map(
(segment) => new paper.Point(segment.point)
)
)
: []
const continueHint = continueDraftHints.get(
getContinueDraftHintKey(target)
)
let contourIndex = -1
for (let i = nextPoints.length - 1; i >= 0; i -= 1) {
if (nextPoints[i]?.length > 3) {
if (
continueHint &&
continueHint.contourIndex >= 0 &&
continueHint.contourIndex < nextPoints.length
) {
contourIndex = continueHint.contourIndex
} else if (continuePathPoints.length) {
contourIndex = findMatchingContinueContourIndex(
continuePathPoints,
nextPoints
)
}
for (
let i = nextPoints.length - 1;
contourIndex === -1 && i >= 0;
i -= 1
) {
if (nextPoints[i]?.length > 2) {
contourIndex = i
break
}
}
if (contourIndex === -1) {
if (contourIndex === -1 && nextPoints.length) {
contourIndex = nextPoints.length - 1
}
let sourceContourPoints = continueHint?.points?.length
? clonePaperPoints(continueHint.points)
: []
if (
sourceContourPoints.length &&
continuePathPoints.length &&
!isSameContour(sourceContourPoints, continuePathPoints)
) {
sourceContourPoints = []
}
if (!sourceContourPoints.length && continuePathPoints.length) {
sourceContourPoints = clonePaperPoints(continuePathPoints)
}
if (!sourceContourPoints.length && contourIndex >= 0) {
sourceContourPoints = getContinueContourPoints(nextPoints[contourIndex])
}
const contourPoints = sourceContourPoints
.slice(0, -1)
.map((point) => new paper.Point(point))
if (!sourceContourPoints.length) {
notifications.show({
color: "yellow",
message: "当前对象没有可继续标注的区域",
@@ -3296,17 +3509,10 @@ export const usePaperStore = create<PaperState>((set) => ({
return false
}
const contourSegments = safeClone(nextPoints[contourIndex] || [])
const continueSegments = contourSegments.slice().reverse()
continueSegments.pop()
const contourPoints = continueSegments
.map((segment: any) => getPointFromSegment(segment))
.filter((point: paper.Point | null): point is paper.Point => !!point)
if (contourPoints.length < 3) {
if (contourPoints.length < 2) {
notifications.show({
color: "yellow",
message: "当前区域至少保留3个节点,无法继续撤回",
message: "当前区域至少保留2个节点,无法继续撤回",
})
clearContinuePolygonContext(true)
return false
@@ -3321,8 +3527,19 @@ export const usePaperStore = create<PaperState>((set) => ({
item.visible = false
})
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: { top: 0, left: 0 },
imageId: target.imageId,
operationId: target.operationId,
id: target.objectId,
})
usePaperSupportStore.getState().clearAll()
useObjectStore.getState().updateSelectedPath(target.imageId, "ADD")
continueTargetContourIndex = contourIndex
continueTargetContext = target
continueTargetSnapshot = safeClone(targetTuple) as LabelPathTuple
points = contourPoints
draftId = target.objectId
polygon?.remove()
@@ -3351,14 +3568,8 @@ export const usePaperStore = create<PaperState>((set) => ({
const saveContinuePolygonDraft = (draftPath: paper.Path) => {
const target =
continueTargetContext || usePaperStore.getState().continuePolygonTarget
if (!target) return false
const imageLabel = useLabelStore.getState().label.get(target.imageId)
const operationPaths = imageLabel?.get(target.operationId) || []
const targetTuple = operationPaths.find(
(path) => path[0] === target.objectId
)
if (!targetTuple) return false
const targetTuple = continueTargetSnapshot
if (!target || !targetTuple) return false
const pathData = JSON.parse(draftPath.exportJSON({ precision: 20 }))
const nextContour = pathData?.[1]?.segments
@@ -3382,6 +3593,7 @@ export const usePaperStore = create<PaperState>((set) => ({
safeClone(targetTuple[2] || initialDetail),
safeClone(targetTuple[3] || []),
])
setContinueDraftHint(target, contourIndex, clonePaperPoints(points))
useObjectStore
.getState()
.updateSelectedPath(target.imageId, "ADD", target.objectId)
@@ -3673,6 +3885,7 @@ export const usePaperStore = create<PaperState>((set) => ({
const points: any[] = []
const hollowPoints: any[] = []
let lastOuterContourPoints: paper.Point[] = []
const nextPaths =
nextPolygon instanceof paper.CompoundPath
? (nextPolygon.children as paper.Path[])
@@ -3700,6 +3913,7 @@ export const usePaperStore = create<PaperState>((set) => ({
hollowPoints.push(segments)
} else {
points.push(segments)
lastOuterContourPoints = getContinueContourPoints(segments)
}
})
@@ -3728,6 +3942,15 @@ export const usePaperStore = create<PaperState>((set) => ({
prevDetail,
hollowPoints,
])
setContinueDraftHint(
{
imageId: activeImage,
operationId,
objectId: selectedId,
},
points.length - 1,
lastOuterContourPoints
)
useObjectStore
.getState()
.updateSelectedPath(activeImage, "ADD", selectedId)
@@ -3746,6 +3969,135 @@ export const usePaperStore = create<PaperState>((set) => ({
return true
}
const handleSelectedPolygonAdd = (draftPath: paper.Path) => {
const activeImage = useBottomToolsStore.getState().activeImage
const selectedIds =
useObjectStore.getState().selectedPath[activeImage] || []
if (selectedIds.length !== 1) return false
const selectedId = selectedIds[0]
const targetShapes = usePaperStore
.getState()
.getItemsById(selectedId)
.filter((item) => item.data?.type === "polygon")
const targetShape =
targetShapes.find((item) => item instanceof paper.CompoundPath) ||
targetShapes.find(
(item) => item instanceof paper.Path && !item.data?.isHollowPolygon
) ||
targetShapes.find((item) => item instanceof paper.Path)
if (!targetShape) return false
let clippedDraft: paper.Path | paper.CompoundPath | null =
draftPath.intersect(usePaperStore.getState().rasterPath!, {
insert: false,
trace: false,
}) as paper.Path | paper.CompoundPath
draftPath.remove()
const draftHasSegments =
clippedDraft instanceof paper.CompoundPath
? (clippedDraft.children as paper.Path[]).some(
(child) => child.segments?.length
)
: clippedDraft.segments?.length
if (!draftHasSegments) {
clippedDraft?.remove()
clippedDraft = null
return false
}
const targetData = getSafePathData(targetShape.data || {})
let nextPolygon = (targetShape as paper.Path | paper.CompoundPath).unite(
clippedDraft
) as paper.Path | paper.CompoundPath
clippedDraft?.remove()
clippedDraft = null
targetShape.remove()
const points: any[] = []
const hollowPoints: any[] = []
let lastOuterContourPoints: paper.Point[] = []
const nextPaths =
nextPolygon instanceof paper.CompoundPath
? (nextPolygon.children as paper.Path[])
: ([nextPolygon] as paper.Path[])
const nextPathSegments = nextPaths.map((path) => {
if (!path.segments?.length) return null
const pathData = JSON.parse(path.exportJSON({ precision: 20 }))
return pathData?.[1]?.segments || null
})
const hollowFlags = getHollowFlagsByContainment(
nextPathSegments.map((segments) => segments || [])
)
if (nextPolygon instanceof paper.CompoundPath) {
nextPolygon.data = Object.assign({}, targetData)
}
nextPaths.forEach((path, index) => {
const segments = nextPathSegments[index]
if (!segments?.length) return
const isHollowPolygon = !!hollowFlags[index]
path.data = Object.assign({}, targetData, {
isHollowPolygon,
})
if (isHollowPolygon) {
hollowPoints.push(segments)
} else {
points.push(segments)
lastOuterContourPoints = getContinueContourPoints(segments)
}
})
const operationId = (
targetData.operationId ||
useTopToolsStore.getState().activeOperation ||
""
).toString()
if (!operationId) {
nextPolygon.remove()
return false
}
const prevDetail = safeClone(
useLabelStore
.getState()
.getLabelDetailDataByKeys(activeImage, operationId, selectedId) ||
initialDetail
)
if (points.length) {
useLabelStore
.getState()
.setOperation(activeImage, operationId, [
selectedId,
points,
prevDetail,
hollowPoints,
])
setContinueDraftHint(
{
imageId: activeImage,
operationId,
objectId: selectedId,
},
points.length - 1,
lastOuterContourPoints
)
useObjectStore
.getState()
.updateSelectedPath(activeImage, "ADD", selectedId)
} else {
nextPolygon.remove()
return false
}
forceUpdate()
return true
}
polygonTool.onMouseDown = (e: {
event: MouseEvent
point: paper.Point
@@ -3756,6 +4108,9 @@ export const usePaperStore = create<PaperState>((set) => ({
if (polygon || points.length) {
hidePaperContextMenu()
undoPolygonPoint()
if (!points.length && hasContinuePolygonDraft()) {
clearContinuePolygonContext(true)
}
return
}
if (!trySelectPaperObjectByPoint(e.point)) {
@@ -3771,11 +4126,7 @@ export const usePaperStore = create<PaperState>((set) => ({
usePaperSupportStore.getState().clearAll()
}
if (
!polygon &&
!points.length &&
usePaperStore.getState().continuePolygonTarget
) {
if (!polygon && !points.length && hasContinuePolygonDraft()) {
if (!startContinuePolygonDraft()) return
}
@@ -3799,7 +4150,7 @@ export const usePaperStore = create<PaperState>((set) => ({
// 初始化 compoundPolygon
compoundPolygon = new paper.Path()
if (usePaperStore.getState().continuePolygonTarget) {
if (hasContinuePolygonDraft()) {
;(polygon as paper.Path).closePath()
const continueSaved = saveContinuePolygonDraft(
polygon as paper.Path
@@ -3841,6 +4192,36 @@ export const usePaperStore = create<PaperState>((set) => ({
return
}
if (
useKeyboardStore.getState().drawAction === "add" &&
useTopToolsStore.getState().pressA
) {
const selectedId =
useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage
]?.[0]
const selectedItem = selectedId
? usePaperStore.getState().getItemById(selectedId)
: null
if (selectedItem?.data?.type === "polygon") {
;(polygon as paper.Path).closePath()
handleSelectedPolygonAdd(polygon as paper.Path)
selectedCircles.forEach((item) => {
item.remove()
})
selectedCircles = []
clearPolygonMagnet()
useTopToolsStore.getState().setPressA(false)
draftId = null
lastPoint = null
lastTime = 0
points = []
polygon = null
return
}
}
// pressA 模式下添加多边形
if (useTopToolsStore.getState().pressA) {
let ids =
@@ -3941,11 +4322,15 @@ export const usePaperStore = create<PaperState>((set) => ({
if (polygon.children && polygon.children.length) {
let points: any[] = []
let hollowPoints: any[] = []
let lastOuterContourPoints: paper.Point[] = []
;(polygon.children as paper.Path[]).forEach((path) => {
let pathData = JSON.parse(path.exportJSON({ precision: 20 }))
// 在镂空或分割的区域中 路径方向是false
if (path.clockwise) {
points.push(pathData[1]?.segments)
lastOuterContourPoints = getContinueContourPoints(
pathData[1]?.segments
)
} else {
hollowPoints.push(pathData[1]?.segments)
}
@@ -3981,6 +4366,17 @@ export const usePaperStore = create<PaperState>((set) => ({
hollowPoints,
]
)
if (points.length) {
setContinueDraftHint(
{
imageId: useBottomToolsStore.getState().activeImage,
operationId: useTopToolsStore.getState().activeOperation,
objectId: polygon.data.id,
},
points.length - 1,
lastOuterContourPoints
)
}
if (!useTopToolsStore.getState().pressA)
useRightToolsStore.getState().pushPathId(polygon.data.id)
}
@@ -4015,6 +4411,15 @@ export const usePaperStore = create<PaperState>((set) => ({
[],
]
)
setContinueDraftHint(
{
imageId: useBottomToolsStore.getState().activeImage,
operationId: useTopToolsStore.getState().activeOperation,
objectId: polygon.data.id,
},
0,
clonePaperPoints(points)
)
useRightToolsStore.getState().pushPathId(polygon.data.id)
}
@@ -4245,11 +4650,7 @@ export const usePaperStore = create<PaperState>((set) => ({
}
polygonTool.onKeyDown = (e: paper.KeyEvent) => {
if (e.key === "escape" || e.key === "alt") {
if (
polygon ||
points.length ||
usePaperStore.getState().continuePolygonTarget
) {
if (polygon || points.length || hasContinuePolygonDraft()) {
resetPolygonDraft()
clearContinuePolygonContext(true)
usePaperSupportStore.getState().handleBufferPaths(null)
@@ -4269,6 +4670,7 @@ export const usePaperStore = create<PaperState>((set) => ({
set((state: PaperState) => ({
...state,
polygonTool: polygonTool,
beginContinuePolygonDraft: () => startContinuePolygonDraft(),
}))
},
addRectangle: (

View File

@@ -32,9 +32,9 @@ export const processOpts = [
// 状态码
export const STATUS_CODE = new Map([
[1, "已创建"],
[101, "已创建"], // 审核
[102, "已创建"], // 复审
[103, "已创建"], // 审核3
[101, "已创建待审核"], // 审核
[102, "已创建待复审"], // 复审
[103, "已创建待终审"], // 审核3
[180, "数据同步中"],
[190, "Embedding生成中"],
[191, "Embedding生成失败"],

View File

@@ -136,6 +136,7 @@ export default function AppLayout({
const handleLogout = async () => {
useUserStore.getState().removeUserInfo()
usePermissionStore.getState().logout()
process.env.NEXT_PUBLIC_OUTPUT_TYPE === "standalone" &&
(await deleteCookie())
router.push("/login")

View File

@@ -12,12 +12,12 @@ const currentComponentList: MenuItem[] = [
{
title: "图片标注",
url: "project",
icon: "SystemIcon",
icon: "ImageAnnotationIcon",
items: [
{
title: "我的项目",
url: "own",
icon: "PersonalMenuIcon",
icon: "MyProjectsIcon",
},
],
},
@@ -27,23 +27,23 @@ export const componentList: MenuItem[] = [
{
title: "个人中心",
url: "person",
icon: "PersonalMenuIcon",
icon: "PersonalCenterIcon",
},
...currentComponentList,
{
title: "视频标注",
url: "video",
icon: "SystemIcon",
icon: "VideoAnnotationIcon",
},
{
title: "点云标注",
url: "lidar",
icon: "LidarAnnotationIcon",
},
// {
// title: "点云标注",
// url: "lidar",
// icon: "SystemIcon",
// },
{
title: "管理中心",
url: "mgt",
icon: "SystemIcon",
icon: "ManagementCenterIcon",
},
]

View File

@@ -1,14 +1,39 @@
// import { type LucideIcon } from "lucide-react";
import {
AuthMenuIcon,
PersonalMenuIcon,
SystemIcon,
TaskMenuIcon,
} from "./icons/custom"
IconCategory2,
IconCube3dSphere,
IconFileText,
IconFolder,
IconFolders,
IconHierarchy3,
IconLayoutDashboard,
IconPhoto,
IconSettings,
IconUserCircle,
IconUsersGroup,
IconVideo,
} from "@tabler/icons-react"
import React from "react"
const iconMap = {
DashboardIcon: IconLayoutDashboard,
EmployeeManagementIcon: IconUsersGroup,
ImageAnnotationIcon: IconPhoto,
LidarAnnotationIcon: IconCube3dSphere,
ManagementCenterIcon: IconSettings,
MyProjectsIcon: IconFolder,
PersonalCenterIcon: IconUserCircle,
ProjectCategoryIcon: IconCategory2,
ProjectManagementIcon: IconFolders,
ReportIcon: IconFileText,
TeamManagementIcon: IconHierarchy3,
VideoAnnotationIcon: IconVideo,
SystemIcon: IconSettings,
PersonalMenuIcon: IconUserCircle,
TaskMenuIcon: IconLayoutDashboard,
AuthMenuIcon: IconFileText,
} as const
export const ClientIcon = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"button"> & {
@@ -16,12 +41,7 @@ export const ClientIcon = React.forwardRef<
}
>(({ icon, ...props }, _ref) => {
if (icon) {
const IconComponent = {
SystemIcon,
PersonalMenuIcon,
TaskMenuIcon,
AuthMenuIcon,
}[icon]
const IconComponent = iconMap[icon as keyof typeof iconMap]
return IconComponent ? <IconComponent style={{ ...props.style }} /> : null
} else {
return null

View File

@@ -19,6 +19,7 @@ import {
useProjectStore,
} from "@/components/label/store/auth"
import {
ALL_CODE,
projectLabelTypeOpts,
projectStatusOpts,
STATUS_CODE,
@@ -36,7 +37,6 @@ import {
Modal,
MultiSelect,
Pagination,
Paper,
Select,
SimpleGrid,
Stack,
@@ -46,6 +46,15 @@ import {
Tooltip,
UnstyledButton,
} from "@mantine/core"
import {
SettingContentPanel,
SettingFilterActions,
SettingFilterPanel,
SettingHeaderActions,
SettingListHeader,
SettingPage,
SettingPanel,
} from "@/components/setting/PageSurface"
import { useForm } from "@mantine/form"
import { notifications } from "@mantine/notifications"
import {
@@ -134,6 +143,7 @@ export default function ProjectInfoPage({
type: "dynamic" | "collect" | ""
}) {
const router = useRouter()
const storeType = type || "all"
const { userOpts } = useAllUserStore()
const userName = usePermissionStore((s) => s.user_name)
@@ -150,20 +160,20 @@ export default function ProjectInfoPage({
}: any = useProjectStore()
const storeParams = useMemo(() => {
return type === "collect"
return storeType === "collect"
? collectParams
: type === "dynamic"
: storeType === "dynamic"
? dynamicParams
: allParams
}, [allParams, collectParams, dynamicParams, type])
}, [allParams, collectParams, dynamicParams, storeType])
const total = useMemo(() => {
return type === "collect"
return storeType === "collect"
? collectTotal
: type === "dynamic"
: storeType === "dynamic"
? dynamicTotal
: allTotal
}, [allTotal, collectTotal, dynamicTotal, type])
}, [allTotal, collectTotal, dynamicTotal, storeType])
const [typeOpts, setTypeOpts] = useState<
Array<{ label: string; value: string }>
@@ -179,7 +189,7 @@ export default function ProjectInfoPage({
status: "",
owner: "",
label_type: "",
admin_user: "",
admin_user: [] as string[],
start_date: dayjs().subtract(6, "day").format("YYYY-MM-DD"),
end_date: dayjs().format("YYYY-MM-DD"),
}
@@ -200,9 +210,9 @@ export default function ProjectInfoPage({
const queryParams = useMemo(() => {
const base =
type === "collect"
storeType === "collect"
? { ...initCollectParams }
: type === "dynamic"
: storeType === "dynamic"
? { ...initDynamicParams }
: { ...initAllParams }
@@ -214,7 +224,9 @@ export default function ProjectInfoPage({
project_name: appliedFilters.project_name || undefined,
project_type: appliedFilters.project_type || undefined,
owner: appliedFilters.owner || undefined,
admin_user: appliedFilters.admin_user || undefined,
admin_user: appliedFilters.admin_user?.length
? appliedFilters.admin_user.map((n) => Number(n))
: undefined,
start_date: appliedFilters.start_date || undefined,
end_date: appliedFilters.end_date || undefined,
label_type: appliedFilters.label_type
@@ -222,11 +234,11 @@ export default function ProjectInfoPage({
: undefined,
// 0: 图片标注 1: 点云标注 2: 视频标注
project_data_type: 0,
status: parseStatusValue(appliedFilters.status),
status: parseStatusValue(appliedFilters.status) ?? [...ALL_CODE],
}
return params
}, [appliedFilters, storeParams, type])
}, [appliedFilters, storeParams, storeType])
const load = useCallback(async () => {
if (!queryTrigger) return
@@ -234,10 +246,10 @@ export default function ProjectInfoPage({
try {
const res = await getProjectList(queryParams)
setRecords(res?.project_list ?? [])
setTotal(res?.total_items ?? 0, type)
setTotal(res?.total_items ?? 0, storeType)
} catch (e) {
setRecords([])
setTotal(0, type)
setTotal(0, storeType)
notifications.show({
color: "red",
title: "加载失败",
@@ -246,7 +258,7 @@ export default function ProjectInfoPage({
} finally {
setLoading(false)
}
}, [queryParams, queryTrigger, setTotal, type])
}, [queryParams, queryTrigger, setTotal, storeType])
useEffect(() => {
load()
@@ -345,11 +357,10 @@ export default function ProjectInfoPage({
const canDoAction = !!actionKey
return (
<Paper
<SettingPanel
key={record.id}
withBorder
radius="md"
shadow="sm"
shadow="xs"
style={{
height: 160,
padding: 14,
@@ -357,6 +368,9 @@ export default function ProjectInfoPage({
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
borderColor: "#E8EDF3",
background:
"linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(247,250,252,0.96) 100%)",
}}>
<Group justify="space-between" wrap="nowrap" align="flex-start">
<Flex flex={1}>
@@ -551,7 +565,7 @@ export default function ProjectInfoPage({
</div>
</div>
</Stack>
</Paper>
</SettingPanel>
)
}
@@ -604,19 +618,12 @@ export default function ProjectInfoPage({
}
return (
<Stack w="100%" h="calc(100vh - 56px)" p="md" gap="md">
<Paper withBorder p="md" radius="md">
<SettingPage>
<SettingFilterPanel>
<Group justify="space-between" wrap="wrap" gap="sm">
<Group gap="xs">
<Text fw={700}>
{type === "collect"
? "我的收藏"
: type === "dynamic"
? "动态项目"
: "我的项目"}
</Text>
<Text fw={700}></Text>
<Button
size="xs"
variant="transparent"
rightSection={
searchExpanded ? (
@@ -629,21 +636,19 @@ export default function ProjectInfoPage({
{searchExpanded ? "收起筛选" : "展开筛选"}
</Button>
</Group>
<Group gap="xs">
<SettingFilterActions mt={0}>
<Button
size="xs"
radius="xs"
variant="default"
leftSection={<IconRefresh size={16} />}
onClick={() => {
resetParams(type)
resetParams(storeType)
const next = {
project_name: "",
project_type: "",
status: "",
owner: "",
label_type: "",
admin_user: "",
admin_user: [],
start_date: dayjs().subtract(6, "day").format("YYYY-MM-DD"),
end_date: dayjs().format("YYYY-MM-DD"),
}
@@ -654,17 +659,15 @@ export default function ProjectInfoPage({
</Button>
<Button
size="xs"
radius="xs"
leftSection={<IconSearch size={16} />}
onClick={() => {
setSearchParams({ ...storeParams, page_number: 1 }, type)
setSearchParams({ ...storeParams, page_number: 1 }, storeType)
setAppliedFilters(filters)
setQueryTrigger((v) => v + 1)
}}>
</Button>
</Group>
</SettingFilterActions>
</Group>
<Collapse
@@ -676,8 +679,6 @@ export default function ProjectInfoPage({
<SimpleGrid cols={{ base: 1, sm: 2, md: 3, lg: 4 }} spacing="sm">
<TextInput
label="项目名称"
size="xs"
radius="xs"
value={filters.project_name}
onChange={(e) =>
setFilters((s) => ({
@@ -688,8 +689,6 @@ export default function ProjectInfoPage({
/>
<Select
label="所属业务"
size="xs"
radius="xs"
searchable
clearable
data={typeOpts}
@@ -700,20 +699,16 @@ export default function ProjectInfoPage({
/>
<Select
label="状态"
size="xs"
radius="xs"
searchable
data={projectStatusOpts.map((o) => ({
label: o.label,
value: o.label,
value: o.value.toString(),
}))}
value={filters.status}
onChange={(v) => setFilters((s) => ({ ...s, status: v ?? "" }))}
/>
<TextInput
label="负责人"
size="xs"
radius="xs"
value={filters.owner}
onChange={(e) =>
setFilters((s) => ({ ...s, owner: e.currentTarget.value }))
@@ -721,8 +716,6 @@ export default function ProjectInfoPage({
/>
<Select
label="标注类型"
size="xs"
radius="xs"
searchable
clearable
data={projectLabelTypeOpts.map((o) => ({
@@ -734,25 +727,19 @@ export default function ProjectInfoPage({
setFilters((s) => ({ ...s, label_type: v ?? "" }))
}
/>
<Select
<MultiSelect
label="标注管理员"
size="xs"
radius="xs"
searchable
clearable
data={userOpts.map((o) => ({
label: o.label,
value: String(o.value),
}))}
value={filters.admin_user || null}
onChange={(v) =>
setFilters((s) => ({ ...s, admin_user: v ?? "" }))
}
value={filters.admin_user}
onChange={(v) => setFilters((s) => ({ ...s, admin_user: v }))}
searchable
clearable
/>
<TextInput
label="开始日期"
size="xs"
radius="xs"
type="date"
value={filters.start_date}
onChange={(e) =>
@@ -764,8 +751,6 @@ export default function ProjectInfoPage({
/>
<TextInput
label="结束日期"
size="xs"
radius="xs"
type="date"
value={filters.end_date}
onChange={(e) =>
@@ -775,18 +760,36 @@ export default function ProjectInfoPage({
</SimpleGrid>
</Stack>
</Collapse>
</Paper>
</SettingFilterPanel>
<Paper
withBorder
p="md"
radius="md"
<SettingContentPanel
style={{
flex: 1,
minHeight: 0,
display: "flex",
flexDirection: "column",
}}>
<SettingListHeader
title={
type === "collect"
? "我的收藏"
: type === "dynamic"
? "动态项目"
: "全部项目"
}
count={`${total ?? 0}`}
actions={
<SettingHeaderActions>
<Button
variant="default"
leftSection={<IconRefresh size={16} />}
onClick={() => setQueryTrigger((v) => v + 1)}
loading={loading}>
</Button>
</SettingHeaderActions>
}
/>
<div
style={{
flex: 1,
@@ -839,19 +842,19 @@ export default function ProjectInfoPage({
pageSize={pageSize}
total={total ?? 0}
onChangePage={(p) => {
setSearchParams({ ...storeParams, page_number: p }, type)
setSearchParams({ ...storeParams, page_number: p }, storeType)
setQueryTrigger((v) => v + 1)
}}
onChangePageSize={(s) => {
setSearchParams(
{ ...storeParams, page_number: 1, page_size: s },
type
storeType
)
setQueryTrigger((v) => v + 1)
}}
/>
</div>
</Paper>
</SettingContentPanel>
<Modal
opened={infoModalRecord !== null}
@@ -1139,6 +1142,6 @@ export default function ProjectInfoPage({
</Group>
</Stack>
</Modal>
</Stack>
</SettingPage>
)
}