feat: 设备管理页改为左右两列布局

This commit is contained in:
12451 2026-07-02 19:26:23 +08:00
parent 1be7480472
commit 8d531b34a3

View File

@ -8,7 +8,6 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { import {
Layout,
Typography, Typography,
Modal, Modal,
Button, Button,
@ -17,11 +16,14 @@ import {
Dropdown, Dropdown,
Menu, Menu,
Message, Message,
Spin,
Tree,
} from '@arco-design/web-react'; } from '@arco-design/web-react';
import { import {
IconPlus, IconPlus,
IconDelete, IconDelete,
IconEdit, IconEdit,
IconRefresh,
} from '@arco-design/web-react/icon'; } from '@arco-design/web-react/icon';
import { import {
treeApi, treeApi,
@ -33,7 +35,7 @@ import {
type Organization, type Organization,
} from '@/api/devices'; } from '@/api/devices';
import OrganizationTree, { import {
buildTreeData, buildTreeData,
collectCabinets, collectCabinets,
findOrgInTree, findOrgInTree,
@ -43,8 +45,6 @@ import OrganizationTree, {
import CabinetGrid from './CabinetGrid'; import CabinetGrid from './CabinetGrid';
import AddCabinetModal from './AddCabinetModal'; import AddCabinetModal from './AddCabinetModal';
const Content = Layout.Content;
export default function Devices() { export default function Devices() {
const navigate = useNavigate(); const navigate = useNavigate();
const [treeData, setTreeData] = useState<TreeInternalNode[]>([]); const [treeData, setTreeData] = useState<TreeInternalNode[]>([]);
@ -285,18 +285,63 @@ export default function Devices() {
</Menu> </Menu>
); );
return ( // -------- 树渲染 --------
<Layout style={{ height: '100%', background: 'transparent' }}> const renderNodeTitle = useCallback(
<OrganizationTree (nodeType: NodeType, nodeId: number, title: string) => (
treeData={treeData} <span
loading={loading} style={{ display: 'flex', alignItems: 'center', gap: 4 }}
selectedKeys={selectedKeys} onContextMenu={(e) => handleRightClick(e, nodeType, nodeId)}
onSelect={handleTreeSelect} >
onReload={loadTree} <span style={{ fontSize: 13 }}>{title}</span>
onRightClick={handleRightClick} </span>
/> ),
[handleRightClick],
);
<Content style={{ padding: '0 0 0 16px', overflow: 'auto' }}> const injectTitleRender = useCallback(
(nodes: TreeInternalNode[]): unknown[] =>
nodes.map((n) => ({
...n,
title: renderNodeTitle(n._type, n._id, n.title),
children: n.children ? injectTitleRender(n.children) : undefined,
})),
[renderNodeTitle],
);
const renderedTreeData = [
{
key: 'all',
title: renderNodeTitle('all', 0, '全部'),
children: injectTitleRender(treeData) as TreeInternalNode[],
},
];
return (
<div style={{ display: 'flex', gap: 16, height: '100%', overflow: 'hidden' }}>
{/* 左列:组织架构树 */}
<div style={{ width: 300, flexShrink: 0, background: 'var(--color-bg-2)', borderRadius: 8, padding: 16, overflow: 'auto' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
<Typography.Text style={{ fontWeight: 600, fontSize: 14 }}></Typography.Text>
<Button size="small" type="text" icon={<IconRefresh />} onClick={loadTree} />
</div>
<Spin loading={loading && treeData.length === 0} dot>
{treeData.length > 0 ? (
<Tree
treeData={renderedTreeData as never}
selectedKeys={selectedKeys}
onSelect={handleTreeSelect as never}
autoExpandParent
defaultExpandedKeys={['all']}
style={{ fontSize: 13 }}
/>
) : (
!loading && <Typography.Text type="secondary"></Typography.Text>
)}
</Spin>
</div>
{/* 右列:柜子列表 */}
<div style={{ flex: 1, overflow: 'auto' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<Typography.Title heading={5} style={{ margin: 0 }}> <Typography.Title heading={5} style={{ margin: 0 }}>
({cabinets.length}) ({cabinets.length})
@ -306,7 +351,7 @@ export default function Devices() {
</Button> </Button>
</div> </div>
<CabinetGrid cabinets={cabinets} loading={loading} /> <CabinetGrid cabinets={cabinets} loading={loading} />
</Content> </div>
{/* 右键菜单 */} {/* 右键菜单 */}
<Dropdown <Dropdown
@ -354,6 +399,6 @@ export default function Devices() {
onCancel={() => setCabinetModalVisible(false)} onCancel={() => setCabinetModalVisible(false)}
onSuccess={async () => { setCabinetModalVisible(false); await loadTree(); }} onSuccess={async () => { setCabinetModalVisible(false); await loadTree(); }}
/> />
</Layout> </div>
); );
} }