From 14e9e4618e1e545f4798f7e897631988486830d4 Mon Sep 17 00:00:00 2001 From: tianzhendong <1203886034@qq.com> Date: Sun, 16 Aug 2026 19:26:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BAExcel=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=EF=BC=9A=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=8F=B0=E8=B4=A6=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=AF=8F=E6=9E=B6=E9=A3=9E=E8=A1=8C=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=EF=BC=8C=E6=96=B0=E5=A2=9E=E9=A3=9E=E8=A1=8C?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=B7=A5=E4=BD=9C=E8=A1=A8=EF=BC=88=E8=A1=8C?= =?UTF-8?q?=3D=E7=BC=96=E5=8F=B7=E3=80=81=E9=A6=96=E5=88=97=3D=E6=80=BB?= =?UTF-8?q?=E6=AC=A1=E6=95=B0=E3=80=81=E5=90=8E=E5=88=97=3D=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E9=A3=9E=E8=A1=8C=E8=AE=B0=E5=BD=95=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E5=BF=BD=E7=95=A5=E6=95=B0=E6=8D=AE=E5=BA=93=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 2 +- main.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0262f41..12de097 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ Thumbs.db # ===== 工具状态 / 临时文件 ===== .reasonix/ cmd_out.txt +uav.db.zip diff --git a/README.md b/README.md index 1776877..a93c151 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ BS 架构的无人机资产管理库:管理无人机基本信息(机型、 - **飞行透视表**:行=日期、列=编号,单元格=当日飞行次数,支持机型与日期范围筛选 - **按机型透视表**:行 = 日期、列 = 飞机编号、单元格 = 当日状态;无变更日期自动沿用前一状态;支持自定义日期范围(默认近 30 天) - **状态颜色区分**:待组装=灰、待调试=橙、待飞=绿、损坏=红 -- **手动导出 Excel**:点击「导出 Excel」按钮,下载 `无人机台账.xlsx`(含"无人机台账"与"状态历史"两个工作表),数据以点击时刻为准 +- **手动导出 Excel**:点击「导出 Excel」按钮,下载 `无人机台账.xlsx`;不含日期时导出「无人机台账(含每架飞行次数)+ 状态历史 + 飞行历史」,选择日期时导出「每日状态矩阵 + 状态变更明细 + 无人机台账 + 飞行历史」;飞行历史 sheet 行=飞机编号、首列=飞行次数总计、后列=每日飞行记录;数据以点击时刻为准 ## 技术栈 diff --git a/main.py b/main.py index 38b8b92..5f95b4c 100644 --- a/main.py +++ b/main.py @@ -251,6 +251,39 @@ def _current_status(conn: sqlite3.Connection, drone_id: int) -> Optional[dict]: # ---------------------------------------------------------------- Excel 台账 +def _flights_day_map(flight_rows) -> dict: + """将飞行记录行按 (drone_id -> {date: [摘要]}) 分组,同日多条保留。""" + day_map: dict = {} + for f in flight_rows: + parts = [] + if f["duration_min"]: + parts.append(f"{f['duration_min']}分钟") + if f["takeoff"] or f["landing"]: + parts.append(f"{f['takeoff']}→{f['landing']}") + if f["remark"]: + parts.append(f["remark"]) + summary = " ".join(parts) if parts else "飞行" + day_map.setdefault(f["drone_id"], {}).setdefault(f["flight_date"], []).append(summary) + return day_map + + +def _write_flight_history_sheet(ws, rows, dates, day_map) -> None: + """写入「飞行历史」sheet:行=飞机编号,首列=次数总计,后列=每日飞行记录。""" + ws.append(["飞机编号", "飞行次数总计"] + list(dates)) + for r in rows: + dm = day_map.get(r["id"], {}) + total = sum(len(v) for v in dm.values()) + row_vals = [r["serial_no"], total] + for ds in dates: + row_vals.append("\n".join(dm.get(ds, []))) + ws.append(row_vals) + # 每日单元格换行显示 + for row in ws.iter_rows(min_row=2, min_col=3): + for cell in row: + if cell.value: + cell.alignment = Alignment(vertical="center", wrap_text=True) + + def export_to_excel( models: Optional[List[str]] = None, start: Optional[str] = None, @@ -361,16 +394,31 @@ def export_to_excel( # ---- Sheet3: 无人机台账 ---- ws3 = wb.create_sheet("无人机台账") ws3.append(["机型", "编号", "链路ID", "飞控ID", "飞控版本", - "导航版本", "当前状态", "备注", "创建时间"]) + "导航版本", "当前状态", "飞行次数", "备注", "创建时间"]) for r in rows: + cnt = conn.execute( + "SELECT COUNT(*) FROM flight_records " + "WHERE drone_id = ? AND flight_date BETWEEN ? AND ?", + (r["id"], dates[0], dates[-1]), + ).fetchone()[0] ws3.append([ r["model"], r["serial_no"], r["link_id"], r["fc_id"], r["fc_version"], r["nav_version"], - r["cur_status"] or "", r["remark"], r["created_at"], + r["cur_status"] or "", cnt, r["remark"], r["created_at"], ]) - sheets = [ws, ws2, ws3] + # ---- Sheet4: 飞行历史(范围内) ---- + flight_rows = conn.execute( + "SELECT f.drone_id, f.flight_date, f.duration_min, f.takeoff, f.landing, f.remark " + "FROM flight_records f JOIN drones d ON d.id = f.drone_id " + f"{where} AND f.flight_date BETWEEN ? AND ? " + "ORDER BY f.drone_id, f.flight_date, f.id", + params + (dates[0], dates[-1]), + ).fetchall() + ws4 = wb.create_sheet("飞行历史") + _write_flight_history_sheet(ws4, rows, dates, _flights_day_map(flight_rows)) + sheets = [ws, ws2, ws3, ws4] else: - # ---- 无日期:台账 + 状态历史 ---- + # ---- 无日期:台账 + 状态历史 + 飞行历史 ---- statuses = conn.execute( "SELECT d.serial_no, d.model, s.status, s.remark, s.changed_at " "FROM drone_status s JOIN drones d ON d.id = s.drone_id " @@ -378,22 +426,47 @@ def export_to_excel( "ORDER BY s.changed_at, s.id", params, ).fetchall() + # 全部飞行记录(用于次数统计与飞行历史 sheet) + flight_rows = conn.execute( + "SELECT f.drone_id, f.flight_date, f.duration_min, f.takeoff, f.landing, f.remark " + "FROM flight_records f JOIN drones d ON d.id = f.drone_id " + f"{where} ORDER BY f.drone_id, f.flight_date, f.id", + params, + ).fetchall() + if flight_rows: + d0 = date.fromisoformat(min(f["flight_date"] for f in flight_rows)) + d1 = date.fromisoformat(max(f["flight_date"] for f in flight_rows)) + dates = [] + d = d0 + while d <= d1: + dates.append(d.isoformat()) + d += timedelta(days=1) + else: + dates = [] + # 每机每日飞行摘要 + day_map = _flights_day_map(flight_rows) ws = wb.active ws.title = "无人机台账" ws.append(["机型", "编号", "链路ID", "飞控ID", "飞控版本", - "导航版本", "当前状态", "备注", "创建时间"]) + "导航版本", "当前状态", "飞行次数", "备注", "创建时间"]) for r in rows: + cnt = conn.execute( + "SELECT COUNT(*) FROM flight_records WHERE drone_id = ?", + (r["id"],), + ).fetchone()[0] ws.append([ r["model"], r["serial_no"], r["link_id"], r["fc_id"], r["fc_version"], r["nav_version"], - r["cur_status"] or "", r["remark"], r["created_at"], + r["cur_status"] or "", cnt, r["remark"], r["created_at"], ]) ws2 = wb.create_sheet("状态历史") ws2.append(["编号", "机型", "状态", "备注", "变更时间"]) for s in statuses: ws2.append([s["serial_no"], s["model"], s["status"], s["remark"], s["changed_at"]]) - sheets = [ws, ws2] + ws4 = wb.create_sheet("飞行历史") + _write_flight_history_sheet(ws4, rows, dates, day_map) + sheets = [ws, ws2, ws4] # ---- 样式 ---- for sheet in sheets: