feat: Enhance DatasetDetail component with delete functionality and improved download handling

feat: Add automatic data refresh and improved user feedback in DatasetManagementPage

fix: Update dataset API to streamline download functionality and improve error handling
This commit is contained in:
chenghh-9609
2025-10-23 15:37:22 +08:00
parent a6d4b51601
commit bb116839ae
19 changed files with 397 additions and 1007 deletions

View File

@@ -0,0 +1,115 @@
import { Dropdown, Popconfirm, Button, Space } from "antd";
import { EllipsisOutlined } from "@ant-design/icons";
import { useState } from "react";
interface ActionItem {
key: string;
label: string;
icon?: React.ReactNode;
danger?: boolean;
confirm?: {
title: string;
description?: string;
okText?: string;
cancelText?: string;
};
}
interface ActionDropdownProps {
actions?: ActionItem[];
onAction?: (key: string, action: ActionItem) => void;
placement?:
| "bottomRight"
| "topLeft"
| "topCenter"
| "topRight"
| "bottomLeft"
| "bottomCenter"
| "top"
| "bottom";
}
const ActionDropdown = ({
actions = [],
onAction,
placement = "bottomRight",
}: ActionDropdownProps) => {
const [open, setOpen] = useState(false);
const handleActionClick = (action: ActionItem) => {
if (action.confirm) {
// 如果有确认框,不立即执行,等待确认
return;
}
// 执行操作
onAction?.(action.key, action);
// 如果没有确认框,则立即关闭 Dropdown
setOpen(false);
};
const dropdownContent = (
<div className="bg-white p-2 rounded shadow-md">
<Space direction="vertical" className="w-full">
{actions.map((action) => {
if (action.confirm) {
return (
<Popconfirm
key={action.key}
title={action.confirm.title}
description={action.confirm.description}
onConfirm={() => {
onAction?.(action.key, action);
setOpen(false);
}}
okText={action.confirm.okText || "确定"}
cancelText={action.confirm.cancelText || "取消"}
okType={action.danger ? "danger" : "primary"}
styles={{ root: { zIndex: 9999 } }}
>
<Button
type="text"
size="small"
className="w-full text-left"
danger={action.danger}
icon={action.icon}
>
{action.label}
</Button>
</Popconfirm>
);
}
return (
<Button
key={action.key}
className="w-full"
size="small"
type="text"
danger={action.danger}
icon={action.icon}
onClick={() => handleActionClick(action)}
>
{action.label}
</Button>
);
})}
</Space>
</div>
);
return (
<Dropdown
overlay={dropdownContent}
trigger={["click"]}
placement={placement}
open={open}
onOpenChange={setOpen}
>
<Button
type="text"
icon={<EllipsisOutlined style={{ fontSize: 24 }} />}
/>
</Dropdown>
);
};
export default ActionDropdown;

View File

@@ -64,6 +64,7 @@ export default function AddTagPopover({
open={showPopover}
trigger="click"
placement="bottom"
onOpenChange={setShowPopover}
content={
<div className="space-y-4 w-[300px]">
<h4 className="font-medium border-b pb-2 border-gray-100">

View File

@@ -1,12 +1,17 @@
import React, { useState, useEffect, useRef } from "react";
import { Tag, Pagination, Dropdown, Tooltip, Empty, Popover } from "antd";
import {
EllipsisOutlined,
ClockCircleOutlined,
StarFilled,
} from "@ant-design/icons";
Tag,
Pagination,
Tooltip,
Empty,
Popover,
Menu,
Popconfirm,
} from "antd";
import { ClockCircleOutlined, StarFilled } from "@ant-design/icons";
import type { ItemType } from "antd/es/menu/interface";
import { formatDateTime } from "@/utils/unit";
import ActionDropdown from "./ActionDropdown";
interface BaseCardDataType {
id: string | number;
@@ -168,6 +173,48 @@ function CardView<T extends BaseCardDataType>(props: CardViewProps<T>) {
const ops = (item) =>
typeof operations === "function" ? operations(item) : operations;
const menu = (item) => {
const ops =
typeof operations === "function" ? operations(item) : operations;
<Menu>
{ops.map((op) => {
if (op?.danger) {
return (
<Menu.Item key={op?.key} disabled icon={op?.icon}>
<Popconfirm
title="确定删除吗?"
description="此操作不可撤销"
onConfirm={op.onClick ? () => op.onClick(item) : undefined}
okText="确定"
cancelText="取消"
// 阻止事件冒泡,避免 Dropdown 关闭
onClick={(e) => e.stopPropagation()}
>
<div
style={{
display: "block",
width: "100%",
color: "inherit",
}}
onClick={(e) => e.stopPropagation()}
>
{op.icon}
{op.label}
</div>
</Popconfirm>
</Menu.Item>
);
} else {
return (
<Menu.Item key={op?.key} onClick={op?.onClick} icon={op?.icon}>
{op?.label}
</Menu.Item>
);
}
})}
</Menu>;
};
return (
<div className="flex-overflow-hidden">
<div className="overflow-auto grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
@@ -261,24 +308,15 @@ function CardView<T extends BaseCardDataType>(props: CardViewProps<T>) {
</div>
</div>
{operations && (
<Dropdown
trigger={["click"]}
menu={{
items: ops(item),
onClick: ({ key }) => {
const operation = ops(item).find(
(op) => op.key === key
);
if (operation?.onClick) {
operation.onClick(item);
}
},
<ActionDropdown
actions={ops(item)}
onAction={(key) => {
const operation = ops(item).find((op) => op.key === key);
if (operation?.onClick) {
operation.onClick(item);
}
}}
>
<div className="cursor-pointer">
<EllipsisOutlined style={{ fontSize: 24 }} />
</div>
</Dropdown>
/>
)}
</div>
</div>

View File

@@ -1,8 +1,9 @@
import React from "react";
import { Database } from "lucide-react";
import { Card, Dropdown, Button, Tag, Tooltip } from "antd";
import { Card, Button, Tag, Tooltip, Popconfirm } from "antd";
import type { ItemType } from "antd/es/menu/interface";
import AddTagPopover from "./AddTagPopover";
import ActionDropdown from "./ActionDropdown";
interface StatisticItem {
icon: React.ReactNode;
@@ -100,22 +101,39 @@ function DetailHeader<T>({
{operations.map((op) => {
if (op.isDropdown) {
return (
<Dropdown
key={op.key}
menu={{
items: op?.items as ItemType[],
onClick: op?.onMenuClick,
}}
>
<Tooltip title={op.label}>
<Button icon={op.icon} />
</Tooltip>
</Dropdown>
<ActionDropdown
actions={op?.items}
onAction={op?.onMenuClick}
/>
);
}
if (op.confirm) {
return (
<Tooltip key={op.key} title={op.label}>
<Popconfirm
key={op.key}
title={op.confirm.title}
description={op.confirm.description}
onConfirm={() => {
op?.onClick();
}}
okText={op.confirm.okText || "确定"}
cancelText={op.confirm.cancelText || "取消"}
okType={op.danger ? "danger" : "primary"}
overlayStyle={{ zIndex: 9999 }}
>
<Button icon={op.icon} danger={op.danger} />
</Popconfirm>
</Tooltip>
);
}
return (
<Tooltip key={op.key} title={op.label}>
<Button {...op} />
<Button
icon={op.icon}
danger={op.danger}
onClick={op.onClick}
/>
</Tooltip>
);
})}