Files
desungongpai/app/routers/heartbeats.py
default 61c300bad8 feat: 告警/考勤/蓝牙/数据日志页面添加批量删除功能
- 新增 POST /api/alarms/batch-delete、/api/bluetooth/batch-delete、
  /api/heartbeats/batch-delete 批量删除端点 (最多500条)
- 四个页面表格添加全选复选框和"删除选中"按钮
- 提取通用 toggleAllCheckboxes/updateSelCount/_batchDelete 函数
- 数据日志页面根据当前查询类型自动路由到对应的批量删除API

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
2026-03-31 02:03:21 +00:00

118 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Heartbeats Router - 心跳数据接口
API endpoints for querying device heartbeat records.
"""
import math
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models import HeartbeatRecord
from app.schemas import (
APIResponse,
HeartbeatRecordResponse,
PaginatedList,
)
from app.services import device_service
router = APIRouter(prefix="/api/heartbeats", tags=["Heartbeats / 心跳数据"])
@router.get(
"",
response_model=APIResponse[PaginatedList[HeartbeatRecordResponse]],
summary="获取心跳记录列表 / List heartbeat records",
)
async def list_heartbeats(
device_id: int | None = Query(default=None, description="设备ID / Device ID"),
start_time: datetime | None = Query(default=None, description="开始时间 / Start time (ISO 8601)"),
end_time: datetime | None = Query(default=None, description="结束时间 / End time (ISO 8601)"),
page: int = Query(default=1, ge=1, description="页码 / Page number"),
page_size: int = Query(default=20, ge=1, le=100, description="每页数量 / Items per page"),
db: AsyncSession = Depends(get_db),
):
"""
获取心跳记录列表,支持按设备和时间范围过滤。
List heartbeat records with optional device and time range filters.
"""
query = select(HeartbeatRecord)
count_query = select(func.count(HeartbeatRecord.id))
if device_id is not None:
query = query.where(HeartbeatRecord.device_id == device_id)
count_query = count_query.where(HeartbeatRecord.device_id == device_id)
if start_time:
query = query.where(HeartbeatRecord.created_at >= start_time)
count_query = count_query.where(HeartbeatRecord.created_at >= start_time)
if end_time:
query = query.where(HeartbeatRecord.created_at <= end_time)
count_query = count_query.where(HeartbeatRecord.created_at <= end_time)
total_result = await db.execute(count_query)
total = total_result.scalar() or 0
offset = (page - 1) * page_size
query = query.order_by(HeartbeatRecord.created_at.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
records = list(result.scalars().all())
return APIResponse(
data=PaginatedList(
items=[HeartbeatRecordResponse.model_validate(r) for r in records],
total=total,
page=page,
page_size=page_size,
total_pages=math.ceil(total / page_size) if total else 0,
)
)
@router.post(
"/batch-delete",
response_model=APIResponse[dict],
summary="批量删除心跳记录 / Batch delete heartbeats",
)
async def batch_delete_heartbeats(
body: dict,
db: AsyncSession = Depends(get_db),
):
"""批量删除心跳记录最多500条。 / Batch delete heartbeat records (max 500)."""
record_ids = body.get("record_ids", [])
if not record_ids:
raise HTTPException(status_code=400, detail="record_ids is required")
if len(record_ids) > 500:
raise HTTPException(status_code=400, detail="Max 500 records per request")
result = await db.execute(
select(HeartbeatRecord).where(HeartbeatRecord.id.in_(record_ids))
)
records = list(result.scalars().all())
for r in records:
await db.delete(r)
await db.flush()
return APIResponse(data={"deleted": len(records)})
@router.get(
"/{heartbeat_id}",
response_model=APIResponse[HeartbeatRecordResponse],
summary="获取心跳详情 / Get heartbeat details",
)
async def get_heartbeat(heartbeat_id: int, db: AsyncSession = Depends(get_db)):
"""
按ID获取心跳记录详情。
Get heartbeat record details by ID.
"""
result = await db.execute(
select(HeartbeatRecord).where(HeartbeatRecord.id == heartbeat_id)
)
record = result.scalar_one_or_none()
if record is None:
raise HTTPException(status_code=404, detail=f"Heartbeat {heartbeat_id} not found")
return APIResponse(data=HeartbeatRecordResponse.model_validate(record))