refactor: abstract feature property panels
This commit is contained in:
@@ -9,8 +9,10 @@ import { SCADA_ICON_PATHS } from "../map/scada";
|
||||
import type { DetailFeature } from "../types";
|
||||
import {
|
||||
formatFeaturePropertyValue,
|
||||
getFeatureInsightMetrics,
|
||||
getLocalizedFeatureProperties
|
||||
} from "../utils/feature-properties";
|
||||
import { FeaturePropertyList } from "./feature-property-list";
|
||||
|
||||
type FeatureInsightPanelProps = {
|
||||
feature: DetailFeature | null;
|
||||
@@ -32,50 +34,7 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (feature.layer) {
|
||||
case "conduits":
|
||||
return [
|
||||
["管径", formatFeaturePropertyValue("diameter", feature.properties.diameter)],
|
||||
["长度", formatFeaturePropertyValue("length", feature.properties.length)],
|
||||
["粗糙系数", formatFeaturePropertyValue("roughness", feature.properties.roughness)],
|
||||
["状态", formatFeaturePropertyValue("status", feature.properties.status)]
|
||||
];
|
||||
case "junctions":
|
||||
return [
|
||||
["最大深度", formatFeaturePropertyValue("max_depth", feature.properties.max_depth)],
|
||||
["井底高程", formatFeaturePropertyValue("invert_elevation", feature.properties.invert_elevation)],
|
||||
["对象类型", "检查井"],
|
||||
["状态", "在线"]
|
||||
];
|
||||
case "orifices":
|
||||
return [
|
||||
["孔口类型", formatFeaturePropertyValue("orifice_type", feature.properties.orifice_type)],
|
||||
["断面形状", formatFeaturePropertyValue("shape", feature.properties.shape)],
|
||||
["流量系数", formatFeaturePropertyValue("discharge_coeff", feature.properties.discharge_coeff)],
|
||||
["闸门", formatFeaturePropertyValue("gated", feature.properties.gated)]
|
||||
];
|
||||
case "pumps":
|
||||
return [
|
||||
["状态", formatFeaturePropertyValue("status", feature.properties.status)],
|
||||
["泵曲线", formatFeaturePropertyValue("pump_curve", feature.properties.pump_curve)],
|
||||
["启动水深", formatFeaturePropertyValue("startup_depth", feature.properties.startup_depth)],
|
||||
["停泵水深", formatFeaturePropertyValue("shutoff_depth", feature.properties.shutoff_depth)]
|
||||
];
|
||||
case "outfalls":
|
||||
return [
|
||||
["排放类型", formatFeaturePropertyValue("outfall_type", feature.properties.outfall_type)],
|
||||
["井底高程", formatFeaturePropertyValue("invert_elevation", feature.properties.invert_elevation)],
|
||||
["阶段数据", formatFeaturePropertyValue("stage_data", feature.properties.stage_data)],
|
||||
["状态", "在线"]
|
||||
];
|
||||
case "scada":
|
||||
return [
|
||||
["设备类型", formatFeaturePropertyValue("kind", feature.properties.kind)],
|
||||
["运行状态", formatFeaturePropertyValue("active", feature.properties.active)],
|
||||
["关联检查井", formatFeaturePropertyValue("junction_id", feature.properties.junction_id)],
|
||||
["同步时间", formatFeaturePropertyValue("synced_at", feature.properties.synced_at)]
|
||||
];
|
||||
}
|
||||
return getFeatureInsightMetrics(feature.layer, feature.properties);
|
||||
}, [feature]);
|
||||
|
||||
const propertyEntries = useMemo(() => {
|
||||
@@ -83,14 +42,13 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
return [];
|
||||
}
|
||||
|
||||
const properties = feature.layer === "scada" && feature.properties.scada_id === undefined
|
||||
? { scada_id: feature.id, ...feature.properties }
|
||||
: feature.properties;
|
||||
return getLocalizedFeatureProperties(properties);
|
||||
return feature.layer === "scada"
|
||||
? []
|
||||
: getLocalizedFeatureProperties(feature.properties);
|
||||
}, [feature]);
|
||||
|
||||
const scadaQueryUuid = feature?.layer === "scada"
|
||||
? getCopyablePropertyValue(feature.properties.scada_id ?? feature.id)
|
||||
? getCopyablePropertyValue(feature.properties.sensor_id ?? feature.id)
|
||||
: null;
|
||||
|
||||
const handleCopyScadaUuid = async () => {
|
||||
@@ -148,11 +106,23 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
<div className="min-h-0 flex-1 overflow-auto p-4">
|
||||
{feature ? (
|
||||
<>
|
||||
{feature.layer === "scada" ? (
|
||||
<h3 className="mb-2 text-sm font-semibold text-slate-900">监测数据</h3>
|
||||
) : null}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{metrics.map(([label, value]) => (
|
||||
<div key={label} className="surface-reading rounded border p-3">
|
||||
{metrics.map(({ label, value }, index) => (
|
||||
<div
|
||||
key={label}
|
||||
className={cn(
|
||||
"surface-reading rounded border p-3",
|
||||
feature.layer === "scada" && index === metrics.length - 1 && "col-span-2"
|
||||
)}
|
||||
>
|
||||
<p className="text-xs text-slate-500">{label}</p>
|
||||
<p className="mt-1 truncate text-sm font-semibold text-slate-900">{value}</p>
|
||||
<p className={cn(
|
||||
"mt-1 truncate text-sm font-semibold text-slate-900 tabular-nums",
|
||||
value === "暂无" && "font-medium text-slate-400"
|
||||
)}>{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -162,18 +132,18 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
<div className="flex items-center gap-3">
|
||||
<Image src={getScadaPresentation(feature.properties.kind).iconPath} alt="" width={42} height={42} />
|
||||
<div className="min-w-0">
|
||||
<p className={cn("truncate text-sm font-semibold", getScadaPresentation(feature.properties.kind).labelClassName)}>{formatFeaturePropertyValue("name", feature.properties.name)}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-500 tabular-nums">{formatFeaturePropertyValue("external_id", feature.properties.external_id)}</p>
|
||||
<p className={cn("truncate text-sm font-semibold", getScadaPresentation(feature.properties.kind).labelClassName)}>{formatFeaturePropertyValue("sensor_name", feature.properties.sensor_name)}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-500 tabular-nums">{formatFeaturePropertyValue("sensor_id", feature.properties.sensor_id ?? feature.id)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{scadaQueryUuid ? (
|
||||
<div className="mt-3 rounded border border-white/70 bg-white/65 px-3 py-2 shadow-sm">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-xs font-semibold text-slate-500">编号 UUID</p>
|
||||
<p className="text-xs font-semibold text-slate-500">传感器 ID</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={copiedScadaUuid ? "编号 UUID 已复制" : "复制编号 UUID"}
|
||||
title={copiedScadaUuid ? "编号 UUID 已复制" : "复制编号 UUID"}
|
||||
aria-label={copiedScadaUuid ? "传感器 ID 已复制" : "复制传感器 ID"}
|
||||
title={copiedScadaUuid ? "传感器 ID 已复制" : "复制传感器 ID"}
|
||||
onClick={() => void handleCopyScadaUuid()}
|
||||
className="relative grid h-8 w-8 shrink-0 place-items-center rounded border border-slate-200/80 bg-white/80 text-slate-500 transition-[background-color,color,transform] duration-150 hover:text-slate-950 active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600/25"
|
||||
>
|
||||
@@ -182,10 +152,10 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 break-all font-mono text-xs font-semibold leading-5 text-slate-900">{scadaQueryUuid}</p>
|
||||
<p className="mt-1 text-xs leading-5 text-slate-500">SCADA 时序查询 asset_ids</p>
|
||||
<p className="mt-1 text-xs leading-5 text-slate-500">SCADA 时序查询 sensor_id</p>
|
||||
</div>
|
||||
) : null}
|
||||
<p className="mt-2 text-sm leading-6 text-slate-700">当前点位由管网节点空间均匀采样生成,仅用于空间展示,不代表设备真实安装坐标。图层暂未提供实时测值或历史曲线。</p>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-700">设备类型为综合监测点,数据源为 geo_scadas_mat。</p>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
@@ -199,20 +169,14 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-4">
|
||||
<h3 className="text-sm font-semibold text-slate-900">属性</h3>
|
||||
<dl className="surface-reading mt-2 divide-y divide-slate-100 rounded border">
|
||||
{propertyEntries.map((entry) => (
|
||||
<div key={entry.key} className="grid grid-cols-[110px_1fr] gap-2 px-3 py-2 text-sm">
|
||||
<dt className="truncate text-slate-500">{feature.layer === "scada" && entry.key === "scada_id" ? "编号 UUID" : entry.label}</dt>
|
||||
<dd className={cn(
|
||||
"font-medium text-slate-800",
|
||||
feature.layer === "scada" && entry.key === "scada_id" ? "break-all font-mono text-xs leading-5" : "truncate"
|
||||
)}>{entry.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
{feature.layer !== "scada" ? (
|
||||
<section className="mt-4">
|
||||
<h3 className="text-sm font-semibold text-slate-900">属性</h3>
|
||||
<div className="mt-2">
|
||||
<FeaturePropertyList entries={propertyEntries} />
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex h-full flex-col items-center justify-center text-center">
|
||||
@@ -248,5 +212,6 @@ function getScadaPresentation(rawKind: unknown) {
|
||||
if (kind === "water_quality") return { iconPath: SCADA_ICON_PATHS.waterQuality, sectionClassName: "material-tone-normal border-teal-200", labelClassName: "text-teal-800" };
|
||||
if (kind === "radar_level") return { iconPath: SCADA_ICON_PATHS.radarLevel, sectionClassName: "material-tone-info border-blue-200", labelClassName: "text-blue-800" };
|
||||
if (kind === "ultrasonic_flow") return { iconPath: SCADA_ICON_PATHS.ultrasonicFlow, sectionClassName: "material-tone-warning border-orange-200", labelClassName: "text-orange-800" };
|
||||
return { iconPath: SCADA_ICON_PATHS.unknown, sectionClassName: "surface-control border-transparent", labelClassName: "text-slate-800" };
|
||||
if (kind === "integrated_monitoring" || kind === "综合监测点") return { iconPath: SCADA_ICON_PATHS.integratedMonitoring, sectionClassName: "material-tone-normal border-cyan-200", labelClassName: "text-cyan-800" };
|
||||
return { iconPath: SCADA_ICON_PATHS.integratedMonitoring, sectionClassName: "material-tone-normal border-cyan-200", labelClassName: "text-cyan-800" };
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
ScadaFeatureIcon,
|
||||
type DrainageFeatureIconProps
|
||||
} from "./drainage-feature-icons";
|
||||
import { FeaturePropertyList } from "./feature-property-list";
|
||||
|
||||
type FeaturePopoverProps = {
|
||||
feature: DetailFeature | null;
|
||||
@@ -156,16 +157,7 @@ export function FeaturePopover({ feature, onClose }: FeaturePopoverProps) {
|
||||
<Database size={14} aria-hidden="true" />
|
||||
设施属性
|
||||
</div>
|
||||
<dl className="surface-reading divide-y divide-slate-100 rounded-xl px-3">
|
||||
{panelModel.attributes.map((entry) => (
|
||||
<div key={entry.key} className="grid min-h-10 grid-cols-[96px_minmax(0,1fr)] items-baseline gap-3 py-2.5">
|
||||
<dt className="text-xs font-medium leading-5 text-slate-500">{entry.label}</dt>
|
||||
<dd className={cn("break-words text-right text-sm font-semibold leading-5 text-slate-800 tabular-nums", entry.value === "暂无" && "font-medium text-slate-400")}>
|
||||
{entry.value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
<FeaturePropertyList entries={panelModel.attributes} variant="popover" />
|
||||
</>
|
||||
) : (
|
||||
<p className="py-3 text-center text-sm text-slate-500">暂无可展示属性</p>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { LocalizedFeatureProperty } from "../utils/feature-properties";
|
||||
|
||||
type FeaturePropertyListProps = {
|
||||
entries: LocalizedFeatureProperty[];
|
||||
variant?: "insight" | "popover";
|
||||
};
|
||||
|
||||
export function FeaturePropertyList({
|
||||
entries,
|
||||
variant = "insight"
|
||||
}: FeaturePropertyListProps) {
|
||||
if (entries.length === 0) {
|
||||
return <p className="py-3 text-center text-sm text-slate-500">暂无可展示属性</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<dl className={cn(
|
||||
"surface-reading divide-y divide-slate-100 border",
|
||||
variant === "popover" ? "rounded-xl px-3" : "rounded"
|
||||
)}>
|
||||
{entries.map((entry) => (
|
||||
<div
|
||||
key={entry.key}
|
||||
className={cn(
|
||||
"grid",
|
||||
variant === "popover"
|
||||
? "min-h-10 grid-cols-[96px_minmax(0,1fr)] items-baseline gap-3 py-2.5"
|
||||
: "grid-cols-[110px_minmax(0,1fr)] gap-2 px-3 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
<dt className={cn(
|
||||
"text-slate-500",
|
||||
variant === "popover" ? "text-xs font-medium leading-5" : "truncate"
|
||||
)}>
|
||||
{entry.label}
|
||||
</dt>
|
||||
<dd className={cn(
|
||||
"font-medium text-slate-800",
|
||||
variant === "popover"
|
||||
? "break-words text-right text-sm font-semibold leading-5 tabular-nums"
|
||||
: "truncate",
|
||||
entry.value === "暂无" && "font-medium text-slate-400"
|
||||
)}>
|
||||
{entry.value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user