feat: 围栏Tab布局重构、低精度过滤、蓝牙考勤去重、考勤删除API

- 围栏管理页面Tab移至顶部,设备绑定Tab隐藏地图全屏展示绑定矩阵
- 位置追踪新增"低精度"按钮,隐藏LBS/WiFi点(地图+折线+表格联动)
- 移除LBS/WiFi精度半径圆圈,仅通过标记颜色区分定位类型
- 蓝牙打卡(0xB2)自动创建考勤记录,含去重和WebSocket广播
- 新增考勤批量删除和单条删除API
- fence_checker补充json导入

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

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-03-30 09:41:55 +00:00
parent 891344bfa0
commit 3437cd24ea
5 changed files with 391 additions and 719 deletions

View File

@@ -2064,12 +2064,53 @@ class TCPManager:
beacon_major, beacon_minor, rssi,
beacon_battery or 0,
)
# Create AttendanceRecord for bluetooth punch
from app.services.fence_checker import _has_attendance_today
if not await _has_attendance_today(session, device_id, attendance_type):
device = await session.get(Device, device_id)
att_record = AttendanceRecord(
device_id=device_id,
imei=imei,
attendance_type=attendance_type,
attendance_source="bluetooth",
protocol_number=pkt["protocol"],
gps_positioned=False,
latitude=beacon_lat,
longitude=beacon_lon,
address=beacon_addr,
battery_level=device.battery_level if device else None,
gsm_signal=device.gsm_signal if device else None,
lbs_data={
"source": "bluetooth",
"beacon_mac": beacon_mac,
"beacon_name": beacon_cfg.name if beacon_cfg else None,
},
recorded_at=recorded_at,
)
session.add(att_record)
logger.info(
"BT attendance created: IMEI=%s type=%s beacon=%s",
imei, attendance_type, beacon_mac,
)
else:
logger.info(
"BT attendance dedup: IMEI=%s already has %s today",
imei, attendance_type,
)
# Broadcast bluetooth punch
ws_manager.broadcast_nonblocking("bluetooth", {
"imei": imei, "record_type": "punch",
"beacon_mac": beacon_mac, "attendance_type": attendance_type,
"recorded_at": str(recorded_at),
})
ws_manager.broadcast_nonblocking("attendance", {
"imei": imei, "attendance_type": attendance_type,
"latitude": beacon_lat, "longitude": beacon_lon,
"address": beacon_addr, "recorded_at": str(recorded_at),
"source": "bluetooth",
})
except Exception:
logger.exception("DB error storing BT punch for IMEI=%s", imei)