From f64068475f106501ab649efdd48aafa491fc80bb Mon Sep 17 00:00:00 2001 From: ywb <347742090@qq.com> Date: Fri, 29 May 2026 13:37:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BF=90=E8=A1=8C=E6=97=B6?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agv_app/__pycache__/app.cpython-312.pyc | Bin 90938 -> 91918 bytes agv_app/app.py | 30 ++++++++- agv_app/static/css/style.css | 33 +++++++++ agv_app/static/js/running.js | 55 +++++++++++++-- agv_app/templates/running.html | 86 +++++++++++++++--------- agv_app/utils/mission_executor.py | 62 ++++++++++++++++- 6 files changed, 229 insertions(+), 37 deletions(-) diff --git a/agv_app/__pycache__/app.cpython-312.pyc b/agv_app/__pycache__/app.cpython-312.pyc index 3f184e9ddfd32d14acd848a430cb579308da6a25..08361e93fc1a452ab605b3152a2b298eb3dc72ba 100644 GIT binary patch delta 2246 zcmZ`(eNa?Y6u;*^-m=Sn0KzV?3(I~$d~BeEh@*m{##q{fESXRa0S^o##JdkE*^SX` z!f8vAsjI0YPFa*SLsK&^%bIaC^COD|9k(54s+pSpY6i7t`k1lKee9~)_;%lY=lp)> z>z;ew`}DN*$~ThbyxE*W@G5iUZ0f)9q2;IpbRYfd@Z1oxQ`B@j9FT^Bx*(C0m!MHF z86|@z=}}7Il2XLo`4!a3`fd0;3Ytn*lHHcwrVg>2bO;;lgoG@&*(Tqz+JFUwOhLDidLYHxJ)WWM`sw*_0?# zleCJIO7^lF3PmwIIr<~B4ULH5JEe?HaB-nkjCcn{xqkO5eU}|klB+F@gdpE{ReVe;`mDYgLrt}A5mZ#e~p6u@JjO+b6wEg9ST|a;ELWv<}e6lUj8aJTt z;u{guZK17O+j^*vD~2w*86$P{DzuF2`u4Vtr!+N-t4RT+vu|7Syr)|SL-^k&4Tj1F zLnQ{f4_W#KU*6(&QcqiZXAk8*bQ2CDyX=K#u(OH+*kNoyfJ#_Y$sBg90P5{RAB{;$ zPj^6x33!XNPYKLH-sNJJLMSl~qQiW22+Wb#@c{G2i&J5zoR& zch{J2+TDd=o{Q+6QE%a}ZZb7LqIX?Mw~Z*Hj%&nVunpH;vD){}AARf^(VM|GYW(J& zr9Tw;qRyg;`B8W2C+SgF$;6hZ$2YP5s!_MZHc~(7@I^B3oq+McgnOcHqH?@tDs$mg zNpH5zh!}9j#@;G|&5+3^s=&(1eUQQ`i=kop50$$w3&Nqb2X}8&*k#xGePLIl0)fQaO=;{C;XZ7iPwR7UE zw;I=T4vw5F@6XnKZCZfx1(CBCtem~ztSiIERE5;IIC*LTho)*Jzdm_d z;J~_GSg-NUtFBnTjR{Z?Wi`&vsVA{n9A1U-AGx z_H7kN>WxZh0b4u@ax{uvq~G!7Rn}Vt6^7Rly@2C>o&7Txid6Jk3%M^N*hKH{=1Xg9 z-%J&{5A80YGi7X`8ZJo(Q2$;&VSb7Idepj8fP%6L?_P{czGQQ2d7I6p7#%-26dpea)`X1tXovJQ_ zD?+vwS_@br&@sM04LQP&u7IjEErf=~X5-APfO}Hj#{e1ly$XCe`uF8UYRgLaTf(I4 z9)$C-gLzg%CA`hnt%l`T+`iRNFC9Y%ulmz!SR=wZwc=r9;B!@JfC6zj|7=ixyp*3S z&Gtn3jg+-efO*S+dO`L!HLDcxMAB*R0Y53#4H|IU? zbDs0O?>VQv^p$jLRWjW+8gmIgUeR{)+`mhv#p1m!p1#O1jd()i6aHWzHXIxr33}Ym z4UPI^o>+*UiiG_`!=59d!BEuW4?I8M4~ISP-6}4BAZT*b9|~b!vIa=P6gPH|d2Cc{;#-giO_DkgPf z15*k7CZ(xF{ZwK@7ak5OBf-G97Il7o_u?-fpJxv$p+vi(iA0CPtgH%rP{<~$;03LO z6jwgeRYPZ<6dfIjL^p&;G?I@n3si$kAHnJDMONgd$mXiy3D~3FtA+vr%o5dA(4)^zYYca6uFv>2?s@t1`o6nm~Xur^U0hhI$o!l1j zz?!oe?Rqgs*7w(QXnmj8g-c2&$vbr^1BX&(Nw(`!ghow!FV41UYeE3a%}nj!4QqoS1|e^PK>_9p4t!JO0VI5S*>h?qXICNH~_Ea&LOnM zhkA`IG=N2Q9|VtJWr$M#hoC-g!dOEueVs+T@QiR-NPhB>g?;CRUAYU$yUVlJGTDZ9 z$QR%WE9rnH!&NTQ%Ja8V^z2Lr=(N``^qZv4bint5Eqz$}2$w+LZJ1<#bweXuWi>Li!(}!mL#MQaM4kG93{Q*D zs!Gpd1a7K_d!a&X=Rau5M@#ukDW4+cBcgl=l(#wELCV`kd7~(=m-5R~egV3~t{;VQ vSW_$dLDcVh5}^1GkqCP>1@3P43#{xI6sSEzU>D&r7CHg7LcE^Mp8)zFIF(^K diff --git a/agv_app/app.py b/agv_app/app.py index fc9f86c..dd22d3f 100644 --- a/agv_app/app.py +++ b/agv_app/app.py @@ -1376,6 +1376,23 @@ def api_mission_state(): if r < len(grid) and c < len(grid[r]) and grid[r][c]: path.append((r, c)) + # 网格级别任务数据 + result["rows"] = rows + result["cols"] = cols + result["grid"] = grid + result["point_status"] = {f"{pr}_{c}": "pending" for pr in range(rows+1) for c in range(cols)} + result["machine_status"] = {} + for r in range(rows): + for c in range(cols): + if r < len(grid) and c < len(grid[r]) and grid[r][c]: + result["machine_status"][f"{r}_{c}"] = { + "has_machine": True, + "qr": "pending", "qr_val": None, + "front": "pending", "front_cnt": 0, + "back": "pending", "back_cnt": 0, + "status": "pending", "step": "等待", + } + # 保留旧的 tasks 列表(兼容) tlist = [] for (r, c) in path: tlist.append({ @@ -1390,15 +1407,26 @@ def api_mission_state(): }) result["tasks"] = tlist except Exception: + result["rows"] = 1 + result["cols"] = 1 + result["grid"] = [] + result["point_status"] = {} + result["machine_status"] = {} result["tasks"] = [] - # 错误弹窗状态 + # 错误弹窗状态和实时网格状态 if hasattr(MissionExecutorV3, "_instance") and MissionExecutorV3._instance: ex = MissionExecutorV3._instance st = ex.get_status() result["error_msg"] = st.get("error", "") result["waiting_step"] = (st.get("status") == "waiting_step") result["waiting_error"] = (st.get("status") == "waiting_error") + # 从 executor.report 读取实时点/机器状态 + rpt = ex.report + if rpt.get("point_status"): + result["point_status"] = rpt["point_status"] + if rpt.get("machine_status"): + result["machine_status"] = rpt["machine_status"] else: result["error_msg"] = "" result["waiting_step"] = False diff --git a/agv_app/static/css/style.css b/agv_app/static/css/style.css index a28ea53..d2e3b69 100644 --- a/agv_app/static/css/style.css +++ b/agv_app/static/css/style.css @@ -1043,3 +1043,36 @@ a:hover { text-decoration: underline; } font-size: 12px; color: #666; } + +/* ===== 运行页网格任务状态 ===== */ +/* 点位单元格 - 导航状态 */ +.point-cell { min-height: 52px; } +.cell-nav-icon { font-size: 18px; text-align: center; } +.cell-nav-label { font-size: 11px; color: #8899aa; text-align: center; margin-top: 2px; } +.point-cell.nav-pending { background: #141e28; border-color: #2a3a4a; } +.point-cell.nav-active { background: #1a3020; border-color: #4caf50; animation: navPulse 1.5s infinite; } +.point-cell.nav-done { background: #152522; border-color: #2e7d32; } +.point-cell.nav-done .cell-nav-icon { color: #4caf50; } +.point-cell.nav-skipped { background: #141e28; border-color: #2a3a4a; opacity: 0.5; } +@keyframes navPulse { 0%,100% { border-color: #4caf50; } 50% { border-color: #1b5e20; } } + +/* 机器单元格状态 */ +.machine-cell { min-height: 62px; padding: 6px 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; } +.machine-label { font-size: 12px; font-weight: 600; color: #ccc; } +.machine-steps-mini { display: flex; gap: 8px; font-size: 13px; } +.machine-qr-mini { font-size: 10px; color: #4fc3f7; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.empty-cell { color: #445566; font-size: 12px; } + +/* 步骤圆点 */ +.step-dot { opacity: 0.4; transition: opacity 0.3s; } +.step-dot.dot-done { opacity: 1; } +.step-dot.dot-scanning, +.step-dot.dot-shooting { opacity: 0.8; animation: dotPulse 0.8s infinite; } +@keyframes dotPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } } +.step-dot.dot-manual { opacity: 0.7; } +.step-dot.dot-skipped { opacity: 0.3; } + +/* 机器状态背景 */ +.machine-cell.mstatus-pending { background: #141e28; border-color: #2a3a4a; } +.machine-cell.mstatus-active { background: #1a2535; border-color: #4fc3f7; } +.machine-cell.mstatus-completed { background: #152522; border-color: #2e7d32; } diff --git a/agv_app/static/js/running.js b/agv_app/static/js/running.js index e3e1852..7757013 100644 --- a/agv_app/static/js/running.js +++ b/agv_app/static/js/running.js @@ -2,8 +2,6 @@ const { createApp } = Vue const API = '' createApp({ - delimiters: ['[[', ']]'], - data() { return { missionState: 'idle', @@ -15,7 +13,14 @@ createApp({ polling: null, logs: [], showQrModal: false, + qrSubmitting: false, qrValue: '', + // 网格任务显示 + missionRows: 0, + missionCols: 0, + missionGrid: [], + pointStatus: {}, + machineStatus: {}, // 错误弹窗 / 单步执行 waitingError: false, errorMsg: '', @@ -68,6 +73,13 @@ createApp({ this.progress = data.progress || 0 if (data.tasks) this.tasks = data.tasks + // 网格数据 + if (data.rows) this.missionRows = data.rows + if (data.cols) this.missionCols = data.cols + if (data.grid) this.missionGrid = data.grid + if (data.point_status) this.pointStatus = data.point_status + if (data.machine_status) this.machineStatus = data.machine_status + // 错误弹窗 if (data.waiting_error) { this.waitingError = true @@ -84,8 +96,11 @@ createApp({ this.waitingStep = false } - // QR 弹窗 - if (this.missionState === 'waiting_qr' && !this.showQrModal) { + // QR 弹窗(防止提交后重复弹出) + if (this.missionState !== 'waiting_qr') { + this.qrSubmitting = false + } + if (this.missionState === 'waiting_qr' && !this.showQrModal && !this.qrSubmitting) { this.showQrModal = true this.qrValue = '' } @@ -190,10 +205,12 @@ createApp({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ qr: val || ' ' }) }) + this.qrSubmitting = true this.showQrModal = false this.qrValue = '' }, cancelQr() { + this.qrSubmitting = true this.showQrModal = false this.qrValue = '' fetch(API + '/api/mission/manual-qr', { @@ -207,6 +224,36 @@ createApp({ }, onArmPreviewError(e) { e.target.style.display = 'none' + }, + // ===== 网格任务显示方法 ===== + getPointStatus(pr, c) { + return (this.pointStatus && this.pointStatus[pr + '_' + c]) || 'pending' + }, + navIcon(s) { + const m = { pending: '⏳', active: '🔄', done: '✅', skipped: '⏭️' } + return m[s] || '⏳' + }, + navLabel(s) { + const m = { pending: '等待', active: '导航中', done: '到达', skipped: '空位' } + return m[s] || '等待' + }, + hasMachine(r, c) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + return ms && ms.has_machine + }, + getMachineClass(r, c) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + if (!ms) return '' + const s = ms.status || 'pending' + return 'mstatus-' + s + }, + getMachineField(r, c, field) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + if (!ms) return '' + return ms[field] || '' } } }).mount('#app') \ No newline at end of file diff --git a/agv_app/templates/running.html b/agv_app/templates/running.html index 294122c..56e2644 100644 --- a/agv_app/templates/running.html +++ b/agv_app/templates/running.html @@ -4,7 +4,7 @@ 运行监控 - AGV 拍摄系统 - +
@@ -23,10 +23,10 @@
- [[ missionStateText ]] + {% raw %}{{ missionStateText }}{% endraw %}
- 进度 [[ Math.round(progress) ]]% + 进度 {% raw %}{{ Math.round(progress) }}{% endraw %}%
@@ -96,50 +96,74 @@
- -
-

