组件居中,可拖拽

This commit is contained in:
JIANG
2025-12-16 17:21:04 +08:00
parent 3aa68d796b
commit 15f48d0496

View File

@@ -1,6 +1,14 @@
"use client"; "use client";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import Draggable from "react-draggable";
import { import {
Box, Box,
Button, Button,
@@ -294,6 +302,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
const [selectedSource, setSelectedSource] = useState< const [selectedSource, setSelectedSource] = useState<
"raw" | "clean" | "sim" | "all" "raw" | "clean" | "sim" | "all"
>(() => (deviceIds.length === 1 ? "all" : "clean")); >(() => (deviceIds.length === 1 ? "all" : "clean"));
const draggableRef = useRef<HTMLDivElement>(null);
// 获取 SCADA 设备信息,生成 deviceLabels // 获取 SCADA 设备信息,生成 deviceLabels
useEffect(() => { useEffect(() => {
@@ -648,214 +657,216 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
return ( return (
<> <>
{/* 主面板 */} {/* 主面板 */}
<Box <Draggable nodeRef={draggableRef}>
sx={{
position: "fixed",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
width: "min(920px, calc(100vw - 2rem))",
maxWidth: "100vw",
height: "860px",
maxHeight: "calc(100vh - 2rem)",
boxSizing: "border-box",
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: "opacity 0.3s ease-in-out",
border: "none",
display: "flex",
flexDirection: "column",
zIndex: 1300,
backgroundColor: "white",
overflow: "hidden",
"&:hover": {
opacity: 1,
},
}}
>
<Box <Box
className="flex flex-col h-full rounded-xl" ref={draggableRef}
sx={{ height: "100%", width: "100%" }} sx={{
position: "absolute",
right: "1rem",
top: "1rem",
width: "min(920px, calc(100vw - 2rem))",
maxWidth: "100vw",
height: "860px",
maxHeight: "calc(100vh - 2rem)",
boxSizing: "border-box",
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: "opacity 0.3s ease-in-out",
border: "none",
display: "flex",
flexDirection: "column",
zIndex: 1300,
backgroundColor: "white",
overflow: "hidden",
"&:hover": {
opacity: 1,
},
}}
> >
{/* Header */}
<Box <Box
sx={{ className="flex flex-col h-full rounded-xl"
p: 2, sx={{ height: "100%", width: "100%" }}
borderBottom: 1,
borderColor: "divider",
backgroundColor: "primary.main",
color: "primary.contrastText",
}}
> >
<Stack {/* Header */}
direction="row" <Box
alignItems="center" sx={{
justifyContent="space-between" p: 2,
borderBottom: 1,
borderColor: "divider",
backgroundColor: "primary.main",
color: "primary.contrastText",
}}
> >
<Stack direction="row" spacing={1} alignItems="center"> <Stack
<ShowChart fontSize="small" /> direction="row"
<Typography variant="h6" sx={{ fontWeight: "bold" }}> alignItems="center"
justifyContent="space-between"
</Typography> >
<Chip
size="small"
label={`${deviceIds.length}`}
sx={{
backgroundColor: "rgba(255,255,255,0.2)",
color: "primary.contrastText",
fontWeight: "bold",
}}
/>
</Stack>
</Stack>
</Box>
{/* Controls */}
<Box sx={{ p: 2, backgroundColor: "grey.50" }}>
<LocalizationProvider
dateAdapter={AdapterDayjs}
adapterLocale="zh-cn"
>
<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}
onChange={(value) => { </Typography>
if (value && dayjs.isDayjs(value) && value.isValid()) { <Chip
setFrom(value); size="small"
} label={`${deviceIds.length}`}
}} sx={{
onAccept={(value) => { backgroundColor: "rgba(255,255,255,0.2)",
if ( color: "primary.contrastText",
value && fontWeight: "bold",
dayjs.isDayjs(value) &&
value.isValid() &&
hasDevices
) {
handleFetch("date-change");
}
}}
maxDateTime={to}
slotProps={{
textField: { fullWidth: true, size: "small" },
}}
/>
<DateTimePicker
label="结束时间"
value={to}
onChange={(value) => {
if (value && dayjs.isDayjs(value) && value.isValid()) {
setTo(value);
}
}}
onAccept={(value) => {
if (
value &&
dayjs.isDayjs(value) &&
value.isValid() &&
hasDevices
) {
handleFetch("date-change");
}
}}
minDateTime={from}
slotProps={{
textField: { fullWidth: true, size: "small" },
}} }}
/> />
</Stack> </Stack>
<Stack </Stack>
direction="row" </Box>
spacing={1}
alignItems="center" {/* Controls */}
justifyContent="space-between" <Box sx={{ p: 2, backgroundColor: "grey.50" }}>
> <LocalizationProvider
<Tabs dateAdapter={AdapterDayjs}
value={activeTab} adapterLocale="zh-cn"
onChange={(_, value: PanelTab) => setActiveTab(value)} >
variant="fullWidth" <Stack spacing={1.5}>
<Stack direction="row" spacing={1} alignItems="center">
<DateTimePicker
label="开始时间"
value={from}
onChange={(value) => {
if (value && dayjs.isDayjs(value) && value.isValid()) {
setFrom(value);
}
}}
onAccept={(value) => {
if (
value &&
dayjs.isDayjs(value) &&
value.isValid() &&
hasDevices
) {
handleFetch("date-change");
}
}}
maxDateTime={to}
slotProps={{
textField: { fullWidth: true, size: "small" },
}}
/>
<DateTimePicker
label="结束时间"
value={to}
onChange={(value) => {
if (value && dayjs.isDayjs(value) && value.isValid()) {
setTo(value);
}
}}
onAccept={(value) => {
if (
value &&
dayjs.isDayjs(value) &&
value.isValid() &&
hasDevices
) {
handleFetch("date-change");
}
}}
minDateTime={from}
slotProps={{
textField: { fullWidth: true, size: "small" },
}}
/>
</Stack>
<Stack
direction="row"
spacing={1}
alignItems="center"
justifyContent="space-between"
> >
<Tab <Tabs
value="chart" value={activeTab}
icon={<ShowChart fontSize="small" />} onChange={(_, value: PanelTab) => setActiveTab(value)}
iconPosition="start" variant="fullWidth"
label="曲线" >
/> <Tab
<Tab value="chart"
value="table" icon={<ShowChart fontSize="small" />}
icon={<TableChart fontSize="small" />} iconPosition="start"
iconPosition="start" label="曲线"
label="表格" />
/> <Tab
</Tabs> value="table"
<Stack direction="row" spacing={1}> icon={<TableChart fontSize="small" />}
<Tooltip title="刷新数据"> iconPosition="start"
<span> label="表格"
<Button />
variant="outlined" </Tabs>
size="small" <Stack direction="row" spacing={1}>
color="primary" <Tooltip title="刷新数据">
startIcon={<Refresh fontSize="small" />} <span>
disabled={!hasDevices || loadingState === "loading"} <Button
onClick={() => handleFetch("manual")} variant="outlined"
> size="small"
color="primary"
</Button> startIcon={<Refresh fontSize="small" />}
</span> disabled={!hasDevices || loadingState === "loading"}
</Tooltip> onClick={() => handleFetch("manual")}
>
</Button>
</span>
</Tooltip>
</Stack>
</Stack> </Stack>
</Stack> </Stack>
</Stack> </LocalizationProvider>
</LocalizationProvider>
{!hasDevices && ( {!hasDevices && (
<Typography <Typography
variant="caption" variant="caption"
color="warning.main" color="warning.main"
sx={{ mt: 1, display: "block" }} sx={{ mt: 1, display: "block" }}
> >
</Typography> </Typography>
)} )}
{error && ( {error && (
<Typography <Typography
variant="caption" variant="caption"
color="error" color="error"
sx={{ mt: 1, display: "block" }} sx={{ mt: 1, display: "block" }}
> >
{error} {error}
</Typography> </Typography>
)} )}
</Box> </Box>
<Divider /> <Divider />
{/* Content */} {/* Content */}
<Box sx={{ flex: 1, position: "relative", p: 2, overflow: "auto" }}> <Box sx={{ flex: 1, position: "relative", p: 2, overflow: "auto" }}>
{loadingState === "loading" && ( {loadingState === "loading" && (
<Box <Box
sx={{ sx={{
position: "absolute", position: "absolute",
inset: 0, inset: 0,
backgroundColor: "rgba(255,255,255,0.6)", backgroundColor: "rgba(255,255,255,0.6)",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
zIndex: 1, zIndex: 1,
}} }}
> >
<CircularProgress size={48} /> <CircularProgress size={48} />
</Box> </Box>
)} )}
{activeTab === "chart" ? renderChart() : renderTable()} {activeTab === "chart" ? renderChart() : renderTable()}
</Box>
</Box> </Box>
</Box> </Box>
</Box> </Draggable>
</> </>
); );
}; };