Revert "feat: fix the problem in the Operator Market frontend pages"

This commit is contained in:
Kecheng Sha
2025-12-29 12:00:37 +08:00
committed by GitHub
parent 8f30f71a68
commit 0df7a872e4
213 changed files with 45537 additions and 45547 deletions

View File

@@ -1,61 +1,61 @@
import { useEffect, useState } from "react";
import { Tabs, Button } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router";
import TaskList from "./components/TaskList";
import TemplateList from "./components/TemplateList";
import ProcessFlowDiagram from "./components/ProcessFlowDiagram";
import { useSearchParams } from "@/hooks/useSearchParams";
export default function DataProcessingPage() {
const navigate = useNavigate();
const urlParams = useSearchParams();
const [currentView, setCurrentView] = useState<"task" | "template">("task");
useEffect(() => {
if (urlParams.view) {
setCurrentView(urlParams.view);
}
}, [urlParams]);
return (
<div className="h-full flex flex-col gap-4">
{/* Header */}
<div className="flex justify-between items-center">
<h1 className="text-xl font-bold"></h1>
<div className="flex gap-2">
<Button
icon={<PlusOutlined />}
onClick={() => navigate("/data/cleansing/create-template")}
>
</Button>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => navigate("/data/cleansing/create-task")}
>
</Button>
</div>
</div>
<ProcessFlowDiagram />
<Tabs
activeKey={currentView}
onChange={(key) => setCurrentView(key as any)}
items={[
{
key: "task",
label: "任务列表",
},
{
key: "template",
label: "模板管理",
},
]}
/>
{currentView === "task" && <TaskList />}
{currentView === "template" && <TemplateList />}
</div>
);
}
import { useEffect, useState } from "react";
import { Tabs, Button } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router";
import TaskList from "./components/TaskList";
import TemplateList from "./components/TemplateList";
import ProcessFlowDiagram from "./components/ProcessFlowDiagram";
import { useSearchParams } from "@/hooks/useSearchParams";
export default function DataProcessingPage() {
const navigate = useNavigate();
const urlParams = useSearchParams();
const [currentView, setCurrentView] = useState<"task" | "template">("task");
useEffect(() => {
if (urlParams.view) {
setCurrentView(urlParams.view);
}
}, [urlParams]);
return (
<div className="h-full flex flex-col gap-4">
{/* Header */}
<div className="flex justify-between items-center">
<h1 className="text-xl font-bold"></h1>
<div className="flex gap-2">
<Button
icon={<PlusOutlined />}
onClick={() => navigate("/data/cleansing/create-template")}
>
</Button>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => navigate("/data/cleansing/create-task")}
>
</Button>
</div>
</div>
<ProcessFlowDiagram />
<Tabs
activeKey={currentView}
onChange={(key) => setCurrentView(key as any)}
items={[
{
key: "task",
label: "任务列表",
},
{
key: "template",
label: "模板管理",
},
]}
/>
{currentView === "task" && <TaskList />}
{currentView === "template" && <TemplateList />}
</div>
);
}

View File

