为 SCADA 设备列表实现虚拟列表,优化渲染性能
This commit is contained in:
36
package-lock.json
generated
36
package-lock.json
generated
@@ -37,7 +37,7 @@
|
|||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-window": "^2.2.2",
|
"react-window": "^1.8.10",
|
||||||
"tailwindcss": "^4.1.13"
|
"tailwindcss": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19.1.0",
|
"@types/react": "^19.1.0",
|
||||||
"@types/react-dom": "^19.1.0",
|
"@types/react-dom": "^19.1.0",
|
||||||
|
"@types/react-window": "^1.8.8",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^15.0.3",
|
||||||
@@ -7992,6 +7993,16 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-window": {
|
||||||
|
"version": "1.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz",
|
||||||
|
"integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "2.0.11",
|
"version": "2.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
||||||
@@ -14590,6 +14601,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memoize-one": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
@@ -16462,13 +16479,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-window": {
|
"node_modules/react-window": {
|
||||||
"version": "2.2.2",
|
"version": "1.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
|
||||||
"integrity": "sha512-kvHKwFImKBWNbx2S87NZOhQhAVkBthjmnOfHlhQI45p3A+D+V53E+CqQMsyHrxNe3ke+YtWXuKDa1eoHAaIWJg==",
|
"integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.0.0",
|
||||||
|
"memoize-one": ">=3.1.1 <6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">8.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-window": "^2.2.2",
|
"react-window": "^1.8.10",
|
||||||
"tailwindcss": "^4.1.13"
|
"tailwindcss": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19.1.0",
|
"@types/react": "^19.1.0",
|
||||||
"@types/react-dom": "^19.1.0",
|
"@types/react-dom": "^19.1.0",
|
||||||
|
"@types/react-window": "^1.8.8",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^15.0.3",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
List,
|
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
@@ -39,6 +38,7 @@ import {
|
|||||||
Clear,
|
Clear,
|
||||||
DeviceHub,
|
DeviceHub,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { FixedSizeList } from "react-window";
|
||||||
|
|
||||||
import { useMap } from "@app/OlMap/MapComponent";
|
import { useMap } from "@app/OlMap/MapComponent";
|
||||||
import { GeoJSON } from "ol/format";
|
import { GeoJSON } from "ol/format";
|
||||||
@@ -452,108 +452,122 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<List dense sx={{ p: 0 }}>
|
<FixedSizeList
|
||||||
{filteredDevices.map((device, index) => (
|
height={400}
|
||||||
<React.Fragment key={device.id}>
|
itemCount={filteredDevices.length}
|
||||||
<ListItem disablePadding>
|
itemSize={80}
|
||||||
<ListItemButton
|
width="100%"
|
||||||
selected={activeSelection.includes(device.id)}
|
>
|
||||||
onClick={(event) => handleDeviceClick(device, event)}
|
{({
|
||||||
sx={{
|
index,
|
||||||
"&.Mui-selected": {
|
style,
|
||||||
backgroundColor: "primary.50",
|
}: {
|
||||||
borderLeft: 3,
|
index: number;
|
||||||
borderColor: "primary.main",
|
style: React.CSSProperties;
|
||||||
},
|
}) => {
|
||||||
"&:hover": {
|
const device = filteredDevices[index];
|
||||||
backgroundColor: "grey.50",
|
return (
|
||||||
},
|
<div style={style}>
|
||||||
}}
|
<ListItem disablePadding>
|
||||||
>
|
<ListItemButton
|
||||||
<ListItemIcon sx={{ minWidth: 36 }}>
|
selected={activeSelection.includes(device.id)}
|
||||||
<Typography
|
onClick={(event) => handleDeviceClick(device, event)}
|
||||||
variant="caption"
|
sx={{
|
||||||
sx={{
|
"&.Mui-selected": {
|
||||||
color: `${getStatusColor(device.status)}.main`,
|
backgroundColor: "primary.50",
|
||||||
fontWeight: "bold",
|
borderLeft: 3,
|
||||||
fontSize: 16,
|
borderColor: "primary.main",
|
||||||
}}
|
},
|
||||||
>
|
"&:hover": {
|
||||||
{getStatusIcon(device.status)}
|
backgroundColor: "grey.50",
|
||||||
</Typography>
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={1}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ fontWeight: "medium" }}
|
|
||||||
>
|
|
||||||
{device.name}
|
|
||||||
</Typography>
|
|
||||||
<Chip
|
|
||||||
label={device.type}
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
sx={{ fontSize: "0.7rem", height: 20 }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
secondary={
|
|
||||||
<Stack spacing={0.5}>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
>
|
|
||||||
ID: {device.id}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
>
|
|
||||||
坐标: {device.coordinates[0].toFixed(6)},{" "}
|
|
||||||
{device.coordinates[1].toFixed(6)}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
slotProps={{
|
|
||||||
secondary: {
|
|
||||||
component: "div", // 使其支持多行
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<ListItemIcon sx={{ minWidth: 36 }}>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
color: `${getStatusColor(device.status)}.main`,
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getStatusIcon(device.status)}
|
||||||
|
</Typography>
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
<Tooltip title="缩放到设备位置">
|
<ListItemText
|
||||||
<IconButton
|
primary={
|
||||||
size="small"
|
<Stack
|
||||||
onClick={(event) => {
|
direction="row"
|
||||||
event.stopPropagation();
|
alignItems="center"
|
||||||
handleZoomToDevice(device);
|
spacing={1}
|
||||||
}}
|
>
|
||||||
sx={{
|
<Typography
|
||||||
ml: 1,
|
variant="body2"
|
||||||
color: "primary.main",
|
sx={{ fontWeight: "medium" }}
|
||||||
"&:hover": {
|
>
|
||||||
backgroundColor: "primary.50",
|
{device.name}
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label={device.type}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: "0.7rem", height: 20 }}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<Stack spacing={0.5}>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
ID: {device.id}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
坐标: {device.coordinates[0].toFixed(6)},{" "}
|
||||||
|
{device.coordinates[1].toFixed(6)}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
slotProps={{
|
||||||
|
secondary: {
|
||||||
|
component: "div", // 使其支持多行
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<MyLocation fontSize="small" />
|
|
||||||
</IconButton>
|
<Tooltip title="缩放到设备位置">
|
||||||
</Tooltip>
|
<IconButton
|
||||||
</ListItemButton>
|
size="small"
|
||||||
</ListItem>
|
onClick={(event) => {
|
||||||
{index < filteredDevices.length - 1 && (
|
event.stopPropagation();
|
||||||
<Divider variant="inset" />
|
handleZoomToDevice(device);
|
||||||
)}
|
}}
|
||||||
</React.Fragment>
|
sx={{
|
||||||
))}
|
ml: 1,
|
||||||
</List>
|
color: "primary.main",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "primary.50",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyLocation fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
{index < filteredDevices.length - 1 && (
|
||||||
|
<Divider variant="inset" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</FixedSizeList>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
Reference in New Issue
Block a user