feat: initialize drainage network frontend
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
import {
|
||||
Activity,
|
||||
Camera,
|
||||
Circle,
|
||||
Copy,
|
||||
Database,
|
||||
Dot,
|
||||
Download,
|
||||
Keyboard,
|
||||
Layers3,
|
||||
MapPinned,
|
||||
Pentagon,
|
||||
Redo2,
|
||||
RefreshCw,
|
||||
Route,
|
||||
Ruler,
|
||||
Share2,
|
||||
Trash2,
|
||||
Undo2,
|
||||
Waypoints,
|
||||
type LucideIcon
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
MapAnnotationPanel,
|
||||
type MapAnnotationItem,
|
||||
type MapAnnotationShareAction
|
||||
} from "@/features/map/core/components/annotation-panel";
|
||||
import type { BaseLayerOption } from "@/features/map/core/components/base-layers-control";
|
||||
import {
|
||||
MapActionRow,
|
||||
MapControlPanel,
|
||||
MapMetricTile,
|
||||
MapPanelSection
|
||||
} from "@/features/map/core/components/control-panel";
|
||||
import { MapDrawToolbar } from "@/features/map/core/components/draw-toolbar";
|
||||
import { MapLayerPanel } from "@/features/map/core/components/layer-panel";
|
||||
import type { MapLayerControlItem } from "@/features/map/core/components/layer-control";
|
||||
import { MapLegendList, type MapLegendItem } from "@/features/map/core/components/legend";
|
||||
import { MapMeasurePanel } from "@/features/map/core/components/measure-panel";
|
||||
import {
|
||||
type WorkbenchDrawingAnnotation,
|
||||
type WorkbenchDrawMode
|
||||
} from "../hooks/use-workbench-drawing";
|
||||
import {
|
||||
type WorkbenchMeasurementResult,
|
||||
type WorkbenchMeasureMode,
|
||||
type WorkbenchMeasureUnit
|
||||
} from "../hooks/use-workbench-measurement";
|
||||
import { waterNetworkToolbarItems } from "../map/toolbar-config";
|
||||
|
||||
export type ToolbarToolId = "layers" | "measure" | "analysis" | "annotation" | "more";
|
||||
export type ExportViewPreset = "current" | "4k";
|
||||
|
||||
type MeasureModeOption = {
|
||||
id: WorkbenchMeasureMode;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
};
|
||||
|
||||
const MEASURE_MODES: MeasureModeOption[] = [
|
||||
{ id: "distance", label: "距离", icon: Route },
|
||||
{ id: "area", label: "面积", icon: Pentagon },
|
||||
{ id: "segment", label: "管段", icon: Waypoints }
|
||||
];
|
||||
|
||||
const MEASURE_UNITS: Array<{ id: WorkbenchMeasureUnit; label: string }> = [
|
||||
{ id: "m", label: "m" },
|
||||
{ id: "km", label: "km" }
|
||||
];
|
||||
|
||||
const ANNOTATION_SHARE_ACTIONS: MapAnnotationShareAction[] = [
|
||||
{
|
||||
id: "export",
|
||||
icon: Share2,
|
||||
label: "导出标注",
|
||||
description: "GeoJSON / 截图报告"
|
||||
}
|
||||
];
|
||||
|
||||
const TOOL_PANEL_EXIT_MS = 160;
|
||||
|
||||
const DRAW_TOOL_OPTIONS: Array<{
|
||||
id: WorkbenchDrawMode | "undo" | "redo" | "delete" | "clear";
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
tone?: "danger";
|
||||
}> = [
|
||||
{ id: "point", label: "点标注", icon: Dot },
|
||||
{ id: "line", label: "线标注", icon: Route },
|
||||
{ id: "polygon", label: "范围标注", icon: Pentagon },
|
||||
{ id: "circle", label: "圆形标注", icon: Circle },
|
||||
{ id: "undo", label: "撤销", icon: Undo2 },
|
||||
{ id: "redo", label: "重做", icon: Redo2 },
|
||||
{ id: "delete", label: "删除选中", icon: Trash2, tone: "danger" },
|
||||
{ id: "clear", label: "清空全部", icon: Trash2, tone: "danger" }
|
||||
];
|
||||
|
||||
export type ToolbarPanelProps = {
|
||||
activeToolId: ToolbarToolId | null;
|
||||
activeMeasureModeId: WorkbenchMeasureMode;
|
||||
activeMeasureUnitId: WorkbenchMeasureUnit;
|
||||
measuring: boolean;
|
||||
measureResult: WorkbenchMeasurementResult;
|
||||
measureEnabled: boolean;
|
||||
hasMeasurePoints: boolean;
|
||||
activeDrawToolId: WorkbenchDrawMode | null;
|
||||
drawingEnabled: boolean;
|
||||
drawingAnnotations: WorkbenchDrawingAnnotation[];
|
||||
canUndoDrawing: boolean;
|
||||
canRedoDrawing: boolean;
|
||||
canDeleteSelectedDrawing: boolean;
|
||||
hasDrawingFeatures: boolean;
|
||||
layerItems: MapLayerControlItem[];
|
||||
legendItems: MapLegendItem[];
|
||||
baseLayerOptions: BaseLayerOption[];
|
||||
activeBaseLayerId: string;
|
||||
onSelectMeasureMode: (id: WorkbenchMeasureMode) => void;
|
||||
onSelectMeasureUnit: (id: WorkbenchMeasureUnit) => void;
|
||||
onStartMeasure: () => void;
|
||||
onStopMeasure: () => void;
|
||||
onClearMeasure: () => void;
|
||||
onCopyMeasure: () => void;
|
||||
onSelectDrawTool: (id: WorkbenchDrawMode | null) => void;
|
||||
onUndoDrawing: () => void;
|
||||
onRedoDrawing: () => void;
|
||||
onDeleteSelectedDrawing: () => void;
|
||||
onClearDrawing: () => void;
|
||||
onExportAnnotations: () => void;
|
||||
onSelectBaseLayer: (id: string) => void;
|
||||
onToggleLayer: (id: string, visible: boolean) => void;
|
||||
onExportView: (preset: ExportViewPreset) => void;
|
||||
onRefreshTiles: () => void;
|
||||
onShowDataStatus: () => void;
|
||||
onShowShortcuts: () => void;
|
||||
onExportConfig: () => void;
|
||||
panelWidthClassName?: string;
|
||||
};
|
||||
|
||||
export function ToolbarPanel({
|
||||
activeToolId,
|
||||
activeMeasureModeId,
|
||||
activeMeasureUnitId,
|
||||
measuring,
|
||||
measureResult,
|
||||
measureEnabled,
|
||||
hasMeasurePoints,
|
||||
activeDrawToolId,
|
||||
drawingEnabled,
|
||||
drawingAnnotations,
|
||||
canUndoDrawing,
|
||||
canRedoDrawing,
|
||||
canDeleteSelectedDrawing,
|
||||
hasDrawingFeatures,
|
||||
layerItems,
|
||||
legendItems,
|
||||
baseLayerOptions,
|
||||
activeBaseLayerId,
|
||||
onSelectMeasureMode,
|
||||
onSelectMeasureUnit,
|
||||
onStartMeasure,
|
||||
onStopMeasure,
|
||||
onClearMeasure,
|
||||
onCopyMeasure,
|
||||
onSelectDrawTool,
|
||||
onUndoDrawing,
|
||||
onRedoDrawing,
|
||||
onDeleteSelectedDrawing,
|
||||
onClearDrawing,
|
||||
onExportAnnotations,
|
||||
onSelectBaseLayer,
|
||||
onToggleLayer,
|
||||
onExportView,
|
||||
onRefreshTiles,
|
||||
onShowDataStatus,
|
||||
onShowShortcuts,
|
||||
onExportConfig,
|
||||
panelWidthClassName
|
||||
}: ToolbarPanelProps) {
|
||||
const annotationItems = useMemo<MapAnnotationItem[]>(
|
||||
() =>
|
||||
drawingAnnotations.map((annotation) => ({
|
||||
id: annotation.id,
|
||||
icon: getDrawingAnnotationIcon(annotation.kind),
|
||||
label: annotation.label,
|
||||
description: annotation.description,
|
||||
status: annotation.hidden ? "隐藏" : "显示",
|
||||
selected: annotation.selected,
|
||||
muted: annotation.hidden,
|
||||
onClick: annotation.onToggleVisibility
|
||||
})),
|
||||
[drawingAnnotations]
|
||||
);
|
||||
const annotationShareActions = useMemo(
|
||||
() =>
|
||||
ANNOTATION_SHARE_ACTIONS.map((action) => ({
|
||||
...action,
|
||||
onClick: action.id === "export" ? onExportAnnotations : action.onClick
|
||||
})),
|
||||
[onExportAnnotations]
|
||||
);
|
||||
|
||||
const [renderedToolId, setRenderedToolId] = useState<ToolbarToolId | null>(activeToolId);
|
||||
const panelVisible = Boolean(activeToolId);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeToolId) {
|
||||
setRenderedToolId(activeToolId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!renderedToolId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exitTimer = window.setTimeout(() => {
|
||||
setRenderedToolId(null);
|
||||
}, TOOL_PANEL_EXIT_MS);
|
||||
|
||||
return () => window.clearTimeout(exitTimer);
|
||||
}, [activeToolId, renderedToolId]);
|
||||
|
||||
if (!renderedToolId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeTool = waterNetworkToolbarItems.find((item) => item.id === renderedToolId);
|
||||
const Icon = activeTool?.icon ?? Layers3;
|
||||
|
||||
if (renderedToolId === "layers") {
|
||||
return (
|
||||
<MapControlPanel
|
||||
key="layers"
|
||||
title="图层"
|
||||
description={`${layerItems.filter((item) => item.visible).length}/${layerItems.length} 个业务图层可见`}
|
||||
icon={Icon}
|
||||
widthClassName={panelWidthClassName}
|
||||
visible={panelVisible}
|
||||
>
|
||||
<MapLayerPanel
|
||||
layerItems={layerItems}
|
||||
baseLayerOptions={baseLayerOptions}
|
||||
activeBaseLayerId={activeBaseLayerId}
|
||||
onSelectBaseLayer={onSelectBaseLayer}
|
||||
onToggleLayer={onToggleLayer}
|
||||
/>
|
||||
</MapControlPanel>
|
||||
);
|
||||
}
|
||||
|
||||
if (renderedToolId === "measure") {
|
||||
return (
|
||||
<MapControlPanel key="measure" title="测量" description="距离与范围测量" icon={Icon} widthClassName={panelWidthClassName ?? "w-[312px]"} visible={panelVisible}>
|
||||
<MapMeasurePanel
|
||||
modes={MEASURE_MODES}
|
||||
activeModeId={activeMeasureModeId}
|
||||
resultLabel={measureResult.label}
|
||||
resultValue={measureResult.value}
|
||||
resultUnit={measureResult.unit}
|
||||
metrics={measureResult.metrics}
|
||||
units={MEASURE_UNITS}
|
||||
activeUnitId={activeMeasureUnitId}
|
||||
actions={[
|
||||
{
|
||||
id: measuring ? "stop" : "start",
|
||||
icon: Ruler,
|
||||
label: measuring ? "停止测量" : "开始测量",
|
||||
description:
|
||||
activeMeasureModeId === "segment"
|
||||
? measuring
|
||||
? "暂停管线选择"
|
||||
: "点击管线选择资产"
|
||||
: measuring
|
||||
? "暂停地图取点"
|
||||
: "在地图上连续点击取点",
|
||||
variant: measuring ? "default" : "primary",
|
||||
disabled: !measureEnabled,
|
||||
onClick: measuring ? onStopMeasure : onStartMeasure
|
||||
},
|
||||
{
|
||||
id: "copy",
|
||||
icon: Copy,
|
||||
label: "复制结果",
|
||||
description: "复制当前读数",
|
||||
disabled: !hasMeasurePoints,
|
||||
onClick: onCopyMeasure
|
||||
},
|
||||
{
|
||||
id: "clear",
|
||||
icon: Trash2,
|
||||
label: "清除测量",
|
||||
description: activeMeasureModeId === "segment" ? "清空已选管段" : "移除临时测量线",
|
||||
tone: "danger",
|
||||
disabled: !hasMeasurePoints,
|
||||
onClick: onClearMeasure
|
||||
}
|
||||
]}
|
||||
onSelectMode={(id) => onSelectMeasureMode(id as WorkbenchMeasureMode)}
|
||||
onSelectUnit={(id) => onSelectMeasureUnit(id as WorkbenchMeasureUnit)}
|
||||
/>
|
||||
</MapControlPanel>
|
||||
);
|
||||
}
|
||||
|
||||
if (renderedToolId === "analysis") {
|
||||
return (
|
||||
<MapControlPanel key="analysis" title="分析" description="图例与当前覆盖" icon={Icon} widthClassName={panelWidthClassName ?? "w-[316px]"} visible={panelVisible}>
|
||||
<AnalysisPanel legendItems={legendItems} />
|
||||
</MapControlPanel>
|
||||
);
|
||||
}
|
||||
|
||||
if (renderedToolId === "annotation") {
|
||||
return (
|
||||
<MapControlPanel key="annotation" title="标注" description="创建与管理地图标注" icon={Icon} widthClassName={panelWidthClassName ?? "w-[312px]"} visible={panelVisible}>
|
||||
<MapPanelSection title="绘制">
|
||||
<MapDrawToolbar
|
||||
variant="panel"
|
||||
items={DRAW_TOOL_OPTIONS.map((item) => ({
|
||||
...item,
|
||||
active: activeDrawToolId === item.id,
|
||||
disabled:
|
||||
!drawingEnabled ||
|
||||
(item.id === "undo" && !canUndoDrawing) ||
|
||||
(item.id === "redo" && !canRedoDrawing) ||
|
||||
(item.id === "delete" && !canDeleteSelectedDrawing) ||
|
||||
(item.id === "clear" && !canUndoDrawing && !hasDrawingFeatures),
|
||||
onClick: () => {
|
||||
if (item.id === "undo") {
|
||||
onUndoDrawing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.id === "redo") {
|
||||
onRedoDrawing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.id === "delete") {
|
||||
onDeleteSelectedDrawing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.id === "clear") {
|
||||
onClearDrawing();
|
||||
return;
|
||||
}
|
||||
|
||||
onSelectDrawTool(activeDrawToolId === item.id ? null : item.id);
|
||||
}
|
||||
}))}
|
||||
/>
|
||||
</MapPanelSection>
|
||||
<MapAnnotationPanel annotations={annotationItems} shareActions={annotationShareActions} />
|
||||
</MapControlPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MapControlPanel key="more" title="更多" description={activeTool?.description} icon={Icon} widthClassName={panelWidthClassName ?? "w-[292px]"} visible={panelVisible}>
|
||||
<MorePanel
|
||||
onExportView={onExportView}
|
||||
onRefreshTiles={onRefreshTiles}
|
||||
onShowDataStatus={onShowDataStatus}
|
||||
onShowShortcuts={onShowShortcuts}
|
||||
onExportConfig={onExportConfig}
|
||||
/>
|
||||
</MapControlPanel>
|
||||
);
|
||||
}
|
||||
|
||||
function AnalysisPanel({ legendItems }: { legendItems: MapLegendItem[] }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<MapPanelSection title="图例">
|
||||
<MapLegendList items={legendItems} showStatusDot />
|
||||
</MapPanelSection>
|
||||
|
||||
<MapPanelSection title="当前覆盖">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<MapMetricTile label="影响范围" value="未开启" />
|
||||
<MapMetricTile label="风险预览" value="待生成" />
|
||||
</div>
|
||||
<MapActionRow icon={Activity} label="模拟结果" description="影响面、事故点与标签" />
|
||||
<MapActionRow icon={MapPinned} label="选中对象" description="点击管线或节点查看详情" />
|
||||
</MapPanelSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type MorePanelProps = {
|
||||
onExportView: (preset: ExportViewPreset) => void;
|
||||
onRefreshTiles: () => void;
|
||||
onShowDataStatus: () => void;
|
||||
onShowShortcuts: () => void;
|
||||
onExportConfig: () => void;
|
||||
};
|
||||
|
||||
function MorePanel({
|
||||
onExportView,
|
||||
onRefreshTiles,
|
||||
onShowDataStatus,
|
||||
onShowShortcuts,
|
||||
onExportConfig
|
||||
}: MorePanelProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<MapPanelSection title="导出视图">
|
||||
<MapActionRow icon={Camera} label="当前分辨率" description="按当前地图画布尺寸导出" onClick={() => onExportView("current")} />
|
||||
<MapActionRow icon={Camera} label="4K 分辨率" description="长边约 3840px 的 PNG" onClick={() => onExportView("4k")} />
|
||||
</MapPanelSection>
|
||||
|
||||
<MapPanelSection title="数据">
|
||||
<MapActionRow icon={RefreshCw} label="刷新瓦片" description="请求地图重新渲染" onClick={onRefreshTiles} />
|
||||
<MapActionRow icon={Database} label="数据状态" description="查看当前数据源状态" status="查看" onClick={onShowDataStatus} />
|
||||
</MapPanelSection>
|
||||
|
||||
<MapPanelSection title="系统">
|
||||
<MapActionRow icon={Keyboard} label="快捷键" description="查看地图操作键位" onClick={onShowShortcuts} />
|
||||
<MapActionRow icon={Download} label="导出配置" description="保存当前图层与工具状态" onClick={onExportConfig} />
|
||||
</MapPanelSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getDrawingAnnotationIcon(kind: WorkbenchDrawMode) {
|
||||
if (kind === "line") {
|
||||
return Route;
|
||||
}
|
||||
|
||||
if (kind === "polygon") {
|
||||
return Pentagon;
|
||||
}
|
||||
|
||||
if (kind === "circle") {
|
||||
return Circle;
|
||||
}
|
||||
|
||||
return MapPinned;
|
||||
}
|
||||
Reference in New Issue
Block a user