增加了配置查询功能,解决websocket推送问题。

This commit is contained in:
root 2026-05-18 12:35:27 +08:00
parent b8974dce59
commit 622fff10fe
16 changed files with 644 additions and 52 deletions

View File

@ -1,3 +1,6 @@
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from app.core.response import success_response from app.core.response import success_response
@ -8,6 +11,20 @@ router = APIRouter(prefix="/alarm", tags=["alarm"])
@router.get("/list") @router.get("/list")
def list_alarms(page: int = Query(default=1, ge=1), size: int = Query(default=20, ge=1, le=100)) -> dict: def list_alarms(
data = platform_service.list_alarms(page=page, size=size) 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="获取报警历史成功") return success_response(data, msg="获取报警历史成功")

View File

@ -11,26 +11,51 @@ from app.services.platform_service import platform_service
router = APIRouter(prefix="/config", tags=["config"]) 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)]) @router.post("/device", dependencies=[Depends(verify_api_token)])
def save_device_config(payload: DeviceConfigIn) -> Dict[str, Any]: def save_device_config(payload: DeviceConfigIn) -> Dict[str, Any]:
return success_response(platform_service.save_device_config(payload)) 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)]) @router.post("/channel", dependencies=[Depends(verify_api_token)])
def save_channel_config(payload: ChannelConfigIn) -> Dict[str, Any]: def save_channel_config(payload: ChannelConfigIn) -> Dict[str, Any]:
return success_response(platform_service.save_channel_config(payload)) 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)]) @router.post("/line_alarm_setting", dependencies=[Depends(verify_api_token)])
def save_line_alarm_setting(payload: LineAlarmSettingIn) -> Dict[str, Any]: def save_line_alarm_setting(payload: LineAlarmSettingIn) -> Dict[str, Any]:
return success_response(platform_service.save_line_alarm_setting(payload)) 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)]) @router.post("/ai_alarm_setting", dependencies=[Depends(verify_api_token)])
def save_ai_alarm_setting(payload: List[AiAlarmSettingIn]) -> Dict[str, Any]: def save_ai_alarm_setting(payload: List[AiAlarmSettingIn]) -> Dict[str, Any]:
return success_response(platform_service.save_ai_alarm_setting(payload)) 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)]) @router.post("/system", dependencies=[Depends(verify_api_token)])
def save_system_config(payload: SystemConfigIn) -> Dict[str, Any]: def save_system_config(payload: SystemConfigIn) -> Dict[str, Any]:
return success_response(platform_service.save_system_config(payload)) return success_response(platform_service.save_system_config(payload))

View File

@ -1,6 +1,7 @@
from __future__ import annotations 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.db.sqlite import get_connection
from app.schemas.platform import AlarmEvent from app.schemas.platform import AlarmEvent
@ -26,16 +27,45 @@ class AlarmRepository:
connection.commit() connection.commit()
return int(cursor.lastrowid) 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 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: with get_connection() as connection:
cursor = connection.execute( cursor = connection.execute(
""" f"""
SELECT id, alarm_type, time, no, type, content, level SELECT id, alarm_type, time, no, type, content, level
FROM alarm_event FROM alarm_event
{where_clause}
ORDER BY id DESC ORDER BY id DESC
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
""", """,
(size, offset), tuple(params + [size, offset]),
) )
return [dict(row) for row in cursor.fetchall()] return [dict(row) for row in cursor.fetchall()]

View File

@ -31,6 +31,21 @@ class JsonConfigRepository:
def write_setting_config(self, payload: Dict[str, Any]) -> Path: def write_setting_config(self, payload: Dict[str, Any]) -> Path:
return self._write_json("setting.json", payload) 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]]: def read_json(self, filename: str) -> Union[Dict[str, Any], List[Any]]:
path = self.config_dir / filename path = self.config_dir / filename
if not path.exists(): if not path.exists():

View File

