diff --git a/edc-web/app/models.py b/edc-web/app/models.py
index 511ebca..bb586bf 100644
--- a/edc-web/app/models.py
+++ b/edc-web/app/models.py
@@ -832,7 +832,8 @@ def delete_test_data(serial: str = "", date_from: str = "",
# ─── tb_device_log ─────────────────────────────────────────────────
def get_device_logs(page: int = 1, per_page: int = 30,
- serial: str = "", event_type: str = "") -> tuple[list[dict], int]:
+ serial: str = "", event_type: str = "",
+ date_from: str = "", date_to: str = "") -> tuple[list[dict], int]:
"""分页查询设备事件日志,返回 (records, total)"""
conn = get_conn()
try:
@@ -845,6 +846,12 @@ def get_device_logs(page: int = 1, per_page: int = 30,
if event_type:
where.append("event_type = %s")
params.append(event_type)
+ if date_from:
+ where.append("create_time >= %s")
+ params.append(date_from if len(date_from) > 10 else date_from)
+ if date_to:
+ where.append("create_time <= %s")
+ params.append(date_to if len(date_to) > 10 else date_to + " 23:59:59")
where_clause = " AND ".join(where) if where else "1=1"
@@ -865,6 +872,38 @@ def get_device_logs(page: int = 1, per_page: int = 30,
conn.close()
+def export_device_logs(serial: str = "", event_type: str = "",
+ date_from: str = "", date_to: str = "") -> list[dict]:
+ """导出全部设备事件日志(不分页)"""
+ conn = get_conn()
+ try:
+ with conn.cursor() as cur:
+ where = []
+ params = []
+ if serial:
+ where.append("device_serial LIKE %s")
+ params.append(f"%{serial}%")
+ if event_type:
+ where.append("event_type = %s")
+ params.append(event_type)
+ if date_from:
+ where.append("create_time >= %s")
+ params.append(date_from if len(date_from) > 10 else date_from)
+ if date_to:
+ where.append("create_time <= %s")
+ params.append(date_to if len(date_to) > 10 else date_to + " 23:59:59")
+
+ where_clause = " AND ".join(where) if where else "1=1"
+ cur.execute(
+ f"SELECT * FROM tb_device_log WHERE {where_clause} "
+ f"ORDER BY id DESC",
+ params,
+ )
+ return cur.fetchall()
+ finally:
+ conn.close()
+
+
def delete_device_logs(serial: str = "", event_type: str = "",
date_from: str = "", date_to: str = "") -> int:
"""删除符合条件的设备日志,返回删除行数。至少需要一个条件。"""
diff --git a/edc-web/app/routes/device_logs.py b/edc-web/app/routes/device_logs.py
index 20b1f75..71a9c01 100644
--- a/edc-web/app/routes/device_logs.py
+++ b/edc-web/app/routes/device_logs.py
@@ -1,8 +1,11 @@
"""设备事件日志 API"""
-from flask import Blueprint, jsonify, render_template, request
+import csv
+import io
+
+from flask import Blueprint, jsonify, render_template, request, Response
from flask_login import login_required, current_user
-from app.models import get_device_logs, delete_device_logs, insert_log
+from app.models import get_device_logs, export_device_logs, delete_device_logs, insert_log
bp = Blueprint("device_logs", __name__)
@@ -22,15 +25,49 @@ def api_device_logs():
per_page = request.args.get("per_page", 30, type=int)
serial = request.args.get("serial", "", type=str)
event_type = request.args.get("event_type", "", type=str)
+ date_from = request.args.get("date_from", "", type=str)
+ date_to = request.args.get("date_to", "", type=str)
records, total = get_device_logs(
page=page, per_page=per_page,
serial=serial, event_type=event_type,
+ date_from=date_from, date_to=date_to,
)
pages = max(1, (total + per_page - 1) // per_page)
return jsonify({"records": records, "total": total, "pages": pages})
+@bp.route("/api/device-logs/export")
+@login_required
+def api_export():
+ """导出设备事件日志为 CSV"""
+ serial = request.args.get("serial", "", type=str)
+ event_type = request.args.get("event_type", "", type=str)
+ date_from = request.args.get("date_from", "", type=str)
+ date_to = request.args.get("date_to", "", type=str)
+
+ records = export_device_logs(
+ serial=serial, event_type=event_type,
+ date_from=date_from, date_to=date_to,
+ )
+
+ output = io.StringIO()
+ writer = csv.writer(output)
+
+ if records:
+ headers = list(records[0].keys())
+ writer.writerow(headers)
+ for r in records:
+ writer.writerow(r.values())
+
+ output.seek(0)
+ return Response(
+ output.getvalue(),
+ mimetype="text/csv",
+ headers={"Content-Disposition": "attachment; filename=device_logs.csv"},
+ )
+
+
@bp.route("/api/device-logs/delete", methods=["POST"])
@login_required
def api_device_logs_delete():
diff --git a/edc-web/app/templates/device_logs.html b/edc-web/app/templates/device_logs.html
index c651cf2..4686df3 100644
--- a/edc-web/app/templates/device_logs.html
+++ b/edc-web/app/templates/device_logs.html
@@ -17,7 +17,16 @@
+
+
{% if current_user.role == 'admin' %}
{% endif %}
@@ -44,13 +53,25 @@