diff --git a/src/cold_display_guard/config.py b/src/cold_display_guard/config.py index 25b0c56..03363b9 100644 --- a/src/cold_display_guard/config.py +++ b/src/cold_display_guard/config.py @@ -58,18 +58,30 @@ def merge_calibration( trash_roi: list[list[float]] | None, ) -> dict[str, Any]: merged = deepcopy(data) - valid_zones = [] + valid_zones: dict[str, dict[str, Any]] = {} for zone in zones: zone_id = str(zone.get("id", "")).strip() polygon = _normalize_points(zone.get("polygon", [])) if not zone_id or len(polygon) < 3: continue - valid_zones.append({"id": zone_id, "polygon": polygon}) + valid_zones[zone_id] = {"id": zone_id, "polygon": polygon} if valid_zones: - merged["zones"] = valid_zones + existing_by_id = { + str(zone.get("id", "")).strip(): zone + for zone in merged.get("zones", []) + if str(zone.get("id", "")).strip() + } + existing_by_id.update(valid_zones) layout = merged.setdefault("layout", {}) - layout["zone_ids"] = [zone["id"] for zone in valid_zones] + zone_order = [str(item) for item in layout.get("zone_ids", []) if str(item) in existing_by_id] + for zone_id in valid_zones: + if zone_id not in zone_order: + zone_order.append(zone_id) + if not zone_order: + zone_order = list(valid_zones) + layout["zone_ids"] = zone_order + merged["zones"] = [existing_by_id[zone_id] for zone_id in zone_order if zone_id in existing_by_id] if trash_roi is not None: normalized_roi = _normalize_points(trash_roi) diff --git a/tests/test_manage_api.py b/tests/test_manage_api.py index 82fd7b9..5554251 100644 --- a/tests/test_manage_api.py +++ b/tests/test_manage_api.py @@ -13,8 +13,8 @@ class ManageApiTests(unittest.TestCase): def test_merge_calibration_updates_zones_and_trash(self) -> None: data = { "camera_id": "cam", - "layout": {"rows": 2, "cols": 4, "zone_ids": ["r1c1"]}, - "zones": [], + "layout": {"rows": 2, "cols": 4, "zone_ids": ["r1c1", "r1c2"]}, + "zones": [{"id": "r1c2", "polygon": [[0.5, 0], [1, 0], [1, 0.5]]}], } merged = merge_calibration( @@ -23,8 +23,9 @@ class ManageApiTests(unittest.TestCase): [[0.8, 0.8], [1, 0.8], [1, 1], [0.8, 1]], ) - self.assertEqual(merged["layout"]["zone_ids"], ["r1c1"]) + self.assertEqual(merged["layout"]["zone_ids"], ["r1c1", "r1c2"]) self.assertEqual(merged["zones"][0]["id"], "r1c1") + self.assertEqual(merged["zones"][1]["id"], "r1c2") self.assertEqual(merged["trash"]["roi"][0], [0.8, 0.8]) def test_save_config_document_round_trips_manage_fields(self) -> None: diff --git a/web/src/main.js b/web/src/main.js index 1eeb8dc..1e4b515 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -57,9 +57,9 @@ app.innerHTML = ` RTSP 地址 - + - +
@@ -184,9 +184,10 @@ async function saveConfig() { }, }; state.config = await apiJson("/api/manage/config", {method: "PUT", body: payload}); + const calibrationSaved = await persistCalibration({requireAny: false}); fillForm(); renderConfigPreview(); - setStatus("配置已保存"); + setStatus(calibrationSaved ? "配置和标定已保存" : "配置已保存;当前没有可保存的标定点"); } catch (error) { setStatus(`保存配置失败:${error.message}`); } @@ -225,29 +226,38 @@ async function captureSnapshot() { async function saveCalibration() { try { - const zones = zoneIds - .map((id) => ({id, polygon: state.polygons[id]})) - .filter((zone) => zone.polygon.length >= 3); - const trashPolygon = state.polygons.trash; - if (zones.length !== zoneIds.length) { - setStatus("8 个格口都标定后才能保存"); - return; + const saved = await persistCalibration({requireAny: true}); + if (saved) { + render(); + setStatus("标定已保存到项目配置"); } - if (trashPolygon.length < 3) { - setStatus("垃圾桶区域至少需要 3 个点"); - return; - } - state.config = await apiJson("/api/manage/calibration", { - method: "PUT", - body: {zones, trash: {roi: trashPolygon}}, - }); - render(); - setStatus("标定已保存到项目配置"); } catch (error) { setStatus(`保存标定失败:${error.message}`); } } +async function persistCalibration({requireAny}) { + const zones = zoneIds + .map((id) => ({id, polygon: state.polygons[id]})) + .filter((zone) => zone.polygon.length >= 3); + const trashPolygon = state.polygons.trash; + const payload = {zones, trash: {}}; + if (trashPolygon.length >= 3) { + payload.trash.roi = trashPolygon; + } + if (!zones.length && !payload.trash.roi) { + if (requireAny) { + setStatus("当前没有可保存的标定点;每个区域至少需要 3 个点"); + } + return false; + } + state.config = await apiJson("/api/manage/calibration", { + method: "PUT", + body: payload, + }); + return true; +} + function setTab(tab) { state.activeTab = tab; document.querySelectorAll(".tabs button").forEach((button) => {