From 6c1cead3d9762ae4307b778f159a8d60e0e91809 Mon Sep 17 00:00:00 2001 From: chengkai3 Date: Sun, 28 Jun 2026 14:37:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:[FL-207][=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=A7=86=E5=9B=BE=E6=A8=A1=E5=BC=8F-?= =?UTF-8?q?=E9=80=90=E7=BA=A7=E4=B8=8B=E9=92=BB=E5=AF=BC=E8=88=AA]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: multica-agent --- web/src/app/admin/atp-models/page.tsx | 339 +++++++++++++++++--------- 1 file changed, 228 insertions(+), 111 deletions(-) diff --git a/web/src/app/admin/atp-models/page.tsx b/web/src/app/admin/atp-models/page.tsx index 2194c04..c1ddbd6 100644 --- a/web/src/app/admin/atp-models/page.tsx +++ b/web/src/app/admin/atp-models/page.tsx @@ -16,15 +16,14 @@ import { Space, Spin, Table, - Tree, + Breadcrumb, Typography, Upload, message, type CardProps, } from "antd"; -import { UploadOutlined, FolderOutlined, FileOutlined, TableOutlined, ApartmentOutlined } from "@ant-design/icons"; +import { UploadOutlined, FolderOutlined, FileOutlined, TableOutlined, FolderOpenOutlined, HomeOutlined } from "@ant-design/icons"; import type { ColumnsType } from "antd/es/table"; -import type { DataNode } from "antd/es/tree"; import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ComponentType, type RefAttributes } from "react"; import { AdminPageLoading } from "@/components/admin-page-loading"; @@ -168,8 +167,8 @@ export default function AtpModelsPage() { const [tableScrollY, setTableScrollY] = useState(ATP_TABLE_MIN_SCROLL_Y); const tableScrollAnchorRef = useRef(null); const viewMode: "table" | "card" = isMobile ? "card" : "table"; - const [displayMode, setDisplayMode] = useState<"list" | "tree">("list"); - const [expandedKeys, setExpandedKeys] = useState([]); + const [displayMode, setDisplayMode] = useState<"list" | "file">("list"); + const [fileViewPath, setFileViewPath] = useState([]); const [pagination, setPagination] = useState({ current: 1, pageSize: 20 }); const [cardViewPage, setCardViewPage] = useState(1); const [allLoadedAssets, setAllLoadedAssets] = useState([]); @@ -523,101 +522,150 @@ export default function AtpModelsPage() { [canManage, deleteMutation], ); - const buildTreeData = useCallback(() => { - const treeMap = new Map(); + type FileViewItem = { + type: "folder" | "file"; + name: string; + displayName: string; + value: string; + item?: AtpAssetSummary; + }; - assetItems.forEach((item) => { - const voltage = item.voltage_level || "未分类"; - const tower = item.tower_type || "未分类"; - const scene = item.scene_type || "未分类"; - const arrester = item.arrester_config || "未分类"; + const getFileViewItems = useCallback((): FileViewItem[] => { + const currentLevel = fileViewPath.length; - const voltageKey = `voltage-${voltage}`; - const towerKey = `${voltageKey}-tower-${tower}`; - const sceneKey = `${towerKey}-scene-${scene}`; - const arresterKey = `${sceneKey}-arrester-${arrester}`; - const itemKey = `${arresterKey}-item-${item.id}`; + if (currentLevel === 0) { + const voltageSet = new Set(); + assetItems.forEach((item) => { + const voltage = item.voltage_level || "未分类"; + voltageSet.add(voltage); + }); + return Array.from(voltageSet) + .sort((a, b) => a.localeCompare(b, "zh-CN")) + .map((voltage) => ({ + type: "folder" as const, + name: voltage, + displayName: formatDimensionValue(voltage, DEFAULT_VOLTAGE_LEVELS), + value: voltage, + })); + } - if (!treeMap.has(voltageKey)) { - treeMap.set(voltageKey, { - key: voltageKey, - title: formatDimensionValue(voltage, DEFAULT_VOLTAGE_LEVELS), - icon: , - children: [], + if (currentLevel === 1) { + const voltage = fileViewPath[0]; + const towerSet = new Set(); + assetItems + .filter((item) => (item.voltage_level || "未分类") === voltage) + .forEach((item) => { + const tower = item.tower_type || "未分类"; + towerSet.add(tower); }); - } + return Array.from(towerSet) + .sort((a, b) => a.localeCompare(b, "zh-CN")) + .map((tower) => ({ + type: "folder" as const, + name: tower, + displayName: formatDimensionValue(tower, DEFAULT_TOWER_TYPES), + value: tower, + })); + } - const voltageNode = treeMap.get(voltageKey)!; - let towerNode = voltageNode.children?.find((n) => n.key === towerKey); - if (!towerNode) { - towerNode = { - key: towerKey, - title: formatDimensionValue(tower, DEFAULT_TOWER_TYPES), - icon: , - children: [], - }; - voltageNode.children = [...(voltageNode.children || []), towerNode]; - } + if (currentLevel === 2) { + const voltage = fileViewPath[0]; + const tower = fileViewPath[1]; + const sceneSet = new Set(); + assetItems + .filter( + (item) => + (item.voltage_level || "未分类") === voltage && + (item.tower_type || "未分类") === tower + ) + .forEach((item) => { + const scene = item.scene_type || "未分类"; + sceneSet.add(scene); + }); + return Array.from(sceneSet) + .sort((a, b) => a.localeCompare(b, "zh-CN")) + .map((scene) => ({ + type: "folder" as const, + name: scene, + displayName: formatDimensionValue(scene, DEFAULT_SCENE_TYPES), + value: scene, + })); + } - let sceneNode = towerNode.children?.find((n) => n.key === sceneKey); - if (!sceneNode) { - sceneNode = { - key: sceneKey, - title: formatDimensionValue(scene, DEFAULT_SCENE_TYPES), - icon: , - children: [], - }; - towerNode.children = [...(towerNode.children || []), sceneNode]; - } + if (currentLevel === 3) { + const voltage = fileViewPath[0]; + const tower = fileViewPath[1]; + const scene = fileViewPath[2]; + const arresterSet = new Set(); + assetItems + .filter( + (item) => + (item.voltage_level || "未分类") === voltage && + (item.tower_type || "未分类") === tower && + (item.scene_type || "未分类") === scene + ) + .forEach((item) => { + const arrester = item.arrester_config || "未分类"; + arresterSet.add(arrester); + }); + return Array.from(arresterSet) + .sort((a, b) => a.localeCompare(b, "zh-CN")) + .map((arrester) => ({ + type: "folder" as const, + name: arrester, + displayName: formatDimensionValue(arrester, DEFAULT_ARRESTER_CONFIGS), + value: arrester, + })); + } - let arresterNode = sceneNode.children?.find((n) => n.key === arresterKey); - if (!arresterNode) { - arresterNode = { - key: arresterKey, - title: formatDimensionValue(arrester, DEFAULT_ARRESTER_CONFIGS), - icon: , - children: [], - }; - sceneNode.children = [...(sceneNode.children || []), arresterNode]; - } + if (currentLevel === 4) { + const voltage = fileViewPath[0]; + const tower = fileViewPath[1]; + const scene = fileViewPath[2]; + const arrester = fileViewPath[3]; + return assetItems + .filter( + (item) => + (item.voltage_level || "未分类") === voltage && + (item.tower_type || "未分类") === tower && + (item.scene_type || "未分类") === scene && + (item.arrester_config || "未分类") === arrester + ) + .map((item) => ({ + type: "file" as const, + name: item.name, + displayName: item.name, + value: item.id, + item, + })); + } - const deleteLoading = deleteMutation.isPending; - const itemNode: DataNode = { - key: itemKey, - title: ( -
- {item.name} - - - {formatDateTime(item.update_date)} - - deleteMutation.mutate(item.id)} - disabled={!canManage || deleteLoading} - > - - - -
- ), - icon: , - isLeaf: true, - }; + return []; + }, [assetItems, fileViewPath]); - arresterNode.children = [...(arresterNode.children || []), itemNode]; - }); + const fileViewItems = useMemo(() => getFileViewItems(), [getFileViewItems]); - return Array.from(treeMap.values()); - }, [assetItems, canManage, deleteMutation]); + const handleFileViewItemClick = (item: FileViewItem) => { + if (item.type === "folder") { + setFileViewPath([...fileViewPath, item.value]); + } + }; - const treeData = useMemo(() => buildTreeData(), [buildTreeData]); + const handleBreadcrumbClick = (index: number) => { + if (index === -1) { + setFileViewPath([]); + } else { + setFileViewPath(fileViewPath.slice(0, index + 1)); + } + }; + + const getBreadcrumbLabel = (index: number): string => { + if (index === 0) return formatDimensionValue(fileViewPath[0], DEFAULT_VOLTAGE_LEVELS); + if (index === 1) return formatDimensionValue(fileViewPath[1], DEFAULT_TOWER_TYPES); + if (index === 2) return formatDimensionValue(fileViewPath[2], DEFAULT_SCENE_TYPES); + if (index === 3) return formatDimensionValue(fileViewPath[3], DEFAULT_ARRESTER_CONFIGS); + return ""; + }; const renderAtpModelCard = (item: AtpAssetSummary) => { const deleteLoading = deleteMutation.isPending; @@ -705,10 +753,15 @@ export default function AtpModelsPage() { {!isMobile && ( setDisplayMode(value as "list" | "tree")} + onChange={(value) => { + setDisplayMode(value as "list" | "file"); + if (value === "list") { + setFileViewPath([]); + } + }} options={[ { label: "列表视图", value: "list", icon: }, - { label: "树形视图", value: "tree", icon: }, + { label: "文件视图", value: "file", icon: }, ]} /> )} @@ -746,27 +799,91 @@ export default function AtpModelsPage() { )} - {displayMode === "tree" && viewMode === "table" ? ( -
- {assetsQuery.isLoading || assetsQuery.isFetching ? ( -
- -
- ) : treeData.length === 0 ? ( - +
+ handleBreadcrumbClick(-1)}> + ATP模型 + + ), + }, + ...fileViewPath.map((_, index) => ({ + title: ( + handleBreadcrumbClick(index)}> + {getBreadcrumbLabel(index)} + + ), + })), + ]} /> - ) : ( - setExpandedKeys(keys)} - treeData={treeData} - blockNode - style={{ backgroundColor: "#fff" }} - /> - )} +
+
+ {assetsQuery.isLoading || assetsQuery.isFetching ? ( +
+ +
+ ) : fileViewItems.length === 0 ? ( + + ) : ( +
+ {fileViewItems.map((item, index) => + item.type === "folder" ? ( + handleFileViewItemClick(item)} + style={{ cursor: "pointer" }} + bodyStyle={{ padding: "16px" }} + > +
+ + + {item.displayName} + +
+
+ ) : ( + +
+ + + {item.displayName} + + + {item.item && formatDateTime(item.item.update_date)} + + {item.item && ( + item.item && deleteMutation.mutate(item.item.id)} + disabled={!canManage} + > + + + )} +
+
+ ) + )} +
+ )} +
) : viewMode === "table" ? (