You've already forked DataMate
refactor(DataManagement): 移除相似数据集表格并改用卡片视图显示
- 移除了 Overview 组件中的相似数据集表格相关代码 - 移除了 Tag 组件和相关依赖的导入 - 在 DatasetDetail 中添加 CardView 组件用于显示相似数据集 - 将相似数据集的展示从表格改为卡片布局 - 移除了 Overview 组件中的相似数据集参数传递 - 更新了页面布局以
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user