You've already forked DataMate
refactor(DataManagement): 移除相似数据集表格并改用卡片视图显示
- 移除了 Overview 组件中的相似数据集表格相关代码 - 移除了 Tag 组件和相关依赖的导入 - 在 DatasetDetail 中添加 CardView 组件用于显示相似数据集 - 将相似数据集的展示从表格改为卡片布局 - 移除了 Overview 组件中的相似数据集参数传递 - 更新了页面布局以
This commit is contained in:
@@ -1,155 +1,69 @@
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsProps,
|
||||
Modal,
|
||||
Table,
|
||||
Input,
|
||||
Tag,
|
||||
} from "antd";
|
||||
import { formatBytes, formatDateTime } from "@/utils/unit";
|
||||
import { Download, Trash2, Folder, File } from "lucide-react";
|
||||
import { datasetTypeMap } from "../../dataset.const";
|
||||
import type { Dataset, DatasetFile } from "@/pages/DataManagement/dataset.model";
|
||||
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,
|
||||
},
|
||||
];
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsProps,
|
||||
Modal,
|
||||
Table,
|
||||
Input,
|
||||
} from "antd";
|
||||
import { formatBytes, formatDateTime } from "@/utils/unit";
|
||||
import { Download, Trash2, Folder, File } from "lucide-react";
|
||||
import { datasetTypeMap } from "../../dataset.const";
|
||||
import type { Dataset, DatasetFile } from "@/pages/DataManagement/dataset.model";
|
||||
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;
|
||||
|
||||
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"] = [
|
||||
{
|
||||
key: "id",
|
||||
@@ -211,7 +125,7 @@ export default function Overview({
|
||||
dataIndex: "fileName",
|
||||
key: "fileName",
|
||||
fixed: "left",
|
||||
render: (text: string, record: DatasetFileRow) => {
|
||||
render: (text: string, record: DatasetFileRow) => {
|
||||
const isDirectory = record.id.startsWith('directory-');
|
||||
const iconSize = 16;
|
||||
|
||||
@@ -230,35 +144,35 @@ export default function Overview({
|
||||
return (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => {
|
||||
const currentPath = filesOperation.pagination.prefix || '';
|
||||
// 文件夹路径必须以斜杠结尾
|
||||
const newPath = `${currentPath}${record.fileName}/`;
|
||||
filesOperation.fetchFiles(newPath, 1, filesOperation.pagination.pageSize);
|
||||
}}
|
||||
>
|
||||
onClick={() => {
|
||||
const currentPath = filesOperation.pagination.prefix || '';
|
||||
// 文件夹路径必须以斜杠结尾
|
||||
const newPath = `${currentPath}${record.fileName}/`;
|
||||
filesOperation.fetchFiles(newPath, 1, filesOperation.pagination.pageSize);
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="link"
|
||||
loading={previewLoading && previewFileName === record.fileName}
|
||||
onClick={() => handlePreviewFile(record)}
|
||||
>
|
||||
{content}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
return (
|
||||
<Button
|
||||
type="link"
|
||||
loading={previewLoading && previewFileName === record.fileName}
|
||||
onClick={() => handlePreviewFile(record)}
|
||||
>
|
||||
{content}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "大小",
|
||||
dataIndex: "fileSize",
|
||||
key: "fileSize",
|
||||
width: 150,
|
||||
render: (text: number, record: DatasetFileRow) => {
|
||||
render: (text: number, record: DatasetFileRow) => {
|
||||
const isDirectory = record.id.startsWith('directory-');
|
||||
if (isDirectory) {
|
||||
return formatBytes(record.fileSize || 0);
|
||||
@@ -271,7 +185,7 @@ export default function Overview({
|
||||
dataIndex: "fileCount",
|
||||
key: "fileCount",
|
||||
width: 120,
|
||||
render: (text: number, record: DatasetFileRow) => {
|
||||
render: (text: number, record: DatasetFileRow) => {
|
||||
const isDirectory = record.id.startsWith('directory-');
|
||||
if (!isDirectory) {
|
||||
return "-";
|
||||
@@ -291,7 +205,7 @@ export default function Overview({
|
||||
key: "action",
|
||||
width: 180,
|
||||
fixed: "right",
|
||||
render: (_, record: DatasetFileRow) => {
|
||||
render: (_, record: DatasetFileRow) => {
|
||||
const isDirectory = record.id.startsWith('directory-');
|
||||
|
||||
if (isDirectory) {
|
||||
@@ -330,21 +244,21 @@ export default function Overview({
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
loading={previewLoading && previewFileName === record.fileName}
|
||||
onClick={() => handlePreviewFile(record)}
|
||||
>
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => handleDownloadFile(record)}
|
||||
>
|
||||
return (
|
||||
<div className="flex">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
loading={previewLoading && previewFileName === record.fileName}
|
||||
onClick={() => handlePreviewFile(record)}
|
||||
>
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => handleDownloadFile(record)}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
<Button
|
||||
@@ -375,70 +289,45 @@ export default function Overview({
|
||||
column={5}
|
||||
/>
|
||||
|
||||
{/* 相似数据集 */}
|
||||
<div className="mt-8">
|
||||
<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>
|
||||
<Table
|
||||
size="small"
|
||||
rowKey="id"
|
||||
columns={similarColumns}
|
||||
dataSource={similarDatasets}
|
||||
loading={similarDatasetsLoading}
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: similarTags?.length
|
||||
? "暂无相似数据集"
|
||||
: "当前数据集未设置标签",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 文件列表 */}
|
||||
<div className="flex items-center justify-between mt-8 mb-2">
|
||||
<h2 className="text-base font-semibold">文件列表</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="small" onClick={() => onUpload?.()}>
|
||||
文件上传
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
let dirName = "";
|
||||
modal.confirm({
|
||||
title: "新建文件夹",
|
||||
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>
|
||||
{/* 文件列表 */}
|
||||
<div className="flex items-center justify-between mt-8 mb-2">
|
||||
<h2 className="text-base font-semibold">文件列表</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="small" onClick={() => onUpload?.()}>
|
||||
文件上传
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
let dirName = "";
|
||||
modal.confirm({
|
||||
title: "新建文件夹",
|
||||
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 && (
|
||||
<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">
|
||||
@@ -519,70 +408,70 @@ export default function Overview({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 文件预览弹窗 */}
|
||||
<Modal
|
||||
title={`文件预览:${previewFileName}`}
|
||||
open={previewVisible}
|
||||
onCancel={closePreview}
|
||||
footer={[
|
||||
<Button key="close" onClick={closePreview}>
|
||||
关闭
|
||||
</Button>,
|
||||
]}
|
||||
width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media}
|
||||
>
|
||||
{previewFileType === "text" && (
|
||||
<pre
|
||||
style={{
|
||||
maxHeight: `${PREVIEW_MAX_HEIGHT}px`,
|
||||
overflow: "auto",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-all",
|
||||
fontSize: PREVIEW_TEXT_FONT_SIZE,
|
||||
color: "#222",
|
||||
backgroundColor: "#f5f5f5",
|
||||
padding: `${PREVIEW_TEXT_PADDING}px`,
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{previewContent}
|
||||
</pre>
|
||||
)}
|
||||
{previewFileType === "image" && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<img
|
||||
src={previewMediaUrl}
|
||||
alt={previewFileName}
|
||||
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px`, objectFit: "contain" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{previewFileType === "pdf" && (
|
||||
<iframe
|
||||
src={previewMediaUrl}
|
||||
title={previewFileName || "PDF 预览"}
|
||||
style={{ width: "100%", height: `${PREVIEW_MAX_HEIGHT}px`, border: "none" }}
|
||||
/>
|
||||
)}
|
||||
{previewFileType === "video" && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<video
|
||||
src={previewMediaUrl}
|
||||
controls
|
||||
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px` }}
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
)}
|
||||
{previewFileType === "audio" && (
|
||||
<div style={{ textAlign: "center", padding: `${PREVIEW_AUDIO_PADDING}px 0` }}>
|
||||
<audio src={previewMediaUrl} controls style={{ width: "100%" }}>
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
{/* 文件预览弹窗 */}
|
||||
<Modal
|
||||
title={`文件预览:${previewFileName}`}
|
||||
open={previewVisible}
|
||||
onCancel={closePreview}
|
||||
footer={[
|
||||
<Button key="close" onClick={closePreview}>
|
||||
关闭
|
||||
</Button>,
|
||||
]}
|
||||
width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media}
|
||||
>
|
||||
{previewFileType === "text" && (
|
||||
<pre
|
||||
style={{
|
||||
maxHeight: `${PREVIEW_MAX_HEIGHT}px`,
|
||||
overflow: "auto",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-all",
|
||||
fontSize: PREVIEW_TEXT_FONT_SIZE,
|
||||
color: "#222",
|
||||
backgroundColor: "#f5f5f5",
|
||||
padding: `${PREVIEW_TEXT_PADDING}px`,
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{previewContent}
|
||||
</pre>
|
||||
)}
|
||||
{previewFileType === "image" && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<img
|
||||
src={previewMediaUrl}
|
||||
alt={previewFileName}
|
||||
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px`, objectFit: "contain" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{previewFileType === "pdf" && (
|
||||
<iframe
|
||||
src={previewMediaUrl}
|
||||
title={previewFileName || "PDF 预览"}
|
||||
style={{ width: "100%", height: `${PREVIEW_MAX_HEIGHT}px`, border: "none" }}
|
||||
/>
|
||||
)}
|
||||
{previewFileType === "video" && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<video
|
||||
src={previewMediaUrl}
|
||||
controls
|
||||
style={{ maxWidth: "100%", maxHeight: `${PREVIEW_MAX_HEIGHT}px` }}
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
)}
|
||||
{previewFileType === "audio" && (
|
||||
<div style={{ textAlign: "center", padding: `${PREVIEW_AUDIO_PADDING}px 0` }}>
|
||||
<audio src={previewMediaUrl} controls style={{ width: "100%" }}>
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user