更新 SCADA 设备面板样式,新增清洗按钮

This commit is contained in:
JIANG
2025-11-04 11:39:06 +08:00
parent 85d73bcd07
commit af44a7c503
4 changed files with 851 additions and 522 deletions

View File

@@ -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>
); );

View File

@@ -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>

View File

@@ -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,17 +563,60 @@ 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>
)} )}
{/* 主面板 */}
<Drawer
anchor="right"
open={isExpanded && visible}
variant="persistent"
hideBackdrop
sx={{ sx={{
width: isExpanded ? drawerWidth : 0,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: "min(920px, calc(100vw - 2rem))", width: "min(920px, calc(100vw - 2rem))",
maxHeight: "calc(100vh - 100px)", 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,
},
},
}} }}
> >
<Box className="flex flex-col h-full bg-white rounded-xl overflow-hidden">
{/* Header */} {/* Header */}
<Box <Box
sx={{ sx={{
@@ -602,29 +648,19 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
/> />
</Stack> </Stack>
<Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}>
<Tooltip title="展开/收起"> <Tooltip title="收起">
<IconButton <IconButton
size="small" size="small"
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(false)}
sx={{ color: "primary.contrastText" }} sx={{ color: "primary.contrastText" }}
> >
{isExpanded ? <ExpandLess /> : <ExpandMore />} <ChevronRight fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="关闭">
<IconButton
size="small"
onClick={onClose}
sx={{ color: "primary.contrastText" }}
>
<Close fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</Stack> </Stack>
</Stack> </Stack>
</Box> </Box>
<Collapse in={isExpanded}>
{/* Controls */} {/* Controls */}
<Box sx={{ p: 2, backgroundColor: "grey.50" }}> <Box sx={{ p: 2, backgroundColor: "grey.50" }}>
<LocalizationProvider dateAdapter={AdapterDayjs}> <LocalizationProvider dateAdapter={AdapterDayjs}>
@@ -642,7 +678,9 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
} }
}} }}
maxDateTime={to} maxDateTime={to}
slotProps={{ textField: { fullWidth: true, size: "small" } }} slotProps={{
textField: { fullWidth: true, size: "small" },
}}
/> />
<DateTimePicker <DateTimePicker
label="结束时间" label="结束时间"
@@ -656,7 +694,9 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
} }
}} }}
minDateTime={from} minDateTime={from}
slotProps={{ textField: { fullWidth: true, size: "small" } }} slotProps={{
textField: { fullWidth: true, size: "small" },
}}
/> />
</Stack> </Stack>
<Stack <Stack
@@ -683,6 +723,23 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
label="表格" label="表格"
/> />
</Tabs> </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="刷新数据"> <Tooltip title="刷新数据">
<span> <span>
<Button <Button
@@ -699,6 +756,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
</Tooltip> </Tooltip>
</Stack> </Stack>
</Stack> </Stack>
</Stack>
</LocalizationProvider> </LocalizationProvider>
{!hasDevices && ( {!hasDevices && (
@@ -743,8 +801,9 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
{activeTab === "chart" ? renderChart() : renderTable()} {activeTab === "chart" ? renderChart() : renderTable()}
</Box> </Box>
</Collapse> </Box>
</Paper> </Drawer>
</>
); );
}; };

View File