📋 任务清单 ([[ tasks.length ]] 台机器)

-
-
-
[[ task.label ]]
-
- - 🔄 - - + +
+

📋 任务清单 ({% raw %}{{ missionRows }}{% endraw %}行×{% raw %}{{ missionCols }}{% endraw %}列,点位+机器网格)

+
+
+ +
+
第{% raw %}{{ c }}{% endraw %}列
+ + +
点位行 1
+
+
{% raw %}{{ navIcon(getPointStatus(0, c-1)) }}{% endraw %}
+
{% raw %}{{ navLabel(getPointStatus(0, c-1)) }}{% endraw %}
-
[[ task.step ]]
-
-
🏷 [[ task.qr_value.substring(0,8) ]]
-
- 📷 [[ task.photos_front ]]正 [[ task.photos_back ]]背 + + +
+
+ ⏳等待 🔄执行中 ✅完成 ⏭️跳过 ⚠️手动 🔍扫码 📸拍摄 +

📜 任务日志

-
[[ log ]]
+
{% raw %}{{ log }}{% endraw %}
等待任务开始...
@@ -163,9 +187,9 @@

📊 任务报告

-
✅ 完成: [[ report.completed ]]
-
❌ 失败: [[ report.failed ]]
-
总计: [[ report.total_points ]]
+
✅ 完成: {% raw %}{{ report.completed }}{% endraw %}
+
❌ 失败: {% raw %}{{ report.failed }}{% endraw %}
+
总计: {% raw %}{{ report.total_points }}{% endraw %}
@@ -186,7 +210,7 @@