refactor(DataManagement): 移除相似数据集表格并改用卡片视图显示

- 移除了 Overview 组件中的相似数据集表格相关代码
- 移除了 Tag 组件和相关依赖的导入
- 在 DatasetDetail 中添加 CardView 组件用于显示相似数据集
- 将相似数据集的展示从表格改为卡片布局
- 移除了 Overview 组件中的相似数据集参数传递
- 更新了页面布局以
This commit is contained in:
2026-01-31 09:40:06 +08:00
parent 790385bd80
commit 85d7141a91
2 changed files with 525 additions and 596 deletions

View File

@@ -1,195 +1,210 @@
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { Breadcrumb, App, Tabs, Table, Tag } from "antd"; import { Breadcrumb, App, Tabs, Table, Tag } from "antd";
import { import {
ReloadOutlined, ReloadOutlined,
DownloadOutlined, DownloadOutlined,
EditOutlined, EditOutlined,
DeleteOutlined, DeleteOutlined,
PlusOutlined, PlusOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import DetailHeader from "@/components/DetailHeader"; import DetailHeader from "@/components/DetailHeader";
import { mapDataset, datasetTypeMap } from "../dataset.const"; import { mapDataset, datasetTypeMap } from "../dataset.const";
import type { Dataset } from "@/pages/DataManagement/dataset.model"; import type { Dataset } from "@/pages/DataManagement/dataset.model";
import { Link, useNavigate, useParams } from "react-router"; import { Link, useNavigate, useParams } from "react-router";
import { useFilesOperation } from "./useFilesOperation"; import { useFilesOperation } from "./useFilesOperation";
import { import {
createDatasetTagUsingPost, createDatasetTagUsingPost,
deleteDatasetByIdUsingDelete, deleteDatasetByIdUsingDelete,
downloadDatasetUsingGet, downloadDatasetUsingGet,
queryDatasetByIdUsingGet, queryDatasetByIdUsingGet,
queryDatasetsUsingGet, queryDatasetsUsingGet,
queryDatasetTagsUsingGet, queryDatasetTagsUsingGet,
querySimilarDatasetsUsingGet, querySimilarDatasetsUsingGet,
updateDatasetByIdUsingPut, updateDatasetByIdUsingPut,
} from "../dataset.api"; } from "../dataset.api";
import DataQuality from "./components/DataQuality"; import DataQuality from "./components/DataQuality";
import DataLineageFlow from "./components/DataLineageFlow"; import DataLineageFlow from "./components/DataLineageFlow";
import Overview from "./components/Overview"; import Overview from "./components/Overview";
import { Activity, Clock, File, FileType } from "lucide-react"; import { Activity, Clock, File, FileType } from "lucide-react";
import EditDataset from "../Create/EditDataset"; import EditDataset from "../Create/EditDataset";
import ImportConfiguration from "./components/ImportConfiguration"; import ImportConfiguration from "./components/ImportConfiguration";
import CardView from "@/components/CardView";
const SIMILAR_DATASET_LIMIT = 4;
export default function DatasetDetail() {
const { id } = useParams(); // 获取动态路由参数
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("overview");
const { message } = App.useApp();
const [showEditDialog, setShowEditDialog] = useState(false);
const [dataset, setDataset] = useState<Dataset>({} as Dataset);
const [parentDataset, setParentDataset] = useState<Dataset | null>(null);
const [childDatasets, setChildDatasets] = useState<Dataset[]>([]);
const [childDatasetsLoading, setChildDatasetsLoading] = useState(false);
const [similarDatasets, setSimilarDatasets] = useState<Dataset[]>([]);
const [similarDatasetsLoading, setSimilarDatasetsLoading] = useState(false);
const [similarTagNames, setSimilarTagNames] = useState<string[]>([]);
const similarRequestRef = useRef(0);
const filesOperation = useFilesOperation(dataset);
const [showUploadDialog, setShowUploadDialog] = useState(false); const SIMILAR_DATASET_LIMIT = 4;
const normalizeTagNames = ( const SIMILAR_TAGS_PREVIEW_LIMIT = 3;
tags?: Array<string | { name?: string | null } | null>
) => {
if (!tags || tags.length === 0) {
return [];
}
const names = tags
.map((tag) => (typeof tag === "string" ? tag : tag?.name))
.filter((name): name is string => !!name && name.trim().length > 0)
.map((name) => name.trim());
return Array.from(new Set(names));
};
const fetchSimilarDatasets = async (currentDataset: Dataset) => {
const requestId = similarRequestRef.current + 1;
similarRequestRef.current = requestId;
if (!currentDataset?.id) {
setSimilarDatasets([]);
setSimilarTagNames([]);
setSimilarDatasetsLoading(false);
return;
}
const tagNames = normalizeTagNames(
currentDataset.tags as Array<string | { name?: string }>
);
setSimilarTagNames(tagNames);
setSimilarDatasets([]);
if (tagNames.length === 0) {
setSimilarDatasetsLoading(false);
return;
}
setSimilarDatasetsLoading(true);
try {
const { data } = await querySimilarDatasetsUsingGet(currentDataset.id, {
limit: SIMILAR_DATASET_LIMIT,
});
if (similarRequestRef.current !== requestId) {
return;
}
const list = Array.isArray(data) ? data : [];
setSimilarDatasets(list.map((item) => mapDataset(item)));
} catch (error) {
console.error("Failed to fetch similar datasets:", error);
} finally {
if (similarRequestRef.current === requestId) {
setSimilarDatasetsLoading(false);
}
}
};
const navigateItems = useMemo(() => {
const items = [
{
title: <Link to="/data/management"></Link>,
},
];
if (parentDataset) {
items.push({
title: (
<Link to={`/data/management/detail/${parentDataset.id}`}>
{parentDataset.name}
</Link>
),
});
}
items.push({
title: dataset.name || "数据集详情",
});
return items;
}, [dataset, parentDataset]);
const tabList = useMemo(() => {
const items = [
{
key: "overview",
label: "概览",
},
];
if (!dataset?.parentDatasetId) {
items.push({
key: "children",
label: "关联数据集",
});
}
return items;
}, [dataset?.parentDatasetId]);
const handleCreateChildDataset = () => {
if (!dataset?.id) {
return;
}
navigate("/data/management/create", {
state: { parentDatasetId: dataset.id },
});
};
const fetchChildDatasets = async (parentId?: string) => {
if (!parentId) {
setChildDatasets([]);
return;
}
setChildDatasetsLoading(true);
try {
const { data: res } = await queryDatasetsUsingGet({
parentDatasetId: parentId,
page: 1,
size: 1000,
});
const list = res?.content || res?.data || [];
setChildDatasets(list.map((item) => mapDataset(item)));
} finally {
setChildDatasetsLoading(false);
}
};
const fetchDataset = async () => {
if (!id) {
return;
}
const { data } = await queryDatasetByIdUsingGet(id);
const mapped = mapDataset(data);
setDataset(mapped);
fetchSimilarDatasets(mapped);
if (data?.parentDatasetId) {
const { data: parentData } = await queryDatasetByIdUsingGet(
data.parentDatasetId
);
setParentDataset(mapDataset(parentData));
setChildDatasets([]);
} else {
setParentDataset(null);
await fetchChildDatasets(data?.id);
}
};
useEffect(() => { export default function DatasetDetail() {
if (!id) { const { id } = useParams(); // 获取动态路由参数
return; const navigate = useNavigate();
} const [activeTab, setActiveTab] = useState("overview");
fetchDataset(); const { message } = App.useApp();
filesOperation.fetchFiles("", 1, 10); // 从根目录开始,第一页 const [showEditDialog, setShowEditDialog] = useState(false);
}, [id]);
useEffect(() => { const [dataset, setDataset] = useState<Dataset>({} as Dataset);
if (dataset?.parentDatasetId && activeTab === "children") { const [parentDataset, setParentDataset] = useState<Dataset | null>(null);
setActiveTab("overview"); const [childDatasets, setChildDatasets] = useState<Dataset[]>([]);
} const [childDatasetsLoading, setChildDatasetsLoading] = useState(false);
}, [activeTab, dataset?.parentDatasetId]); const [similarDatasets, setSimilarDatasets] = useState<Dataset[]>([]);
const [similarDatasetsLoading, setSimilarDatasetsLoading] = useState(false);
const [similarTagNames, setSimilarTagNames] = useState<string[]>([]);
const similarRequestRef = useRef(0);
const filesOperation = useFilesOperation(dataset);
const [showUploadDialog, setShowUploadDialog] = useState(false);
const normalizeTagNames = (
tags?: Array<string | { name?: string | null } | null>
) => {
if (!tags || tags.length === 0) {
return [];
}
const names = tags
.map((tag) => (typeof tag === "string" ? tag : tag?.name))
.filter((name): name is string => !!name && name.trim().length > 0)
.map((name) => name.trim());
return Array.from(new Set(names));
};
const fetchSimilarDatasets = async (currentDataset: Dataset) => {
const requestId = similarRequestRef.current + 1;
similarRequestRef.current = requestId;
if (!currentDataset?.id) {
setSimilarDatasets([]);
setSimilarTagNames([]);
setSimilarDatasetsLoading(false);
return;
}
const tagNames = normalizeTagNames(
currentDataset.tags as Array<string | { name?: string }>
);
setSimilarTagNames(tagNames);
setSimilarDatasets([]);
if (tagNames.length === 0) {
setSimilarDatasetsLoading(false);
return;
}
setSimilarDatasetsLoading(true);
try {
const { data } = await querySimilarDatasetsUsingGet(currentDataset.id, {
limit: SIMILAR_DATASET_LIMIT,
});
if (similarRequestRef.current !== requestId) {
return;
}
const list = Array.isArray(data) ? data : [];
setSimilarDatasets(list.map((item) => mapDataset(item)));
} catch (error) {
console.error("Failed to fetch similar datasets:", error);
} finally {
if (similarRequestRef.current === requestId) {
setSimilarDatasetsLoading(false);
}
}
};
const similarTagsSummary = useMemo(() => {
if (!similarTagNames || similarTagNames.length === 0) {
return "";
}
const visibleTags = similarTagNames.slice(0, SIMILAR_TAGS_PREVIEW_LIMIT);
const hiddenCount = similarTagNames.length - visibleTags.length;
if (hiddenCount > 0) {
return `${visibleTags.join("、")}${similarTagNames.length}`;
}
return visibleTags.join("、");
}, [similarTagNames]);
const navigateItems = useMemo(() => {
const items = [
{
title: <Link to="/data/management"></Link>,
},
];
if (parentDataset) {
items.push({
title: (
<Link to={`/data/management/detail/${parentDataset.id}`}>
{parentDataset.name}
</Link>
),
});
}
items.push({
title: dataset.name || "数据集详情",
});
return items;
}, [dataset, parentDataset]);
const tabList = useMemo(() => {
const items = [
{
key: "overview",
label: "概览",
},
];
if (!dataset?.parentDatasetId) {
items.push({
key: "children",
label: "关联数据集",
});
}
return items;
}, [dataset?.parentDatasetId]);
const handleCreateChildDataset = () => {
if (!dataset?.id) {
return;
}
navigate("/data/management/create", {
state: { parentDatasetId: dataset.id },
});
};
const fetchChildDatasets = async (parentId?: string) => {
if (!parentId) {
setChildDatasets([]);
return;
}
setChildDatasetsLoading(true);
try {
const { data: res } = await queryDatasetsUsingGet({
parentDatasetId: parentId,
page: 1,
size: 1000,
});
const list = res?.content || res?.data || [];
setChildDatasets(list.map((item) => mapDataset(item)));
} finally {
setChildDatasetsLoading(false);
}
};
const fetchDataset = async () => {
if (!id) {
return;
}
const { data } = await queryDatasetByIdUsingGet(id);
const mapped = mapDataset(data);
setDataset(mapped);
fetchSimilarDatasets(mapped);
if (data?.parentDatasetId) {
const { data: parentData } = await queryDatasetByIdUsingGet(
data.parentDatasetId
);
setParentDataset(mapDataset(parentData));
setChildDatasets([]);
} else {
setParentDataset(null);
await fetchChildDatasets(data?.id);
}
};
useEffect(() => {
if (!id) {
return;
}
fetchDataset();
filesOperation.fetchFiles("", 1, 10); // 从根目录开始,第一页
}, [id]);
useEffect(() => {
if (dataset?.parentDatasetId && activeTab === "children") {
setActiveTab("overview");
}
}, [activeTab, dataset?.parentDatasetId]);
const handleRefresh = async (showMessage = true, prefixOverride?: string) => { const handleRefresh = async (showMessage = true, prefixOverride?: string) => {
fetchDataset(); fetchDataset();
@@ -261,22 +276,22 @@ export default function DatasetDetail() {
]; ];
// 数据集操作列表 // 数据集操作列表
const operations = [ const operations = [
...(dataset?.id && !dataset.parentDatasetId ...(dataset?.id && !dataset.parentDatasetId
? [ ? [
{ {
key: "create-child", key: "create-child",
label: "创建关联数据集", label: "创建关联数据集",
icon: <PlusOutlined />, icon: <PlusOutlined />,
onClick: handleCreateChildDataset, onClick: handleCreateChildDataset,
}, },
] ]
: []), : []),
{ {
key: "edit", key: "edit",
label: "编辑", label: "编辑",
icon: <EditOutlined />, icon: <EditOutlined />,
onClick: () => { onClick: () => {
setShowEditDialog(true); setShowEditDialog(true);
}, },
}, },
@@ -314,55 +329,55 @@ export default function DatasetDetail() {
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
onClick: handleDeleteDataset, onClick: handleDeleteDataset,
}, },
]; ];
const childColumns = [ const childColumns = [
{ {
title: "名称", title: "名称",
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
render: (_: string, record: Dataset) => ( render: (_: string, record: Dataset) => (
<Link to={`/data/management/detail/${record.id}`}>{record.name}</Link> <Link to={`/data/management/detail/${record.id}`}>{record.name}</Link>
), ),
}, },
{ {
title: "类型", title: "类型",
dataIndex: "datasetType", dataIndex: "datasetType",
key: "datasetType", key: "datasetType",
width: 120, width: 120,
render: (value: string) => datasetTypeMap[value]?.label || "未知", render: (value: string) => datasetTypeMap[value]?.label || "未知",
}, },
{ {
title: "状态", title: "状态",
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
width: 120, width: 120,
render: (status) => render: (status) =>
status ? <Tag color={status.color}>{status.label}</Tag> : "-", status ? <Tag color={status.color}>{status.label}</Tag> : "-",
}, },
{ {
title: "文件数", title: "文件数",
dataIndex: "fileCount", dataIndex: "fileCount",
key: "fileCount", key: "fileCount",
width: 120, width: 120,
render: (value?: number) => value ?? 0, render: (value?: number) => value ?? 0,
}, },
{ {
title: "大小", title: "大小",
dataIndex: "size", dataIndex: "size",
key: "size", key: "size",
width: 140, width: 140,
render: (value?: string) => value || "0 B", render: (value?: string) => value || "0 B",
}, },
{ {
title: "更新时间", title: "更新时间",
dataIndex: "updatedAt", dataIndex: "updatedAt",
key: "updatedAt", key: "updatedAt",
width: 180, width: 180,
}, },
]; ];
return ( return (
<div className="h-full flex flex-col gap-4"> <div className="h-full flex flex-col gap-4 overflow-hidden">
<Breadcrumb items={navigateItems} /> <Breadcrumb items={navigateItems} />
{/* Header */} {/* Header */}
<DetailHeader <DetailHeader
@@ -398,42 +413,67 @@ export default function DatasetDetail() {
}, },
}} }}
/> />
<div className="flex-overflow-auto p-6 pt-2 bg-white rounded-md shadow"> <div className="flex-1 overflow-auto">
<Tabs activeKey={activeTab} items={tabList} onChange={setActiveTab} /> <div className="p-6 pt-2 bg-white rounded-md shadow mb-4">
<div className="h-full overflow-auto"> <Tabs activeKey={activeTab} items={tabList} onChange={setActiveTab} />
{activeTab === "overview" && ( <div className="">
<Overview {activeTab === "overview" && (
dataset={dataset} <Overview
filesOperation={filesOperation} dataset={dataset}
fetchDataset={fetchDataset} filesOperation={filesOperation}
onUpload={() => setShowUploadDialog(true)} fetchDataset={fetchDataset}
similarDatasets={similarDatasets} onUpload={() => setShowUploadDialog(true)}
similarDatasetsLoading={similarDatasetsLoading} />
similarTags={similarTagNames} )}
/> {activeTab === "children" && (
)} <div className="pt-4">
{activeTab === "children" && ( <div className="flex items-center justify-between mb-3">
<div className="pt-4"> <h2 className="text-base font-semibold"></h2>
<div className="flex items-center justify-between mb-3"> <span className="text-xs text-gray-500">
<h2 className="text-base font-semibold"></h2> {childDatasets.length}
<span className="text-xs text-gray-500"> </span>
{childDatasets.length} </div>
</span> <Table
</div> rowKey="id"
<Table columns={childColumns}
rowKey="id" dataSource={childDatasets}
columns={childColumns} loading={childDatasetsLoading}
dataSource={childDatasets} pagination={false}
loading={childDatasetsLoading} locale={{ emptyText: "暂无关联数据集" }}
pagination={false} />
locale={{ emptyText: "暂无关联数据集" }} </div>
/> )}
</div> {activeTab === "lineage" && <DataLineageFlow dataset={dataset} />}
)} {activeTab === "quality" && <DataQuality />}
{activeTab === "lineage" && <DataLineageFlow dataset={dataset} />} </div>
{activeTab === "quality" && <DataQuality />} </div>
</div>
</div> {/* 相似数据集 */}
<div className="bg-white rounded-md shadow p-6 mb-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-base font-semibold"></h2>
{similarTagsSummary && (
<span className="text-xs text-gray-500">
{similarTagsSummary}
</span>
)}
</div>
<CardView
data={similarDatasets}
loading={similarDatasetsLoading}
operations={[]}
pagination={{
current: 1,
pageSize: similarDatasets.length || 10,
total: similarDatasets.length || 0,
style: { display: "none" },
}}
onView={(item) => {
navigate(`/data/management/detail/${item.id}`);
}}
/>
</div>
</div>
<ImportConfiguration <ImportConfiguration
data={dataset} data={dataset}
open={showUploadDialog} open={showUploadDialog}

View File

@@ -1,155 +1,69 @@
import { import {
App, App,
Button, Button,
Descriptions, Descriptions,
DescriptionsProps, DescriptionsProps,
Modal, Modal,
Table, Table,
Input, Input,
Tag, } from "antd";
} from "antd"; import { formatBytes, formatDateTime } from "@/utils/unit";
import { formatBytes, formatDateTime } from "@/utils/unit"; import { Download, Trash2, Folder, File } from "lucide-react";
import { Download, Trash2, Folder, File } from "lucide-react"; import { datasetTypeMap } from "../../dataset.const";
import { datasetTypeMap } from "../../dataset.const"; import type { Dataset, DatasetFile } from "@/pages/DataManagement/dataset.model";
import type { Dataset, DatasetFile } from "@/pages/DataManagement/dataset.model"; import type { useFilesOperation } from "../useFilesOperation";
import { Link } from "react-router";
import type { useFilesOperation } from "../useFilesOperation";
type DatasetFileRow = DatasetFile & {
fileSize?: number;
fileCount?: number;
uploadTime?: string;
};
const PREVIEW_MAX_HEIGHT = 500;
const PREVIEW_MODAL_WIDTH = {
text: 800,
media: 700,
};
const PREVIEW_TEXT_FONT_SIZE = 12;
const PREVIEW_TEXT_PADDING = 12;
const PREVIEW_AUDIO_PADDING = 40;
const SIMILAR_TAGS_PREVIEW_LIMIT = 3;
const SIMILAR_DATASET_TAG_PREVIEW_LIMIT = 4;
type OverviewProps = {
dataset: Dataset;
filesOperation: ReturnType<typeof useFilesOperation>;
fetchDataset: () => void;
onUpload?: () => void;
similarDatasets: Dataset[];
similarDatasetsLoading: boolean;
similarTags: string[];
};
export default function Overview({
dataset,
filesOperation,
fetchDataset,
onUpload,
similarDatasets,
similarDatasetsLoading,
similarTags,
}: OverviewProps) {
const { modal, message } = App.useApp();
const {
fileList,
pagination,
selectedFiles,
previewVisible,
previewFileName,
previewContent,
previewFileType,
previewMediaUrl,
previewLoading,
closePreview,
handleDeleteFile,
handleDownloadFile,
handleBatchDeleteFiles,
handleBatchExport,
handleCreateDirectory,
handleDownloadDirectory,
handleDeleteDirectory,
handlePreviewFile,
} = filesOperation;
const similarTagsSummary = (() => {
if (!similarTags || similarTags.length === 0) {
return "";
}
const visibleTags = similarTags.slice(0, SIMILAR_TAGS_PREVIEW_LIMIT);
const hiddenCount = similarTags.length - visibleTags.length;
if (hiddenCount > 0) {
return `${visibleTags.join("、")}${similarTags.length}`;
}
return visibleTags.join("、");
})();
const renderDatasetTags = (
tags?: Array<string | { name?: string; color?: string } | null>
) => {
if (!tags || tags.length === 0) {
return "-";
}
const visibleTags = tags.slice(0, SIMILAR_DATASET_TAG_PREVIEW_LIMIT);
const hiddenCount = tags.length - visibleTags.length;
return (
<div className="flex flex-wrap gap-1">
{visibleTags.map((tag, index) => {
const tagName = typeof tag === "string" ? tag : tag?.name;
if (!tagName) {
return null;
}
const tagColor = typeof tag === "string" ? undefined : tag?.color;
return (
<Tag key={`${tagName}-${index}`} color={tagColor}>
{tagName}
</Tag>
);
})}
{hiddenCount > 0 && <Tag>+{hiddenCount}</Tag>}
</div>
);
};
const similarColumns = [
{
title: "名称",
dataIndex: "name",
key: "name",
render: (_: string, record: Dataset) => (
<Link to={`/data/management/detail/${record.id}`}>{record.name}</Link>
),
},
{
title: "标签",
dataIndex: "tags",
key: "tags",
render: (tags: Array<string | { name?: string; color?: string }>) =>
renderDatasetTags(tags),
},
{
title: "类型",
dataIndex: "datasetType",
key: "datasetType",
width: 120,
render: (_: string, record: Dataset) =>
datasetTypeMap[record.datasetType as keyof typeof datasetTypeMap]?.label ||
"未知",
},
{
title: "文件数",
dataIndex: "fileCount",
key: "fileCount",
width: 120,
render: (value?: number) => value ?? 0,
},
{
title: "更新时间",
dataIndex: "updatedAt",
key: "updatedAt",
width: 180,
},
];
// 基本信息 type DatasetFileRow = DatasetFile & {
fileSize?: number;
fileCount?: number;
uploadTime?: string;
};
const PREVIEW_MAX_HEIGHT = 500;
const PREVIEW_MODAL_WIDTH = {
text: 800,
media: 700,
};
const PREVIEW_TEXT_FONT_SIZE = 12;
const PREVIEW_TEXT_PADDING = 12;
const PREVIEW_AUDIO_PADDING = 40;
type OverviewProps = {
dataset: Dataset;
filesOperation: ReturnType<typeof useFilesOperation>;
fetchDataset: () => void;
onUpload?: () => void;
};
export default function Overview({
dataset,
filesOperation,
fetchDataset,
onUpload,
}: OverviewProps) {
const { modal, message } = App.useApp();
const {
fileList,
pagination,
selectedFiles,
previewVisible,
previewFileName,
previewContent,
previewFileType,
previewMediaUrl,
previewLoading,
closePreview,
handleDeleteFile,
handleDownloadFile,
handleBatchDeleteFiles,
handleBatchExport,
handleCreateDirectory,
handleDownloadDirectory,
handleDeleteDirectory,
handlePreviewFile,
} = filesOperation;
// 基本信息
const items: DescriptionsProps["items"] = [ const items: DescriptionsProps["items"] = [
{ {
key: "id", key: "id",
@@ -211,7 +125,7 @@ export default function Overview({
dataIndex: "fileName", dataIndex: "fileName",
key: "fileName", key: "fileName",
fixed: "left", fixed: "left",
render: (text: string, record: DatasetFileRow) => { render: (text: string, record: DatasetFileRow) => {
const isDirectory = record.id.startsWith('directory-'); const isDirectory = record.id.startsWith('directory-');
const iconSize = 16; const iconSize = 16;
@@ -230,35 +144,35 @@ export default function Overview({
return ( return (
<Button <Button
type="link" type="link"
onClick={() => { onClick={() => {
const currentPath = filesOperation.pagination.prefix || ''; const currentPath = filesOperation.pagination.prefix || '';
// 文件夹路径必须以斜杠结尾 // 文件夹路径必须以斜杠结尾
const newPath = `${currentPath}${record.fileName}/`; const newPath = `${currentPath}${record.fileName}/`;
filesOperation.fetchFiles(newPath, 1, filesOperation.pagination.pageSize); filesOperation.fetchFiles(newPath, 1, filesOperation.pagination.pageSize);
}} }}
> >
{content} {content}
</Button> </Button>
); );
} }
return ( return (
<Button <Button
type="link" type="link"
loading={previewLoading && previewFileName === record.fileName} loading={previewLoading && previewFileName === record.fileName}
onClick={() => handlePreviewFile(record)} onClick={() => handlePreviewFile(record)}
> >
{content} {content}
</Button> </Button>
); );
}, },
}, },
{ {
title: "大小", title: "大小",
dataIndex: "fileSize", dataIndex: "fileSize",
key: "fileSize", key: "fileSize",
width: 150, width: 150,
render: (text: number, record: DatasetFileRow) => { render: (text: number, record: DatasetFileRow) => {
const isDirectory = record.id.startsWith('directory-'); const isDirectory = record.id.startsWith('directory-');
if (isDirectory) { if (isDirectory) {
return formatBytes(record.fileSize || 0); return formatBytes(record.fileSize || 0);
@@ -271,7 +185,7 @@ export default function Overview({
dataIndex: "fileCount", dataIndex: "fileCount",
key: "fileCount", key: "fileCount",
width: 120, width: 120,
render: (text: number, record: DatasetFileRow) => { render: (text: number, record: DatasetFileRow) => {
const isDirectory = record.id.startsWith('directory-'); const isDirectory = record.id.startsWith('directory-');
if (!isDirectory) { if (!isDirectory) {
return "-"; return "-";
@@ -291,7 +205,7 @@ export default function Overview({
key: "action", key: "action",
width: 180, width: 180,
fixed: "right", fixed: "right",
render: (_, record: DatasetFileRow) => { render: (_, record: DatasetFileRow) => {
const isDirectory = record.id.startsWith('directory-'); const isDirectory = record.id.startsWith('directory-');
if (isDirectory) { if (isDirectory) {
@@ -330,21 +244,21 @@ export default function Overview({
); );
} }
return ( return (
<div className="flex"> <div className="flex">
<Button <Button
size="small" size="small"
type="link" type="link"
loading={previewLoading && previewFileName === record.fileName} loading={previewLoading && previewFileName === record.fileName}
onClick={() => handlePreviewFile(record)} onClick={() => handlePreviewFile(record)}
> >
</Button> </Button>
<Button <Button
size="small" size="small"
type="link" type="link"
onClick={() => handleDownloadFile(record)} onClick={() => handleDownloadFile(record)}
> >
</Button> </Button>
<Button <Button
@@ -375,70 +289,45 @@ export default function Overview({
column={5} column={5}
/> />
{/* 相似数据集 */} {/* 文件列表 */}
<div className="mt-8"> <div className="flex items-center justify-between mt-8 mb-2">
<div className="flex items-center justify-between mb-3"> <h2 className="text-base font-semibold"></h2>
<h2 className="text-base font-semibold"></h2> <div className="flex items-center gap-2">
{similarTagsSummary && ( <Button size="small" onClick={() => onUpload?.()}>
<span className="text-xs text-gray-500">
{similarTagsSummary} </Button>
</span> <Button
)} type="primary"
</div> size="small"
<Table onClick={() => {
size="small" let dirName = "";
rowKey="id" modal.confirm({
columns={similarColumns} title: "新建文件夹",
dataSource={similarDatasets} content: (
loading={similarDatasetsLoading} <Input
pagination={false} autoFocus
locale={{ placeholder="请输入文件夹名称"
emptyText: similarTags?.length onChange={(e) => {
? "暂无相似数据集" dirName = e.target.value?.trim();
: "当前数据集未设置标签", }}
}} />
/> ),
</div> okText: "确定",
cancelText: "取消",
{/* 文件列表 */} onOk: async () => {
<div className="flex items-center justify-between mt-8 mb-2"> if (!dirName) {
<h2 className="text-base font-semibold"></h2> message.warning("请输入文件夹名称");
<div className="flex items-center gap-2"> return Promise.reject();
<Button size="small" onClick={() => onUpload?.()}> }
await handleCreateDirectory(dirName);
</Button> },
<Button });
type="primary" }}
size="small" >
onClick={() => {
let dirName = ""; </Button>
modal.confirm({ </div>
title: "新建文件夹", </div>
content: (
<Input
autoFocus
placeholder="请输入文件夹名称"
onChange={(e) => {
dirName = e.target.value?.trim();
}}
/>
),
okText: "确定",
cancelText: "取消",
onOk: async () => {
if (!dirName) {
message.warning("请输入文件夹名称");
return Promise.reject();
}
await handleCreateDirectory(dirName);
},
});
}}
>
</Button>
</div>
</div>
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<div className="flex items-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200"> <div className="flex items-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200">
<span className="text-sm text-blue-700 font-medium"> <span className="text-sm text-blue-700 font-medium">
@@ -519,70 +408,70 @@ export default function Overview({
/> />
</div> </div>
</div> </div>
{/* 文件预览弹窗 */} {/* 文件预览弹窗 */}
<Modal <Modal
title={`文件预览:${previewFileName}`} title={`文件预览:${previewFileName}`}
open={previewVisible} open={previewVisible}
onCancel={closePreview} onCancel={closePreview}
footer={[ footer={[
<Button key="close" onClick={closePreview}> <Button key="close" onClick={closePreview}>
</Button>, </Button>,
]} ]}
width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media} width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media}
> >
{previewFileType === "text" && ( {previewFileType === "text" && (
<pre <pre
style={{ style={{
maxHeight: `${PREVIEW_MAX_HEIGHT}px`, maxHeight: `${PREVIEW_MAX_HEIGHT}px`,
overflow: "auto", overflow: "auto",
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
wordBreak: "break-all", wordBreak: "break-all",
fontSize: PREVIEW_TEXT_FONT_SIZE, fontSize: PREVIEW_TEXT_FONT_SIZE,
color: "#222", color: "#222",
backgroundColor: "#f5f5f5", backgroundColor: "#f5f5f5",
padding: `${PREVIEW_TEXT_PADDING}px`, padding: `${PREVIEW_TEXT_PADDING}px`,
borderRadius: "4px", borderRadius: "4px",
}} }}
> >
{previewContent} {previewContent}
</pre> </pre>
)} )}
{previewFileType === "image" && ( {previewFileType === "image" && (
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<img <img
src={previewMediaUrl} src={previewMediaUrl}
alt={previewFileName} alt={previewFileName}
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px`, objectFit: "contain" }} style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px`, objectFit: "contain" }}
/> />
</div> </div>
)} )}
{previewFileType === "pdf" && ( {previewFileType === "pdf" && (
<iframe <iframe
src={previewMediaUrl} src={previewMediaUrl}
title={previewFileName || "PDF 预览"} title={previewFileName || "PDF 预览"}
style={{ width: "100%", height: `${PREVIEW_MAX_HEIGHT}px`, border: "none" }} style={{ width: "100%", height: `${PREVIEW_MAX_HEIGHT}px`, border: "none" }}
/> />
)} )}
{previewFileType === "video" && ( {previewFileType === "video" && (
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<video <video
src={previewMediaUrl} src={previewMediaUrl}
controls controls
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px` }} style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px` }}
> >
</video> </video>
</div> </div>
)} )}
{previewFileType === "audio" && ( {previewFileType === "audio" && (
<div style={{ textAlign: "center", padding: `${PREVIEW_AUDIO_PADDING}px 0` }}> <div style={{ textAlign: "center", padding: `${PREVIEW_AUDIO_PADDING}px 0` }}>
<audio src={previewMediaUrl} controls style={{ width: "100%" }}> <audio src={previewMediaUrl} controls style={{ width: "100%" }}>
</audio> </audio>
</div> </div>
)} )}
</Modal> </Modal>
</> </>
); );
} }