@@ -1,86 +1,86 @@
import {
ArrowRight,
CheckCircle,
Database,
Play,
Settings,
Workflow,
Zap,
} from "lucide-react";
// 流程图组件
export default function ProcessFlowDiagram() {
const flowSteps = [
{
id: "start",
label: "开始",
type: "start",
icon: Play,
color: "bg-green-500",
},
{
id: "select",
label: "选择数据集",
type: "process",
icon: Database,
color: "bg-blue-500",
},
{
id: "config",
label: "基本配置",
type: "process",
icon: Settings,
color: "bg-purple-500",
},
{
id: "operators",
label: "算子编排",
type: "process",
icon: Workflow,
color: "bg-orange-500",
},
{
id: "execute",
label: "执行任务",
type: "process",
icon: Zap,
color: "bg-red-500",
},
{
id: "end",
label: "完成",
type: "end",
icon: CheckCircle,
color: "bg-green-500",
},
];
return (
<div className="border-card p-6">
<div className="w-full flex items-center justify-center">
<div className="w-full flex items-center space-x-12">
{flowSteps.map((step, index) => {
const IconComponent = step.icon;
return (
<div key={step.id} className="flex-1 flex items-center">
<div className="flex flex-col items-center w-full">
<div
className={`w-12 h-12 ${step.color} rounded-full flex items-center justify-center text-white shadow-lg`}
>
<IconComponent className="w-6 h-6" />
</div>
<span className="text-xs font-medium text-gray-700 mt-2 text-center max-w-16">
{step.label}
</span>
</div>
{index < flowSteps.length - 1 && (
<ArrowRight className="w-6 h-6 text-gray-400 mx-3" />
)}
</div>
);
})}
</div>
</div>
</div>
);
}
import {
ArrowRight,
CheckCircle,
Database,
Play,
Settings,
Workflow,
Zap,
} from "lucide-react";
// 流程图组件
export default function ProcessFlowDiagram() {
const flowSteps = [
{
id: "start",
label: "开始",
type: "start",
icon: Play,
color: "bg-green-500",
},
{
id: "select",
label: "选择数据集",
type: "process",
icon: Database,
color: "bg-blue-500",
},
{
id: "config",
label: "基本配置",
type: "process",
icon: Settings,
color: "bg-purple-500",
},
{
id: "operators",
label: "算子编排",
type: "process",
icon: Workflow,
color: "bg-orange-500",
},
{
id: "execute",
label: "执行任务",
type: "process",
icon: Zap,
color: "bg-red-500",
},
{
id: "end",
label: "完成",
type: "end",
icon: CheckCircle,
color: "bg-green-500",
},
];
return (
<div className="border-card p-6">
<div className="w-full flex items-center justify-center">
<div className="w-full flex items-center space-x-12">
{flowSteps.map((step, index) => {
const IconComponent = step.icon;
return (
<div key={step.id} className="flex-1 flex items-center">
<div className="flex flex-col items-center w-full">
<div
className={`w-12 h-12 ${step.color} rounded-full flex items-center justify-center text-white shadow-lg`}
>
<IconComponent className="w-6 h-6" />
</div>
<span className="text-xs font-medium text-gray-700 mt-2 text-center max-w-16">
{step.label}
</span>
</div>
{index < flowSteps.length - 1 && (
<ArrowRight className="w-6 h-6 text-gray-400 mx-3" />
)}
</div>
);
})}
</div>
</div>
</div>
);
}

View File

