From 622fff10feb3c04cd6c67e41308ddaf156ae130a Mon Sep 17 00:00:00 2001 From: root <13910913995@163.com> Date: Mon, 18 May 2026 12:35:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?websocket=E6=8E=A8=E9=80=81=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/routes/alarms.py | 21 ++- backend/app/api/routes/config.py | 25 +++ backend/app/repositories/alarm_repo.py | 38 +++- backend/app/repositories/json_config_repo.py | 15 ++ backend/app/services/platform_service.py | 97 ++++++++++- backend/tests/test_api.py | 122 +++++++++++++ ...气量测控平台系统需求分析及技术框架设计.md | 164 ++++++++++++++++-- frontend/src/api/platform.ts | 25 +++ frontend/src/stores/platform.ts | 71 ++++++-- frontend/src/views/AlarmHistoryView.vue | 2 +- frontend/src/views/AlarmSettingView.vue | 37 +++- frontend/src/views/ChannelConfigView.vue | 29 +++- frontend/src/views/ControlView.vue | 2 +- frontend/src/views/DeviceConfigView.vue | 32 +++- frontend/src/views/StatusView.vue | 2 +- frontend/src/views/SystemConfigView.vue | 14 +- 16 files changed, 644 insertions(+), 52 deletions(-) diff --git a/backend/app/api/routes/alarms.py b/backend/app/api/routes/alarms.py index d94452e..34eb659 100644 --- a/backend/app/api/routes/alarms.py +++ b/backend/app/api/routes/alarms.py @@ -1,3 +1,6 @@ +from datetime import datetime +from typing import Optional + from fastapi import APIRouter, Query from app.core.response import success_response @@ -8,6 +11,20 @@ router = APIRouter(prefix="/alarm", tags=["alarm"]) @router.get("/list") -def list_alarms(page: int = Query(default=1, ge=1), size: int = Query(default=20, ge=1, le=100)) -> dict: - data = platform_service.list_alarms(page=page, size=size) +def list_alarms( + page: int = Query(default=1, ge=1), + size: int = Query(default=20, ge=1, le=100), + no: Optional[str] = Query(default=None), + type: Optional[str] = Query(default=None), + start_time: Optional[datetime] = Query(default=None), + end_time: Optional[datetime] = Query(default=None), +) -> dict: + data = platform_service.list_alarms( + page=page, + size=size, + no=no, + alarm_type=type, + start_time=start_time, + end_time=end_time, + ) return success_response(data, msg="获取报警历史成功") diff --git a/backend/app/api/routes/config.py b/backend/app/api/routes/config.py index 410b0c6..999fc30 100644 --- a/backend/app/api/routes/config.py +++ b/backend/app/api/routes/config.py @@ -11,26 +11,51 @@ from app.services.platform_service import platform_service router = APIRouter(prefix="/config", tags=["config"]) +@router.get("/device", dependencies=[Depends(verify_api_token)]) +def get_device_config() -> Dict[str, Any]: + return success_response(platform_service.get_device_config(), msg="获取设备配置成功") + + @router.post("/device", dependencies=[Depends(verify_api_token)]) def save_device_config(payload: DeviceConfigIn) -> Dict[str, Any]: return success_response(platform_service.save_device_config(payload)) +@router.get("/channel", dependencies=[Depends(verify_api_token)]) +def get_channel_config() -> Dict[str, Any]: + return success_response(platform_service.get_channel_config(), msg="获取通道配置成功") + + @router.post("/channel", dependencies=[Depends(verify_api_token)]) def save_channel_config(payload: ChannelConfigIn) -> Dict[str, Any]: return success_response(platform_service.save_channel_config(payload)) +@router.get("/line_alarm_setting", dependencies=[Depends(verify_api_token)]) +def get_line_alarm_setting() -> Dict[str, Any]: + return success_response(platform_service.get_line_alarm_setting(), msg="获取线路报警设置成功") + + @router.post("/line_alarm_setting", dependencies=[Depends(verify_api_token)]) def save_line_alarm_setting(payload: LineAlarmSettingIn) -> Dict[str, Any]: return success_response(platform_service.save_line_alarm_setting(payload)) +@router.get("/ai_alarm_setting", dependencies=[Depends(verify_api_token)]) +def get_ai_alarm_setting() -> Dict[str, Any]: + return success_response(platform_service.get_ai_alarm_setting(), msg="获取AI报警设置成功") + + @router.post("/ai_alarm_setting", dependencies=[Depends(verify_api_token)]) def save_ai_alarm_setting(payload: List[AiAlarmSettingIn]) -> Dict[str, Any]: return success_response(platform_service.save_ai_alarm_setting(payload)) +@router.get("/system", dependencies=[Depends(verify_api_token)]) +def get_system_config() -> Dict[str, Any]: + return success_response(platform_service.get_system_config(), msg="获取系统设置成功") + + @router.post("/system", dependencies=[Depends(verify_api_token)]) def save_system_config(payload: SystemConfigIn) -> Dict[str, Any]: return success_response(platform_service.save_system_config(payload)) diff --git a/backend/app/repositories/alarm_repo.py b/backend/app/repositories/alarm_repo.py index 4fca949..7f7b369 100644 --- a/backend/app/repositories/alarm_repo.py +++ b/backend/app/repositories/alarm_repo.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any, Dict, List +from datetime import datetime +from typing import Any, Dict, List, Optional from app.db.sqlite import get_connection from app.schemas.platform import AlarmEvent @@ -26,16 +27,45 @@ class AlarmRepository: connection.commit() return int(cursor.lastrowid) - def list_alarms(self, page: int = 1, size: int = 20) -> List[Dict[str, Any]]: + def list_alarms( + self, + page: int = 1, + size: int = 20, + no: str = "", + alarm_type: str = "", + start_time: Optional[datetime] = None, + end_time: Optional[datetime] = None, + ) -> List[Dict[str, Any]]: offset = (page - 1) * size + conditions = [] + params: List[Any] = [] + + if no: + conditions.append("no = ?") + params.append(no) + if alarm_type: + conditions.append("type = ?") + params.append(alarm_type) + if start_time is not None: + conditions.append("time >= ?") + params.append(start_time.isoformat(sep=" ", timespec="seconds")) + if end_time is not None: + conditions.append("time <= ?") + params.append(end_time.isoformat(sep=" ", timespec="seconds")) + + where_clause = "" + if conditions: + where_clause = "WHERE " + " AND ".join(conditions) + with get_connection() as connection: cursor = connection.execute( - """ + f""" SELECT id, alarm_type, time, no, type, content, level FROM alarm_event + {where_clause} ORDER BY id DESC LIMIT ? OFFSET ? """, - (size, offset), + tuple(params + [size, offset]), ) return [dict(row) for row in cursor.fetchall()] diff --git a/backend/app/repositories/json_config_repo.py b/backend/app/repositories/json_config_repo.py index ebf37aa..eae6768 100644 --- a/backend/app/repositories/json_config_repo.py +++ b/backend/app/repositories/json_config_repo.py @@ -31,6 +31,21 @@ class JsonConfigRepository: def write_setting_config(self, payload: Dict[str, Any]) -> Path: return self._write_json("setting.json", payload) + def read_device_config(self) -> Dict[str, Any]: + data = self.read_json("device.json") + return data if isinstance(data, dict) else {} + + def read_channel_config(self) -> Dict[str, Any]: + data = self.read_json("channel.json") + return data if isinstance(data, dict) else {} + + def read_setting_config(self) -> Dict[str, Any]: + data = self.read_json("setting.json") + return data if isinstance(data, dict) else {} + + def read_setting_section(self, section: str) -> Any: + return self.read_setting_config().get(section) + def read_json(self, filename: str) -> Union[Dict[str, Any], List[Any]]: path = self.config_dir / filename if not path.exists(): diff --git a/backend/app/services/platform_service.py b/backend/app/services/platform_service.py index 44d8d8c..30225f5 100644 --- a/backend/app/services/platform_service.py +++ b/backend/app/services/platform_service.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any, Dict, List +from datetime import datetime +from typing import Any, Dict, List, Optional from app.adapters.device_client import CDeviceClient, MockDeviceClient from app.cache.memory_store import memory_store @@ -50,11 +51,48 @@ class PlatformService: device_result = self.device_client.send_device_config(payload) return {"save_path": f"/config/{path.name}", **device_result} + def get_device_config(self) -> Dict[str, Any]: + current = self.config_repo.read_device_config() + data = { + "password": "", + "hardware_version": { + "board_version": "B001.001.001", + "display_version": "S001.001.001", + "other_version": "Y001.001.001", + }, + "software_version": { + "display_program": "001.001.001", + "communication_program": "001.001.001", + "measurement_program": "001.001.001", + }, + "net": [ + {"nic": "网卡一", "ip": "192.168.1.10", "mask": "255.255.255.0", "gateway": "192.168.1.1", "protocol": "Modbus TCP"}, + {"nic": "网卡二", "ip": "192.168.1.56", "mask": "255.255.255.255", "gateway": "192.168.1.56", "protocol": "Modbus TCP"}, + ], + "uart": [ + {"port": "COM1", "baud": 9600, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": ""}, + {"port": "COM2", "baud": 115200, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": "Modbus RTU"}, + ], + } + data.update(current) + # 不返回已保存的哈希密码,避免前端把哈希串直接显示到设置界面 + data["password"] = "" + return data + def save_channel_config(self, payload: ChannelConfigIn) -> Dict[str, Any]: path = self.config_repo.write_channel_config(payload.model_dump()) device_result = self.device_client.send_channel_config(payload) return {"save_path": f"/config/{path.name}", **device_result} + def get_channel_config(self) -> Dict[str, Any]: + current = self.config_repo.read_channel_config() + data = { + "ai_channel": [{"ch": 1, "singal_type": "4-20mA", "line_no": 1, "type": "UA", "limit_low": 0, "limit_high": 20}], + "ao_channel": [{"ch": 1, "singal_type": "1-5v", "line_no": 2, "type": "UA", "limit_low": 0, "limit_high": 20}], + } + data.update(current) + return data + def save_line_alarm_setting(self, payload: LineAlarmSettingIn) -> Dict[str, Any]: current = self.config_repo.read_json("setting.json") if not isinstance(current, dict): @@ -64,6 +102,21 @@ class PlatformService: device_result = self.device_client.send_line_alarm_setting(payload) return {"save_path": f"/config/{path.name}", **device_result} + def get_line_alarm_setting(self) -> Dict[str, Any]: + current = self.config_repo.read_setting_section("line_alarm_setting") + if isinstance(current, dict): + return current + return { + "line_no": 1, + "over_limit_alarm": [ + {"category": "电压", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True}, + {"category": "电流", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True}, + {"category": "差流", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": False}, + {"category": "频率", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": False}, + ], + "fault_alarm": [{"category": "PT断线", "delay": 180, "output_node": "开出1", "enabled": True}], + } + def save_ai_alarm_setting(self, payload: List[AiAlarmSettingIn]) -> Dict[str, Any]: current = self.config_repo.read_json("setting.json") if not isinstance(current, dict): @@ -73,6 +126,22 @@ class PlatformService: device_result = self.device_client.send_ai_alarm_setting(current["ai_alarm_setting"]) return {"save_path": f"/config/{path.name}", **device_result} + def get_ai_alarm_setting(self) -> List[Dict[str, Any]]: + current = self.config_repo.read_setting_section("ai_alarm_setting") + if isinstance(current, list): + return current + return [ + { + "channel_no": 1, + "singal_type": "4-20mA", + "limit_low": 0, + "limit_high": 20, + "delay": 180, + "output_node": "开出1", + "enabled": True, + } + ] + def save_system_config(self, payload: SystemConfigIn) -> Dict[str, Any]: current = self.config_repo.read_json("setting.json") if not isinstance(current, dict): @@ -81,8 +150,30 @@ class PlatformService: self.config_repo.write_setting_config(current) return self.device_client.send_system_config(payload) - def list_alarms(self, page: int, size: int) -> List[Dict[str, Any]]: - return self.alarm_repo.list_alarms(page=page, size=size) + def get_system_config(self) -> Dict[str, Any]: + current = self.config_repo.read_setting_section("system_config") + data = SystemConfigIn().model_dump() + if isinstance(current, dict): + data.update(current) + return data + + def list_alarms( + self, + page: int, + size: int, + no: str = "", + alarm_type: str = "", + start_time: Optional[datetime] = None, + end_time: Optional[datetime] = None, + ) -> List[Dict[str, Any]]: + return self.alarm_repo.list_alarms( + page=page, + size=size, + no=no, + alarm_type=alarm_type, + start_time=start_time, + end_time=end_time, + ) def switch_control(self, payload: SwitchControlIn) -> Dict[str, Any]: return self.device_client.send_switch_control(payload) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index e8225cb..a36b72c 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -1,6 +1,14 @@ +from datetime import datetime + from fastapi.testclient import TestClient +from app.core.config import settings +from app.db.sqlite import get_connection, init_db from app.main import app +from app.repositories.alarm_repo import AlarmRepository +from app.repositories.json_config_repo import JsonConfigRepository +from app.schemas.platform import AlarmEvent +from app.services.platform_service import platform_service client = TestClient(app) @@ -18,3 +26,117 @@ def test_realtime_endpoint() -> None: payload = response.json() assert payload["code"] == 200 assert "line_list" in payload["data"] + + +def test_alarm_list_filters(tmp_path) -> None: + settings.alarm_db_path = tmp_path / "alarm.db" + init_db() + + with get_connection() as connection: + connection.execute("DELETE FROM alarm_event") + connection.commit() + + repo = AlarmRepository() + repo.save_alarm( + AlarmEvent( + alarm_type="保护报警", + time=datetime(2026, 5, 16, 10, 0, 0), + no="L1", + type="voltage", + content="A 相过压", + level="严重", + ) + ) + repo.save_alarm( + AlarmEvent( + alarm_type="状态报警", + time=datetime(2026, 5, 16, 11, 0, 0), + no="L2", + type="current", + content="B 相过流", + level="一般", + ) + ) + + response = client.get( + "/api/alarm/list", + params={ + "page": 1, + "size": 20, + "no": "L2", + "type": "current", + "start_time": "2026-05-16T10:30:00", + "end_time": "2026-05-16T11:30:00", + }, + ) + + assert response.status_code == 200 + payload = response.json() + assert payload["code"] == 200 + assert len(payload["data"]) == 1 + assert payload["data"][0]["no"] == "L2" + assert payload["data"][0]["type"] == "current" + + +def test_config_query_endpoints(tmp_path) -> None: + old_repo = platform_service.config_repo + platform_service.config_repo = JsonConfigRepository(tmp_path) + + try: + platform_service.config_repo.write_device_config( + { + "password": "hashed-password", + "hardware_version": { + "board_version": "B001.001.002", + "display_version": "S001.001.001", + "other_version": "Y001.001.001", + }, + "software_version": { + "display_program": "001.001.001", + "communication_program": "001.001.001", + "measurement_program": "001.001.001", + }, + "net": [ + {"nic": "网卡一", "ip": "192.168.1.10", "mask": "255.255.255.0", "gateway": "192.168.1.1", "protocol": "Modbus TCP"} + ], + "uart": [ + {"port": "COM1", "baud": 9600, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": "Modbus RTU"} + ], + } + ) + platform_service.config_repo.write_channel_config( + { + "ai_channel": [{"ch": 1, "singal_type": "4-20mA", "line_no": 1, "type": "UA", "limit_low": 0, "limit_high": 20}], + "ao_channel": [{"ch": 1, "singal_type": "1-5v", "line_no": 2, "type": "UA", "limit_low": 0, "limit_high": 20}], + } + ) + platform_service.config_repo.write_setting_config( + { + "line_alarm_setting": {"line_no": 1, "over_limit_alarm": [{"category": "电压", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True}], "fault_alarm": []}, + "ai_alarm_setting": [{"channel_no": 1, "singal_type": "4-20mA", "limit_low": 0, "limit_high": 20, "delay": 180, "output_node": "开出1", "enabled": True}], + "system_config": {"time_sync": "manual", "brightness": 83, "screen_saver": 60}, + } + ) + + headers = {"X-API-Token": settings.auth_password} + + device_response = client.get("/api/config/device", headers=headers) + channel_response = client.get("/api/config/channel", headers=headers) + line_alarm_response = client.get("/api/config/line_alarm_setting", headers=headers) + ai_alarm_response = client.get("/api/config/ai_alarm_setting", headers=headers) + system_response = client.get("/api/config/system", headers=headers) + + assert device_response.status_code == 200 + assert channel_response.status_code == 200 + assert line_alarm_response.status_code == 200 + assert ai_alarm_response.status_code == 200 + assert system_response.status_code == 200 + + assert device_response.json()["data"]["password"] == "" + assert device_response.json()["data"]["net"][0]["ip"] == "192.168.1.10" + assert channel_response.json()["data"]["ai_channel"][0]["singal_type"] == "4-20mA" + assert line_alarm_response.json()["data"]["line_no"] == 1 + assert ai_alarm_response.json()["data"][0]["channel_no"] == 1 + assert system_response.json()["data"]["brightness"] == 83 + finally: + platform_service.config_repo = old_repo diff --git a/document/电气量测控平台系统需求分析及技术框架设计.md b/document/电气量测控平台系统需求分析及技术框架设计.md index a437fbb..e8e07d6 100644 --- a/document/电气量测控平台系统需求分析及技术框架设计.md +++ b/document/电气量测控平台系统需求分析及技术框架设计.md @@ -34,6 +34,9 @@ 3. **系统管理配置** - 对时设置:参数实时下发嵌入式程序 - 灯光设置:屏幕亮度、屏保时间,参数实时下发嵌入式程序 +4. **配置回显要求** + - 设备网络配置、通道配置、报警设置、系统设置页面在打开时需先读取已保存参数 + - 已保存配置从本地JSON文件读取,并通过HTTP查询接口返回给Web界面显示 #### 2.2.2 实时数据动态显示功能 1. **数据读取周期**:间隔**0.5秒**从C语言控制系统读取实时数据 @@ -126,6 +129,8 @@ Python FastAPI服务层(RESTful接口 + WebSocket推送 + 数据处理) Web界面 → HTTP POST → Python → JSON存储 + 下发C程序 3. **状态/查询流向** Web界面 → HTTP GET → Python → 返回数据展示 +4. **配置回显流向** +Web界面进入设置页 → HTTP GET配置查询接口 → Python读取JSON配置 → 返回当前配置数据 → Web界面显示 ## 四、接口详细设计(RESTful + WebSocket) ### 4.1 RESTful API 接口详情(完整参数+返回值) @@ -288,7 +293,48 @@ Web界面 → HTTP GET → Python → 返回数据展示 --- -#### 3. POST /api/config/device +#### 3. GET /api/config/device +**作用**:读取设备基础配置,用于设备/网络配置界面打开时回显当前参数 +**参数**:无 +**返回data**: +```json +{ + "password": "", + "hardware_version": { + "board_version": "B001.001.001", + "display_version": "S001.001.001", + "other_version": "Y001.001.001" + }, + "software_version": { + "display_program": "001.001.001", + "communication_program": "001.001.001", + "measurement_program": "001.001.001" + }, + "net": [ + { + "nic": "网卡一", + "ip": "192.168.1.10", + "mask": "255.255.255.0", + "gateway": "192.168.1.1", + "protocol": "Modbus TCP" + } + ], + "uart": [ + { + "port": "COM1", + "baud": 9600, + "parity": "NONE", + "data_bits": 8, + "stop_bits": 1, + "protocol": "Modbus RTU" + } + ] +} +``` + +--- + +#### 4. POST /api/config/device **作用**:提交设备基础配置(版本、通讯、密码) **请求body参数**: ```json @@ -344,7 +390,20 @@ Web界面 → HTTP GET → Python → 返回数据展示 --- -#### 4. POST /api/config/channel +#### 5. GET /api/config/channel +**作用**:读取通道配置,用于通道配置界面打开时回显当前参数 +**参数**:无 +**返回data**: +```json +{ + "ai_channel": [{"ch":1,"singal_type":"4-20mA","line_no":1,"type":"UA","limit_low":0,"limit_high":20}], + "ao_channel": [{"ch":1,"singal_type":"1-5v","line_no":2,"type":"UA","limit_low":0,"limit_high":20}] +} +``` + +--- + +#### 6. POST /api/config/channel **作用**:提交AI/AO通道配置(AI:12通道,AO:12通道) **请求body参数**: @@ -358,13 +417,42 @@ Web界面 → HTTP GET → Python → 返回数据展示 --- -#### 5. POST /api/config/line_alarm_setting +#### 7. GET /api/config/line_alarm_setting +**作用**:读取线路报警设置,用于报警设置界面打开时回显当前线路参数 +**参数**:无 +**返回data**: +```json +{ + "line_no": 1, + "over_limit_alarm": [ + { + "category": "电压", + "limit": 180, + "delay": 180, + "output_node": "开出1", + "enabled": true + } + ], + "fault_alarm": [ + { + "category": "PT断线", + "delay": 180, + "output_node": "开出1", + "enabled": true + } + ] +} +``` + +--- + +#### 8. POST /api/config/line_alarm_setting **作用**:提交定值报警阈值配置 **请求body参数**: ```json -{ - "line_no": 1, +[ + {"line_no": 1, "over_limit_alarm": [ { "category": "电压", @@ -416,13 +504,34 @@ Web界面 → HTTP GET → Python → 返回数据展示 "enabled": false } ] - } + } + ] ``` **返回data**:`{"save_path":"/config/setting.json","send_status":"成功"}` --- -#### 5. POST /api/config/ai_alarm_setting +#### 9. GET /api/config/ai_alarm_setting +**作用**:读取AI报警设置,用于报警设置界面打开时回显当前AI报警参数 +**参数**:无 +**返回data**: +```json +[ + { + "channel_no": 1, + "singal_type": "4-20mA", + "limit_low": 0, + "limit_high": 20, + "delay": 180, + "output_node": "开出1", + "enabled": true + } +] +``` + +--- + +#### 10. POST /api/config/ai_alarm_setting **作用**:提交AI报警设置 **请求body参数**: @@ -447,12 +556,26 @@ Web界面 → HTTP GET → Python → 返回数据展示 "output_node": "开出1", "enabled": true }, - ] + ] ``` **返回data**:`{"save_path":"/config/setting.json","send_status":"成功"}` -#### 6. POST /api/config/system +#### 11. GET /api/config/system +**作用**:读取系统设置,用于系统设置界面打开时回显当前参数 +**参数**:无 +**返回data**: +```json +{ + "time_sync": "auto", + "brightness": 80, + "screen_saver": 60 +} +``` + +--- + +#### 12. POST /api/config/system **作用**:提交系统对时、灯光配置 **请求body参数**: @@ -467,9 +590,26 @@ Web界面 → HTTP GET → Python → 返回数据展示 --- -#### 7. GET /api/alarm/list +#### 13. GET /api/alarm/list **作用**:分页查询历史报警 -**参数**:`page=1&size=20` +**参数**: +- `page=1`:页码,从 `1` 开始 +- `size=20`:每页条数,最大 `100` +- `no=L1`:按线路号/通道号精确筛选 +- `type=current`:按报警类型字段精确筛选 +- `start_time=2026-05-16T10:30:00`:按报警开始时间筛选 +- `end_time=2026-05-16T11:30:00`:按报警结束时间筛选 + +**示例请求**: +```text +GET /api/alarm/list?page=1&size=20&no=L1&type=current&start_time=2026-05-16T10:30:00&end_time=2026-05-16T11:30:00 +``` + +说明: +- 所有筛选条件均为可选 +- 未传筛选条件时,默认按分页查询全部历史报警 +- `start_time` 和 `end_time` 使用日期时间格式 + **返回data**: ```json [ @@ -487,7 +627,7 @@ Web界面 → HTTP GET → Python → 返回数据展示 --- -#### 8. POST /api/control/switch +#### 14. POST /api/control/switch **作用**:下发开关量控制指令 **请求body参数**: ```json diff --git a/frontend/src/api/platform.ts b/frontend/src/api/platform.ts index 5cef58a..0b40ef0 100644 --- a/frontend/src/api/platform.ts +++ b/frontend/src/api/platform.ts @@ -21,6 +21,31 @@ export async function fetchDeviceStatus(): Promise { return response.data.data } +export async function fetchDeviceConfig(): Promise { + const response = await http.get>('/config/device') + return response.data.data +} + +export async function fetchChannelConfig(): Promise { + const response = await http.get>('/config/channel') + return response.data.data +} + +export async function fetchLineAlarmSetting(): Promise { + const response = await http.get>('/config/line_alarm_setting') + return response.data.data +} + +export async function fetchAiAlarmSetting(): Promise { + const response = await http.get>('/config/ai_alarm_setting') + return response.data.data +} + +export async function fetchSystemConfig(): Promise { + const response = await http.get>('/config/system') + return response.data.data +} + export async function fetchAlarmHistory(page = 1, size = 20): Promise { const response = await http.get>(`/alarm/list?page=${page}&size=${size}`) return response.data.data diff --git a/frontend/src/stores/platform.ts b/frontend/src/stores/platform.ts index e2be739..61e41e7 100644 --- a/frontend/src/stores/platform.ts +++ b/frontend/src/stores/platform.ts @@ -38,6 +38,8 @@ const state = reactive({ let realtimeSocket: WebSocket | null = null let alarmSocket: WebSocket | null = null let realtimePollTimer: number | null = null +let realtimeReconnectTimer: number | null = null +let realtimeReconnectAttempts = 0 function markRealtime(data: RealtimeData, source: string) { state.realtime = data @@ -50,6 +52,57 @@ async function syncRealtimeByHttp(source = 'http-poll') { markRealtime(realtime, source) } +function clearRealtimeReconnectTimer() { + if (realtimeReconnectTimer !== null) { + window.clearTimeout(realtimeReconnectTimer) + realtimeReconnectTimer = null + } +} + +function scheduleRealtimeReconnect() { + if (realtimeReconnectTimer !== null) { + return + } + const delay = Math.min(5000, 1000 * Math.max(1, realtimeReconnectAttempts + 1)) + realtimeReconnectTimer = window.setTimeout(() => { + realtimeReconnectTimer = null + realtimeReconnectAttempts += 1 + startRealtimeSocket() + }, delay) +} + +function startRealtimeSocket() { + if (realtimeSocket && [WebSocket.CONNECTING, WebSocket.OPEN].includes(realtimeSocket.readyState)) { + return + } + + const socket = connectRealtimeWithLifecycle( + (data) => { + state.realtimeConnected = true + markRealtime(data, 'websocket') + }, + { + onOpen: () => { + state.realtimeConnected = true + realtimeReconnectAttempts = 0 + clearRealtimeReconnectTimer() + }, + onClose: () => { + state.realtimeConnected = false + if (realtimeSocket === socket) { + realtimeSocket = null + } + scheduleRealtimeReconnect() + }, + onError: () => { + state.realtimeConnected = false + }, + }, + ) + + realtimeSocket = socket +} + async function refreshStatus() { state.statusLoading = true try { @@ -93,23 +146,7 @@ async function bootstrap() { await refreshAlarms(1) if (!realtimeSocket) { - realtimeSocket = connectRealtimeWithLifecycle( - (data) => { - state.realtimeConnected = true - markRealtime(data, 'websocket') - }, - { - onOpen: () => { - state.realtimeConnected = true - }, - onClose: () => { - state.realtimeConnected = false - }, - onError: () => { - state.realtimeConnected = false - }, - }, - ) + startRealtimeSocket() } if (!alarmSocket) { diff --git a/frontend/src/views/AlarmHistoryView.vue b/frontend/src/views/AlarmHistoryView.vue index b936793..a745dc2 100644 --- a/frontend/src/views/AlarmHistoryView.vue +++ b/frontend/src/views/AlarmHistoryView.vue @@ -1,4 +1,4 @@ - diff --git a/frontend/src/views/AlarmSettingView.vue b/frontend/src/views/AlarmSettingView.vue index a586794..c67aeea 100644 --- a/frontend/src/views/AlarmSettingView.vue +++ b/frontend/src/views/AlarmSettingView.vue @@ -1,6 +1,6 @@ - diff --git a/frontend/src/views/SystemConfigView.vue b/frontend/src/views/SystemConfigView.vue index 6785909..d50d1b1 100644 --- a/frontend/src/views/SystemConfigView.vue +++ b/frontend/src/views/SystemConfigView.vue @@ -1,6 +1,6 @@