@ -1,6 +1,7 @@
from __future__ import annotations 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.adapters.device_client import CDeviceClient, MockDeviceClient
from app.cache.memory_store import memory_store from app.cache.memory_store import memory_store
@ -50,11 +51,48 @@ class PlatformService:
device_result = self.device_client.send_device_config(payload) device_result = self.device_client.send_device_config(payload)
return {"save_path": f"/config/{path.name}", **device_result} 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]: def save_channel_config(self, payload: ChannelConfigIn) -> Dict[str, Any]:
path = self.config_repo.write_channel_config(payload.model_dump()) path = self.config_repo.write_channel_config(payload.model_dump())
device_result = self.device_client.send_channel_config(payload) device_result = self.device_client.send_channel_config(payload)
return {"save_path": f"/config/{path.name}", **device_result} 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]: def save_line_alarm_setting(self, payload: LineAlarmSettingIn) -> Dict[str, Any]:
current = self.config_repo.read_json("setting.json") current = self.config_repo.read_json("setting.json")
if not isinstance(current, dict): if not isinstance(current, dict):
@ -64,6 +102,21 @@ class PlatformService:
device_result = self.device_client.send_line_alarm_setting(payload) device_result = self.device_client.send_line_alarm_setting(payload)
return {"save_path": f"/config/{path.name}", **device_result} 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]: def save_ai_alarm_setting(self, payload: List[AiAlarmSettingIn]) -> Dict[str, Any]:
current = self.config_repo.read_json("setting.json") current = self.config_repo.read_json("setting.json")
if not isinstance(current, dict): if not isinstance(current, dict):
@ -73,6 +126,22 @@ class PlatformService:
device_result = self.device_client.send_ai_alarm_setting(current["ai_alarm_setting"]) device_result = self.device_client.send_ai_alarm_setting(current["ai_alarm_setting"])
return {"save_path": f"/config/{path.name}", **device_result} 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]: def save_system_config(self, payload: SystemConfigIn) -> Dict[str, Any]:
current = self.config_repo.read_json("setting.json") current = self.config_repo.read_json("setting.json")
if not isinstance(current, dict): if not isinstance(current, dict):
@ -81,8 +150,30 @@ class PlatformService:
self.config_repo.write_setting_config(current) self.config_repo.write_setting_config(current)
return self.device_client.send_system_config(payload) return self.device_client.send_system_config(payload)
def list_alarms(self, page: int, size: int) -> List[Dict[str, Any]]: def get_system_config(self) -> Dict[str, Any]:
return self.alarm_repo.list_alarms(page=page, size=size) 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]: def switch_control(self, payload: SwitchControlIn) -> Dict[str, Any]:
return self.device_client.send_switch_control(payload) return self.device_client.send_switch_control(payload)

View File

@ -1,6 +1,14 @@
from datetime import datetime
from fastapi.testclient import TestClient 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.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) client = TestClient(app)
@ -18,3 +26,117 @@ def test_realtime_endpoint() -> None:
payload = response.json() payload = response.json()
assert payload["code"] == 200 assert payload["code"] == 200
assert "line_list" in payload["data"] 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

View File