@@ -1,308 +1,308 @@
import { useState } from "react";
import { Table, Progress, Badge, Button, Tooltip, Card, App } from "antd";
import {
PlayCircleOutlined,
PauseCircleOutlined,
DeleteOutlined,
} from "@ant-design/icons";
import { SearchControls } from "@/components/SearchControls";
import CardView from "@/components/CardView";
import { useNavigate } from "react-router";
import { mapTask, TaskStatusMap } from "../../cleansing.const";
import {
TaskStatus,
type CleansingTask,
} from "@/pages/DataCleansing/cleansing.model";
import useFetchData from "@/hooks/useFetchData";
import {
deleteCleaningTaskByIdUsingDelete,
executeCleaningTaskUsingPost,
queryCleaningTasksUsingGet,
stopCleaningTaskUsingPost,
} from "../../cleansing.api";
export default function TaskList() {
const navigate = useNavigate();
const { message } = App.useApp();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const filterOptions = [
{
key: "status",
label: "状态",
options: [...Object.values(TaskStatusMap)],
},
];
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
handleKeywordChange,
} = useFetchData(queryCleaningTasksUsingGet, mapTask);
const pauseTask = async (item: CleansingTask) => {
await stopCleaningTaskUsingPost(item.id);
message.success("任务已暂停");
fetchData();
};
const startTask = async (item: CleansingTask) => {
await executeCleaningTaskUsingPost(item.id);
message.success("任务已启动");
fetchData();
};
const deleteTask = async (item: CleansingTask) => {
await deleteCleaningTaskByIdUsingDelete(item.id);
message.success("任务已删除");
fetchData();
};
const taskOperations = (record: CleansingTask) => {
const isRunning = record.status?.value === TaskStatus.RUNNING;
const showStart = [
TaskStatus.PENDING,
TaskStatus.FAILED,
TaskStatus.STOPPED,
].includes(record.status?.value);
const pauseBtn = {
key: "pause",
label: "暂停",
icon: isRunning ? <PauseCircleOutlined /> : <PlayCircleOutlined />,
onClick: pauseTask, // implement pause/play logic
};
const startBtn = {
key: "start",
label: "启动",
icon: isRunning ? <PauseCircleOutlined /> : <PlayCircleOutlined />,
onClick: startTask, // implement pause/play logic
};
return [
...(isRunning
? [ pauseBtn ]
: []),
...(showStart
? [ startBtn ]
: []),
{
key: "delete",
label: "删除",
danger: true,
icon: <DeleteOutlined />,
onClick: deleteTask, // implement delete logic
},
];
};
const taskColumns = [
{
title: "任务名称",
dataIndex: "name",
key: "name",
fixed: "left",
width: 150,
ellipsis: true,
render: (_, task: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/cleansing/task-detail/" + task.id)
}
>
{task.name}
</Button>
);
},
},
{
title: "任务ID",
dataIndex: "id",
key: "id",
width: 150,
ellipsis: true,
},
{
title: "源数据集",
dataIndex: "srcDatasetId",
key: "srcDatasetId",
width: 150,
ellipsis: true,
render: (_, record: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/management/detail/" + record.srcDatasetId)
}
>
{record.srcDatasetName}
</Button>
);
},
},
{
title: "目标数据集",
dataIndex: "destDatasetId",
key: "destDatasetId",
width: 150,
ellipsis: true,
render: (_, record: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/management/detail/" + record.destDatasetId)
}
>
{record.destDatasetName}
</Button>
);
},
},
{
title: "状态",
dataIndex: "status",
key: "status",
width: 100,
render: (status: any) => {
return <Badge color={status?.color} text={status?.label} />;
},
},
{
title: "进度",
dataIndex: "process",
key: "process",
width: 150,
render: (_, record: CleansingTask) => {
if (record?.status?.value == TaskStatus.FAILED) {
return <Progress percent={record?.progress?.process} size="small" status="exception" />;
}
return <Progress percent={record?.progress?.process} size="small"/>;
},
},
{
title: "已处理文件数",
dataIndex: "finishedFileNum",
key: "finishedFileNum",
width: 120,
align: "right",
ellipsis: true,
},
{
title: "总文件数",
dataIndex: "totalFileNum",
key: "totalFileNum",
width: 100,
align: "right",
ellipsis: true,
},
{
title: "执行耗时",
dataIndex: "duration",
key: "duration",
width: 100,
ellipsis: true,
},
{
title: "开始时间",
dataIndex: "startedAt",
key: "startedAt",
width: 180,
ellipsis: true,
},
{
title: "结束时间",
dataIndex: "finishedAt",
key: "finishedAt",
width: 180,
ellipsis: true,
},
{
title: "创建时间",
dataIndex: "createdAt",
key: "createdAt",
width: 180,
ellipsis: true,
},
{
title: "数据量变化",
dataIndex: "dataSizeChange",
key: "dataSizeChange",
width: 180,
ellipsis: true,
render: (_: any, record: CleansingTask) => {
if (record.before !== undefined && record.after !== undefined) {
return `${record.before}${record.after}`;
}
return "-";
},
},
{
title: "操作",
key: "action",
fixed: "right",
render: (text: string, record: any) => (
<div className="flex gap-2">
{taskOperations(record).map((op) =>
op ? (
<Tooltip key={op.key} title={op.label}>
<Button
type="text"
icon={op.icon}
danger={op?.danger}
onClick={() => op.onClick(record)}
/>
</Tooltip>
) : null
)}
</div>
),
},
];
return (
<>
{/* Search and Filters */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索任务名称、描述"
filters={filterOptions}
onFiltersChange={handleFiltersChange}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle={true}
onReload={fetchData}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
/>
{/* Task List */}
{viewMode === "card" ? (
<CardView
data={tableData}
operations={taskOperations}
pagination={pagination}
onView={(tableData) => {
navigate("/data/cleansing/task-detail/" + tableData.id)
}}
/>
) : (
<Card>
<Table
columns={taskColumns}
dataSource={tableData}
rowKey="id"
loading={loading}
scroll={{ x: "max-content", y: "calc(100vh - 35rem)" }}
pagination={pagination}
/>
</Card>
)}
</>
);
}
import { useState } from "react";
import { Table, Progress, Badge, Button, Tooltip, Card, App } from "antd";
import {
PlayCircleOutlined,
PauseCircleOutlined,
DeleteOutlined,
} from "@ant-design/icons";
import { SearchControls } from "@/components/SearchControls";
import CardView from "@/components/CardView";
import { useNavigate } from "react-router";
import { mapTask, TaskStatusMap } from "../../cleansing.const";
import {
TaskStatus,
type CleansingTask,
} from "@/pages/DataCleansing/cleansing.model";
import useFetchData from "@/hooks/useFetchData";
import {
deleteCleaningTaskByIdUsingDelete,
executeCleaningTaskUsingPost,
queryCleaningTasksUsingGet,
stopCleaningTaskUsingPost,
} from "../../cleansing.api";
export default function TaskList() {
const navigate = useNavigate();
const { message } = App.useApp();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const filterOptions = [
{
key: "status",
label: "状态",
options: [...Object.values(TaskStatusMap)],
},
];
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
handleKeywordChange,
} = useFetchData(queryCleaningTasksUsingGet, mapTask);
const pauseTask = async (item: CleansingTask) => {
await stopCleaningTaskUsingPost(item.id);
message.success("任务已暂停");
fetchData();
};
const startTask = async (item: CleansingTask) => {
await executeCleaningTaskUsingPost(item.id);
message.success("任务已启动");
fetchData();
};
const deleteTask = async (item: CleansingTask) => {
await deleteCleaningTaskByIdUsingDelete(item.id);
message.success("任务已删除");
fetchData();
};
const taskOperations = (record: CleansingTask) => {
const isRunning = record.status?.value === TaskStatus.RUNNING;
const showStart = [
TaskStatus.PENDING,
TaskStatus.FAILED,
TaskStatus.STOPPED,
].includes(record.status?.value);
const pauseBtn = {
key: "pause",
label: "暂停",
icon: isRunning ? <PauseCircleOutlined /> : <PlayCircleOutlined />,
onClick: pauseTask, // implement pause/play logic
};
const startBtn = {
key: "start",
label: "启动",
icon: isRunning ? <PauseCircleOutlined /> : <PlayCircleOutlined />,
onClick: startTask, // implement pause/play logic
};
return [
...(isRunning
? [ pauseBtn ]
: []),
...(showStart
? [ startBtn ]
: []),
{
key: "delete",
label: "删除",
danger: true,
icon: <DeleteOutlined />,
onClick: deleteTask, // implement delete logic
},
];
};
const taskColumns = [
{
title: "任务名称",
dataIndex: "name",
key: "name",
fixed: "left",
width: 150,
ellipsis: true,
render: (_, task: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/cleansing/task-detail/" + task.id)
}
>
{task.name}
</Button>
);
},
},
{
title: "任务ID",
dataIndex: "id",
key: "id",
width: 150,
ellipsis: true,
},
{
title: "源数据集",
dataIndex: "srcDatasetId",
key: "srcDatasetId",
width: 150,
ellipsis: true,
render: (_, record: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/management/detail/" + record.srcDatasetId)
}
>
{record.srcDatasetName}
</Button>
);
},
},
{
title: "目标数据集",
dataIndex: "destDatasetId",
key: "destDatasetId",
width: 150,
ellipsis: true,
render: (_, record: CleansingTask) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/management/detail/" + record.destDatasetId)
}
>
{record.destDatasetName}
</Button>
);
},
},
{
title: "状态",
dataIndex: "status",
key: "status",
width: 100,
render: (status: any) => {
return <Badge color={status?.color} text={status?.label} />;
},
},
{
title: "进度",
dataIndex: "process",
key: "process",
width: 150,
render: (_, record: CleansingTask) => {
if (record?.status?.value == TaskStatus.FAILED) {
return <Progress percent={record?.progress?.process} size="small" status="exception" />;
}
return <Progress percent={record?.progress?.process} size="small"/>;
},
},
{
title: "已处理文件数",
dataIndex: "finishedFileNum",
key: "finishedFileNum",
width: 120,
align: "right",
ellipsis: true,
},
{
title: "总文件数",
dataIndex: "totalFileNum",
key: "totalFileNum",
width: 100,
align: "right",
ellipsis: true,
},
{
title: "执行耗时",
dataIndex: "duration",
key: "duration",
width: 100,
ellipsis: true,
},
{
title: "开始时间",
dataIndex: "startedAt",
key: "startedAt",
width: 180,
ellipsis: true,
},
{
title: "结束时间",
dataIndex: "finishedAt",
key: "finishedAt",
width: 180,
ellipsis: true,
},
{
title: "创建时间",
dataIndex: "createdAt",
key: "createdAt",
width: 180,
ellipsis: true,
},
{
title: "数据量变化",
dataIndex: "dataSizeChange",
key: "dataSizeChange",
width: 180,
ellipsis: true,
render: (_: any, record: CleansingTask) => {
if (record.before !== undefined && record.after !== undefined) {
return `${record.before}${record.after}`;
}
return "-";
},
},
{
title: "操作",
key: "action",
fixed: "right",
render: (text: string, record: any) => (
<div className="flex gap-2">
{taskOperations(record).map((op) =>
op ? (
<Tooltip key={op.key} title={op.label}>
<Button
type="text"
icon={op.icon}
danger={op?.danger}
onClick={() => op.onClick(record)}
/>
</Tooltip>
) : null
)}
</div>
),
},
];
return (
<>
{/* Search and Filters */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索任务名称、描述"
filters={filterOptions}
onFiltersChange={handleFiltersChange}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle={true}
onReload={fetchData}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
/>
{/* Task List */}
{viewMode === "card" ? (
<CardView
data={tableData}
operations={taskOperations}
pagination={pagination}
onView={(tableData) => {
navigate("/data/cleansing/task-detail/" + tableData.id)
}}
/>
) : (
<Card>
<Table
columns={taskColumns}
dataSource={tableData}
rowKey="id"
loading={loading}
scroll={{ x: "max-content", y: "calc(100vh - 35rem)" }}
pagination={pagination}
/>
</Card>
)}
</>
);
}

