数据面板新增缩放滑块,调整样式
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Drawer,
|
Drawer,
|
||||||
|
Slider,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Refresh,
|
Refresh,
|
||||||
@@ -35,7 +36,6 @@ import { GeoJSON } from "ol/format";
|
|||||||
import { useGetIdentity } from "@refinedev/core";
|
import { useGetIdentity } from "@refinedev/core";
|
||||||
import { useNotification } from "@refinedev/core";
|
import { useNotification } from "@refinedev/core";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { set } from "date-fns";
|
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
@@ -349,6 +349,9 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
"raw" | "clean" | "sim" | "all"
|
"raw" | "clean" | "sim" | "all"
|
||||||
>("all");
|
>("all");
|
||||||
|
|
||||||
|
// 滑块状态:用于图表缩放
|
||||||
|
const [zoomRange, setZoomRange] = useState<[number, number]>([0, 100]);
|
||||||
|
|
||||||
// 获取 SCADA 设备信息,生成 deviceLabels
|
// 获取 SCADA 设备信息,生成 deviceLabels
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDeviceLabels = async () => {
|
const fetchDeviceLabels = async () => {
|
||||||
@@ -393,6 +396,21 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
[timeSeries, deviceIds, fractionDigits, showCleaning]
|
[timeSeries, deviceIds, fractionDigits, showCleaning]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 根据滑块范围过滤数据集
|
||||||
|
const filteredDataset = useMemo(() => {
|
||||||
|
if (dataset.length === 0) return dataset;
|
||||||
|
|
||||||
|
const startIndex = Math.floor((zoomRange[0] / 100) * dataset.length);
|
||||||
|
const endIndex = Math.ceil((zoomRange[1] / 100) * dataset.length);
|
||||||
|
|
||||||
|
return dataset.slice(startIndex, endIndex);
|
||||||
|
}, [dataset, zoomRange]);
|
||||||
|
|
||||||
|
// 重置滑块范围当数据变化时
|
||||||
|
useEffect(() => {
|
||||||
|
setZoomRange([0, 100]);
|
||||||
|
}, [timeSeries]);
|
||||||
|
|
||||||
const handleFetch = useCallback(
|
const handleFetch = useCallback(
|
||||||
async (reason: string) => {
|
async (reason: string) => {
|
||||||
if (!hasDevices) {
|
if (!hasDevices) {
|
||||||
@@ -404,9 +422,6 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
|
|
||||||
setLoadingState("loading");
|
setLoadingState("loading");
|
||||||
setError(null);
|
setError(null);
|
||||||
if (deviceIds.length > 1) {
|
|
||||||
setSelectedSource("clean");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const { from: rangeFrom, to: rangeTo } = normalizedRange;
|
const { from: rangeFrom, to: rangeTo } = normalizedRange;
|
||||||
const result = await customFetcher(deviceIds, {
|
const result = await customFetcher(deviceIds, {
|
||||||
@@ -510,6 +525,15 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
}
|
}
|
||||||
}, [deviceIds.join(",")]);
|
}, [deviceIds.join(",")]);
|
||||||
|
|
||||||
|
// 当设备数量变化时,调整数据源选择
|
||||||
|
useEffect(() => {
|
||||||
|
if (deviceIds.length > 1 && selectedSource === "all") {
|
||||||
|
setSelectedSource("clean");
|
||||||
|
} else if (deviceIds.length === 1 && selectedSource !== "all") {
|
||||||
|
setSelectedSource("all");
|
||||||
|
}
|
||||||
|
}, [deviceIds.length]);
|
||||||
|
|
||||||
const columns: GridColDef[] = useMemo(() => {
|
const columns: GridColDef[] = useMemo(() => {
|
||||||
const base: GridColDef[] = [
|
const base: GridColDef[] = [
|
||||||
{
|
{
|
||||||
@@ -655,50 +679,107 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
"#3f51b5", // 靛蓝色
|
"#3f51b5", // 靛蓝色
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 获取当前显示范围的时间边界
|
||||||
|
const getTimeRangeLabel = () => {
|
||||||
|
if (filteredDataset.length === 0) return "";
|
||||||
|
const firstTime = filteredDataset[0].time;
|
||||||
|
const lastTime = filteredDataset[filteredDataset.length - 1].time;
|
||||||
|
if (firstTime instanceof Date && lastTime instanceof Date) {
|
||||||
|
return `${dayjs(firstTime).format("MM-DD HH:mm")} ~ ${dayjs(
|
||||||
|
lastTime
|
||||||
|
).format("MM-DD HH:mm")}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: "100%", height: "100%" }}>
|
<Box
|
||||||
<LineChart
|
sx={{
|
||||||
dataset={dataset}
|
width: "100%",
|
||||||
height={520}
|
height: "100%",
|
||||||
margin={{ left: 70, right: 40, top: 30, bottom: 90 }}
|
display: "flex",
|
||||||
xAxis={[
|
flexDirection: "column",
|
||||||
{
|
}}
|
||||||
dataKey: "time",
|
>
|
||||||
scaleType: "time",
|
<Box sx={{ flex: 1 }}>
|
||||||
valueFormatter: (value) =>
|
<LineChart
|
||||||
value instanceof Date
|
dataset={filteredDataset}
|
||||||
? dayjs(value).format("MM-DD HH:mm")
|
height={480}
|
||||||
: String(value),
|
margin={{ left: 70, right: 40, top: 30, bottom: 90 }}
|
||||||
tickLabelStyle: {
|
xAxis={[
|
||||||
angle: -45,
|
{
|
||||||
textAnchor: "end",
|
dataKey: "time",
|
||||||
fontSize: 11,
|
scaleType: "time",
|
||||||
fill: "#666",
|
valueFormatter: (value) =>
|
||||||
|
value instanceof Date
|
||||||
|
? dayjs(value).format("MM-DD HH:mm")
|
||||||
|
: String(value),
|
||||||
|
tickLabelStyle: {
|
||||||
|
angle: -45,
|
||||||
|
textAnchor: "end",
|
||||||
|
fontSize: 11,
|
||||||
|
fill: "#666",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
yAxis={[
|
||||||
yAxis={[
|
{
|
||||||
{
|
label: "数值",
|
||||||
label: "数值",
|
labelStyle: {
|
||||||
labelStyle: {
|
fontSize: 13,
|
||||||
fontSize: 13,
|
fill: "#333",
|
||||||
fill: "#333",
|
fontWeight: 500,
|
||||||
fontWeight: 500,
|
},
|
||||||
|
tickLabelStyle: {
|
||||||
|
fontSize: 11,
|
||||||
|
fill: "#666",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tickLabelStyle: {
|
]}
|
||||||
fontSize: 11,
|
series={(() => {
|
||||||
fill: "#666",
|
if (showCleaning) {
|
||||||
},
|
if (selectedSource === "all") {
|
||||||
},
|
// 全部模式:显示所有设备的三种数据
|
||||||
]}
|
return deviceIds.flatMap((id, index) => [
|
||||||
series={(() => {
|
{
|
||||||
if (showCleaning) {
|
dataKey: `${id}_raw`,
|
||||||
if (selectedSource === "all") {
|
label: `${deviceLabels?.[id] ?? id} (原始)`,
|
||||||
// 全部模式:显示所有设备的三种数据
|
showMark: dataset.length < 50,
|
||||||
return deviceIds.flatMap((id, index) => [
|
curve: "catmullRom",
|
||||||
{
|
color: colors[index % colors.length],
|
||||||
dataKey: `${id}_raw`,
|
valueFormatter: (value: number | null) =>
|
||||||
label: `${deviceLabels?.[id] ?? id} (原始)`,
|
value !== null ? value.toFixed(fractionDigits) : "--",
|
||||||
|
area: false,
|
||||||
|
stack: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: `${id}_clean`,
|
||||||
|
label: `${deviceLabels?.[id] ?? id} (清洗)`,
|
||||||
|
showMark: dataset.length < 50,
|
||||||
|
curve: "catmullRom",
|
||||||
|
color: colors[(index + 3) % colors.length],
|
||||||
|
valueFormatter: (value: number | null) =>
|
||||||
|
value !== null ? value.toFixed(fractionDigits) : "--",
|
||||||
|
area: false,
|
||||||
|
stack: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: `${id}_sim`,
|
||||||
|
label: `${deviceLabels?.[id] ?? id} (模拟)`,
|
||||||
|
showMark: dataset.length < 50,
|
||||||
|
curve: "catmullRom",
|
||||||
|
color: colors[(index + 6) % colors.length],
|
||||||
|
valueFormatter: (value: number | null) =>
|
||||||
|
value !== null ? value.toFixed(fractionDigits) : "--",
|
||||||
|
area: false,
|
||||||
|
stack: undefined,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// 单一数据源模式:只显示选中的数据源
|
||||||
|
return deviceIds.map((id, index) => ({
|
||||||
|
dataKey: `${id}_${selectedSource}`,
|
||||||
|
label: deviceLabels?.[id] ?? id,
|
||||||
showMark: dataset.length < 50,
|
showMark: dataset.length < 50,
|
||||||
curve: "catmullRom",
|
curve: "catmullRom",
|
||||||
color: colors[index % colors.length],
|
color: colors[index % colors.length],
|
||||||
@@ -706,34 +787,11 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
value !== null ? value.toFixed(fractionDigits) : "--",
|
||||||
area: false,
|
area: false,
|
||||||
stack: undefined,
|
stack: undefined,
|
||||||
},
|
}));
|
||||||
{
|
}
|
||||||
dataKey: `${id}_clean`,
|
|
||||||
label: `${deviceLabels?.[id] ?? id} (清洗)`,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[(index + 3) % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataKey: `${id}_sim`,
|
|
||||||
label: `${deviceLabels?.[id] ?? id} (模拟)`,
|
|
||||||
showMark: dataset.length < 50,
|
|
||||||
curve: "catmullRom",
|
|
||||||
color: colors[(index + 6) % colors.length],
|
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
// 单一数据源模式:只显示选中的数据源
|
|
||||||
return deviceIds.map((id, index) => ({
|
return deviceIds.map((id, index) => ({
|
||||||
dataKey: `${id}_${selectedSource}`,
|
dataKey: id,
|
||||||
label: deviceLabels?.[id] ?? id,
|
label: deviceLabels?.[id] ?? id,
|
||||||
showMark: dataset.length < 50,
|
showMark: dataset.length < 50,
|
||||||
curve: "catmullRom",
|
curve: "catmullRom",
|
||||||
@@ -744,68 +802,134 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
stack: undefined,
|
stack: undefined,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
})()}
|
||||||
return deviceIds.map((id, index) => ({
|
grid={{ vertical: true, horizontal: true }}
|
||||||
dataKey: id,
|
sx={{
|
||||||
label: deviceLabels?.[id] ?? id,
|
"& .MuiLineElement-root": {
|
||||||
showMark: dataset.length < 50,
|
strokeWidth: 2.5,
|
||||||
curve: "catmullRom",
|
strokeLinecap: "round",
|
||||||
color: colors[index % colors.length],
|
strokeLinejoin: "round",
|
||||||
valueFormatter: (value: number | null) =>
|
|
||||||
value !== null ? value.toFixed(fractionDigits) : "--",
|
|
||||||
area: false,
|
|
||||||
stack: undefined,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
grid={{ vertical: true, horizontal: true }}
|
|
||||||
sx={{
|
|
||||||
"& .MuiLineElement-root": {
|
|
||||||
strokeWidth: 2.5,
|
|
||||||
strokeLinecap: "round",
|
|
||||||
strokeLinejoin: "round",
|
|
||||||
},
|
|
||||||
"& .MuiMarkElement-root": {
|
|
||||||
scale: "0.8",
|
|
||||||
strokeWidth: 2,
|
|
||||||
},
|
|
||||||
"& .MuiChartsAxis-line": {
|
|
||||||
stroke: "#e0e0e0",
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
"& .MuiChartsAxis-tick": {
|
|
||||||
stroke: "#e0e0e0",
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
"& .MuiChartsGrid-line": {
|
|
||||||
stroke: "#f5f5f5",
|
|
||||||
strokeWidth: 1,
|
|
||||||
strokeDasharray: "3 3",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
slotProps={{
|
|
||||||
legend: {
|
|
||||||
direction: "row",
|
|
||||||
position: { horizontal: "middle", vertical: "bottom" },
|
|
||||||
padding: { bottom: 2, left: 0, right: 0 },
|
|
||||||
itemMarkWidth: 16,
|
|
||||||
itemMarkHeight: 3,
|
|
||||||
markGap: 8,
|
|
||||||
itemGap: 16,
|
|
||||||
labelStyle: {
|
|
||||||
fontSize: 12,
|
|
||||||
fill: "#333",
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
},
|
||||||
},
|
"& .MuiMarkElement-root": {
|
||||||
loadingOverlay: {
|
scale: "0.8",
|
||||||
style: { backgroundColor: "rgba(255, 255, 255, 0.7)" },
|
strokeWidth: 2,
|
||||||
},
|
},
|
||||||
}}
|
"& .MuiChartsAxis-line": {
|
||||||
tooltip={{
|
stroke: "#e0e0e0",
|
||||||
trigger: "axis",
|
strokeWidth: 1,
|
||||||
}}
|
},
|
||||||
/>
|
"& .MuiChartsAxis-tick": {
|
||||||
|
stroke: "#e0e0e0",
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
"& .MuiChartsGrid-line": {
|
||||||
|
stroke: "#d0d0d0",
|
||||||
|
strokeWidth: 0.8,
|
||||||
|
strokeDasharray: "4 4",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
legend: {
|
||||||
|
direction: "row",
|
||||||
|
position: { horizontal: "middle", vertical: "bottom" },
|
||||||
|
padding: { bottom: 2, left: 0, right: 0 },
|
||||||
|
itemMarkWidth: 16,
|
||||||
|
itemMarkHeight: 3,
|
||||||
|
markGap: 8,
|
||||||
|
itemGap: 16,
|
||||||
|
labelStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fill: "#333",
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loadingOverlay: {
|
||||||
|
style: { backgroundColor: "rgba(255, 255, 255, 0.7)" },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
tooltip={{
|
||||||
|
trigger: "axis",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 时间范围滑块 */}
|
||||||
|
<Box sx={{ px: 3, pb: 2, pt: 1 }}>
|
||||||
|
<Stack direction="row" spacing={2} alignItems="center">
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ minWidth: 60, color: "text.secondary", fontSize: "0.8rem" }}
|
||||||
|
>
|
||||||
|
时间范围
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
value={zoomRange}
|
||||||
|
onChange={(_, newValue) =>
|
||||||
|
setZoomRange(newValue as [number, number])
|
||||||
|
}
|
||||||
|
valueLabelDisplay="auto"
|
||||||
|
valueLabelFormat={(value) => {
|
||||||
|
const index = Math.floor((value / 100) * dataset.length);
|
||||||
|
if (dataset[index] && dataset[index].time instanceof Date) {
|
||||||
|
return dayjs(dataset[index].time).format("MM-DD HH:mm");
|
||||||
|
}
|
||||||
|
return `${value}%`;
|
||||||
|
}}
|
||||||
|
marks={[
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
label:
|
||||||
|
dataset.length > 0 && dataset[0].time instanceof Date
|
||||||
|
? dayjs(dataset[0].time).format("MM-DD HH:mm")
|
||||||
|
: "起始",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 100,
|
||||||
|
label:
|
||||||
|
dataset.length > 0 &&
|
||||||
|
dataset[dataset.length - 1].time instanceof Date
|
||||||
|
? dayjs(dataset[dataset.length - 1].time).format(
|
||||||
|
"MM-DD HH:mm"
|
||||||
|
)
|
||||||
|
: "结束",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
"& .MuiSlider-thumb": {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
"& .MuiSlider-markLabel": {
|
||||||
|
fontSize: "0.7rem",
|
||||||
|
color: "text.secondary",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setZoomRange([0, 100])}
|
||||||
|
sx={{ minWidth: 60, fontSize: "0.75rem" }}
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
{getTimeRangeLabel() && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
color: "primary.main",
|
||||||
|
display: "block",
|
||||||
|
textAlign: "center",
|
||||||
|
mt: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
当前显示: {getTimeRangeLabel()} (共 {filteredDataset.length}{" "}
|
||||||
|
个数据点)
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -892,7 +1016,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 80,
|
top: 80,
|
||||||
right: 16,
|
right: 16,
|
||||||
height: "820px",
|
height: "860px",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow:
|
boxShadow:
|
||||||
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
|
|
||||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const filterBoxRef = useRef<HTMLDivElement | null>(null);
|
const filterBoxRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [listHeight, setListHeight] = useState<number>(560);
|
const [listHeight, setListHeight] = useState<number>(600);
|
||||||
|
|
||||||
// 清洗对话框状态
|
// 清洗对话框状态
|
||||||
const [cleanDialogOpen, setCleanDialogOpen] = useState<boolean>(false);
|
const [cleanDialogOpen, setCleanDialogOpen] = useState<boolean>(false);
|
||||||
@@ -748,7 +748,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateListHeight = () => {
|
const updateListHeight = () => {
|
||||||
if (filterBoxRef.current) {
|
if (filterBoxRef.current) {
|
||||||
const drawerHeight = 820; // Drawer 总高度
|
const drawerHeight = 860; // Drawer 总高度
|
||||||
const headerHeight = 73; // 头部高度(估算)
|
const headerHeight = 73; // 头部高度(估算)
|
||||||
const dividerHeight = 1; // 分隔线高度
|
const dividerHeight = 1; // 分隔线高度
|
||||||
const filterBoxHeight = filterBoxRef.current.offsetHeight;
|
const filterBoxHeight = filterBoxRef.current.offsetHeight;
|
||||||
@@ -810,7 +810,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
"& .MuiDrawer-paper": {
|
"& .MuiDrawer-paper": {
|
||||||
width: 360,
|
width: 360,
|
||||||
height: "820px",
|
height: "860px",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 80,
|
top: 80,
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useState } from "react";
|
|
||||||
import { Box, Stack } from "@mui/material";
|
|
||||||
import SCADADeviceList from "./SCADADeviceList";
|
|
||||||
import SCADADataPanel from "./SCADADataPanel";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 集成面板:左侧为 SCADA 设备列表(支持从地图选择),右侧为历史数据面板(曲线/表格)。
|
|
||||||
* - 使用 SCADADeviceList 内置的地图点击选择功能
|
|
||||||
* - 使用 SCADADataPanel 的时间段查询与图表展示
|
|
||||||
* - 两者通过选中设备 ID 进行联动
|
|
||||||
*/
|
|
||||||
export interface SCADAIntegratedPanelProps {
|
|
||||||
/** 初始选中设备 ID 列表 */
|
|
||||||
initialSelection?: string[];
|
|
||||||
/** 是否展示数据清洗相关功能(传递给两个子面板) */
|
|
||||||
showCleaning?: boolean;
|
|
||||||
/** 是否显示右侧数据面板 */
|
|
||||||
showDataPanel?: boolean;
|
|
||||||
/** 数据面板默认选项卡 */
|
|
||||||
dataPanelDefaultTab?: "chart" | "table";
|
|
||||||
/** 数据面板小数位数 */
|
|
||||||
fractionDigits?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SCADAIntegratedPanel: React.FC<SCADAIntegratedPanelProps> = ({
|
|
||||||
initialSelection = [],
|
|
||||||
showCleaning = false,
|
|
||||||
showDataPanel = true,
|
|
||||||
dataPanelDefaultTab = "chart",
|
|
||||||
fractionDigits = 2,
|
|
||||||
}) => {
|
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>(initialSelection);
|
|
||||||
// 通过变更 key 强制重新挂载 DataPanel,从而在“清洗全部”后自动刷新
|
|
||||||
const [dataPanelKey, setDataPanelKey] = useState<number>(0);
|
|
||||||
|
|
||||||
const handleSelectionChange = useCallback((ids: string[]) => {
|
|
||||||
setSelectedIds(ids);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 清洗全部数据后,强制刷新右侧数据面板(重新挂载触发首轮查询)
|
|
||||||
const handleCleanAllData = useCallback((_from: Date, _to: Date) => {
|
|
||||||
setDataPanelKey((k) => k + 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hasSelection = useMemo(() => selectedIds.length > 0, [selectedIds]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ position: "relative", width: "100%", height: "100%" }}>
|
|
||||||
{/* 左侧:设备列表(内置抽屉布局) */}
|
|
||||||
<SCADADeviceList
|
|
||||||
selectedDeviceIds={selectedIds}
|
|
||||||
onSelectionChange={handleSelectionChange}
|
|
||||||
showCleaning={showCleaning}
|
|
||||||
onCleanAllData={handleCleanAllData}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 右侧:历史数据面板(内置抽屉布局) */}
|
|
||||||
{showDataPanel && (
|
|
||||||
<SCADADataPanel
|
|
||||||
key={`data-panel-${dataPanelKey}`}
|
|
||||||
deviceIds={selectedIds}
|
|
||||||
visible={true}
|
|
||||||
defaultTab={dataPanelDefaultTab}
|
|
||||||
fractionDigits={fractionDigits}
|
|
||||||
showCleaning={showCleaning}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SCADAIntegratedPanel;
|
|
||||||
Reference in New Issue
Block a user