完善Toolbar和HistoryDataPanel的交互函数
This commit is contained in:
@@ -90,7 +90,7 @@ const fetchFromBackend = async (
|
|||||||
const end_time = dayjs(range.to).toISOString();
|
const end_time = dayjs(range.to).toISOString();
|
||||||
|
|
||||||
// 监测值数据接口
|
// 监测值数据接口
|
||||||
const monitoredDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=monitored_value&start_time=${start_time}&end_time=${end_time}`;
|
const rawDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=raw_value&start_time=${start_time}&end_time=${end_time}`;
|
||||||
// 清洗数据接口
|
// 清洗数据接口
|
||||||
const cleanedDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=cleaned_value&start_time=${start_time}&end_time=${end_time}`;
|
const cleanedDataUrl = `${config.BACKEND_URL}/timescaledb/scada/by-ids-field-time-range?device_ids=${device_ids}&field=cleaned_value&start_time=${start_time}&end_time=${end_time}`;
|
||||||
// 模拟数据接口
|
// 模拟数据接口
|
||||||
@@ -101,32 +101,32 @@ const fetchFromBackend = async (
|
|||||||
try {
|
try {
|
||||||
if (type === "none") {
|
if (type === "none") {
|
||||||
// 查询清洗值和监测值
|
// 查询清洗值和监测值
|
||||||
const [cleanedRes, monitoredRes] = await Promise.all([
|
const [cleanedRes, rawRes] = await Promise.all([
|
||||||
fetch(cleanedDataUrl)
|
fetch(cleanedDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
fetch(monitoredDataUrl)
|
fetch(rawDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
||||||
const monitoredData = transformBackendData(monitoredRes, deviceIds);
|
const rawData = transformBackendData(rawRes, deviceIds);
|
||||||
|
|
||||||
return mergeTimeSeriesData(
|
return mergeTimeSeriesData(
|
||||||
cleanedData,
|
cleanedData,
|
||||||
monitoredData,
|
rawData,
|
||||||
deviceIds,
|
deviceIds,
|
||||||
"clean",
|
"clean",
|
||||||
"monitored"
|
"raw"
|
||||||
);
|
);
|
||||||
} else if (type === "scheme") {
|
} else if (type === "scheme") {
|
||||||
// 查询策略模拟值、清洗值和监测值
|
// 查询策略模拟值、清洗值和监测值
|
||||||
const [cleanedRes, monitoredRes, schemeSimRes] = await Promise.all([
|
const [cleanedRes, rawRes, schemeSimRes] = await Promise.all([
|
||||||
fetch(cleanedDataUrl)
|
fetch(cleanedDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
fetch(monitoredDataUrl)
|
fetch(rawDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
fetch(schemeSimulationDataUrl)
|
fetch(schemeSimulationDataUrl)
|
||||||
@@ -135,14 +135,14 @@ const fetchFromBackend = async (
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
||||||
const monitoredData = transformBackendData(monitoredRes, deviceIds);
|
const rawData = transformBackendData(rawRes, deviceIds);
|
||||||
const schemeSimData = transformBackendData(schemeSimRes, deviceIds);
|
const schemeSimData = transformBackendData(schemeSimRes, deviceIds);
|
||||||
|
|
||||||
// 合并三组数据
|
// 合并三组数据
|
||||||
const timeMap = new Map<string, Record<string, number | null>>();
|
const timeMap = new Map<string, Record<string, number | null>>();
|
||||||
|
|
||||||
[cleanedData, monitoredData, schemeSimData].forEach((data, index) => {
|
[cleanedData, rawData, schemeSimData].forEach((data, index) => {
|
||||||
const suffix = ["clean", "monitored", "scheme_sim"][index];
|
const suffix = ["clean", "raw", "scheme_sim"][index];
|
||||||
data.forEach((point) => {
|
data.forEach((point) => {
|
||||||
if (!timeMap.has(point.timestamp)) {
|
if (!timeMap.has(point.timestamp)) {
|
||||||
timeMap.set(point.timestamp, {});
|
timeMap.set(point.timestamp, {});
|
||||||
@@ -172,11 +172,11 @@ const fetchFromBackend = async (
|
|||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
// realtime: 查询模拟值、清洗值和监测值
|
// realtime: 查询模拟值、清洗值和监测值
|
||||||
const [cleanedRes, monitoredRes, simulationRes] = await Promise.all([
|
const [cleanedRes, rawRes, simulationRes] = await Promise.all([
|
||||||
fetch(cleanedDataUrl)
|
fetch(cleanedDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
fetch(monitoredDataUrl)
|
fetch(rawDataUrl)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
fetch(simulationDataUrl)
|
fetch(simulationDataUrl)
|
||||||
@@ -185,14 +185,14 @@ const fetchFromBackend = async (
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
const cleanedData = transformBackendData(cleanedRes, deviceIds);
|
||||||
const monitoredData = transformBackendData(monitoredRes, deviceIds);
|
const rawData = transformBackendData(rawRes, deviceIds);
|
||||||
const simulationData = transformBackendData(simulationRes, deviceIds);
|
const simulationData = transformBackendData(simulationRes, deviceIds);
|
||||||
|
|
||||||
// 合并三组数据
|
// 合并三组数据
|
||||||
const timeMap = new Map<string, Record<string, number | null>>();
|
const timeMap = new Map<string, Record<string, number | null>>();
|
||||||
|
|
||||||
[cleanedData, monitoredData, simulationData].forEach((data, index) => {
|
[cleanedData, rawData, simulationData].forEach((data, index) => {
|
||||||
const suffix = ["clean", "monitored", "sim"][index];
|
const suffix = ["clean", "raw", "sim"][index];
|
||||||
data.forEach((point) => {
|
data.forEach((point) => {
|
||||||
if (!timeMap.has(point.timestamp)) {
|
if (!timeMap.has(point.timestamp)) {
|
||||||
timeMap.set(point.timestamp, {});
|
timeMap.set(point.timestamp, {});
|
||||||
@@ -351,7 +351,7 @@ const buildDataset = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
deviceIds.forEach((id) => {
|
deviceIds.forEach((id) => {
|
||||||
["clean", "monitored", "sim", "scheme_sim"].forEach((suffix) => {
|
["clean", "raw", "sim", "scheme_sim"].forEach((suffix) => {
|
||||||
const key = `${id}_${suffix}`;
|
const key = `${id}_${suffix}`;
|
||||||
const value = point.values[key];
|
const value = point.values[key];
|
||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
@@ -520,23 +520,49 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const dynamic = (() => {
|
const dynamic = (() => {
|
||||||
return deviceIds.map<GridColDef>((id) => ({
|
const cols: GridColDef[] = [];
|
||||||
field: id,
|
|
||||||
headerName: deviceLabels?.[id] ?? id,
|
deviceIds.forEach((id) => {
|
||||||
minWidth: 140,
|
const deviceName = deviceLabels?.[id] ?? id;
|
||||||
flex: 1,
|
|
||||||
valueFormatter: (value: any) => {
|
// 为每个设备的每种数据类型创建列
|
||||||
if (value === null || value === undefined) return "--";
|
const suffixes = [
|
||||||
if (Number.isFinite(Number(value))) {
|
{ key: "clean", name: "清洗值" },
|
||||||
return Number(value).toFixed(fractionDigits);
|
{ key: "raw", name: "监测值" },
|
||||||
|
{ key: "sim", name: "模拟值" },
|
||||||
|
{ key: "scheme_sim", name: "策略模拟值" },
|
||||||
|
];
|
||||||
|
|
||||||
|
suffixes.forEach(({ key, name }) => {
|
||||||
|
const fieldKey = `${id}_${key}`;
|
||||||
|
// 检查是否有该字段的数据
|
||||||
|
const hasData = dataset.some(
|
||||||
|
(item) => item[fieldKey] !== null && item[fieldKey] !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasData) {
|
||||||
|
cols.push({
|
||||||
|
field: fieldKey,
|
||||||
|
headerName: `${deviceName} (${name})`,
|
||||||
|
minWidth: 140,
|
||||||
|
flex: 1,
|
||||||
|
valueFormatter: (value: any) => {
|
||||||
|
if (value === null || value === undefined) return "--";
|
||||||
|
if (Number.isFinite(Number(value))) {
|
||||||
|
return Number(value).toFixed(fractionDigits);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return String(value);
|
});
|
||||||
},
|
});
|
||||||
}));
|
|
||||||
|
return cols;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return [...base, ...dynamic];
|
return [...base, ...dynamic];
|
||||||
}, [deviceIds, deviceLabels, fractionDigits, selectedSource]);
|
}, [deviceIds, deviceLabels, fractionDigits, dataset]);
|
||||||
|
|
||||||
const rows = useMemo(
|
const rows = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -597,48 +623,46 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
const getSeries = () => {
|
const getSeries = () => {
|
||||||
return deviceIds.flatMap((id, index) => {
|
return deviceIds.flatMap((id, index) => {
|
||||||
const series = [];
|
const series = [];
|
||||||
["clean", "monitored", "sim", "scheme_sim"].forEach(
|
["clean", "raw", "sim", "scheme_sim"].forEach((suffix, sIndex) => {
|
||||||
(suffix, sIndex) => {
|
const key = `${id}_${suffix}`;
|
||||||
const key = `${id}_${suffix}`;
|
const hasData = dataset.some(
|
||||||
const hasData = dataset.some(
|
(item) => item[key] !== null && item[key] !== undefined
|
||||||
(item) => item[key] !== null && item[key] !== undefined
|
);
|
||||||
);
|
if (hasData) {
|
||||||
if (hasData) {
|
const displayName =
|
||||||
const displayName =
|
suffix === "clean"
|
||||||
suffix === "clean"
|
? "清洗值"
|
||||||
? "清洗值"
|
: suffix === "raw"
|
||||||
: suffix === "monitored"
|
? "监测值"
|
||||||
? "监测值"
|
: suffix === "sim"
|
||||||
: suffix === "sim"
|
? "模拟"
|
||||||
? "模拟"
|
: "策略模拟";
|
||||||
: "策略模拟";
|
|
||||||
|
|
||||||
series.push({
|
series.push({
|
||||||
name: `${deviceLabels?.[id] ?? id} (${displayName})`,
|
name: `${deviceLabels?.[id] ?? id} (${displayName})`,
|
||||||
type: "line",
|
type: "line",
|
||||||
symbol: "none",
|
symbol: "none",
|
||||||
sampling: "lttb",
|
sampling: "lttb",
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colors[(index * 4 + sIndex) % colors.length],
|
color: colors[(index * 4 + sIndex) % colors.length],
|
||||||
},
|
},
|
||||||
data: dataset.map((item) => item[key]),
|
data: dataset.map((item) => item[key]),
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{
|
{
|
||||||
offset: 0,
|
offset: 0,
|
||||||
color: colors[(index * 4 + sIndex) % colors.length],
|
color: colors[(index * 4 + sIndex) % colors.length],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offset: 1,
|
offset: 1,
|
||||||
color: "rgba(255, 255, 255, 0)",
|
color: "rgba(255, 255, 255, 0)",
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
// 如果没有任何数据,则使用fallback
|
// 如果没有任何数据,则使用fallback
|
||||||
if (series.length === 0) {
|
if (series.length === 0) {
|
||||||
series.push({
|
series.push({
|
||||||
@@ -955,7 +979,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
color="warning.main"
|
color="warning.main"
|
||||||
sx={{ mt: 1, display: "block" }}
|
sx={{ mt: 1, display: "block" }}
|
||||||
>
|
>
|
||||||
未选择任何设备,无法获取数据。
|
请选择一个要素以查看其历史数据。
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
|
|||||||
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
|
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
|
||||||
const [highlightLayer, setHighlightLayer] =
|
const [highlightLayer, setHighlightLayer] =
|
||||||
useState<VectorLayer<VectorSource> | null>(null);
|
useState<VectorLayer<VectorSource> | null>(null);
|
||||||
|
const [featureId, setFeatureId] = useState<string>("");
|
||||||
|
|
||||||
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
||||||
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
||||||
@@ -186,14 +187,30 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
|
|||||||
);
|
);
|
||||||
// 添加矢量属性查询事件监听器
|
// 添加矢量属性查询事件监听器
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeTools.includes("info") || !map) return;
|
if (!map) return;
|
||||||
map.on("click", handleMapClickSelectFeatures);
|
// 监听 info 或 history 工具激活时添加
|
||||||
|
if (activeTools.includes("info") || activeTools.includes("history")) {
|
||||||
|
map.on("click", handleMapClickSelectFeatures);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
map.un("click", handleMapClickSelectFeatures);
|
map.un("click", handleMapClickSelectFeatures);
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}, [activeTools, map, handleMapClickSelectFeatures]);
|
}, [activeTools, map, handleMapClickSelectFeatures]);
|
||||||
|
|
||||||
|
// 监听 highlightFeature 变化,更新 featureId
|
||||||
|
useEffect(() => {
|
||||||
|
if (highlightFeature) {
|
||||||
|
const id = highlightFeature.getProperties().id;
|
||||||
|
if (id) {
|
||||||
|
setFeatureId(id);
|
||||||
|
}
|
||||||
|
console.log("高亮要素 ID:", id);
|
||||||
|
} else {
|
||||||
|
setFeatureId("");
|
||||||
|
}
|
||||||
|
}, [highlightFeature]);
|
||||||
|
|
||||||
// 处理工具栏按钮点击事件
|
// 处理工具栏按钮点击事件
|
||||||
const handleToolClick = (tool: string) => {
|
const handleToolClick = (tool: string) => {
|
||||||
// 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭
|
// 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭
|
||||||
@@ -652,7 +669,14 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
|
|||||||
setLayerStyleStates={setLayerStyleStates}
|
setLayerStyleStates={setLayerStyleStates}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showHistoryPanel && <HistoryDataPanel deviceIds={[]} />}
|
{showHistoryPanel && (
|
||||||
|
<HistoryDataPanel
|
||||||
|
deviceIds={featureId ? [featureId] : []}
|
||||||
|
scheme_type="burst_Analysis"
|
||||||
|
scheme_name={schemeName}
|
||||||
|
type={queryType as "realtime" | "scheme" | "none"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 图例显示 */}
|
{/* 图例显示 */}
|
||||||
{activeLegendConfigs.length > 0 && (
|
{activeLegendConfigs.length > 0 && (
|
||||||
|
|||||||
@@ -648,24 +648,50 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return deviceIds.map<GridColDef>((id) => ({
|
// 非清洗模式:显示所有设备的所有有数据的列
|
||||||
field: id,
|
const cols: GridColDef[] = [];
|
||||||
headerName: deviceLabels?.[id] ?? id,
|
|
||||||
minWidth: 140,
|
deviceIds.forEach((id) => {
|
||||||
flex: 1,
|
const deviceName = deviceLabels?.[id] ?? id;
|
||||||
valueFormatter: (value: any) => {
|
|
||||||
if (value === null || value === undefined) return "--";
|
// 为每个设备的每种数据类型创建列
|
||||||
if (Number.isFinite(Number(value))) {
|
const suffixes = [
|
||||||
return Number(value).toFixed(fractionDigits);
|
{ key: 'raw', name: '原始值' },
|
||||||
|
{ key: 'clean', name: '清洗值' },
|
||||||
|
{ key: 'sim', name: '模拟值' }
|
||||||
|
];
|
||||||
|
|
||||||
|
suffixes.forEach(({ key, name }) => {
|
||||||
|
const fieldKey = `${id}_${key}`;
|
||||||
|
// 检查是否有该字段的数据
|
||||||
|
const hasData = dataset.some(
|
||||||
|
(item) => item[fieldKey] !== null && item[fieldKey] !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasData) {
|
||||||
|
cols.push({
|
||||||
|
field: fieldKey,
|
||||||
|
headerName: `${deviceName} (${name})`,
|
||||||
|
minWidth: 140,
|
||||||
|
flex: 1,
|
||||||
|
valueFormatter: (value: any) => {
|
||||||
|
if (value === null || value === undefined) return "--";
|
||||||
|
if (Number.isFinite(Number(value))) {
|
||||||
|
return Number(value).toFixed(fractionDigits);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return String(value);
|
});
|
||||||
},
|
});
|
||||||
}));
|
|
||||||
|
return cols;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return [...base, ...dynamic];
|
return [...base, ...dynamic];
|
||||||
}, [deviceIds, deviceLabels, fractionDigits, showCleaning, selectedSource]);
|
}, [deviceIds, deviceLabels, fractionDigits, showCleaning, selectedSource, dataset]);
|
||||||
|
|
||||||
const rows = useMemo(
|
const rows = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user