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 { useNavigate } from 'react-router-dom';
import {
Layout,
Typography,
Modal,
Button,
@ -17,11 +16,14 @@ import {
Dropdown,
Menu,
Message,
Spin,
Tree,
} from '@arco-design/web-react';
import {
IconPlus,
IconDelete,
IconEdit,
IconRefresh,
} from '@arco-design/web-react/icon';
import {
treeApi,
@ -33,7 +35,7 @@ import {
type Organization,
} from '@/api/devices';
import OrganizationTree, {
import {
buildTreeData,
collectCabinets,
findOrgInTree,
@ -43,8 +45,6 @@ import OrganizationTree, {
import CabinetGrid from './CabinetGrid';
import AddCabinetModal from './AddCabinetModal';
const Content = Layout.Content;
export default function Devices() {
const navigate = useNavigate();
const [treeData, setTreeData] = useState<TreeInternalNode[]>([]);
@ -285,18 +285,63 @@ export default function Devices() {
</Menu>
);
return (
<Layout style={{ height: '100%', background: 'transparent' }}>
<OrganizationTree
treeData={treeData}
loading={loading}
selectedKeys={selectedKeys}
onSelect={handleTreeSelect}
onReload={loadTree}
onRightClick={handleRightClick}
/>
// -------- 树渲染 --------
const renderNodeTitle = useCallback(
(nodeType: NodeType, nodeId: number, title: string) => (
<span
style={{ display: 'flex', alignItems: 'center', gap: 4 }}
onContextMenu={(e) => handleRightClick(e, nodeType, nodeId)}
>
<span style={{ fontSize: 13 }}>{title}</span>
</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 }}>
<Typography.Title heading={5} style={{ margin: 0 }}>
({cabinets.length})
@ -306,7 +351,7 @@ export default function Devices() {
</Button>
</div>
<CabinetGrid cabinets={cabinets} loading={loading} />
</Content>
</div>
{/* 右键菜单 */}
<Dropdown
@ -354,6 +399,6 @@ export default function Devices() {
onCancel={() => setCabinetModalVisible(false)}
onSuccess={async () => { setCabinetModalVisible(false); await loadTree(); }}
/>
</Layout>
</div>
);
}