feat: extend map targeting and overlay controls

Support batch feature targeting and clear map highlights and overlays from the More panel. Remove misleading action chevrons and unused configuration export entries.

Validated the isolated patch with lint, typecheck, 36 focused tests and 5 browser cases using mocked data.
This commit is contained in:
2026-10-28 14:47:33 +08:00
parent e57e0f099f
commit 5b710d1247
17 changed files with 666 additions and 202 deletions
+84
View File
@@ -0,0 +1,84 @@
import { expect, test, type Page } from "@playwright/test";
import type { GeoJSONSource } from "maplibre-gl";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
const sourceCount = (page: Page, id: string) => page.evaluate(async (sourceId) => {
const source = window.__waterNetworkMap?.getSource(sourceId) as GeoJSONSource | undefined;
const data = await source?.getData();
return data && typeof data !== "string" && data.type === "FeatureCollection" ? data.features.length : 0;
}, id);
test("More clears controller and investigation overlays while keeping the investigation chart", async ({ page }) => {
const task = investigationFixture(78, "清理地图覆盖层验证");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await page.waitForFunction(() => Boolean(window.__waterNetworkMap?.getLayer("scada-pressure-hit")));
const presentation = page.getByTestId("investigation-presentation-panel");
await expect(presentation.getByText("跟随所选调查", { exact: true })).toBeVisible();
await mock.emit(task, { action: "highlight_features", layer: "scada", feature_ids: ["P1"], effect: "pulse" });
await expect.poll(() => sourceCount(page, "investigation-presentation-highlights")).toBe(1);
await mock.emit(task, { action: "set_overlay", overlay_id: "clear-test", title: "待清理范围", mode: "geometry", evidence_id: "synthetic",
data: { type: "FeatureCollection", features: [{ type: "Feature", properties: {}, geometry: { type: "Point", coordinates: [121.48, 31.1] } }] } });
await expect(presentation.getByRole("region", { name: "调查覆盖层" })).toBeVisible();
await mock.emit(task, { action: "open_chart", chart_id: "retain-chart", evidence_refs: ["synthetic"],
chart: { title: "保留的调查曲线", chart_type: "line", x_data: ["10:00", "10:15"], series: [{ name: "P1", data: [20, 21] }] } });
const chart = presentation.locator('[data-investigation-chart="retain-chart"]');
await expect(chart).toBeVisible();
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const dev = page.getByTestId("map-dev-panel");
await dev.getByRole("combobox", { name: "业务源" }).click();
await page.getByRole("option", { name: "SCADA", exact: true }).click();
await dev.getByLabel("要素 ID(可多个)").fill("P1");
await dev.getByRole("button", { name: "仅高亮", exact: true }).click();
await dev.locator("summary").filter({ hasText: "水流调试" }).click();
await dev.getByRole("button", { name: "开启水流", exact: true }).click();
await dev.locator("summary").filter({ hasText: "数值标注" }).click();
await dev.getByRole("button", { name: "应用标注", exact: true }).click();
await expect.poll(() => page.evaluate(() => window.__waterNetworkMap?.getFeatureState({ source: "scada", id: "P1" }).highlighted)).toBe(true);
await dev.getByRole("button", { name: "关闭 Dev Panel" }).click();
await page.getByRole("button", { name: "更多:更多地图操作", exact: true }).click();
const more = page.getByRole("region", { name: "更多", exact: true });
await more.getByRole("button", { name: /清空所有高亮和覆盖层/ }).click();
await expect.poll(() => sourceCount(page, "investigation-presentation-highlights")).toBe(0);
await expect.poll(() => sourceCount(page, "investigation-presentation-overlay")).toBe(0);
await expect.poll(() => page.evaluate(() => ({
highlighted: window.__waterNetworkMap?.getFeatureState({ source: "scada", id: "P1" }).highlighted ?? false,
flow: window.__waterNetworkMap?.getLayoutProperty("workbench-network-flow-forward", "visibility"),
labels: window.__waterNetworkMap?.getLayoutProperty("workbench-value-label-pipes", "visibility")
}))).toEqual({ highlighted: false, flow: "none", labels: "none" });
await expect(presentation.getByRole("region", { name: "调查覆盖层" })).toHaveCount(0);
await expect(chart).toBeVisible();
await page.screenshot({ path: "/tmp/map-clear-overlays-desktop.png" });
// A later explicit command still works; clearing is a one-time display operation.
await mock.emit(task, { action: "highlight_features", layer: "scada", feature_ids: ["P1"], effect: "steady" });
await expect.poll(() => sourceCount(page, "investigation-presentation-highlights")).toBe(1);
});
test("mobile More clears drawing and measurement data and remains usable when already empty", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await mockInvestigationPresentation(page, []);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.waitForFunction(() => Boolean(window.__waterNetworkMap?.getLayer("scada-pressure-hit")));
await page.getByRole("button", { name: "测量:距离与范围测量", exact: true }).click();
await page.getByRole("region", { name: "测量", exact: true }).getByRole("button", { name: /开始测量/ }).click();
await page.locator("canvas.maplibregl-canvas").click({ position: { x: 100, y: 175 } });
await expect.poll(() => sourceCount(page, "workbench-measurement")).toBeGreaterThan(0);
await page.getByRole("button", { name: "标注:管理地图标注", exact: true }).click();
await page.getByRole("region", { name: "标注", exact: true }).getByRole("button", { name: "点标注", exact: true }).click();
await page.locator("canvas.maplibregl-canvas").click({ position: { x: 100, y: 175 } });
await expect.poll(() => sourceCount(page, "workbench-drawing-features")).toBe(1);
await page.getByRole("button", { name: "更多:更多地图操作", exact: true }).click();
const more = page.getByRole("region", { name: "更多", exact: true });
const clear = more.getByRole("button", { name: /清空所有高亮和覆盖层/ });
await expect(clear).toBeVisible();
await clear.click();
await expect.poll(() => sourceCount(page, "workbench-measurement")).toBe(0);
await expect.poll(() => sourceCount(page, "workbench-drawing-features")).toBe(0);
await clear.click();
await expect(clear).toBeEnabled();
const box = await more.boundingBox();
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(375);
await page.screenshot({ path: "/tmp/map-clear-overlays-mobile.png" });
});