diff --git a/src/components/olmap/BurstDetection/DetectionResults.tsx b/src/components/olmap/BurstDetection/DetectionResults.tsx index 6c183c5..516b4d3 100644 --- a/src/components/olmap/BurstDetection/DetectionResults.tsx +++ b/src/components/olmap/BurstDetection/DetectionResults.tsx @@ -23,6 +23,7 @@ import { bbox, featureCollection } from "@turf/turf"; import { useMap } from "@components/olmap/core/MapComponent"; import { queryFeaturesByIds } from "@/utils/mapQueryService"; import { BurstDetectionResult, BurstDetectionRow } from "./types"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; export interface BurstDetectionResultsState { selectedDay: number | null; @@ -73,17 +74,11 @@ const formatDateTime = (value?: string) => value ? dayjs(value).format("YYYY-MM-DD HH:mm") : "-"; const EmptyState = () => ( - - - - - - 等待侦测结果 - - - 提交侦测后,这里会展示目标时刻状态、前 14 天参考分数和异常测点。 - - + } + title="尚未生成侦测结果" + description="请在“侦测参数”中运行分析,或在“方案查询”中打开历史结果。" + /> ); const DetectionResults: React.FC = ({ result, state, onStateChange }) => { diff --git a/src/components/olmap/BurstDetection/SchemeQuery.tsx b/src/components/olmap/BurstDetection/SchemeQuery.tsx index 4385e8c..7486bda 100644 --- a/src/components/olmap/BurstDetection/SchemeQuery.tsx +++ b/src/components/olmap/BurstDetection/SchemeQuery.tsx @@ -24,6 +24,7 @@ import { useNotification } from "@refinedev/core"; import { api } from "@/lib/api"; import { NETWORK_NAME } from "@config/config"; import { useControllableObjectState } from "@components/olmap/core/useControllableState"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; import { BurstDetectionResult, BurstDetectionSchemeDetail, @@ -42,6 +43,7 @@ export interface BurstDetectionSchemeQueryState { queryAll: boolean; queryDate: Dayjs | null; expandedId: number | null; + hasQueried: boolean; } export const createBurstDetectionSchemeQueryState = @@ -49,6 +51,7 @@ export const createBurstDetectionSchemeQueryState = queryAll: true, queryDate: dayjs(), expandedId: null, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -64,7 +67,7 @@ const SchemeQuery: React.FC = ({ onStateChange, createBurstDetectionSchemeQueryState(), ); - const { queryAll, queryDate, expandedId } = queryState; + const { queryAll, queryDate, expandedId, hasQueried } = queryState; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes; @@ -138,6 +141,7 @@ const SchemeQuery: React.FC = ({ const response = await api.get("/api/v1/schemes", { params }); const nextSchemes = response.data as BurstDetectionSchemeRecord[]; setSchemes(nextSchemes); + setQueryField("hasQueried", true); open?.({ type: "success", message: "查询成功", @@ -203,7 +207,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryAll", event.target.checked)} + onChange={(event) => { + setQueryField("queryAll", event.target.checked); + setQueryField("hasQueried", false); + }} /> } label={查询全部} @@ -212,7 +219,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryDate", value)} + onChange={(value) => { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + }} disabled={queryAll} format="YYYY-MM-DD" slotProps={{ textField: { size: "small", sx: { width: 180 } } }} @@ -234,12 +244,7 @@ const SchemeQuery: React.FC = ({ {sortedSchemes.length === 0 ? ( - - 暂无侦测方案 - - 运行一次展示版侦测后,可在这里回看历史结果。 - - + ) : ( diff --git a/src/components/olmap/BurstLocation/LocationResults.tsx b/src/components/olmap/BurstLocation/LocationResults.tsx index 6bf7a8a..809171e 100644 --- a/src/components/olmap/BurstLocation/LocationResults.tsx +++ b/src/components/olmap/BurstLocation/LocationResults.tsx @@ -31,6 +31,7 @@ import { Stroke, Style, Circle, Fill } from "ol/style"; import { bbox, featureCollection } from "@turf/turf"; import { BurstCandidate, BurstLocationResult } from "./types"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; interface Props { result: BurstLocationResult | null; @@ -128,17 +129,11 @@ const MetricCard = ({ label, value, hint, tone }: MetricCardProps) => { }; const EmptyState = () => ( - - - - - - 等待定位结果 - - - 请先提交爆管定位分析,结果面板将展示定位摘要、时间窗、采样情况和候选管段。 - - + } + title="尚未生成定位结果" + description="请在“定位参数”中运行分析,或在“方案查询”中打开历史结果。" + /> ); const LocationResults: React.FC = ({ result }) => { diff --git a/src/components/olmap/BurstLocation/SchemeQuery.tsx b/src/components/olmap/BurstLocation/SchemeQuery.tsx index 5899981..23127d7 100644 --- a/src/components/olmap/BurstLocation/SchemeQuery.tsx +++ b/src/components/olmap/BurstLocation/SchemeQuery.tsx @@ -43,6 +43,7 @@ import { } from "./types"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface Props { onViewResult: (result: BurstLocationResult) => void; @@ -57,6 +58,7 @@ export interface BurstLocationSchemeQueryState { queryDate: Dayjs | null; expandedId: number | null; simulationBurstIdsByName: Record; + hasQueried: boolean; } export const createBurstLocationSchemeQueryState = @@ -65,6 +67,7 @@ export const createBurstLocationSchemeQueryState = queryDate: dayjs(), expandedId: null, simulationBurstIdsByName: {}, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -83,7 +86,7 @@ const SchemeQuery: React.FC = ({ onStateChange, createBurstLocationSchemeQueryState(), ); - const { queryAll, queryDate, expandedId } = queryState; + const { queryAll, queryDate, expandedId, hasQueried } = queryState; const simulationBurstIdsByName = queryState.simulationBurstIdsByName ?? {}; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); @@ -256,6 +259,7 @@ const SchemeQuery: React.FC = ({ ), ), ); + setQueryField("hasQueried", true); open?.({ type: "success", message: "查询成功", @@ -320,7 +324,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} /> } label={查询全部} @@ -329,7 +336,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryDate", value)} + onChange={(value) => { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + }} disabled={queryAll} format="YYYY-MM-DD" slotProps={{ textField: { size: "small", sx: { width: 200 } } }} @@ -350,39 +360,7 @@ const SchemeQuery: React.FC = ({ {sortedSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/BurstSimulation/AnalysisReport.test.tsx b/src/components/olmap/BurstSimulation/AnalysisReport.test.tsx index 1571911..4032017 100644 --- a/src/components/olmap/BurstSimulation/AnalysisReport.test.tsx +++ b/src/components/olmap/BurstSimulation/AnalysisReport.test.tsx @@ -62,6 +62,24 @@ describe("AnalysisReport", () => { document.body.classList.remove("burst-analysis-report-printing"); }); + it("guides the user to choose a scheme when no report is active", () => { + render( + , + ); + + expect(screen.getByText("尚未选择分析方案")).toBeInTheDocument(); + expect( + screen.getByText( + "请在“方案查询”中打开一个方案,再查看分析报告。", + ), + ).toBeInTheDocument(); + }); + it("renders the selected scheme, pipe data, and matching valve result", async () => { render( = ({ if (!scheme) { return ( - - - - 等待选择分析方案 - - - 请在“方案查询”中点击“查看分析报告”。 - - + } + title="尚未选择分析方案" + description="请在“方案查询”中打开一个方案,再查看分析报告。" + /> ); } diff --git a/src/components/olmap/BurstSimulation/SchemeQuery.tsx b/src/components/olmap/BurstSimulation/SchemeQuery.tsx index e72af40..e930d07 100644 --- a/src/components/olmap/BurstSimulation/SchemeQuery.tsx +++ b/src/components/olmap/BurstSimulation/SchemeQuery.tsx @@ -57,6 +57,7 @@ import { type PipeDiameterMap, } from "./schemePipeDiameters"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface SchemeQueryProps { schemes?: SchemeRecord[]; @@ -76,6 +77,7 @@ export interface BurstSchemeQueryState { selectedDate: Date | undefined; timeRange: { start: Date; end: Date } | undefined; expandedId: number | null; + hasQueried: boolean; } export const createBurstSchemeQueryState = (): BurstSchemeQueryState => ({ @@ -85,6 +87,7 @@ export const createBurstSchemeQueryState = (): BurstSchemeQueryState => ({ selectedDate: undefined, timeRange: undefined, expandedId: null, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -107,6 +110,7 @@ const SchemeQuery: React.FC = ({ selectedDate, timeRange, expandedId, + hasQueried, } = queryState; const [highlightLayer, setHighlightLayer] = useState | null>(null); @@ -185,6 +189,7 @@ const SchemeQuery: React.FC = ({ schemeDetail: item.scheme_detail, })); setSchemes(nextSchemes); + setQueryField("hasQueried", true); if (filteredResults.length === 0) { open?.({ @@ -462,7 +467,10 @@ const SchemeQuery: React.FC = ({ control={ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} size="small" /> } @@ -475,9 +483,12 @@ const SchemeQuery: React.FC = ({ > - value && dayjs.isDayjs(value) && setQueryField("queryDate", value) - } + onChange={(value) => { + if (value && dayjs.isDayjs(value)) { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + } + }} format="YYYY-MM-DD" disabled={queryAll} slotProps={{ @@ -505,39 +516,7 @@ const SchemeQuery: React.FC = ({ {/* 结果列表 */} {filteredSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/ContaminantSimulation/SchemeQuery.tsx b/src/components/olmap/ContaminantSimulation/SchemeQuery.tsx index 5f4173c..760a2b7 100644 --- a/src/components/olmap/ContaminantSimulation/SchemeQuery.tsx +++ b/src/components/olmap/ContaminantSimulation/SchemeQuery.tsx @@ -41,6 +41,7 @@ import { bbox, featureCollection } from "@turf/turf"; import Timeline from "@components/olmap/core/Controls/Timeline"; import { ContaminantSchemaItem, ContaminantSchemeRecord } from "./types"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface SchemeQueryProps { schemes?: ContaminantSchemeRecord[]; @@ -60,6 +61,7 @@ export interface ContaminantSchemeQueryState { selectedDate: Date | undefined; timeRange: { start: Date; end: Date } | undefined; expandedId: number | null; + hasQueried: boolean; } export const createContaminantSchemeQueryState = @@ -70,6 +72,7 @@ export const createContaminantSchemeQueryState = selectedDate: undefined, timeRange: undefined, expandedId: null, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -92,6 +95,7 @@ const SchemeQuery: React.FC = ({ selectedDate, timeRange, expandedId, + hasQueried, } = queryState; const [highlightLayer, setHighlightLayer] = useState | null>(null); @@ -250,6 +254,7 @@ const SchemeQuery: React.FC = ({ schemeDetail: item.scheme_detail, })); setSchemes(nextSchemes); + setQueryField("hasQueried", true); if (filteredResults.length === 0) { open?.({ @@ -351,7 +356,10 @@ const SchemeQuery: React.FC = ({ control={ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} size="small" /> } @@ -364,9 +372,12 @@ const SchemeQuery: React.FC = ({ > - value && dayjs.isDayjs(value) && setQueryField("queryDate", value) - } + onChange={(value) => { + if (value && dayjs.isDayjs(value)) { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + } + }} format="YYYY-MM-DD" disabled={queryAll} slotProps={{ @@ -393,39 +404,7 @@ const SchemeQuery: React.FC = ({ {filteredSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/DMALeakDetection/RecognitionResults.tsx b/src/components/olmap/DMALeakDetection/RecognitionResults.tsx index dce5e08..a5a1fb2 100644 --- a/src/components/olmap/DMALeakDetection/RecognitionResults.tsx +++ b/src/components/olmap/DMALeakDetection/RecognitionResults.tsx @@ -11,11 +11,15 @@ import { TableHead, TableRow, } from "@mui/material"; -import { FormatListBulleted } from "@mui/icons-material"; +import { + FormatListBulleted, + TroubleshootOutlined, +} from "@mui/icons-material"; import dayjs from "dayjs"; import { getAreaColor } from "./utils"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; import { LeakageResultDetail } from "./types"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; interface Props { result: LeakageResultDetail | null; @@ -31,39 +35,11 @@ const RecognitionResults: React.FC = ({ result }) => { if (!result || !sortedRows.length) { return ( - - - - - - - - 暂无识别结果 - - 请先加载方案或执行识别分析 - - + } + title="尚未生成识别结果" + description="请在“识别参数”中运行分析,或在“方案查询”中打开历史结果。" + /> ); } diff --git a/src/components/olmap/DMALeakDetection/SchemeQuery.tsx b/src/components/olmap/DMALeakDetection/SchemeQuery.tsx index c599550..3e15444 100644 --- a/src/components/olmap/DMALeakDetection/SchemeQuery.tsx +++ b/src/components/olmap/DMALeakDetection/SchemeQuery.tsx @@ -27,6 +27,7 @@ import { useControllableObjectState } from "@components/olmap/core/useControllab import { LeakageResultDetail, LeakageSchemeRecord } from "./types"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface Props { onViewResult: (result: LeakageResultDetail) => void; @@ -40,12 +41,14 @@ export interface DMALeakSchemeQueryState { queryAll: boolean; queryDate: Dayjs | null; expandedId: number | null; + hasQueried: boolean; } export const createDMALeakSchemeQueryState = (): DMALeakSchemeQueryState => ({ queryAll: true, queryDate: dayjs(), expandedId: null, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -61,7 +64,7 @@ const SchemeQuery: React.FC = ({ onStateChange, createDMALeakSchemeQueryState(), ); - const { queryAll, queryDate, expandedId } = queryState; + const { queryAll, queryDate, expandedId, hasQueried } = queryState; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); const creatorName = useSchemeCreatorName(); @@ -93,6 +96,7 @@ const SchemeQuery: React.FC = ({ }); const nextSchemes = response.data as LeakageSchemeRecord[]; setSchemes(nextSchemes); + setQueryField("hasQueried", true); if (nextSchemes.length === 0) { open?.({ type: "success", @@ -150,7 +154,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} /> } label={查询全部} @@ -159,7 +166,10 @@ const SchemeQuery: React.FC = ({ setQueryField("queryDate", value)} + onChange={(value) => { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + }} disabled={queryAll} format="YYYY-MM-DD" slotProps={{ textField: { size: "small", sx: { width: 200 } } }} @@ -180,39 +190,7 @@ const SchemeQuery: React.FC = ({ {sortedSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/FlushingAnalysis/AnalysisParameters.tsx b/src/components/olmap/FlushingAnalysis/AnalysisParameters.tsx index 8fdad66..77ec889 100644 --- a/src/components/olmap/FlushingAnalysis/AnalysisParameters.tsx +++ b/src/components/olmap/FlushingAnalysis/AnalysisParameters.tsx @@ -11,7 +11,11 @@ import { Alert, Divider, } from "@mui/material"; -import { Close as CloseIcon } from "@mui/icons-material"; +import { + AdjustOutlined, + Close as CloseIcon, + WaterDropOutlined, +} from "@mui/icons-material"; import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { zhCN as pickerZhCN } from "@mui/x-date-pickers/locales"; @@ -31,6 +35,7 @@ import { useNotification } from "@refinedev/core"; import { api } from "@/lib/api"; import { config, NETWORK_NAME } from "@/config/config"; import { useControllableObjectState } from "@components/olmap/core/useControllableState"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; export interface ValveItem { id: string; @@ -383,9 +388,12 @@ const AnalysisParameters: React.FC = ({ ))} {valves.length === 0 && ( - - 暂无选中阀门 - + } + title="尚未选择阀门" + description="点击“选择阀门”,然后在地图上添加。" + /> )} @@ -412,7 +420,7 @@ const AnalysisParameters: React.FC = ({ 💡 点击地图上的节点作为排水点 )} - + {drainageNode && ( {drainageNode} @@ -428,9 +436,12 @@ const AnalysisParameters: React.FC = ({ )} {!drainageNode && ( - - 暂无选中排水节点 - + } + title="尚未选择排水节点" + description="点击“选择节点”,然后在地图上指定排水点。" + /> )} diff --git a/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx b/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx index 1515897..e7fe0a8 100644 --- a/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx +++ b/src/components/olmap/FlushingAnalysis/SchemeQuery.tsx @@ -43,6 +43,7 @@ import Timeline from "@components/olmap/core/Controls/Timeline"; import { SchemeRecord, SchemaItem } from "./types"; import { FLOW_DISPLAY_UNIT } from "@utils/units"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface SchemeQueryProps { schemes?: SchemeRecord[]; @@ -61,6 +62,7 @@ export interface FlushingSchemeQueryState { showTimeline: boolean; selectedDate: Date | undefined; timeRange: { start: Date; end: Date } | undefined; + hasQueried: boolean; } export const createFlushingSchemeQueryState = @@ -71,6 +73,7 @@ export const createFlushingSchemeQueryState = showTimeline: false, selectedDate: undefined, timeRange: undefined, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -92,6 +95,7 @@ const SchemeQuery: React.FC = ({ showTimeline, selectedDate, timeRange, + hasQueried, } = queryState; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); @@ -298,6 +302,7 @@ const SchemeQuery: React.FC = ({ schemeDetail: item.scheme_detail, })); setSchemes(nextSchemes); + setQueryField("hasQueried", true); if (filteredResults.length === 0) { open?.({ @@ -371,7 +376,10 @@ const SchemeQuery: React.FC = ({ control={ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} size="small" /> } @@ -384,9 +392,12 @@ const SchemeQuery: React.FC = ({ > - value && dayjs.isDayjs(value) && setQueryField("queryDate", value) - } + onChange={(value) => { + if (value && dayjs.isDayjs(value)) { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + } + }} format="YYYY-MM-DD" disabled={queryAll} slotProps={{ @@ -414,39 +425,7 @@ const SchemeQuery: React.FC = ({ {/* Results List */} {sortedSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx b/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx index 07da4f0..7c0e7e0 100644 --- a/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx +++ b/src/components/olmap/HealthRiskAnalysis/HealthRiskStatistics.tsx @@ -23,6 +23,7 @@ import { RAINBOW_COLORS, RISK_BREAKS, RISK_LABELS } from "./types"; import { useHealthRisk } from "./HealthRiskContext"; import { useProject } from "@/contexts/ProjectContext"; import HealthRiskReport from "./HealthRiskReport"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; const SIMPLE_LABELS = [ "0.0 - 0.1", @@ -229,24 +230,11 @@ const HealthRiskStatistics: React.FC = () => { }; const renderEmpty = () => ( - - - - 暂无预测数据 - - - 请先进行健康风险分析预测,以生成风险分布数据 - - + } + title="暂无风险预测结果" + description="请先运行健康风险预测,完成后将在这里展示风险分布统计。" + /> ); return ( diff --git a/src/components/olmap/HealthRiskAnalysis/PredictDataPanel.tsx b/src/components/olmap/HealthRiskAnalysis/PredictDataPanel.tsx index 8ab3f1a..7a6f653 100644 --- a/src/components/olmap/HealthRiskAnalysis/PredictDataPanel.tsx +++ b/src/components/olmap/HealthRiskAnalysis/PredictDataPanel.tsx @@ -8,6 +8,7 @@ import { ShowChart } from "@mui/icons-material"; import ReactECharts from "echarts-for-react"; import "dayjs/locale/zh-cn"; import { useHealthRisk } from "./HealthRiskContext"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; export interface PredictDataPanelProps { /** 选中的要素信息列表,格式为 [[id, type], [id, type]] */ @@ -58,26 +59,11 @@ const PredictDataPanel: React.FC = ({ }, [filteredResults]); const renderEmpty = () => ( - - - - 暂无预测数据 - - - 请在地图上选择已分析的管道 - - + } + title="暂无可展示的管道预测" + description="请先在地图上选择已完成健康风险分析的管道。" + /> ); const renderChart = () => { diff --git a/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx b/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx index 6e59fc5..0036bd6 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx +++ b/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx @@ -38,6 +38,7 @@ import { Style, Icon, Circle, Fill, Stroke } from "ol/style"; import Feature, { FeatureLike } from "ol/Feature"; import { bbox, featureCollection } from "@turf/turf"; import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName"; +import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; interface SchemeRecord { id: number; @@ -72,6 +73,7 @@ export interface MonitoringSchemeQueryState { queryAll: boolean; queryDate: Dayjs | null; expandedId: number | null; + hasQueried: boolean; } export const createMonitoringSchemeQueryState = @@ -79,6 +81,7 @@ export const createMonitoringSchemeQueryState = queryAll: true, queryDate: dayjs(new Date()), expandedId: null, + hasQueried: false, }); const SchemeQuery: React.FC = ({ @@ -94,7 +97,7 @@ const SchemeQuery: React.FC = ({ onStateChange, createMonitoringSchemeQueryState(), ); - const { queryAll, queryDate, expandedId } = queryState; + const { queryAll, queryDate, expandedId, hasQueried } = queryState; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); const creatorName = useSchemeCreatorName(); @@ -207,6 +210,7 @@ const SchemeQuery: React.FC = ({ sensorLocation: item.sensor_location, })); setSchemes(nextSchemes); + setQueryField("hasQueried", true); if (filteredResults.length === 0) { open?.({ @@ -290,7 +294,10 @@ const SchemeQuery: React.FC = ({ control={ setQueryField("queryAll", e.target.checked)} + onChange={(e) => { + setQueryField("queryAll", e.target.checked); + setQueryField("hasQueried", false); + }} size="small" /> } @@ -303,9 +310,12 @@ const SchemeQuery: React.FC = ({ > - value && dayjs.isDayjs(value) && setQueryField("queryDate", value) - } + onChange={(value) => { + if (value && dayjs.isDayjs(value)) { + setQueryField("queryDate", value); + setQueryField("hasQueried", false); + } + }} format="YYYY-MM-DD" disabled={queryAll} slotProps={{ @@ -333,39 +343,7 @@ const SchemeQuery: React.FC = ({ {/* 结果列表 */} {sortedSchemes.length === 0 ? ( - - - - - - - - 总共 0 条 - - No data - - + ) : ( diff --git a/src/components/olmap/SCADA/SCADADataPanel.tsx b/src/components/olmap/SCADA/SCADADataPanel.tsx index adbeb05..e892175 100644 --- a/src/components/olmap/SCADA/SCADADataPanel.tsx +++ b/src/components/olmap/SCADA/SCADADataPanel.tsx @@ -40,6 +40,7 @@ import { useGetIdentity } from "@refinedev/core"; import { useNotification } from "@refinedev/core"; import { api } from "@/lib/api"; import { apiFetch } from "@/lib/apiFetch"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; dayjs.extend(utc); dayjs.extend(timezone); @@ -314,11 +315,11 @@ const emptyStateMessages: Record< > = { chart: { title: "暂无时序数据", - subtitle: "请切换时间段来获取曲线", + subtitle: "请调整时间范围,或确认所选设备在该时段已有数据。", }, table: { title: "暂无表格数据", - subtitle: "请切换时间段来获取记录", + subtitle: "请调整时间范围,或确认所选设备在该时段已有数据。", }, }; @@ -721,28 +722,23 @@ const SCADADataPanel: React.FC = ({ ); const renderEmpty = () => { + if (!hasDevices) { + return ( + } + title="尚未选择设备" + description="请先在设备列表中选择需要查看的 SCADA 设备。" + /> + ); + } + const message = emptyStateMessages[activeTab]; return ( - - - - {message.title} - - - {message.subtitle} - - + : } + title={message.title} + description={message.subtitle} + /> ); }; @@ -1254,15 +1250,6 @@ const SCADADataPanel: React.FC = ({ - {!hasDevices && ( - - 未选择任何设备,无法获取数据。 - - )} {error && ( = ({ ) : filteredDevices.length === 0 ? ( - - - - {searchQuery || + } + title={ + searchQuery || selectedType !== "all" || selectedStatus !== "all" - ? "未找到匹配的设备" - : "暂无 SCADA 设备"} - - + ? "未找到匹配设备" + : "暂无 SCADA 设备" + } + description={ + searchQuery || + selectedType !== "all" || + selectedStatus !== "all" + ? "请清除关键词,或调整设备类型和状态筛选条件。" + : "请确认设备已接入当前管网后刷新列表。" + } + /> ) : ( { + it("renders an accessible panel instruction", () => { + render( + } + title="尚未选择分析方案" + description="请在方案查询中打开一个方案。" + />, + ); + + expect(screen.getByRole("status")).toHaveAttribute("aria-live", "polite"); + expect(screen.getByText("尚未选择分析方案")).toBeInTheDocument(); + expect(screen.getByText("请在方案查询中打开一个方案。")).toBeInTheDocument(); + }); + + it("distinguishes initial query guidance from an empty result", () => { + const { rerender } = render( + , + ); + + expect(screen.getByText("尚未查询历史方案")).toBeInTheDocument(); + + rerender(); + + expect(screen.getByText("未找到符合条件的方案")).toBeInTheDocument(); + expect( + screen.getByText("请调整查询条件后重新查询,或先创建一个新方案。"), + ).toBeInTheDocument(); + }); + + it("supports compact selection guidance", () => { + render( + , + ); + + expect(screen.getByText("尚未选择阀门")).toBeInTheDocument(); + }); +}); diff --git a/src/components/olmap/common/PanelEmptyState.tsx b/src/components/olmap/common/PanelEmptyState.tsx new file mode 100644 index 0000000..57d95bb --- /dev/null +++ b/src/components/olmap/common/PanelEmptyState.tsx @@ -0,0 +1,112 @@ +"use client"; + +import type { ReactNode } from "react"; +import { Box, Typography } from "@mui/material"; +import { SearchOffOutlined } from "@mui/icons-material"; +import { alpha } from "@mui/material/styles"; + +export interface PanelEmptyStateProps { + icon?: ReactNode; + title: string; + description?: string; + variant?: "panel" | "compact"; +} + +const PanelEmptyState = ({ + icon, + title, + description, + variant = "panel", +}: PanelEmptyStateProps) => { + const compact = variant === "compact"; + + return ( + ({ + width: "100%", + height: compact ? "auto" : "100%", + minHeight: compact ? 48 : 220, + display: "flex", + flexDirection: compact ? "row" : { xs: "column", sm: "row" }, + alignItems: "center", + justifyContent: "center", + gap: compact ? 1.5 : { xs: 1.75, sm: 2.5 }, + px: compact ? 1.5 : { xs: 2, sm: 4 }, + py: compact ? 1.25 : { xs: 4, sm: 5 }, + color: "text.secondary", + textAlign: compact ? "left" : { xs: "center", sm: "left" }, + boxSizing: "border-box", + "& .MuiTypography-root": { + textWrap: "pretty", + }, + "& .MuiSvgIcon-root": { + fontSize: compact ? 22 : 32, + }, + "& > [aria-hidden='true']": { + flex: "0 0 auto", + width: compact ? 36 : 56, + height: compact ? 36 : 56, + borderRadius: compact ? 2 : 3, + display: "grid", + placeItems: "center", + color: "primary.main", + backgroundColor: alpha(theme.palette.primary.main, 0.08), + boxShadow: `inset 0 0 0 1px ${alpha( + theme.palette.primary.main, + 0.12, + )}`, + }, + })} + > + {icon ? : null} + + + {title} + + {description ? ( + + {description} + + ) : null} + + + ); +}; + +export interface SchemeQueryEmptyStateProps { + hasQueried: boolean; +} + +export const SchemeQueryEmptyState = ({ + hasQueried, +}: SchemeQueryEmptyStateProps) => ( + } + title={hasQueried ? "未找到符合条件的方案" : "尚未查询历史方案"} + description={ + hasQueried + ? "请调整查询条件后重新查询,或先创建一个新方案。" + : "设置查询条件后点击“查询”,这里将显示可查看的历史方案。" + } + /> +); + +export default PanelEmptyState; diff --git a/src/components/olmap/core/Controls/HistoryDataPanel.tsx b/src/components/olmap/core/Controls/HistoryDataPanel.tsx index 4455589..4849cd7 100644 --- a/src/components/olmap/core/Controls/HistoryDataPanel.tsx +++ b/src/components/olmap/core/Controls/HistoryDataPanel.tsx @@ -36,6 +36,7 @@ import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { zhCN as pickerZhCN } from "@mui/x-date-pickers/locales"; import config from "@/config/config"; import { apiFetch } from "@/lib/apiFetch"; +import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; dayjs.extend(utc); dayjs.extend(timezone); @@ -413,11 +414,11 @@ const emptyStateMessages: Record< > = { chart: { title: "暂无时序数据", - subtitle: "请切换时间段来获取曲线", + subtitle: "请调整时间范围,或确认所选设备在该时段已有数据。", }, table: { title: "暂无表格数据", - subtitle: "请切换时间段来获取记录", + subtitle: "请调整时间范围,或确认所选设备在该时段已有数据。", }, }; @@ -613,28 +614,23 @@ const SCADADataPanel: React.FC = ({ ); const renderEmpty = () => { + if (!hasDevices) { + return ( + } + title="尚未选择地图要素" + description="请在地图上选择一个要素以查看历史数据。" + /> + ); + } + const message = emptyStateMessages[activeTab]; return ( - - - - {message.title} - - - {message.subtitle} - - + : } + title={message.title} + description={message.subtitle} + /> ); }; @@ -1048,15 +1044,6 @@ const SCADADataPanel: React.FC = ({ - {!hasDevices && ( - - 请选择一个要素以查看其历史数据。 - - )} {error && ( = ({ {/* 内容区域 */}
{!id ? ( -
- - - -

暂无属性信息

-

请选择一个要素以查看其属性

-
+ } + title="尚未选择地图要素" + description="请在地图上选择管道、节点或设备以查看属性。" + /> ) : (
{/* ID 属性 */}