View File

@@ -1,156 +1,156 @@
import {DeleteOutlined, EditOutlined} from "@ant-design/icons";
import CardView from "@/components/CardView";
import {
deleteCleaningTemplateByIdUsingDelete, queryCleaningTemplatesUsingGet,
} from "../../cleansing.api";
import useFetchData from "@/hooks/useFetchData";
import {mapTemplate} from "../../cleansing.const";
import {App, Button, Card, Table, Tooltip} from "antd";
import {CleansingTemplate} from "../../cleansing.model";
import {SearchControls} from "@/components/SearchControls.tsx";
import {useNavigate} from "react-router";
import {useState} from "react";
export default function TemplateList() {
const navigate = useNavigate();
const { message } = App.useApp();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
handleKeywordChange,
} = useFetchData(queryCleaningTemplatesUsingGet, mapTemplate);
const templateOperations = () => {
return [
{
key: "update",
label: "编辑",
icon: <EditOutlined />,
onClick: (template: CleansingTemplate) => navigate(`/data/cleansing/update-template/${template.id}`)
},
{
key: "delete",
label: "删除",
danger: true,
icon: <DeleteOutlined />,
onClick: deleteTemplate, // implement delete logic
},
];
};
const templateColumns = [
{
title: "模板名称",
dataIndex: "name",
key: "name",
fixed: "left",
width: 150,
ellipsis: true,
render: (_, template: CleansingTemplate) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/cleansing/template-detail/" + template.id)
}
>
{template.name}
</Button>
);
}},
{
title: "模板ID",
dataIndex: "id",
key: "id",
fixed: "left",
width: 150,
},
{
title: "算子数量",
dataIndex: "num",
key: "num",
width: 100,
ellipsis: true,
render: (_, template: CleansingTemplate) => {
return template.instance?.length ?? 0;
},
},
{
title: "操作",
key: "action",
fixed: "right",
width: 20,
render: (text: string, record: any) => (
<div className="flex gap-2">
{templateOperations(record).map((op) =>
op ? (
<Tooltip key={op.key} title={op.label}>
<Button
type="text"
icon={op.icon}
danger={op?.danger}
onClick={() => op.onClick(record)}
/>
</Tooltip>
) : null
)}
</div>
),
},
]
const deleteTemplate = async (template: CleansingTemplate) => {
if (!template.id) {
return;
}
// 实现删除逻辑
await deleteCleaningTemplateByIdUsingDelete(template.id);
fetchData();
message.success("模板删除成功");
};
return (
<>
{/* Search and Filters */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索模板名称、描述"
onFiltersChange={handleFiltersChange}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle={true}
onReload={fetchData}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
/>
{viewMode === "card" ? (
<CardView
data={tableData}
operations={templateOperations}
pagination={pagination}
onView={(tableData) => {
navigate("/data/cleansing/template-detail/" + tableData.id)
}}
/>
) : (
<Card>
<Table
columns={templateColumns}
dataSource={tableData}
rowKey="id"
loading={loading}
scroll={{ x: "max-content", y: "calc(100vh - 35rem)" }}
pagination={pagination}
/>
</Card>
)}
</>
);
}
import {DeleteOutlined, EditOutlined} from "@ant-design/icons";
import CardView from "@/components/CardView";
import {
deleteCleaningTemplateByIdUsingDelete, queryCleaningTemplatesUsingGet,
} from "../../cleansing.api";
import useFetchData from "@/hooks/useFetchData";
import {mapTemplate} from "../../cleansing.const";
import {App, Button, Card, Table, Tooltip} from "antd";
import {CleansingTemplate} from "../../cleansing.model";
import {SearchControls} from "@/components/SearchControls.tsx";
import {useNavigate} from "react-router";
import {useState} from "react";
export default function TemplateList() {
const navigate = useNavigate();
const { message } = App.useApp();
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
handleKeywordChange,
} = useFetchData(queryCleaningTemplatesUsingGet, mapTemplate);
const templateOperations = () => {
return [
{
key: "update",
label: "编辑",
icon: <EditOutlined />,
onClick: (template: CleansingTemplate) => navigate(`/data/cleansing/update-template/${template.id}`)
},
{
key: "delete",
label: "删除",
danger: true,
icon: <DeleteOutlined />,
onClick: deleteTemplate, // implement delete logic
},
];
};
const templateColumns = [
{
title: "模板名称",
dataIndex: "name",
key: "name",
fixed: "left",
width: 150,
ellipsis: true,
render: (_, template: CleansingTemplate) => {
return (
<Button
type="link"
onClick={() =>
navigate("/data/cleansing/template-detail/" + template.id)
}
>
{template.name}
</Button>
);
}},
{
title: "模板ID",
dataIndex: "id",
key: "id",
fixed: "left",
width: 150,
},
{
title: "算子数量",
dataIndex: "num",
key: "num",
width: 100,
ellipsis: true,
render: (_, template: CleansingTemplate) => {
return template.instance?.length ?? 0;
},
},
{
title: "操作",
key: "action",
fixed: "right",
width: 20,
render: (text: string, record: any) => (
<div className="flex gap-2">
{templateOperations(record).map((op) =>
op ? (
<Tooltip key={op.key} title={op.label}>
<Button
type="text"
icon={op.icon}
danger={op?.danger}
onClick={() => op.onClick(record)}
/>
</Tooltip>
) : null
)}
</div>
),
},
]
const deleteTemplate = async (template: CleansingTemplate) => {
if (!template.id) {
return;
}
// 实现删除逻辑
await deleteCleaningTemplateByIdUsingDelete(template.id);
fetchData();
message.success("模板删除成功");
};
return (
<>
{/* Search and Filters */}
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={handleKeywordChange}
searchPlaceholder="搜索模板名称、描述"
onFiltersChange={handleFiltersChange}
viewMode={viewMode}
onViewModeChange={setViewMode}
showViewToggle={true}
onReload={fetchData}
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
/>
{viewMode === "card" ? (
<CardView
data={tableData}
operations={templateOperations}
pagination={pagination}
onView={(tableData) => {
navigate("/data/cleansing/template-detail/" + tableData.id)
}}
/>
) : (
<Card>
<Table
columns={templateColumns}
dataSource={tableData}
rowKey="id"
loading={loading}
scroll={{ x: "max-content", y: "calc(100vh - 35rem)" }}
pagination={pagination}
/>
</Card>
)}
</>
);
}