@ -34,6 +34,9 @@
3. **系统管理配置** 3. **系统管理配置**
- 对时设置:参数实时下发嵌入式程序 - 对时设置:参数实时下发嵌入式程序
- 灯光设置:屏幕亮度、屏保时间,参数实时下发嵌入式程序 - 灯光设置:屏幕亮度、屏保时间,参数实时下发嵌入式程序
4. **配置回显要求**
- 设备网络配置、通道配置、报警设置、系统设置页面在打开时需先读取已保存参数
- 已保存配置从本地JSON文件读取并通过HTTP查询接口返回给Web界面显示
#### 2.2.2 实时数据动态显示功能 #### 2.2.2 实时数据动态显示功能
1. **数据读取周期**:间隔**0.5秒**从C语言控制系统读取实时数据 1. **数据读取周期**:间隔**0.5秒**从C语言控制系统读取实时数据
@ -126,6 +129,8 @@ Python FastAPI服务层RESTful接口 + WebSocket推送 + 数据处理)
Web界面 → HTTP POST → Python → JSON存储 + 下发C程序 Web界面 → HTTP POST → Python → JSON存储 + 下发C程序
3. **状态/查询流向** 3. **状态/查询流向**
Web界面 → HTTP GET → Python → 返回数据展示 Web界面 → HTTP GET → Python → 返回数据展示
4. **配置回显流向**
Web界面进入设置页 → HTTP GET配置查询接口 → Python读取JSON配置 → 返回当前配置数据 → Web界面显示
## 四、接口详细设计RESTful + WebSocket ## 四、接口详细设计RESTful + WebSocket
### 4.1 RESTful API 接口详情(完整参数+返回值) ### 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参数** **请求body参数**
```json ```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通道配置(AI12通道AO12通道) **作用**提交AI/AO通道配置(AI12通道AO12通道)
**请求body参数** **请求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参数** **请求body参数**
```json ```json
{ [
"line_no": 1, {"line_no": 1,
"over_limit_alarm": [ "over_limit_alarm": [
{ {
"category": "电压", "category": "电压",
@ -416,13 +504,34 @@ Web界面 → HTTP GET → Python → 返回数据展示
"enabled": false "enabled": false
} }
] ]
} }
]
``` ```
**返回data**`{"save_path":"/config/setting.json","send_status":"成功"}` **返回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报警设置 **作用**提交AI报警设置
**请求body参数** **请求body参数**
@ -447,12 +556,26 @@ Web界面 → HTTP GET → Python → 返回数据展示
"output_node": "开出1", "output_node": "开出1",
"enabled": true "enabled": true
}, },
] ]
``` ```
**返回data**`{"save_path":"/config/setting.json","send_status":"成功"}` **返回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参数** **请求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** **返回data**
```json ```json
[ [
@ -487,7 +627,7 @@ Web界面 → HTTP GET → Python → 返回数据展示
--- ---
#### 8. POST /api/control/switch #### 14. POST /api/control/switch
**作用**:下发开关量控制指令 **作用**:下发开关量控制指令
**请求body参数** **请求body参数**
```json ```json

View File

@ -21,6 +21,31 @@ export async function fetchDeviceStatus(): Promise<DeviceStatus> {
return response.data.data return response.data.data
} }
export async function fetchDeviceConfig(): Promise<DeviceConfigPayload> {
const response = await http.get<ApiResponse<DeviceConfigPayload>>('/config/device')
return response.data.data
}
export async function fetchChannelConfig(): Promise<ChannelConfigPayload> {
const response = await http.get<ApiResponse<ChannelConfigPayload>>('/config/channel')
return response.data.data
}
export async function fetchLineAlarmSetting(): Promise<LineAlarmSettingPayload> {
const response = await http.get<ApiResponse<LineAlarmSettingPayload>>('/config/line_alarm_setting')
return response.data.data
}
export async function fetchAiAlarmSetting(): Promise<AiAlarmSettingItem[]> {
const response = await http.get<ApiResponse<AiAlarmSettingItem[]>>('/config/ai_alarm_setting')
return response.data.data
}
export async function fetchSystemConfig(): Promise<SystemConfigPayload> {
const response = await http.get<ApiResponse<SystemConfigPayload>>('/config/system')
return response.data.data
}
export async function fetchAlarmHistory(page = 1, size = 20): Promise<AlarmEvent[]> { export async function fetchAlarmHistory(page = 1, size = 20): Promise<AlarmEvent[]> {
const response = await http.get<ApiResponse<AlarmEvent[]>>(`/alarm/list?page=${page}&size=${size}`) const response = await http.get<ApiResponse<AlarmEvent[]>>(`/alarm/list?page=${page}&size=${size}`)
return response.data.data return response.data.data

View File

@ -38,6 +38,8 @@ const state = reactive<PlatformState>({
let realtimeSocket: WebSocket | null = null let realtimeSocket: WebSocket | null = null
let alarmSocket: WebSocket | null = null let alarmSocket: WebSocket | null = null
let realtimePollTimer: number | null = null let realtimePollTimer: number | null = null
let realtimeReconnectTimer: number | null = null
let realtimeReconnectAttempts = 0
function markRealtime(data: RealtimeData, source: string) { function markRealtime(data: RealtimeData, source: string) {
state.realtime = data state.realtime = data
@ -50,6 +52,57 @@ async function syncRealtimeByHttp(source = 'http-poll') {
markRealtime(realtime, source) 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() { async function refreshStatus() {
state.statusLoading = true state.statusLoading = true
try { try {
@ -93,23 +146,7 @@ async function bootstrap() {
await refreshAlarms(1) await refreshAlarms(1)
if (!realtimeSocket) { if (!realtimeSocket) {
realtimeSocket = connectRealtimeWithLifecycle( startRealtimeSocket()
(data) => {
state.realtimeConnected = true
markRealtime(data, 'websocket')
},
{
onOpen: () => {
state.realtimeConnected = true
},
onClose: () => {
state.realtimeConnected = false
},
onError: () => {
state.realtimeConnected = false
},
},
)
} }
if (!alarmSocket) { if (!alarmSocket) {

View File

@ -1,4 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ store: any; actions: any }>() defineProps<{ store: any; actions: any }>()
</script> </script>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { saveAiAlarmSetting, saveLineAlarmSetting } from '../api/platform' import { fetchAiAlarmSetting, fetchLineAlarmSetting, saveAiAlarmSetting, saveLineAlarmSetting } from '../api/platform'
import type { AiAlarmSettingItem, LineAlarmSettingPayload } from '../types/platform' import type { AiAlarmSettingItem, LineAlarmSettingPayload } from '../types/platform'
import { ensureSaveAuthorized } from '../utils/saveGuard' import { ensureSaveAuthorized } from '../utils/saveGuard'
@ -32,6 +32,37 @@ const aiAlarm = reactive<AiAlarmSettingItem[]>([
const result = ref('未保存') const result = ref('未保存')
const saving = ref(false) const saving = ref(false)
onMounted(async () => {
try {
const [lineData, aiData] = await Promise.all([fetchLineAlarmSetting(), fetchAiAlarmSetting()])
lineAlarm.line_no = lineData.line_no
lineData.over_limit_alarm.forEach((item, index) => {
if (lineAlarm.over_limit_alarm[index]) {
Object.assign(lineAlarm.over_limit_alarm[index], item)
} else {
lineAlarm.over_limit_alarm.push(item)
}
})
lineData.fault_alarm.forEach((item, index) => {
if (lineAlarm.fault_alarm[index]) {
Object.assign(lineAlarm.fault_alarm[index], item)
} else {
lineAlarm.fault_alarm.push(item)
}
})
aiData.forEach((item, index) => {
if (aiAlarm[index]) {
Object.assign(aiAlarm[index], item)
} else {
aiAlarm.push(item)
}
})
result.value = '已加载当前报警设置'
} catch (error) {
result.value = error instanceof Error ? error.message : '加载报警设置失败'
}
})
async function save() { async function save() {
saving.value = true saving.value = true
try { try {

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { saveChannelConfig } from '../api/platform' import { fetchChannelConfig, saveChannelConfig } from '../api/platform'
import type { ChannelConfigPayload } from '../types/platform' import type { ChannelConfigPayload } from '../types/platform'
import { ensureSaveAuthorized } from '../utils/saveGuard' import { ensureSaveAuthorized } from '../utils/saveGuard'
@ -14,6 +14,29 @@ const form = reactive<ChannelConfigPayload>({
const result = ref('未保存') const result = ref('未保存')
const saving = ref(false) const saving = ref(false)
onMounted(async () => {
try {
const data = await fetchChannelConfig()
data.ai_channel.forEach((item, index) => {
if (form.ai_channel[index]) {
Object.assign(form.ai_channel[index], item)
} else {
form.ai_channel.push(item)
}
})
data.ao_channel.forEach((item, index) => {
if (form.ao_channel[index]) {
Object.assign(form.ao_channel[index], item)
} else {
form.ao_channel.push(item)
}
})
result.value = '已加载当前通道配置'
} catch (error) {
result.value = error instanceof Error ? error.message : '加载通道配置失败'
}
})
async function save() { async function save() {
saving.value = true saving.value = true
try { try {

View File

@ -1,4 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue" import { ref } from "vue"
import { controlSwitch } from "../api/platform" import { controlSwitch } from "../api/platform"

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { saveDeviceConfig } from '../api/platform' import { fetchDeviceConfig, saveDeviceConfig } from '../api/platform'
import type { DeviceConfigPayload } from '../types/platform' import type { DeviceConfigPayload } from '../types/platform'
import { ensureSaveAuthorized } from '../utils/saveGuard' import { ensureSaveAuthorized } from '../utils/saveGuard'
@ -31,6 +31,32 @@ const form = reactive<DeviceConfigPayload>({
const result = ref('未保存') const result = ref('未保存')
const saving = ref(false) const saving = ref(false)
onMounted(async () => {
try {
const data = await fetchDeviceConfig()
form.password = data.password
Object.assign(form.hardware_version, data.hardware_version)
Object.assign(form.software_version, data.software_version)
data.net.forEach((item, index) => {
if (form.net[index]) {
Object.assign(form.net[index], item)
} else {
form.net.push(item)
}
})
data.uart.forEach((item, index) => {
if (form.uart[index]) {
Object.assign(form.uart[index], item)
} else {
form.uart.push(item)
}
})
result.value = '已加载当前设备配置'
} catch (error) {
result.value = error instanceof Error ? error.message : '加载设备配置失败'
}
})
async function save() { async function save() {
saving.value = true saving.value = true
try { try {

View File

@ -1,4 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ store: any; actions: any }>() defineProps<{ store: any; actions: any }>()
</script> </script>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from "vue" import { onMounted, reactive, ref } from "vue"
import { saveSystemConfig } from "../api/platform" import { fetchSystemConfig, saveSystemConfig } from "../api/platform"
import { ensureSaveAuthorized } from "../utils/saveGuard" import { ensureSaveAuthorized } from "../utils/saveGuard"
defineProps<{ store: any; actions: any }>() defineProps<{ store: any; actions: any }>()
@ -12,6 +12,16 @@ const form = reactive({
}) })
const result = ref("未保存") const result = ref("未保存")
onMounted(async () => {
try {
const data = await fetchSystemConfig()
Object.assign(form, data)
result.value = "已加载当前系统设置"
} catch (error) {
result.value = error instanceof Error ? error.message : "加载系统设置失败"
}
})
async function save() { async function save() {
const guard = await ensureSaveAuthorized() const guard = await ensureSaveAuthorized()
if (!guard.ok) { if (!guard.ok) {