feat(team): add page

This commit is contained in:
2026-02-27 09:51:33 +08:00
parent 5325ef7b95
commit 6cb8f98004
29 changed files with 4557 additions and 11 deletions

View File

@@ -0,0 +1,455 @@
"use client"
import { getProjectList } from "@/components/label/api/project"
import {
getAllSyncServerList,
getDataSyncTaskList,
newSyncTask,
updateSyncTaskStatus,
} from "@/components/label/api/sync"
import { Response } from "@/components/label/api/sync/typing"
import useAuth from "@/components/label/hooks/useAuth"
import {
ActionIcon,
Button,
Group,
Modal,
Paper,
Select,
Stack,
Text,
} from "@mantine/core"
import { modals } from "@mantine/modals"
import { notifications } from "@mantine/notifications"
import { IconPlayerPause, IconPlayerPlay, IconPlus, IconRefresh, IconSearch, IconTrash, IconX } from "@tabler/icons-react"
import { DataTable, DataTableColumn } from "mantine-datatable"
import { useCallback, useEffect, useMemo, useState } from "react"
const StatusEnum = new Map<number, string>([
[1, "待同步"],
[2, "同步中"],
[3, "同步失败"],
[4, "同步成功"],
[5, "待删除"],
[6, "删除中"],
[7, "删除失败"],
[8, "删除完成"],
[9, "等待暂停"],
[10, "已暂停"],
[11, "等待关闭"],
[12, "已关闭"],
])
export default function TeamSyncTaskPage() {
const { isShow } = useAuth()
const [records, setRecords] = useState<Response.SyncTask[]>([])
const [loading, setLoading] = useState(false)
const [totalItems, setTotalItems] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [status, setStatus] = useState<string | null>(null)
const [projectId, setProjectId] = useState<string | null>(null)
const [serverId, setServerId] = useState<string | null>(null)
const [appliedStatus, setAppliedStatus] = useState<string | null>(null)
const [appliedProjectId, setAppliedProjectId] = useState<string | null>(null)
const [appliedServerId, setAppliedServerId] = useState<string | null>(null)
const [projectOpt, setProjectOpt] = useState<Array<{ label: string; value: string }>>([])
const [serverAllOpt, setServerAllOpt] = useState<Array<{ label: string; value: string }>>([])
const [serverOnlineOpt, setServerOnlineOpt] = useState<Array<{ label: string; value: string }>>(
[]
)
const [serverMap, setServerMap] = useState<Map<number, string>>(new Map())
const [open, setOpen] = useState(false)
const [newProjectId, setNewProjectId] = useState<string | null>(null)
const [newServerId, setNewServerId] = useState<string | null>(null)
const [creating, setCreating] = useState(false)
const loadOptions = useCallback(async () => {
const [projectRes, serverRes] = await Promise.all([
getProjectList({ page_number: 1, page_size: 10000 }),
getAllSyncServerList(),
])
const projects = projectRes?.project_list ?? []
setProjectOpt(projects.map((p) => ({ label: p.name, value: String(p.id) })))
const allServers = serverRes ?? []
setServerAllOpt(allServers.map((s) => ({ label: s.ip, value: String(s.id) })))
setServerOnlineOpt(
allServers
.filter((s) => s.is_online)
.map((s) => ({ label: s.ip, value: String(s.id) }))
)
setServerMap(new Map(allServers.map((s) => [s.id, s.ip])))
}, [])
const load = useCallback(async () => {
try {
setLoading(true)
const params: any = {
page_number: page,
page_size: pageSize,
}
if (appliedStatus) params.status = Number(appliedStatus)
if (appliedProjectId) params.project_id = Number(appliedProjectId)
if (appliedServerId) params.sync_server_id = Number(appliedServerId)
const res = await getDataSyncTaskList(params)
setRecords(res?.sync_tasks ?? [])
setTotalItems(res?.total_items ?? 0)
} catch (e) {
setRecords([])
setTotalItems(0)
notifications.show({
color: "red",
title: "加载失败",
message: e instanceof Error ? e.message : "请求失败",
})
} finally {
setLoading(false)
}
}, [appliedProjectId, appliedServerId, appliedStatus, page, pageSize])
useEffect(() => {
queueMicrotask(loadOptions)
}, [loadOptions])
useEffect(() => {
load()
}, [load])
const performStatusAction = async (id: number, statusTo: number, successText: string) => {
try {
await updateSyncTaskStatus(id, statusTo)
notifications.show({
color: "green",
title: "操作成功",
message: successText,
})
load()
} catch (e) {
notifications.show({
color: "red",
title: "操作失败",
message: e instanceof Error ? e.message : "请求失败",
})
}
}
const columns = useMemo(() => {
const cols: DataTableColumn<Response.SyncTask>[] = [
{ accessor: "id", title: "ID", width: 80 },
{ accessor: "uuid", title: "UUID", width: 320 },
{
accessor: "status",
title: "状态",
width: 120,
render: (record) => StatusEnum.get(record.status ?? 0) ?? "-",
},
{ accessor: "project_name", title: "项目名称", width: 260 },
{
accessor: "sync_server_id",
title: "同步服务IP",
width: 160,
render: (record) =>
record.sync_server_id ? serverMap.get(record.sync_server_id) ?? "-" : "-",
},
{ accessor: "location", title: "IP所在地", width: 140 },
{ accessor: "create_at", title: "创建时间", width: 180 },
{ accessor: "update_at", title: "更新时间", width: 180 },
{
accessor: "operation",
title: "操作",
width: 200,
textAlign: "center",
render: (record) => {
const couldRun = isShow("task_edit") && [10].includes(record.status ?? -1)
const couldStop = isShow("task_edit") && [1, 2].includes(record.status ?? -1)
const couldDelete =
isShow("task_edit") && [1, 2, 3, 4, 9, 10].includes(record.status ?? -1)
const couldClose =
isShow("task_edit") && ![11, 12].includes(record.status ?? -1)
return (
<Group justify="center" gap={4} wrap="nowrap">
{couldRun ? (
<ActionIcon
variant="subtle"
color="green"
onClick={() =>
performStatusAction(record.id as number, 1, "已恢复同步")
}>
<IconPlayerPlay size={16} />
</ActionIcon>
) : null}
{couldStop ? (
<ActionIcon
variant="subtle"
color="yellow"
onClick={() => {
modals.openConfirmModal({
title: "暂停任务",
centered: true,
children: (
<Text size="sm"></Text>
),
labels: { confirm: "确定", cancel: "取消" },
onConfirm: () =>
performStatusAction(record.id as number, 9, "已暂停任务"),
})
}}>
<IconPlayerPause size={16} />
</ActionIcon>
) : null}
{couldDelete ? (
<ActionIcon
variant="subtle"
color="red"
onClick={() => {
modals.openConfirmModal({
title: "删除任务",
centered: true,
children: (
<Text size="sm"></Text>
),
labels: { confirm: "确定", cancel: "取消" },
confirmProps: { color: "red" },
onConfirm: () =>
performStatusAction(record.id as number, 5, "已提交删除"),
})
}}>
<IconTrash size={16} />
</ActionIcon>
) : null}
{couldClose ? (
<ActionIcon
variant="subtle"
color="gray"
onClick={() => {
modals.openConfirmModal({
title: "关闭任务",
centered: true,
children: (
<Text size="sm"></Text>
),
labels: { confirm: "确定", cancel: "取消" },
onConfirm: () =>
performStatusAction(record.id as number, 11, "已关闭任务"),
})
}}>
<IconX size={16} />
</ActionIcon>
) : null}
</Group>
)
},
},
]
return cols
}, [isShow, serverMap])
const createTask = async () => {
if (!newProjectId || !newServerId) {
notifications.show({
color: "yellow",
title: "参数不完整",
message: "请先选择项目和同步服务",
})
return
}
try {
setCreating(true)
await newSyncTask({
project_id: Number(newProjectId),
sync_server_id: Number(newServerId),
})
notifications.show({
color: "green",
title: "操作成功",
message: "已新增同步任务",
})
setOpen(false)
setNewProjectId(null)
setNewServerId(null)
load()
} catch (e) {
notifications.show({
color: "red",
title: "操作失败",
message: e instanceof Error ? e.message : "请求失败",
})
} finally {
setCreating(false)
}
}
return (
<Stack w="100%" h="calc(100vh - 56px)" p="md" gap="md">
<Paper
withBorder
p="md"
radius="md"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
}}>
<Group justify="space-between" wrap="wrap">
<Group>
<Select
placeholder="任务状态"
size="xs"
radius="xs"
data={Array.from(StatusEnum.entries()).map(([value, label]) => ({
label,
value: String(value),
}))}
clearable
searchable
value={status}
onChange={setStatus}
/>
<Select
placeholder="项目名称"
size="xs"
radius="xs"
data={projectOpt}
clearable
searchable
value={projectId}
onChange={setProjectId}
/>
<Select
placeholder="同步服务IP"
size="xs"
radius="xs"
data={serverAllOpt}
clearable
searchable
value={serverId}
onChange={setServerId}
/>
<Button
size="xs"
radius="xs"
leftSection={<IconSearch size={16} />}
onClick={() => {
setAppliedStatus(status)
setAppliedProjectId(projectId)
setAppliedServerId(serverId)
setPage(1)
}}>
</Button>
<Button
variant="default"
size="xs"
radius="xs"
leftSection={<IconRefresh size={16} />}
onClick={() => {
setStatus(null)
setProjectId(null)
setServerId(null)
setAppliedStatus(null)
setAppliedProjectId(null)
setAppliedServerId(null)
setPage(1)
setPageSize(20)
}}>
</Button>
</Group>
{isShow("task_add") ? (
<Button
size="xs"
radius="xs"
leftSection={<IconPlus size={16} />}
onClick={() => {
setNewProjectId(null)
setNewServerId(null)
setOpen(true)
}}>
</Button>
) : null}
</Group>
</Paper>
<Paper
withBorder
p="md"
radius="md"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
flex: 1,
minHeight: 0,
display: "flex",
}}>
<DataTable<Response.SyncTask>
withTableBorder
withRowBorders
fetching={loading}
records={records}
columns={columns}
totalRecords={totalItems}
recordsPerPage={pageSize}
page={page}
onPageChange={setPage}
onRecordsPerPageChange={(v) => {
setPageSize(v)
setPage(1)
}}
recordsPerPageOptions={[10, 20, 50, 100]}
noRecordsText="暂无数据"
scrollAreaProps={{ type: "auto" }}
style={{ width: "100%" }}
/>
</Paper>
<Modal
opened={open}
onClose={() => setOpen(false)}
title="新增同步任务"
centered
size={520}
closeOnClickOutside={false}>
<Stack gap="sm">
<Select
label="项目名称"
size="xs"
radius="xs"
data={projectOpt}
searchable
value={newProjectId}
onChange={setNewProjectId}
/>
<Select
label="同步服务IP仅在线"
size="xs"
radius="xs"
data={serverOnlineOpt}
searchable
value={newServerId}
onChange={setNewServerId}
/>
<Group justify="flex-end">
<Button variant="default" size="xs" radius="xs" onClick={() => setOpen(false)}>
</Button>
<Button
size="xs"
radius="xs"
loading={creating}
onClick={createTask}>
</Button>
</Group>
</Stack>
</Modal>
</Stack>
)
}