@@ -10,7 +10,6 @@ import React, {
} from "react"; } from "react";
import { import {
Box, Box,
Paper,
Typography, Typography,
ListItem, ListItem,
ListItemButton, ListItemButton,
@@ -18,7 +17,6 @@ import {
ListItemIcon, ListItemIcon,
Chip, Chip,
IconButton, IconButton,
Collapse,
FormControl, FormControl,
InputLabel, InputLabel,
Select, Select,
@@ -28,16 +26,25 @@ import {
Divider, Divider,
InputBase, InputBase,
CircularProgress, CircularProgress,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Alert,
Drawer,
} from "@mui/material"; } from "@mui/material";
import { import {
Search, Search,
MyLocation, MyLocation,
ExpandMore,
ExpandLess,
FilterList, FilterList,
Clear, Clear,
DeviceHub, DeviceHub,
TouchApp, TouchApp,
CleaningServices,
Sensors,
ChevronLeft,
ChevronRight,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { FixedSizeList } from "react-window"; import { FixedSizeList } from "react-window";
import { useNotification } from "@refinedev/core"; import { useNotification } from "@refinedev/core";
@@ -53,6 +60,9 @@ import { Point } from "ol/geom";
import { getVectorContext } from "ol/render"; import { getVectorContext } from "ol/render";
import { unByKey } from "ol/Observable"; import { unByKey } from "ol/Observable";
import config from "@/config/config"; import config from "@/config/config";
import dayjs, { Dayjs } from "dayjs";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers";
const STATUS_OPTIONS: { const STATUS_OPTIONS: {
value: "online" | "offline" | "warning" | "error"; value: "online" | "offline" | "warning" | "error";
@@ -84,6 +94,8 @@ interface SCADADeviceListProps {
multiSelect?: boolean; multiSelect?: boolean;
selectedDeviceIds?: string[]; selectedDeviceIds?: string[];
onSelectionChange?: (ids: string[]) => void; onSelectionChange?: (ids: string[]) => void;
showCleaning?: boolean;
onCleanAllData?: (from: Date, to: Date) => void;
} }
const SCADADeviceList: React.FC<SCADADeviceListProps> = ({ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
@@ -92,6 +104,8 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
multiSelect = true, multiSelect = true,
selectedDeviceIds, selectedDeviceIds,
onSelectionChange, onSelectionChange,
showCleaning = false,
onCleanAllData,
}) => { }) => {
const [searchQuery, setSearchQuery] = useState<string>(""); const [searchQuery, setSearchQuery] = useState<string>("");
const [selectedType, setSelectedType] = useState<string>("all"); const [selectedType, setSelectedType] = useState<string>("all");
@@ -113,6 +127,16 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
const blinkListenerKeyRef = useRef<any>(null); const blinkListenerKeyRef = useRef<any>(null);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null); const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const filterBoxRef = useRef<HTMLDivElement | null>(null);
const [listHeight, setListHeight] = useState<number>(500);
// 清洗对话框状态
const [cleanDialogOpen, setCleanDialogOpen] = useState<boolean>(false);
const [cleanStartTime, setCleanStartTime] = useState<Dayjs>(() =>
dayjs().subtract(1, "week")
);
const [cleanEndTime, setCleanEndTime] = useState<Dayjs>(() => dayjs());
const [timeRangeError, setTimeRangeError] = useState<string>("");
// 防抖更新搜索查询 // 防抖更新搜索查询
const debouncedSetSearchQuery = useCallback((value: string) => { const debouncedSetSearchQuery = useCallback((value: string) => {
@@ -497,6 +521,67 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
[map, effectiveDevices, multiSelect, open] [map, effectiveDevices, multiSelect, open]
); );
// 处理清洗对话框关闭
const handleCleanDialogClose = useCallback(() => {
setCleanDialogOpen(false);
setTimeRangeError("");
}, []);
// 验证时间范围
const validateTimeRange = useCallback((start: Dayjs, end: Dayjs): string => {
if (start.isAfter(end)) {
return "开始时间不能晚于结束时间";
}
const daysDiff = end.diff(start, "day");
if (daysDiff > 14) {
return "时间范围不能超过两周14天";
}
return "";
}, []);
// 处理开始时间变化
const handleCleanStartTimeChange = useCallback(
(newValue: Dayjs | Date | null) => {
if (newValue) {
const dayjsValue = dayjs.isDayjs(newValue) ? newValue : dayjs(newValue);
setCleanStartTime(dayjsValue);
const error = validateTimeRange(dayjsValue, cleanEndTime);
setTimeRangeError(error);
}
},
[cleanEndTime, validateTimeRange]
);
// 处理结束时间变化
const handleCleanEndTimeChange = useCallback(
(newValue: Dayjs | Date | null) => {
if (newValue) {
const dayjsValue = dayjs.isDayjs(newValue) ? newValue : dayjs(newValue);
setCleanEndTime(dayjsValue);
const error = validateTimeRange(cleanStartTime, dayjsValue);
setTimeRangeError(error);
}
},
[cleanStartTime, validateTimeRange]
);
// 确认清洗
const handleConfirmClean = useCallback(() => {
const error = validateTimeRange(cleanStartTime, cleanEndTime);
if (error) {
setTimeRangeError(error);
return;
}
onCleanAllData?.(cleanStartTime.toDate(), cleanEndTime.toDate());
handleCleanDialogClose();
}, [
cleanStartTime,
cleanEndTime,
validateTimeRange,
onCleanAllData,
handleCleanDialogClose,
]);
// 开始选择 SCADA 设备 // 开始选择 SCADA 设备
const handleStartSelection = useCallback(() => { const handleStartSelection = useCallback(() => {
if (!map) return; if (!map) return;
@@ -586,8 +671,93 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
}; };
}, []); }, []);
// 动态计算列表高度
useEffect(() => {
const updateListHeight = () => {
if (filterBoxRef.current) {
const drawerHeight = 760; // Drawer 总高度
const headerHeight = 73; // 头部高度(估算)
const dividerHeight = 1; // 分隔线高度
const filterBoxHeight = filterBoxRef.current.offsetHeight;
const availableHeight =
drawerHeight - headerHeight - filterBoxHeight - dividerHeight - 8; // 减去一些边距
setListHeight(Math.max(availableHeight, 200)); // 最小高度 200
}
};
updateListHeight();
// 使用 ResizeObserver 监听筛选框高度变化
const resizeObserver = new ResizeObserver(updateListHeight);
if (filterBoxRef.current) {
resizeObserver.observe(filterBoxRef.current);
}
return () => {
resizeObserver.disconnect();
};
}, [
activeSelection.length,
searchQuery,
selectedType,
selectedStatus,
selectedReliability,
showCleaning,
]);
const drawerWidth = 360;
return ( return (
<Paper className="absolute left-4 top-20 w-90 max-h-[calc(100vh-100px)] bg-white rounded-xl shadow-lg overflow-hidden flex flex-col opacity-95 transition-opacity duration-200 ease-in-out hover:opacity-100"> <>
{/* 收起时的触发按钮 */}
{!isExpanded && (
<Box
className="absolute top-20 left-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">
<Sensors className="text-[#1976d2] w-5 h-5" />
<Typography
variant="caption"
className="text-gray-700 font-semibold my-1 text-xs"
style={{ writingMode: "vertical-rl" }}
>
SCADA设备
</Typography>
<ChevronRight className="text-gray-600 w-4 h-4" />
</Box>
</Box>
)}
{/* 主面板 */}
<Drawer
anchor="left"
open={isExpanded}
variant="persistent"
hideBackdrop
sx={{
width: isExpanded ? drawerWidth : 0,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: drawerWidth,
height: "760px",
boxSizing: "border-box",
position: "absolute",
top: 80,
left: 16,
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,
},
},
}}
>
<Box className="flex flex-col h-full bg-white rounded-xl overflow-hidden">
{/* 头部控制栏 */} {/* 头部控制栏 */}
<Box <Box
sx={{ sx={{
@@ -604,7 +774,16 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
justifyContent="space-between" justifyContent="space-between"
> >
<Stack direction="row" alignItems="center" spacing={1}> <Stack direction="row" alignItems="center" spacing={1}>
<DeviceHub fontSize="small" /> <Tooltip title="收起">
<IconButton
size="small"
onClick={() => setIsExpanded(false)}
sx={{ color: "white" }}
>
<ChevronLeft fontSize="small" />
</IconButton>
</Tooltip>
<Sensors fontSize="small" />
<Typography variant="h6" sx={{ fontWeight: "bold" }}> <Typography variant="h6" sx={{ fontWeight: "bold" }}>
SCADA SCADA
</Typography> </Typography>
@@ -618,23 +797,11 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
}} }}
/> />
</Stack> </Stack>
<Stack direction="row" spacing={1}>
<Tooltip title="展开/收起">
<IconButton
size="small"
onClick={() => setIsExpanded(!isExpanded)}
sx={{ color: "white" }}
>
{isExpanded ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Tooltip>
</Stack>
</Stack> </Stack>
</Box> </Box>
<Collapse in={isExpanded}>
{/* 搜索和筛选栏 */} {/* 搜索和筛选栏 */}
<Box sx={{ p: 2, backgroundColor: "grey.50" }}> <Box ref={filterBoxRef} sx={{ p: 2, backgroundColor: "grey.50" }}>
<Stack spacing={2}> <Stack spacing={2}>
{/* 搜索框 */} {/* 搜索框 */}
<Box className="h-10 flex items-center border border-gray-300 rounded-md p-0.5"> <Box className="h-10 flex items-center border border-gray-300 rounded-md p-0.5">
@@ -653,7 +820,10 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
</IconButton> </IconButton>
{searchQuery && ( {searchQuery && (
<> <>
<Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" /> <Divider
sx={{ height: 28, m: 0.5 }}
orientation="vertical"
/>
<IconButton <IconButton
color="primary" color="primary"
sx={{ p: "6px" }} sx={{ p: "6px" }}
@@ -730,7 +900,9 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
</Typography> </Typography>
{/* 地图选择按钮 */} {/* 地图选择按钮 */}
<Tooltip title={isSelecting ? "结束地图选择" : "从地图选择设备"}> <Tooltip
title={isSelecting ? "结束地图选择" : "从地图选择设备"}
>
<IconButton <IconButton
size="small" size="small"
color={isSelecting ? "primary" : "default"} color={isSelecting ? "primary" : "default"}
@@ -740,15 +912,39 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
sx={{ sx={{
border: 1, border: 1,
borderColor: isSelecting ? "primary.main" : "divider", borderColor: isSelecting ? "primary.main" : "divider",
backgroundColor: isSelecting ? "primary.50" : "transparent", backgroundColor: isSelecting
? "primary.50"
: "transparent",
"&:hover": { "&:hover": {
backgroundColor: isSelecting ? "primary.100" : "grey.100", backgroundColor: isSelecting
? "primary.100"
: "grey.100",
}, },
}} }}
> >
<TouchApp fontSize="small" /> <TouchApp fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
{/* 清洗全部数据按钮 */}
{showCleaning && (
<Tooltip title="清洗全部数据">
<IconButton
size="small"
color="secondary"
onClick={() => setCleanDialogOpen(true)}
sx={{
border: 1,
borderColor: "secondary.main",
"&:hover": {
backgroundColor: "secondary.50",
},
}}
>
<CleaningServices fontSize="small" />
</IconButton>
</Tooltip>
)}
</Stack> </Stack>
{/* 清除选择按钮 */} {/* 清除选择按钮 */}
@@ -770,7 +966,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
<Divider /> <Divider />
{/* 设备列表 */} {/* 设备列表 */}
<Box sx={{ flex: 1, overflow: "auto", maxHeight: 400 }}> <Box sx={{ flex: 1, overflow: "hidden" }}>
{loading ? ( {loading ? (
<Box <Box
sx={{ sx={{
@@ -801,7 +997,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
</Box> </Box>
) : ( ) : (
<FixedSizeList <FixedSizeList
height={400} height={listHeight}
itemCount={filteredDevices.length} itemCount={filteredDevices.length}
itemSize={92} itemSize={92}
width="100%" width="100%"
@@ -930,8 +1126,93 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
</FixedSizeList> </FixedSizeList>
)} )}
</Box> </Box>
</Collapse> </Box>
</Paper>
{/* 清洗数据时间段选择对话框 */}
<Dialog
open={cleanDialogOpen}
onClose={handleCleanDialogClose}
maxWidth="sm"
fullWidth
>
<DialogTitle>
<Stack direction="row" alignItems="center" spacing={1}>
<CleaningServices color="secondary" />
<Typography variant="h6"></Typography>
</Stack>
</DialogTitle>
<DialogContent>
<Stack spacing={3} sx={{ mt: 2 }}>
<Alert severity="info">
14
</Alert>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateTimePicker
label="开始时间"
value={cleanStartTime}
onChange={handleCleanStartTimeChange}
maxDateTime={cleanEndTime}
slotProps={{
textField: {
fullWidth: true,
error: !!timeRangeError,
},
}}
/>
<DateTimePicker
label="结束时间"
value={cleanEndTime}
onChange={handleCleanEndTimeChange}
minDateTime={cleanStartTime}
maxDateTime={dayjs()}
slotProps={{
textField: {
fullWidth: true,
error: !!timeRangeError,
},
}}
/>
</LocalizationProvider>
{timeRangeError && (
<Alert severity="error">{timeRangeError}</Alert>
)}
<Box sx={{ p: 2, backgroundColor: "grey.50", borderRadius: 1 }}>
<Typography variant="body2" color="text.secondary">
<strong></strong>
{cleanStartTime.format("YYYY-MM-DD HH:mm")} {" "}
{cleanEndTime.format("YYYY-MM-DD HH:mm")}
</Typography>
<Typography
variant="body2"
color="text.secondary"
sx={{ mt: 1 }}
>
<strong></strong>
{cleanEndTime.diff(cleanStartTime, "day")} {" "}
{cleanEndTime.diff(cleanStartTime, "hour") % 24}
</Typography>
</Box>
</Stack>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={handleCleanDialogClose}></Button>
<Button
variant="contained"
color="secondary"
onClick={handleConfirmClean}
disabled={!!timeRangeError}
startIcon={<CleaningServices />}
>
</Button>
</DialogActions>
</Dialog>
</Drawer>
</>
); );
}; };