更新 SCADA 设备面板样式,新增清洗按钮
This commit is contained in:
@@ -21,10 +21,6 @@ export default function Home() {
|
|||||||
setPanelVisible(true);
|
setPanelVisible(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClosePanel = useCallback(() => {
|
|
||||||
setPanelVisible(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full overflow-hidden">
|
<div className="relative w-full h-full overflow-hidden">
|
||||||
<MapComponent>
|
<MapComponent>
|
||||||
@@ -35,11 +31,7 @@ export default function Home() {
|
|||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
selectedDeviceIds={selectedDeviceIds}
|
selectedDeviceIds={selectedDeviceIds}
|
||||||
/>
|
/>
|
||||||
<SCADADataPanel
|
<SCADADataPanel deviceIds={selectedDeviceIds} visible={panelVisible} />
|
||||||
deviceIds={selectedDeviceIds}
|
|
||||||
visible={panelVisible}
|
|
||||||
onClose={handleClosePanel}
|
|
||||||
/>
|
|
||||||
</MapComponent>
|
</MapComponent>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ export default function Home() {
|
|||||||
setPanelVisible(true);
|
setPanelVisible(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClosePanel = useCallback(() => {
|
|
||||||
setPanelVisible(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full overflow-hidden">
|
<div className="relative w-full h-full overflow-hidden">
|
||||||
<MapComponent>
|
<MapComponent>
|
||||||
@@ -32,11 +28,12 @@ export default function Home() {
|
|||||||
onDeviceClick={handleDeviceClick}
|
onDeviceClick={handleDeviceClick}
|
||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
selectedDeviceIds={selectedDeviceIds}
|
selectedDeviceIds={selectedDeviceIds}
|
||||||
|
showCleaning={true}
|
||||||
/>
|
/>
|
||||||
<SCADADataPanel
|
<SCADADataPanel
|
||||||
deviceIds={selectedDeviceIds}
|
deviceIds={selectedDeviceIds}
|
||||||
visible={panelVisible}
|
visible={panelVisible}
|
||||||
onClose={handleClosePanel}
|
showCleaning={true}
|
||||||
/>
|
/>
|
||||||
</MapComponent>
|
</MapComponent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ import {
|
|||||||
CircularProgress,
|
CircularProgress,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
Paper,
|
|
||||||
Stack,
|
Stack,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Collapse,
|
Drawer,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Close,
|
Close,
|
||||||
Refresh,
|
Refresh,
|
||||||
ShowChart,
|
ShowChart,
|
||||||
TableChart,
|
TableChart,
|
||||||
ExpandLess,
|
CleaningServices,
|
||||||
ExpandMore,
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||||
import { LineChart } from "@mui/x-charts";
|
import { LineChart } from "@mui/x-charts";
|
||||||
@@ -55,12 +55,14 @@ export interface SCADADataPanelProps {
|
|||||||
) => Promise<TimeSeriesPoint[]>;
|
) => Promise<TimeSeriesPoint[]>;
|
||||||
/** 可选:控制浮窗显示 */
|
/** 可选:控制浮窗显示 */
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
/** 可选:关闭浮窗的回调 */
|
|
||||||
onClose?: () => void;
|
|
||||||
/** 默认展示的选项卡 */
|
/** 默认展示的选项卡 */
|
||||||
defaultTab?: "chart" | "table";
|
defaultTab?: "chart" | "table";
|
||||||
/** Y 轴数值的小数位数 */
|
/** Y 轴数值的小数位数 */
|
||||||
fractionDigits?: number;
|
fractionDigits?: number;
|
||||||
|
/** 是否显示清洗功能 */
|
||||||
|
showCleaning?: boolean;
|
||||||
|
/** 清洗数据的回调 */
|
||||||
|
onCleanData?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelTab = "chart" | "table";
|
type PanelTab = "chart" | "table";
|
||||||
@@ -242,9 +244,10 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
deviceIds,
|
deviceIds,
|
||||||
fetchTimeSeriesData = defaultFetcher,
|
fetchTimeSeriesData = defaultFetcher,
|
||||||
visible = true,
|
visible = true,
|
||||||
onClose,
|
|
||||||
defaultTab = "chart",
|
defaultTab = "chart",
|
||||||
fractionDigits = 2,
|
fractionDigits = 2,
|
||||||
|
showCleaning = false,
|
||||||
|
onCleanData,
|
||||||
}) => {
|
}) => {
|
||||||
const [from, setFrom] = useState<Dayjs>(() => dayjs().subtract(1, "day"));
|
const [from, setFrom] = useState<Dayjs>(() => dayjs().subtract(1, "day"));
|
||||||
const [to, setTo] = useState<Dayjs>(() => dayjs());
|
const [to, setTo] = useState<Dayjs>(() => dayjs());
|
||||||
@@ -560,191 +563,247 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drawerWidth = 920;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<>
|
||||||
className={clsx(
|
{/* 收起时的触发按钮 */}
|
||||||
"absolute right-4 top-20 bg-white rounded-xl shadow-lg overflow-hidden flex flex-col transition-all duration-300",
|
{!isExpanded && hasDevices && (
|
||||||
visible ? "opacity-95 hover:opacity-100" : "opacity-0 -z-10"
|
<Box
|
||||||
|
className="absolute top-20 right-4 bg-white shadow-2xl rounded-lg cursor-pointer hover:shadow-xl transition-all duration-300 opacity-95 hover:opacity-100"
|
||||||
|
onClick={() => setIsExpanded(true)}
|
||||||
|
>
|
||||||
|
<Box className="flex flex-col items-center py-3 px-3 gap-1">
|
||||||
|
<ShowChart className="text-[#257DD4] w-5 h-5" />
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
className="text-gray-700 font-semibold my-1 text-xs"
|
||||||
|
style={{ writingMode: "vertical-rl" }}
|
||||||
|
>
|
||||||
|
历史数据
|
||||||
|
</Typography>
|
||||||
|
<ChevronLeft className="text-gray-600 w-4 h-4" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
sx={{
|
|
||||||
width: "min(920px, calc(100vw - 2rem))",
|
{/* 主面板 */}
|
||||||
maxHeight: "calc(100vh - 100px)",
|
<Drawer
|
||||||
}}
|
anchor="right"
|
||||||
>
|
open={isExpanded && visible}
|
||||||
{/* Header */}
|
variant="persistent"
|
||||||
<Box
|
hideBackdrop
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
width: isExpanded ? drawerWidth : 0,
|
||||||
borderBottom: 1,
|
flexShrink: 0,
|
||||||
borderColor: "divider",
|
"& .MuiDrawer-paper": {
|
||||||
backgroundColor: "primary.main",
|
width: "min(920px, calc(100vw - 2rem))",
|
||||||
color: "primary.contrastText",
|
boxSizing: "border-box",
|
||||||
|
position: "absolute",
|
||||||
|
top: 80,
|
||||||
|
right: 16,
|
||||||
|
height: "760px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
boxShadow:
|
||||||
|
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
|
||||||
|
backdropFilter: "blur(8px)",
|
||||||
|
opacity: 0.95,
|
||||||
|
transition: "all 0.3s ease-in-out",
|
||||||
|
border: "none",
|
||||||
|
"&:hover": {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<Box className="flex flex-col h-full bg-white rounded-xl overflow-hidden">
|
||||||
direction="row"
|
{/* Header */}
|
||||||
alignItems="center"
|
<Box
|
||||||
justifyContent="space-between"
|
sx={{
|
||||||
>
|
p: 2,
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
borderBottom: 1,
|
||||||
<ShowChart fontSize="small" />
|
borderColor: "divider",
|
||||||
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
|
backgroundColor: "primary.main",
|
||||||
SCADA 历史数据
|
color: "primary.contrastText",
|
||||||
</Typography>
|
}}
|
||||||
<Chip
|
>
|
||||||
size="small"
|
<Stack
|
||||||
label={`${deviceIds.length}`}
|
direction="row"
|
||||||
sx={{
|
alignItems="center"
|
||||||
backgroundColor: "rgba(255,255,255,0.2)",
|
justifyContent="space-between"
|
||||||
color: "primary.contrastText",
|
>
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack direction="row" spacing={1}>
|
|
||||||
<Tooltip title="展开/收起">
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
|
||||||
sx={{ color: "primary.contrastText" }}
|
|
||||||
>
|
|
||||||
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="关闭">
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={onClose}
|
|
||||||
sx={{ color: "primary.contrastText" }}
|
|
||||||
>
|
|
||||||
<Close fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Collapse in={isExpanded}>
|
|
||||||
{/* Controls */}
|
|
||||||
<Box sx={{ p: 2, backgroundColor: "grey.50" }}>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
||||||
<Stack spacing={1.5}>
|
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
<DateTimePicker
|
<ShowChart fontSize="small" />
|
||||||
label="开始时间"
|
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
|
||||||
value={from}
|
SCADA 历史数据
|
||||||
onChange={(value) =>
|
</Typography>
|
||||||
value && dayjs.isDayjs(value) && setFrom(value)
|
<Chip
|
||||||
}
|
size="small"
|
||||||
onAccept={(value) => {
|
label={`${deviceIds.length}`}
|
||||||
if (value && dayjs.isDayjs(value) && hasDevices) {
|
sx={{
|
||||||
handleFetch("date-change");
|
backgroundColor: "rgba(255,255,255,0.2)",
|
||||||
}
|
color: "primary.contrastText",
|
||||||
|
fontWeight: "bold",
|
||||||
}}
|
}}
|
||||||
maxDateTime={to}
|
|
||||||
slotProps={{ textField: { fullWidth: true, size: "small" } }}
|
|
||||||
/>
|
|
||||||
<DateTimePicker
|
|
||||||
label="结束时间"
|
|
||||||
value={to}
|
|
||||||
onChange={(value) =>
|
|
||||||
value && dayjs.isDayjs(value) && setTo(value)
|
|
||||||
}
|
|
||||||
onAccept={(value) => {
|
|
||||||
if (value && dayjs.isDayjs(value) && hasDevices) {
|
|
||||||
handleFetch("date-change");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
minDateTime={from}
|
|
||||||
slotProps={{ textField: { fullWidth: true, size: "small" } }}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack direction="row" spacing={1}>
|
||||||
direction="row"
|
<Tooltip title="收起">
|
||||||
spacing={1}
|
<IconButton
|
||||||
alignItems="center"
|
size="small"
|
||||||
justifyContent="space-between"
|
onClick={() => setIsExpanded(false)}
|
||||||
>
|
sx={{ color: "primary.contrastText" }}
|
||||||
<Tabs
|
>
|
||||||
value={activeTab}
|
<ChevronRight fontSize="small" />
|
||||||
onChange={(_, value: PanelTab) => setActiveTab(value)}
|
</IconButton>
|
||||||
variant="fullWidth"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
value="chart"
|
|
||||||
icon={<ShowChart fontSize="small" />}
|
|
||||||
iconPosition="start"
|
|
||||||
label="曲线"
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
value="table"
|
|
||||||
icon={<TableChart fontSize="small" />}
|
|
||||||
iconPosition="start"
|
|
||||||
label="表格"
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
<Tooltip title="刷新数据">
|
|
||||||
<span>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
size="small"
|
|
||||||
color="primary"
|
|
||||||
startIcon={<Refresh fontSize="small" />}
|
|
||||||
disabled={!hasDevices || loadingState === "loading"}
|
|
||||||
onClick={() => handleFetch("manual")}
|
|
||||||
>
|
|
||||||
刷新
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</LocalizationProvider>
|
</Box>
|
||||||
|
|
||||||
{!hasDevices && (
|
{/* Controls */}
|
||||||
<Typography
|
<Box sx={{ p: 2, backgroundColor: "grey.50" }}>
|
||||||
variant="caption"
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
color="warning.main"
|
<Stack spacing={1.5}>
|
||||||
sx={{ mt: 1, display: "block" }}
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
>
|
<DateTimePicker
|
||||||
未选择任何设备,无法获取数据。
|
label="开始时间"
|
||||||
</Typography>
|
value={from}
|
||||||
)}
|
onChange={(value) =>
|
||||||
{error && (
|
value && dayjs.isDayjs(value) && setFrom(value)
|
||||||
<Typography
|
}
|
||||||
variant="caption"
|
onAccept={(value) => {
|
||||||
color="error"
|
if (value && dayjs.isDayjs(value) && hasDevices) {
|
||||||
sx={{ mt: 1, display: "block" }}
|
handleFetch("date-change");
|
||||||
>
|
}
|
||||||
获取数据失败:{error}
|
}}
|
||||||
</Typography>
|
maxDateTime={to}
|
||||||
)}
|
slotProps={{
|
||||||
|
textField: { fullWidth: true, size: "small" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DateTimePicker
|
||||||
|
label="结束时间"
|
||||||
|
value={to}
|
||||||
|
onChange={(value) =>
|
||||||
|
value && dayjs.isDayjs(value) && setTo(value)
|
||||||
|
}
|
||||||
|
onAccept={(value) => {
|
||||||
|
if (value && dayjs.isDayjs(value) && hasDevices) {
|
||||||
|
handleFetch("date-change");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
minDateTime={from}
|
||||||
|
slotProps={{
|
||||||
|
textField: { fullWidth: true, size: "small" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(_, value: PanelTab) => setActiveTab(value)}
|
||||||
|
variant="fullWidth"
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
value="chart"
|
||||||
|
icon={<ShowChart fontSize="small" />}
|
||||||
|
iconPosition="start"
|
||||||
|
label="曲线"
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
value="table"
|
||||||
|
icon={<TableChart fontSize="small" />}
|
||||||
|
iconPosition="start"
|
||||||
|
label="表格"
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
{showCleaning && (
|
||||||
|
<Tooltip title="清洗数据">
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<CleaningServices fontSize="small" />}
|
||||||
|
disabled={!hasDevices || loadingState === "loading"}
|
||||||
|
onClick={onCleanData}
|
||||||
|
>
|
||||||
|
清洗
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip title="刷新数据">
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<Refresh fontSize="small" />}
|
||||||
|
disabled={!hasDevices || loadingState === "loading"}
|
||||||
|
onClick={() => handleFetch("manual")}
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</LocalizationProvider>
|
||||||
|
|
||||||
|
{!hasDevices && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="warning.main"
|
||||||
|
sx={{ mt: 1, display: "block" }}
|
||||||
|
>
|
||||||
|
未选择任何设备,无法获取数据。
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="error"
|
||||||
|
sx={{ mt: 1, display: "block" }}
|
||||||
|
>
|
||||||
|
获取数据失败:{error}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<Box sx={{ flex: 1, position: "relative", p: 2, overflow: "auto" }}>
|
||||||
|
{loadingState === "loading" && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
backgroundColor: "rgba(255,255,255,0.6)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={48} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "chart" ? renderChart() : renderTable()}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Drawer>
|
||||||
<Divider />
|
</>
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<Box sx={{ flex: 1, position: "relative", p: 2, overflow: "auto" }}>
|
|
||||||
{loadingState === "loading" && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
backgroundColor: "rgba(255,255,255,0.6)",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CircularProgress size={48} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "chart" ? renderChart() : renderTable()}
|
|
||||||
</Box>
|
|
||||||
</Collapse>
|
|
||||||
</Paper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user