diff --git a/web/src/app/admin/atp-models/page.tsx b/web/src/app/admin/atp-models/page.tsx index 02ab54e..2194c04 100644 --- a/web/src/app/admin/atp-models/page.tsx +++ b/web/src/app/admin/atp-models/page.tsx @@ -11,17 +11,20 @@ import { Modal, Popconfirm, Row, + Segmented, Select, Space, Spin, Table, + Tree, Typography, Upload, message, type CardProps, } from "antd"; -import { UploadOutlined } from "@ant-design/icons"; +import { UploadOutlined, FolderOutlined, FileOutlined, TableOutlined, ApartmentOutlined } 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"; @@ -165,6 +168,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 [pagination, setPagination] = useState({ current: 1, pageSize: 20 }); const [cardViewPage, setCardViewPage] = useState(1); const [allLoadedAssets, setAllLoadedAssets] = useState([]); @@ -518,6 +523,102 @@ export default function AtpModelsPage() { [canManage, deleteMutation], ); + const buildTreeData = useCallback(() => { + const treeMap = new Map(); + + assetItems.forEach((item) => { + const voltage = item.voltage_level || "未分类"; + const tower = item.tower_type || "未分类"; + const scene = item.scene_type || "未分类"; + const arrester = item.arrester_config || "未分类"; + + 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 (!treeMap.has(voltageKey)) { + treeMap.set(voltageKey, { + key: voltageKey, + title: formatDimensionValue(voltage, DEFAULT_VOLTAGE_LEVELS), + icon: , + children: [], + }); + } + + 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]; + } + + 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]; + } + + 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]; + } + + 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, + }; + + arresterNode.children = [...(arresterNode.children || []), itemNode]; + }); + + return Array.from(treeMap.values()); + }, [assetItems, canManage, deleteMutation]); + + const treeData = useMemo(() => buildTreeData(), [buildTreeData]); + const renderAtpModelCard = (item: AtpAssetSummary) => { const deleteLoading = deleteMutation.isPending; const rowBusy = deleteLoading; @@ -600,13 +701,25 @@ export default function AtpModelsPage() { className="admin-atp-models-page-card" title="ATP 模型管理" extra={ - + + {!isMobile && ( + setDisplayMode(value as "list" | "tree")} + options={[ + { label: "列表视图", value: "list", icon: }, + { label: "树形视图", value: "tree", icon: }, + ]} + /> + )} + + } > {viewMode === "card" ? ( @@ -633,7 +746,29 @@ export default function AtpModelsPage() { )} - {viewMode === "table" ? ( + {displayMode === "tree" && viewMode === "table" ? ( +
+ {assetsQuery.isLoading || assetsQuery.isFetching ? ( +
+ +
+ ) : treeData.length === 0 ? ( + + ) : ( + setExpandedKeys(keys)} + treeData={treeData} + blockNode + style={{ backgroundColor: "#fff" }} + /> + )} +
+ ) : viewMode === "table" ? (