441 lines
14 KiB
TypeScript
441 lines
14 KiB
TypeScript
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;
|
|
}
|